From c70f5e1d471ddf002ef0ebd5f4c6a5ed1c865d0d Mon Sep 17 00:00:00 2001
From: WXL (wul) <wl_5969728@163.com>
Date: 星期三, 21 十月 2026 17:47:58 +0800
Subject: [PATCH] 测试完成

---
 src/views/Satisfaction/configurationmyd/batch.vue |  778 ++++++++++++++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 629 insertions(+), 149 deletions(-)

diff --git a/src/views/Satisfaction/configurationmyd/batch.vue b/src/views/Satisfaction/configurationmyd/batch.vue
index 750c893..6d85fff 100644
--- a/src/views/Satisfaction/configurationmyd/batch.vue
+++ b/src/views/Satisfaction/configurationmyd/batch.vue
@@ -211,7 +211,7 @@
                 </div>
                 <div class="info-item">
                   <span class="label">澶勭悊璇存槑锛�</span>
-                  <span class="value">{{ row.handledesc }}</span>
+                  <span class="value">{{ formatHandledesc(row.handledesc) }}</span>
                 </div>
               </div>
               <span v-else class="no-data">鏈鐞�</span>
@@ -238,7 +238,7 @@
                 size="small"
                 icon="el-icon-edit"
                 @click="handleProcess(row)"
-                :disabled="row.handleFlag === '1'"
+                :disabled="row.handleresult === 'resolved'"
               >
                 澶勭悊
               </el-button>
@@ -266,88 +266,245 @@
     <el-dialog
       title="澶勭悊寮傚父鍙嶉"
       :visible.sync="processDialogVisible"
-      width="600px"
+      width="80%"
+      top="6vh"
       center
     >
-      <el-form
-        :model="processForm"
-        :rules="processRules"
-        ref="processForm"
-        label-width="100px"
-        size="medium"
-      >
-        <el-form-item label="澶勭悊鐘舵��" prop="handleFlag">
-          <el-select
-            v-model="processForm.handleFlag"
-            placeholder="璇烽�夋嫨澶勭悊鐘舵��"
-            style="width: 100%"
+      <div class="flow-dialog-body">
+        <!-- 宸﹀崐閮ㄥ垎锛氬鐞嗕俊鎭� -->
+        <div class="flow-left">
+          <el-form
+            :model="processForm"
+            :rules="processRules"
+            :validate-on-rule-change="false"
+            ref="processForm"
+            label-width="100px"
+            size="medium"
           >
-            <el-option label="宸插鐞�" :value="'1'" />
-            <el-option label="鍙栨秷澶勭悊" :value="'0'" />
-          </el-select>
-        </el-form-item>
+            <el-form-item label="鏄惁娴佽浆" prop="isFlow">
+              <el-switch
+                v-model="processForm.isFlow"
+                active-value="1"
+                inactive-value="0"
+                active-text="娴佽浆"
+                inactive-text="涓嶆祦杞�"
+              />
+            </el-form-item>
 
-        <el-form-item label="鎶ュ绉戝" prop="ccdepts">
-          <el-select
-            v-model="processForm.ccdepts"
-            placeholder="璇烽�夋嫨鎶ュ绉戝"
-            multiple
-            filterable
-            collapse-tags
-            style="width: 100%"
-            :disabled="processForm.handleFlag !== '1'"
-          >
-            <el-option
-              v-for="dept in deptList"
-              :key="dept.deptCode"
-              :label="dept.label"
-              :value="dept.deptCode"
-            />
-          </el-select>
-        </el-form-item>
+            <el-form-item label="澶勭悊鐘舵��" prop="handleFlag">
+              <el-select
+                v-model="processForm.handleFlag"
+                placeholder="璇烽�夋嫨澶勭悊鐘舵��"
+                style="width: 100%"
+                @change="handleFlagChange('single', $event)"
+              >
+                <el-option label="宸插鐞�" :value="'1'" />
+                <el-option label="鍙栨秷澶勭悊" :value="'0'" />
+              </el-select>
+            </el-form-item>
 
-        <el-form-item label="澶勭悊缁撴灉" prop="handleresult">
-          <el-select
-            v-model="processForm.handleresult"
-            placeholder="璇烽�夋嫨澶勭悊缁撴灉"
-            style="width: 100%"
-            :disabled="processForm.handleFlag !== '1'"
-          >
-            <el-option label="宸茶В鍐�" value="resolved" />
-            <el-option label="宸茶В閲�" value="explained" />
-            <el-option label="宸茶浆浜�" value="transferred" />
-            <el-option label="闇�鏀硅繘" value="improvement" />
-            <el-option label="宸查┏鍥�" value="rejected" />
-          </el-select>
-        </el-form-item>
+            <el-form-item label="鎶ュ绉戝" prop="ccdepts">
+              <el-select
+                v-model="processForm.ccdepts"
+                placeholder="璇烽�夋嫨鎶ュ绉戝"
+                multiple
+                filterable
+                collapse-tags
+                style="width: 100%"
+                :disabled="processForm.handleFlag !== '1'"
+              >
+                <el-option
+                  v-for="dept in deptList"
+                  :key="dept.deptCode"
+                  :label="dept.label"
+                  :value="dept.deptCode"
+                />
+              </el-select>
+            </el-form-item>
 
-        <el-form-item label="澶勭悊璇存槑" prop="handledesc">
-          <el-input
-            v-model="processForm.handledesc"
-            type="textarea"
-            :rows="4"
-            placeholder="璇疯緭鍏ュ鐞嗚鏄庯紙鏈�澶�500瀛楋級"
-            maxlength="500"
-            show-word-limit
-            :disabled="processForm.handleFlag !== '1'"
-          />
-        </el-form-item>
+            <el-form-item label="澶勭悊缁撴灉" prop="handleresult">
+              <el-select
+                v-model="processForm.handleresult"
+                placeholder="璇烽�夋嫨澶勭悊缁撴灉"
+                style="width: 100%"
+                :disabled="processForm.handleFlag !== '1'"
+                @change="handleResultChange('single')"
+              >
+                <el-option label="宸茶В鍐�" value="resolved" />
+                <el-option label="宸茶В閲�" value="explained" />
+                <el-option label="宸茶浆浜�" value="transferred" />
+                <el-option label="闇�鏀硅繘" value="improvement" />
+                <el-option label="宸查┏鍥�" value="rejected" />
+              </el-select>
+            </el-form-item>
 
-        <el-form-item
-          label="鏈�缁堟剰瑙�"
-          prop="finaloption"
-          v-if="hasQualityPermission"
-        >
-          <el-input
-            v-model="processForm.finaloption"
-            type="textarea"
-            :rows="3"
-            placeholder="璇疯緭鍏ユ渶缁堝鐞嗘剰瑙侊紙鏈�澶�300瀛楋級"
-            maxlength="300"
-            show-word-limit
-          />
-        </el-form-item>
-      </el-form>
+            <el-form-item label="澶勭悊璇存槑" prop="handledesc">
+              <el-input
+                v-model="processForm.handledesc"
+                type="textarea"
+                :rows="4"
+                placeholder="璇疯緭鍏ュ鐞嗚鏄庯紙鏈�澶�500瀛楋級"
+                maxlength="500"
+                show-word-limit
+                :disabled="processForm.handleFlag !== '1'"
+              />
+            </el-form-item>
+
+            <el-form-item
+              label="鏈�缁堟剰瑙�"
+              prop="finaloption"
+              v-if="hasQualityPermission"
+            >
+              <el-input
+                v-model="processForm.finaloption"
+                type="textarea"
+                :rows="3"
+                placeholder="璇疯緭鍏ユ渶缁堝鐞嗘剰瑙侊紙鏈�澶�300瀛楋級"
+                maxlength="300"
+                show-word-limit
+              />
+            </el-form-item>
+          </el-form>
+        </div>
+
+        <!-- 鍙冲崐閮ㄥ垎锛氭祦杞椂闂寸嚎锛堥�変腑娴佽浆鍚庡睍寮�锛� -->
+        <div class="flow-right" v-if="processForm.isFlow === '1'">
+          <el-timeline>
+            <el-timeline-item type="primary" placement="top" color="#5788fe">
+              <div class="flow-step-title">绗竴姝� 路 娴佽浆鍙戣捣</div>
+              <el-form label-width="90px" size="small" class="flow-step-form">
+                <el-form-item label="娴佽浆浜�">
+                  <el-input
+                    v-model="processForm.flowPerson"
+                    placeholder="娴佽浆浜猴紙榛樿褰撳墠鐢ㄦ埛锛�"
+                  />
+                </el-form-item>
+                <el-form-item label="娴佽浆浜虹紪鍙�">
+                  <el-input
+                    v-model="processForm.flowPersonCode"
+                    placeholder="娴佽浆浜虹紪鍙凤紙榛樿褰撳墠鐢ㄦ埛宸ュ彿锛�"
+                  />
+                </el-form-item>
+                <el-form-item label="娴佽浆鏃堕棿">
+                  <el-date-picker
+                    v-model="processForm.flowTime"
+                    type="datetime"
+                    value-format="yyyy-MM-dd HH:mm:ss"
+                    placeholder="娴佽浆鏃堕棿"
+                    style="width: 100%"
+                  />
+                </el-form-item>
+                <el-form-item label="娴佽浆璇存槑">
+                  <el-input
+                    v-model="processForm.flowDesc"
+                    type="textarea"
+                    :rows="2"
+                    placeholder="璇疯緭鍏ユ祦杞鏄�"
+                  />
+                </el-form-item>
+                <el-form-item label="娴佽浆绉戝">
+                  <el-select
+                    v-model="processForm.flowDepts"
+                    multiple
+                    filterable
+
+                    placeholder="璇烽�夋嫨娴佽浆绉戝"
+                    style="width: 100%"
+                    @change="onFlowDeptChange"
+                  >
+                    <el-option
+                      v-for="dept in deptList"
+                      :key="dept.deptCode"
+                      :label="dept.label + '锛�' + dept.deptCode + '锛�'"
+                      :value="dept.deptCode"
+                    />
+                  </el-select>
+                </el-form-item>
+                <el-form-item label="鎺ㄩ�佽礋璐d汉">
+                  <el-select
+                    v-model="processForm.pushUsers"
+                    multiple
+                    filterable
+
+                    placeholder="璇烽�夋嫨鎺ㄩ�佽礋璐d汉"
+                    style="width: 100%"
+                    @change="onPushUserChange"
+                  >
+                    <el-option
+                      v-for="u in userList"
+                      :key="u.userId"
+                      :label="u.nickName || u.userName"
+                      :value="u.userId"
+                    />
+                  </el-select>
+                </el-form-item>
+              </el-form>
+            </el-timeline-item>
+
+            <el-timeline-item type="primary" placement="top" color="#e6a23c">
+              <div class="flow-step-title">绗簩姝� 路 绉戝鍙嶉</div>
+              <el-table
+                :data="processForm.flowFeedbacks"
+                border
+                size="mini"
+                v-if="processForm.flowFeedbacks.length > 0"
+              >
+                <el-table-column
+                  label="绉戝"
+                  prop="deptName"
+                  width="120"
+                  align="center"
+                />
+                <el-table-column label="鏄惁澶勭悊" width="130" align="center">
+                  <template slot-scope="scope">
+                    <el-select
+                      v-model="scope.row.agree"
+                      placeholder="璇烽�夋嫨"
+                      size="mini"
+                    >
+                      <el-option label="宸插鐞�" value="1" />
+                      <el-option label="鏈鐞�" value="0" />
+                    </el-select>
+                  </template>
+                </el-table-column>
+                <el-table-column label="鍏蜂綋璇存槑" min-width="200" align="center">
+                  <template slot-scope="scope">
+                    <el-input
+                      v-model="scope.row.desc"
+                      placeholder="璇疯緭鍏ュ叿浣撹鏄�"
+                      size="mini"
+                    />
+                  </template>
+                </el-table-column>
+              </el-table>
+              <div v-else class="flow-empty-tip">
+                璇峰厛鍦ㄧ涓�姝ラ�夋嫨娴佽浆绉戝
+              </div>
+            </el-timeline-item>
+
+            <el-timeline-item type="primary" placement="top" color="#67c23a">
+              <div class="flow-step-title">绗笁姝� 路 澶勭悊缁撴灉</div>
+              <el-form label-width="90px" size="small" class="flow-step-form">
+                <el-form-item label="澶勭悊浜�">
+                  <el-input
+                    v-model="processForm.handleBy"
+                    placeholder="澶勭悊浜�"
+                  />
+                </el-form-item>
+                <el-form-item label="澶勭悊鏃堕棿">
+                  <el-date-picker
+                    v-model="processForm.handleTime"
+                    type="datetime"
+                    value-format="yyyy-MM-dd HH:mm:ss"
+                    placeholder="澶勭悊鏃堕棿"
+                    style="width: 100%"
+                  />
+                </el-form-item>
+              </el-form>
+            </el-timeline-item>
+          </el-timeline>
+        </div>
+      </div>
       <span slot="footer" class="dialog-footer">
         <el-button @click="processDialogVisible = false">鍙栨秷</el-button>
         <el-button type="primary" @click="submitProcess" :loading="processing">
@@ -365,7 +522,8 @@
     >
       <el-form
         :model="batchProcessForm"
-        :rules="processRules"
+        :rules="batchProcessRules"
+        :validate-on-rule-change="false"
         ref="batchProcessForm"
         label-width="100px"
         size="medium"
@@ -373,8 +531,10 @@
         <el-form-item label="澶勭悊鐘舵��" prop="handleFlag">
           <el-select
             v-model="batchProcessForm.handleFlag"
+            clearable
             placeholder="璇烽�夋嫨澶勭悊鐘舵��"
             style="width: 100%"
+            @change="handleFlagChange('batch', $event)"
           >
             <el-option label="宸插鐞�" :value="'1'" />
             <el-option label="鍙栨秷澶勭悊" :value="'0'" />
@@ -384,6 +544,7 @@
         <el-form-item label="鎶ュ绉戝" prop="ccdepts">
           <el-select
             v-model="batchProcessForm.ccdepts"
+            clearable
             placeholder="璇烽�夋嫨鎶ュ绉戝"
             multiple
             filterable
@@ -403,9 +564,11 @@
         <el-form-item label="澶勭悊缁撴灉" prop="handleresult">
           <el-select
             v-model="batchProcessForm.handleresult"
+            clearable
             placeholder="璇烽�夋嫨澶勭悊缁撴灉"
             style="width: 100%"
             :disabled="batchProcessForm.handleFlag !== '1'"
+            @change="handleResultChange('batch')"
           >
             <el-option label="宸茶В鍐�" value="resolved" />
             <el-option label="宸茶В閲�" value="explained" />
@@ -483,7 +646,7 @@
 import DetailsAnomaly from "./components/DetailsAnomaly.vue";
 import { tracelist, traceedit } from "@/api/AiCentre/index";
 import dayjs from "dayjs";
-import { deptTreeSelect } from "@/api/system/user";
+import { deptTreeSelect, listUser } from "@/api/system/user";
 
 export default {
   name: "BatchProcess",
@@ -512,7 +675,7 @@
         todeptcode: "",
         handleFlag: "",
         templateType: null,
-        scriptids: null,
+        scriptids: [],
         pageNum: 1,
         pageSize: 10,
       },
@@ -527,6 +690,8 @@
 
       // 绉戝鍒楄〃
       deptList: [],
+      // 鐢ㄦ埛鍒楄〃锛堟祦杞礋璐d汉鍊欓�夛級
+      userList: [],
 
       // 寮傚父鍒楄〃鏁版嵁
       exceptionList: [],
@@ -540,48 +705,27 @@
         handleresult: "",
         handledesc: "",
         finaloption: "",
+        // 娴佽浆鐩稿叧
+        isFlow: "0",
+        flowPerson: "",
+        flowPersonCode: "",
+        flowTime: "",
+        flowDesc: "",
+        flowDepts: [],
+        flowDeptNames: [],
+        flowDeptCodes: [],
+        pushUsers: [],
+        pushUserNames: [],
+        pushUserNos: [],
+        flowFeedbacks: [],
+        handleBy: "",
+        handleTime: "",
       },
       batchProgress: {
         visible: false,
         percentage: 0,
         processed: 0,
         total: 0,
-      },
-      processRules: {
-        handleFlag: [
-          { required: true, message: "璇烽�夋嫨澶勭悊鐘舵��", trigger: "change" },
-        ],
-        handleresult: [
-          {
-            required: true,
-            message: "璇烽�夋嫨澶勭悊缁撴灉",
-            trigger: "change",
-            validator: (rule, value, callback) => {
-              if (this.processForm.handleFlag === "1" && !value) {
-                callback(new Error("璇烽�夋嫨澶勭悊缁撴灉"));
-              } else {
-                callback();
-              }
-            },
-          },
-        ],
-        handledesc: [
-          {
-            required: true,
-            message: "璇疯緭鍏ュ鐞嗚鏄�",
-            trigger: "blur",
-            validator: (rule, value, callback) => {
-              if (
-                this.processForm.handleFlag === "1" &&
-                (!value || value.trim().length < 3)
-              ) {
-                callback(new Error("澶勭悊璇存槑鑷冲皯3涓瓧绗�"));
-              } else {
-                callback();
-              }
-            },
-          },
-        ],
       },
 
       // 鎵归噺澶勭悊瀵硅瘽妗�
@@ -596,29 +740,101 @@
   },
 
   created() {
-    // 浠庤矾鐢卞弬鏁拌幏鍙栭棶棰業D
-    this.filterParams.scriptids =
-      this.$route.query.questionId || this.$route.query.questionIds || null;
-    // if (this.$route.query.questionId) {
-    // } else if (this.$route.query.questionIds) {
-    //   console.log(
-    //     this.$route.query.questionIds,
-    //     "this.$route.query.questionIds"
-    //   );
-
+    // 浠庤矾鐢卞弬鏁拌幏鍙栭棶棰業D闆嗗悎锛坰criptids 涓烘暟缁勫舰寮忥級
+    this.filterParams.scriptids = this.resolveScriptids();
     this.filterParams.templateType = Number(this.$route.query.type) || null;
-
-    //   this.filterParams.scriptid = null;
-    // }
     this.hasQualityPermission = this.checkQualityPermission();
   },
 
   mounted() {
     this.loadExceptionList();
     this.getDeptOptions();
+    this.getUserOptions();
+  },
+
+  computed: {
+    // 鍗曟潯澶勭悊琛ㄥ崟鏍¢獙瑙勫垯
+    processRules() {
+      return this.buildProcessRules(
+        () => this.processForm.handleFlag,
+        () => this.processForm.handleresult
+      );
+    },
+    // 鎵归噺澶勭悊琛ㄥ崟鏍¢獙瑙勫垯
+    batchProcessRules() {
+      return this.buildProcessRules(
+        () => this.batchProcessForm.handleFlag,
+        () => this.batchProcessForm.handleresult
+      );
+    },
   },
 
   methods: {
+    // 鐢熸垚澶勭悊琛ㄥ崟鏍¢獙瑙勫垯
+    //   澶勭悊缁撴灉锛氬鐞嗙姸鎬佷负銆屽凡澶勭悊銆嶆椂蹇呭~
+    //   澶勭悊璇存槑锛氬鐞嗙粨鏋滀负銆屽凡瑙e喅銆嶆椂蹇呭~锛屽叾浠栨儏鍐典笉蹇呭~锛堜絾浠嶅彲濉啓锛�
+    buildProcessRules(getFlag, getResult) {
+      return {
+        handleFlag: [
+          { required: true, message: "璇烽�夋嫨澶勭悊鐘舵��", trigger: "change" },
+        ],
+        handleresult: [
+          {
+            required: true,
+            trigger: "change",
+            validator: (rule, value, callback) => {
+              if (getFlag() === "1" && !value) {
+                callback(new Error("璇烽�夋嫨澶勭悊缁撴灉"));
+              } else {
+                callback();
+              }
+            },
+          },
+        ],
+        handledesc: [
+          {
+            required: true,
+            trigger: "blur",
+            validator: (rule, value, callback) => {
+              if (
+                getResult() === "resolved" &&
+                (!value || String(value).trim().length < 3)
+              ) {
+                callback(
+                  new Error("澶勭悊缁撴灉涓恒�屽凡瑙e喅銆嶆椂锛屽鐞嗚鏄庤嚦灏戝~鍐�3涓瓧绗�")
+                );
+              } else {
+                callback();
+              }
+            },
+          },
+        ],
+      };
+    },
+
+    // 鍒囨崲澶勭悊鐘舵�佹椂锛氶潪銆屽凡澶勭悊銆嶆竻绌轰粠灞炲瓧娈靛苟娓呮帀鏃х殑鏍¢獙鎻愮ず
+    // 锛堟鏃朵笁涓帶浠跺浜� disabled锛岀敤鎴锋棤娉曚慨鏀癸紝鐣欐畫鍊间細琚彁浜ゆ垚鑴忔暟鎹級
+    handleFlagChange(type, val) {
+      const isBatch = type === "batch";
+      const form = isBatch ? this.batchProcessForm : this.processForm;
+      const refName = isBatch ? "batchProcessForm" : "processForm";
+      if (val !== "1") {
+        form.ccdepts = [];
+        form.handleresult = "";
+        form.handledesc = "";
+      }
+      const ref = this.$refs[refName];
+      if (ref) ref.clearValidate(["handleresult", "handledesc"]);
+    },
+
+    // 鍒囨崲澶勭悊缁撴灉鏃讹細鍙竻鎺夊鐞嗚鏄庣殑鏃ф牎楠屾彁绀猴紝涓嶆竻绌哄凡濉唴瀹�
+    // 锛堥潪銆屽凡瑙e喅銆嶆椂璇存槑涓嶆槸蹇呭~锛屼絾鐢ㄦ埛浠嶅彲鑷効濉啓锛�
+    handleResultChange(type) {
+      const refName = type === "batch" ? "batchProcessForm" : "processForm";
+      const ref = this.$refs[refName];
+      if (ref) ref.clearValidate(["handledesc"]);
+    },
+
     // 鏍煎紡鍖栨棩鏈熸椂闂�
     formatDateTime(dateTime) {
       if (!dateTime) return "";
@@ -648,6 +864,19 @@
         .catch((error) => {
           console.error("鑾峰彇绉戝鍒楄〃澶辫触:", error);
           this.$message.error("鑾峰彇绉戝鍒楄〃澶辫触");
+        });
+    },
+    /** 鏌ヨ鐢ㄦ埛鍒楄〃锛堟祦杞礋璐d汉鍊欓�夛級 */
+    getUserOptions() {
+      listUser({ pageNum: 1, pageSize: 19999 })
+        .then((res) => {
+          if (res.code == 200) {
+            this.userList = res.rows || [];
+          }
+        })
+        .catch((error) => {
+          console.error("鑾峰彇鐢ㄦ埛鍒楄〃澶辫触:", error);
+          this.$message.error("鑾峰彇鐢ㄦ埛鍒楄〃澶辫触");
         });
     },
     flattenArray(multiArray) {
@@ -731,6 +960,14 @@
       });
     },
 
+    // 瑙f瀽璺敱涓殑 scriptids锛堝吋瀹规暟缁� / 閫楀彿鍒嗛殧瀛楃涓诧紝缁熶竴杩斿洖鏁扮粍锛�
+    resolveScriptids() {
+      const raw = this.$route.query.scriptids;
+      if (Array.isArray(raw)) return raw;
+      if (raw) return String(raw).split(",");
+      return [];
+    },
+
     // 鏋勫缓鏌ヨ鍙傛暟
     buildQueryParams() {
       const params = {
@@ -750,11 +987,8 @@
         params.templateType = this.filterParams.templateType;
       }
 
-      // if (this.filterParams.scriptid) {
-      //   params.scriptid = this.filterParams.scriptid;
-      // }
-      if (this.filterParams.scriptids) {
-        params.scriptids = this.filterParams.scriptids.split(",");
+      if (this.filterParams.scriptids && this.filterParams.scriptids.length) {
+        params.scriptids = this.filterParams.scriptids;
       }
 
       return params;
@@ -796,10 +1030,8 @@
       this.filterParams = {
         todeptcode: "",
         handleFlag: "",
-        scriptids:
-          this.$route.query.questionId || this.$route.query.questionIds || null,
+        scriptids: this.resolveScriptids(),
         templateType: Number(this.$route.query.type) || null,
-
         pageNum: 1,
         pageSize: 10,
       };
@@ -870,22 +1102,212 @@
     handleProcess(row) {
       this.currentExceptionId = row.id;
 
+      const nickName = this.$store.state.user.nickName || "";
+      const now = dayjs().format("YYYY-MM-DD HH:mm:ss");
+
+      // 鏄惁娴佽浆锛堝悗绔� circulationStatus锛�0 涓嶆祦杞� / 1 娴佽浆锛�
+      const isFlow = String(row.circulationStatus) === "1" ? "1" : "0";
+
+      // 鎶ュ绉戝锛堝吋瀹规暟缁勬垨閫楀彿鍒嗛殧瀛楃涓诧級
+      const ccdepts = Array.isArray(row.ccdepts)
+        ? row.ccdepts
+        : row.ccdepts
+        ? row.ccdepts.split(",")
+        : [];
+
+      // 娴佽浆绉戝 code / 鍚嶇О
+      const flowDeptCodes = row.circulationDeptCode
+        ? row.circulationDeptCode.split(",")
+        : [];
+      const flowDeptNames = row.circulationDeptName
+        ? row.circulationDeptName.split(",")
+        : [];
+
+      // 鎺ㄩ�佽礋璐d汉锛氬悗绔瓨宸ュ彿(userName)锛屼笅鎷� value 鐢� userId锛岄渶鍥炴槧
+      const pushUserNos = row.circulationDeptPersonCode
+        ? row.circulationDeptPersonCode.split(",")
+        : [];
+      const pushUserNames = row.circulationDeptPersonName
+        ? row.circulationDeptPersonName.split(",")
+        : [];
+      const pushUsers = pushUserNos.map((no) => {
+        const u = this.userList.find((item) => item.userName === no);
+        return u ? u.userId : no;
+      });
+
+      // 绉戝鍙嶉锛氫粠 handledesc JSON 涓繕鍘�
+      let flowFeedbacks = [];
+      try {
+        const parsed =
+          typeof row.handledesc === "string" &&
+          row.handledesc.trim().startsWith("{")
+            ? JSON.parse(row.handledesc)
+            : null;
+        if (parsed && parsed.flow && Array.isArray(parsed.flow.feedbacks)) {
+          flowFeedbacks = parsed.flow.feedbacks;
+        }
+      } catch (e) {
+        // handledesc 闈� JSON锛屽拷鐣�
+      }
+
+      // 娴佽浆鏃堕棿锛氬悗绔� Date 瀛楁锛岀粺涓�杞垚 yyyy-MM-dd HH:mm:ss 渚涙棩鏈熼�夋嫨鍣ㄥ洖鏄�
+      let flowTime = now;
+      if (row.circulationTime) {
+        const d = new Date(
+          typeof row.circulationTime === "number"
+            ? row.circulationTime
+            : String(row.circulationTime).replace(" ", "T")
+        );
+        if (!isNaN(d.getTime())) {
+          const pad = (n) => String(n).padStart(2, "0");
+          flowTime =
+            d.getFullYear() +
+            "-" +
+            pad(d.getMonth() + 1) +
+            "-" +
+            pad(d.getDate()) +
+            " " +
+            pad(d.getHours()) +
+            ":" +
+            pad(d.getMinutes()) +
+            ":" +
+            pad(d.getSeconds());
+        }
+      }
+
       // 鍒濆鍖栬〃鍗曟暟鎹�
       this.processForm = {
-        handleFlag: row.handleFlag === "0" ? "1" : "0",
-        ccdepts: row.ccdepts ? row.ccdepts.split(",") : [],
+        handleFlag: "1",
+        ccdepts,
         handleresult: row.handleresult || "",
-        handledesc: row.handledesc || "",
+        handledesc: this.formatHandledesc(row.handledesc),
         finaloption: row.finaloption || "",
+        // 娴佽浆鐩稿叧
+        isFlow,
+        flowPerson: row.circulationPersonName || nickName,
+        flowPersonCode:
+          row.circulationPersonCode || this.$store.state.user.userName || "",
+        flowTime,
+        flowDesc: row.circulationExplain || "",
+        flowDepts: [...flowDeptCodes],
+        flowDeptNames: [...flowDeptNames],
+        flowDeptCodes: [...flowDeptCodes],
+        pushUsers,
+        pushUserNames: [...pushUserNames],
+        pushUserNos: [...pushUserNos],
+        flowFeedbacks,
+        handleBy: nickName,
+        handleTime: now,
       };
 
       this.processDialogVisible = true;
+    },
+
+    // 娴佽浆绉戝鍙樺寲锛氬悓姝ョ瀹ゅ悕绉般�佺瀹ode锛屽苟鐢熸垚鍙嶉琛ㄦ牸
+    onFlowDeptChange(codes) {
+      this.processForm.flowDeptCodes = [...codes];
+      this.processForm.flowDeptNames = codes.map((code) => {
+        const dept = this.deptList.find((item) => item.deptCode === code);
+        return dept ? dept.label : code;
+      });
+      // 鐢熸垚/缁存姢绗簩姝ュ弽棣堣〃鏍硷紙淇濈暀宸插~鍐欑殑鍙嶉锛�
+      this.processForm.flowFeedbacks = codes.map((code) => {
+        const existing = this.processForm.flowFeedbacks.find(
+          (item) => item.deptCode === code
+        );
+        if (existing) return existing;
+        const dept = this.deptList.find((item) => item.deptCode === code);
+        return {
+          deptCode: code,
+          deptName: dept ? dept.label : code,
+          agree: "",
+          desc: "",
+        };
+      });
+    },
+
+    // 鎺ㄩ�佽礋璐d汉鍙樺寲锛氬悓姝ヨ礋璐d汉鏄电О銆佸伐鍙�
+    onPushUserChange(ids) {
+      this.processForm.pushUserNames = ids.map((id) => {
+        const user = this.userList.find((u) => String(u.userId) === String(id));
+        return user ? user.nickName || user.userName : "";
+      });
+      this.processForm.pushUserNos = ids.map((id) => {
+        const user = this.userList.find((u) => String(u.userId) === String(id));
+        return user ? user.userName || "" : "";
+      });
+    },
+
+    // 缁勮 handledesc锛氭祦杞椂浠� JSON 褰㈠紡淇濆瓨鍘熷鐞嗚鏄庝笌娴佽浆璇存槑銆佺瀹ゅ弽棣�
+    buildHandledesc() {
+      if (this.processForm.isFlow !== "1") {
+        return this.processForm.handledesc;
+      }
+      const flow = {
+        feedbacks: this.processForm.flowFeedbacks.map((f) => ({
+          deptCode: f.deptCode,
+          deptName: f.deptName,
+          agree: f.agree,
+          desc: f.desc,
+        })),
+      };
+      return JSON.stringify({
+        desc: this.processForm.handledesc,
+        flow,
+      });
+    },
+
+    // 瑙f瀽 handledesc锛氳嫢鏄祦杞� JSON 鍒欏彇鍏朵腑鐨勫鐞嗚鏄庯紝鍚﹀垯鍘熸牱杩斿洖
+    formatHandledesc(val) {
+      if (!val) return "";
+      if (typeof val !== "string") return val;
+      const trimmed = val.trim();
+      if (trimmed.startsWith("{")) {
+        try {
+          const obj = JSON.parse(trimmed);
+          if (obj && obj.desc != null) return obj.desc;
+        } catch (e) {
+          // 涓嶆槸鍚堟硶 JSON锛屾寜鍘熸枃鏈繑鍥�
+        }
+      }
+      return val;
+    },
+
+    // 娴佽浆鏃堕棿杞负甯︽椂鍖虹殑 ISO 鏍煎紡锛屼緵鍚庣 Date 绫诲瀷瀛楁鍙嶅簭鍒楀寲
+    toISODateTime(str) {
+      if (!str) return null;
+      const d = new Date(String(str).replace(" ", "T"));
+      if (isNaN(d.getTime())) return null;
+      const pad = (n) => String(n).padStart(2, "0");
+      const offset = -d.getTimezoneOffset();
+      const sign = offset >= 0 ? "+" : "-";
+      const abs = Math.abs(offset);
+      const tz = sign + pad(Math.floor(abs / 60)) + ":" + pad(abs % 60);
+      return (
+        d.getFullYear() +
+        "-" + pad(d.getMonth() + 1) +
+        "-" + pad(d.getDate()) +
+        "T" + pad(d.getHours()) +
+        ":" + pad(d.getMinutes()) +
+        ":" + pad(d.getSeconds()) +
+        "." + String(d.getMilliseconds()).padStart(3, "0") +
+        tz
+      );
     },
 
     // 鎻愪氦澶勭悊
     async submitProcess() {
       this.$refs.processForm.validate(async (valid) => {
         if (!valid) {
+          return;
+        }
+
+        // 娴佽浆鏃舵牎楠屽繀椤婚�夋嫨娴佽浆绉戝
+        if (
+          this.processForm.isFlow === "1" &&
+          (!this.processForm.flowDepts || this.processForm.flowDepts.length === 0)
+        ) {
+          this.$message.warning("璇峰厛閫夋嫨娴佽浆绉戝");
           return;
         }
 
@@ -897,14 +1319,30 @@
             id: this.currentExceptionId,
             handleFlag: this.processForm.handleFlag,
             handleresult: this.processForm.handleresult,
-            handledesc: this.processForm.handledesc,
+            handledesc: this.buildHandledesc(),
             finaloption: this.processForm.finaloption,
-            handleBy: this.$store.state.user.nickName,
-            handleTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
+            handleBy:
+              this.processForm.handleBy || this.$store.state.user.nickName,
+            handleTime:
+              this.processForm.handleTime ||
+              dayjs().format("YYYY-MM-DD HH:mm:ss"),
             // 灏嗘暟缁勮浆鎹负閫楀彿鍒嗛殧鐨勫瓧绗︿覆
             ccdepts: Array.isArray(this.processForm.ccdepts)
               ? this.processForm.ccdepts.join(",")
               : this.processForm.ccdepts,
+            // 娴佽浆鐩稿叧涓昏〃瀛楁锛堝悗绔凡瀹氱锛�
+            circulationPersonName: this.processForm.flowPerson,
+            circulationPersonCode:
+              this.processForm.flowPersonCode ||
+              this.$store.state.user.userName ||
+              "",
+            circulationStatus: this.processForm.isFlow,
+            circulationDeptName: this.processForm.flowDeptNames.join(","),
+            circulationDeptCode: this.processForm.flowDeptCodes.join(","),
+            circulationDeptPersonName: this.processForm.pushUserNames.join(","),
+            circulationDeptPersonCode: this.processForm.pushUserNos.join(","),
+            circulationExplain: this.processForm.flowDesc,
+            circulationTime: this.toISODateTime(this.processForm.flowTime),
           };
           // TODO: 杩欓噷闇�瑕佽皟鐢ㄥ疄闄呯殑澶勭悊鎺ュ彛
           await traceedit(submitData);
@@ -950,7 +1388,7 @@
           };
 
           // 鎺у埗骞跺彂鏁�
-          const CONCURRENT_LIMIT = 10; // 鍚屾椂鏈�澶�3涓姹�
+          const CONCURRENT_LIMIT = 10; // 鍚屾椂鏈�澶� 10 涓姹�
           const totalCount = this.selectedExceptionIds.length;
           const results = [];
           let successCount = 0;
@@ -999,11 +1437,12 @@
                 console.error(`澶勭悊璁板綍 ${result.id} 澶辫触:`, result.error);
               }
             });
-            // 鏇存柊杩涘害
-            this.batchProgress.processed = i + 1;
-            this.batchProgress.percentage = Math.round(
-              ((i + 1) / totalCount) * 100
-            );
+            // 鏇存柊杩涘害锛歩 鏄壒娆¤捣濮嬬储寮曪紙姝ラ暱涓� CONCURRENT_LIMIT锛夛紝
+            // 蹇呴』鍔犱笂鏈壒瀹為檯鏉℃暟锛屽惁鍒欒繘搴﹀彧浼氭寜鎵规暟閫掑锛堝 25 鏉″彧鍒� 84%锛�
+            this.batchProgress.processed = i + batchIds.length;
+            this.batchProgress.percentage = totalCount
+              ? Math.round(((i + batchIds.length) / totalCount) * 100)
+              : 100;
             // 鏄剧ず杩涘害
             console.log(
               `杩涘害: ${Math.min(
@@ -1227,6 +1666,47 @@
   }
 }
 
+// 澶勭悊寮规宸﹀彸甯冨眬
+.flow-dialog-body {
+  display: flex;
+  align-items: stretch;
+
+  .flow-left {
+    flex: 1;
+    min-width: 320px;
+    padding-right: 24px;
+    border-right: 1px solid #ebeef5;
+  }
+
+  .flow-right {
+    flex: 1;
+    min-width: 320px;
+    padding-left: 24px;
+    max-height: 60vh;
+    overflow-y: auto;
+
+    .flow-step-title {
+      font-weight: 600;
+      color: #303133;
+      margin-bottom: 12px;
+      font-size: 14px;
+    }
+
+    .flow-step-form {
+      ::v-deep .el-form-item {
+        margin-bottom: 12px;
+      }
+    }
+
+    .flow-empty-tip {
+      padding: 20px;
+      text-align: center;
+      color: #909399;
+      font-size: 13px;
+    }
+  }
+}
+
 @media (max-width: 768px) {
   .batch-process {
     padding: 10px;

--
Gitblit v1.9.3