feat(mes): 增加质检指标详情查看功能并调整接口配置
- 添加 MesQcIndicatorItemType 枚举定义
- 扩展 IndicatorResultDetail 接口支持分组树结构和平行测量值
- 在表单组件中增加 detail 模式和只读展示逻辑
- 实现分组树平铺为录入项列表的功能函数
- 增加质检报告 Word 导出功能
- 调整表格操作列宽度以适应新增按钮
- 修改开发环境接口代理目标地址
- 更新请求基础 URL 配置
| | |
| | | VITE_BASE=/ |
| | | |
| | | # 请求路径 |
| | | VITE_BASE_URL=http://127.0.0.1:48080 |
| | | VITE_BASE_URL=http://127.0.0.1:48081 |
| | | # 接口地址 |
| | | VITE_GLOB_API_URL=/admin-api |
| | | # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务 |
| | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace MesQcIndicatorResultApi { |
| | | /** MES 检验结果明细 */ |
| | | /** 平行测量单批次值 */ |
| | | export interface IndicatorResultMeasure { |
| | | detailId?: number; // 明细编号 |
| | | resultId?: number; // 关联检验结果 ID |
| | | measureNo?: number; // 测量批次号 |
| | | value?: string; // 检测值 |
| | | remark?: string; // 备注 |
| | | } |
| | | |
| | | /** MES 检验结果明细(接口返回为分组树,children 挂子项) */ |
| | | export interface IndicatorResultDetail { |
| | | id?: number; // 编号 |
| | | resultId?: number; // 关联检验结果 ID |
| | | indicatorId?: number; // 检测指标 ID |
| | | value?: string; // 检测值(统一存为字符串) |
| | | value?: string; // 检测值(统一存为字符串,兼容第一批测量值) |
| | | valueNumber?: number; // UI 数值绑定(提交前转字符串) |
| | | remark?: string; // 备注 |
| | | measures?: IndicatorResultMeasure[]; // 平行测量多批次值 |
| | | // 关联查询字段(来自 indicator) |
| | | indicatorName?: string; // 检测指标名称 |
| | | parentId?: number; // 父分组指标 ID |
| | | itemType?: number; // 指标项类型(1 录入项 / 2 分组) |
| | | formulaText?: string; // 公式文本 |
| | | unitText?: string; // 单位文本 |
| | | sortOrder?: number; // 排序号 |
| | | valueType?: number; // 质检值类型 |
| | | valueSpecification?: string; // 值属性 |
| | | children?: IndicatorResultDetail[]; // 分组子项 |
| | | } |
| | | |
| | | /** MES 检验结果 */ |
| | |
| | | export function deleteIndicatorResult(id: number) { |
| | | return requestClient.delete(`/mes/qc/indicator-result/delete?id=${id}`); |
| | | } |
| | | |
| | | /** 导出产品检验单 Word(qcType:1=IQC、2=IPQC、3=OQC、4=RQC;resultId 可选,默认取最早样品) */ |
| | | export function exportProductWord(params: { |
| | | qcId: number; |
| | | qcType: number; |
| | | resultId?: number; |
| | | }) { |
| | | return requestClient.download( |
| | | '/mes/qc/indicator-result/export-product-word', |
| | | { params }, |
| | | ); |
| | | } |
| | |
| | | WIP_VIRTUAL: 'WIP_VIRTUAL', |
| | | } as const; |
| | | |
| | | /** MES 质检指标项类型枚举(对应后端 MesQcIndicatorItemTypeEnum) */ |
| | | export const MesQcIndicatorItemType = { |
| | | RECORD: 1, |
| | | GROUP: 2, |
| | | } as const; |
| | | |
| | | /** MES 质检结果值类型枚举 */ |
| | | export const MesQcResultValueType = { |
| | | FLOAT: 1, |
| | |
| | | import { generateAutoCode } from '#/api/mes/md/autocode/record'; |
| | | |
| | | /** 表单类型 */ |
| | | export type FormType = 'create' | 'update'; |
| | | export type FormType = 'create' | 'detail' | 'update'; |
| | | |
| | | /** 新增/修改检测结果的表单 */ |
| | | export function useQcIndicatorResultFormSchema( |
| | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | import { MesQcResultValueType } from '@vben/constants'; |
| | | import { MesQcIndicatorItemType, MesQcResultValueType } from '@vben/constants'; |
| | | import { getDictOptions } from '@vben/hooks'; |
| | | |
| | | import { |
| | |
| | | const items = ref<MesQcIndicatorResultApi.IndicatorResultDetail[]>([]); |
| | | const ctxData = ref<CtxData>(); |
| | | |
| | | const getTitle = computed(() => |
| | | formType.value === 'update' |
| | | const isDetail = computed(() => formType.value === 'detail'); |
| | | |
| | | const getTitle = computed(() => { |
| | | if (formType.value === 'detail') { |
| | | return $t('ui.actionTitle.view', ['检验结果']); |
| | | } |
| | | return formType.value === 'update' |
| | | ? $t('ui.actionTitle.edit', ['检验结果']) |
| | | : $t('ui.actionTitle.create', ['检验结果']), |
| | | ); |
| | | : $t('ui.actionTitle.create', ['检验结果']); |
| | | }); |
| | | |
| | | /** 分组树平铺为录入项列表:分组节点不展示,仅展开其子项 */ |
| | | function flattenRecordItems( |
| | | nodes: MesQcIndicatorResultApi.IndicatorResultDetail[] | undefined, |
| | | out: MesQcIndicatorResultApi.IndicatorResultDetail[], |
| | | ) { |
| | | (nodes ?? []).forEach((node) => { |
| | | if (node.itemType === MesQcIndicatorItemType.GROUP) { |
| | | flattenRecordItems(node.children, out); |
| | | return; |
| | | } |
| | | out.push({ |
| | | ...node, |
| | | valueNumber: |
| | | (node.valueType === MesQcResultValueType.FLOAT || |
| | | node.valueType === MesQcResultValueType.INTEGER) && |
| | | node.value != null |
| | | ? Number(node.value) |
| | | : undefined, |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | /** 详情展示值:平行测量多批次顿号合并,兼容旧数据单值 */ |
| | | function measureText(item: MesQcIndicatorResultApi.IndicatorResultDetail) { |
| | | const vals = (item.measures ?? []) |
| | | .map((m) => m.value) |
| | | .filter((v) => v !== undefined && v !== null && v !== ''); |
| | | if (vals.length > 0) { |
| | | return vals.join('、'); |
| | | } |
| | | return item.value ?? ''; |
| | | } |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | |
| | | const data = modalApi.getData<CtxData>(); |
| | | ctxData.value = data; |
| | | formType.value = data.formType; |
| | | const detailMode = data.formType === 'detail'; |
| | | formApi.setDisabled(detailMode); |
| | | modalApi.setState({ showConfirmButton: !detailMode }); |
| | | modalApi.lock(); |
| | | try { |
| | | const detail = await getIndicatorResultDetail( |
| | |
| | | data.qcType, |
| | | data.id, |
| | | ); |
| | | // 回填数值字段(用于 InputNumber 绑定) |
| | | items.value = (detail.items ?? []).map((item) => ({ |
| | | ...item, |
| | | valueNumber: |
| | | (item.valueType === MesQcResultValueType.FLOAT || |
| | | item.valueType === MesQcResultValueType.INTEGER) && |
| | | item.value !== null |
| | | ? Number(item.value) |
| | | : undefined, |
| | | })); |
| | | // 分组树平铺 + 回填数值字段(用于 InputNumber 绑定) |
| | | const flat: MesQcIndicatorResultApi.IndicatorResultDetail[] = []; |
| | | flattenRecordItems(detail.items, flat); |
| | | items.value = flat; |
| | | // 设置到 values |
| | | await formApi.setValues({ |
| | | id: detail.id, |
| | |
| | | class="mb-2" |
| | | > |
| | | <AForm.Item |
| | | :label="`检测项${index + 1}:${item.indicatorName ?? ''}`" |
| | | :label="`检测项${index + 1}:${item.indicatorName ?? ''}${ |
| | | isDetail && item.unitText ? `(${item.unitText})` : '' |
| | | }`" |
| | | > |
| | | <Input |
| | | v-if="isDetail" |
| | | :value="measureText(item) || '/'" |
| | | disabled |
| | | /> |
| | | <InputNumber |
| | | v-if=" |
| | | v-else-if=" |
| | | item.valueType === MesQcResultValueType.FLOAT || |
| | | item.valueType === MesQcResultValueType.INTEGER |
| | | " |
| | |
| | | .open(); |
| | | } |
| | | |
| | | /** 查看检测结果详情(只读) */ |
| | | function handleDetail(row: MesQcIndicatorResultApi.IndicatorResult) { |
| | | formModalApi |
| | | .setData({ |
| | | formType: 'detail', |
| | | id: row.id, |
| | | qcId: props.qcId, |
| | | qcType: props.qcType, |
| | | }) |
| | | .open(); |
| | | } |
| | | |
| | | /** 编辑检测结果 */ |
| | | function handleEdit(row: MesQcIndicatorResultApi.IndicatorResult) { |
| | | formModalApi |
| | |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.detail'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.VIEW, |
| | | ifShow: readonly, |
| | | onClick: handleDetail.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | |
| | | }, |
| | | { |
| | | title: '操作', |
| | | width: 180, |
| | | width: 260, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | |
| | | import { Button, message } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { exportProductWord } from '#/api/mes/qc/indicatorresult'; |
| | | import { deleteIpqc, exportIpqc, getIpqcPage } from '#/api/mes/qc/ipqc'; |
| | | import { $t } from '#/locales'; |
| | | |
| | |
| | | async function handleExport() { |
| | | const data = await exportIpqc(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: '过程检验单.xls', source: data }); |
| | | } |
| | | |
| | | /** 下载质检报告(产品检验单 Word,打印用 Word 打开) */ |
| | | async function handleExportWord(row: MesQcIpqcApi.Ipqc) { |
| | | const hideLoading = message.loading({ content: '报告导出中...', duration: 0 }); |
| | | try { |
| | | const data = await exportProductWord({ qcId: row.id!, qcType: 2 }); |
| | | downloadFileFromBlobPart({ |
| | | fileName: `${row.code || ''}-产品质量检验单.docx`, |
| | | source: data, |
| | | }); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | |
| | | auth: ['mes:qc-ipqc:query'], |
| | | onClick: handleDetail.bind(null, row), |
| | | }, |
| | | { |
| | | label: '质检报告', |
| | | type: 'link', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['mes:qc-ipqc:query'], |
| | | onClick: handleExportWord.bind(null, row), |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | |
| | | }, |
| | | { |
| | | title: '操作', |
| | | width: 180, |
| | | width: 260, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | |
| | | import { Button, message } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { exportProductWord } from '#/api/mes/qc/indicatorresult'; |
| | | import { deleteIqc, exportIqc, getIqcPage } from '#/api/mes/qc/iqc'; |
| | | import { $t } from '#/locales'; |
| | | |
| | |
| | | async function handleExport() { |
| | | const data = await exportIqc(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: '来料检验单.xls', source: data }); |
| | | } |
| | | |
| | | /** 下载质检报告(产品检验单 Word,打印用 Word 打开) */ |
| | | async function handleExportWord(row: MesQcIqcApi.Iqc) { |
| | | const hideLoading = message.loading({ content: '报告导出中...', duration: 0 }); |
| | | try { |
| | | const data = await exportProductWord({ qcId: row.id!, qcType: 1 }); |
| | | downloadFileFromBlobPart({ |
| | | fileName: `${row.code || ''}-产品质量检验单.docx`, |
| | | source: data, |
| | | }); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | |
| | | auth: ['mes:qc-iqc:query'], |
| | | onClick: handleDetail.bind(null, row), |
| | | }, |
| | | { |
| | | label: '质检报告', |
| | | type: 'link', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['mes:qc-iqc:query'], |
| | | onClick: handleExportWord.bind(null, row), |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | |
| | | }, |
| | | { |
| | | title: '操作', |
| | | width: 220, |
| | | width: 260, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | |
| | | import { Button, message } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { exportProductWord } from '#/api/mes/qc/indicatorresult'; |
| | | import { deleteOqc, exportOqc, getOqcPage } from '#/api/mes/qc/oqc'; |
| | | import { $t } from '#/locales'; |
| | | |
| | |
| | | async function handleExport() { |
| | | const data = await exportOqc(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: '出货检验单.xls', source: data }); |
| | | } |
| | | |
| | | /** 下载质检报告(产品检验单 Word,打印用 Word 打开) */ |
| | | async function handleExportWord(row: MesQcOqcApi.Oqc) { |
| | | const hideLoading = message.loading({ content: '报告导出中...', duration: 0 }); |
| | | try { |
| | | const data = await exportProductWord({ qcId: row.id!, qcType: 3 }); |
| | | downloadFileFromBlobPart({ |
| | | fileName: `${row.code || ''}-产品质量检验单.docx`, |
| | | source: data, |
| | | }); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | |
| | | auth: ['mes:qc-oqc:query'], |
| | | onClick: handleDetail.bind(null, row), |
| | | }, |
| | | { |
| | | label: '质检报告', |
| | | type: 'link', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['mes:qc-oqc:query'], |
| | | onClick: handleExportWord.bind(null, row), |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | |
| | | }, |
| | | { |
| | | title: '操作', |
| | | width: 180, |
| | | width: 260, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | |
| | | import { Button, message } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { exportProductWord } from '#/api/mes/qc/indicatorresult'; |
| | | import { deleteRqc, exportRqc, getRqcPage } from '#/api/mes/qc/rqc'; |
| | | import { $t } from '#/locales'; |
| | | |
| | |
| | | async function handleExport() { |
| | | const data = await exportRqc(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: '退货检验单.xls', source: data }); |
| | | } |
| | | |
| | | /** 下载质检报告(产品检验单 Word,打印用 Word 打开) */ |
| | | async function handleExportWord(row: MesQcRqcApi.Rqc) { |
| | | const hideLoading = message.loading({ content: '报告导出中...', duration: 0 }); |
| | | try { |
| | | const data = await exportProductWord({ qcId: row.id!, qcType: 4 }); |
| | | downloadFileFromBlobPart({ |
| | | fileName: `${row.code || ''}-产品质量检验单.docx`, |
| | | source: data, |
| | | }); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | |
| | | auth: ['mes:qc-rqc:query'], |
| | | onClick: handleDetail.bind(null, row), |
| | | }, |
| | | { |
| | | label: '质检报告', |
| | | type: 'link', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['mes:qc-rqc:query'], |
| | | onClick: handleExportWord.bind(null, row), |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | |
| | | "/admin-api": { |
| | | changeOrigin: true, |
| | | rewrite: path => path.replace(/^\/admin-api/, ""), |
| | | target: "http://42.63.70.90:9003/admin-api", |
| | | target: "http://localhost:48081/admin-api", |
| | | ws: true, |
| | | }, |
| | | }, |