feat&refactor: 2026-09-22 半成品业务模型重构与功能完善

- 重构半成品流转逻辑:进度权威源改为 workpiece.currentStationNo,新增 completedText 快照字段,废除 doneProcessCodes
- 清理物料品类中半成品类型,存量数据幂等清理,调整物料管理方式文案为批次/序列号
- 新增日排产状态专用更新接口,修复工单工艺校验逻辑
- 新增物料档案代理接口、深路径文件下载接口
- 优化前端界面文案与工位选择逻辑,补充追溯页半成品流转展示
- 调整WMS端物料品类校验与入库接口契约
- 新增/更新数据库表结构与实体类代码
This commit is contained in:
SunYF
2026-09-22 14:42:52 +08:00
parent 09cfc6ca1c
commit d3eda6b588
49 changed files with 1155 additions and 1122 deletions
+4 -4
View File
@@ -84,7 +84,7 @@ export const helpDailyPlan = {
export const helpBom = {
title: '产品物料清单',
overview:
'物料清单 = 生产 1 台该产品需要哪些物料、各用多少。\n按「产品编号 + 物料清单名称」维护:同一产品编号可并存多份物料清单(选用制)——不同厂家供货、含半成品的结构差异,都用不同的物料清单名称区分(缺省叫「默认」,不用 V1/V2 版本号)。\n工单建单时选定其中一份,之后该工单的备料算料、装机绑定校验都锁定这份物料清单,不同厂家的料单互不串。\n\n与 WMS 的关系:本页(MES)只维护「产品由哪几种料、各用几个」;每种物料叫什么、规格、是电气件(SN)还是结构件(批次)、库存有多少,统一在 WMS(库房)的「物料档案」里维护,两套系统互相解耦、通过图号对齐。\n\n编码对应规则:本页的「图号」必须与 WMS 物料档案里的「图号」完全一致;「管理方式」也要对应(电气件=SN 逐件,结构件=批次)。编码对不上,WMS 将无法为对应工单备料/出库。\n\n保护规则:某份物料清单一旦被工单选用过,就不允许再从中移除物料(防止历史工单追溯断链);新建/改名的物料清单名称在本页「物料清单名称」下拉直接输入即可创建。\n\n数据链路:先建「物料档案」→ 到本页选产品编号 + 命名物料清单,逐条添加物料 → 建工单时选定物料清单 → 仓库才能为对应工单领料。',
'物料清单 = 生产 1 台该产品需要哪些物料、各用多少。\n按「产品编号 + 物料清单名称」维护:同一产品编号可并存多份物料清单(选用制)——不同厂家供货、含半成品的结构差异,都用不同的物料清单名称区分(缺省叫「默认」,不用 V1/V2 版本号)。\n工单建单时选定其中一份,之后该工单的备料算料、装机绑定校验都锁定这份物料清单,不同厂家的料单互不串。\n\n与 WMS 的关系:本页(MES)只维护「产品由哪几种料、各用几个」;每种物料叫什么、规格、按序列号还是按批次管理、库存有多少,统一在 WMS(库房)的「物料档案」里维护,两套系统互相解耦、通过图号对齐。\n\n编码对应规则:本页的「图号」必须与 WMS 物料档案里的「图号」完全一致;「管理方式」也要对应(电气件=SN 逐件,结构件=批次)。编码对不上,WMS 将无法为对应工单备料/出库。\n\n保护规则:某份物料清单一旦被工单选用过,就不允许再从中移除物料(防止历史工单追溯断链);新建/改名的物料清单名称在本页「物料清单名称」下拉直接输入即可创建。\n\n数据链路:先建「物料档案」→ 到本页选产品编号 + 命名物料清单,逐条添加物料 → 建工单时选定物料清单 → 仓库才能为对应工单领料。',
fields: [
{ name: '物料清单名称', source: '下拉选择/输入(可搜索,输入新名即创建)。缺省「默认」。', purpose: '同一产品编号下多份物料清单的区分名,工单建单时按此选定', fill: '如「默认」「A厂料单」「含半成品」;同一型号下名称唯一' },
{ name: '产品编号', source: '下拉选择。来自「物料档案」里建的编码。', purpose: '要为哪个产品配置物料清单', fill: '在查询区或「添加物料」弹窗里选择产品编号' },
@@ -92,7 +92,7 @@ export const helpBom = {
{ name: '物料名称', source: '手工填写。', purpose: '物料中文名,便于识别', fill: '如 十字螺丝、主控芯片' },
{ name: '规格', source: '手工填写。', purpose: '物料规格型号', fill: '如 M16×80 / SMT-48脚' },
{ name: '单位', source: '手工填写。', purpose: '计量单位,用于算量', fill: '个 / 件 / 套 / kg' },
{ name: '管理方式', source: '单选。按物料类型决定,与 WMS 一致。', purpose: '电气件=逐件按SN管理;结构件=按批次管理', fill: '电件/电件选"电气件 SN";螺丝等标准件选"结构件 批次"' },
{ name: '管理方式', source: '单选。按物料类型决定,与 WMS 一致。', purpose: '电气件=逐件按SN管理;结构件=按批次管理', fill: '电件/电件选"序列号";螺丝等标准件选"批次"' },
{ name: '单台用量', source: '手工填写。', purpose: '生产 1 台产品用的该物料数量', fill: '如每台 8 颗螺丝 → 填 8' },
{ name: '损耗率%', source: '手工填写。', purpose: '考虑报废/损耗的加放比例', fill: '如 2% 填 20=不计算损耗' },
{ name: '需求量', source: '自动计算 = 数量×单台用量×(1+损耗率),可覆盖。', purpose: '该物料总需求,出库不能超它', fill: '一般自动算,不必改' },
@@ -313,7 +313,7 @@ export const helpEventLog = {
export const helpProcessFlow = {
title: '工艺(作业指导,含工艺步骤与物料清单)',
overview:
'页面分「工艺列表」「工艺配置」两个页签:列表查看全部工艺并做启用/停用/删除;配置页维护工艺基本信息、工艺步骤与物料清单(新增与编辑共用,不再弹窗套弹窗)。\n\n工艺 = 一份「菜谱」:只描述某个产品该怎么装配(工艺步骤名、采集方式、考核标准、所需物料),可被多个工位绑定,不隶属某个工位。\n\n概念统一说明:\n· 工位:产线上的物理工位(工位数以数据库为准),工位号即顺序号,相当于「灶台」。\n· 工艺:一份作业指导(PDF + 工艺步骤 + 物料清单);其启用状态决定是否参与生产。\n· 工艺步骤:工艺内包含多个步骤(如:装配完成、拧紧扭矩、测试)。\n· 物料清单:该工艺装配所需物料(编码/名称/规格/单位/管控方式/单台用量/损耗率),整表覆盖保存。\n\n绑定关系在「工位绑定」页维护:为某工位选择启用的工艺即绑定;一个工位只能绑定一个工艺。工单在「工单管理」勾选「工艺组合」确定执行路线(工位号升序)。\n\n数据链路:本页维护工艺+工艺步骤+物料清单+考核标准 → 工位绑定页绑定 → 工单勾选工艺组合 → 工位终端按工艺步骤采集 → 报工按考核标准自动判 合格/不合格 → 工件追溯/绩效报表可见。',
'页面分「工艺列表」「工艺配置」两个页签:列表查看全部工艺并做启用/停用/删除;配置页维护工艺基本信息、工艺步骤与物料清单(新增与编辑共用,不再弹窗套弹窗)。\n\n工艺 = 一份「菜谱」:只描述某个产品该怎么装配(工艺步骤名、采集方式、考核标准、所需物料),可被多个工位绑定,不隶属某个工位。\n\n概念统一说明:\n· 工位:产线上的物理工位(工位数以数据库为准),工位号即顺序号,相当于「灶台」。\n· 工艺:一份作业指导(PDF + 工艺步骤 + 物料清单);其启用状态决定是否参与生产。\n· 工艺步骤:工艺内包含多个步骤(如:装配完成、拧紧扭矩、测试)。\n· 物料清单:该工艺装配所需物料(物料编号/名称/规格/单位/管控方式/单台用量/损耗率),整表覆盖保存。\n\n绑定关系在「工位绑定」页维护:为某工位选择启用的工艺即绑定;一个工位只能绑定一个工艺。工单在「工单管理」勾选「工艺组合」确定执行路线(工位号升序)。\n\n数据链路:本页维护工艺+工艺步骤+物料清单+考核标准 → 工位绑定页绑定 → 工单勾选工艺组合 → 工位终端按工艺步骤采集 → 报工按考核标准自动判 合格/不合格 → 工件追溯/绩效报表可见。',
sections: [
{
title: '工艺基本信息',
@@ -324,7 +324,7 @@ export const helpProcessFlow = {
{ name: '作业指导PDF', source: '本页上传。文件由 MES 服务器保存。', purpose: '该工艺的作业指导图,工位终端打开预览', fill: '选择 PDF 上传,成功后显示文件名可预览' },
{ name: '工艺步骤', source: '本页维护(步骤名/采集方式/是否拧紧/考核标准)。', purpose: '工位终端报工时按此动态渲染参数输入', fill: '一个工艺可含多个工艺步骤,按序号顺序执行' },
{ name: '步骤附件/图纸', source: '每个工艺步骤单独上传(绑定式)。文件由 MES 服务器保存。', purpose: '把图纸精确绑定到具体步骤,工位终端按当前步骤自动展示对应图纸', fill: '在步骤行上传该步骤图纸/附件(可多个);随步骤自动分发到工位终端' },
{ name: '物料清单', source: '本页维护:物料编/名称/规格/单位/管控方式/单台用量/损耗率%,整表覆盖保存。', purpose: '该工艺装配所需物料;备料需求 = 各工位分产量 × 单台用量 × (1+损耗率%)', fill: '物料编码/名称/规格/单位可手填,须属于产品物料清单范围,工单保存时校验;管控方式:批次·结构件=1、序列号·电气件=2' }
{ name: '物料清单', source: '本页维护:物料编号(下拉选择,取自物料档案)/名称/规格/单位/管控方式/单台用量/损耗率%,整表覆盖保存。', purpose: '该工艺装配所需物料;备料需求 = 各工位分产量 × 单台用量 × (1+损耗率%)', fill: '物料编号取自物料档案编码(结构件通常即图号),选中后名称/规格/单位/管控方式自动带出、不可改;管控方式:批次=1、序列号=2' }
]
},
{
@@ -66,7 +66,7 @@
<el-dialog v-model="dialog.show" :title="dialog.id ? '编辑账号' : '新增账号'" width="520px" append-to-body destroy-on-close>
<el-form label-width="100px">
<el-form-item label="用户名" required>
<el-input v-model="dialog.username" :disabled="lockedName(dialog.username)" placeholder="登录名(创建后不可改)" />
<el-input v-model="dialog.username" :disabled="dialog.id > 0 && lockedName(dialog.username)" placeholder="登录名(创建后不可改)" />
</el-form-item>
<el-form-item :label="dialog.id ? '重置密码' : '初始密码'" required>
<el-input v-model="dialog.password" type="password" show-password
+3 -3
View File
@@ -37,7 +37,7 @@
<el-table-column prop="spec" label="规格" />
<el-table-column prop="unit" label="单位" width="70" />
<el-table-column prop="manageMode" label="管理方式" width="110">
<template #default="{row}">{{ row.manageMode === '1' ? '结构件(批次)' : '电气件(SN)' }}</template>
<template #default="{row}">{{ row.manageMode === '1' ? '批次' : '序列号' }}</template>
</el-table-column>
<el-table-column prop="unitQty" label="单台用量" width="90" />
<el-table-column prop="lossRate" label="损耗率%" width="90" />
@@ -66,8 +66,8 @@
<el-form-item label="单位"><el-input v-model="add.unit" /></el-form-item>
<el-form-item label="管理方式">
<el-radio-group v-model="add.manageMode">
<el-radio label="2">电气件(SN)</el-radio>
<el-radio label="1">结构件(批次)</el-radio>
<el-radio label="2">序列号</el-radio>
<el-radio label="1">批次</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="单台用量"><el-input-number v-model="add.unitQty" :min="0" /></el-form-item>
+25 -4
View File
@@ -131,7 +131,7 @@ const query = reactive({ orderNo: '', planDate: '' })
const list = ref([])
const workOrders = ref([])
const productTypes = ref([])
const dialog = reactive({ show: false, id: 0, orderNo: '', planDate: '', planRange: [], mode: 'AVG', planQty: 1, productType: '', productCode: '', productName: '', woQty: '', stationPlanQty: {}, combination: [] })
const dialog = reactive({ show: false, id: 0, orderNo: '', planDate: '', planRange: [], mode: 'AVG', planQty: 1, productType: '', productCode: '', productName: '', woQty: '', woStart: '', woDue: '', stationPlanQty: {}, combination: [] })
// 工位号→接驳台编码、工位号→名称(送达接驳台由工单工艺组合派生)
const stationDockMap = ref({})
@@ -210,6 +210,7 @@ function loadWorkOrderInfo() {
if (!wo) {
dialog.productType = ''; dialog.productCode = ''; dialog.productName = ''; dialog.woQty = ''
dialog.combination = []
dialog.woStart = ''; dialog.woDue = ''
return
}
dialog.productCode = wo.productCode || ''
@@ -218,9 +219,20 @@ function loadWorkOrderInfo() {
dialog.combination = Array.isArray(wo.flowCombination) ? wo.flowCombination : []
const pt = productTypes.value.find((p) => p.id === wo.productTypeId)
dialog.productType = pt ? (pt.name + (pt.code ? ' / ' + pt.code : '')) : ''
// 记录工单起止日期(planStart/dueDate),用于日期预填与超期提示
dialog.woStart = wo.planStart ? String(wo.planStart).slice(0, 10) : ''
dialog.woDue = wo.dueDate ? String(wo.dueDate).slice(0, 10) : ''
// 新建排产:日期范围默认取所选工单的开始/结束日期(可修改)
if (!dialog.id && (dialog.woStart || dialog.woDue)) {
dialog.planRange = [dialog.woStart || dialog.woDue, dialog.woDue || dialog.woStart]
}
}
function onOrderChange() {
loadWorkOrderInfo()
// 编辑弹窗切换工单:排产日期默认取该工单开始日期(无则取结束日期,可修改)
if (dialog.id && (dialog.woStart || dialog.woDue)) {
dialog.planDate = dialog.woStart || dialog.woDue
}
}
function openAdd() {
const today = new Date().toISOString().slice(0, 10)
@@ -242,6 +254,15 @@ function openEdit(row) {
: dialog.combination
loadAlloc()
}
// 所选日期超出工单执行期间(planStart~dueDate)时提示,但不阻断提交
function warnOutOfWindow(dates) {
const s = dialog.woStart || ''
const e = dialog.woDue || ''
if ((!s && !e) || !dialog.orderNo) return
if (dates.some((d) => (s && d < s) || (e && d > e))) {
ElMessage.warning('所选日期超出工单执行期间')
}
}
async function save() {
if (!dialog.orderNo) return ElMessage.warning('请选择工单号')
if (dialog.id) {
@@ -251,6 +272,7 @@ async function save() {
if (alloc[st]) spq[String(st)] = Number(alloc[st]) || 0
})
if (!dialog.planDate) return ElMessage.warning('请选择排产日期')
warnOutOfWindow([dialog.planDate])
await request.put(`/daily-plans/${dialog.id}`, {
id: dialog.id, orderNo: dialog.orderNo, planDate: dialog.planDate, planQty: dialog.planQty, stationPlanQty: spq
})
@@ -259,6 +281,7 @@ async function save() {
if (!dialog.planRange || dialog.planRange.length !== 2 || !dialog.planRange[0] || !dialog.planRange[1])
return ElMessage.warning('请选择排产日期范围')
if (!dialog.planQty || dialog.planQty < 1) return ElMessage.warning('请填写数量')
warnOutOfWindow([dialog.planRange[0], dialog.planRange[1]])
await request.post('/daily-plans', {
orderNo: dialog.orderNo, planRange: dialog.planRange, mode: dialog.mode, planQty: dialog.planQty
})
@@ -292,9 +315,7 @@ async function toggleStatus(row) {
const target = row.status === 'CONFIRMED' ? 'PENDING' : 'CONFIRMED'
const label = target === 'CONFIRMED' ? '启用' : '停用'
await ElMessageBox.confirm(`确认${label}该排产(${row.orderNo} ${row.planDate})?`, '提示', { type: 'warning' })
await request.put(`/daily-plans/${row.id}`, {
orderNo: row.orderNo, planDate: row.planDate, planQty: row.planQty, status: target
})
await request.post(`/daily-plans/${row.id}/status`, { status: target })
ElMessage.success(`${label}`)
load()
}
@@ -48,7 +48,7 @@
<el-table-column prop="materialName" label="物料名称" min-width="110" show-overflow-tooltip />
<el-table-column prop="unit" label="单位" width="70" />
<el-table-column prop="manageMode" label="管理方式" width="110">
<template #default="{row}">{{ row.manageMode === '1' ? '结构件(批次)' : '电气件(SN)' }}</template>
<template #default="{row}">{{ row.manageMode === '1' ? '批次' : '序列号' }}</template>
</el-table-column>
<el-table-column prop="stationNo" label="工位" width="70" align="center" />
<el-table-column prop="targetDock" label="接驳台" width="100" align="center" />
+79 -44
View File
@@ -161,53 +161,62 @@
</div>
</el-form-item>
<!-- 物料清单子表(整表覆盖保存) -->
<!-- 物料清单子表(整表覆盖保存;物料编号下拉选择,快照信息取自物料档案 -->
<el-form-item label="物料清单">
<div style="width:100%">
<el-alert v-if="!flowDlg.id" type="info" :closable="false" style="margin-bottom:8px"
title="新工艺请先保存保存后即可维护物料清单" />
<el-table v-else :data="flowDlg.materials" border size="small">
<el-table-column label="物料编码" min-width="130">
<template #default="{ row }"><el-input v-model="row.materialCode" placeholder="手填" /></template>
</el-table-column>
<el-table-column label="物料名称" min-width="120">
<template #default="{ row }"><el-input v-model="row.materialName" placeholder="手填" /></template>
</el-table-column>
<el-table-column label="规格" min-width="110">
<template #default="{ row }"><el-input v-model="row.spec" /></template>
</el-table-column>
<el-table-column label="单位" width="80">
<template #default="{ row }"><el-input v-model="row.unit" /></template>
</el-table-column>
<el-table-column label="管控方式" width="150">
<template #default="{ row }">
<el-select v-model="row.manageMode" placeholder="请选择">
<el-option value="1" label="批次·结构件" />
<el-option value="2" label="序列号·电气件" />
</el-select>
</template>
</el-table-column>
<el-table-column label="单台用量" width="120">
<template #default="{ row }">
<el-input-number v-model="row.unitQty" :min="0" size="small" controls-position="right" style="width:100%" />
</template>
</el-table-column>
<el-table-column label="损耗率%" width="110">
<template #default="{ row }">
<el-input-number v-model="row.lossRate" :min="0" size="small" controls-position="right" style="width:100%" />
</template>
</el-table-column>
<el-table-column label="操作" width="70" align="center">
<template #default="{ row }">
<el-button link type="danger" size="small" @click="removeMaterial(row)">删</el-button>
</template>
</el-table-column>
</el-table>
<div style="display:flex;gap:10px;margin-top:10px;align-items:center">
<el-button v-if="flowDlg.id" size="small" @click="addMaterial">新增物料行</el-button>
<el-button v-if="flowDlg.id" size="small" type="primary" :loading="matSaving" @click="saveMaterials">保存物料清单</el-button>
<span style="color:#909399;font-size:12px">物料编码/名称/规格/单位手填,须属于产品物料清单范围,工单保存时校验;备料需求 = 各工位分产量 × 单台用量 ×(1+损耗率%)。</span>
</div>
<template v-else>
<el-alert v-if="materialsUnavailable" type="warning" :closable="false" style="margin-bottom:8px"
title="物料档案服务不可用清单暂只读不可新增物料行已有行仅可调整单台用量/损耗率" />
<el-table :data="flowDlg.materials" border size="small">
<el-table-column label="物料编号" min-width="150">
<template #default="{ row }">
<el-select v-model="row.materialCode" filterable placeholder="请选择物料编号" style="width:100%"
@change="(code) => onMaterialPicked(row, code)">
<el-option v-for="m in materialOptions" :key="m.code" :label="m.code" :value="m.code" />
</el-select>
</template>
</el-table-column>
<el-table-column label="物料名称" min-width="120">
<template #default="{ row }"><el-input v-model="row.materialName" disabled /></template>
</el-table-column>
<el-table-column label="规格" min-width="110">
<template #default="{ row }"><el-input v-model="row.spec" disabled /></template>
</el-table-column>
<el-table-column label="单位" width="80">
<template #default="{ row }"><el-input v-model="row.unit" disabled /></template>
</el-table-column>
<el-table-column label="管控方式" width="110">
<template #default="{ row }">
<el-select v-model="row.manageMode" disabled>
<el-option value="1" label="批次" />
<el-option value="2" label="序列号" />
</el-select>
</template>
</el-table-column>
<el-table-column label="单台用量" width="120">
<template #default="{ row }">
<el-input-number v-model="row.unitQty" :min="0" size="small" controls-position="right" style="width:100%" />
</template>
</el-table-column>
<el-table-column label="损耗率%" width="110">
<template #default="{ row }">
<el-input-number v-model="row.lossRate" :min="0" size="small" controls-position="right" style="width:100%" />
</template>
</el-table-column>
<el-table-column label="操作" width="70" align="center">
<template #default="{ row }">
<el-button link type="danger" size="small" @click="removeMaterial(row)">删</el-button>
</template>
</el-table-column>
</el-table>
<div style="display:flex;gap:10px;margin-top:10px;align-items:center">
<el-button v-if="!materialsUnavailable" size="small" @click="addMaterial">新增物料行</el-button>
<el-button size="small" type="primary" :loading="matSaving" @click="saveMaterials">保存物料清单</el-button>
<span style="color:#909399;font-size:12px">物料编号取自物料档案编码(结构件通常即图号),选中后名称/规格/单位/管控方式自动带出、不可改;备料需求 = 各工位分产量 × 单台用量 ×(1+损耗率%)。</span>
</div>
</template>
</div>
</el-form-item>
</el-form>
@@ -369,6 +378,31 @@ function blankMaterial() {
return { materialCode: '', materialName: '', spec: '', unit: '', manageMode: '2', unitQty: 1, lossRate: 0 }
}
/* ---------- 物料档案(物料清单「物料编号」下拉数据源) ---------- */
const materialOptions = ref([])
const materialsUnavailable = ref(false)
async function loadMaterialArchive() {
materialsUnavailable.value = false
try {
const list = (await request.get('/materials')) || []
materialOptions.value = Array.isArray(list) ? list : []
} catch {
materialOptions.value = []
}
if (!materialOptions.value.length) materialsUnavailable.value = true
}
// 选中物料编号后,快照字段(名称/规格/单位/管控方式)自动带出,均不可手改
function onMaterialPicked(row, code) {
const m = materialOptions.value.find((x) => x.code === code)
if (!m) return
row.materialName = m.name || ''
row.spec = m.spec || ''
row.unit = m.unit || ''
if (m.manageMode != null && m.manageMode !== '') row.manageMode = String(m.manageMode)
}
// 加载该工艺的物料清单(整表编辑,保存时整表覆盖)
async function loadMaterials(flowId) {
flowDlg.materials = []
@@ -399,7 +433,7 @@ async function saveMaterials() {
unitQty: Number(m.unitQty) || 0,
lossRate: Number(m.lossRate) || 0
}))
if (items.some((m) => !m.materialCode)) return ElMessage.warning('存在未填写物料编的行,请补全或删除')
if (items.some((m) => !m.materialCode)) return ElMessage.warning('存在未选择物料编的行,请补全或删除')
matSaving.value = true
try {
await request.post(`/process-flows/${flowDlg.id}/materials`, { items })
@@ -552,6 +586,7 @@ async function toggleStatus(row) {
onMounted(() => {
loadFlows()
loadMaterialArchive()
})
</script>
+23 -2
View File
@@ -82,6 +82,27 @@
<p>批次号{{ (data.associationTrace?.batchItems||[]).join(', ') || '无' }}</p>
<p>电气件SN{{ (data.associationTrace?.serialItems||[]).join(', ') || '无' }}</p>
</el-tab-pane>
<el-tab-pane label="暂存退库/重上线">
<el-alert type="info" :closable="false" style="margin-bottom:10px"
title="半成品流转记录:暂存退库=入 WMS 库、重上线=出库继续执行;「完成至」为流转时的进度快照(最后报工工位)。" />
<el-table :data="data.semiFlows||[]" border size="small">
<el-table-column label="动作" width="110" align="center">
<template #default="{row}">
<el-tag :type="row.action==='INBOUND' ? 'warning' : 'success'">{{ row.action==='INBOUND' ? '暂存退库' : '重上线' }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="completedText" label="完成至工位" min-width="150">
<template #default="{row}">{{ row.completedText || '尚未报工' }}</template>
</el-table-column>
<el-table-column prop="operator" label="操作人" width="110" />
<el-table-column prop="targetDock" label="目标接驳台" width="110">
<template #default="{row}">{{ row.targetDock || '-' }}</template>
</el-table-column>
<el-table-column label="时间" min-width="165">
<template #default="{row}">{{ fmt(row.createdAt) }}</template>
</el-table-column>
</el-table>
</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 />
@@ -91,7 +112,7 @@
<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>
<template #default="{row}">{{ row.bindType === 'SN' ? '序列号' : '批次' }}</template>
</el-table-column>
<el-table-column prop="bindValue" label="绑定值(SN/批次)" min-width="140" />
<el-table-column prop="operator" label="绑定人" width="90" />
@@ -109,7 +130,7 @@
<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>
<template #default="{row}">{{ row.manageMode === '2' ? '序列号' : '批次' }}</template>
</el-table-column>
</el-table>
</el-tab-pane>
+11 -9
View File
@@ -134,7 +134,7 @@
执行路线{{ routeText }}
<span class="combo-route-tip">执行顺序=工位号升序可跳过未选工位</span>
</div>
<div class="combo-tip">默认每个已绑定工艺的工位全选至少选一道工艺全不选某工艺则该工艺不进入组合</div>
<div class="combo-tip">仅显示已绑定启用工位的工艺默认每个工艺的启用工位全选至少选一道工艺全不选某工艺则该工艺不进入组合</div>
</div>
</el-form-item>
</el-form>
@@ -246,16 +246,13 @@ const flowGroups = computed(() => {
flows.value.forEach((f) => {
if (map.has(f.id) && f.name) map.get(f.id).flowName = f.name
})
const arr = Array.from(map.values())
arr.sort((a, b) => {
if (a.flowId === 0) return 1
if (b.flowId === 0) return -1
return (a.flowName || '').localeCompare(b.flowName || '', 'zh')
})
// 只显示「至少绑定了一个启用工位」的工艺;未绑定工位、或绑定的工位全部停用的工艺整组隐藏
const arr = Array.from(map.values()).filter((g) => g.flowId > 0 && g.stations.some((s) => s.isActive))
arr.sort((a, b) => (a.flowName || '').localeCompare(b.flowName || '', 'zh'))
return arr.map((g) => ({
key: g.flowId,
flowId: g.flowId,
title: g.flowId ? (g.flowName || `工艺${g.flowId}`) : '未绑定工艺',
title: g.flowName || `工艺${g.flowId}`,
stations: [...g.stations].sort((a, b) => (a.stationNo || 0) - (b.stationNo || 0))
}))
})
@@ -272,7 +269,12 @@ const routeText = computed(() => flowCombination.value.map((c) => c.stationNo).j
const hasCombination = (row) => Array.isArray(row.flowCombination) && row.flowCombination.length > 0
function defaultSelStations() {
return stations.value.filter((s) => s.flowId > 0).map((s) => s.stationNo).sort((a, b) => a - b)
// 默认全选「可见工艺组」内的启用工位(被隐藏工艺组下的工位不参与组合)
const visible = new Set(flowGroups.value.map((g) => g.flowId))
return stations.value
.filter((s) => s.flowId > 0 && visible.has(s.flowId) && s.isActive)
.map((s) => s.stationNo)
.sort((a, b) => a - b)
}
async function loadComboData() {