From 492802e4fc1b371ba21a2a490c8dcd67d7c8b29c Mon Sep 17 00:00:00 2001
From: gongchunyi <deslre0381@gmail.com>
Date: 星期一, 22 六月 2026 14:03:40 +0800
Subject: [PATCH] fix: 出差和请假审批新增开始与结束日期

---
 src/views/salesManagement/salesLedger/components/ProcessFlowConfigSelectDialog.vue |  177 +++++++++++++++++++++++++++++++++++++++++++++-------------
 1 files changed, 137 insertions(+), 40 deletions(-)

diff --git a/src/views/salesManagement/salesLedger/components/ProcessFlowConfigSelectDialog.vue b/src/views/salesManagement/salesLedger/components/ProcessFlowConfigSelectDialog.vue
index 1dc7e25..606d86c 100644
--- a/src/views/salesManagement/salesLedger/components/ProcessFlowConfigSelectDialog.vue
+++ b/src/views/salesManagement/salesLedger/components/ProcessFlowConfigSelectDialog.vue
@@ -7,33 +7,41 @@
     @close="handleClose"
   >
     <el-row :gutter="20">
-      <el-col :span="10">
-        <div style="font-weight: 600; margin-bottom: 8px;">閰嶇疆</div>
+      <el-col :span="24">
+        <div class="dialog-topbar">
+          <div>
+            <div style="font-weight: 600; margin-bottom: 8px;">閰嶇疆</div>
+            <div style="font-size: 12px; margin-bottom: 8px;">
+              <span v-if="boundRouteName" style="color: #67c23a;">宸茬粦瀹氾細{{ boundRouteName }}</span>
+              <span v-else style="color: #e6a23c;">鏈粦瀹�</span>
+            </div>
+          </div>
+        </div>
         <el-select
-          v-model="selectedConfigId"
+          v-model="selectedRouteId"
           filterable
           clearable
-          placeholder="璇烽�夋嫨宸ヨ壓璺嚎閰嶇疆"
+          placeholder="璇烽�夋嫨宸ヨ壓璺嚎"
           style="width: 100%;"
-          @change="handleConfigChange"
+          @change="handleRouteChange"
         >
           <el-option
-            v-for="cfg in configList"
-            :key="cfg.configId"
-            :label="cfg.configName"
-            :value="cfg.configId"
+            v-for="cfg in routeList"
+            :key="cfg.routeId"
+            :label="cfg.processRouteName"
+            :value="cfg.routeId"
           />
         </el-select>
 
         <el-divider style="margin: 16px 0;" />
 
         <div style="font-weight: 600; margin-bottom: 8px;">姝ラ棰勮</div>
-        <div style="font-size: 12px; color: #909399; margin-bottom: 6px;">
-          鏍规嵁鎵�閫夐厤缃睍绀烘祦绋嬪浘
+        <div style="font-size: 12px; color: #909399; margin-bottom: 10px;">
+          鏍规嵁鎵�閫夐厤缃睍绀烘祦绋嬪浘锛屽嬀閫夎〃绀鸿宸ュ簭宸插畬鎴�
         </div>
       </el-col>
 
-      <el-col :span="14">
+      <el-col :span="24">
         <div class="process-diagram">
           <div v-if="steps.length === 0" class="process-diagram-empty">鏆傛棤姝ラ</div>
           <div
@@ -42,13 +50,21 @@
             class="process-diagram-segment"
           >
             <div class="process-diagram-node">
+              <el-checkbox
+                v-model="step.isCompleted"
+                class="process-diagram-checkbox"
+                @change="() => handleStepCompletedChange(step)"
+              />
               <div class="process-diagram-index">{{ idx + 1 }}</div>
               <div class="process-diagram-name">{{ step.processName }}</div>
+              <div class="process-diagram-status" :class="{ 'is-done': Number(step.isCompleted) === 1 }">
+                {{ Number(step.isCompleted) === 1 ? "宸插畬鎴�" : "鏈畬鎴�" }}
+              </div>
             </div>
             <div v-if="idx < steps.length - 1" class="process-diagram-arrow">鈫�</div>
           </div>
         </div>
-        <div v-if="selectedConfigId === null" style="margin-top: 10px; font-size: 12px; color: #909399;">
+        <div v-if="selectedRouteId === null" style="margin-top: 10px; font-size: 12px; color: #909399;">
           璇峰厛閫夋嫨涓�鏉″凡缁存姢濂界殑宸ヨ壓璺嚎
         </div>
       </el-col>
@@ -67,13 +83,18 @@
 
 <script setup>
 import { computed, getCurrentInstance, ref, watch } from "vue";
-import { salesProcessFlowConfigList, salesProcessFlowConfigGetById } from "@/api/salesManagement/salesProcessFlowConfig.js";
+import { salesProcessFlowConfigList, salesProcessFlowConfigItemList } from "@/api/salesManagement/salesProcessFlowConfig.js";
 
 const emit = defineEmits(["update:visible", "confirm"]);
 
 const props = defineProps({
   visible: { type: Boolean, default: false },
-  defaultConfigId: { type: [Number, String, null], default: null },
+  // 鎵撳紑寮圭獥鏃剁殑鍥炴樉锛氳嫢涓氬姟宸茬粦瀹氬伐鑹鸿矾绾垮垯浼犲叆璇� routeId锛涘惁鍒欓粯璁ゅ睍绀哄垪琛ㄧ涓�鏉�
+  defaultRouteId: { type: [Number, String, null], default: null },
+  // 鎵撳紑寮圭獥鏃剁殑宸ュ簭瀹屾垚璁板綍鍥炴樉
+  defaultRecordList: { type: Array, default: () => [] },
+  // 椤甸潰鎻愮ず锛氳鍗曞凡缁戝畾鐨勫伐鑹鸿矾绾垮悕绉�
+  boundRouteName: { type: String, default: "" },
 });
 
 const { proxy } = getCurrentInstance();
@@ -87,8 +108,8 @@
   },
 });
 
-const configList = ref([]);
-const selectedConfigId = ref(null);
+const routeList = ref([]);
+const selectedRouteId = ref(null);
 const steps = ref([]);
 const saving = ref(false);
 
@@ -96,32 +117,64 @@
   if (!Array.isArray(list)) return [];
   return list.map((s, idx) => ({
     stepId: s.stepId ?? s.id ?? null,
+    processRouteItemId: s.processRouteItemId ?? s.process_route_item_id ?? s.id ?? null,
     processId: s.processId ?? s.process_id ?? s.id ?? null,
     processName: s.processName ?? s.process_name ?? s.name ?? "",
     sortNo: s.sortNo ?? idx + 1,
+    isCompleted: Boolean(Number(s.isCompleted ?? s.completed ?? 0)),
   }));
 };
 
-const fetchConfigList = async () => {
-  const res = await salesProcessFlowConfigList();
-  const list = res?.data ?? res?.records ?? res ?? [];
-  configList.value = Array.isArray(list) ? list : [];
+const normalizeRouteList = (list) => {
+  if (!Array.isArray(list)) return [];
+  return list.map((r) => ({
+    routeId: r.routeId ?? r.id ?? null,
+    processRouteName: r.processRouteName ?? r.routeName ?? r.name ?? "",
+    isDefault: Boolean(r.isDefault),
+  }));
 };
 
-const fetchConfigDetail = async (id) => {
-  if (!id) {
+const applyRecordListToSteps = (stepList, recordList) => {
+  if (!Array.isArray(stepList) || stepList.length === 0) return stepList;
+  if (!Array.isArray(recordList) || recordList.length === 0) return stepList;
+
+  const recordMap = new Map(
+    recordList
+      .filter((item) => item && item.processRouteItemId !== null && item.processRouteItemId !== undefined)
+      .map((item) => [String(item.processRouteItemId), item])
+  );
+
+  return stepList.map((step) => {
+    const matched = recordMap.get(String(step.processRouteItemId));
+    if (!matched) return step;
+    return {
+      ...step,
+      isCompleted: Boolean(Number(matched.isCompleted ?? 0)),
+      completedTime: matched.completedTime ?? matched.completed_time ?? null,
+    };
+  });
+};
+
+const fetchRouteList = async () => {
+  // 閫夋嫨寮圭獥锛氬敖閲忎竴娆℃�ф媺鍏紝閬垮厤鍒嗛〉褰卞搷閫夋嫨浣撻獙
+  const res = await salesProcessFlowConfigList({ current: 1, size: 1000 });
+  const records = res?.records ?? res?.data?.records ?? res?.data ?? res ?? [];
+  routeList.value = normalizeRouteList(records).filter((r) => r.routeId !== null && r.routeId !== undefined && r.routeId !== "");
+};
+
+const fetchRouteSteps = async (routeId) => {
+  if (!routeId) {
     steps.value = [];
     return;
   }
-  const res = await salesProcessFlowConfigGetById(id);
-  const detail = res?.data ?? res ?? {};
-  steps.value = normalizeStepsFromApi(detail?.steps ?? []);
-};
-
-const initDefault = async () => {
-  await fetchConfigList();
-  selectedConfigId.value = props.defaultConfigId ?? null;
-  await fetchConfigDetail(selectedConfigId.value);
+  const res = await salesProcessFlowConfigItemList(routeId);
+  const raw = res?.data ?? res ?? [];
+  const normalizedSteps = normalizeStepsFromApi(raw);
+  if (String(routeId) === String(props.defaultRouteId)) {
+    steps.value = applyRecordListToSteps(normalizedSteps, props.defaultRecordList);
+    return;
+  }
+  steps.value = normalizedSteps;
 };
 
 watch(
@@ -129,7 +182,18 @@
   async (v) => {
     if (v) {
       try {
-        await initDefault();
+        await fetchRouteList();
+
+        // 鍥炴樉缁戝畾锛�
+        // 1. 鑻ヤ紶鍏� defaultRouteId锛屽垯浼樺厛浣跨敤瀹�
+        // 2. 鍚﹀垯浼樺厛閫変腑鏍囪涓洪粯璁�(isDefault=true)鐨勫伐鑹鸿矾绾�
+        // 3. 鑻ラ兘娌℃湁锛屽垯鍥為��涓虹涓�鏉�
+        const first = routeList.value?.[0] ?? null;
+        const defaultRoute =
+          routeList.value.find((r) => r.isDefault) ?? first;
+        const desired = props.defaultRouteId ?? (defaultRoute ? defaultRoute.routeId : null);
+        selectedRouteId.value = desired ?? null;
+        await fetchRouteSteps(selectedRouteId.value);
       } catch {
         proxy?.$modal?.msgError?.("鑾峰彇宸ヨ壓璺嚎閰嶇疆澶辫触");
       }
@@ -137,8 +201,12 @@
   }
 );
 
-const handleConfigChange = async () => {
-  await fetchConfigDetail(selectedConfigId.value);
+const handleRouteChange = async () => {
+  await fetchRouteSteps(selectedRouteId.value);
+};
+
+const handleStepCompletedChange = (step) => {
+  step.isCompleted = Boolean(step.isCompleted);
 };
 
 const handleClose = () => {
@@ -148,14 +216,19 @@
 
 const confirmSelect = async () => {
   if (saving.value) return;
-  if (selectedConfigId.value === null || selectedConfigId.value === undefined || selectedConfigId.value === "") {
-    proxy?.$modal?.msgWarning?.("璇烽�夋嫨宸ヨ壓璺嚎閰嶇疆");
+  if (selectedRouteId.value === null || selectedRouteId.value === undefined || selectedRouteId.value === "") {
+    proxy?.$modal?.msgWarning?.("璇烽�夋嫨宸ヨ壓璺嚎");
     return;
   }
   saving.value = true;
   try {
-    emit("confirm", selectedConfigId.value);
-    handleClose();
+    emit("confirm", {
+      routeId: selectedRouteId.value,
+      recordList: steps.value.map((step) => ({
+        processRouteItemId: step.processRouteItemId,
+        isCompleted: Number(step.isCompleted ?? 0),
+      })),
+    });
   } catch (e) {
     proxy?.$modal?.msgError?.("纭澶辫触锛岃绋嶅悗閲嶈瘯");
   } finally {
@@ -192,6 +265,13 @@
   padding: 10px 12px;
   margin-right: 10px;
   box-sizing: border-box;
+  position: relative;
+}
+
+.process-diagram-checkbox {
+  position: absolute;
+  top: 8px;
+  right: 8px;
 }
 
 .process-diagram-index {
@@ -207,6 +287,17 @@
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
+}
+
+.process-diagram-status {
+  margin-top: 4px;
+  font-size: 12px;
+  color: #909399;
+}
+
+.process-diagram-status.is-done {
+  color: #67c23a;
+  font-weight: 600;
 }
 
 .process-diagram-arrow {
@@ -230,5 +321,11 @@
   justify-content: flex-end;
   gap: 10px;
 }
-</style>
 
+.dialog-topbar {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  gap: 16px;
+}
+</style>

--
Gitblit v1.9.3