yyb
15 小时以前 a1a9521e1f537d742c4f3ebada9b102bfefa6583
src/views/officeProcessAutomation/ApproveManage/approve-list/useApproveList.js
@@ -1,57 +1,52 @@
import { Search } from "@element-plus/icons-vue";
import dayjs from "dayjs";
import {
  getApprovalTemplateDetail,
  listApprovalTemplate,
  TEMPLATE_TYPE_BUILTIN,
  TEMPLATE_TYPE_CUSTOM,
} from "@/api/officeProcessAutomation/approvalTemplate.js";
import {
  approveApprovalInstance,
  deleteApprovalInstance,
  listApprovalInstancePage,
  saveApprovalInstance,
  updateApprovalInstance,
} from "@/api/officeProcessAutomation/approvalInstance.js";
import useUserStore from "@/store/modules/user";
import { computed, reactive, ref, watch } from "vue";
import { Search } from "@element-plus/icons-vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { computed, reactive, ref } from "vue";
import {
  formatDisplayTime,
  mapEnabledFromApi,
  mapTemplateFromApi,
  unwrapTemplateDetail,
  unwrapTemplateList,
} from "../approve-template/approveTemplateConstants.js";
import { buildSubmitTemplateFromRow } from "../approve-template/formConfigUtils.js";
import {
  APPROVAL_TYPE_OPTIONS,
  SUBMIT_TEMPLATES,
  approvalModeLabel,
  approvalStatusLabel,
  approvalStatusTagType,
  approvalTypeLabel,
  buildApprovalInstanceListParams,
  buildApproveInstanceDto,
  buildEditFormFromInstanceRow,
  buildInstanceDto,
  clearLegacyApproveListStorage,
  createEmptySubmitForm,
  createInitialMockRows,
  loadStoredRows,
  saveStoredRows,
  buildDefaultFlowNodes,
  mapInstanceFromApi,
  mapSubmitTemplateCard,
  validateSubmitFlowNodes,
  unwrapInstancePage,
} from "./approveListConstants.js";
import { buildSubmitTemplateFromRow } from "../approve-template/formConfigUtils.js";
function advanceFlow(row, result, opinion) {
  const nodes = row.approvalFlowNodes || [];
  const idx = row.currentNodeIndex ?? 0;
  const node = nodes[idx];
  if (!node) return;
  node.nodeStatus = result === "approved" ? "finish" : "error";
  node.approveOpinion = opinion || (result === "approved" ? "同意" : "驳回");
  node.approveTime = dayjs().format("YYYY-MM-DD HH:mm:ss");
  row.approvalRecords = row.approvalRecords || [];
  row.approvalRecords.push({
    operatorName: node.approverName || "审批人",
    result,
    opinion: node.approveOpinion,
    time: node.approveTime,
  });
  if (result === "rejected") {
    row.approvalStatus = "rejected";
    row.rejectReason = opinion || node.approveOpinion;
    return;
  }
  const next = idx + 1;
  if (next < nodes.length) {
    row.currentNodeIndex = next;
    nodes[next].nodeStatus = "process";
    row.approvalStatus = "pending";
  } else {
    row.approvalStatus = "approved";
    row.rejectReason = "";
  }
}
export function useApproveList() {
  clearLegacyApproveListStorage();
  const userStore = useUserStore();
  const stored = loadStoredRows();
  const allRows = ref(stored?.length ? stored : createInitialMockRows());
  const tableData = ref([]);
  const submitTemplateCards = ref([]);
  const submitTemplatesLoading = ref(false);
  const searchForm = reactive({
    approvalType: "",
@@ -67,59 +62,37 @@
  const approveDialog = reactive({ visible: false, row: null });
  const approveOpinion = ref("");
  const approveSubmitting = ref(false);
  const submitDialog = reactive({ visible: false, step: 1 });
  const submitDialog = reactive({ visible: false, step: 1, mode: "add" });
  const submitEditRow = ref(null);
  const submitForm = reactive(createEmptySubmitForm(""));
  const submitFormRef = ref();
  const submitSaving = ref(false);
  const filteredList = computed(() => {
    let list = [...allRows.value];
    if (searchForm.approvalType) {
      list = list.filter((r) => r.approvalType === searchForm.approvalType);
  const isSubmitEdit = computed(() => submitDialog.mode === "edit");
  const submitDialogTitle = computed(() => {
    if (submitDialog.mode === "edit") {
      return `修改${activeTemplate.value?.label || submitForm.templateName || "审批"}`;
    }
    const kw = (searchForm.applicantKeyword || "").trim().toLowerCase();
    if (kw) {
      list = list.filter((r) => {
        const name = (r.applicantName || "").toLowerCase();
        const no = (r.applicantNo || "").toLowerCase();
        return name.includes(kw) || no.includes(kw);
      });
    }
    const range = searchForm.createTimeRange;
    if (range?.length === 2) {
      const [from, to] = range;
      list = list.filter((r) => {
        const t = (r.createTime || "").slice(0, 10);
        return t && t >= from && t <= to;
      });
    }
    return list.sort((a, b) => (String(a.createTime) < String(b.createTime) ? 1 : -1));
    if (submitDialog.step === 1) return "选择审批模板";
    return `提交${activeTemplate.value?.label || "审批"}`;
  });
  watch(
    filteredList,
    (list) => {
      page.total = list.length;
      const maxPage = Math.max(1, Math.ceil(list.length / page.size) || 1);
      if (page.current > maxPage) page.current = maxPage;
    },
    { immediate: true }
  );
  const activeTemplate = computed(() => submitForm.templateSnapshot || null);
  const tableData = computed(() => {
    const start = (page.current - 1) * page.size;
    return filteredList.value.slice(start, start + page.size);
  /** 填报项定义(新增/修改与 formConfig 一致) */
  const submitFormFields = computed(() => {
    const tplFields = activeTemplate.value?.fields;
    if (tplFields?.length) return tplFields;
    return submitForm.formFieldDefs || [];
  });
  const activeTemplate = computed(
    () => submitForm.templateSnapshot || SUBMIT_TEMPLATES[submitForm.templateKey] || null
  );
  const submitFormRules = computed(() => {
    const rules = {
      templateKey: [{ required: true, message: "请选择审批类型", trigger: "change" }],
    };
    (activeTemplate.value?.fields || []).forEach((f) => {
    submitFormFields.value.forEach((f) => {
      if (!f.required) return;
      if (f.type === "number") {
        rules[`formPayload.${f.key}`] = [{ required: true, message: `请填写${f.label}`, trigger: "blur" }];
@@ -143,14 +116,7 @@
      slot: "approveType",
    },
    {
      label: "审批方式",
      prop: "approvalMode",
      width: 90,
      dataType: "slot",
      slot: "approvalMethod",
    },
    {
      label: "是否未读",
      label: "待我审批",
      prop: "unread",
      width: 90,
      align: "center",
@@ -164,35 +130,82 @@
      formatData: (v) => approvalStatusLabel(v),
      formatType: (v) => approvalStatusTagType(v),
    },
    { label: "创建时间", prop: "createTime", width: 170 },
    {
      label: "创建时间",
      prop: "createTime",
      width: 170,
      formatData: (v) => formatDisplayTime(v),
    },
    {
      dataType: "action",
      label: "操作",
      align: "center",
      fixed: "right",
      width: 160,
      width: 240,
      operation: [
        { name: "详情", type: "text", clickFun: (row) => openDetail(row) },
        {
          name: "审批",
          name: "修改",
          type: "text",
          disabled: (row) => row.approvalStatus !== "pending",
          clickFun: (row) => openEditDialog(row),
        },
        {
          name: "审批",
          type: "text",
          disabled: (row) => row.approvalStatus !== "pending" || !row.isApprove,
          clickFun: (row) => openApprove(row),
        },
        {
          name: "删除",
          type: "danger",
          clickFun: (row) => removeInstance(row),
        },
      ],
    },
  ]);
  function persist() {
    saveStoredRows(allRows.value);
  async function fetchApprovalList() {
    tableLoading.value = true;
    try {
      const res = await listApprovalInstancePage(
        buildApprovalInstanceListParams({ page, searchForm })
      );
      const { records, total } = unwrapInstancePage(res);
      tableData.value = records.map(mapInstanceFromApi);
      page.total = total;
    } catch {
      tableData.value = [];
      page.total = 0;
      ElMessage.error("审批列表加载失败");
    } finally {
      tableLoading.value = false;
    }
  }
  async function loadSubmitTemplates() {
    submitTemplatesLoading.value = true;
    try {
      const [builtinRes, customRes] = await Promise.all([
        listApprovalTemplate(TEMPLATE_TYPE_BUILTIN),
        listApprovalTemplate(TEMPLATE_TYPE_CUSTOM),
      ]);
      const merged = [
        ...unwrapTemplateList(builtinRes),
        ...unwrapTemplateList(customRes),
      ].filter((row) => mapEnabledFromApi(row.enabled));
      submitTemplateCards.value = merged.map(mapSubmitTemplateCard);
    } catch {
      submitTemplateCards.value = [];
      ElMessage.error("加载审批模板失败");
    } finally {
      submitTemplatesLoading.value = false;
    }
  }
  function handleQuery() {
    tableLoading.value = true;
    page.current = 1;
    setTimeout(() => {
      tableLoading.value = false;
    }, 200);
    fetchApprovalList();
  }
  function resetSearch() {
@@ -205,50 +218,81 @@
  function pagination({ page: p, limit }) {
    page.current = p;
    page.size = limit;
  }
  function markRead(row) {
    if (!row.unread) return;
    const hit = allRows.value.find((r) => r.id === row.id);
    if (hit) {
      hit.unread = false;
      persist();
    }
    fetchApprovalList();
  }
  function openDetail(row) {
    markRead(row);
    detailRow.value = { ...row };
    detailDialog.visible = true;
  }
  function openApprove(row) {
    markRead(row);
    approveDialog.row = { ...row };
    approveOpinion.value = "";
    approveDialog.visible = true;
  }
  function openSubmitDialog() {
    Object.assign(submitForm, createEmptySubmitForm(""));
  function resetSubmitDialogState() {
    submitDialog.mode = "add";
    submitDialog.step = 1;
    submitEditRow.value = null;
    Object.assign(submitForm, createEmptySubmitForm(""));
  }
  function openSubmitDialog() {
    resetSubmitDialogState();
    submitDialog.visible = true;
    loadSubmitTemplates();
  }
  function openEditDialog(row) {
    if (row?.approvalStatus !== "pending") {
      ElMessage.warning("仅审核中的审批可修改");
      return;
    }
    if (!row?.id) {
      ElMessage.warning("无法修改:缺少审批实例 ID");
      return;
    }
    submitDialog.mode = "edit";
    submitDialog.step = 2;
    submitEditRow.value = { ...row };
    Object.assign(submitForm, buildEditFormFromInstanceRow(row));
    submitDialog.visible = true;
  }
  /** @param {string} key 内置模板 key 或自定义 id */
  function onTemplatePick(key, templateRow) {
    const base = templateRow
      ? createEmptySubmitForm(key, buildSubmitTemplateFromRow(templateRow))
      : createEmptySubmitForm(key);
    Object.assign(submitForm, {
      ...base,
      templateSnapshot: templateRow ? buildSubmitTemplateFromRow(templateRow) : null,
    });
    submitDialog.step = 2;
  async function onTemplatePick(card) {
    if (!card?.id) return;
    submitTemplatesLoading.value = true;
    try {
      const res = await getApprovalTemplateDetail(card.id);
      const mapped = mapTemplateFromApi(unwrapTemplateDetail(res));
      const tpl = {
        ...buildSubmitTemplateFromRow(mapped),
        templateId: mapped.id,
      };
      const base = createEmptySubmitForm(String(card.id), tpl, mapped.flowNodes);
      Object.assign(submitForm, {
        ...base,
        templateName: mapped.templateName || tpl.label || "",
        templateSnapshot: tpl,
        formFieldDefs: tpl.fields || [],
      });
      submitDialog.step = 2;
    } catch {
      ElMessage.error("加载模板详情失败");
    } finally {
      submitTemplatesLoading.value = false;
    }
  }
  function backToTemplatePick() {
    submitDialog.step = 1;
  }
  async function submitInstanceForm() {
    if (submitDialog.mode === "edit") return submitEditApproval();
    return submitNewApproval();
  }
  async function submitNewApproval() {
@@ -258,56 +302,143 @@
    } catch {
      return false;
    }
    const tpl = activeTemplate.value;
    if (!tpl) return false;
    const id = `user_${Date.now()}`;
    const summary =
      submitForm.formPayload.summary ||
      submitForm.formPayload.handoverTo ||
      `${tpl.label}申请`;
    const row = {
      id,
      bizId: `BIZ${dayjs().format("YYYYMMDDHHmmss")}`,
      applicantNo: userStore.name || String(userStore.id || "当前用户"),
      applicantName: userStore.nickName || userStore.name || "当前用户",
      approvalType: tpl.approvalType,
      approvalMode: submitForm.approvalMode,
      unread: false,
      approvalStatus: "pending",
      createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
      summary,
      formPayload: { ...submitForm.formPayload },
      approvalFlowNodes: (submitForm.approvalFlowNodes?.length
        ? submitForm.approvalFlowNodes
        : buildDefaultFlowNodes()
      ).map((n, i) => ({ ...n, nodeStatus: i === 0 ? "process" : n.nodeStatus || "wait" })),
      currentNodeIndex: 0,
      approvalRecords: [],
      rejectReason: "",
    };
    allRows.value.unshift(row);
    persist();
    submitDialog.visible = false;
    page.current = 1;
    return true;
    if (!activeTemplate.value) return false;
    const flowCheck = validateSubmitFlowNodes(submitForm.flowNodes);
    if (!flowCheck.ok) {
      ElMessage.warning(flowCheck.message);
      return false;
    }
    if (!submitForm.templateId) {
      ElMessage.warning("缺少模板 ID,无法提交");
      return false;
    }
    if (submitSaving.value) return false;
    submitSaving.value = true;
    try {
      await saveApprovalInstance(
        buildInstanceDto({
          submitForm,
          activeTemplate: activeTemplate.value,
          userStore,
          flowNodes: flowCheck.nodes,
        })
      );
      submitDialog.visible = false;
      page.current = 1;
      await fetchApprovalList();
      return true;
    } catch {
      return false;
    } finally {
      submitSaving.value = false;
    }
  }
  function submitApprove(result) {
  async function submitEditApproval() {
    if (!submitFormRef.value) return false;
    try {
      await submitFormRef.value.validate();
    } catch {
      return false;
    }
    if (!activeTemplate.value) return false;
    const flowCheck = validateSubmitFlowNodes(submitForm.flowNodes);
    if (!flowCheck.ok) {
      ElMessage.warning(flowCheck.message);
      return false;
    }
    if (!submitForm.instanceId) {
      ElMessage.warning("缺少审批实例 ID,无法保存");
      return false;
    }
    if (submitSaving.value) return false;
    submitSaving.value = true;
    try {
      await updateApprovalInstance(
        buildInstanceDto({
          submitForm,
          activeTemplate: activeTemplate.value,
          flowNodes: flowCheck.nodes,
          existingRow: submitEditRow.value,
        })
      );
      submitDialog.visible = false;
      await fetchApprovalList();
      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 {
      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 (approveDialog.visible && approveDialog.row?.id === row.id) {
        approveDialog.visible = false;
      }
      await fetchApprovalList();
    } catch {
      /* 错误由拦截器提示 */
    }
  }
  async function submitApprove(result) {
    const row = approveDialog.row;
    if (!row) return;
    const hit = allRows.value.find((r) => r.id === row.id);
    if (!hit || hit.approvalStatus !== "pending") return;
    if (!row?.id) return { ok: false };
    if (result === "rejected" && !(approveOpinion.value || "").trim()) {
      return { needOpinion: true };
    }
    advanceFlow(hit, result, (approveOpinion.value || "").trim());
    hit.unread = false;
    persist();
    approveDialog.visible = false;
    if (detailDialog.visible && detailRow.value?.id === hit.id) {
      detailRow.value = { ...hit };
    if (approveSubmitting.value) return { ok: false };
    approveSubmitting.value = true;
    try {
      await approveApprovalInstance(
        buildApproveInstanceDto(row, result, approveOpinion.value)
      );
      approveDialog.visible = false;
      await fetchApprovalList();
      if (detailDialog.visible && detailRow.value?.id === row.id) {
        const hit = tableData.value.find((r) => r.id === row.id);
        if (hit) detailRow.value = { ...hit };
        else detailDialog.visible = false;
      }
      return { ok: true, result };
    } catch {
      ElMessage.error("审批操作失败");
      return { ok: false };
    } finally {
      approveSubmitting.value = false;
    }
    return { ok: true };
  }
  function approvalActionLabel(result) {
@@ -319,9 +450,7 @@
  return {
    Search,
    APPROVAL_TYPE_OPTIONS,
    SUBMIT_TEMPLATES,
    approvalTypeLabel,
    approvalModeLabel,
    approvalStatusLabel,
    approvalStatusTagType,
    approvalActionLabel,
@@ -334,20 +463,31 @@
    detailRow,
    approveDialog,
    approveOpinion,
    approveSubmitting,
    submitDialog,
    isSubmitEdit,
    submitDialogTitle,
    submitForm,
    submitFormRef,
    submitSaving,
    activeTemplate,
    submitFormFields,
    submitFormRules,
    submitTemplateCards,
    submitTemplatesLoading,
    handleQuery,
    resetSearch,
    pagination,
    resetSubmitDialogState,
    openSubmitDialog,
    openEditDialog,
    onTemplatePick,
    backToTemplatePick,
    submitInstanceForm,
    submitNewApproval,
    submitApprove,
    openDetail,
    openApprove,
    fetchApprovalList,
  };
}