src/pages/oa/ApproveManage/approve-list/template-select.vue
@@ -5,10 +5,10 @@
-->
<template>
  <view class="template-select-page sales-account">
    <PageHeader title="选择审批模板"
    <PageHeader :title="pageHeaderTitle"
                @back="goBack" />
    <view v-if="typeOptions.length"
    <view v-if="typeOptions.length && !moduleKey"
          class="step-section">
      <view class="tabs-wrap">
        <up-tabs :list="tabList"
@@ -16,6 +16,11 @@
                 line-color="#2979ff"
                 @click="onTabClick" />
      </view>
    </view>
    <view v-if="useAllTemplatesFallback && allTemplates.length"
          class="fallback-hint">
      <text>当前类型下无匹配模板,已显示全部 {{ allTemplates.length }} 个可用模板</text>
    </view>
    <view class="search-section">
@@ -41,11 +46,6 @@
            class="loading-wrap">
        <up-loading-icon mode="circle" />
        <text class="loading-text">加载中...</text>
      </view>
      <view v-else-if="!typeOptions.length"
            class="empty-wrap">
        <up-empty mode="list"
                  text="未获取到审批类型" />
      </view>
      <view v-else-if="displayList.length"
            class="ledger-list">
@@ -95,17 +95,25 @@
  import { computed, ref } from "vue";
  import { onLoad } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import { listApprovalTemplateByType } from "@/api/oa/approvalTemplate.js";
  import { OA_NAV } from "@/config/oaPaths.js";
  import {
    buildTypeLabelMap,
    CUSTOM_TEMPLATE_LIST_TYPE,
    fetchApprovalTemplateTypes,
    buildTypeOptionsFromTemplates,
    FALLBACK_BUSINESS_TYPE_OPTIONS,
    fetchEnabledApprovalTemplates,
    filterTemplatesByBusinessType,
    filterTemplatesByBusinessTypes,
    getBusinessTypeLabel,
    getDefaultTypeTabIndex,
    pickTabIndexWithTemplates,
  } from "../../_utils/approvalTemplateType.js";
  import {
    getApprovalModuleConfig,
    getModuleMatchingBusinessTypes,
    resolveModuleBusinessType,
  } from "../../_utils/approvalModuleRegistry.js";
  const moduleKey = ref("");
  const typeOptions = ref([]);
  const typeLabelMap = ref({});
  /** 全部自定义已启用模板(list/1 一次拉取) */
@@ -118,9 +126,45 @@
    typeOptions.value.map(opt => ({ name: opt.name }))
  );
  const moduleConfig = computed(() =>
    moduleKey.value ? getApprovalModuleConfig(moduleKey.value) : null
  );
  const pageHeaderTitle = computed(() => {
    if (moduleConfig.value?.label) {
      return `选择${moduleConfig.value.label}模板`;
    }
    return "选择审批模板";
  });
  const moduleBusinessTypes = computed(() => {
    if (!moduleKey.value) return [];
    return getModuleMatchingBusinessTypes(moduleKey.value, typeOptions.value);
  });
  const currentTypeOption = computed(() => typeOptions.value[activeTab.value]);
  /** 无 moduleKey 且当前 Tab 筛不到时,展示全部模板避免「有数据却空白」 */
  const useAllTemplatesFallback = computed(() => {
    if (moduleKey.value) return false;
    if (!allTemplates.value.length) return false;
    const businessType = currentTypeOption.value?.value;
    if (businessType == null || businessType === "") return true;
    return filterTemplatesByBusinessType(allTemplates.value, businessType).length === 0;
  });
  const currentSource = computed(() => {
    if (moduleKey.value && moduleBusinessTypes.value.length) {
      const filtered = filterTemplatesByBusinessTypes(
        allTemplates.value,
        moduleBusinessTypes.value
      );
      if (filtered.length) return filtered;
      return allTemplates.value;
    }
    if (useAllTemplatesFallback.value) {
      return allTemplates.value;
    }
    const businessType = currentTypeOption.value?.value;
    return filterTemplatesByBusinessType(allTemplates.value, businessType);
  });
@@ -134,8 +178,17 @@
  });
  const emptyText = computed(() => {
    if (allTemplates.value.length === 0) {
      return "暂无已启用的审批模板,请先在「审批模板」中创建并启用";
    }
    if (moduleConfig.value?.label) {
      return `暂无${moduleConfig.value.label}可用模板`;
    }
    if (useAllTemplatesFallback.value) {
      return "当前类型下无匹配模板";
    }
    const typeName = currentTypeOption.value?.name || "该审批类型";
    return `暂无${typeName}下的模板`;
    return `暂无${typeName}下的模板(可切换上方类型)`;
  });
  const businessTypeText = type =>
@@ -160,41 +213,46 @@
    return count != null ? `${count} 个` : "-";
  };
  const normalizeList = data => {
    const list = Array.isArray(data)
      ? data
      : Array.isArray(data?.records)
        ? data.records
        : [];
    return list.filter(item => String(item?.enabled ?? "1") === "1");
  };
  const loadCustomTemplates = () =>
    listApprovalTemplateByType(CUSTOM_TEMPLATE_LIST_TYPE)
      .then(res => {
        allTemplates.value = normalizeList(res?.data);
      })
      .catch(() => {
        allTemplates.value = [];
        uni.showToast({ title: "加载模板列表失败", icon: "none" });
      });
  const initPage = async () => {
    loading.value = true;
    keyword.value = "";
    allTemplates.value = [];
    try {
      const [opts] = await Promise.all([
      const [opts, templates] = await Promise.all([
        fetchApprovalTemplateTypes(),
        loadCustomTemplates(),
        fetchEnabledApprovalTemplates(),
      ]);
      typeOptions.value = opts;
      typeLabelMap.value = buildTypeLabelMap(opts);
      activeTab.value = getDefaultTypeTabIndex(opts);
      let resolvedOpts = opts?.length ? opts : buildTypeOptionsFromTemplates(templates);
      if (!resolvedOpts.length) {
        resolvedOpts = [...FALLBACK_BUSINESS_TYPE_OPTIONS];
      }
      typeOptions.value = resolvedOpts;
      typeLabelMap.value = buildTypeLabelMap(resolvedOpts);
      allTemplates.value = templates;
      if (!templates.length) {
        uni.showToast({
          title: "未获取到已启用模板",
          icon: "none",
          duration: 2500,
        });
      }
      if (moduleKey.value) {
        const resolved = resolveModuleBusinessType(moduleKey.value, opts);
        const idx = opts.findIndex(
          opt => String(opt.value) === String(resolved)
        );
        activeTab.value =
          idx >= 0 ? idx : pickTabIndexWithTemplates(resolvedOpts, templates);
      } else {
        activeTab.value = pickTabIndexWithTemplates(resolvedOpts, templates);
      }
    } catch {
      typeOptions.value = [];
      typeLabelMap.value = {};
      uni.showToast({ title: "获取审批类型失败", icon: "none" });
      allTemplates.value = [];
      uni.showToast({ title: "加载模板失败", icon: "none" });
    } finally {
      loading.value = false;
    }
@@ -215,12 +273,14 @@
      uni.showToast({ title: "该模板已停用", icon: "none" });
      return;
    }
    const base = `${OA_NAV.approveListApply}?templateId=${item.id}`;
    uni.navigateTo({
      url: `${OA_NAV.approveListApply}?templateId=${item.id}`,
      url: moduleKey.value ? `${base}&moduleKey=${moduleKey.value}` : base,
    });
  };
  onLoad(() => {
  onLoad(options => {
    moduleKey.value = options?.moduleKey || "";
    initPage();
  });
</script>
@@ -234,6 +294,16 @@
    min-height: 100vh;
  }
  .fallback-hint {
    margin: 8px 12px 0;
    padding: 8px 12px;
    font-size: 12px;
    color: #e6a23c;
    background: #fdf6ec;
    border-radius: 6px;
    line-height: 1.5;
  }
  .step-section {
    background: #fff;
    border-bottom: 1px solid #f0f0f0;