11 小时以前 c1a9bd81744474027abc73abcc778fd2798ec7ec
feat(mes): 增加质检指标详情查看功能并调整接口配置

- 添加 MesQcIndicatorItemType 枚举定义
- 扩展 IndicatorResultDetail 接口支持分组树结构和平行测量值
- 在表单组件中增加 detail 模式和只读展示逻辑
- 实现分组树平铺为录入项列表的功能函数
- 增加质检报告 Word 导出功能
- 调整表格操作列宽度以适应新增按钮
- 修改开发环境接口代理目标地址
- 更新请求基础 URL 配置
已修改15个文件
235 ■■■■ 文件已修改
.env.development 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/qc/indicatorresult/index.ts 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/packages/constants/src/biz-mes-enum.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/indicatorresult/components/data.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/indicatorresult/components/form.vue 76 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/indicatorresult/components/list.vue 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/ipqc/data.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/ipqc/index.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/iqc/data.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/iqc/index.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/oqc/data.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/oqc/index.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/rqc/data.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/rqc/index.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.development
@@ -4,7 +4,7 @@
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服务
src/api/mes/qc/indicatorresult/index.ts
@@ -3,18 +3,34 @@
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 检验结果 */
@@ -71,3 +87,15 @@
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 },
  );
}
src/packages/constants/src/biz-mes-enum.ts
@@ -440,6 +440,12 @@
  WIP_VIRTUAL: 'WIP_VIRTUAL',
} as const;
/** MES 质检指标项类型枚举(对应后端 MesQcIndicatorItemTypeEnum) */
export const MesQcIndicatorItemType = {
  RECORD: 1,
  GROUP: 2,
} as const;
/** MES 质检结果值类型枚举 */
export const MesQcResultValueType = {
  FLOAT: 1,
src/views/mes/qc/indicatorresult/components/data.ts
@@ -11,7 +11,7 @@
import { generateAutoCode } from '#/api/mes/md/autocode/record';
/** 表单类型 */
export type FormType = 'create' | 'update';
export type FormType = 'create' | 'detail' | 'update';
/** 新增/修改检测结果的表单 */
export function useQcIndicatorResultFormSchema(
src/views/mes/qc/indicatorresult/components/form.vue
@@ -6,7 +6,7 @@
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 {
@@ -43,11 +43,49 @@
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: {
@@ -135,6 +173,9 @@
    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(
@@ -142,16 +183,10 @@
        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,
@@ -179,10 +214,17 @@
          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
              "
src/views/mes/qc/indicatorresult/components/list.vue
@@ -43,6 +43,18 @@
    .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
@@ -127,6 +139,13 @@
              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,
src/views/mes/qc/ipqc/data.ts
@@ -491,7 +491,7 @@
    },
    {
      title: '操作',
      width: 180,
      width: 260,
      fixed: 'right',
      slots: { default: 'actions' },
    },
src/views/mes/qc/ipqc/index.vue
@@ -9,6 +9,7 @@
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';
@@ -59,6 +60,20 @@
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({
@@ -151,6 +166,13 @@
              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>
src/views/mes/qc/iqc/data.ts
@@ -436,7 +436,7 @@
    },
    {
      title: '操作',
      width: 180,
      width: 260,
      fixed: 'right',
      slots: { default: 'actions' },
    },
src/views/mes/qc/iqc/index.vue
@@ -12,6 +12,7 @@
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';
@@ -66,6 +67,20 @@
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({
@@ -165,6 +180,13 @@
              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>
src/views/mes/qc/oqc/data.ts
@@ -439,7 +439,7 @@
    },
    {
      title: '操作',
      width: 220,
      width: 260,
      fixed: 'right',
      slots: { default: 'actions' },
    },
src/views/mes/qc/oqc/index.vue
@@ -9,6 +9,7 @@
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';
@@ -59,6 +60,20 @@
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({
@@ -151,6 +166,13 @@
              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>
src/views/mes/qc/rqc/data.ts
@@ -417,7 +417,7 @@
    },
    {
      title: '操作',
      width: 180,
      width: 260,
      fixed: 'right',
      slots: { default: 'actions' },
    },
src/views/mes/qc/rqc/index.vue
@@ -9,6 +9,7 @@
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';
@@ -59,6 +60,20 @@
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({
@@ -151,6 +166,13 @@
              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>
vite.config.ts
@@ -72,7 +72,7 @@
          "/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,
          },
        },