From 61b9452f138841d453bf4b2503d78c2aaf2e4394 Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期六, 23 五月 2026 17:13:33 +0800
Subject: [PATCH] Merge branch 'dev-new_pro_OA' into dev_NEW_pro

---
 src/views/officeProcessAutomation/ApproveManage/approve-shared/useApprovalInstanceModule.js |  408 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 408 insertions(+), 0 deletions(-)

diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-shared/useApprovalInstanceModule.js b/src/views/officeProcessAutomation/ApproveManage/approve-shared/useApprovalInstanceModule.js
new file mode 100644
index 0000000..b474bb2
--- /dev/null
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-shared/useApprovalInstanceModule.js
@@ -0,0 +1,408 @@
+import {
+  deleteApprovalInstance,
+  listApprovalInstancePage,
+  saveApprovalInstance,
+  updateApprovalInstance,
+} from "@/api/officeProcessAutomation/approvalInstance.js";
+import useUserStore from "@/store/modules/user";
+import { ElMessage, ElMessageBox } from "element-plus";
+import { computed, reactive, ref } from "vue";
+import {
+  applyBindingToForm,
+  buildFormPayloadRules,
+  validateTemplateBinding,
+} from "./approvalTemplateBindingUtils.js";
+import {
+  buildApprovalInstanceListParams,
+  buildEditFormFromInstanceRow,
+  buildInstanceDto,
+  canEditBusinessInstanceRow,
+  createEmptySubmitForm,
+  mapInstanceFromApi,
+  resolveInstanceFormFields,
+  unwrapInstancePage,
+} from "../approve-list/approveListConstants.js";
+import { fetchBusinessTypeOptions } from "../approve-template/approveTemplateConstants.js";
+import {
+  collectOptionSourcesFromFields,
+  fetchSelectOptionCaches,
+} from "../approve-template/selectOptionSource.js";
+import { enrichInstanceRowFromFormConfig } from "./approvalInstanceFormConfigTable.js";
+import {
+  filterInstanceRowsByModuleSearch,
+  hasActiveModuleSearch,
+} from "./approvalInstanceListSearch.js";
+import {
+  getApprovalModuleConfig,
+  getModuleListBusinessType,
+  resolveModuleBusinessType,
+} from "./approvalModuleRegistry.js";
+
+/**
+ * 涓氬姟鐢宠椤靛叡鐢細瀹℃壒瀹炰緥鍒楄〃鏌ヨ銆佹柊澧�/淇敼淇濆瓨銆佽鎯�/缂栬緫寮圭獥锛堜笌瀹℃壒鍒楄〃涓�鑷达級
+ *
+ * @param {object} options
+ * @param {string} options.moduleKey approvalModuleRegistry 涓殑 key
+ * @param {(row: object) => object} [options.enrichListRow] 鍒楄〃琛屽寮猴紙浠� formPayload 瑙f瀽灞曠ず瀛楁锛�
+ * @param {(base: object) => object} [options.buildExtraListParams] 杩藉姞鏌ヨ鍙傛暟
+ * @param {() => void} [options.beforeSave] 淇濆瓨鍓嶉挬瀛愶紙濡傚悓姝ヤ笟鍔″瓧娈靛埌 formPayload锛�
+ * @param {import('vue').ComputedRef|object} [options.extraFormRules] 棰濆琛ㄥ崟鏍¢獙
+ */
+export function useApprovalInstanceModule(options = {}) {
+  const {
+    moduleKey,
+    enrichListRow,
+    buildExtraListParams,
+    beforeSave,
+    extraFormRules,
+  } = options;
+
+  const userStore = useUserStore();
+  const moduleConfig = computed(() => getApprovalModuleConfig(moduleKey));
+  const businessTypeOptions = ref([]);
+
+  /** 鍒楄〃鏌ヨ businessType锛氫紭鍏� registry 鍐欐鏋氫妇锛屽啀鍥為�� TypeEnums */
+  const defaultListBusinessType = computed(() => {
+    const fixed = getModuleListBusinessType(moduleKey);
+    if (fixed != null && fixed !== "") return fixed;
+    const resolved = resolveModuleBusinessType(moduleKey, businessTypeOptions.value);
+    if (resolved != null && resolved !== "") return resolved;
+    return "";
+  });
+
+  async function loadBusinessTypeOptions() {
+    if (businessTypeOptions.value.length) return;
+    try {
+      businessTypeOptions.value = await fetchBusinessTypeOptions();
+    } catch {
+      businessTypeOptions.value = [];
+    }
+  }
+
+  const tableData = ref([]);
+  const tableLoading = ref(false);
+  const page = reactive({ current: 1, size: 10, total: 0 });
+
+  const detailDialog = reactive({ visible: false });
+  const detailRow = ref({});
+
+  const submitDialog = reactive({ visible: false, mode: "add" });
+  const submitEditRow = ref(null);
+  const submitForm = reactive(createEmptySubmitForm(""));
+  const submitFormRef = ref();
+  const submitSaving = ref(false);
+
+  const templateBindVisible = ref(false);
+  const pendingTemplateBinding = ref(null);
+  /** 鏈�杩戜竴娆″垪琛ㄦ煡璇㈡潯浠讹紙淇濆瓨鍚庡埛鏂板垪琛ㄦ椂娌跨敤锛� */
+  let lastListSearchForm = null;
+
+  const isSubmitEdit = computed(() => submitDialog.mode === "edit");
+  const activeTemplate = computed(() => submitForm.templateSnapshot || null);
+  const submitFormFields = computed(() => {
+    const tplFields = activeTemplate.value?.fields;
+    if (tplFields?.length) return tplFields;
+    return submitForm.formFieldDefs || [];
+  });
+
+  const submitFormRules = computed(() => ({
+    ...buildFormPayloadRules(submitFormFields.value),
+    ...(extraFormRules?.value ?? extraFormRules ?? {}),
+  }));
+
+  const submitDialogTitle = computed(() => {
+    const label = moduleConfig.value?.label || "鐢宠";
+    if (submitDialog.mode === "edit") {
+      return `淇敼${activeTemplate.value?.label || submitForm.templateName || label}`;
+    }
+    return `鏂板${label}`;
+  });
+
+  function mapListRow(row, caches) {
+    const mapped = mapInstanceFromApi(row);
+    const fromFormConfig = enrichInstanceRowFromFormConfig(mapped, caches);
+    return enrichListRow ? enrichListRow(fromFormConfig) : fromFormConfig;
+  }
+
+  async function fetchList(searchForm = {}) {
+    await loadBusinessTypeOptions();
+    tableLoading.value = true;
+    try {
+      let extraParams = {};
+      if (buildExtraListParams) {
+        extraParams = buildExtraListParams(searchForm) || {};
+      }
+      const res = await listApprovalInstancePage(
+        buildApprovalInstanceListParams({
+          page,
+          searchForm,
+          businessType: defaultListBusinessType.value,
+          extraParams,
+        })
+      );
+      const { records, total } = unwrapInstancePage(res);
+      const mapped = records.map(mapInstanceFromApi);
+      const allFields = [];
+      for (const row of mapped) {
+        const { fields } = resolveInstanceFormFields(row);
+        allFields.push(...fields);
+      }
+      const caches = await fetchSelectOptionCaches(
+        collectOptionSourcesFromFields(allFields)
+      );
+      let rows = mapped.map((row) => mapListRow(row, caches));
+      if (hasActiveModuleSearch(moduleKey, searchForm)) {
+        rows = filterInstanceRowsByModuleSearch(moduleKey, rows, searchForm);
+      }
+      tableData.value = rows;
+      page.total = hasActiveModuleSearch(moduleKey, searchForm)
+        ? rows.length
+        : total;
+    } catch {
+      tableData.value = [];
+      page.total = 0;
+      ElMessage.error(`${moduleConfig.value?.label || "鐢宠"}鍒楄〃鍔犺浇澶辫触`);
+    } finally {
+      tableLoading.value = false;
+    }
+  }
+
+  function handleQuery(searchForm) {
+    lastListSearchForm = searchForm;
+    page.current = 1;
+    return fetchList(searchForm);
+  }
+
+  /** 杩涘叆椤甸潰锛氬厛鎷� TypeEnums 瑙f瀽 businessType锛屽啀鏌ュ垪琛� */
+  async function initModuleList(searchForm) {
+    await loadBusinessTypeOptions();
+    return handleQuery(searchForm);
+  }
+
+  function pagination({ page: p, limit }, searchForm) {
+    page.current = p;
+    page.size = limit;
+    return fetchList(searchForm);
+  }
+
+  function openDetail(row) {
+    detailRow.value = { ...row };
+    detailDialog.visible = true;
+  }
+
+  function openEdit(row) {
+    if (!canEditBusinessInstanceRow(row)) {
+      ElMessage.warning("杩涜涓垨宸插畬鎴愮殑瀹℃壒涓嶅彲淇敼");
+      return;
+    }
+    if (!row?.id) {
+      ElMessage.warning("鏃犳硶淇敼锛氱己灏戝鎵瑰疄渚� ID");
+      return;
+    }
+    submitDialog.mode = "edit";
+    submitEditRow.value = { ...row };
+    Object.assign(submitForm, buildEditFormFromInstanceRow(row));
+    submitDialog.visible = true;
+  }
+
+  function openEditFromDetail() {
+    const row = detailRow.value;
+    detailDialog.visible = false;
+    openEdit(row);
+  }
+
+  function resetSubmitForm() {
+    Object.assign(submitForm, createEmptySubmitForm(""));
+    submitEditRow.value = null;
+  }
+
+  function openAddWithTemplate() {
+    submitDialog.visible = false;
+    pendingTemplateBinding.value = null;
+    templateBindVisible.value = true;
+  }
+
+  function onTemplateBound(binding) {
+    pendingTemplateBinding.value = binding;
+  }
+
+  function onTemplateBindClosed() {
+    const binding = pendingTemplateBinding.value;
+    if (!binding) return;
+    pendingTemplateBinding.value = null;
+    openAddFromBinding(binding);
+  }
+
+  function openAddFromBinding(binding) {
+    resetSubmitForm();
+    applyBindingToForm(submitForm, binding);
+    submitDialog.mode = "add";
+    submitEditRow.value = null;
+    submitDialog.visible = true;
+  }
+
+  function closeSubmitDialog() {
+    submitDialog.visible = false;
+  }
+
+  async function submitInstanceForm(options = {}) {
+    const { skipValidate = false } = options;
+    if (!skipValidate) {
+      if (!submitFormRef.value?.validate) {
+        ElMessage.warning("琛ㄥ崟鏈氨缁紝璇峰叧闂脊绐楀悗閲嶈瘯");
+        return false;
+      }
+      try {
+        await submitFormRef.value.validate();
+      } catch {
+        ElMessage.warning("璇峰畬鍠勮〃鍗曞繀濉」鍚庡啀淇濆瓨");
+        return false;
+      }
+    }
+    if (!activeTemplate.value) {
+      ElMessage.warning("鏈姞杞藉鎵规ā鏉匡紝鏃犳硶淇濆瓨");
+      return false;
+    }
+    const bindingCheck = validateTemplateBinding({ flowNodes: submitForm.flowNodes });
+    if (!bindingCheck.ok) {
+      ElMessage.warning(bindingCheck.message);
+      return false;
+    }
+    if (!submitForm.templateId) {
+      ElMessage.warning("缂哄皯妯℃澘 ID锛屾棤娉曟彁浜�");
+      return false;
+    }
+    if (beforeSave) {
+      try {
+        await beforeSave(submitForm, { isEdit: isSubmitEdit.value, editRow: submitEditRow.value });
+      } catch {
+        return false;
+      }
+    }
+    if (submitSaving.value) return false;
+    submitSaving.value = true;
+    try {
+      const dto = buildInstanceDto({
+        submitForm,
+        activeTemplate: activeTemplate.value,
+        userStore,
+        flowNodes: bindingCheck.nodes,
+        existingRow: isSubmitEdit.value ? submitEditRow.value : null,
+      });
+      if (isSubmitEdit.value) {
+        await updateApprovalInstance(dto);
+      } else {
+        await saveApprovalInstance(dto);
+      }
+      submitDialog.visible = false;
+      if (!isSubmitEdit.value) page.current = 1;
+      await fetchList(lastListSearchForm ?? {});
+      if (detailDialog.visible && detailRow.value?.id === submitForm.instanceId) {
+        const hit = tableData.value.find((r) => r.id === submitForm.instanceId);
+        if (hit) detailRow.value = { ...hit };
+        else detailDialog.visible = false;
+      }
+      return true;
+    } catch {
+      ElMessage.error(isSubmitEdit.value ? "淇濆瓨澶辫触" : "鎻愪氦澶辫触");
+      return false;
+    } finally {
+      submitSaving.value = false;
+    }
+  }
+
+  async function removeInstance(row) {
+    if (row?.id == null || row.id === "") {
+      ElMessage.warning("鏃犳硶鍒犻櫎锛氱己灏戝鎵瑰疄渚� ID");
+      return;
+    }
+    const title = row.title || row.templateName || row.instanceNo || "璇ュ鎵�";
+    try {
+      await ElMessageBox.confirm(
+        `纭畾瑕佸垹闄ゅ鎵广��${title}銆嶅悧锛熷垹闄ゅ悗涓嶅彲鎭㈠銆俙,
+        "鍒犻櫎纭",
+        {
+          type: "warning",
+          confirmButtonText: "纭畾鍒犻櫎",
+          cancelButtonText: "鍙栨秷",
+          distinguishCancelAndClose: true,
+          autofocus: false,
+        }
+      );
+    } catch {
+      return;
+    }
+    try {
+      await deleteApprovalInstance([row.id]);
+      ElMessage.success("鍒犻櫎鎴愬姛");
+      if (detailDialog.visible && detailRow.value?.id === row.id) {
+        detailDialog.visible = false;
+      }
+      if (submitDialog.visible && submitEditRow.value?.id === row.id) {
+        submitDialog.visible = false;
+      }
+      await fetchList(lastListSearchForm ?? {});
+    } catch {
+      /* 閿欒鐢辨嫤鎴櫒鎻愮ず */
+    }
+  }
+
+  /** 鏋勫缓鏍囧噯鎿嶄綔鍒楋細璇︽儏銆佷慨鏀广�佸垹闄わ紙涓庡鎵瑰垪琛ㄤ竴鑷达級 */
+  function buildTableActions(extraOperations = []) {
+    return [
+      { name: "璇︽儏", type: "text", clickFun: (row) => openDetail(row) },
+      {
+        name: "淇敼",
+        type: "text",
+        disabled: (row) => !canEditBusinessInstanceRow(row),
+        clickFun: (row) => openEdit(row),
+      },
+      {
+        name: "鍒犻櫎",
+        type: "danger",
+        clickFun: (row) => removeInstance(row),
+      },
+      ...extraOperations,
+    ];
+  }
+
+  return {
+    moduleConfig,
+    defaultListBusinessType,
+    tableData,
+    tableLoading,
+    page,
+    detailDialog,
+    detailRow,
+    submitDialog,
+    submitEditRow,
+    submitForm,
+    submitFormRef,
+    submitSaving,
+    isSubmitEdit,
+    activeTemplate,
+    submitFormFields,
+    submitFormRules,
+    submitDialogTitle,
+    templateBindVisible,
+    pendingTemplateBinding,
+    fetchList,
+    handleQuery,
+    initModuleList,
+    pagination,
+    openDetail,
+    openEdit,
+    openEditFromDetail,
+    openAddWithTemplate,
+    onTemplateBound,
+    onTemplateBindClosed,
+    openAddFromBinding,
+    closeSubmitDialog,
+    resetSubmitForm,
+    submitInstanceForm,
+    removeInstance,
+    buildTableActions,
+    loadBusinessTypeOptions,
+    canEditBusinessInstanceRow,
+  };
+}

--
Gitblit v1.9.3