diff --git a/src/api/l2/sendTemplate.js b/src/api/l2/sendTemplate.js index d34bc82..b152e4d 100644 --- a/src/api/l2/sendTemplate.js +++ b/src/api/l2/sendTemplate.js @@ -34,3 +34,12 @@ export function updateSendTemplateItems(data) { data }) } + +// 批量保存模板明细(新增/更新/删除)- 仅提交变更,避免请求体过大 +export function batchSaveSendTemplateItems(data) { + return request({ + url: '/business/sendTemplate/items/batchSave', + method: 'put', + data + }) +} diff --git a/src/views/l2/send/components/FurnaceHistoryPanel.vue b/src/views/l2/send/components/FurnaceHistoryPanel.vue new file mode 100644 index 0000000..0ca3075 --- /dev/null +++ b/src/views/l2/send/components/FurnaceHistoryPanel.vue @@ -0,0 +1,138 @@ + + + + + + diff --git a/src/views/l2/send/drive.vue b/src/views/l2/send/drive.vue index f84a8ad..038f454 100644 --- a/src/views/l2/send/drive.vue +++ b/src/views/l2/send/drive.vue @@ -52,7 +52,7 @@
- + + + + @@ -90,34 +98,52 @@ import { getLastSuccess } from '@/api/l2/sendTemplate' // Drive fields definition (English UI, Chinese comments) / 传动字段定义(英文界面,中文注释) // 说明:key 必须与 setupForm 字段一致(来自 plan/components/setupForm.vue) +// Drive + Plan fields definition (show effect first; OPC address can be edited later) +// 传动 + 计划字段定义(先把效果做出来;OPC点位后续可协商配置) const DRIVE_FIELDS = [ - { key: 'porTension', label: 'Pay-off Reel Tension' }, - { key: 'celTension', label: 'Entry Loop Tension' }, - { key: 'cleanTension', label: 'Cleaning Section Tension' }, - { key: 'furTension', label: 'Furnace Zone Tension' }, - { key: 'towerTension', label: 'Cooling Tower Tension' }, - { key: 'tmNoneTension', label: 'TM No Tension' }, - { key: 'tmEntryTension', label: 'TM Entry Tension' }, - { key: 'tmExitTension', label: 'TM Exit Tension' }, - { key: 'tlNoneTension', label: 'TL No Tension' }, - { key: 'tlExitTension', label: 'TL Exit Tension' }, - { key: 'coatTension', label: 'Post-treatment Tension' }, - { key: 'cxlTension', label: 'Exit Loop Tension' }, - { key: 'trTension', label: 'Take-up Reel Tension' }, + // ---- Drive tension / 传动张力 ---- + { key: 'porTension', label: 'Pay-off Reel Tension', source: 'setup' }, + { key: 'celTension', label: 'Entry Loop Tension', source: 'setup' }, + { key: 'cleanTension', label: 'Cleaning Section Tension', source: 'setup' }, + { key: 'furTension', label: 'Furnace Zone Tension', source: 'setup' }, + { key: 'towerTension', label: 'Cooling Tower Tension', source: 'setup' }, + { key: 'tmNoneTension', label: 'TM No Tension', source: 'setup' }, + { key: 'tmEntryTension', label: 'TM Entry Tension', source: 'setup' }, + { key: 'tmExitTension', label: 'TM Exit Tension', source: 'setup' }, + { key: 'tlNoneTension', label: 'TL No Tension', source: 'setup' }, + { key: 'tlExitTension', label: 'TL Exit Tension', source: 'setup' }, + { key: 'coatTension', label: 'Post-treatment Tension', source: 'setup' }, + { key: 'cxlTension', label: 'Exit Loop Tension', source: 'setup' }, + { key: 'trTension', label: 'Take-up Reel Tension', source: 'setup' }, - { key: 'tlElong', label: 'TL Elongation' }, - { key: 'tlLvlMesh1', label: 'TL Leveling Roll Mesh 1' }, - { key: 'tlLvlMesh2', label: 'TL Leveling Roll Mesh 2' }, - { key: 'tlAcbMesh', label: 'TL Anti-crossbow Mesh' }, + // ---- TL / TM setup ---- + { key: 'tlElong', label: 'TL Elongation', source: 'setup' }, + { key: 'tlLvlMesh1', label: 'TL Leveling Roll Mesh 1', source: 'setup' }, + { key: 'tlLvlMesh2', label: 'TL Leveling Roll Mesh 2', source: 'setup' }, + { key: 'tlAcbMesh', label: 'TL Anti-crossbow Mesh', source: 'setup' }, - { key: 'tmBendforce', label: 'TM Bending Force' }, - { key: 'tmAcrMesh', label: 'TM Anti-crimping Roll Mesh' }, - { key: 'tmBrMesh', label: 'TM Anti-tremor Roll Mesh' }, - { key: 'tmRollforce', label: 'TM Roll Force' } + { key: 'tmBendforce', label: 'TM Bending Force', source: 'setup' }, + { key: 'tmAcrMesh', label: 'TM Anti-crimping Roll Mesh', source: 'setup' }, + { key: 'tmBrMesh', label: 'TM Anti-tremor Roll Mesh', source: 'setup' }, + { key: 'tmRollforce', label: 'TM Roll Force', source: 'setup' }, + + // ---- Plan (from listPlan response) / 计划参数(来自 listPlan 返回)---- + { key: 'entryWidth', label: 'Entry Width', source: 'plan' }, + { key: 'entryThick', label: 'Entry Thick', source: 'plan' }, + { key: 'entryWeight', label: 'Entry Weight', source: 'plan' }, + { key: 'entryLength', label: 'Entry Length', source: 'plan' }, + + { key: 'steelGrade', label: 'Steel Grade', source: 'plan' }, + + { key: 'spmElongation', label: 'SPM Elongation', source: 'plan' }, + { key: 'spmRollforce', label: 'SPM Roll Force', source: 'plan' }, + { key: 'spmBendingForce', label: 'SPM Bending Force', source: 'plan' }, + + { key: 'yieldPoint', label: 'Yield Point', source: 'plan' } ] -// OPC address mapping (must align with back-end OpcMessageIdsManager.pdiSetupIds) / OPC点位映射(需与后端一致) -// 中文注释:这里用“字段名->OPC地址”的方式直接组装发送items +// OPC address mapping / OPC点位映射 +// 说明:此处后续可协商配置;当前允许在页面上编辑(默认可为空) const DRIVE_ADDRESS = { porTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionPorBR1', celTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR3', @@ -151,7 +177,9 @@ export default { loading: false, lastSuccess: null, plans: [], - driveFields: DRIVE_FIELDS + driveFields: DRIVE_FIELDS, + // 可编辑的 OPC 点位(默认从常量拷贝;你也可以后续改成从后端/本地存储加载) + driveAddress: { ...DRIVE_ADDRESS } } }, created() { @@ -167,7 +195,8 @@ export default { // plans / 获取计划 const planRes = await listPlan({ status: 'NEW,READY,ONLINE,PRODUCING' }) - const planList = planRes.rows || [] + // 兼容后端返回结构:既可能是 {rows: []} 也可能是 {data: []} + const planList = (planRes && (planRes.rows || planRes.data)) || [] const tasks = planList.map(async (p) => { let setup = {} @@ -180,14 +209,27 @@ export default { const params = {} this.driveFields.forEach(f => { - const fromSetup = setup[f.key] + const fromPlan = p ? p[f.key] : undefined + const fromSetup = setup ? setup[f.key] : undefined const fromLast = this.lastSuccess?.values?.[f.key] - if (fromSetup !== undefined && fromSetup !== null && String(fromSetup) !== '') { - params[f.key] = String(fromSetup) - } else if (fromLast !== undefined && fromLast !== null) { - params[f.key] = String(fromLast) + + // 优先级:setup(如果字段来自setup) / plan(如果字段来自plan) -> lastSuccess -> '' + if (f.source === 'plan') { + if (fromPlan !== undefined && fromPlan !== null && String(fromPlan) !== '') { + params[f.key] = String(fromPlan) + } else if (fromLast !== undefined && fromLast !== null) { + params[f.key] = String(fromLast) + } else { + params[f.key] = '' + } } else { - params[f.key] = '' + if (fromSetup !== undefined && fromSetup !== null && String(fromSetup) !== '') { + params[f.key] = String(fromSetup) + } else if (fromLast !== undefined && fromLast !== null) { + params[f.key] = String(fromLast) + } else { + params[f.key] = '' + } } }) @@ -252,18 +294,24 @@ export default { try { const items = this.driveFields.map(f => ({ paramCode: f.key, - address: DRIVE_ADDRESS[f.key], + // OPC点位允许为空:为空则本次不发送该字段(先做效果,后续再配置) + address: this.driveAddress[f.key], valueRaw: String(plan.params[f.key] || ''), setTime: new Date() })).filter(it => !!it.address) + if (!items.length) { + this.$message.warning('OPC点位为空:当前没有可发送的字段(请先在输入框里填写点位)') + return + } + const dto = { deviceName: 'CGL_LINE_1', groups: [ { groupNo: 1, groupType: 'DRIVE', - groupName: `Drive Params for ${plan.steelGrade || ''}`, + groupName: `Drive/Plan Params for ${plan.steelGrade || ''}`, items } ] diff --git a/src/views/l2/send/furnace.vue b/src/views/l2/send/furnace.vue index 98d6632..1060242 100644 --- a/src/views/l2/send/furnace.vue +++ b/src/views/l2/send/furnace.vue @@ -36,8 +36,31 @@ > Apply Last Success Values + + + History +
+ + + + +
@@ -62,27 +85,54 @@ - - - +
- - +
+ {{ group.groupTitle }} + ({{ group.items.length }} items) +
- -
- Address: - -
-
- - + + + + + + + +
+ + Last Success: {{ getLastValue(item) }} + + + Default: {{ getDefaultValue(item) }} + + +Modified + +
+ + +
+ Address: + +
+
+
+
+
+
@@ -96,10 +146,18 @@