<!--
|
OA / 审批管理 / 选择审批模板
|
路由:/pages/oa/ApproveManage/approve-list/template-select
|
Tab:TypeEnums → businessType;列表:GET /approvalTemplate/list/1(自定义已启用)后按 businessType 筛选
|
-->
|
<template>
|
<view class="template-select-page sales-account">
|
<PageHeader :title="pageHeaderTitle"
|
@back="goBack" />
|
|
<view v-if="typeOptions.length && !moduleKey"
|
class="step-section">
|
<view class="tabs-wrap">
|
<up-tabs :list="tabList"
|
:current="activeTab"
|
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">
|
<view class="search-bar">
|
<view class="search-input">
|
<up-input v-model="keyword"
|
class="search-text"
|
placeholder="请输入模板名称"
|
clearable />
|
</view>
|
<view class="filter-button">
|
<up-icon name="search"
|
size="24"
|
color="#999" />
|
</view>
|
</view>
|
</view>
|
|
<scroll-view class="list-scroll"
|
scroll-y
|
:show-scrollbar="false">
|
<view v-if="loading"
|
class="loading-wrap">
|
<up-loading-icon mode="circle" />
|
<text class="loading-text">加载中...</text>
|
</view>
|
<view v-else-if="displayList.length"
|
class="ledger-list">
|
<view v-for="item in displayList"
|
:key="item.id"
|
class="ledger-item ledger-item--clickable"
|
@click="selectTemplate(item)">
|
<view class="item-header">
|
<view class="item-left">
|
<view class="document-icon">
|
<up-icon name="file-text"
|
size="16"
|
color="#ffffff" />
|
</view>
|
<text class="item-id">{{ item.templateName || "-" }}</text>
|
</view>
|
<u-tag :type="enabledTagType(item.enabled)"
|
:text="enabledText(item.enabled)" />
|
</view>
|
<up-divider />
|
<view class="item-details">
|
<view class="detail-row">
|
<text class="detail-label">审批类型</text>
|
<text class="detail-value">{{ businessTypeText(item.businessType) }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">审批节点</text>
|
<text class="detail-value">{{ nodeCount(item) }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">模板说明</text>
|
<text class="detail-value">{{ item.description || "-" }}</text>
|
</view>
|
</view>
|
</view>
|
</view>
|
<view v-else
|
class="empty-wrap">
|
<up-empty mode="list"
|
:text="emptyText" />
|
</view>
|
</scroll-view>
|
</view>
|
</template>
|
|
<script setup>
|
import { computed, ref } from "vue";
|
import { onLoad } from "@dcloudio/uni-app";
|
import PageHeader from "@/components/PageHeader.vue";
|
import { OA_NAV } from "@/config/oaPaths.js";
|
import {
|
buildTypeLabelMap,
|
fetchApprovalTemplateTypes,
|
buildTypeOptionsFromTemplates,
|
FALLBACK_BUSINESS_TYPE_OPTIONS,
|
fetchEnabledApprovalTemplates,
|
filterTemplatesByBusinessType,
|
filterTemplatesByBusinessTypes,
|
getBusinessTypeLabel,
|
pickTabIndexWithTemplates,
|
} from "../../_utils/approvalTemplateType.js";
|
import {
|
getApprovalModuleConfig,
|
getModuleMatchingBusinessTypes,
|
resolveModuleBusinessType,
|
} from "../../_utils/approvalModuleRegistry.js";
|
|
const moduleKey = ref("");
|
const typeOptions = ref([]);
|
const typeLabelMap = ref({});
|
/** 全部自定义已启用模板(list/1 一次拉取) */
|
const allTemplates = ref([]);
|
const activeTab = ref(0);
|
const keyword = ref("");
|
const loading = ref(false);
|
|
const tabList = computed(() =>
|
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);
|
});
|
|
const displayList = computed(() => {
|
const kw = keyword.value?.trim().toLowerCase();
|
if (!kw) return currentSource.value;
|
return currentSource.value.filter(item =>
|
(item.templateName || "").toLowerCase().includes(kw)
|
);
|
});
|
|
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}下的模板(可切换上方类型)`;
|
});
|
|
const businessTypeText = type =>
|
getBusinessTypeLabel(type, typeLabelMap.value);
|
|
const enabledText = enabled => {
|
const val = String(enabled ?? "");
|
if (val === "1") return "启用";
|
if (val === "0") return "停用";
|
return "-";
|
};
|
|
const enabledTagType = enabled => {
|
const val = String(enabled ?? "");
|
if (val === "1") return "success";
|
if (val === "0") return "info";
|
return "info";
|
};
|
|
const nodeCount = item => {
|
const count = item?.nodes?.length;
|
return count != null ? `${count} 个` : "-";
|
};
|
|
const initPage = async () => {
|
loading.value = true;
|
keyword.value = "";
|
allTemplates.value = [];
|
try {
|
const [opts, templates] = await Promise.all([
|
fetchApprovalTemplateTypes(),
|
fetchEnabledApprovalTemplates(),
|
]);
|
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 = {};
|
allTemplates.value = [];
|
uni.showToast({ title: "加载模板失败", icon: "none" });
|
} finally {
|
loading.value = false;
|
}
|
};
|
|
const onTabClick = item => {
|
activeTab.value = item?.index ?? 0;
|
keyword.value = "";
|
};
|
|
const goBack = () => {
|
uni.navigateBack();
|
};
|
|
const selectTemplate = item => {
|
if (!item?.id) return;
|
if (String(item.enabled) === "0") {
|
uni.showToast({ title: "该模板已停用", icon: "none" });
|
return;
|
}
|
const base = `${OA_NAV.approveListApply}?templateId=${item.id}`;
|
uni.navigateTo({
|
url: moduleKey.value ? `${base}&moduleKey=${moduleKey.value}` : base,
|
});
|
};
|
|
onLoad(options => {
|
moduleKey.value = options?.moduleKey || "";
|
initPage();
|
});
|
</script>
|
|
<style scoped lang="scss">
|
@import "@/styles/sales-common.scss";
|
|
.template-select-page {
|
display: flex;
|
flex-direction: column;
|
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;
|
}
|
|
.step-label {
|
display: block;
|
padding: 10px 16px 0;
|
font-size: 13px;
|
font-weight: 600;
|
color: #303133;
|
}
|
|
.step-hint {
|
display: flex;
|
align-items: baseline;
|
justify-content: space-between;
|
padding: 10px 16px 4px;
|
gap: 8px;
|
}
|
|
.step-desc {
|
flex-shrink: 0;
|
font-size: 12px;
|
color: #909399;
|
}
|
|
.tabs-wrap {
|
padding: 0 12px 4px;
|
}
|
|
.list-scroll {
|
flex: 1;
|
height: 0;
|
padding-bottom: env(safe-area-inset-bottom);
|
}
|
|
.loading-wrap {
|
padding: 48px 0;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
gap: 12px;
|
}
|
|
.loading-text {
|
font-size: 14px;
|
color: #909399;
|
}
|
|
.empty-wrap {
|
padding: 48px 20px;
|
}
|
|
.ledger-item--clickable:active {
|
opacity: 0.92;
|
}
|
|
.card-footer {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
margin-top: 10px;
|
padding-top: 10px;
|
border-top: 1px dashed #e8ecf0;
|
}
|
|
.card-footer-tip {
|
font-size: 13px;
|
color: #2979ff;
|
}
|
</style>
|