Files
bj_power/bj_power_mes/frontend/src/pages/Trace.vue
T
SunYF d609ff8a9e refactor: 重构工艺工序相关命名与数据模型
1.  全局替换"工序"为"工艺"统一术语,包括页面文案、枚举、注释
2.  重构工单与工件工艺数据模型:
    - 新增工艺组合表flow_material作为备料/齐套唯一源头
    - 新增工位状态表station_state支持自主停单/恢复接单
    - 移除station_process派工表,改用工单工艺组合作为路线唯一源头
    - 替换processCode为flowId作为工艺关联标识
    - 重构工件当前进度字段为currentStationNo
3.  删除冗余的静态备份资源文件
4.  调整物料选择组件默认启用仅显示激活物料
5.  优化工单创建/编辑逻辑,新增工艺组合校验与保存
2026-09-22 11:32:29 +08:00

209 lines
10 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>
<el-card>
<el-form inline>
<el-form-item label="工件SN">
<el-input v-model="sn" style="width:220px" placeholder="输入SN查询追溯" @keyup.enter="search" />
</el-form-item>
<el-form-item label="工单号">
<el-input v-model="orderNo" style="width:200px" placeholder="按工单号列出工件(模糊)" @keyup.enter="searchByOrder" />
</el-form-item>
<el-form-item><el-button type="primary" @click="search">追溯查询</el-button></el-form-item>
<el-form-item><el-button @click="searchByOrder">按工单查询</el-button></el-form-item>
<el-form-item>
<el-button type="warning" :disabled="!sn" @click="printCard">打印流程卡</el-button>
</el-form-item>
</el-form>
<el-card v-if="orderRows.length" shadow="never" style="margin-bottom:12px">
<template #header>工单{{ orderNo }}下的工件共 {{ orderRows.length }} 追溯查看单个工件</template>
<el-table :data="orderRows" border size="small" max-height="240">
<el-table-column prop="sn" label="工件SN" min-width="180" show-overflow-tooltip />
<el-table-column label="状态" width="100" align="center">
<template #default="{ row }">{{ statusText(row.status) }}</template>
</el-table-column>
<el-table-column prop="currentProcess" label="当前工位" width="100" align="center" />
<el-table-column label="操作" width="90" align="center">
<template #default="{ row }">
<el-button link type="primary" size="small" @click="pickSn(row.sn)">追溯</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<el-alert v-if="error" :title="error" type="error" show-icon closable style="margin-bottom:10px" />
<template v-if="data">
<el-descriptions :column="4" border style="margin-bottom:12px" title="工件信息">
<el-descriptions-item label="工件SN">{{ data.workpiece?.sn }}</el-descriptions-item>
<el-descriptions-item label="所属工单">{{ data.workpiece?.orderNo }}</el-descriptions-item>
<el-descriptions-item v-if="data.workpiece?.productCode || data.order?.productCode" label="产品型号">
{{ data.workpiece?.productCode || data.order?.productCode }}{{ (data.workpiece?.productName || data.order?.productName) ? '' + (data.workpiece?.productName || data.order?.productName) + '' : '' }}
</el-descriptions-item>
<el-descriptions-item label="状态">{{ statusText(data.workpiece?.status) }}</el-descriptions-item>
<el-descriptions-item v-if="data.workpiece?.currentProcess" label="当前工位">{{ data.workpiece?.currentProcess }}</el-descriptions-item>
<el-descriptions-item v-if="data.workpiece?.onlineAt" label="进线时间">{{ fmt(data.workpiece?.onlineAt) }}</el-descriptions-item>
<el-descriptions-item v-if="data.workpiece?.doneAt" label="完工时间">{{ fmt(data.workpiece?.doneAt) }}</el-descriptions-item>
</el-descriptions>
<el-alert type="info" :closable="false" style="margin-bottom:12px"
title="追溯链路:产品型号(物)→ 工单(批次任务)→ 工件(实例SN)。本页展示单个工件的完整生命周期。" />
<el-tabs>
<el-tab-pane label="工位时间线">
<el-timeline>
<el-timeline-item v-for="p in (data.processTimeline||[])" :key="p.id"
:timestamp="p.operator ? (p.operator+' · '+fmt(p.endedAt)) : fmt(p.endedAt)"
:type="p.result==='NG'?'danger':'success'">
{{ p.processName }}工位 {{ p.stationNo || p.processCode }})— {{ p.result }}
</el-timeline-item>
</el-timeline>
</el-tab-pane>
<el-tab-pane label="步骤考核数据">
<el-table :data="data.stepData||[]" border size="small">
<el-table-column prop="stepName" label="步骤" min-width="110" />
<el-table-column prop="criterionName" label="考核项" min-width="110" />
<el-table-column prop="value" label="数值" width="100" />
<el-table-column prop="isOK" label="合格" width="80">
<template #default="{row}"><el-tag :type="row.isOK?'success':'danger'">{{ row.isOK?'合格':'不合格' }}</el-tag></template>
</el-table-column>
<el-table-column prop="operator" label="操作人" width="90" />
</el-table>
</el-tab-pane>
<el-tab-pane label="拧紧数据">
<el-table :data="data.torqueRecords||[]" border size="small">
<el-table-column prop="stationNo" label="工位" width="80" />
<el-table-column prop="torque" label="扭矩" width="100" />
<el-table-column prop="angle" label="角度" width="90" />
<el-table-column prop="result" label="结果" width="80" />
<el-table-column prop="operator" label="操作人" width="90" />
<el-table-column prop="time" label="时间" min-width="160" />
</el-table>
</el-tab-pane>
<el-tab-pane label="成品关联">
<p>批次号{{ (data.associationTrace?.batchItems||[]).join(', ') || '无' }}</p>
<p>电气件SN{{ (data.associationTrace?.serialItems||[]).join(', ') || '无' }}</p>
</el-tab-pane>
<el-tab-pane label="装机绑定">
<el-alert :type="data.bindTrace?.complete ? 'success' : 'warning'" :closable="false" style="margin-bottom:10px"
:title="data.bindTrace?.complete ? '装配物料已全部绑齐 ✓' : ('装配物料未绑齐:' + (data.bindTrace?.missing || ''))" show-icon />
<el-table :data="data.bindTrace?.binds||[]" border size="small">
<el-table-column prop="processCode" label="工位" width="70" />
<el-table-column prop="materialCode" label="图号" min-width="110" />
<el-table-column prop="materialName" label="物料名称" min-width="110" />
<el-table-column prop="spec" label="规格" min-width="90" />
<el-table-column prop="bindType" label="类型" width="70">
<template #default="{row}">{{ row.bindType === 'SN' ? '电气件SN' : '批次' }}</template>
</el-table-column>
<el-table-column prop="bindValue" label="绑定值(SN/批次)" min-width="140" />
<el-table-column prop="operator" label="绑定人" width="90" />
<el-table-column prop="createdAt" label="绑定时间" min-width="165">
<template #default="{row}">{{ fmt(row.createdAt) }}</template>
</el-table-column>
</el-table>
<el-divider content-position="left">物料组成递归展开成品零部件到原材料</el-divider>
<el-table :data="flatBom" border size="small" row-key="_key" default-expand-all>
<el-table-column label="物料" min-width="180">
<template #default="{row}">
<span :style="{paddingLeft: (row._depth*18)+'px'}">{{ row.materialName }} <small>({{ row.materialCode }})</small></span>
</template>
</el-table-column>
<el-table-column prop="spec" label="规格" min-width="90" />
<el-table-column prop="qty" label="单台用量" width="100" />
<el-table-column label="管理方式" width="100">
<template #default="{row}">{{ row.manageMode === '2' ? '电气件SN' : '批次' }}</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
</template>
</el-card>
<PageHelp :data="helpTrace" page="Trace" />
</template>
<script setup>
import { ref, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import request from '../utils/request'
import { openPrintableHtml } from '../utils/print'
import PageHelp from '../components/PageHelp.vue'
import { helpTrace } from '../help'
const sn = ref('')
const orderNo = ref('')
const orderRows = ref([])
const data = ref(null)
const error = ref('')
// 按工单号列出该工单下的全部工件,点击行内「追溯」查看单个工件生命周期(客户第12条:记录查看加工单号查询)
async function searchByOrder() {
const no = orderNo.value.trim()
if (!no) {
ElMessage.warning('请输入工单号')
return
}
try {
orderRows.value = (await request.get('/workpieces', { params: { orderNo: no } })) || []
if (!orderRows.value.length) ElMessage.info('该工单下暂无工件')
} catch {
orderRows.value = []
}
}
function pickSn(v) {
sn.value = v
search()
}
async function search() {
if (!sn.value) return
error.value = ''
data.value = null
try {
data.value = await request.get('/trace', { params: { sn: sn.value } })
} catch (e) {
error.value = e.message || '查询失败'
}
}
async function printCard() {
if (!sn.value) return
// 打印前数据完整性预检
let missing = []
try {
const check = await request.get('/process-card', { params: { sn: sn.value, check: 1 } })
missing = check?.missing || []
} catch {
/* 预检失败不阻塞打印 */
}
if (missing.length) {
try {
await ElMessageBox.confirm(
`以下数据不完整,是否继续打印?\n\n${missing.map((m) => '· ' + m).join('\n')}`,
'打印流程卡', {
type: 'warning',
confirmButtonText: '继续打印',
cancelButtonText: '取消'
})
} catch {
return // 用户取消
}
}
// 直接 window.open 带不上 Bearer 头会被 401,改由 axios 取回 HTML 后写入新窗口
await openPrintableHtml('/process-card', { sn: sn.value })
}
// 把递归的物料组成树扁平化为带层级缩进的列表,避免 el-table 树形 row-key 冲突
const flatBom = computed(() => {
const out = []
const walk = (nodes, depth, prefix) => {
;(nodes || []).forEach((n, i) => {
const key = prefix + '-' + n.materialCode + '-' + i
out.push(Object.assign({}, n, { _depth: depth, _key: key }))
if (n.children && n.children.length) walk(n.children, depth + 1, key)
})
}
walk(data.value?.bindTrace?.bomTree || [], 0, 'r')
return out
})
function statusText(st) {
const map = { CREATED: '已创建', ONLINE: '生产中', DONE: '已完工', SCRAPPED: '已报废' }
return map[st] || st || '-'
}
function fmt(v) { return v ? String(v).replace('T', ' ').slice(0, 19) : '-' }
</script>