Files
l2-g30/src/views/l2/send/drive.vue
2026-01-02 17:15:26 +08:00

317 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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
v-for="setup in setups"
:key="setup.ID"
:xs="24"
:sm="12"
:md="8"
class="card-col"
>
<el-card class="parameter-card" shadow="hover">
<div slot="header" class="card-header">
<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>
<div class="header-right">
<el-button
type="primary"
size="mini"
icon="el-icon-s-promotion"
@click="handleSend(setup)"
:loading="setup.sending"
>
Send
</el-button>
</div>
</div>
<div class="card-body">
<!-- 可编辑表单 -->
<el-form :model="setup.params" label-position="top" size="mini">
<el-row :gutter="10">
<el-col
v-for="item in driveFields"
:key="item.key"
:span="12"
>
<el-form-item :label="item.label">
<el-input
v-model="setup.params[item.key]"
:placeholder="getPlaceholder(item.key)"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</el-card>
</el-col>
</el-row>
<div v-if="setups.length === 0 && !loading" class="empty-data">
<el-empty description="No Setup History Data"></el-empty>
</div>
</div>
</div>
</template>
<script>
// Import APIs / 引入接口
import { listSetup } from '@/api/business/setup'
import { createSendJob, executeSendJob } from '@/api/l2/sendJob'
import { getLastSuccess } from '@/api/l2/sendTemplate'
// Drive fields definition (English UI, Chinese comments) / 传动字段定义(英文界面,中文注释)
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' },
{ 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' }
]
// OPC address mapping / OPC点位映射
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'
}
export default {
name: 'DriveSend',
data() {
return {
loading: false,
lastSuccess: null,
setups: [], // Changed from 'plans' to 'setups'
driveFields: DRIVE_FIELDS,
driveAddress: { ...DRIVE_ADDRESS }
}
},
created() {
this.reload()
},
methods: {
async reload() {
this.loading = true
try {
// 1. Get last success for DRIVE
const lastRes = await getLastSuccess('DRIVE')
this.lastSuccess = lastRes && lastRes.code === 200 ? lastRes.data : null
// 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) || []
// 3. Map setup list to display data
this.setups = setupList.map(s => {
const params = {}
this.driveFields.forEach(f => {
const fromSetup = s ? s[f.key] : undefined
const fromLast = this.lastSuccess?.values?.[f.key]
// 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)
} else {
params[f.key] = ''
}
})
return {
...s,
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
}
this.setups.forEach(setup => {
this.driveFields.forEach(f => {
const v = this.lastSuccess.values[f.key]
if (v !== undefined) {
this.$set(setup.params, f.key, String(v))
}
})
})
this.$message.success('Last success values applied')
},
getPlaceholder(key) {
const v = this.lastSuccess?.values?.[key]
if (v !== undefined) return `Last: ${v}`
return 'Please enter'
},
formatTime(t) {
if (!t) return ''
return new Date(t).toLocaleString()
},
handleSend(setup) {
this.$confirm(
`Confirm to send parameters for Coil [${setup.coilid || '-'}]?`,
'Warning',
{
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'warning'
}
).then(() => this.doSend(setup)).catch(() => {})
},
async doSend(setup) {
setup.sending = true
try {
const items = this.driveFields.map(f => ({
paramCode: f.key,
address: this.driveAddress[f.key], // OPC address can be empty
valueRaw: String(setup.params[f.key] || ''),
setTime: new Date()
})).filter(it => !!it.address) // Filter out items without an address
if (!items.length) {
this.$message.warning('OPC addresses are not configured. Nothing to send.')
return
}
const dto = {
deviceName: 'CGL_LINE_1',
bizKey: setup.coilid, // Use coilid as business key
groups: [
{
groupNo: 1,
groupType: 'DRIVE',
groupName: `Drive Params for ${setup.coilid || ''}`,
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 {
setup.sending = false
}
}
}
}
</script>
<style scoped>
.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; }
.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; }
.last-send-time { font-size: 12px; color:#909399; margin-right:16px; }
.empty-data { margin-top: 20px; }
</style>