From 0c9aa3e1d5cfbf2245f9e7462e1d27cc8c7827d1 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期三, 14 十月 2026 18:29:49 +0800
Subject: [PATCH] feat: 客户签订合同展示、质检合格率卡片、生产订单选择默认状态修正

---
 src/views/bi/dashboard/index.vue                                  |   34 ++++
 src/views/wls/productreceipt/data.ts                              |    2 
 src/views/mes/workbench/components/tabs/ReturnIssueTab.vue        |    2 
 src/views/mes/qc/ipqc/data.ts                                     |    3 
 src/views/mes/home/modules/kpi-cards.vue                          |  120 +++++++++++------
 src/views/mes/pro/card/data.ts                                    |    2 
 src/views/crm/customer/detail/index.vue                           |   24 ---
 src/views/mes/workbench/components/tabs/ProductIssueFormModal.vue |    2 
 vite.config.ts                                                    |    2 
 src/views/mes/qc/batchtrace/modules/trace-detail.vue              |   23 ---
 src/views/mes/workbench/components/tabs/ReturnIssueFormModal.vue  |    2 
 src/views/wls/outsourcereceipt/data.ts                            |    2 
 src/views/wls/returnissue/data.ts                                 |    2 
 src/views/crm/customer/index.vue                                  |    8 +
 src/views/mes/pro/andon/record/data.ts                            |    3 
 src/api/mes/home/index.ts                                         |    6 
 src/views/crm/customer/detail/data.ts                             |    5 
 src/api/crm/customer/index.ts                                     |   10 -
 src/views/mes/workbench/components/tabs/ProductIssueTab.vue       |    2 
 src/views/wls/outsourceissue/data.ts                              |    2 
 src/views/wls/productissue/data.ts                                |    2 
 src/views/wls/barcode/data.ts                                     |    2 
 src/views/dashboard/analytics/modules/kpi-cards.vue               |  130 +++++++++++++----
 src/views/crm/customer/pool/data.ts                               |    9 -
 src/views/mes/pro/feedback/data.ts                                |    2 
 src/views/crm/customer/data.ts                                    |   11 -
 src/views/wls/packages/data.ts                                    |    2 
 27 files changed, 228 insertions(+), 186 deletions(-)

diff --git a/src/api/crm/customer/index.ts b/src/api/crm/customer/index.ts
index 38d6e04..874fbc3 100644
--- a/src/api/crm/customer/index.ts
+++ b/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}`,
-  );
 }
 
 /** 杩涘叆鍏捣瀹㈡埛鎻愰啋鐨勫鎴峰垪琛� */
diff --git a/src/api/mes/home/index.ts b/src/api/mes/home/index.ts
index 1cce28e..e7b0daa 100644
--- a/src/api/mes/home/index.ts
+++ b/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; // 鍋滄満璁惧鏁�
diff --git a/src/views/bi/dashboard/index.vue b/src/views/bi/dashboard/index.vue
index 70b9437..98072c2 100644
--- a/src/views/bi/dashboard/index.vue
+++ b/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;
diff --git a/src/views/crm/customer/data.ts b/src/views/crm/customer/data.ts
index 77edaea..ba98137 100644
--- a/src/views/crm/customer/data.ts
+++ b/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',
diff --git a/src/views/crm/customer/detail/data.ts b/src/views/crm/customer/detail/data.ts
index ec5b2ea..59aebac 100644
--- a/src/views/crm/customer/detail/data.ts
+++ b/src/views/crm/customer/detail/data.ts
@@ -52,11 +52,6 @@
       render: (val) => (val === false ? '鍚�' : '鏄�'),
     },
     {
-      field: 'dealStatus',
-      label: '鎴愪氦鐘舵��',
-      render: (val) => (val ? '宸叉垚浜�' : '鏈垚浜�'),
-    },
-    {
       field: 'ownerUserName',
       label: '璐熻矗浜�',
     },
diff --git a/src/views/crm/customer/detail/index.vue b/src/views/crm/customer/detail/index.vue
index e854603..068be87 100644
--- a/src/views/crm/customer/detail/index.vue
+++ b/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: '閿佸畾',
diff --git a/src/views/crm/customer/index.vue b/src/views/crm/customer/index.vue
index 6168b30..c53e784 100644
--- a/src/views/crm/customer/index.vue
+++ b/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="[
diff --git a/src/views/crm/customer/pool/data.ts b/src/views/crm/customer/pool/data.ts
index a42ee8a..da07768 100644
--- a/src/views/crm/customer/pool/data.ts
+++ b/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,
diff --git a/src/views/dashboard/analytics/modules/kpi-cards.vue b/src/views/dashboard/analytics/modules/kpi-cards.vue
index 88853ef..055f8f6 100644
--- a/src/views/dashboard/analytics/modules/kpi-cards.vue
+++ b/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-xs text-gray-400">%</span>
+              <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 text-xs text-gray-400">
-              鍚堟牸 {{ summary.todayQualifiedQuantity }} | 涓嶈壇 {{ summary.todayUnqualifiedQuantity }}
+            <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 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 inset-0 -z-0 opacity-0 transition-opacity duration-300 group-hover:opacity-100"
+          :class="item.washClass"
+        />
+        <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')"
diff --git a/src/views/mes/home/modules/kpi-cards.vue b/src/views/mes/home/modules/kpi-cards.vue
index f104479..eff3bb2 100644
--- a/src/views/mes/home/modules/kpi-cards.vue
+++ b/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;
-  }
-  return (props.summary.todayQualifiedQuantity / total) * 100;
-});
+/** 鍚堟牸鐜囧睍绀烘枃妗堬紙涓�浣嶅皬鏁帮紝涓庡師銆岃川閲忓悎鏍肩巼銆嶅崱鐗囦竴鑷达級锛屾棤鏁版嵁鏃剁敤銆屸�斻�嶈�屼笉鏄� 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;
+}
 </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">
diff --git a/src/views/mes/pro/andon/record/data.ts b/src/views/mes/pro/andon/record/data.ts
index f660e23..9ac0771 100644
--- a/src/views/mes/pro/andon/record/data.ts
+++ b/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,
           },
         }
       : {
diff --git a/src/views/mes/pro/card/data.ts b/src/views/mes/pro/card/data.ts
index 4e3b4a8..1ad6325 100644
--- a/src/views/mes/pro/card/data.ts
+++ b/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',
     },
diff --git a/src/views/mes/pro/feedback/data.ts b/src/views/mes/pro/feedback/data.ts
index 3e25ee4..c7ce9fe 100644
--- a/src/views/mes/pro/feedback/data.ts
+++ b/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({
diff --git a/src/views/mes/qc/batchtrace/modules/trace-detail.vue b/src/views/mes/qc/batchtrace/modules/trace-detail.vue
index 8d01d5b..e936d30 100644
--- a/src/views/mes/qc/batchtrace/modules/trace-detail.vue
+++ b/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>
diff --git a/src/views/mes/qc/ipqc/data.ts b/src/views/mes/qc/ipqc/data.ts
index 6f92628..7233c70 100644
--- a/src/views/mes/qc/ipqc/data.ts
+++ b/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),
         }),
       },
diff --git a/src/views/mes/workbench/components/tabs/ProductIssueFormModal.vue b/src/views/mes/workbench/components/tabs/ProductIssueFormModal.vue
index 1e0a056..60d3d2a 100644
--- a/src/views/mes/workbench/components/tabs/ProductIssueFormModal.vue
+++ b/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,
       },
     },
     {
diff --git a/src/views/mes/workbench/components/tabs/ProductIssueTab.vue b/src/views/mes/workbench/components/tabs/ProductIssueTab.vue
index ae52420..27f62be 100644
--- a/src/views/mes/workbench/components/tabs/ProductIssueTab.vue
+++ b/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,
       },
     },
diff --git a/src/views/mes/workbench/components/tabs/ReturnIssueFormModal.vue b/src/views/mes/workbench/components/tabs/ReturnIssueFormModal.vue
index 3a0e29e..c652297 100644
--- a/src/views/mes/workbench/components/tabs/ReturnIssueFormModal.vue
+++ b/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,
       },
     },
     {
diff --git a/src/views/mes/workbench/components/tabs/ReturnIssueTab.vue b/src/views/mes/workbench/components/tabs/ReturnIssueTab.vue
index 5ab1037..edccef1 100644
--- a/src/views/mes/workbench/components/tabs/ReturnIssueTab.vue
+++ b/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,
       },
     },
diff --git a/src/views/wls/barcode/data.ts b/src/views/wls/barcode/data.ts
index 069d12f..ff99a67 100644
--- a/src/views/wls/barcode/data.ts
+++ b/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: {
diff --git a/src/views/wls/outsourceissue/data.ts b/src/views/wls/outsourceissue/data.ts
index dd70166..9b6e2e0 100644
--- a/src/views/wls/outsourceissue/data.ts
+++ b/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',
diff --git a/src/views/wls/outsourcereceipt/data.ts b/src/views/wls/outsourcereceipt/data.ts
index 4d336b4..0efa1db 100644
--- a/src/views/wls/outsourcereceipt/data.ts
+++ b/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',
diff --git a/src/views/wls/packages/data.ts b/src/views/wls/packages/data.ts
index ec8952a..f771d1c 100644
--- a/src/views/wls/packages/data.ts
+++ b/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',
     },
diff --git a/src/views/wls/productissue/data.ts b/src/views/wls/productissue/data.ts
index 5ef003a..cc592e2 100644
--- a/src/views/wls/productissue/data.ts
+++ b/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);
diff --git a/src/views/wls/productreceipt/data.ts b/src/views/wls/productreceipt/data.ts
index f249a84..3b9188b 100644
--- a/src/views/wls/productreceipt/data.ts
+++ b/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,
       },
     },
diff --git a/src/views/wls/returnissue/data.ts b/src/views/wls/returnissue/data.ts
index 22549d5..8f66b81 100644
--- a/src/views/wls/returnissue/data.ts
+++ b/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',
     },
diff --git a/vite.config.ts b/vite.config.ts
index 9382ae0..6b61522 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -82,7 +82,7 @@
         proxy: {
           "/admin-api": {
             changeOrigin: true,
-            target: "http://localhost:48080",
+            target: "http://localhost:48081",
             ws: true,
           },
         },

--
Gitblit v1.9.3