Files
bj_power/bj_power_mes/frontend/src/pages/ProcessCard.vue
T
SunYF bf3a408839 refactor: 统一术语BOM为物料清单并补充工位扩展能力
主要变更:
1.  后端:重构工位管理逻辑,新增工位自定义扩展能力,将工位号上限调整为999并支持14+虚拟工位
2.  前后端:全量替换"BOM"术语为"物料清单",包括提示语、注释和文档
3.  WMS模块:新增AGV任务取消接口与前端页面,对接海康RCS取消任务逻辑
4.  权限与种子数据:新增工位新增权限并更新权限配置
5.  修复错误校验逻辑,优化AGV客户端签名与请求处理
2026-09-17 19:41:00 +08:00

207 lines
10 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
<el-card>
<el-alert type="info" :closable="false" style="margin-bottom:12px"
title="产品型号全景流程卡:按产品型号全方位展示制造全流程——工位组合 → 每工位工艺流程与步骤(采集方式 / 检测点 / 拧紧 / 考核标准 / 工艺图纸)→ 物料清单(含装配工位、单台用量、损耗、需求总量)→ 检验卡项 → 各环节时间。可打印或另存为 PDF。" />
<el-form inline>
<el-form-item label="产品型号">
<el-select v-model="productTypeId" filterable clearable placeholder="请选择产品型号" style="width:260px" @change="onProductChange">
<el-option v-for="p in productTypes" :key="p.id" :label="p.name + (p.code ? ' / ' + p.code : '')" :value="p.id" />
</el-select>
</el-form-item>
<el-form-item label="产品编号">
<el-input v-model="productCode" placeholder="也可直接按产品编号生成" clearable style="width:220px" />
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="loading" @click="load">生成全景流程卡</el-button>
<el-button :disabled="!card" @click="printCard">打印 / 导出 PDF</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card v-if="card" style="margin-top:12px">
<template #header>
<div class="hd">
<span class="t">产品型号全景流程卡</span>
<span class="sub">{{ card.productName }}{{ card.productCode }}</span>
</div>
</template>
<el-descriptions :column="4" border size="small" style="margin-bottom:14px">
<el-descriptions-item label="产品名称">{{ card.productName || '-' }}</el-descriptions-item>
<el-descriptions-item label="产品编号">{{ card.productCode || '-' }}</el-descriptions-item>
<el-descriptions-item label="产品分类">{{ card.category || '-' }}</el-descriptions-item>
<el-descriptions-item label="工位数">{{ card.stationCount }}</el-descriptions-item>
<el-descriptions-item label="工位组合" :span="2">{{ card.processSeq || '-' }}</el-descriptions-item>
<el-descriptions-item label="组合来源" :span="2">{{ card.seqSource }}</el-descriptions-item>
<el-descriptions-item label="参考工单">{{ card.orderNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="计划开始">{{ card.planStart || '-' }}</el-descriptions-item>
<el-descriptions-item label="计划结束">{{ card.planEnd || '-' }}</el-descriptions-item>
<el-descriptions-item label="生成时间">{{ card.generatedAt }}</el-descriptions-item>
</el-descriptions>
<el-tabs v-model="tab">
<!-- 工位流程总览 -->
<el-tab-pane label="工位流程总览" name="stations">
<el-table :data="card.stations" border size="small">
<el-table-column prop="stationNo" label="工位号" width="80" align="center" />
<el-table-column prop="stationName" label="工位名称" min-width="130" show-overflow-tooltip />
<el-table-column label="工艺流程" min-width="180" show-overflow-tooltip>
<template #default="{ row }">
<span v-if="row.flowName">{{ row.flowName }}</span>
<span v-else class="off">未绑定流程</span>
</template>
</el-table-column>
<el-table-column prop="flowStatus" label="流程状态" width="90" align="center" />
<el-table-column prop="stepCount" label="步骤数" width="80" align="center" />
<el-table-column label="检验点" width="80" align="center">
<template #default="{ row }">
<el-tag v-if="row.checkCount" type="warning" size="small">{{ row.checkCount }}</el-tag>
<span v-else>0</span>
</template>
</el-table-column>
<el-table-column prop="torqueCount" label="拧紧步骤" width="90" align="center" />
<el-table-column label="工艺图纸" min-width="150" show-overflow-tooltip>
<template #default="{ row }">
<span v-if="row.pdfFile">{{ row.pdfFile }}</span>
<span v-else class="off">-</span>
</template>
</el-table-column>
<el-table-column label="作业时间(本型号实绩)" min-width="190">
<template #default="{ row }">
<span v-if="row.refTime">{{ row.refTime }}</span>
<span v-else class="off">暂无实绩</span>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<!-- 工艺步骤明细 -->
<el-tab-pane label="工艺流程与步骤" name="steps">
<el-empty v-if="!card.stations.some((s) => s.steps && s.steps.length)" description="暂无工艺流程步骤" />
<div v-for="st in card.stations.filter((s) => s.steps && s.steps.length)" :key="st.stationNo" class="blk">
<div class="blk-t">工位{{ st.stationNo }} {{ st.stationName }} · {{ st.flowName || '未绑定流程' }}</div>
<el-table :data="st.steps" border size="small">
<el-table-column prop="seq" label="步骤号" width="80" align="center" />
<el-table-column prop="name" label="步骤名称" min-width="180" show-overflow-tooltip />
<el-table-column prop="collectType" label="采集方式" width="110" align="center" />
<el-table-column label="检测确认" width="100" align="center">
<template #default="{ row }">
<el-tag v-if="row.needCheck" type="warning" size="small">需检验</el-tag>
<span v-else></span>
</template>
</el-table-column>
<el-table-column label="拧紧采集" width="100" align="center">
<template #default="{ row }">
<span v-if="row.isTorque" class="ok"></span>
<span v-else></span>
</template>
</el-table-column>
<el-table-column prop="criteria" label="考核标准" min-width="220" show-overflow-tooltip />
</el-table>
</div>
</el-tab-pane>
<!-- 物料清单 -->
<el-tab-pane label="物料清单" name="bom">
<el-empty v-if="!card.bomGroups || !card.bomGroups.length" description="该产品型号暂无物料清单" />
<div v-for="g in card.bomGroups" :key="g.bomName" class="blk">
<div class="blk-t">物料清单{{ g.bomName }}</div>
<el-table :data="g.items" border size="small">
<el-table-column prop="materialCode" label="图号" min-width="140" show-overflow-tooltip />
<el-table-column prop="materialName" label="物料名称" min-width="150" show-overflow-tooltip />
<el-table-column prop="spec" label="规格" min-width="120" show-overflow-tooltip />
<el-table-column prop="unit" label="单位" width="70" align="center" />
<el-table-column prop="manageMode" label="管理方式" width="150" align="center" />
<el-table-column prop="stationNo" label="装配工位" width="100" align="center" />
<el-table-column prop="unitQty" label="单台用量" width="90" align="center" />
<el-table-column prop="lossRate" label="损耗率(%)" width="100" align="center" />
<el-table-column prop="requiredQty" label="需求总量" width="100" align="center" />
</el-table>
</div>
</el-tab-pane>
<!-- 检验卡项 -->
<el-tab-pane :label="`检验卡项(${(card.checkItems || []).length}`" name="checks">
<el-empty v-if="!card.checkItems || !card.checkItems.length" description="该产品型号暂无检验卡项(工艺流程未设置「需要检测确认」的步骤)" />
<el-table v-else :data="card.checkItems" border size="small">
<el-table-column prop="stationNo" label="工位号" width="90" align="center" />
<el-table-column prop="stationName" label="工位名称" min-width="140" show-overflow-tooltip />
<el-table-column prop="stepName" label="检验步骤" min-width="180" show-overflow-tooltip />
<el-table-column prop="criteria" label="判定标准" min-width="240" show-overflow-tooltip />
</el-table>
</el-tab-pane>
</el-tabs>
</el-card>
<el-card v-else style="margin-top:12px">
<el-empty description="请选择产品型号(或填写产品编号)后点击「生成全景流程卡」" />
</el-card>
</div>
<PageHelp :data="helpProcessCard" page="ProcessCard" />
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import request from '../utils/request'
import { openPrintableHtml } from '../utils/print'
import PageHelp from '../components/PageHelp.vue'
import { helpProcessCard } from '../help'
const productTypes = ref([])
const productTypeId = ref(null)
const productCode = ref('')
const card = ref(null)
const tab = ref('stations')
const loading = ref(false)
function onProductChange(id) {
const p = productTypes.value.find((x) => x.id === id)
if (p) productCode.value = p.code || ''
}
async function load() {
if (!productTypeId.value && !productCode.value) {
ElMessage.warning('请选择产品型号或填写产品编号')
return
}
loading.value = true
try {
card.value = await request.get('/process-card/model', {
params: { productTypeId: productTypeId.value || 0, productCode: productCode.value, format: 'json' }
})
} catch {
card.value = null
} finally {
loading.value = false
}
}
function printCard() {
openPrintableHtml('/process-card/model', {
productTypeId: productTypeId.value || 0,
productCode: productCode.value
})
}
onMounted(async () => {
try {
productTypes.value = (await request.get('/product-types')) || []
} catch {
productTypes.value = []
}
})
</script>
<style scoped>
.hd { display: flex; align-items: baseline; gap: 12px; }
.hd .t { font-size: 16px; font-weight: 600; }
.hd .sub { color: #6b7280; font-size: 13px; }
.blk { margin-bottom: 16px; }
.blk-t { font-weight: 600; margin-bottom: 6px; color: #303133; }
.off { color: #b0b3b8; }
.ok { color: #15803d; font-weight: 600; }
</style>