From b4e3bcda9d02f40702758485d894def270201ee6 Mon Sep 17 00:00:00 2001
From: gongchunyi <deslre0381@gmail.com>
Date: 星期一, 22 六月 2026 19:27:16 +0800
Subject: [PATCH] feat: 工艺路线自定义完成日期

---
 src/views/salesManagement/salesLedger/components/ProcessFlowConfigSelectDialog.vue |  118 +++++++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 107 insertions(+), 11 deletions(-)

diff --git a/src/views/salesManagement/salesLedger/components/ProcessFlowConfigSelectDialog.vue b/src/views/salesManagement/salesLedger/components/ProcessFlowConfigSelectDialog.vue
index f3c33b5..69e7350 100644
--- a/src/views/salesManagement/salesLedger/components/ProcessFlowConfigSelectDialog.vue
+++ b/src/views/salesManagement/salesLedger/components/ProcessFlowConfigSelectDialog.vue
@@ -8,10 +8,14 @@
   >
     <el-row :gutter="20">
       <el-col :span="24">
-        <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 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="selectedRouteId"
@@ -33,7 +37,7 @@
 
         <div style="font-weight: 600; margin-bottom: 8px;">姝ラ棰勮</div>
         <div style="font-size: 12px; color: #909399; margin-bottom: 10px;">
-          鏍规嵁鎵�閫夐厤缃睍绀烘祦绋嬪浘
+          鏍规嵁鎵�閫夐厤缃睍绀烘祦绋嬪浘锛屽嬀閫夎〃绀鸿宸ュ簭宸插畬鎴�
         </div>
       </el-col>
 
@@ -46,8 +50,27 @@
             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 v-if="step.isCompleted" style="margin-top: 8px;">
+                <el-date-picker
+                  v-model="step.completedTime"
+                  type="datetime"
+                  placeholder="閫夋嫨鏃堕棿"
+                  format="YYYY-MM-DD HH:mm:ss"
+                  value-format="YYYY-MM-DD HH:mm:ss"
+                  size="small"
+                  style="width: 100%;"
+                />
+              </div>
             </div>
             <div v-if="idx < steps.length - 1" class="process-diagram-arrow">鈫�</div>
           </div>
@@ -79,6 +102,8 @@
   visible: { type: Boolean, default: false },
   // 鎵撳紑寮圭獥鏃剁殑鍥炴樉锛氳嫢涓氬姟宸茬粦瀹氬伐鑹鸿矾绾垮垯浼犲叆璇� routeId锛涘惁鍒欓粯璁ゅ睍绀哄垪琛ㄧ涓�鏉�
   defaultRouteId: { type: [Number, String, null], default: null },
+  // 鎵撳紑寮圭獥鏃剁殑宸ュ簭瀹屾垚璁板綍鍥炴樉
+  defaultRecordList: { type: Array, default: () => [] },
   // 椤甸潰鎻愮ず锛氳鍗曞凡缁戝畾鐨勫伐鑹鸿矾绾垮悕绉�
   boundRouteName: { type: String, default: "" },
 });
@@ -103,9 +128,11 @@
   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)),
   }));
 };
 
@@ -116,6 +143,27 @@
     processRouteName: r.processRouteName ?? r.routeName ?? r.name ?? "",
     isDefault: Boolean(r.isDefault),
   }));
+};
+
+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 () => {
@@ -132,7 +180,12 @@
   }
   const res = await salesProcessFlowConfigItemList(routeId);
   const raw = res?.data ?? res ?? [];
-  steps.value = normalizeStepsFromApi(raw);
+  const normalizedSteps = normalizeStepsFromApi(raw);
+  if (String(routeId) === String(props.defaultRouteId)) {
+    steps.value = applyRecordListToSteps(normalizedSteps, props.defaultRecordList);
+    return;
+  }
+  steps.value = normalizedSteps;
 };
 
 watch(
@@ -163,6 +216,18 @@
   await fetchRouteSteps(selectedRouteId.value);
 };
 
+const formatDateTime = (date) => {
+  const pad = (n) => (n < 10 ? '0' + n : n);
+  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
+};
+
+const handleStepCompletedChange = (step) => {
+  step.isCompleted = Boolean(step.isCompleted);
+  if (step.isCompleted && !step.completedTime) {
+    step.completedTime = proxy?.parseTime ? proxy.parseTime(new Date()) : formatDateTime(new Date());
+  }
+};
+
 const handleClose = () => {
   emit("update:visible", false);
   saving.value = false;
@@ -176,7 +241,14 @@
   }
   saving.value = true;
   try {
-    emit("confirm", selectedRouteId.value);
+    emit("confirm", {
+      routeId: selectedRouteId.value,
+      recordList: steps.value.map((step) => ({
+        processRouteItemId: step.processRouteItemId,
+        isCompleted: Number(step.isCompleted ?? 0),
+        completedTime: step.completedTime || (step.isCompleted ? (proxy?.parseTime ? proxy.parseTime(new Date()) : formatDateTime(new Date())) : null)
+      })),
+    });
   } catch (e) {
     proxy?.$modal?.msgError?.("纭澶辫触锛岃绋嶅悗閲嶈瘯");
   } finally {
@@ -201,9 +273,9 @@
 }
 
 .process-diagram-node {
-  width: 160px;
-  min-width: 160px;
-  height: 78px;
+  width: 180px;
+  min-width: 180px;
+  min-height: 78px;
   border: 1px solid #ebeef5;
   border-radius: 10px;
   background: #fff;
@@ -213,6 +285,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 {
@@ -228,6 +307,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 {
@@ -251,5 +341,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