yyb
昨天 6689bfb1c2f0638e8493adfa058d57d86e473eac
审批列表得提交审批根据模板类型区分
已修改6个文件
204 ■■■■ 文件已修改
src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-list/index.vue 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-list/useApproveList.js 74 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-template/approveTemplateConstants.js 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-template/formConfigUtils.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-template/useApproveTemplate.js 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js
@@ -50,10 +50,16 @@
  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 */
@@ -275,6 +281,7 @@
  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,
src/views/officeProcessAutomation/ApproveManage/approve-list/index.vue
@@ -77,7 +77,34 @@
      @closed="resetSubmitDialogState"
    >
      <template v-if="submitDialog.step === 1 && !isSubmitEdit">
        <p class="template-hint">请选择已启用的审批模板,系统将按模板配置引导填报。</p>
        <p class="template-hint">请先选择模板类型,再选择该类型下已启用的审批模板。</p>
        <div v-loading="submitTemplatesLoading" class="template-grid">
          <div
            v-for="opt in submitBusinessTypeOptions"
            :key="`biz-type-${opt.value}`"
            class="template-card"
            :class="{ 'is-disabled': !countTemplatesByBusinessType(opt.value) }"
            @click="onBusinessTypePick(opt.value)"
          >
            <span class="template-card-type">{{ opt.label }}</span>
            <span class="template-card-desc">
              {{ countTemplatesByBusinessType(opt.value) }} 个可用模板
            </span>
          </div>
          <el-empty
            v-if="!submitTemplatesLoading && !submitBusinessTypeOptions.length"
            description="暂无模板类型"
            :image-size="80"
            class="template-empty"
          />
        </div>
      </template>
      <template v-else-if="submitDialog.step === 2 && !isSubmitEdit">
        <p class="template-hint">
          当前类型:{{ selectedBusinessTypeLabel || "—" }},请选择具体审批模板。
          <el-button type="primary" link class="ml8" @click="backToBusinessTypePick">更换类型</el-button>
        </p>
        <div v-loading="submitTemplatesLoading" class="template-grid">
          <div
            v-for="card in submitTemplateCards"
@@ -92,7 +119,7 @@
          </div>
          <el-empty
            v-if="!submitTemplatesLoading && !submitTemplateCards.length"
            description="暂无可用审批模板"
            description="该类型下暂无可用审批模板"
            :image-size="80"
            class="template-empty"
          />
@@ -132,12 +159,18 @@
      <template #footer>
        <el-button
          v-if="submitDialog.step === 2 || isSubmitEdit"
          v-if="submitDialog.step === 3 || isSubmitEdit"
          type="primary"
          :loading="submitSaving"
          @click="onSubmitInstance"
        >
          {{ isSubmitEdit ? "保 存" : "提 交" }}
        </el-button>
        <el-button
          v-if="submitDialog.step === 2 && !isSubmitEdit"
          @click="backToBusinessTypePick"
        >
          上一步
        </el-button>
        <el-button @click="submitDialog.visible = false">
          {{ submitDialog.step === 1 && !isSubmitEdit ? "取 消" : "关 闭" }}
@@ -274,8 +307,13 @@
const {
  Search,
  APPROVAL_TYPE_OPTIONS,
  submitBusinessTypeOptions,
  submitTemplateCards,
  selectedBusinessTypeLabel,
  countTemplatesByBusinessType,
  submitTemplatesLoading,
  onBusinessTypePick,
  backToBusinessTypePick,
  approvalTypeLabel,
  approvalActionLabel,
  searchForm,
@@ -434,6 +472,17 @@
  border-color: var(--el-color-primary);
  box-shadow: var(--shadow-sm, 0 2px 8px rgba(0, 0, 0, 0.06));
}
.template-card.is-disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.template-card.is-disabled:hover {
  border-color: var(--el-border-color-lighter);
  box-shadow: none;
}
.ml8 {
  margin-left: 8px;
}
.template-card-type {
  display: inline-block;
  padding: 2px 8px;
src/views/officeProcessAutomation/ApproveManage/approve-list/useApproveList.js
@@ -1,7 +1,6 @@
import {
  getApprovalTemplateDetail,
  listApprovalTemplate,
  TEMPLATE_TYPE_BUILTIN,
  TEMPLATE_TYPE_CUSTOM,
} from "@/api/officeProcessAutomation/approvalTemplate.js";
import {
@@ -16,6 +15,7 @@
import { ElMessage, ElMessageBox } from "element-plus";
import { computed, reactive, ref } from "vue";
import {
  fetchBusinessTypeOptions,
  formatDisplayTime,
  mapEnabledFromApi,
  mapTemplateFromApi,
@@ -36,6 +36,7 @@
  createEmptySubmitForm,
  mapInstanceFromApi,
  mapSubmitTemplateCard,
  matchBusinessTypeValue,
  validateSubmitFlowNodes,
  unwrapInstancePage,
} from "./approveListConstants.js";
@@ -45,8 +46,17 @@
  const userStore = useUserStore();
  const tableData = ref([]);
  const submitTemplateCards = ref([]);
  const submitBusinessTypeOptions = ref([]);
  const allSubmitTemplates = ref([]);
  const selectedBusinessType = ref("");
  const submitTemplatesLoading = ref(false);
  const submitTemplateCards = computed(() => {
    if (selectedBusinessType.value == null || selectedBusinessType.value === "") return [];
    return allSubmitTemplates.value.filter((card) =>
      matchBusinessTypeValue(card.businessType, selectedBusinessType.value)
    );
  });
  const searchForm = reactive({
    approvalType: "",
@@ -75,9 +85,22 @@
    if (submitDialog.mode === "edit") {
      return `修改${activeTemplate.value?.label || submitForm.templateName || "审批"}`;
    }
    if (submitDialog.step === 1) return "选择审批模板";
    if (submitDialog.step === 1) return "选择模板类型";
    if (submitDialog.step === 2) return `选择审批模板${businessTypeLabel(selectedBusinessType.value) ? `(${businessTypeLabel(selectedBusinessType.value)})` : ""}`;
    return `提交${activeTemplate.value?.label || "审批"}`;
  });
  const selectedBusinessTypeLabel = computed(() => businessTypeLabel(selectedBusinessType.value));
  function businessTypeLabel(type) {
    if (type == null || type === "") return "";
    const hit = submitBusinessTypeOptions.value.find((x) => matchBusinessTypeValue(x.value, type));
    return hit?.label || "";
  }
  function countTemplatesByBusinessType(type) {
    return allSubmitTemplates.value.filter((card) => matchBusinessTypeValue(card.businessType, type)).length;
  }
  const activeTemplate = computed(() => submitForm.templateSnapshot || null);
@@ -187,17 +210,17 @@
  async function loadSubmitTemplates() {
    submitTemplatesLoading.value = true;
    try {
      const [builtinRes, customRes] = await Promise.all([
        listApprovalTemplate(TEMPLATE_TYPE_BUILTIN),
      const [typeOptions, customRes] = await Promise.all([
        fetchBusinessTypeOptions(),
        listApprovalTemplate(TEMPLATE_TYPE_CUSTOM),
      ]);
      const merged = [
        ...unwrapTemplateList(builtinRes),
        ...unwrapTemplateList(customRes),
      ].filter((row) => mapEnabledFromApi(row.enabled));
      submitTemplateCards.value = merged.map(mapSubmitTemplateCard);
      submitBusinessTypeOptions.value = typeOptions;
      allSubmitTemplates.value = unwrapTemplateList(customRes)
        .filter((row) => mapEnabledFromApi(row.enabled))
        .map(mapSubmitTemplateCard);
    } catch {
      submitTemplateCards.value = [];
      submitBusinessTypeOptions.value = [];
      allSubmitTemplates.value = [];
      ElMessage.error("加载审批模板失败");
    } finally {
      submitTemplatesLoading.value = false;
@@ -236,6 +259,7 @@
  function resetSubmitDialogState() {
    submitDialog.mode = "add";
    submitDialog.step = 1;
    selectedBusinessType.value = "";
    submitEditRow.value = null;
    Object.assign(submitForm, createEmptySubmitForm(""));
  }
@@ -256,7 +280,7 @@
      return;
    }
    submitDialog.mode = "edit";
    submitDialog.step = 2;
    submitDialog.step = 3;
    submitEditRow.value = { ...row };
    Object.assign(submitForm, buildEditFormFromInstanceRow(row));
    submitDialog.visible = true;
@@ -276,10 +300,11 @@
      Object.assign(submitForm, {
        ...base,
        templateName: mapped.templateName || tpl.label || "",
        businessType: mapped.businessType ?? card.businessType ?? selectedBusinessType.value,
        templateSnapshot: tpl,
        formFieldDefs: tpl.fields || [],
      });
      submitDialog.step = 2;
      submitDialog.step = 3;
    } catch {
      ElMessage.error("加载模板详情失败");
    } finally {
@@ -287,8 +312,22 @@
    }
  }
  function backToTemplatePick() {
  function onBusinessTypePick(type) {
    if (!countTemplatesByBusinessType(type)) {
      ElMessage.warning("该类型下暂无可用审批模板");
      return;
    }
    selectedBusinessType.value = type;
    submitDialog.step = 2;
  }
  function backToBusinessTypePick() {
    selectedBusinessType.value = "";
    submitDialog.step = 1;
  }
  function backToTemplatePick() {
    submitDialog.step = 2;
  }
  async function submitInstanceForm() {
@@ -474,7 +513,12 @@
    activeTemplate,
    submitFormFields,
    submitFormRules,
    submitBusinessTypeOptions,
    submitTemplateCards,
    selectedBusinessType,
    selectedBusinessTypeLabel,
    businessTypeLabel,
    countTemplatesByBusinessType,
    submitTemplatesLoading,
    handleQuery,
    resetSearch,
@@ -482,7 +526,9 @@
    resetSubmitDialogState,
    openSubmitDialog,
    openEditDialog,
    onBusinessTypePick,
    onTemplatePick,
    backToBusinessTypePick,
    backToTemplatePick,
    submitInstanceForm,
    submitNewApproval,
src/views/officeProcessAutomation/ApproveManage/approve-template/approveTemplateConstants.js
@@ -1,4 +1,5 @@
import dayjs from "dayjs";
import { getTypeEnums } from "@/api/basicData/enum.js";
import { TEMPLATE_TYPE_CUSTOM } from "@/api/officeProcessAutomation/approvalTemplate.js";
import { APPROVAL_TYPE_OPTIONS } from "../approve-list/approveListConstants.js";
import {
@@ -8,6 +9,41 @@
  validateFormConfigData,
} from "./formConfigUtils.js";
export function unwrapEnumList(data) {
  if (Array.isArray(data)) return data;
  if (!data || typeof data !== "object") return [];
  if (Array.isArray(data.TypeEnums)) return data.TypeEnums;
  if (Array.isArray(data.typeEnums)) return data.typeEnums;
  const nested = Object.values(data).find((v) => Array.isArray(v));
  return nested || [];
}
export function normalizeBusinessTypeOptions(data) {
  return unwrapEnumList(data)
    .map((item) => {
      const rawValue = item?.value ?? item?.code ?? item?.businessType ?? item?.dictValue ?? item?.key;
      if (rawValue == null || rawValue === "") return null;
      const num = Number(rawValue);
      const value =
        typeof rawValue === "number" || (Number.isFinite(num) && String(rawValue).trim() !== "")
          ? num
          : rawValue;
      const label =
        item?.label ?? item?.name ?? item?.desc ?? item?.dictLabel ?? item?.text ?? String(value);
      return { label, value };
    })
    .filter(Boolean);
}
export async function fetchBusinessTypeOptions() {
  try {
    const res = await getTypeEnums();
    return normalizeBusinessTypeOptions(res?.data);
  } catch {
    return [];
  }
}
/** 节点内审批方式:会签 / 或签 */
export const NODE_SIGN_MODE_OPTIONS = [
  { value: "countersign", label: "会签", desc: "本节点所有审批人均需通过" },
src/views/officeProcessAutomation/ApproveManage/approve-template/formConfigUtils.js
@@ -264,6 +264,7 @@
  }));
  return {
    label: row?.templateName || "审批",
    businessType: row?.businessType ?? cfg.approvalType ?? "",
    approvalType: cfg.approvalType || "",
    summaryPlaceholder: cfg.summaryPlaceholder || "",
    approvalMode: cfg.approvalMode || "parallel",
src/views/officeProcessAutomation/ApproveManage/approve-template/useApproveTemplate.js
@@ -6,13 +6,13 @@
  TEMPLATE_TYPE_CUSTOM,
  updateApprovalTemplate,
} from "@/api/officeProcessAutomation/approvalTemplate.js";
import { getTypeEnums } from "@/api/basicData/enum.js";
import { Search } from "@element-plus/icons-vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { reactive, ref } from "vue";
import {
  buildApprovalTemplateListParams,
  createEmptyTemplateForm,
  fetchBusinessTypeOptions,
  flowNodesSummary,
  mapTemplateFromApi,
  mapTemplateToApi,
@@ -29,32 +29,6 @@
  { value: 0, label: "系统内置" },
  { value: 1, label: "自定义" },
];
function unwrapEnumList(data) {
  if (Array.isArray(data)) return data;
  if (!data || typeof data !== "object") return [];
  if (Array.isArray(data.TypeEnums)) return data.TypeEnums;
  if (Array.isArray(data.typeEnums)) return data.typeEnums;
  const nested = Object.values(data).find((v) => Array.isArray(v));
  return nested || [];
}
function normalizeTypeEnumOptions(data) {
  return unwrapEnumList(data)
    .map((item) => {
      const rawValue = item?.value ?? item?.code ?? item?.businessType ?? item?.dictValue ?? item?.key;
      if (rawValue == null || rawValue === "") return null;
      const num = Number(rawValue);
      const value =
        typeof rawValue === "number" || (Number.isFinite(num) && String(rawValue).trim() !== "")
          ? num
          : rawValue;
      const label =
        item?.label ?? item?.name ?? item?.desc ?? item?.dictLabel ?? item?.text ?? String(value);
      return { label, value };
    })
    .filter(Boolean);
}
function matchTemplateTypeValue(options, type) {
  if (type == null || type === "") return false;
@@ -99,8 +73,7 @@
  async function loadTemplateTypeOptions() {
    try {
      const res = await getTypeEnums();
      const list = normalizeTypeEnumOptions(res?.data);
      const list = await fetchBusinessTypeOptions();
      templateTypeOptions.value = list.length ? list : [...FALLBACK_TEMPLATE_TYPE_OPTIONS];
    } catch {
      templateTypeOptions.value = [...FALLBACK_TEMPLATE_TYPE_OPTIONS];