gaoluyang
4 小时以前 0c9aa3e1d5cfbf2245f9e7462e1d27cc8c7827d1
feat: 客户签订合同展示、质检合格率卡片、生产订单选择默认状态修正

- CRM 客户列表/详情:新增已签订合同数量与金额,重点客户(A)排前
- MES 首页/分析台 KPI 卡片:新增来料/过程/出货质检合格率
- BI 大屏调整
- 批次追溯:移除「向前追溯」「采购来源」页签,默认向后追溯
- 生产订单选择:IPQC 及 14 处选择器移除默认「已确认」状态过滤,避免工单均为已完成而搜不到订单

Co-Authored-By: Claude Code <noreply@anthropic.com>
已修改27个文件
418 ■■■■■ 文件已修改
src/api/crm/customer/index.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/home/index.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/bi/dashboard/index.vue 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/customer/data.ts 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/customer/detail/data.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/customer/detail/index.vue 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/customer/index.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/customer/pool/data.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dashboard/analytics/modules/kpi-cards.vue 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/home/modules/kpi-cards.vue 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/andon/record/data.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/card/data.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/feedback/data.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/batchtrace/modules/trace-detail.vue 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/ipqc/data.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/workbench/components/tabs/ProductIssueFormModal.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/workbench/components/tabs/ProductIssueTab.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/workbench/components/tabs/ReturnIssueFormModal.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/workbench/components/tabs/ReturnIssueTab.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/barcode/data.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/outsourceissue/data.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/outsourcereceipt/data.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/packages/data.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productissue/data.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productreceipt/data.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnissue/data.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/crm/customer/index.ts
@@ -18,7 +18,6 @@
    ownerUserDept?: string; // 负责人的部门名称
    ownerUserDeptName?: string; // 负责人的部门名称
    lockStatus?: boolean;
    dealStatus?: boolean;
    autoPoolEnabled?: boolean; // 是否流入公海,关闭时取消公海可见度
    mobile: string; // 手机号
    telephone: string; // 电话
@@ -37,6 +36,8 @@
    createTime: Date; // 创建时间
    updateTime: Date; // 更新时间
    poolDay?: number; // 距离进入公海天数
    contractCount?: number; // 已签订合同数量(审批状态为审核通过),没有则为 0
    contractTotalPrice?: null | number; // 已签订合同金额合计(审核通过),没有则为 null
  }
  /** 客户导入请求 */
@@ -134,13 +135,6 @@
/** 客户放入公海 */
export function putCustomerPool(id: number) {
  return requestClient.put(`/crm/customer/put-pool?id=${id}`);
}
/** 更新客户的成交状态 */
export function updateCustomerDealStatus(id: number, dealStatus: boolean) {
  return requestClient.put(
    `/crm/customer/update-deal-status?id=${id}&dealStatus=${dealStatus}`,
  );
}
/** 进入公海客户提醒的客户列表 */
src/api/mes/home/index.ts
@@ -10,6 +10,12 @@
    yesterdayOutput: number; // 昨日产量
    todayQualifiedQuantity: number; // 今日合格品数
    todayUnqualifiedQuantity: number; // 今日不良品数
    iqcQualifiedRate: null | number; // 来料检验合格率(%),无检验数据时为 null
    ipqcQualifiedRate: null | number; // 过程检验合格率(%),无检验数据时为 null
    oqcQualifiedRate: null | number; // 出货检验合格率(%),无检验数据时为 null
    iqcCheckQuantity: number; // 来料检验的检验数量合计(合格率分母),无数据时为 0
    ipqcCheckQuantity: number; // 过程检验的检验数量合计(合格率分母),无数据时为 0
    oqcCheckQuantity: number; // 出货检验的检验数量合计(合格率分母),无数据时为 0
    machineryTotal: number; // 设备总数
    machineryProducing: number; // 生产中设备数
    machineryStop: number; // 停机设备数
src/views/bi/dashboard/index.vue
@@ -39,7 +39,7 @@
    loading.value = false;
    await nextTick();
    resizeAllCharts();
    if (!background && dashboardCode.value === 'purchase-sales') {
    if (!background) {
      const intervals = charts.value.map((chart) => chart.refreshInterval).filter((interval) => interval > 0);
      if (intervals.length) dataRefreshTimer = setInterval(() => {
        if (!loading.value && document.visibilityState === 'visible') loadData(true);
@@ -159,6 +159,11 @@
    return val.slice(0, 10);
  }
  return val;
}
// 大屏表格「实时滚动」:数据复制一份,配合 CSS marquee 做无缝纵向滚动
function duplicateRows(rows?: Record<string, unknown>[]): Record<string, unknown>[] {
  return rows ? [...rows, ...rows] : [];
}
// ======== 看板主题色 ========
@@ -316,9 +321,9 @@
                  >{{ key }}</th>
                </tr>
              </thead>
              <tbody>
              <tbody class="marquee-tbody">
                <tr
                  v-for="(row, ri) in chart.data"
                  v-for="(row, ri) in duplicateRows(chart.data)"
                  :key="ri"
                  :style="{ animationDelay: `${ri * 50}ms` }"
                >
@@ -610,7 +615,10 @@
}
.table-wrap {
  overflow-x: auto; border-radius: 8px;
  overflow-x: auto;
  overflow-y: hidden;
  max-height: 320px;
  border-radius: 8px;
  background: rgba(4, 14, 36, 0.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
@@ -619,6 +627,12 @@
.data-table {
  width: 100%; border-collapse: collapse; font-size: 12px;
}
.data-table thead {
  position: sticky;
  top: 0;
  z-index: 2;
}
.data-table thead th {
@@ -653,6 +667,18 @@
  to { opacity: 1; transform: translateY(0); }
}
/* 大屏表格「实时滚动」:tbody 复制一份后 translateY(-50%) 无缝循环 */
.marquee-tbody {
  animation: table-marquee 15s linear infinite;
}
.marquee-tbody:hover {
  animation-play-state: paused;
}
@keyframes table-marquee {
  from { transform: translateY(0); }
  to { transform: translateY(-50%); }
}
/* ======== 空状态 ======== */
.empty-state {
  position: relative; z-index: 1;
src/views/crm/customer/data.ts
@@ -348,13 +348,10 @@
      },
    },
    {
      field: 'dealStatus',
      title: '成交状态',
      minWidth: 120,
      cellRender: {
        name: 'CellDict',
        props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
      },
      field: 'contractCount',
      title: '签订合同',
      minWidth: 170,
      slots: { default: 'contracts' },
    },
    {
      field: 'contactLastTime',
src/views/crm/customer/detail/data.ts
@@ -52,11 +52,6 @@
      render: (val) => (val === false ? '否' : '是'),
    },
    {
      field: 'dealStatus',
      label: '成交状态',
      render: (val) => (val ? '已成交' : '未成交'),
    },
    {
      field: 'ownerUserName',
      label: '负责人',
    },
src/views/crm/customer/detail/index.vue
@@ -15,7 +15,6 @@
  lockCustomer,
  putCustomerPool,
  receiveCustomer,
  updateCustomerDealStatus,
} from '#/api/crm/customer';
import { getOperateLogPage } from '#/api/crm/operateLog';
import { BizTypeEnum } from '#/api/crm/permission';
@@ -148,23 +147,6 @@
//   return true;
// }
/** 更新成交状态操作 */
async function handleUpdateDealStatus(): Promise<boolean | undefined> {
  const dealStatus = !customer.value.dealStatus;
  try {
    await confirm(
      `确定更新成交状态为【${dealStatus ? '已成交' : '未成交'}】吗?`,
    );
  } catch {
    return false;
  }
  // 更新成交状态
  await updateCustomerDealStatus(customerId.value, dealStatus);
  // 提示并返回成功
  message.success('更新成交状态成功');
  return true;
}
/** 加载数据 */
onMounted(() => {
  customerId.value = Number(route.params.id);
@@ -199,12 +181,6 @@
            type: 'primary',
            ifShow: permissionListRef?.validateOwnerUser,
            onClick: handleTransfer,
          },
          {
            label: '更改成交状态',
            type: 'default',
            ifShow: permissionListRef?.validateWrite,
            onClick: handleUpdateDealStatus,
          },
          {
            label: '锁定',
src/views/crm/customer/index.vue
@@ -6,7 +6,7 @@
import { useRouter } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart } from '@vben/utils';
import { downloadFileFromBlobPart, erpNumberFormatter } from '@vben/utils';
import { Button, message, Tabs } from 'ant-design-vue';
@@ -166,6 +166,12 @@
          {{ row.name }}
        </Button>
      </template>
      <template #contracts="{ row }">
        <template v-if="row.contractCount">
          {{ row.contractCount }} 份 · ¥{{ erpNumberFormatter(row.contractTotalPrice, 2) }}
        </template>
        <template v-else>—</template>
      </template>
      <template #actions="{ row }">
        <TableAction
          :actions="[
src/views/crm/customer/pool/data.ts
@@ -122,15 +122,6 @@
      minWidth: 200,
    },
    {
      title: '成交状态',
      field: 'dealStatus',
      minWidth: 80,
      cellRender: {
        name: 'CellDict',
        props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
      },
    },
    {
      title: '最后跟进时间',
      field: 'contactLastTime',
      minWidth: 180,
src/views/dashboard/analytics/modules/kpi-cards.vue
@@ -22,12 +22,60 @@
  navigate: [name: string];
}>();
const qualityRate = computed(() => {
  const total =
    props.summary.todayQualifiedQuantity +
    props.summary.todayUnqualifiedQuantity;
  return total === 0 ? 0 : (props.summary.todayQualifiedQuantity / total) * 100;
});
/**
 * 质量合格率:来料 / 过程 / 出货三个检验环节,各自一张卡片
 *
 * 由后端按「合格数量 / 检验数量」计算(不过滤单据状态,无时间限制),
 * 该环节没有检验数据时后端返回 null。
 */
const qualityRates = computed(() => [
  {
    key: 'quality-iqc',
    label: '来料合格率',
    rate: props.summary.iqcQualifiedRate,
    checkQuantity: props.summary.iqcCheckQuantity,
    routeName: 'MesQcIqc',
    icon: 'lucide:package-check',
    iconClass: 'bg-gradient-to-br from-orange-500 to-amber-400 shadow-lg shadow-orange-500/25',
    textClass: 'text-orange-600 dark:text-orange-400',
    washClass: 'bg-gradient-to-br from-orange-500/5 to-amber-500/5',
    glowClass: 'bg-orange-400/10',
  },
  {
    key: 'quality-ipqc',
    label: '过程合格率',
    rate: props.summary.ipqcQualifiedRate,
    checkQuantity: props.summary.ipqcCheckQuantity,
    routeName: 'MesQcIpqc',
    icon: 'lucide:workflow',
    iconClass: 'bg-gradient-to-br from-cyan-500 to-teal-400 shadow-lg shadow-cyan-500/25',
    textClass: 'text-cyan-600 dark:text-cyan-400',
    washClass: 'bg-gradient-to-br from-cyan-500/5 to-teal-500/5',
    glowClass: 'bg-cyan-400/10',
  },
  {
    key: 'quality-oqc',
    label: '出货合格率',
    rate: props.summary.oqcQualifiedRate,
    checkQuantity: props.summary.oqcCheckQuantity,
    routeName: 'MesQcOqc',
    icon: 'lucide:truck',
    iconClass: 'bg-gradient-to-br from-pink-500 to-rose-400 shadow-lg shadow-pink-500/25',
    textClass: 'text-pink-600 dark:text-pink-400',
    washClass: 'bg-gradient-to-br from-pink-500/5 to-rose-500/5',
    glowClass: 'bg-pink-400/10',
  },
]);
/** 合格率展示文案(一位小数,与原「质量合格率」卡片一致),无数据时用「—」而不是 0% */
function formatRate(rate: null | number | undefined) {
  return rate === null || rate === undefined ? '—' : rate.toFixed(1);
}
/** 无检验数据时不渲染百分号,避免出现「—%」 */
function hasRate(rate: null | number | undefined) {
  return rate !== null && rate !== undefined;
}
const equipmentRate = computed(() => {
  if (props.summary.machineryTotal === 0) return 0;
@@ -63,9 +111,9 @@
<template>
  <Row :gutter="16">
    <!-- 生产订单 -->
    <Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
    <Col :lg="6" :md="12" :sm="12" :xl="4" :xs="24" class="mb-4">
      <div
        class="glass-tilt group overflow-hidden rounded-2xl border-0"
        class="glass-tilt group h-full overflow-hidden rounded-2xl border-0"
        :class="{ 'cursor-pointer': canNavigate('MesProWorkOrder') }"
        :style="tiltStyle('order')"
        @mousemove="(e: MouseEvent) => onMouseMove(e, 'order')"
@@ -95,9 +143,9 @@
    </Col>
    <!-- 今日产量 -->
    <Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
    <Col :lg="6" :md="12" :sm="12" :xl="4" :xs="24" class="mb-4">
      <div
        class="glass-tilt group overflow-hidden rounded-2xl border-0"
        class="glass-tilt group h-full overflow-hidden rounded-2xl border-0"
        :class="{ 'cursor-pointer': canNavigate('MesProFeedback') }"
        :style="tiltStyle('output')"
        @mousemove="(e: MouseEvent) => onMouseMove(e, 'output')"
@@ -126,44 +174,60 @@
      </div>
    </Col>
    <!-- 质量合格率 -->
    <Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
    <!-- 质量合格率:来料 / 过程 / 出货,各一张卡片 -->
    <Col
      v-for="item in qualityRates"
      :key="item.key"
      :lg="6"
      :md="12"
      :sm="12"
      :xl="4"
      :xs="24"
      class="mb-4"
    >
      <div
        class="glass-tilt group overflow-hidden rounded-2xl border-0"
        :class="{ 'cursor-pointer': canNavigate('MesQcRecord') }"
        :style="tiltStyle('quality')"
        @mousemove="(e: MouseEvent) => onMouseMove(e, 'quality')"
        @mouseleave="onMouseLeave('quality')"
        @click="canNavigate('MesQcRecord') && emit('navigate', 'MesQcRecord')"
        class="glass-tilt group h-full overflow-hidden rounded-2xl border-0"
        :class="{ 'cursor-pointer': canNavigate(item.routeName) }"
        :style="tiltStyle(item.key)"
        @mousemove="(e: MouseEvent) => onMouseMove(e, item.key)"
        @mouseleave="onMouseLeave(item.key)"
        @click="canNavigate(item.routeName) && emit('navigate', item.routeName)"
      >
        <div class="relative z-10 flex items-center gap-4 p-5">
          <div class="flex size-12 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-orange-500 to-amber-400 shadow-lg shadow-orange-500/25 transition-transform duration-300 group-hover:scale-110">
            <IconifyIcon class="size-6 text-white" icon="lucide:shield-check" />
          <div
            class="flex size-12 flex-shrink-0 items-center justify-center rounded-xl transition-transform duration-300 group-hover:scale-110"
            :class="item.iconClass"
          >
            <IconifyIcon class="size-6 text-white" :icon="item.icon" />
          </div>
          <div class="min-w-0 flex-1">
            <div class="mb-1 text-xs font-medium text-gray-500 dark:text-gray-400">质量合格率</div>
            <div class="mb-1 text-xs font-medium text-gray-500 dark:text-gray-400">{{ item.label }}</div>
            <div class="flex items-baseline gap-1">
              <CountTo
                class="text-2xl font-bold leading-tight"
                :class="qualityRate >= 95 ? 'text-emerald-600 dark:text-emerald-400' : qualityRate >= 80 ? 'text-orange-600 dark:text-orange-400' : 'text-red-600 dark:text-red-400'"
                :decimals="1" :end-val="qualityRate" :duration="1200"
              <span class="text-2xl font-bold leading-tight" :class="item.textClass">
                {{ formatRate(item.rate) }}
              </span>
              <span v-if="hasRate(item.rate)" class="text-xs text-gray-400">%</span>
            </div>
            <div class="mt-1 flex items-center gap-1 text-xs text-gray-400">
              <span>检验 {{ item.checkQuantity }}</span>
            </div>
          </div>
        </div>
        <div
          class="absolute inset-0 -z-0 opacity-0 transition-opacity duration-300 group-hover:opacity-100"
          :class="item.washClass"
              />
              <span class="text-xs text-gray-400">%</span>
            </div>
            <div class="mt-1 text-xs text-gray-400">
              合格 {{ summary.todayQualifiedQuantity }} | 不良 {{ summary.todayUnqualifiedQuantity }}
            </div>
          </div>
        </div>
        <div class="absolute inset-0 -z-0 bg-gradient-to-br from-orange-500/5 to-amber-500/5 opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
        <div class="absolute -right-6 -top-6 size-24 rounded-full bg-orange-400/10 blur-xl transition-all duration-300 group-hover:scale-150" />
        <div
          class="absolute -right-6 -top-6 size-24 rounded-full blur-xl transition-all duration-300 group-hover:scale-150"
          :class="item.glowClass"
        />
      </div>
    </Col>
    <!-- 设备运行率 -->
    <Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
    <Col :lg="6" :md="12" :sm="12" :xl="4" :xs="24" class="mb-4">
      <div
        class="glass-tilt group overflow-hidden rounded-2xl border-0"
        class="glass-tilt group h-full overflow-hidden rounded-2xl border-0"
        :class="{ 'cursor-pointer': canNavigate('MesDvMachinery') }"
        :style="tiltStyle('equip')"
        @mousemove="(e: MouseEvent) => onMouseMove(e, 'equip')"
src/views/mes/home/modules/kpi-cards.vue
@@ -18,31 +18,58 @@
  navigate: [name: string];
}>();
/** 是否有质量数据(合格品 + 不良品 > 0) */
const hasQualityData = computed(
  () =>
    props.summary.todayQualifiedQuantity +
      props.summary.todayUnqualifiedQuantity >
    0,
);
/**
 * 质量合格率:来料 / 过程 / 出货三个检验环节,各自一张卡片
 *
 * 由后端按「合格数量 / 检验数量」计算(不过滤单据状态,无时间限制),
 * 该环节没有检验数据时后端返回 null。
 */
const qualityRates = computed(() => [
  {
    label: '来料合格率',
    rate: props.summary.iqcQualifiedRate,
    checkQuantity: props.summary.iqcCheckQuantity,
    routeName: 'MesQcIqc',
    icon: 'lucide:package-check',
    color: '#e6a23c',
    colorLight: '#ebb563',
  },
  {
    label: '过程合格率',
    rate: props.summary.ipqcQualifiedRate,
    checkQuantity: props.summary.ipqcCheckQuantity,
    routeName: 'MesQcIpqc',
    icon: 'lucide:workflow',
    color: '#13c2c2',
    colorLight: '#5cdbd3',
  },
  {
    label: '出货合格率',
    rate: props.summary.oqcQualifiedRate,
    checkQuantity: props.summary.oqcCheckQuantity,
    routeName: 'MesQcOqc',
    icon: 'lucide:truck',
    color: '#eb2f96',
    colorLight: '#f759ab',
  },
]);
/** 质量合格率 = 合格品 / (合格品 + 不良品) * 100,无数据时为 0 */
const qualityRate = computed(() => {
  const total =
    props.summary.todayQualifiedQuantity +
    props.summary.todayUnqualifiedQuantity;
  if (total === 0) {
    return 0;
/** 合格率展示文案(一位小数,与原「质量合格率」卡片一致),无数据时用「—」而不是 0% */
function formatRate(rate: null | number | undefined) {
  return rate === null || rate === undefined ? '—' : rate.toFixed(1);
  }
  return (props.summary.todayQualifiedQuantity / total) * 100;
});
/** 无检验数据时不渲染百分号,避免出现「—%」 */
function hasRate(rate: null | number | undefined) {
  return rate !== null && rate !== undefined;
}
</script>
<template>
  <Row :gutter="16">
    <Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
    <Col :lg="6" :md="12" :sm="12" :xl="4" :xs="24" class="mb-4">
      <Card
        class="kpi-card cursor-pointer transition-all hover:-translate-y-1 hover:shadow-lg"
        class="kpi-card h-full cursor-pointer transition-all hover:-translate-y-1 hover:shadow-lg"
        @click="emit('navigate', 'MesProWorkOrder')"
      >
        <div class="flex items-center gap-4">
@@ -71,9 +98,9 @@
        </div>
      </Card>
    </Col>
    <Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
    <Col :lg="6" :md="12" :sm="12" :xl="4" :xs="24" class="mb-4">
      <Card
        class="kpi-card cursor-pointer transition-all hover:-translate-y-1 hover:shadow-lg"
        class="kpi-card h-full cursor-pointer transition-all hover:-translate-y-1 hover:shadow-lg"
        @click="emit('navigate', 'MesProFeedback')"
      >
        <div class="flex items-center gap-4">
@@ -100,44 +127,53 @@
        </div>
      </Card>
    </Col>
    <Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
    <Col
      v-for="item in qualityRates"
      :key="item.label"
      :lg="6"
      :md="12"
      :sm="12"
      :xl="4"
      :xs="24"
      class="mb-4"
    >
      <Card
        class="kpi-card cursor-pointer transition-all hover:-translate-y-1 hover:shadow-lg"
        @click="emit('navigate', 'MesProFeedback')"
        class="kpi-card h-full cursor-pointer transition-all hover:-translate-y-1 hover:shadow-lg"
        @click="emit('navigate', item.routeName)"
      >
        <div class="flex items-center gap-4">
          <div
            class="flex size-14 flex-shrink-0 items-center justify-center rounded-xl text-white"
            style="background: linear-gradient(135deg, #e6a23c, #ebb563)"
            :style="{ background: `linear-gradient(135deg, ${item.color}, ${item.colorLight})` }"
          >
            <IconifyIcon class="size-7" icon="lucide:circle-check" />
            <IconifyIcon class="size-7" :icon="item.icon" />
          </div>
          <div>
            <div class="text-muted-foreground mb-1 text-sm">质量合格率</div>
            <div class="text-muted-foreground mb-1 text-sm">{{ item.label }}</div>
            <div class="flex items-baseline gap-1">
              <CountTo
                class="text-2xl font-bold leading-tight text-[#e6a23c]"
                :decimals="1"
                :end-val="qualityRate"
                :duration="1500"
              />
              <span class="text-muted-foreground text-xs">%</span>
              <span
                class="text-2xl font-bold leading-tight"
                :style="{ color: item.color }"
              >
                {{ formatRate(item.rate) }}
              </span>
              <span
                v-if="hasRate(item.rate)"
                class="text-muted-foreground text-xs"
              >
                %
              </span>
            </div>
            <div class="text-muted-foreground mt-1 text-xs">
              <template v-if="hasQualityData">
                <span>合格 {{ summary.todayQualifiedQuantity }}</span>
                <Divider type="vertical" />
                <span>不良 {{ summary.todayUnqualifiedQuantity }}</span>
              </template>
              <span v-else>暂无数据</span>
              <span>检验 {{ item.checkQuantity }}</span>
            </div>
          </div>
        </div>
      </Card>
    </Col>
    <Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
    <Col :lg="6" :md="12" :sm="12" :xl="4" :xs="24" class="mb-4">
      <Card
        class="kpi-card cursor-pointer transition-all hover:-translate-y-1 hover:shadow-lg"
        class="kpi-card h-full cursor-pointer transition-all hover:-translate-y-1 hover:shadow-lg"
        @click="emit('navigate', 'MesDvMachinery')"
      >
        <div class="flex items-center gap-4">
src/views/mes/pro/andon/record/data.ts
@@ -5,7 +5,7 @@
import { markRaw } from 'vue';
import { DICT_TYPE, MesProWorkOrderStatusEnum } from '@vben/constants';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { getSimpleUserList } from '#/api/system/user';
@@ -191,7 +191,6 @@
          component: markRaw(ProWorkOrderSelect),
          componentProps: {
            placeholder: '请选择订单(可选)',
            status: MesProWorkOrderStatusEnum.CONFIRMED,
          },
        }
      : {
src/views/mes/pro/card/data.ts
@@ -8,7 +8,6 @@
import {
  DICT_TYPE,
  MesAutoCodeRuleCode,
  MesProWorkOrderStatusEnum,
} from '@vben/constants';
import { Button } from 'ant-design-vue';
@@ -85,7 +84,6 @@
      componentProps: {
        disabled: headerReadonly,
        placeholder: '请选择生产订单',
        status: MesProWorkOrderStatusEnum.CONFIRMED,
      },
      rules: 'selectRequired',
    },
src/views/mes/pro/feedback/data.ts
@@ -10,7 +10,6 @@
  DICT_TYPE,
  MesAutoCodeRuleCode,
  MesProTaskStatusEnum,
  MesProWorkOrderStatusEnum,
} from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { formatDate } from '@vben/utils';
@@ -254,7 +253,6 @@
      componentProps: {
        disabled: isHeaderReadonly,
        placeholder: '请选择订单',
        status: MesProWorkOrderStatusEnum.CONFIRMED,
        // 订单变更:清空任务及任务带出的产品信息、数量区域控制位
        onChange: async () => {
          await formApi?.setValues({
src/views/mes/qc/batchtrace/modules/trace-detail.vue
@@ -11,18 +11,15 @@
import { useDetailSchema } from '../data';
import ProcessTraceList from './process-trace-list.vue';
import PurchaseSourceList from './purchase-source-list.vue';
import SalesTargetList from './sales-target-list.vue';
import TraceList from './trace-list.vue';
const detailData = ref<MesWmBatchApi.Batch>(); // 当前批次详情
const subTabsName = ref<
  | 'backward'
  | 'forward'
  | 'purchase'
  | 'sales'
  | 'process'
>('forward'); // 当前激活的追溯页签
>('backward'); // 当前激活的追溯页签
const [Descriptions] = useDescription({
  bordered: true,
@@ -37,7 +34,7 @@
      return;
    }
    detailData.value = modalApi.getData<MesWmBatchApi.Batch>();
    subTabsName.value = 'forward';
    subTabsName.value = 'backward';
  },
});
</script>
@@ -56,14 +53,6 @@
      class="mx-4 mt-4"
      type="card"
    >
      <Tabs.TabPane key="forward">
        <template #tab>
          <Tooltip title="顺着生产流往下游查:该批次被哪些工单消耗后,产出成了哪些成品批次">
            <span>向前追溯</span>
          </Tooltip>
        </template>
        <TraceList :batch-code="detailData.code" direction="forward" />
      </Tabs.TabPane>
      <Tabs.TabPane key="backward">
        <template #tab>
          <Tooltip title="逆着生产流往上游查:生产该批次时,消耗了哪些原料批次">
@@ -71,14 +60,6 @@
          </Tooltip>
        </template>
        <TraceList :batch-code="detailData.code" direction="backward" />
      </Tabs.TabPane>
      <Tabs.TabPane key="purchase">
        <template #tab>
          <Tooltip title="原料侧来源:该批次对应的采购入库单与 IQC 来料检验记录">
            <span>采购来源</span>
          </Tooltip>
        </template>
        <PurchaseSourceList :batch-code="detailData.code" />
      </Tabs.TabPane>
      <Tabs.TabPane key="sales">
        <template #tab>
src/views/mes/qc/ipqc/data.ts
@@ -9,7 +9,6 @@
  DICT_TYPE,
  MesAutoCodeRuleCode,
  MesProTaskStatusEnum,
  MesProWorkOrderStatusEnum,
} from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
@@ -157,7 +156,6 @@
      component: markRaw(ProWorkOrderSelect),
      componentProps: {
        placeholder: '请选择生产订单',
        status: MesProWorkOrderStatusEnum.CONFIRMED,
        // 订单变更时清空生产任务
        onChange: () => formApi?.setFieldValue('taskId', undefined),
      },
@@ -167,7 +165,6 @@
        componentProps: (values) => ({
          disabled: !!values.sourceDocId,
          placeholder: '请选择生产订单',
          status: MesProWorkOrderStatusEnum.CONFIRMED,
          onChange: () => formApi?.setFieldValue('taskId', undefined),
        }),
      },
src/views/mes/workbench/components/tabs/ProductIssueFormModal.vue
@@ -9,7 +9,6 @@
import {
  DICT_TYPE,
  MesAutoCodeRuleCode,
  MesProWorkOrderStatusEnum,
  MesWmProductIssueStatusEnum,
} from '@vben/constants';
@@ -106,7 +105,6 @@
      label: '生产订单',
      component: markRaw(ProWorkOrderSelect),
      componentProps: {
        status: MesProWorkOrderStatusEnum.CONFIRMED,
      },
    },
    {
src/views/mes/workbench/components/tabs/ProductIssueTab.vue
@@ -8,7 +8,6 @@
import {
  DICT_TYPE,
  MesAutoCodeRuleCode,
  MesProWorkOrderStatusEnum,
  MesWmProductIssueStatusEnum,
} from '@vben/constants';
import { IconifyIcon } from '@vben/icons';
@@ -100,7 +99,6 @@
      label: '生产订单',
      component: markRaw(ProWorkOrderSelect),
      componentProps: {
        status: MesProWorkOrderStatusEnum.CONFIRMED,
        disabled: true,
      },
    },
src/views/mes/workbench/components/tabs/ReturnIssueFormModal.vue
@@ -9,7 +9,6 @@
import {
  DICT_TYPE,
  MesAutoCodeRuleCode,
  MesProWorkOrderStatusEnum,
  MesWmReturnIssueStatusEnum,
} from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
@@ -99,7 +98,6 @@
      label: '生产订单',
      component: markRaw(ProWorkOrderSelect),
      componentProps: {
        status: MesProWorkOrderStatusEnum.CONFIRMED,
      },
    },
    {
src/views/mes/workbench/components/tabs/ReturnIssueTab.vue
@@ -8,7 +8,6 @@
import {
  DICT_TYPE,
  MesAutoCodeRuleCode,
  MesProWorkOrderStatusEnum,
  MesWmReturnIssueStatusEnum,
} from '@vben/constants';
import { IconifyIcon } from '@vben/icons';
@@ -112,7 +111,6 @@
      label: '生产订单',
      component: markRaw(ProWorkOrderSelect),
      componentProps: {
        status: MesProWorkOrderStatusEnum.CONFIRMED,
        disabled: true,
      },
    },
src/views/wls/barcode/data.ts
@@ -10,7 +10,6 @@
  BarcodeBizTypeEnum,
  CommonStatusEnum,
  DICT_TYPE,
  MesProWorkOrderStatusEnum,
} from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { formatDate } from '@vben/utils';
@@ -224,7 +223,6 @@
      label: '订单',
      component: markRaw(ProWorkOrderSelect),
      componentProps: {
        status: MesProWorkOrderStatusEnum.CONFIRMED,
        onChange: (item: any) => syncBizDetail(formApi, item),
      },
      dependencies: {
src/views/wls/outsourceissue/data.ts
@@ -11,7 +11,6 @@
import {
  DICT_TYPE,
  MesAutoCodeRuleCode,
  MesProWorkOrderStatusEnum,
  MesProWorkOrderTypeEnum,
} from '@vben/constants';
@@ -112,7 +111,6 @@
        onChange: async (workOrder?: MesProWorkOrderApi.WorkOrder) => {
          await formApi?.setFieldValue('vendorId', workOrder?.vendorId);
        },
        status: MesProWorkOrderStatusEnum.CONFIRMED,
        type: MesProWorkOrderTypeEnum.OUTSOURCE,
      },
      rules: 'selectRequired',
src/views/wls/outsourcereceipt/data.ts
@@ -10,7 +10,6 @@
import {
  DICT_TYPE,
  MesAutoCodeRuleCode,
  MesProWorkOrderStatusEnum,
  MesProWorkOrderTypeEnum,
} from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
@@ -102,7 +101,6 @@
        onChange: async (workOrder?: MesProWorkOrderApi.WorkOrder) => {
          await formApi?.setFieldValue('vendorId', workOrder?.vendorId);
        },
        status: MesProWorkOrderStatusEnum.CONFIRMED,
        type: MesProWorkOrderTypeEnum.OUTSOURCE,
      },
      rules: 'selectRequired',
src/views/wls/packages/data.ts
@@ -8,7 +8,6 @@
import {
  DICT_TYPE,
  MesAutoCodeRuleCode,
  MesProWorkOrderStatusEnum,
} from '@vben/constants';
import { Button } from 'ant-design-vue';
@@ -449,7 +448,6 @@
      component: markRaw(ProWorkOrderSelect),
      componentProps: {
        placeholder: '请选择生产订单',
        status: MesProWorkOrderStatusEnum.CONFIRMED,
      },
      rules: 'selectRequired',
    },
src/views/wls/productissue/data.ts
@@ -11,7 +11,6 @@
import {
  DICT_TYPE,
  MesAutoCodeRuleCode,
  MesProWorkOrderStatusEnum,
} from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
@@ -113,7 +112,6 @@
      component: markRaw(ProWorkOrderSelect),
      componentProps: {
        placeholder: '请选择生产订单',
        status: MesProWorkOrderStatusEnum.CONFIRMED,
        // 订单变更:清空已选生产任务,避免任务与订单不一致
        onChange: async () => {
          await formApi?.setFieldValue('taskId', undefined);
src/views/wls/productreceipt/data.ts
@@ -10,7 +10,6 @@
import {
  DICT_TYPE,
  MesAutoCodeRuleCode,
  MesProWorkOrderStatusEnum,
} from '@vben/constants';
import { Button } from 'ant-design-vue';
@@ -107,7 +106,6 @@
      label: '生产订单',
      component: markRaw(ProWorkOrderSelect),
      componentProps: {
        status: MesProWorkOrderStatusEnum.CONFIRMED,
        checkMethod: (row: any) => row.productionStatus !== 0,
      },
    },
src/views/wls/returnissue/data.ts
@@ -11,7 +11,6 @@
import {
  DICT_TYPE,
  MesAutoCodeRuleCode,
  MesProWorkOrderStatusEnum,
} from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
@@ -109,7 +108,6 @@
      label: '生产订单',
      component: markRaw(ProWorkOrderSelect),
      componentProps: {
        status: MesProWorkOrderStatusEnum.CONFIRMED,
      },
      rules: 'selectRequired',
    },
vite.config.ts
@@ -82,7 +82,7 @@
        proxy: {
          "/admin-api": {
            changeOrigin: true,
            target: "http://localhost:48080",
            target: "http://localhost:48081",
            ws: true,
          },
        },