From 47bae1f938f915206e3934ea960aff975e5738c9 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期五, 12 六月 2026 16:09:49 +0800
Subject: [PATCH] feat(teachingDemo): 新增工艺路线与BOM教学演示模块

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

diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js b/src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js
new file mode 100644
index 0000000..e2f1e45
--- /dev/null
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js
@@ -0,0 +1,612 @@
+import { createEmptyNode, formatDisplayTime, mapNodesFromApi, mapSignModeFromApi, mapSignModeToApi, normalizeFlowNodes, nodeSignModeLabel } from "../approve-template/approveTemplateConstants.js";
+import { buildFormPayloadFromFields, parseFormConfigToData } from "../approve-template/formConfigUtils.js";
+import { isDynamicOptionSource, resolveSelectDisplayLabel } from "../approve-template/selectOptionSource.js";
+import { appendDotNotationQuery, buildApprovalInstanceSearchDto } from "../approve-shared/approvalInstanceListSearch.js";
+
+/** 瀹℃壒绫诲瀷锛堜笌鍚庣瀛楁 approvalType 瀵归綈锛屽悗鏈熷彲鍚屾锛� */
+export const APPROVAL_TYPE_OPTIONS = [
+  { value: "cost_reimburse", label: "璐圭敤鎶ラ攢鐢宠", cellBg: "#e8f8ef", cellColor: "#1a7f4b" },
+  { value: "travel_reimburse", label: "宸梾鎶ラ攢鐢宠", cellBg: "#f0f2f5", cellColor: "#606266" },
+  { value: "overtime", label: "鍔犵彮鐢宠", cellBg: "#fdf3e8", cellColor: "#c45c26" },
+  { value: "leave", label: "璇峰亣鐢宠", cellBg: "#fce8f0", cellColor: "#b84d7a" },
+  { value: "work_handover", label: "宸ヤ綔浜ゆ帴鐢宠", cellBg: "#f0e8fc", cellColor: "#6b4d9e" },
+  { value: "regular", label: "杞鐢宠", cellBg: "#e8f4fc", cellColor: "#2b6cb0" },
+  { value: "resign", label: "绂昏亴鐢宠", cellBg: "#ffffff", cellColor: "#303133", border: "1px solid #e4e7ed" },
+  { value: "transfer", label: "璋冨矖鐢宠", cellBg: "#ffffff", cellColor: "#303133", border: "1px solid #e4e7ed" },
+  { value: "out_office", label: "鍏嚭鐢宠", cellBg: "#e8f4ff", cellColor: "#409eff" },
+  { value: "business_trip", label: "鍑哄樊鐢宠", cellBg: "#fdf6ec", cellColor: "#e6a23c" },
+  { value: "procurement", label: "閲囪喘瀹℃壒", cellBg: "#f4f4f5", cellColor: "#909399" },
+  { value: "quotation", label: "鎶ヤ环瀹℃壒", cellBg: "#f4ecfc", cellColor: "#9b59b6" },
+  { value: "shipment", label: "鍙戣揣瀹℃壒", cellBg: "#e8faf6", cellColor: "#1abc9c" },
+  { value: "enterprise_news", label: "浼佷笟鏂伴椈", cellBg: "#ecf5ff", cellColor: "#409eff" },
+];
+
+/** 鍒楄〃鏌ヨ锛氬鎵圭姸鎬侊紙涓庡悗绔� status 鏋氫妇涓�鑷达級 */
+export const APPROVAL_STATUS_SEARCH_OPTIONS = [
+  { value: "DRAFT", label: "鑽夌" },
+  { value: "PENDING", label: "寰呭鎵�" },
+  { value: "APPROVED", label: "宸查�氳繃" },
+  { value: "REJECTED", label: "宸查┏鍥�" },
+];
+
+/**
+ * 瀹℃壒鐘舵�佸睍绀猴紙涓庡悗绔� status 鏋氫妇涓�鑷达級
+ * DRAFT鈫掕崏绋� PENDING鈫掑緟瀹℃壒/杩涜涓� APPROVED鈫掑凡閫氳繃/宸插畬鎴� REJECTED鈫掑凡椹冲洖
+ */
+export const APPROVAL_STATUS_OPTIONS = [
+  { value: "draft", api: "DRAFT", label: "鑽夌" },
+  { value: "pending", api: "PENDING", label: "寰呭鎵�" },
+  { value: "approved", api: "APPROVED", label: "宸查�氳繃" },
+  { value: "rejected", api: "REJECTED", label: "宸查┏鍥�" },
+  { value: "cancelled", api: "CANCELLED", label: "宸叉挙閿�" },
+];
+
+/** 鏁板瓧鐘舵�佺爜锛堥儴鍒嗗悗绔敤 0/1/2锛� */
+const STATUS_NUMERIC_MAP = {
+  0: "pending",
+  1: "approved",
+  2: "rejected",
+  3: "cancelled",
+  4: "cancelled",
+};
+
+/** 鍚庣 status / 椤甸潰 approvalStatus 鈫� 缁熶竴椤甸潰 key锛坧ending | approved | rejected | cancelled锛� */
+export function normalizeApprovalStatusKey(v) {
+  if (v == null || v === "") return "pending";
+  if (typeof v === "number" || (typeof v === "string" && /^\d+$/.test(v.trim()))) {
+    const numKey = STATUS_NUMERIC_MAP[Number(v)];
+    if (numKey) return numKey;
+  }
+  const s = String(v).trim();
+  if (!s) return "pending";
+  const upper = s.toUpperCase();
+  if (upper === "DRAFT") return "draft";
+  if (upper === "PUBLISHED") return "approved";
+  if (upper === "OFFLINE") return "cancelled";
+  if (upper === "APPROVED" || upper === "APPROVE" || upper === "PASS" || upper === "AGREE") {
+    return "approved";
+  }
+  if (upper === "REJECTED" || upper === "REJECT" || upper === "REFUSE" || upper === "REFUSED" || upper === "DENIED") {
+    return "rejected";
+  }
+  if (upper === "CANCELLED" || upper === "CANCEL" || upper === "REVOKED") return "cancelled";
+  if (upper === "PENDING" || upper === "IN_PROGRESS" || upper === "PROCESSING" || upper === "RUNNING" || upper === "WAIT" || upper === "WAITING") {
+    return "pending";
+  }
+  if (s.includes("鑽夌")) return "draft";
+  if (s.includes("椹冲洖") || s.includes("鎷掔粷")) return "rejected";
+  if (s.includes("涓嬬嚎")) return "cancelled";
+  if (s.includes("鎾ら攢")) return "cancelled";
+  if (s.includes("鍙戝竷") || s.includes("閫氳繃") || s.includes("瀹屾垚")) return "approved";
+  if (s.includes("寰呭") || s.includes("杩涜涓�") || s.includes("瀹℃壒涓�")) return "pending";
+  const lower = s.toLowerCase();
+  if (["draft", "pending", "approved", "rejected", "cancelled"].includes(lower)) return lower;
+  return "pending";
+}
+
+/** 浠庡垪琛�/璇︽儏琛岃В鏋愬悗绔師濮嬬姸鎬侊紙鍏煎澶氬瓧娈靛懡鍚嶏級 */
+export function resolveInstanceStatusRaw(row) {
+  if (!row || typeof row !== "object") return "";
+  const candidates = [
+    row.status,
+    row.statusRaw,
+    row.approvalStatus,
+    row.statusName,
+    row.statusLabel,
+    row.approvalStatusName,
+    row.statusDesc,
+    row.instanceStatus,
+    row.approvalInstanceStatus,
+    row.approveStatus,
+    row.auditStatus,
+    row.approvalInstance?.status,
+    row.approvalInstanceVo?.status,
+  ];
+  for (const c of candidates) {
+    if (c != null && c !== "") return c;
+  }
+  const tasks = row.tasks;
+  if (Array.isArray(tasks) && tasks.length) {
+    const rejected = tasks.some(t => normalizeApprovalStatusKey(t?.status ?? t?.taskStatus) === "rejected");
+    if (rejected) return "REJECTED";
+    const allApproved = tasks.every(t => normalizeApprovalStatusKey(t?.status ?? t?.taskStatus) === "approved");
+    if (allApproved) return "APPROVED";
+  }
+  return "";
+}
+
+/** 鎻愪氦寮圭獥锛氭ā鏉垮崱鐗囷紙鏉ヨ嚜鍚庣鍒楄〃锛� */
+export function mapSubmitTemplateCard(row) {
+  const cfg = parseFormConfigToData(row?.formConfig);
+  return {
+    id: row?.id,
+    key: String(row?.id ?? ""),
+    businessType: row?.businessType ?? cfg.approvalType ?? row?.approvalType ?? "",
+    approvalType: cfg.approvalType || row?.approvalType || "",
+    label: row?.templateName || "鈥�",
+    summaryPlaceholder: (row?.description || "").trim() || cfg.summaryPlaceholder || "鐐瑰嚮濉啓骞舵彁浜�",
+  };
+}
+
+export function matchBusinessTypeValue(a, b) {
+  if (a == null || a === "" || b == null || b === "") return false;
+  return a === b || a === Number(b) || Number(a) === b || String(a) === String(b);
+}
+
+/** 瀹℃壒璁板綍 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) {
+  return approvalStatusLabel(status);
+}
+
+export function mapTaskStatusTagType(status) {
+  return approvalStatusTagType(status);
+}
+
+/** 鍚庣 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";
+}
+
+/**
+ * 鍗曞瓧娈靛睍绀哄�硷紙璇︽儏鍙銆佸垪琛ㄤ富琛級
+ * @param {object} [caches] 浜哄憳/閮ㄩ棬涓嬫媺缂撳瓨锛岀敤浜庤В鏋愩�屼汉鍛樺垪琛ㄣ�嶇被瀛楁涓哄鍚�
+ */
+export function formatFieldDisplayValue(field, val, caches) {
+  if (val == null || val === "" || (Array.isArray(val) && !val.length)) return "鈥�";
+  if (field?.type === "select" && isDynamicOptionSource(field.optionSource)) {
+    const label = resolveSelectDisplayLabel(field, val, caches || {});
+    if (label && label !== "鈥�") return label;
+    return String(val);
+  }
+  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 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 };
+}
+
+/** 瑙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 || "",
+    businessType: tpl.businessType ?? submitForm?.businessType ?? "",
+    title,
+    formConfig: buildInstanceFormConfigJson({ ...tpl, fields: tpl.fields || submitForm?.formFieldDefs }, payload),
+    tasks: taskList,
+  };
+
+  const attachments = (Array.isArray(submitForm?.storageBlobDTOs) && submitForm.storageBlobDTOs.length ? submitForm.storageBlobDTOs : null) || tpl.storageBlobDTOs;
+  if (attachments?.length) dto.storageBlobDTOs = attachments;
+
+  if (isUpdate) {
+    dto.id = existingRow?.id ?? submitForm?.instanceId;
+    dto.instanceNo = existingRow?.instanceNo ?? submitForm?.instanceNo ?? "";
+    dto.status = submitForm?.saveStatusApi || 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 = submitForm?.saveStatusApi || "PENDING";
+    dto.currentLevel = 1;
+    dto.applicantId = userStore?.id;
+    dto.applicantName = userStore?.nickName || userStore?.name || "";
+  }
+  return dto;
+}
+
+/** 鏍¢獙鎻愪氦瀹℃壒娴佺▼锛堜笌妯℃澘椤佃鍒欎竴鑷达級 */
+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) {
+  return normalizeApprovalStatusKey(status);
+}
+
+/** 鍒楄〃/璇︽儏琛� 鈫� 椤甸潰 approvalStatus key */
+export function mapInstanceApprovalStatusFromRow(row) {
+  const raw = resolveInstanceStatusRaw(row);
+  return normalizeApprovalStatusKey(raw);
+}
+
+/** 椤甸潰 approvalStatus 鈫� 鍚庣 status */
+export function mapInstanceStatusToApi(approvalStatus) {
+  const key = normalizeApprovalStatusKey(approvalStatus);
+  const hit = APPROVAL_STATUS_OPTIONS.find(x => x.value === key);
+  return hit?.api || "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 statusRaw = resolveInstanceStatusRaw(row);
+  const approvalStatus = normalizeApprovalStatusKey(statusRaw);
+  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.approvalType || row.templateName || "",
+    unread: Boolean(row.isApprove) && approvalStatus === "pending",
+    isApprove: Boolean(row.isApprove),
+    approvalStatus,
+    statusRaw: 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 : [],
+    storageBlobVOList: row.storageBlobVOList || [],
+    storageBlobDTOs: row.storageBlobVOList || row.storageBlobDTOs || [],
+    flowNodes,
+    approvalFlowNodes: [],
+    currentNodeIndex: 0,
+    approvalRecords,
+    rejectReason: approvalRecords.find(r => r.result === "rejected")?.opinion || "",
+    purchaseContractNumber: row.purchaseContractNumber || "",
+    quotationNo: row.quotationNo || "",
+    shippingNo: row.shippingNo || "",
+  };
+}
+
+/** 瀹℃壒鎿嶄綔锛氫笌鍚庣 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, businessType, extraParams }) {
+  const dto = buildApprovalInstanceSearchDto(searchForm, extraParams);
+  const bizType = businessType ?? searchForm?.businessType;
+  if (bizType != null && bizType !== "") {
+    dto.businessType = bizType;
+  }
+
+  const params = {
+    current: page.current,
+    size: page.size,
+    "page.current": page.current,
+    "page.size": page.size,
+    ...dto,
+  };
+  appendDotNotationQuery(params, "approvalInstanceDto", dto);
+  return params;
+}
+
+export function approvalTypeLabel(v) {
+  return APPROVAL_TYPE_OPTIONS.find(x => x.value === v)?.label || v || "鈥�";
+}
+
+export function approvalTypeStyle(v) {
+  const hit = APPROVAL_TYPE_OPTIONS.find(x => x.value === v);
+  if (!hit) return {};
+  return {
+    backgroundColor: hit.cellBg,
+    color: hit.cellColor,
+    border: hit.border || "none",
+  };
+}
+
+export function approvalStatusLabel(v) {
+  const key = normalizeApprovalStatusKey(v);
+  return APPROVAL_STATUS_OPTIONS.find(x => x.value === key)?.label || "鈥�";
+}
+
+/** 涓氬姟鐢宠椤电姸鎬佹枃妗堬細PENDING鈫掕繘琛屼腑 APPROVED鈫掑凡瀹屾垚 REJECTED鈫掑凡椹冲洖 */
+export function businessApprovalStatusLabel(v) {
+  const key = normalizeApprovalStatusKey(v);
+  if (key === "draft") return "鑽夌";
+  if (key === "pending") return "杩涜涓�";
+  if (key === "approved") return "宸插畬鎴�";
+  if (key === "rejected") return "宸查┏鍥�";
+  if (key === "cancelled") return "宸叉挙閿�";
+  return "鈥�";
+}
+
+/**
+ * 涓氬姟鐢宠椤垫槸鍚﹀厑璁镐慨鏀癸紙浜斾釜鐢宠椤碉級
+ * 杩涜涓�(PENDING)銆佸凡瀹屾垚(APPROVED) 涓嶅彲淇敼锛涘凡椹冲洖銆佸凡鎾ら攢绛夊彲淇敼
+ */
+export function canEditBusinessInstanceRow(row) {
+  const key = normalizeApprovalStatusKey(row?.approvalStatus ?? row?.statusRaw ?? row?.status);
+  return key !== "pending" && key !== "approved";
+}
+
+export function businessApprovalStatusTagType(v) {
+  const key = normalizeApprovalStatusKey(v);
+  if (key === "draft") return "info";
+  if (key === "approved") return "success";
+  if (key === "rejected") return "danger";
+  if (key === "cancelled") return "info";
+  return "warning";
+}
+
+export function approvalStatusTagType(v) {
+  const key = normalizeApprovalStatusKey(v);
+  if (key === "draft") return "info";
+  if (key === "approved") return "success";
+  if (key === "rejected") return "danger";
+  if (key === "cancelled") return "info";
+  return "warning";
+}
+
+/** 鍒楄〃琛� 鈫� 缂栬緫琛ㄥ崟锛堜粎鐢ㄨ鏁版嵁鍥炴樉锛� */
+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)];
+
+  return {
+    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,
+    templateAttachments: initTemplateAttachmentsFromSnapshot(templateSnapshot),
+    storageBlobDTOs: (row?.storageBlobDTOs?.length ? row.storageBlobDTOs : row?.storageBlobVOList || []).map(f => JSON.parse(JSON.stringify(f))),
+  };
+}
+
+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 || "",
+    templateId: tpl?.templateId || "",
+    templateName: tpl?.label || "",
+    instanceId: "",
+    instanceNo: "",
+    statusRaw: "",
+    currentLevel: 1,
+    applicantId: null,
+    applicantName: "",
+    templateSnapshot: templateOverride || null,
+    formFieldDefs: tpl?.fields || [],
+    formPayload: payload,
+    flowNodes,
+    templateAttachments: tpl?.storageBlobDTOs ? JSON.parse(JSON.stringify(tpl.storageBlobDTOs)) : [],
+    storageBlobDTOs: [],
+  };
+}
+
+export function initTemplateAttachmentsFromSnapshot(templateSnapshot) {
+  const list = templateSnapshot?.storageBlobDTOs;
+  return list?.length ? JSON.parse(JSON.stringify(list)) : [];
+}

--
Gitblit v1.9.3