1完成酸轧轧辊调整

2完成双机架工艺规格串联
3完成双机架计划串联
4完成双机架wip快捷录入检索
5完成双机架实绩串联
This commit is contained in:
2026-05-19 17:13:37 +08:00
parent 417783e64a
commit 53a180787b
46 changed files with 5592 additions and 231 deletions

View File

@@ -65,7 +65,7 @@
/>
</el-select>
</template>
<span class="search-label">点位名称</span>
<span class="search-label">参数名称</span>
<el-input
v-model="filterName"
placeholder="请输入"
@@ -80,8 +80,13 @@
size="mini"
type="primary"
icon="el-icon-plus"
@click="openParamDialog()"
>新建参数</el-button>
<el-button
size="mini"
icon="el-icon-setting"
@click="openPlanDialog()"
>新建方案点位</el-button>
>管理点位</el-button>
<el-button
size="mini"
type="success"
@@ -90,134 +95,42 @@
>模板导入</el-button>
</div>
<!-- 方案点位 -->
<!-- 参数平铺 -->
<el-table
v-loading="planLoading"
:data="filteredPlans"
v-loading="planLoading || allParamLoading"
:data="filteredFlatRows"
size="small"
highlight-current-row
@current-change="onPlanSelect"
border
>
<el-table-column label="序号" type="index" align="center" />
<el-table-column label="父级名称" show-overflow-tooltip>
<template slot-scope="{ row }">{{ segLabel(row.segmentType) }} {{ row.segmentName || '—' }}</template>
</el-table-column>
<el-table-column label="点位名称" prop="pointName" show-overflow-tooltip />
<el-table-column label="点位编码" prop="pointCode" show-overflow-tooltip />
<el-table-column label="操作" align="right">
<el-table-column label="" width="68" align="center" fixed>
<template slot-scope="{ row }">
<el-button type="text" size="mini" @click.stop="openPlanDialog(row)">编辑</el-button>
<el-button type="text" size="mini" @click.stop="openParamDialog(row)">参数</el-button>
<el-button type="text" size="mini" class="btn-danger" @click.stop="removePlan(row)">删除</el-button>
<span :class="['seg-chip', 'seg-' + (row._segmentType || '').toLowerCase()]">{{ row._segmentLabel }}</span>
</template>
</el-table-column>
<el-table-column label="参数名称" prop="paramName" min-width="130" show-overflow-tooltip />
<el-table-column label="编码" prop="paramCode" width="115" show-overflow-tooltip />
<el-table-column label="单位" prop="unit" align="center" width="62" />
<el-table-column label="设定值" prop="targetValue" align="right" width="82" />
<el-table-column label="下限" prop="lowerLimit" align="right" width="72" />
<el-table-column label="上限" prop="upperLimit" align="right" width="72" />
<el-table-column label="实际状态" align="center" width="90">
<template slot-scope="{ row }">
<span v-if="paramAnomalyMap[row.paramCode]" class="anomaly-badge">
<i class="el-icon-warning-outline" /> 异常
</span>
<span v-else-if="row.upperLimit != null || row.lowerLimit != null" class="normal-badge">
<i class="el-icon-circle-check" /> 正常
</span>
<span v-else class="no-data-badge"></span>
</template>
</el-table-column>
<el-table-column label="操作" align="right" width="100" fixed="right">
<template slot-scope="{ row }">
<el-button type="text" size="mini" @click="editParamFromFlat(row)">编辑</el-button>
<el-button type="text" size="mini" class="btn-danger" @click="deleteParamFromFlat(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 方案参数面板 -->
<template v-if="selectedPlan">
<div class="param-header">
<span>{{ selectedPlan.pointName || selectedPlan.pointCode }} 参数</span>
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openParamDialog()">新建参数</el-button>
</div>
<el-table v-loading="paramLoading" :data="paramList" size="small">
<el-table-column label="参数编码" prop="paramCode" width="110" show-overflow-tooltip />
<el-table-column label="参数名称" prop="paramName" show-overflow-tooltip />
<el-table-column label="实际值ID" prop="actualSrcId" width="140" show-overflow-tooltip>
<template slot-scope="{ row }">{{ row.actualSrcId || '—' }}</template>
</el-table-column>
<el-table-column label="L1设定值ID" prop="presetSrcId" width="140" show-overflow-tooltip>
<template slot-scope="{ row }">{{ row.presetSrcId || '—' }}</template>
</el-table-column>
<el-table-column label="设定值" prop="targetValue" align="right" width="80" />
<el-table-column label="最小值" prop="lowerLimit" align="right" width="80" />
<el-table-column label="最大值" prop="upperLimit" align="right" width="80" />
<el-table-column label="单位" prop="unit" align="center" width="60" />
<el-table-column label="更新时间" align="center" width="136">
<template slot-scope="{ row }">{{ (row.updateTime || row.createTime || '').substring(0, 16) || '—' }}</template>
</el-table-column>
<el-table-column label="实际状态" align="center" width="90">
<template slot-scope="{ row }">
<span v-if="paramAnomalyMap[row.paramCode]" class="anomaly-badge">
<i class="el-icon-warning-outline" /> 异常
</span>
<span v-else-if="row.upperLimit != null || row.lowerLimit != null" class="normal-badge">
<i class="el-icon-circle-check" /> 正常
</span>
<span v-else class="no-data-badge"></span>
</template>
</el-table-column>
<el-table-column label="操作" align="right">
<template slot-scope="{ row }">
<el-button type="text" size="mini" @click="openParamDialog(null, row)">编辑</el-button>
<el-button type="text" size="mini" class="btn-danger" @click="removeParam(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 偏差分析区块 -->
<template v-if="planAnomalies.length">
<div class="anomaly-section-header">
<i class="el-icon-warning" style="color:#E6A23C;margin-right:4px" />
实际生产偏差分析
<el-tag type="warning" size="mini" effect="plain" style="margin-left:8px">{{ planAnomalies.length }} 项异常</el-tag>
<el-button type="text" size="mini" style="margin-left:auto" @click="anomalyExpanded = !anomalyExpanded">
{{ anomalyExpanded ? '收起' : '展开' }}
</el-button>
</div>
<div v-show="anomalyExpanded">
<el-table :data="planAnomalies" size="small" border>
<el-table-column label="参数" prop="paramName" width="110" show-overflow-tooltip />
<el-table-column label="规程设定值" align="right" width="96">
<template slot-scope="{ row }">{{ row.storedTarget != null ? row.storedTarget : '—' }}</template>
</el-table-column>
<el-table-column label="规程最大值" align="right" width="96">
<template slot-scope="{ row }">{{ row.storedUpper != null ? row.storedUpper : '—' }}</template>
</el-table-column>
<el-table-column label="规程最小值" align="right" width="96">
<template slot-scope="{ row }">{{ row.storedLower != null ? row.storedLower : '—' }}</template>
</el-table-column>
<el-table-column label="实际最大值" align="right" width="96">
<template slot-scope="{ row }">
<span :class="(row.anomalyType === 'OVER_MAX' || row.anomalyType === 'BOTH') ? 'val-over' : ''">
{{ row.actualMax != null ? row.actualMax : '—' }}
</span>
</template>
</el-table-column>
<el-table-column label="实际最小值" align="right" width="96">
<template slot-scope="{ row }">
<span :class="(row.anomalyType === 'UNDER_MIN' || row.anomalyType === 'BOTH') ? 'val-under' : ''">
{{ row.actualMin != null ? row.actualMin : '—' }}
</span>
</template>
</el-table-column>
<el-table-column label="最大偏差" align="right" width="88">
<template slot-scope="{ row }">
<span v-if="row.deviationMax != null" class="val-over">+{{ row.deviationMax }}</span>
<span v-else></span>
</template>
</el-table-column>
<el-table-column label="最小偏差" align="right" width="88">
<template slot-scope="{ row }">
<span v-if="row.deviationMin != null" class="val-under">{{ row.deviationMin }}</span>
<span v-else></span>
</template>
</el-table-column>
<el-table-column label="来源钢卷" prop="coilId" show-overflow-tooltip width="120" />
<el-table-column label="检测时间" prop="detectedAt" width="140" show-overflow-tooltip>
<template slot-scope="{ row }">{{ (row.detectedAt || '').substring(0, 16) || '—' }}</template>
</el-table-column>
<el-table-column label="异常类型" align="center" width="120">
<template slot-scope="{ row }">
<el-tag v-if="row.anomalyType === 'OVER_MAX' || row.anomalyType === 'BOTH'" size="mini" type="danger" style="margin:1px">超上限</el-tag>
<el-tag v-if="row.anomalyType === 'UNDER_MIN' || row.anomalyType === 'BOTH'" size="mini" type="warning" style="margin:1px">低于下限</el-tag>
</template>
</el-table-column>
</el-table>
<!-- 对比图 -->
<div ref="anomalyChart" class="anomaly-chart" />
</div>
</template>
</template>
</div>
</div>
@@ -361,6 +274,16 @@
<!-- 方案参数 dialog -->
<el-dialog :title="paramTitle" :visible.sync="paramOpen" width="480px" append-to-body @close="paramForm = {}">
<el-form ref="paramFormRef" :model="paramForm" :rules="paramRules" label-width="90px" size="small">
<el-form-item v-if="!paramForm.paramId" label="所属点位" prop="planId">
<el-select v-model="paramForm.planId" style="width:100%" placeholder="请选择点位">
<el-option
v-for="plan in planList"
:key="plan.planId"
:label="segLabel(plan.segmentType) + ' ' + plan.pointName"
:value="plan.planId"
/>
</el-select>
</el-form-item>
<el-form-item label="参数编码" prop="paramCode">
<el-input v-model="paramForm.paramCode" maxlength="64" />
</el-form-item>
@@ -488,7 +411,7 @@ const TEMPLATE_HEADERS = ['段类型', '段名称', '点位名称', '参数名
/** 表头字段映射 */
const HEADER_MAP = {
'段类型': 'segmentType',
'段名称': 'segmentName',
'段名称': 'segmentName',
'点位名称': 'pointName',
'参数名称': 'paramName',
'设定值': 'targetValue',
@@ -514,9 +437,8 @@ export default {
appliedFilterName: '',
planList: [],
planLoading: false,
selectedPlan: null,
paramList: [],
paramLoading: false,
allParamList: [],
allParamLoading: false,
planOpen: false,
planTitle: '',
planSubmitLoading: false,
@@ -531,6 +453,7 @@ export default {
paramSubmitLoading: false,
paramForm: {},
paramRules: {
planId: [{ required: true, message: '请选择所属点位', trigger: 'change' }],
paramCode: [{ required: true, message: '参数编码不能为空', trigger: 'blur' }],
paramName: [{ required: true, message: '参数名称不能为空', trigger: 'blur' }]
},
@@ -632,12 +555,6 @@ export default {
extra.sort((a, b) => String(a.label).localeCompare(String(b.label), 'zh-CN'))
return [...SEGMENT_FORM_OPTIONS, ...extra]
},
/** 当前选中点位下的异常条目 */
planAnomalies() {
if (!this.selectedPlan) return []
return this.allAnomalies.filter(a => a.paramCode === this.selectedPlan.pointCode ||
this.paramList.some(p => p.paramCode === a.paramCode))
},
/** paramCode → anomaly 的快速索引(用于状态列) */
paramAnomalyMap() {
const map = {}
@@ -648,45 +565,42 @@ export default {
if (!this.coilAnomalyCoilId) return []
return this.allAnomalies.filter(a => a.coilId === this.coilAnomalyCoilId)
},
filteredPlans() {
/** 所有 plan 的参数平铺成一行,带 plan 的段/点位信息 */
flatRows() {
const SEG_LABELS = { INLET: '入口段', PROCESS: '工艺段', OUTLET: '出口段' }
const planMap = {}
for (const plan of this.planList) planMap[plan.planId] = plan
const rows = []
for (const param of this.allParamList) {
const plan = planMap[param.planId]
if (!plan) continue
rows.push({
...param,
_planId: plan.planId,
_segmentType: plan.segmentType,
_segmentLabel: SEG_LABELS[plan.segmentType] || plan.segmentName || plan.segmentType,
_pointName: plan.pointName,
_sortOrder: plan.sortOrder || 0
})
}
rows.sort((a, b) => {
const s = (a._sortOrder || 0) - (b._sortOrder || 0)
return s !== 0 ? s : (a.paramId || 0) - (b.paramId || 0)
})
return rows
},
filteredFlatRows() {
const type = this.activeSegmentType
const sub = this.activeSegmentName
return this.planList.filter(p => {
const typeOk =
type === '' || type === undefined || type === null
? true
: String(p.segmentType) === String(type)
let nameOkSeg = true
if (typeOk && type !== '' && type !== undefined && type !== null) {
if (sub === '' || sub === undefined || sub === null) {
nameOkSeg = true
} else if (sub === '__EMPTY__') {
const sn = p.segmentName != null ? String(p.segmentName).trim() : ''
nameOkSeg = !sn
} else {
const sn = p.segmentName != null ? String(p.segmentName).trim() : ''
nameOkSeg = sn === String(sub).trim()
}
}
const nameOk = !this.appliedFilterName || (p.pointName || '').includes(this.appliedFilterName)
return typeOk && nameOkSeg && nameOk
const name = this.appliedFilterName
return this.flatRows.filter(r => {
const typeOk = !type || String(r._segmentType) === String(type)
const nameOk = !name || (r.paramName || '').includes(name) || (r.paramCode || '').includes(name)
return typeOk && nameOk
})
}
},
watch: {
$route: { immediate: true, handler() { this.syncFromRoute() } },
planAnomalies: {
handler(list) {
if (list.length && this.anomalyExpanded) {
this.$nextTick(() => this.renderAnomalyChart())
}
}
},
anomalyExpanded(val) {
if (val && this.planAnomalies.length) {
this.$nextTick(() => this.renderAnomalyChart())
}
},
segmentTypeTabOptions: {
handler() {
const a = this.activeSegmentType
@@ -852,22 +766,26 @@ export default {
},
loadPlans() {
this.planLoading = true
this.selectedPlan = null
this.paramList = []
listProcessPlan({ versionId: this.versionId, pageNum: 1, pageSize: 500 }).then(res => {
this.planList = res.rows || []
this.loadAllParams()
}).catch(e => console.error(e)).finally(() => { this.planLoading = false })
},
loadParams(planId) {
this.paramLoading = true
listProcessPlanParam({ planId, pageNum: 1, pageSize: 500 }).then(res => {
this.paramList = res.rows || []
}).catch(e => console.error(e)).finally(() => { this.paramLoading = false })
},
onPlanSelect(row) {
if (!row) return
this.selectedPlan = row
this.loadParams(row.planId)
async loadAllParams() {
if (!this.planList.length) { this.allParamList = []; return }
this.allParamLoading = true
try {
const results = await Promise.all(
this.planList.map(plan =>
listProcessPlanParam({ planId: plan.planId, pageNum: 1, pageSize: 500 })
.then(res => res.rows || [])
.catch(() => [])
)
)
this.allParamList = results.flat()
} finally {
this.allParamLoading = false
}
},
applyFilter() { this.appliedFilterName = this.filterName },
resetFilter() {
@@ -921,28 +839,33 @@ export default {
}).catch(() => {})
},
openParamDialog(planRow, paramRow) {
const targetPlan = planRow || this.selectedPlan
if (!targetPlan) { this.$message.warning('请先选择一个方案点位'); return }
if (!this.selectedPlan || this.selectedPlan.planId !== targetPlan.planId) {
this.selectedPlan = targetPlan
this.loadParams(targetPlan.planId)
}
this.paramForm = paramRow
? { ...paramRow }
: {
planId: targetPlan.planId,
paramCode: undefined,
paramName: undefined,
planId: planRow ? planRow.planId : undefined,
paramCode: undefined,
paramName: undefined,
targetValue: undefined,
lowerLimit: undefined,
upperLimit: undefined,
unit: undefined,
remark: undefined
lowerLimit: undefined,
upperLimit: undefined,
unit: undefined,
remark: undefined
}
this.paramTitle = paramRow ? '编辑方案参数' : '新建方案参数'
this.paramOpen = true
this.$nextTick(() => this.$refs.paramFormRef && this.$refs.paramFormRef.clearValidate())
},
editParamFromFlat(row) {
this.openParamDialog(null, row)
},
deleteParamFromFlat(row) {
this.$modal.confirm('确认删除该参数?').then(() => {
return delProcessPlanParam(row.paramId)
}).then(() => {
this.$modal.msgSuccess('删除成功')
this.loadAllParams()
}).catch(() => {})
},
submitParam() {
this.$refs.paramFormRef.validate(ok => {
if (!ok) return
@@ -951,18 +874,10 @@ export default {
req.then(() => {
this.$modal.msgSuccess('保存成功')
this.paramOpen = false
this.loadParams(this.selectedPlan.planId)
this.loadAllParams()
}).catch(e => console.error(e)).finally(() => { this.paramSubmitLoading = false })
})
},
removeParam(row) {
this.$modal.confirm('确认删除该参数?').then(() => {
return delProcessPlanParam(row.paramId)
}).then(() => {
this.$modal.msgSuccess('删除成功')
this.loadParams(this.selectedPlan.planId)
}).catch(() => {})
},
// ===================== 导入相关方法 =====================
/**
* 打开导入对话框
@@ -1218,7 +1133,7 @@ export default {
async batchImport() {
// <20>照点位名称分组每个点位创建一条记录然后添加多个参数
const pointGroups = {}
// 分组处理
this.tableData.forEach(row => {
const pointKey = `${row.segmentType}_${row.segmentName}_${row.pointName}`
@@ -1249,12 +1164,12 @@ export default {
try {
await this.importOnePoint(group)
this.importedCount += group.params.length
// 更新进度
index += group.params.length
const currentProgress = Math.round((index / this.totalCount) * 100)
this.progress = currentProgress
await new Promise(resolve => setTimeout(resolve, 50))
} catch (error) {
throw new Error(`导入点位"${group.pointName}"失败:${error.message}`)
@@ -1275,7 +1190,7 @@ export default {
pointCode: group.pointCode,
sortOrder: 0
}
const planRes = await addProcessPlan(planParams)
if (planRes.code !== 200) {
throw new Error(`点位创建失败:${planRes.msg || '接口返回异常'}`)
@@ -1293,7 +1208,7 @@ export default {
lowerLimit: param.lowerLimit ? Number(param.lowerLimit) : null,
unit: param.unit
}
const paramRes = await addProcessPlanParam(paramParams)
if (paramRes.code !== 200) {
throw new Error(`参数"${param.paramName}"创建失败:${paramRes.msg || '接口返回异常'}`)
@@ -1354,7 +1269,7 @@ export default {
// 创建工作簿
const wb = XLSX.utils.book_new()
const ws = XLSX.utils.aoa_to_sheet(templateData)
// 设置列宽
ws['!cols'] = [
{ wch: 10 }, // 段类型
@@ -1366,7 +1281,7 @@ export default {
{ wch: 10 }, // 下限
{ wch: 10 } // 单位
]
XLSX.utils.book_append_sheet(wb, ws, '导入模板')
XLSX.writeFile(wb, '方案点位导入模板.xlsx')
},
@@ -1558,6 +1473,19 @@ export default {
.btn-danger { color: #f56c6c; }
/* ── 段 chip ── */
.seg-chip {
display: inline-block;
padding: 2px 7px;
border-radius: 10px;
font-size: 11px;
font-weight: 600;
white-space: nowrap;
}
.seg-inlet { background: #ecf5ff; color: #3a6ea8; border: 1px solid #b3d8ff; }
.seg-process { background: #f0f9eb; color: #3a7a2a; border: 1px solid #b3e19d; }
.seg-outlet { background: #fdf6ec; color: #a86a00; border: 1px solid #f5dab1; }
/* ── 偏差分析 ── */
.anomaly-section-header {
display: flex;