From addf83dddbdd144c2f5c33b4027400e7bd1edb67 Mon Sep 17 00:00:00 2001 From: "zangch@mesnac.com" Date: Sun, 22 Feb 2026 22:36:27 +0800 Subject: [PATCH] =?UTF-8?q?feat(mes):=20=E5=AE=8C=E5=96=84=E6=B7=B7?= =?UTF-8?q?=E5=90=88=E8=BF=BD=E6=BA=AF=E5=8A=9F=E8=83=BD=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=E5=92=8CAPI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在API中添加MixTraceDetailQuery类型定义和参数支持 - 将追溯详情接口改为可传递查询参数模式 - 重构追溯页面布局为左右分栏结构,左侧显示配方树和托盘条码输入 - 添加右侧列表显示优化的追溯数据表格 - 实现底部双标签页显示每车基本信息和每车明细信息 - 移除原有的抽屉式详情展示,改用内嵌标签页方式 - 添加配方树搜索过滤功能 - 新增托盘条码追溯功能 - 优化表格列显示,调整为更合理的字段展示 - 添加批量导出功能按钮 - 更新类型定义,添加realWeight、testResult等新字段 - 添加新的批次追溯子页面,包含完整的批次查询和导航功能 --- src/api/mes/mixTrace/index.ts | 9 +- src/api/mes/mixTrace/types.ts | 23 + src/views/mes/mixTrace/index.decoded.vue | 1138 ++++++++++++++++++++++ src/views/mes/mixTrace/index.vue | 476 +++++---- src/views/mes/mixTrace/lot/index.vue | 338 +++++++ 5 files changed, 1790 insertions(+), 194 deletions(-) create mode 100644 src/views/mes/mixTrace/index.decoded.vue create mode 100644 src/views/mes/mixTrace/lot/index.vue diff --git a/src/api/mes/mixTrace/index.ts b/src/api/mes/mixTrace/index.ts index c1459d1..c010cce 100644 --- a/src/api/mes/mixTrace/index.ts +++ b/src/api/mes/mixTrace/index.ts @@ -3,6 +3,7 @@ import { AxiosPromise } from 'axios'; import { MixTraceListVO, MixTraceDetailVO, + MixTraceDetailQuery, MixTraceSpcSampleVO, MixTraceSpcResultVO, MixTraceQuery, @@ -35,10 +36,14 @@ export const exportMixTrace = (query?: MixTraceQuery): AxiosPromise => { /** * 追溯详情(图9) */ -export const getMixTraceDetail = (recipeId: string | number): AxiosPromise => { +export const getMixTraceDetail = ( + recipeId: string | number, + query?: MixTraceDetailQuery +): AxiosPromise => { return request({ url: '/mes/mixTrace/detail/' + recipeId, - method: 'get' + method: 'get', + params: query }); }; diff --git a/src/api/mes/mixTrace/types.ts b/src/api/mes/mixTrace/types.ts index 192020d..7cd23eb 100644 --- a/src/api/mes/mixTrace/types.ts +++ b/src/api/mes/mixTrace/types.ts @@ -35,7 +35,11 @@ export interface MixTraceListVO { classTeamName?: string; planAmount?: number; completeAmount?: number; + realWeight?: number; + testResult?: string; trainNumber?: number; + totalTrainNo?: number; + planDetailStatus?: string; realBeginTime?: string; realEndTime?: string; } @@ -97,6 +101,8 @@ export interface MixTraceSummaryVO { dischargePower?: number; dischargeEnergy?: number; mixingStatus?: string; + testResult?: string; + recipeTime?: number; mixingTime?: number; consumeTime?: number; intervalTime?: number; @@ -119,8 +125,18 @@ export interface MixTraceMaterialTraceTreeNode { export interface MixTraceUsageItem { usageId?: string | number; + recipeId?: string | number; + planDetailId?: string | number; + weightId?: string | number; weightSeq?: number; + traceWeightSeq?: number; + traceWeightId?: string | number; + traceActCode?: string; + traceRowCount?: number; + jarTypeName?: string; categoryName?: string; + wareNum?: number; + weighNum?: string; materialName?: string; setWeight?: number; actualWeight?: number; @@ -130,6 +146,8 @@ export interface MixTraceUsageItem { controlMode?: string; actCode?: string; actName?: string; + actionType?: string; + actionStatus?: string; } export interface MixTraceStepItem { @@ -195,6 +213,8 @@ export interface RecipeWeightItem { fatherCode: string | number; unitId: string | number; childCode: number; + /** 物料名称(关联base_material_info) */ + materialName?: string; ifUseBat: string; maxRate: string; } @@ -292,6 +312,7 @@ export interface MixTraceSpcResultVO { * 追溯查询参数 */ export interface MixTraceQuery { + recipeId?: string | number; recipeCode?: string; machineId?: string | number; machineName?: string; @@ -313,6 +334,8 @@ export interface MixTraceQuery { classTeamId?: string | number; shiftName?: string; classTeamName?: string; + trainNumberStart?: string | number; + trainNumberEnd?: string | number; beginDate?: string; endDate?: string; diff --git a/src/views/mes/mixTrace/index.decoded.vue b/src/views/mes/mixTrace/index.decoded.vue new file mode 100644 index 0000000..e4356ba --- /dev/null +++ b/src/views/mes/mixTrace/index.decoded.vue @@ -0,0 +1,1138 @@ + + + + + + + diff --git a/src/views/mes/mixTrace/index.vue b/src/views/mes/mixTrace/index.vue index 0b9c02e..c5b6192 100644 --- a/src/views/mes/mixTrace/index.vue +++ b/src/views/mes/mixTrace/index.vue @@ -49,48 +49,176 @@ - - + + + + + + + + + + + +
+
托盘条码
+ + 耗用本车生产追溯 +
+
+
- - - - - - - - - - - - - - - - - - - @@ -331,12 +346,24 @@ const { recipe_state, mix_trace_spc_param } = toRefs(proxy?.useDict('recipe const activeTab = ref('trace'); const showSearch = ref(true); +/** 当前选中行(列表点击后触发详情加载) */ +const selectedRow = ref(null); +/** 底部详情Tab:basic=每车基本信息, detail=每车明细信息 */ +const detailTab = ref('basic'); +/** 配方树数据(从列表按 recipeId 分组构建) */ +const recipeTreeData = ref([]); +const recipeTreeRef = ref(); +const traceTableRef = ref(); +const treeFilterText = ref(''); +const trayBarcode = ref(''); + const traceLoading = ref(false); const traceList = ref([]); const traceTotal = ref(0); const traceDateRange = ref([]); const traceQueryFormRef = ref(); const traceQuery = reactive({ + recipeId: undefined, recipeCode: undefined, planCode: undefined, planDetailCode: undefined, @@ -383,8 +410,17 @@ const handleTraceQuery = () => { const resetTraceQuery = () => { traceQueryFormRef.value?.resetFields(); traceDateRange.value = []; + // 清除配方树点击设置的 recipeId 和托盘条码 + traceQuery.recipeId = undefined; + traceQuery.productionBarcode = undefined; + trayBarcode.value = ''; traceQuery.pageNum = 1; traceQuery.pageSize = 20; + // 清除选中状态 + selectedRow.value = null; + detailData.value = null; + // 清除配方树高亮 + recipeTreeRef.value?.setCurrentKey(null); handleTraceQuery(); }; @@ -393,7 +429,6 @@ const handleExport = () => { download('/mes/mixTrace/export', { ...traceQuery }, `mix_trace_${Date.now()}.xlsx`); }; -const detailVisible = ref(false); const detailData = ref(null); const detailCurveChartRef = ref(); let detailCurveChart: echarts.ECharts | null = null; @@ -421,7 +456,7 @@ const usageDisplayList = computed(() => { return recipeWeightList.value.map((item: any, index: number) => ({ weightSeq: item.weightSeq ?? index + 1, categoryName: item.weightType || '-', - materialName: item.childCode || '-', + materialName: item.materialName || item.childCode || '-', setWeight: item.setWeight, actualWeight: '-', tolerance: item.errorAllow, @@ -468,9 +503,69 @@ const curveDisplayList = computed(() => { })); }); -const openDetail = async (row: any) => { +/** 点击列表行时加载追溯详情(底部Tab展示,非Drawer) */ +const handleRowSelect = (row: any) => { + if (!row) { + selectedRow.value = null; + detailData.value = null; + return; + } + selectedRow.value = row; + loadDetail(row); +}; + +/** 配方树节点点击:按 recipeId 过滤列表 */ +const handleTreeNodeClick = (node: any) => { + if (node.recipeId) { + traceQuery.recipeId = node.recipeId; + handleTraceQuery(); + } +}; + +/** 配方树过滤 */ +const filterTreeNode = (value: string, data: any) => { + if (!value) return true; + return (data.label || '').toLowerCase().includes(value.toLowerCase()); +}; + +/** 托盘条码追溯 */ +const handleTrayTrace = () => { + if (!trayBarcode.value) { + proxy?.$modal?.msgWarning?.('请输入托盘条码'); + return; + } + traceQuery.productionBarcode = trayBarcode.value; + handleTraceQuery(); +}; + +/** 从列表数据构建配方树(父=配方编码+物料名,子=该配方下的记录) */ +const buildRecipeTree = (list: any[]) => { + const grouped = new Map(); + for (const item of list) { + const key = String(item.recipeId); + if (!grouped.has(key)) { + grouped.set(key, { + label: `${item.recipeCode || ''} (${item.materialName || ''})`, + recipeId: item.recipeId, + children: [] + }); + } + grouped.get(key)!.children.push({ + id: `${key}_${item.planDetailId || item.trainNumber || Math.random()}`, + label: `车次${item.trainNumber ?? '-'} ${item.planCode || ''}`, + recipeId: item.recipeId, + planDetailId: item.planDetailId + }); + } + recipeTreeData.value = Array.from(grouped.values()).map((g, i) => ({ + id: `recipe_${g.recipeId || i}`, + ...g + })); +}; + +/** 加载追溯详情到底部Tab */ +const loadDetail = async (row: any) => { const requestSeq = ++detailRequestSeq; - detailVisible.value = true; detailData.value = null; const q: MixTraceDetailQuery = { @@ -485,27 +580,17 @@ const openDetail = async (row: any) => { try { const res = await getMixTraceDetail(row.recipeId, q); - if (requestSeq !== detailRequestSeq) { - return; - } + if (requestSeq !== detailRequestSeq) return; if (!res?.data) { proxy?.$modal?.msgWarning?.('未查询到追溯详情'); - detailCurveChart?.dispose(); - detailCurveChart = null; - detailVisible.value = false; return; } detailData.value = res.data; await nextTick(); renderDetailCurve(); } catch { - if (requestSeq !== detailRequestSeq) { - return; - } + if (requestSeq !== detailRequestSeq) return; detailData.value = null; - detailCurveChart?.dispose(); - detailCurveChart = null; - detailVisible.value = false; proxy?.$modal?.msgError?.('加载追溯详情失败'); } }; @@ -805,17 +890,32 @@ watch( { immediate: true } ); -watch(detailVisible, (visible) => { - if (!visible) { - detailData.value = null; - detailCurveChart?.dispose(); - detailCurveChart = null; +/** 列表数据变化时构建配方树 */ +watch(traceList, (list) => { + buildRecipeTree(list); +}); + +/** 配方树搜索过滤 */ +watch(treeFilterText, (val) => { + recipeTreeRef.value?.filter(val); +}); + +/** 切换到明细Tab时渲染曲线(lazy tab-pane 中 ref 需延迟绑定) */ +watch(detailTab, async (tab) => { + if (tab === 'detail' && detailData.value) { + await nextTick(); + renderDetailCurve(); } }); onMounted(() => { getTraceList(); window.addEventListener('resize', handleResize); + // 重置查询时清除选中状态 + watch(() => traceQuery.pageNum, () => { + selectedRow.value = null; + detailData.value = null; + }); }); onBeforeUnmount(() => { @@ -829,39 +929,31 @@ onBeforeUnmount(() => { diff --git a/src/views/mes/mixTrace/lot/index.vue b/src/views/mes/mixTrace/lot/index.vue new file mode 100644 index 0000000..ee36941 --- /dev/null +++ b/src/views/mes/mixTrace/lot/index.vue @@ -0,0 +1,338 @@ + + + + +