From 930d38ed2a3c2131be3305a585602c7a5a275fe3 Mon Sep 17 00:00:00 2001
From: spring <2396852758@qq.com>
Date: 星期二, 19 五月 2026 17:09:12 +0800
Subject: [PATCH] Merge branch 'dev-new_pro_OA' of http://114.132.189.42:9002/r/product-inventory-management into dev-new_pro_OA

---
 src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js |  686 ++++++++++++++++++++++++++++++++++++--------------------
 1 files changed, 439 insertions(+), 247 deletions(-)

diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js b/src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js
index 447627a..80af992 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js
@@ -1,4 +1,13 @@
-import dayjs from "dayjs";
+import {
+  createEmptyNode,
+  formatDisplayTime,
+  mapNodesFromApi,
+  mapSignModeFromApi,
+  mapSignModeToApi,
+  normalizeFlowNodes,
+  nodeSignModeLabel,
+} from "../approve-template/approveTemplateConstants.js";
+import { buildFormPayloadFromFields, parseFormConfigToData } from "../approve-template/formConfigUtils.js";
 
 /** 瀹℃壒绫诲瀷锛堜笌鍚庣瀛楁 approvalType 瀵归綈锛屽悗鏈熷彲鍚屾锛� */
 export const APPROVAL_TYPE_OPTIONS = [
@@ -25,105 +34,401 @@
   { value: "cancelled", label: "宸叉挙閿�" },
 ];
 
-/** 瀹℃壒鏂瑰紡 approvalMode */
-export const APPROVAL_MODE_OPTIONS = [
-  { value: "parallel", label: "涓庣" },
-  { value: "or_sign", label: "鎴栫" },
-];
+export const LEGACY_APPROVE_LIST_STORAGE_KEY = "oa_unified_approve_list_v1";
+
+export function clearLegacyApproveListStorage() {
+  try {
+    localStorage.removeItem(LEGACY_APPROVE_LIST_STORAGE_KEY);
+  } catch {
+    /* ignore */
+  }
+}
+
+/** 鎻愪氦寮圭獥锛氭ā鏉垮崱鐗囷紙鏉ヨ嚜鍚庣鍒楄〃锛� */
+export function mapSubmitTemplateCard(row) {
+  const cfg = parseFormConfigToData(row?.formConfig);
+  return {
+    id: row?.id,
+    key: String(row?.id ?? ""),
+    approvalType: cfg.approvalType || row?.approvalType || "",
+    label: row?.templateName || "鈥�",
+    summaryPlaceholder: (row?.description || "").trim() || cfg.summaryPlaceholder || "鐐瑰嚮濉啓骞舵彁浜�",
+  };
+}
+
+/** 瀹℃壒璁板綍 approveAction 鈫� 椤甸潰 result */
+export function mapRecordResultFromApi(action) {
+  const s = String(action || "").toUpperCase();
+  if (s === "APPROVED" || s === "APPROVE" || s === "PASS") return "approved";
+  if (s === "REJECTED" || s === "REJECT" || s === "REFUSE") return "rejected";
+  return "pending";
+}
+
+/** 鍚庣 records 鈫� 鏃堕棿绾垮睍绀虹粨鏋� */
+export function mapRecordsFromApi(records) {
+  const list = Array.isArray(records) ? records : [];
+  return list.map((r) => ({
+    id: r.id,
+    operatorName: r.approverName || r.operatorName || r.createUserName || "",
+    result: mapRecordResultFromApi(r.approveAction ?? r.action ?? r.status),
+    opinion: r.approveComment || r.comment || r.opinion || "",
+    time: formatDisplayTime(r.approveTime || r.createTime || r.time || ""),
+    raw: r,
+  }));
+}
+
+export function mapTaskStatusLabel(status) {
+  const s = String(status || "").toUpperCase();
+  if (s === "APPROVED") return "宸查�氳繃";
+  if (s === "REJECTED") return "宸查┏鍥�";
+  if (s === "PENDING") return "寰呭鎵�";
+  if (s === "CANCELLED") return "宸叉挙閿�";
+  return status || "鈥�";
+}
+
+export function mapTaskStatusTagType(status) {
+  const s = String(status || "").toUpperCase();
+  if (s === "APPROVED") return "success";
+  if (s === "REJECTED") return "danger";
+  if (s === "CANCELLED") return "info";
+  return "warning";
+}
+
+/** 鍚庣 tasks 鈫� 椤甸潰 flowNodes锛堟寜 levelNo 鍒嗙粍锛屼緵娴佺▼缂栬緫/灞曠ず锛� */
+export function mapTasksToFlowNodes(tasks) {
+  const list = Array.isArray(tasks) ? tasks : [];
+  if (!list.length) return [];
+  const byLevel = new Map();
+  list.forEach((t) => {
+    const level = Number(t.levelNo ?? t.taskLevel ?? t.nodeOrder ?? 1);
+    if (!byLevel.has(level)) {
+      byLevel.set(level, {
+        id: t.nodeId,
+        templateId: t.templateId,
+        nodeOrder: level,
+        signMode: mapSignModeFromApi(t.approveType),
+        approvers: [],
+        tasks: [],
+      });
+    }
+    const node = byLevel.get(level);
+    node.approvers.push({
+      id: t.id,
+      nodeId: t.nodeId,
+      templateId: t.templateId,
+      approverId: t.approverId,
+      approverName: t.approverName || "",
+      status: t.status,
+      approveComment: t.approveComment,
+      approveTime: t.approveTime,
+    });
+    node.tasks.push(t);
+    if (t.approveType != null) {
+      node.signMode = mapSignModeFromApi(t.approveType);
+    }
+  });
+  return [...byLevel.entries()]
+    .sort(([a], [b]) => a - b)
+    .map(([, node]) => node);
+}
+
+/** 椤甸潰 flowNodes 鈫� 鍚庣 tasks */
+export function mapFlowNodesToTasks(flowNodes, { instanceId, templateId } = {}) {
+  const nodes = normalizeFlowNodes(flowNodes);
+  const tasks = [];
+  nodes.forEach((n) => {
+    const levelNo = n.nodeOrder ?? 1;
+    const approveType = mapSignModeToApi(n.signMode);
+    n.approvers.forEach((a, idx) => {
+      const task = {
+        levelNo,
+        approveType,
+        approverId: a.approverId,
+        approverName: a.approverName || "",
+        sortNo: a.sortNo ?? idx + 1,
+      };
+      if (a.id != null) task.id = a.id;
+      if (a.nodeId != null) task.nodeId = a.nodeId;
+      if (a.templateId != null) task.templateId = a.templateId;
+      else if (templateId) task.templateId = templateId;
+      if (instanceId) task.instanceId = instanceId;
+      if (a.status != null) task.status = a.status;
+      tasks.push(task);
+    });
+  });
+  return tasks;
+}
+
+function guessFieldTypeFromValue(val) {
+  if (Array.isArray(val) && val.length === 2) return "datetimerange";
+  if (typeof val === "number") return "number";
+  if (typeof val === "string" && /^\d{4}-\d{2}-\d{2}$/.test(val)) return "date";
+  if (typeof val === "string" && val.length > 100) return "textarea";
+  return "text";
+}
+
+/** 鍗曞瓧娈靛睍绀哄�硷紙璇︽儏鍙锛� */
+export function formatFieldDisplayValue(field, val) {
+  if (val == null || val === "" || (Array.isArray(val) && !val.length)) return "鈥�";
+  if (field?.type === "select" && field.options?.length) {
+    const hit = field.options.find((o) => String(o.value) === String(val));
+    return hit?.label || String(val);
+  }
+  if (Array.isArray(val)) return val.join(" 鑷� ");
+  return String(val);
+}
 
 /**
- * 鎻愪氦瀹℃壒妯℃澘锛堟寜绫诲瀷涓�閿~鎶ワ紝瀛楁鍚庢湡涓庡悗绔ā鏉垮悓姝ワ級
+ * 浠庤鏁版嵁 / formConfig 瑙f瀽濉姤瀛楁瀹氫箟涓� formPayload锛堜笌鏂板鎻愪氦缁撴瀯涓�鑷达級
  */
-export const SUBMIT_TEMPLATES = {
-  cost_reimburse: {
-    approvalType: "cost_reimburse",
-    label: "璐圭敤鎶ラ攢",
-    summaryPlaceholder: "璇峰~鍐欐姤閿�浜嬬敱銆侀噾棰濈瓑",
-    fields: [
-      { key: "summary", label: "鐢宠浜嬬敱", type: "textarea", required: true, rows: 3 },
-      { key: "amount", label: "鎶ラ攢閲戦(鍏�)", type: "number", required: true, min: 0, precision: 2 },
-    ],
-    approvalMode: "parallel",
-  },
-  travel_reimburse: {
-    approvalType: "travel_reimburse",
-    label: "宸梾鎶ラ攢",
-    summaryPlaceholder: "鍑哄樊琛岀▼涓庤垂鐢ㄨ鏄�",
-    fields: [
-      { key: "summary", label: "宸梾璇存槑", type: "textarea", required: true, rows: 3 },
-      { key: "amount", label: "鎶ラ攢閲戦(鍏�)", type: "number", required: true, min: 0, precision: 2 },
-      { key: "tripDays", label: "鍑哄樊澶╂暟", type: "number", required: false, min: 0, precision: 0 },
-    ],
-    approvalMode: "parallel",
-  },
-  overtime: {
-    approvalType: "overtime",
-    label: "鍔犵彮鐢宠",
-    fields: [
-      { key: "summary", label: "鍔犵彮浜嬬敱", type: "textarea", required: true, rows: 3 },
-      { key: "overtimeDate", label: "鍔犵彮鏃ユ湡", type: "date", required: true },
-      { key: "hours", label: "鍔犵彮鏃堕暱(灏忔椂)", type: "number", required: true, min: 0.5, precision: 1 },
-    ],
-    approvalMode: "parallel",
-  },
-  leave: {
-    approvalType: "leave",
-    label: "璇峰亣鐢宠",
-    fields: [
-      { key: "leaveType", label: "璇峰亣绫诲瀷", type: "select", required: true, options: [
-        { label: "骞村亣", value: "annual" },
-        { label: "鐥呭亣", value: "sick" },
-        { label: "浜嬪亣", value: "personal" },
-        { label: "璋冧紤", value: "compensatory" },
-      ] },
-      { key: "summary", label: "璇峰亣浜嬬敱", type: "textarea", required: true, rows: 2 },
-      { key: "dateRange", label: "璇峰亣鏃堕棿", type: "datetimerange", required: true },
-    ],
-    approvalMode: "parallel",
-  },
-  work_handover: {
-    approvalType: "work_handover",
-    label: "宸ヤ綔浜ゆ帴",
-    fields: [
-      { key: "summary", label: "浜ゆ帴璇存槑", type: "textarea", required: true, rows: 3 },
-      { key: "handoverTo", label: "浜ゆ帴瀵硅薄", type: "text", required: true },
-    ],
-    approvalMode: "parallel",
-  },
-  regular: {
-    approvalType: "regular",
-    label: "杞鐢宠",
-    fields: [
-      { key: "summary", label: "杞璇存槑", type: "textarea", required: true, rows: 3 },
-      { key: "regularDate", label: "鎷熻浆姝f棩鏈�", type: "date", required: true },
-    ],
-    approvalMode: "parallel",
-  },
-  resign: {
-    approvalType: "resign",
-    label: "绂昏亴鐢宠",
-    fields: [
-      { key: "summary", label: "绂昏亴鍘熷洜", type: "textarea", required: true, rows: 3 },
-      { key: "lastWorkDay", label: "鏈�鍚庡伐浣滄棩", type: "date", required: true },
-    ],
-    approvalMode: "or_sign",
-  },
-  transfer: {
-    approvalType: "transfer",
-    label: "璋冨矖鐢宠",
-    fields: [
-      { key: "summary", label: "璋冨矖璇存槑", type: "textarea", required: true, rows: 2 },
-      { key: "targetDept", label: "鐩爣閮ㄩ棬", type: "text", required: true },
-      { key: "targetPost", label: "鐩爣宀椾綅", type: "text", required: true },
-    ],
-    approvalMode: "parallel",
-  },
-};
+export function resolveInstanceFormFields(row) {
+  const cfg = parseInstanceFormConfig(row?.formConfig);
+  let fields = (row?.formFieldDefs?.length ? row.formFieldDefs : cfg.fields) || [];
+  const formPayload = {
+    ...(fields.length ? buildFormPayloadFromFields(fields) : {}),
+    ...cfg.formPayload,
+    ...(row?.formPayload || {}),
+  };
+  if (!fields.length && Object.keys(formPayload).length) {
+    fields = Object.keys(formPayload)
+      .filter((k) => k && k !== "summary")
+      .map((k) => ({
+        key: k,
+        label: k,
+        type: guessFieldTypeFromValue(formPayload[k]),
+        required: false,
+        rows: 3,
+        min: 0,
+        precision: 0,
+        options: [],
+      }));
+  }
+  const templateSnapshot = {
+    label: row?.templateName || row?.title || "瀹℃壒",
+    approvalType: cfg.approvalType || row?.approvalType || "",
+    summaryPlaceholder: cfg.summaryPlaceholder || "",
+    templateId: row?.templateId,
+    fields,
+  };
+  return { fields, formPayload, templateSnapshot, formConfigData: cfg };
+}
 
-export const STORAGE_KEY = "oa_unified_approve_list_v1";
+/** 瑙f瀽瀹炰緥 formConfig */
+export function parseInstanceFormConfig(formConfig) {
+  let raw = {};
+  if (formConfig) {
+    if (typeof formConfig === "object") raw = formConfig;
+    else {
+      try {
+        raw = JSON.parse(formConfig);
+      } catch {
+        raw = {};
+      }
+    }
+  }
+  const data = parseFormConfigToData(formConfig);
+  const payload = raw.formPayload;
+  return {
+    summaryPlaceholder: raw.summaryPlaceholder || data.summaryPlaceholder || "",
+    approvalType: raw.approvalType || "",
+    fields: data.fields || [],
+    formPayload: payload && typeof payload === "object" ? payload : {},
+  };
+}
+
+export function unwrapInstanceDetail(res) {
+  const data = res?.data ?? res;
+  if (!data || typeof data !== "object") return {};
+  if (data.id != null || data.instanceNo) return data;
+  if (data.approvalInstanceVo) return data.approvalInstanceVo;
+  return data;
+}
+
+/** 濉姤鍐呭 + 妯℃澘瀛楁瀹氫箟 鈫� formConfig JSON */
+export function buildInstanceFormConfigJson(templateSnapshot, formPayload) {
+  const payload = formPayload || {};
+  return JSON.stringify({
+    summaryPlaceholder: templateSnapshot?.summaryPlaceholder || "",
+    approvalType: templateSnapshot?.approvalType || "",
+    fields: templateSnapshot?.fields || [],
+    formPayload: payload,
+  });
+}
+
+/** 缁勮淇濆瓨/鏇存柊瀹℃壒 DTO */
+export function buildInstanceDto({ submitForm, activeTemplate, userStore, flowNodes, existingRow }) {
+  const payload = submitForm?.formPayload || {};
+  const tpl = activeTemplate || {};
+  const title =
+    String(payload.summary || payload.title || "").trim() ||
+    tpl.label ||
+    submitForm?.templateName ||
+    "瀹℃壒鐢宠";
+  const templateId = submitForm?.templateId || tpl.templateId;
+  const instanceId = existingRow?.id ?? submitForm?.instanceId;
+  const taskList = mapFlowNodesToTasks(flowNodes || submitForm?.flowNodes, {
+    instanceId,
+    templateId,
+  });
+  const isUpdate = Boolean(instanceId);
+
+  const dto = {
+    templateId,
+    templateName: submitForm?.templateName || tpl.label || "",
+    title,
+    formConfig: buildInstanceFormConfigJson({ ...tpl, fields: tpl.fields || submitForm?.formFieldDefs }, payload),
+    tasks: taskList,
+  };
+
+  if (isUpdate) {
+    dto.id = existingRow?.id ?? submitForm?.instanceId;
+    dto.instanceNo = existingRow?.instanceNo ?? submitForm?.instanceNo ?? "";
+    dto.status =
+      existingRow?.statusRaw || mapInstanceStatusToApi(existingRow?.approvalStatus) || "PENDING";
+    dto.currentLevel = existingRow?.currentLevel ?? submitForm?.currentLevel ?? 1;
+    dto.applicantId = existingRow?.applicantId ?? existingRow?.applicantNo;
+    dto.applicantName = existingRow?.applicantName || "";
+  } else {
+    dto.status = "PENDING";
+    dto.currentLevel = 1;
+    dto.applicantId = userStore?.id;
+    dto.applicantName = userStore?.nickName || userStore?.name || "";
+  }
+  return dto;
+}
+
+/** @deprecated 浣跨敤 buildInstanceDto */
+export function buildSaveInstanceDto(params) {
+  return buildInstanceDto(params);
+}
+
+/** 鏍¢獙鎻愪氦瀹℃壒娴佺▼锛堜笌妯℃澘椤佃鍒欎竴鑷达級 */
+export function validateSubmitFlowNodes(flowNodes) {
+  const nodes = normalizeFlowNodes(flowNodes);
+  if (!nodes.length) return { ok: false, message: "璇疯嚦灏戦厤缃竴涓鎵硅妭鐐�" };
+  for (let i = 0; i < nodes.length; i++) {
+    if (!nodes[i].approvers.length) {
+      return { ok: false, message: `璇蜂负绗� ${i + 1} 涓妭鐐归�夋嫨鑷冲皯涓�鍚嶅鎵逛汉` };
+    }
+  }
+  return { ok: true, nodes };
+}
+
+/** 鍚庣 status 鈫� 椤甸潰 approvalStatus */
+export function mapInstanceStatusFromApi(status) {
+  const s = String(status || "").toUpperCase();
+  if (s === "APPROVED") return "approved";
+  if (s === "REJECTED") return "rejected";
+  if (s === "CANCELLED") return "cancelled";
+  return "pending";
+}
+
+/** 椤甸潰 approvalStatus 鈫� 鍚庣 status */
+export function mapInstanceStatusToApi(approvalStatus) {
+  const s = String(approvalStatus || "").toLowerCase();
+  if (s === "approved") return "APPROVED";
+  if (s === "rejected") return "REJECTED";
+  if (s === "cancelled") return "CANCELLED";
+  return "PENDING";
+}
+
+export function unwrapInstancePage(res) {
+  const data = res?.data ?? res;
+  return {
+    records: Array.isArray(data?.records) ? data.records : [],
+    total: Number(data?.total ?? 0),
+  };
+}
+
+/** 鍒嗛〉鍒楄〃椤� 鈫� 琛ㄦ牸琛� */
+export function mapInstanceFromApi(row) {
+  if (!row) return {};
+  const approvalStatus = mapInstanceStatusFromApi(row.status);
+  const createTime = formatDisplayTime(row.createTime ?? row.applyTime ?? "");
+  const applyTime = formatDisplayTime(row.applyTime ?? "");
+  const finishTime = formatDisplayTime(row.finishTime ?? "");
+  const resolved = resolveInstanceFormFields(row);
+  const { fields, formPayload, templateSnapshot } = resolved;
+  const tasks = Array.isArray(row.tasks) ? row.tasks : [];
+  const flowNodes = tasks.length
+    ? mapTasksToFlowNodes(tasks)
+    : mapNodesFromApi(row.nodes || row.flowNodes);
+  const approvalRecords = mapRecordsFromApi(row.records);
+  return {
+    id: row.id,
+    bizId: row.instanceNo || String(row.id ?? ""),
+    instanceNo: row.instanceNo || "",
+    templateId: row.templateId,
+    templateName: row.templateName || "",
+    businessId: row.businessId,
+    businessType: row.businessType,
+    businessName: row.businessName || "",
+    applicantId: row.applicantId,
+    applicantNo: row.applicantId != null ? String(row.applicantId) : "",
+    applicantName: row.applicantName || "",
+    approvalType: row.templateName || "",
+    unread: Boolean(row.isApprove) && approvalStatus === "pending",
+    isApprove: Boolean(row.isApprove),
+    approvalStatus,
+    statusRaw: row.status,
+    createTime,
+    applyTime: applyTime === "鈥�" ? "" : applyTime,
+    finishTime: finishTime === "鈥�" ? "" : finishTime,
+    title: row.title || "",
+    summary: row.title || row.templateName || "",
+    currentLevel: row.currentLevel,
+    formConfig: row.formConfig,
+    formPayload,
+    formFieldDefs: fields,
+    templateSnapshot,
+    tasks,
+    records: Array.isArray(row.records) ? row.records : [],
+    flowNodes,
+    approvalFlowNodes: [],
+    currentNodeIndex: 0,
+    approvalRecords,
+    rejectReason:
+      approvalRecords.find((r) => r.result === "rejected")?.opinion || "",
+  };
+}
+
+/** 瀹℃壒鎿嶄綔锛氫笌鍚庣 status 鏋氫妇涓�鑷� */
+export const APPROVE_ACTION_APPROVED = "APPROVED";
+export const APPROVE_ACTION_REJECTED = "REJECTED";
+
+/** 椤甸潰鎿嶄綔 鈫� approveAction */
+export function mapApproveActionToApi(uiResult) {
+  return uiResult === "rejected" ? APPROVE_ACTION_REJECTED : APPROVE_ACTION_APPROVED;
+}
+
+/** 缁勮瀹℃壒鎻愪氦 DTO */
+export function buildApproveInstanceDto(row, uiResult, comment) {
+  const opinion = (comment || "").trim();
+  return {
+    id: row?.id,
+    approveAction: mapApproveActionToApi(uiResult),
+    approveComment: opinion || (uiResult === "approved" ? "鍚屾剰" : ""),
+  };
+}
+
+export function buildApprovalInstanceListParams({ page, searchForm }) {
+  const params = {
+    current: page.current,
+    size: page.size,
+  };
+  const dto = {};
+  const kw = (searchForm?.applicantKeyword || "").trim();
+  if (kw) dto.applicantName = kw;
+  if (searchForm?.approvalType) {
+    const opt = APPROVAL_TYPE_OPTIONS.find((x) => x.value === searchForm.approvalType);
+    if (opt?.label) dto.templateName = opt.label;
+  }
+  if (Object.keys(dto).length) params.approvalInstanceDto = dto;
+  return params;
+}
 
 export function approvalTypeLabel(v) {
-  return APPROVAL_TYPE_OPTIONS.find((x) => x.value === v)?.label || "鈥�";
+  return APPROVAL_TYPE_OPTIONS.find((x) => x.value === v)?.label || v || "鈥�";
 }
 
 export function approvalTypeStyle(v) {
@@ -147,170 +452,57 @@
   return "primary";
 }
 
-export function approvalModeLabel(v) {
-  if (v === "countersign") return "鎴栫";
-  return APPROVAL_MODE_OPTIONS.find((x) => x.value === v)?.label || "涓庣";
-}
-
 export function unreadLabel(v) {
   return v ? "鏄�" : "鍚�";
 }
 
-export function buildDefaultFlowNodes() {
-  return [
-    {
-      approverId: "mock_supervisor",
-      approverName: "鐩村睘涓婄骇",
-      sortOrder: 1,
-      nodeOrder: 1,
-      nodeStatus: "process",
-      approveOpinion: "",
-      approveTime: "",
-    },
-    {
-      approverId: "mock_manager",
-      approverName: "閮ㄩ棬缁忕悊",
-      sortOrder: 2,
-      nodeOrder: 2,
-      nodeStatus: "wait",
-      approveOpinion: "",
-      approveTime: "",
-    },
-  ];
-}
+/** 鍒楄〃琛� 鈫� 缂栬緫琛ㄥ崟锛堜粎鐢ㄨ鏁版嵁鍥炴樉锛� */
+export function buildEditFormFromInstanceRow(row) {
+  const { fields, formPayload, templateSnapshot } = resolveInstanceFormFields(row);
+  const normalized = normalizeFlowNodes(
+    row?.flowNodes?.length ? row.flowNodes : mapTasksToFlowNodes(row?.tasks)
+  );
+  const flowNodes = normalized.length
+    ? JSON.parse(JSON.stringify(normalized))
+    : [createEmptyNode(1)];
 
-function demoRow(partial) {
-  const now = dayjs().format("YYYY-MM-DD HH:mm:ss");
   return {
-    id: partial.id,
-    bizId: partial.bizId || partial.id,
-    applicantNo: partial.applicantNo,
-    applicantName: partial.applicantName,
-    approvalType: partial.approvalType,
-    approvalMode: partial.approvalMode || "parallel",
-    unread: partial.unread ?? false,
-    approvalStatus: partial.approvalStatus || "pending",
-    createTime: partial.createTime || now,
-    summary: partial.summary || "",
-    formPayload: partial.formPayload || {},
-    approvalFlowNodes: partial.approvalFlowNodes || buildDefaultFlowNodes(),
-    currentNodeIndex: partial.currentNodeIndex ?? 0,
-    approvalRecords: partial.approvalRecords || [],
-    rejectReason: partial.rejectReason || "",
-    sourceRoute: partial.sourceRoute || "",
+    templateKey: String(row?.templateId || ""),
+    templateId: row?.templateId,
+    templateName: row?.templateName || templateSnapshot.label,
+    instanceId: row?.id,
+    instanceNo: row?.instanceNo || "",
+    statusRaw: row?.statusRaw || row?.status || "PENDING",
+    currentLevel: row?.currentLevel ?? 1,
+    applicantId: row?.applicantId,
+    applicantName: row?.applicantName || "",
+    templateSnapshot,
+    formFieldDefs: fields,
+    formPayload,
+    flowNodes,
   };
 }
 
-/** 鍒濆婕旂ず鏁版嵁锛堝叡 22 鏉★紝涓庡師鍨嬫暟閲忎竴鑷达級 */
-export function createInitialMockRows() {
-  const types = [
-    "cost_reimburse",
-    "travel_reimburse",
-    "overtime",
-    "leave",
-    "work_handover",
-    "regular",
-    "resign",
-    "transfer",
-    "cost_reimburse",
-    "leave",
-    "overtime",
-    "travel_reimburse",
-    "work_handover",
-    "regular",
-    "cost_reimburse",
-    "leave",
-    "transfer",
-    "resign",
-    "overtime",
-    "travel_reimburse",
-    "cost_reimburse",
-    "leave",
-  ];
-  const applicants = [
-    { no: "007", name: "鑻规灉" },
-    { no: "Guest001", name: "澶栭儴鐢ㄦ埛" },
-    { no: "0056", name: "鐜嬩簲" },
-    { no: "0042", name: "鏉庡洓" },
-    { no: "0088", name: "鐚尗" },
-    { no: "0012", name: "寮犱笁" },
-    { no: "0033", name: "璧靛叚" },
-  ];
-  const summaries = [
-    "鍔炲叕鐢ㄥ搧閲囪喘鎶ラ攢",
-    "涓婃捣鍑哄樊宸梾璐�",
-    "鍛ㄦ湯椤圭洰鍔犵彮",
-    "骞村亣 3 澶�",
-    "绂昏亴宸ヤ綔浜ゆ帴",
-    "璇曠敤鏈熻浆姝g敵璇�",
-    "涓汉鍘熷洜绂昏亴",
-    "璋冭嚦閿�鍞儴",
-    "瀹㈡埛鎺ュ緟椁愯垂",
-    "鐥呭亣 1 澶�",
-    "鑺傚亣鏃ュ�肩彮鍔犵彮",
-    "鍖椾含鍩硅宸梾",
-    "椤圭洰鏂囨。浜ゆ帴",
-    "鐮斿彂宀楄浆姝�",
-    "閫氳璐规姤閿�",
-    "浜嬪亣鍗婂ぉ",
-    "璋冨矖鑷冲競鍦洪儴",
-    "鍗忓晢绂昏亴",
-    "宸ヤ綔鏃ュ欢鏃跺姞鐝�",
-    "鎴愰兘灞曚細宸梾",
-    "浜ら�氳垂鎶ラ攢",
-    "璋冧紤 1 澶�",
-  ];
-  const statuses = ["pending", "pending", "pending", "approved", "pending", "pending", "rejected", "pending"];
-  return types.map((approvalType, i) => {
-    const ap = applicants[i % applicants.length];
-    const daysAgo = i % 14;
-    return demoRow({
-      id: `mock_${i + 1}`,
-      bizId: `BIZ${String(2025031400 + i)}`,
-      applicantNo: ap.no,
-      applicantName: ap.name,
-      approvalType,
-      approvalMode: i % 5 === 0 ? "or_sign" : "parallel",
-      unread: i % 3 === 0,
-      approvalStatus: statuses[i % statuses.length],
-      createTime: dayjs().subtract(daysAgo, "day").hour(9 + (i % 8)).minute((i * 7) % 60).second(0).format("YYYY-MM-DD HH:mm:ss"),
-      summary: summaries[i],
-      formPayload: { summary: summaries[i] },
-    });
-  });
-}
-
-export function loadStoredRows() {
-  try {
-    const raw = localStorage.getItem(STORAGE_KEY);
-    if (!raw) return null;
-    const parsed = JSON.parse(raw);
-    return Array.isArray(parsed) ? parsed : null;
-  } catch {
-    return null;
-  }
-}
-
-export function saveStoredRows(rows) {
-  try {
-    localStorage.setItem(STORAGE_KEY, JSON.stringify(rows));
-  } catch {
-    /* ignore quota */
-  }
-}
-
-export function createEmptySubmitForm(templateKey) {
-  const tpl = SUBMIT_TEMPLATES[templateKey];
-  const payload = { summary: "" };
-  (tpl?.fields || []).forEach((f) => {
-    if (f.type === "number") payload[f.key] = undefined;
-    else if (f.type === "datetimerange") payload[f.key] = [];
-    else payload[f.key] = "";
-  });
+export function createEmptySubmitForm(templateKey, templateOverride, flowNodesOverride) {
+  const tpl = templateOverride || null;
+  const payload = tpl?.fields?.length ? buildFormPayloadFromFields(tpl.fields) : { summary: "" };
+  const normalized = normalizeFlowNodes(flowNodesOverride);
+  const flowNodes = normalized.length
+    ? JSON.parse(JSON.stringify(normalized))
+    : [createEmptyNode(1)];
   return {
     templateKey: templateKey || "",
-    approvalMode: tpl?.approvalMode || "parallel",
+    templateId: tpl?.templateId || "",
+    templateName: tpl?.label || "",
+    instanceId: "",
+    instanceNo: "",
+    statusRaw: "",
+    currentLevel: 1,
+    applicantId: null,
+    applicantName: "",
+    templateSnapshot: templateOverride || null,
+    formFieldDefs: tpl?.fields || [],
     formPayload: payload,
-    approvalFlowNodes: buildDefaultFlowNodes(),
+    flowNodes,
   };
 }

--
Gitblit v1.9.3