From 8a9338f2f43bfe936124feaaa22420b4ca86eb84 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期一, 19 十月 2026 11:31:24 +0800
Subject: [PATCH] feat(qc): 完善质检流程功能

---
 src/views/mes/qc/oqc/index.vue |  129 +++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 124 insertions(+), 5 deletions(-)

diff --git a/src/views/mes/qc/oqc/index.vue b/src/views/mes/qc/oqc/index.vue
index 965cfda..92d4cea 100644
--- a/src/views/mes/qc/oqc/index.vue
+++ b/src/views/mes/qc/oqc/index.vue
@@ -1,19 +1,95 @@
 <script lang="ts" setup>
 import type { VxeTableGridOptions } from '#/adapter/vxe-table';
 import type { MesQcOqcApi } from '#/api/mes/qc/oqc';
+import type { OqcAction } from '#/api/mes/qc/oqc';
+
+import { ref } from 'vue';
+import { useAccess } from '@vben/access';
 
 import { Page, useVbenModal } from '@vben/common-ui';
-import { MesQcStatusEnum } from '@vben/constants';
+import { MesBizTypeEnum, MesQcStatusEnum } from '@vben/constants';
 import { downloadFileFromBlobPart } from '@vben/utils';
 
 import { Button, message } from 'ant-design-vue';
 
 import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { deleteOqc, exportOqc, getOqcPage } from '#/api/mes/qc/oqc';
+import { approveOqc, deleteOqc, exportOqc, getOqcActions, getOqcPage, retestOqc } from '#/api/mes/qc/oqc';
 import { $t } from '#/locales';
 
 import { useGridColumns, useGridFormSchema } from './data';
 import Form from './modules/form.vue';
+
+const props = withDefaults(defineProps<{ salesMode?: boolean }>(), { salesMode: false });
+const { hasAccessByCodes } = useAccess();
+const workflowLabels: Record<number, string> = {
+  0: '鑽夌', 10: '寰呭妫�', 20: '寰呴攢鍞儴瀹℃壒', 30: '寰呭叕鍙歌川妫�瀹℃壒',
+  40: '宸叉斁琛�', 50: '寰呴攢鍞儴涓嶅悎鏍艰瘎瀹�', 60: '寰呭叕鍙镐笉鍚堟牸瀹℃壒', 70: '宸叉嫆缁濆嚭璐�',
+};
+const active = ref<MesQcOqcApi.Oqc>();
+const retestOpen = ref(false);
+const retestBusy = ref(false);
+const retestForm = ref({ checkQuantity: 0, qualifiedQuantity: 0, unqualifiedQuantity: 0, opinion: '' });
+function openRetest(row: MesQcOqcApi.Oqc) {
+  active.value = row;
+  retestForm.value = {
+    checkQuantity: Number(row.checkQuantity ?? 0), qualifiedQuantity: Number(row.qualifiedQuantity ?? 0),
+    unqualifiedQuantity: Number(row.unqualifiedQuantity ?? 0), opinion: '',
+  };
+  retestOpen.value = true;
+}
+async function saveRetest() {
+  if (!active.value?.id) return;
+  const form = retestForm.value;
+  if (form.checkQuantity < 0 || form.qualifiedQuantity < 0 || form.unqualifiedQuantity < 0
+    || form.checkQuantity !== form.qualifiedQuantity + form.unqualifiedQuantity) {
+    message.warning('妫�娴嬫暟閲忓繀椤荤瓑浜庡悎鏍间笌涓嶅悎鏍兼暟閲忎箣鍜�');
+    return;
+  }
+  if (form.unqualifiedQuantity > 0 && !form.opinion.trim()) {
+    message.warning('澶嶆涓嶅悎鏍艰濉啓鍘熷洜');
+    return;
+  }
+  retestBusy.value = true;
+  try {
+    await retestOqc({ id: active.value.id, ...form, checkResult: form.unqualifiedQuantity === 0 ? 1 : 2 });
+    message.success('澶嶆缁撴灉宸叉彁浜わ紝寰呴攢鍞儴璇勫');
+    retestOpen.value = false;
+    handleRefresh();
+  } catch (error) {
+    message.error((error as Error).message || '澶嶆澶辫触');
+  } finally { retestBusy.value = false; }
+}
+const reviewOpen = ref(false);
+const reviewBusy = ref(false);
+const reviewStage = ref<'sales' | 'company'>('sales');
+const reviewApproved = ref(true);
+const reviewOpinion = ref('');
+function openReview(row: MesQcOqcApi.Oqc, stage: 'sales' | 'company') {
+  active.value = row; reviewStage.value = stage; reviewApproved.value = true;
+  reviewOpinion.value = ''; reviewOpen.value = true;
+}
+async function saveReview() {
+  if (!active.value?.id) return;
+  if (!reviewOpinion.value.trim()) { message.warning('璇峰~鍐欏鎵规剰瑙�'); return; }
+  reviewBusy.value = true;
+  try {
+    await approveOqc(reviewStage.value, { id: active.value.id, approved: reviewApproved.value, opinion: reviewOpinion.value.trim() });
+    message.success('瀹℃壒瀹屾垚'); reviewOpen.value = false; handleRefresh();
+  } catch (error) { message.error((error as Error).message || '瀹℃壒澶辫触'); }
+  finally { reviewBusy.value = false; }
+}
+const historyOpen = ref(false);
+const historyActions = ref<OqcAction[]>([]);
+const actionLabels: Record<string, string> = {
+  INITIAL: '鍒濇', RETEST: '澶嶆', SALES_APPROVE: '閿�鍞儴閫氳繃', SALES_REJECT: '閿�鍞儴椹冲洖',
+  COMPANY_APPROVE: '鍏徃璐ㄦ閫氳繃', COMPANY_REJECT: '鍏徃璐ㄦ椹冲洖',
+};
+async function openHistory(row: MesQcOqcApi.Oqc) {
+  if (!row.id) return;
+  active.value = row;
+  try { historyActions.value = await getOqcActions(row.id); historyOpen.value = true; }
+  catch (error) { message.error((error as Error).message || '璇诲彇娴佺▼璁板綍澶辫触'); }
+}
 
 const [FormModal, formModalApi] = useVbenModal({
   connectedComponent: Form,
@@ -58,15 +134,23 @@
 /** 瀵煎嚭琛ㄦ牸 */
 async function handleExport() {
   const data = await exportOqc(await gridApi.formApi.getValues());
-  downloadFileFromBlobPart({ fileName: '鍑鸿揣妫�楠屽崟.xls', source: data });
+  downloadFileFromBlobPart({ fileName: props.salesMode ? '閿�鍞川妫�鍗�.xls' : '鍑鸿揣妫�楠屽崟.xls', source: data });
 }
 
+const columns = useGridColumns();
+if (props.salesMode && columns) {
+  columns.splice(columns.length - 1, 0, {
+    field: 'workflowStatus', title: '閿�鍞川妫�娴佺▼', width: 170, slots: { default: 'workflow' },
+  });
+  const actions = columns.at(-1);
+  if (actions) actions.width = 320;
+}
 const [Grid, gridApi] = useVbenVxeGrid({
   formOptions: {
     schema: useGridFormSchema(),
   },
   gridOptions: {
-    columns: useGridColumns(),
+    columns,
     height: 'auto',
     keepSource: true,
     proxyConfig: {
@@ -76,6 +160,7 @@
             pageNo: page.currentPage,
             pageSize: page.pageSize,
             ...formValues,
+            sourceDocType: props.salesMode ? MesBizTypeEnum.WM_PRODUCT_SALES : undefined,
           });
         },
       },
@@ -95,7 +180,7 @@
 <template>
   <Page auto-content-height><FormModal @success="handleRefresh" />
 
-    <Grid table-title="鍑鸿揣妫�楠屽崟鍒楄〃">
+    <Grid :table-title="props.salesMode ? '閿�鍞川妫�鍗曞垪琛�' : '鍑鸿揣妫�楠屽崟鍒楄〃'">
       <template #toolbar-tools>
         <TableAction
           :actions="[
@@ -120,6 +205,9 @@
         <Button type="link" @click="handleDetail(row)">
           {{ row.code }}
         </Button>
+      </template>
+      <template #workflow="{ row }">
+        <span>{{ workflowLabels[row.workflowStatus ?? 0] || '鏈煡鐘舵��' }}</span>
       </template>
       <template #actions="{ row }">
         <TableAction
@@ -153,7 +241,38 @@
             },
           ]"
         />
+        <Button v-if="props.salesMode && row.workflowStatus === 10 && hasAccessByCodes(['mes:qc-oqc:retest'])" type="link" @click="openRetest(row)">澶嶆</Button>
+        <Button v-if="props.salesMode && [20, 50].includes(row.workflowStatus ?? 0) && hasAccessByCodes(['mes:qc-oqc:sales-approve'])" type="link" @click="openReview(row, 'sales')">閿�鍞儴瀹℃壒</Button>
+        <Button v-if="props.salesMode && [30, 60].includes(row.workflowStatus ?? 0) && hasAccessByCodes(['mes:qc-oqc:company-approve'])" type="link" @click="openReview(row, 'company')">鍏徃璐ㄦ瀹℃壒</Button>
+        <Button v-if="props.salesMode && hasAccessByCodes(['mes:qc-oqc:query'])" type="link" @click="openHistory(row)">娴佺▼璁板綍</Button>
       </template>
     </Grid>
+    <a-modal v-if="props.salesMode" v-model:open="retestOpen" title="閿�鍞川妫�澶嶆" :confirm-loading="retestBusy" ok-text="鎻愪氦澶嶆" @ok="saveRetest">
+      <p>妫�楠屽崟锛歿{ active?.code }}</p>
+      <a-form layout="vertical">
+        <a-form-item label="鏈妫�娴嬫暟閲�"><a-input-number v-model:value="retestForm.checkQuantity" :min="0" style="width: 100%" /></a-form-item>
+        <a-form-item label="鍚堟牸鏁伴噺"><a-input-number v-model:value="retestForm.qualifiedQuantity" :min="0" style="width: 100%" /></a-form-item>
+        <a-form-item label="涓嶅悎鏍兼暟閲�"><a-input-number v-model:value="retestForm.unqualifiedQuantity" :min="0" style="width: 100%" /></a-form-item>
+        <a-form-item label="澶嶆鎰忚"><a-textarea v-model:value="retestForm.opinion" :rows="3" :maxlength="500" /></a-form-item>
+      </a-form>
+    </a-modal>
+    <a-modal v-if="props.salesMode" v-model:open="reviewOpen" :title="reviewStage === 'sales' ? '閿�鍞儴瀹℃壒' : '鍏徃璐ㄦ瀹℃壒'" :confirm-loading="reviewBusy" ok-text="鎻愪氦瀹℃壒" @ok="saveReview">
+      <p>{{ active?.code }} 路 {{ workflowLabels[active?.workflowStatus ?? 0] }}</p>
+      <a-radio-group v-model:value="reviewApproved" style="display: flex; gap: 16px; margin: 12px 0 18px">
+        <a-radio :value="true">鍚屾剰{{ reviewStage === 'company' ? active?.workflowStatus === 30 ? '鍑鸿揣鏀捐' : '涓嶅悎鏍煎缃�' : '涓婃姤鍏徃璐ㄦ' }}</a-radio>
+        <a-radio :value="false">椹冲洖閲嶆</a-radio>
+      </a-radio-group>
+      <a-textarea v-model:value="reviewOpinion" :rows="3" :maxlength="500" placeholder="璇峰~鍐欏鎵规剰瑙�" />
+    </a-modal>
+    <a-modal v-if="props.salesMode" v-model:open="historyOpen" :title="`娴佺▼璁板綍 路 ${active?.code || ''}`" :footer="null">
+      <a-timeline v-if="historyActions.length">
+        <a-timeline-item v-for="action in historyActions" :key="action.id">
+          <strong>{{ actionLabels[action.action] || action.action }}</strong>
+          <span> 路 {{ action.createTime || '鈥�' }} 路 鎿嶄綔浜� {{ action.operatorId || '鈥�' }}</span>
+          <div v-if="action.opinion">{{ action.opinion }}</div>
+        </a-timeline-item>
+      </a-timeline>
+      <a-empty v-else description="鏆傛棤娴佺▼璁板綍" />
+    </a-modal>
   </Page>
 </template>

--
Gitblit v1.9.3