Files
l2-g30/src/views/l2/send/drive.vue

317 lines
11 KiB
Vue
Raw Normal View History

2025-12-31 18:02:35 +08:00
<template>
<div class="app-container">
<!-- Toolbar / 工具栏 -->
<div class="toolbar">
<el-button @click="reload" icon="el-icon-refresh" size="small" :loading="loading">
Refresh
</el-button>
<el-button
v-if="lastSuccess && lastSuccess.lastSendTime"
type="primary"
plain
icon="el-icon-magic-stick"
size="small"
@click="applyLastSuccessValues"
>
Apply Last Success Values
</el-button>
</div>
<!-- Cards / 卡片 -->
<div v-loading="loading" class="card-grid-container">
<el-row :gutter="20">
<el-col
2026-01-02 17:15:26 +08:00
v-for="setup in setups"
:key="setup.ID"
2025-12-31 18:02:35 +08:00
:xs="24"
:sm="12"
:md="8"
class="card-col"
>
<el-card class="parameter-card" shadow="hover">
<div slot="header" class="card-header">
2026-01-02 17:15:26 +08:00
<div class="card-header-content">
<!-- 头部信息参考 setup/panels 卡片表头多字段拼接的风格 -->
<div class="card-title-row">
<span class="card-title">
Plan ID: {{ setup.planid || '-' }}
| Coil ID: {{ setup.coilid || '-' }}
| Steel Grade: {{ setup.steelGrade || setup.grade || '-' }}
</span>
</div>
<div class="card-subtitle">
<span>Entry Thickness: {{ setup.entryThick || '-' }}</span>
<span>Entry Width: {{ setup.entryWidth || '-' }}</span>
<span>Entry Weight: {{ setup.entryWeight || '-' }}</span>
<span>Entry Length: {{ setup.entryLength || '-' }}</span>
</div>
<div class="card-subtitle">
<span>TL Elongation: {{ setup.tlElong || '-' }}</span>
<span>TM Roll Force: {{ setup.tmRollforce || '-' }}</span>
<span>TM Bending Force: {{ setup.tmBendforce || '-' }}</span>
<span v-if="setup.updateTime">Updated: {{ formatTime(setup.updateTime) }}</span>
</div>
</div>
2025-12-31 18:02:35 +08:00
<div class="header-right">
<el-button
type="primary"
size="mini"
icon="el-icon-s-promotion"
2026-01-02 17:15:26 +08:00
@click="handleSend(setup)"
:loading="setup.sending"
2025-12-31 18:02:35 +08:00
>
Send
</el-button>
</div>
</div>
<div class="card-body">
2026-01-02 17:15:26 +08:00
<!-- 可编辑表单 -->
<el-form :model="setup.params" label-position="top" size="mini">
2025-12-31 18:02:35 +08:00
<el-row :gutter="10">
<el-col
2025-12-31 20:05:29 +08:00
v-for="item in driveFields"
:key="item.key"
2025-12-31 18:02:35 +08:00
:span="12"
>
2025-12-31 20:05:29 +08:00
<el-form-item :label="item.label">
2025-12-31 18:02:35 +08:00
<el-input
2026-01-02 17:15:26 +08:00
v-model="setup.params[item.key]"
2025-12-31 20:05:29 +08:00
:placeholder="getPlaceholder(item.key)"
2025-12-31 18:02:35 +08:00
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</el-card>
</el-col>
</el-row>
2026-01-02 17:15:26 +08:00
<div v-if="setups.length === 0 && !loading" class="empty-data">
<el-empty description="No Setup History Data"></el-empty>
2025-12-31 18:02:35 +08:00
</div>
</div>
</div>
</template>
<script>
// Import APIs / 引入接口
import { listSetup } from '@/api/business/setup'
import { createSendJob, executeSendJob } from '@/api/l2/sendJob'
2025-12-31 20:05:29 +08:00
import { getLastSuccess } from '@/api/l2/sendTemplate'
// Drive fields definition (English UI, Chinese comments) / 传动字段定义(英文界面,中文注释)
const DRIVE_FIELDS = [
2026-01-02 17:15:26 +08:00
{ 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' },
{ 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' },
{ 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' }
2025-12-31 20:05:29 +08:00
]
2026-01-02 16:00:10 +08:00
// OPC address mapping / OPC点位映射
2025-12-31 20:05:29 +08:00
const DRIVE_ADDRESS = {
porTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionPorBR1',
celTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR3',
cleanTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR1BR2',
furTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionFur1',
towerTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionFur2',
tmNoneTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR5BR6',
tmEntryTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR5TM',
tmExitTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionTMBR6',
tlNoneTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR6BR7',
tlExitTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionTLBR7',
coatTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR7BR8',
cxlTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR8BR9',
trTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR9TR',
tlElong: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.TLElongation',
tlLvlMesh1: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.LevelingMesh1',
tlLvlMesh2: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.LevelingMesh2',
tlAcbMesh: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.AntiCrossBowUnitMesh',
tmBendforce: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.TMBendforce',
tmAcrMesh: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.ACRMesh',
tmBrMesh: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.BRMesh',
tmRollforce: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.TMRollforce'
}
2025-12-31 18:02:35 +08:00
export default {
name: 'DriveSend',
data() {
return {
2025-12-31 20:05:29 +08:00
loading: false,
lastSuccess: null,
2026-01-02 17:15:26 +08:00
setups: [], // Changed from 'plans' to 'setups'
2026-01-02 16:00:10 +08:00
driveFields: DRIVE_FIELDS,
driveAddress: { ...DRIVE_ADDRESS }
2025-12-31 18:02:35 +08:00
}
},
created() {
this.reload()
},
methods: {
async reload() {
this.loading = true
try {
2026-01-02 17:15:26 +08:00
// 1. Get last success for DRIVE
2025-12-31 20:05:29 +08:00
const lastRes = await getLastSuccess('DRIVE')
this.lastSuccess = lastRes && lastRes.code === 200 ? lastRes.data : null
2025-12-31 18:02:35 +08:00
2026-01-02 17:15:26 +08:00
// 2. Get setup history list (instead of plans)
const setupRes = await listSetup({ pageNum: 1, pageSize: 20 }) // Fetch latest 20 for example
const setupList = (setupRes && setupRes.rows) || []
2025-12-31 18:02:35 +08:00
2026-01-02 17:15:26 +08:00
// 3. Map setup list to display data
this.setups = setupList.map(s => {
2025-12-31 18:02:35 +08:00
const params = {}
2025-12-31 20:05:29 +08:00
this.driveFields.forEach(f => {
2026-01-02 17:15:26 +08:00
const fromSetup = s ? s[f.key] : undefined
2025-12-31 20:05:29 +08:00
const fromLast = this.lastSuccess?.values?.[f.key]
2026-01-02 16:00:10 +08:00
2026-01-02 17:15:26 +08:00
// Priority: current setup value > last success value > ''
if (fromSetup !== undefined && fromSetup !== null && String(fromSetup) !== '') {
params[f.key] = String(fromSetup)
} else if (fromLast !== undefined && fromLast !== null) {
params[f.key] = String(fromLast)
2025-12-31 18:02:35 +08:00
} else {
2026-01-02 17:15:26 +08:00
params[f.key] = ''
2025-12-31 18:02:35 +08:00
}
})
return {
2026-01-02 17:15:26 +08:00
...s,
2025-12-31 18:02:35 +08:00
params,
sending: false
}
})
} catch (e) {
console.error(e)
this.$message.error('Load failed')
} finally {
this.loading = false
}
},
applyLastSuccessValues() {
if (!this.lastSuccess || !this.lastSuccess.values) {
this.$message.info('No last success data')
return
}
2026-01-02 17:15:26 +08:00
this.setups.forEach(setup => {
2025-12-31 20:05:29 +08:00
this.driveFields.forEach(f => {
const v = this.lastSuccess.values[f.key]
2025-12-31 18:02:35 +08:00
if (v !== undefined) {
2026-01-02 17:15:26 +08:00
this.$set(setup.params, f.key, String(v))
2025-12-31 18:02:35 +08:00
}
})
})
this.$message.success('Last success values applied')
},
2025-12-31 20:05:29 +08:00
getPlaceholder(key) {
const v = this.lastSuccess?.values?.[key]
if (v !== undefined) return `Last: ${v}`
2025-12-31 18:02:35 +08:00
return 'Please enter'
},
formatTime(t) {
if (!t) return ''
2025-12-31 20:05:29 +08:00
return new Date(t).toLocaleString()
2025-12-31 18:02:35 +08:00
},
2026-01-02 17:15:26 +08:00
handleSend(setup) {
2025-12-31 18:02:35 +08:00
this.$confirm(
2026-01-02 17:15:26 +08:00
`Confirm to send parameters for Coil [${setup.coilid || '-'}]?`,
2025-12-31 18:02:35 +08:00
'Warning',
{
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'warning'
}
2026-01-02 17:15:26 +08:00
).then(() => this.doSend(setup)).catch(() => {})
2025-12-31 18:02:35 +08:00
},
2026-01-02 17:15:26 +08:00
async doSend(setup) {
setup.sending = true
2025-12-31 18:02:35 +08:00
try {
2025-12-31 20:05:29 +08:00
const items = this.driveFields.map(f => ({
paramCode: f.key,
2026-01-02 17:15:26 +08:00
address: this.driveAddress[f.key], // OPC address can be empty
valueRaw: String(setup.params[f.key] || ''),
2025-12-31 18:02:35 +08:00
setTime: new Date()
2026-01-02 17:15:26 +08:00
})).filter(it => !!it.address) // Filter out items without an address
2025-12-31 18:02:35 +08:00
2026-01-02 16:00:10 +08:00
if (!items.length) {
2026-01-02 17:15:26 +08:00
this.$message.warning('OPC addresses are not configured. Nothing to send.')
2026-01-02 16:00:10 +08:00
return
}
2025-12-31 18:02:35 +08:00
const dto = {
2025-12-31 20:05:29 +08:00
deviceName: 'CGL_LINE_1',
2026-01-02 17:15:26 +08:00
bizKey: setup.coilid, // Use coilid as business key
2025-12-31 18:02:35 +08:00
groups: [
{
groupNo: 1,
groupType: 'DRIVE',
2026-01-02 17:15:26 +08:00
groupName: `Drive Params for ${setup.coilid || ''}`,
2025-12-31 18:02:35 +08:00
items
}
]
}
const createRes = await createSendJob(dto)
const jobId = createRes.data
if (!jobId) throw new Error('Create send job failed')
await executeSendJob(jobId)
this.$message.success('Send success')
await this.reload()
} catch (e) {
console.error(e)
this.$message.error(e.message || 'Send failed')
} finally {
2026-01-02 17:15:26 +08:00
setup.sending = false
2025-12-31 18:02:35 +08:00
}
}
}
}
</script>
<style scoped>
2025-12-31 20:05:29 +08:00
.toolbar { margin-bottom: 20px; display:flex; flex-wrap:wrap; gap:8px; align-items:center; }
.card-grid-container { min-height: 300px; }
.card-col { margin-bottom: 20px; }
.parameter-card .card-header { display:flex; justify-content:space-between; align-items:center; }
2026-01-02 17:15:26 +08:00
.card-header-content { flex-grow: 1; }
.card-title-row { margin-bottom: 4px; }
.card-title { font-weight: 600; font-size: 16px; }
.card-subtitle { font-size: 12px; color: #909399; display: flex; gap: 12px; }
.header-right { flex-shrink: 0; margin-left: 16px; }
2025-12-31 20:05:29 +08:00
.last-send-time { font-size: 12px; color:#909399; margin-right:16px; }
.empty-data { margin-top: 20px; }
2025-12-31 18:02:35 +08:00
</style>