已修改27个文件
389 ■■■■■ 文件已修改
src/api/crm/business/index.ts 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/crm/business/status/index.ts 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/erp/sale/order/index.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/mdm/data.ts 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/mdm/modules/form.vue 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/business/data.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/business/detail/data.ts 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/business/detail/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/business/detail/modules/status-form.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/business/index.vue 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/business/modules/form.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/business/status/data.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/business/status/modules/form.vue 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/contract/components/detail-list.vue 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/contract/data.ts 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/contract/modules/form.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/product/components/data.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/product/components/edit-table.vue 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/sale/order/data.ts 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/sale/order/index.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/sale/order/modules/form.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/md/item/modules/product-media-list.vue 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/process-design/route/modules/product-list.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/defect/data.ts 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/defect/index.vue 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/indicator/data.ts 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/indicator/index.vue 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/crm/business/index.ts
@@ -21,7 +21,6 @@
    statusTypeName?: string;
    statusId: number;
    statusName?: string;
    endStatus: number;
    endRemark: string;
    dealTime: Date;
    totalProductPrice: number;
@@ -52,8 +51,7 @@
  /** 商机更新状态请求 */
  export interface BusinessUpdateStatusReqVO {
    id: number;
    statusId: number | undefined;
    endStatus: number | undefined;
    statusId: number;
  }
}
src/api/crm/business/status/index.ts
@@ -19,32 +19,9 @@
    id?: number;
    name: string;
    percent?: number;
    endStatus?: number;
    key?: string;
    sort?: number;
  }
}
/** 默认商机状态 */
export const DEFAULT_STATUSES = [
  {
    endStatus: 1,
    key: '结束',
    name: '赢单',
    percent: 100,
  },
  {
    endStatus: 2,
    key: '结束',
    name: '输单',
    percent: 0,
  },
  {
    endStatus: 3,
    key: '结束',
    name: '无效',
    percent: 0,
  },
] satisfies CrmBusinessStatusApi.BusinessStatusType[];
/** 查询商机状态组列表 */
export function getBusinessStatusPage(params: PageParam) {
src/api/erp/sale/order/index.ts
@@ -27,6 +27,8 @@
    contractId?: number; // 关联 CRM 合同编号
    contractNo?: string; // CRM 合同单号
    needProduction?: number; // 是否需要生产:0-不需要,1-需要
    productionStatus?: number; // 生产状态:0-未完成,1-已完成
    productionFinishTime?: Date; // 生产完成时间
    hasSalesNotice?: boolean; // 是否已生成发货通知单
    canCreateSalesNotice?: boolean; // 是否可以生成发货通知单
    items?: SaleOrderItem[]; // 销售订单产品明细列表
src/views/basicData/mdm/data.ts
@@ -18,6 +18,24 @@
let unitListCache: any[] | null = null;
let unitListPromise: Promise<any[]> | null = null;
/** 将扁平分类列表转为 TreeSelect 所需的树形结构 */
function buildCategoryTree(list: any[]): any[] {
  const map = new Map<number, any>();
  const roots: any[] = [];
  for (const item of list) {
    map.set(item.id, { label: item.name, value: item.id, children: [] });
  }
  for (const item of list) {
    const node = map.get(item.id)!;
    if (item.parentId && map.has(item.parentId)) {
      map.get(item.parentId)!.children.push(node);
    } else {
      roots.push(node);
    }
  }
  return roots;
}
/** 获取单位列表(带缓存) */
async function getUnitListCached() {
  if (unitListCache) {
@@ -33,6 +51,11 @@
    unitListPromise = null;
  });
  return unitListPromise;
}
/** 清除单位列表缓存 */
export function clearUnitListCache() {
  unitListCache = null;
}
/** 新增/修改的表单 */
@@ -80,19 +103,18 @@
      rules: "required",
    },
    {
      component: "ApiSelect",
      component: "ApiTreeSelect",
      fieldName: "categoryId",
      label: "物料分类",
      rules: "selectRequired",
      componentProps: {
        placeholder: "请选择物料分类",
        allowClear: true,
        treeDefaultExpandAll: true,
        api: async () => {
          const res = await getItemTypeSimpleList();
          return res || [];
          return buildCategoryTree(res || []);
        },
        labelField: "name",
        valueField: "id",
      },
    },
    {
src/views/basicData/mdm/modules/form.vue
@@ -11,7 +11,7 @@
import { createItem, getItem, updateItem } from '#/api/mdm/item';
import { $t } from '#/locales';
import { useFormSchema } from '../data';
import { useFormSchema, clearUnitListCache } from '../data';
defineOptions({ name: "ItemForm" });
@@ -54,6 +54,8 @@
      return;
    }
    const data = modalApi.getData<MdmItemApi.Item>();
    // 清除单位缓存,确保获取最新数据
    clearUnitListCache();
    // 设置 schema(formApi 已初始化)
    formApi.setState({ schema: useFormSchema(formApi) });
    if (data?.id) {
src/views/crm/business/data.ts
@@ -265,6 +265,7 @@
      title: '商机阶段',
      fixed: 'right',
      width: 120,
      slots: { default: 'statusName' },
    },
    {
      title: '操作',
src/views/crm/business/detail/data.ts
@@ -7,7 +7,6 @@
import { erpPriceInputFormatter, formatDateTime } from '@vben/utils';
import {
  DEFAULT_STATUSES,
  getBusinessStatusSimpleList,
} from '#/api/crm/business/status';
@@ -95,24 +94,6 @@
    },
    {
      fieldName: 'statusId',
      label: '商机状态',
      component: 'Input',
      dependencies: {
        triggerFields: [''],
        show: () => false,
      },
    },
    {
      fieldName: 'endStatus',
      label: '商机状态',
      component: 'Input',
      dependencies: {
        triggerFields: [''],
        show: () => false,
      },
    },
    {
      fieldName: 'status',
      label: '商机阶段',
      component: 'Select',
      dependencies: {
@@ -121,17 +102,12 @@
          const statusList = await getBusinessStatusSimpleList(
            formData.value?.statusTypeId ?? 0,
          );
          const statusOptions = statusList.map((item) => ({
          const options = statusList.map((item) => ({
            label: `${item.name}(赢单率:${item.percent}%)`,
            value: item.id,
          }));
          const options = DEFAULT_STATUSES.map((item) => ({
            label: `${item.name}(赢单率:${item.percent}%)`,
            value: item.endStatus,
          }));
          statusOptions.push(...options);
          return {
            options: statusOptions,
            options,
          };
        },
      },
src/views/crm/business/detail/index.vue
@@ -160,6 +160,9 @@
          <ContractDetailsList
            :biz-id="businessId"
            :biz-type="BizTypeEnum.CRM_BUSINESS"
            :customer-id="business.customerId"
            :customer-name="business.customerName"
            :business-name="business.name"
          />
        </Tabs.TabPane>
        <Tabs.TabPane tab="操作日志" key="6" :force-render="true">
src/views/crm/business/detail/modules/status-form.vue
@@ -40,13 +40,12 @@
    // 提交表单
    const data = (await formApi.getValues()) as CrmBusinessApi.Business;
    try {
      if (!data.status) {
      if (!data.statusId) {
        return;
      }
      await updateBusinessStatus({
        id: data.id,
        statusId: data.status > 0 ? data.status : undefined,
        endStatus: data.status < 0 ? -data.status : undefined,
        statusId: data.statusId,
      });
      // 关闭并提示
      await modalApi.close();
@@ -65,7 +64,6 @@
    if (!data || !data.id) {
      return;
    }
    data.status = data.endStatus === null ? data.statusId : -data.endStatus;
    formData.value = data;
    modalApi.lock();
    try {
src/views/crm/business/index.vue
@@ -8,7 +8,7 @@
import { Page, useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message, Tabs } from 'ant-design-vue';
import { Button, message, Tabs, Tag } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
@@ -23,6 +23,22 @@
const { push } = useRouter();
const sceneType = ref('1');
const STATUS_COLORS: Record<string, string> = {
  拜访: 'default',
  初步接触: 'processing',
  需求确认: 'cyan',
  方案报价: 'orange',
  商务谈判: 'volcano',
  合同签订: 'success',
  赢单: 'success',
  输单: 'error',
  无效: 'default',
};
function getStatusColor(name: string): string {
  return STATUS_COLORS[name] || 'blue';
}
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
@@ -157,6 +173,9 @@
          {{ row.customerName }}
        </Button>
      </template>
      <template #statusName="{ row }">
        <Tag v-if="row.statusName" :color="getStatusColor(row.statusName)">{{ row.statusName }}</Tag>
      </template>
      <template #actions="{ row }">
        <TableAction
          :actions="[
src/views/crm/business/modules/form.vue
@@ -105,7 +105,7 @@
</script>
<template>
  <Modal :title="getTitle" class="w-1/2">
  <Modal :title="getTitle" class="w-3/4">
    <Form class="mx-4">
      <template #product="slotProps">
        <ProductEditTable
src/views/crm/business/status/data.ts
@@ -85,10 +85,9 @@
export function useFormColumns(): VxeTableGridOptions['columns'] {
  return [
    {
      field: 'endStatus',
      title: '阶段',
      minWidth: 100,
      slots: { default: 'endStatus' },
      type: 'seq',
      title: '序号',
      width: 60,
    },
    {
      field: 'name',
src/views/crm/business/status/modules/form.vue
@@ -11,7 +11,6 @@
import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
  createBusinessStatus,
  DEFAULT_STATUSES,
  getBusinessStatus,
  updateBusinessStatus,
} from '#/api/crm/business/status';
@@ -53,7 +52,6 @@
    try {
      if (formData.value?.statuses && formData.value.statuses.length > 0) {
        data.statuses = formData.value.statuses;
        data.statuses.splice(-3, 3);
      }
      await (formData.value?.id
        ? updateBusinessStatus(data)
@@ -94,8 +92,7 @@
      // 设置到 values
      await formApi.setValues(formData.value as any);
      await gridApi.grid.reloadData(
        (formData.value!.statuses =
          formData.value?.statuses?.concat(DEFAULT_STATUSES)) as any,
        (formData.value!.statuses) as any,
      );
    } finally {
      modalApi.unlock();
@@ -105,7 +102,7 @@
/** 添加状态 */
async function handleAddStatus() {
  formData.value!.statuses!.splice(-3, 0, {
  formData.value!.statuses!.push({
    name: '',
    percent: undefined,
  });
@@ -128,7 +125,7 @@
      mode: 'cell',
    },
    columns: useFormColumns(),
    data: formData.value?.statuses?.concat(DEFAULT_STATUSES),
    data: formData.value?.statuses,
    border: true,
    showOverflow: true,
    autoResize: true,
@@ -152,29 +149,20 @@
    <Form class="mx-4">
      <template #statuses>
        <Grid class="w-full">
          <template #endStatus="{ row, rowIndex }">
            <span>
              {{ row.endStatus ? '结束' : `阶段${rowIndex + 1}` }}
            </span>
          </template>
          <template #name="{ row }">
            <Input
              v-if="!row.endStatus"
              v-model:value="row.name"
              placeholder="请输入状态名"
            />
            <span v-else>{{ row.name }}</span>
          </template>
          <template #percent="{ row }">
            <InputNumber
              v-if="!row.endStatus"
              v-model:value="row.percent"
              :min="0"
              :max="100"
              :precision="2"
              placeholder="请输入赢单率"
            />
            <span v-else>{{ row.percent }}</span>
          </template>
          <template #actions="{ row, rowIndex }">
            <TableAction
@@ -182,14 +170,12 @@
                {
                  label: $t('ui.actionTitle.create'),
                  type: 'link',
                  ifShow: () => !row.endStatus,
                  onClick: handleAddStatus,
                },
                {
                  label: $t('common.delete'),
                  type: 'link',
                  danger: true,
                  ifShow: () => !row.endStatus,
                  popConfirm: {
                    title: $t('ui.actionMessage.deleteConfirm', [row.name]),
                    confirm: deleteStatusArea.bind(null, row, rowIndex),
src/views/crm/contract/components/detail-list.vue
@@ -24,6 +24,9 @@
const props = defineProps<{
  bizId: number; // 业务编号
  bizType: number; // 业务类型
  customerId?: number; // 客户编号(商机/联系人详情传入)
  customerName?: string; // 客户名称(商机/联系人详情传入)
  businessName?: string; // 商机名称(商机详情传入)
}>();
const { push } = useRouter();
@@ -52,7 +55,12 @@
        ? {
            customerId: props.bizId,
          }
        : { businessId: props.bizId },
        : {
            businessId: props.bizId,
            customerId: props.customerId,
            customerName: props.customerName,
            businessName: props.businessName,
          },
    )
    .open();
}
@@ -77,7 +85,7 @@
              customerId: props.bizId,
              ...formValues,
            });
          } else if (props.bizType === BizTypeEnum.CRM_CONTACT) {
          } else if (props.bizType === BizTypeEnum.CRM_BUSINESS) {
            return await getContractPageByBusiness({
              pageNo: page.currentPage,
              pageSize: page.pageSize,
src/views/crm/contract/data.ts
@@ -66,6 +66,10 @@
      componentProps: {
        placeholder: '请选择客户',
      },
      dependencies: {
        triggerFields: ['customerId'],
        disabled: (values) => !!values.customerId,
      },
    },
    {
      fieldName: 'orderDate',
@@ -88,9 +92,15 @@
        placeholder: '请选择商机',
      },
      dependencies: {
        triggerFields: ['customerId'],
        disabled: (values) => !values.customerId,
        triggerFields: ['customerId', 'businessId'],
        disabled: (values) => !!values.businessId || !values.customerId,
        async componentProps(values) {
          if (values.businessId && values.businessName) {
            return {
              options: [{ label: values.businessName, value: values.businessId }],
              placeholder: '请选择商机',
            };
          }
          if (!values.customerId) {
            return {
              options: [],
src/views/crm/contract/modules/form.vue
@@ -90,6 +90,10 @@
    // 加载数据
    const data = modalApi.getData<CrmContractApi.Contract>();
    if (!data || !data.id) {
      // 新增时,回显从商机/客户详情传入的数据
      if (data) {
        await formApi.setValues(data);
      }
      return;
    }
    modalApi.lock();
src/views/crm/product/components/data.ts
@@ -67,11 +67,6 @@
      minWidth: 120,
    },
    {
      field: 'itemBarCode',
      title: '条码',
      minWidth: 150,
    },
    {
      field: 'itemUnitName',
      title: '单位',
      minWidth: 80,
src/views/crm/product/components/edit-table.vue
@@ -3,15 +3,15 @@
import type { CrmContractApi } from '#/api/crm/contract';
import type { MdmItemApi } from '#/api/mdm/item';
import { nextTick, onMounted, ref, watch } from 'vue';
import { nextTick, ref, watch } from 'vue';
import { erpPriceMultiply } from '@vben/utils';
import { InputNumber, Select } from 'ant-design-vue';
import { InputNumber } from 'ant-design-vue';
import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { BizTypeEnum } from '#/api/crm/permission';
import { getItemPage } from '#/api/mdm/item';
import { MdmItemSelect } from '#/views/basicData/mdm/components';
import { $t } from '#/locales';
import { useProductEditTableColumns } from './data';
@@ -39,12 +39,11 @@
}
/** 切换产品时同步基础信息 */
function handleProductChange(itemId: any, row: any) {
  const product = productOptions.value.find((p) => p.id === itemId);
function handleProductChange(product: MdmItemApi.Item | undefined, row: any) {
  if (!product) {
    return;
  }
  row.itemId = itemId;
  row.itemId = product.id;
  row.itemName = product.name;
  row.itemCode = product.code;
  row.itemBarCode = product.barCode;
@@ -118,23 +117,15 @@
    immediate: true,
  },
);
/** 初始化 */
const productOptions = ref<MdmItemApi.Item[]>([]); // 物料下拉选项
onMounted(async () => {
  const res = await getItemPage({ pageNo: 1, pageSize: 100, status: 0 });
  productOptions.value = res.list || [];
});
</script>
<template>
  <Grid class="w-full">
    <template #itemId="{ row }">
      <Select
        v-model:value="row.itemId"
        :options="productOptions"
        :field-names="{ label: 'name', value: 'id' }"
        class="w-full"
      <MdmItemSelect
        :model-value="row.itemId"
        placeholder="请选择产品"
        @update:model-value="row.itemId = $event"
        @change="handleProductChange($event, row)"
      />
    </template>
src/views/erp/sale/order/data.ts
@@ -36,6 +36,35 @@
      },
    },
    {
      fieldName: 'productionStatus',
      label: '生产状态',
      component: 'Input',
      dependencies: {
        triggerFields: ['needProduction', 'productionStatus'],
        show: (values) => {
          return formType === 'detail' && values.needProduction === 1;
        },
      },
      componentProps: {
        disabled: true,
      },
      slots: { default: 'productionStatus' },
    },
    {
      fieldName: 'productionFinishTime',
      label: '生产完成时间',
      component: 'Input',
      dependencies: {
        triggerFields: ['productionStatus'],
        show: (values) => {
          return formType === 'detail' && values.productionStatus === 1;
        },
      },
      componentProps: {
        disabled: true,
      },
    },
    {
      fieldName: 'orderTime',
      label: '订单时间',
      component: 'DatePicker',
@@ -375,6 +404,19 @@
      },
    },
    {
      fieldName: 'productionStatus',
      label: '生产状态',
      component: 'Select',
      componentProps: {
        options: [
          { label: '未完成', value: 0 },
          { label: '已完成', value: 1 },
        ],
        placeholder: '请选择生产状态',
        allowClear: true,
      },
    },
    {
      fieldName: 'returnStatus',
      label: '退货状态',
      component: 'Select',
@@ -441,12 +483,6 @@
      minWidth: 120,
    },
    {
      field: 'outStatus',
      title: '出库状态',
      minWidth: 100,
      slots: { default: 'outStatus' },
    },
    {
      field: 'totalProductPrice',
      title: '金额合计',
      formatter: 'formatAmount2',
@@ -465,6 +501,20 @@
      minWidth: 120,
    },
    {
      field: 'outStatus',
      title: '出库状态',
      minWidth: 100,
      fixed: 'right',
      slots: { default: 'outStatus' },
    },
    {
      field: 'productionStatus',
      title: '生产状态',
      minWidth: 100,
      fixed: 'right',
      slots: { default: 'productionStatus' },
    },
    {
      field: 'status',
      title: '状态',
      minWidth: 100,
src/views/erp/sale/order/index.vue
@@ -266,6 +266,15 @@
        <Tag v-else-if="row.outStatus === 2" color="warning">部分出库</Tag>
        <Tag v-else-if="row.outStatus === 3" color="success">全部出库</Tag>
      </template>
      <template #productionStatus="{ row }">
        <template v-if="row.needProduction !== 1">
          <Tag color="default">无需生产</Tag>
        </template>
        <template v-else>
          <Tag v-if="row.productionStatus === 0" color="warning">未完成</Tag>
          <Tag v-else-if="row.productionStatus === 1" color="success">已完成</Tag>
        </template>
      </template>
      <template #actions="{ row }">
        <TableAction
          :actions="[
@@ -333,7 +342,7 @@
              type: 'link',
              icon: 'ant-design:file-protect-outlined',
              auth: ['mes:wm-sales-notice:create'],
              ifShow: () => row.status === 20 && row.canCreateSalesNotice,
              ifShow: () => row.status === 20 && (row.canCreateSalesNotice || row.productionStatus === 1),
              onClick: handleGenerateNotice.bind(null, row),
            },
            {
src/views/erp/sale/order/modules/form.vue
@@ -7,7 +7,7 @@
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { message, Tag } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { getAccountSimpleList } from '#/api/erp/finance/account';
@@ -148,6 +148,10 @@
    :show-confirm-button="formType !== 'detail'"
  >
    <Form class="mx-3">
      <template #productionStatus="{ modelValue }">
        <Tag v-if="modelValue === 0" color="warning">未完成</Tag>
        <Tag v-else-if="modelValue === 1" color="success">已完成</Tag>
      </template>
      <template #items>
        <ItemForm
          ref="itemFormRef"
src/views/mes/md/item/modules/product-media-list.vue
@@ -49,6 +49,17 @@
const isReadOnly = computed(() => props.formType === 'detail'); // 是否只读
const title = computed(() => props.kind); // 当前媒体类型标题
/** 将完整 URL 转为相对路径,确保走前端代理 */
function resolveImageUrl(url?: string): string {
  if (!url) return '';
  try {
    const { pathname } = new URL(url, window.location.origin);
    return pathname;
  } catch {
    return url;
  }
}
const loading = ref(false); // SIP/SOP 列表加载中
const formOpen = ref(false); // SIP/SOP 表单是否打开
const formLoading = ref(false); // SIP/SOP 表单提交中
@@ -219,27 +230,28 @@
      添加 {{ title }}
    </Button>
    <Spin :spinning="loading">
      <div v-if="list.length > 0" class="grid grid-cols-4 gap-3">
      <div v-if="list.length > 0" class="grid grid-cols-8 gap-1">
        <Card
          v-for="item in list"
          :key="item.id"
          hoverable
          class="!w-[120px]"
          :body-style="{ padding: '0px' }"
        >
          <Image
            v-if="item.url"
            :src="item.url"
            class="block h-40 w-full object-cover"
            :preview="{ src: item.url }"
            :src="resolveImageUrl(item.url)"
            class="block !h-[90px] !w-[120px] object-cover"
            :preview="{ src: resolveImageUrl(item.url) }"
          />
          <div
            v-else
            class="flex h-40 w-full items-center justify-center bg-gray-100 text-gray-400"
            class="flex !h-[90px] !w-[120px] items-center justify-center bg-gray-100 text-gray-400"
          >
            暂无图片
          </div>
          <div class="p-3">
            <div class="mb-1 truncate text-sm font-bold">{{ item.title }}</div>
          <div class="p-2">
            <div class="mb-1 truncate text-xs font-bold">{{ item.title }}</div>
            <div v-if="item.description" class="truncate text-xs text-gray-500">
              {{ item.description }}
            </div>
src/views/mes/process-design/route/modules/product-list.vue
@@ -112,7 +112,7 @@
      <TableAction
        :actions="[
          {
            label: $t('common.edit'),
            label: '配置投料',
            type: 'link',
            ifShow: () => isEditable,
            onClick: handleEdit.bind(null, row),
src/views/mes/qc/defect/data.ts
@@ -150,10 +150,7 @@
      field: 'type',
      title: '检测项类型',
      width: 120,
      cellRender: {
        name: 'CellDict',
        props: { type: DICT_TYPE.MES_INDICATOR_TYPE },
      },
      slots: { default: 'type' },
    },
    {
      field: 'level',
src/views/mes/qc/defect/index.vue
@@ -5,10 +5,12 @@
import { Page, useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart } from '@vben/utils';
import { message } from 'ant-design-vue';
import { message, Tag } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteDefect, exportDefect, getDefectPage } from '#/api/mes/qc/defect';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
@@ -18,6 +20,23 @@
  connectedComponent: Form,
  destroyOnClose: true,
});
const TAG_COLORS = ['blue', 'green', 'orange', 'purple', 'cyan', 'magenta', 'geekblue', 'volcano'] as const;
const typeDictOptions = getDictOptions(DICT_TYPE.MES_INDICATOR_TYPE, 'number');
const typeColorMap = new Map<number, string>();
typeDictOptions.forEach((item, index) => {
  typeColorMap.set(item.value as number, TAG_COLORS[index % TAG_COLORS.length]!);
});
function getTypeColor(type: number): string {
  return typeColorMap.get(type) || 'default';
}
function getTypeLabel(type: number): string {
  const item = typeDictOptions.find((d) => d.value === type);
  return item?.label || String(type);
}
/** 刷新表格 */
function handleRefresh() {
@@ -110,6 +129,11 @@
          ]"
        />
      </template>
      <template #type="{ row }">
        <Tag v-if="row.type !== undefined && row.type !== null" :color="getTypeColor(row.type)">
          {{ getTypeLabel(row.type) }}
        </Tag>
      </template>
      <template #actions="{ row }">
        <TableAction
          :actions="[
src/views/mes/qc/indicator/data.ts
@@ -186,10 +186,7 @@
      field: 'type',
      title: '检测项类型',
      width: 120,
      cellRender: {
        name: 'CellDict',
        props: { type: DICT_TYPE.MES_INDICATOR_TYPE },
      },
      slots: { default: 'type' },
    },
    {
      field: 'tool',
@@ -273,10 +270,7 @@
      field: 'type',
      title: '检测项类型',
      width: 120,
      cellRender: {
        name: 'CellDict',
        props: { type: DICT_TYPE.MES_INDICATOR_TYPE },
      },
      slots: { default: 'type' },
    },
    {
      field: 'tool',
src/views/mes/qc/indicator/index.vue
@@ -5,7 +5,7 @@
import { Page, useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart } from '@vben/utils';
import { message } from 'ant-design-vue';
import { message, Tag } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
@@ -13,6 +13,8 @@
  exportIndicator,
  getIndicatorPage,
} from '#/api/mes/qc/indicator';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
@@ -22,6 +24,23 @@
  connectedComponent: Form,
  destroyOnClose: true,
});
const TAG_COLORS = ['blue', 'green', 'orange', 'purple', 'cyan', 'magenta', 'geekblue', 'volcano'] as const;
const typeDictOptions = getDictOptions(DICT_TYPE.MES_INDICATOR_TYPE, 'number');
const typeColorMap = new Map<number, string>();
typeDictOptions.forEach((item, index) => {
  typeColorMap.set(item.value as number, TAG_COLORS[index % TAG_COLORS.length]!);
});
function getTypeColor(type: number): string {
  return typeColorMap.get(type) || 'default';
}
function getTypeLabel(type: number): string {
  const item = typeDictOptions.find((d) => d.value === type);
  return item?.label || String(type);
}
/** 刷新表格 */
function handleRefresh() {
@@ -114,6 +133,11 @@
          ]"
        />
      </template>
      <template #type="{ row }">
        <Tag v-if="row.type !== undefined && row.type !== null" :color="getTypeColor(row.type)">
          {{ getTypeLabel(row.type) }}
        </Tag>
      </template>
      <template #actions="{ row }">
        <TableAction
          :actions="[