<!--
|
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="选择审批模板"
|
@back="goBack" />
|
|
<view v-if="typeOptions.length"
|
class="step-section">
|
<view class="tabs-wrap">
|
<up-tabs :list="tabList"
|
:current="activeTab"
|
line-color="#2979ff"
|
@click="onTabClick" />
|
</view>
|
</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="!typeOptions.length"
|
class="empty-wrap">
|
<up-empty mode="list"
|
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 { listApprovalTemplateByType } from "@/api/oa/approvalTemplate.js";
|
import { OA_NAV } from "@/config/oaPaths.js";
|
import {
|
buildTypeLabelMap,
|
CUSTOM_TEMPLATE_LIST_TYPE,
|
fetchApprovalTemplateTypes,
|
filterTemplatesByBusinessType,
|
getBusinessTypeLabel,
|
getDefaultTypeTabIndex,
|
} from "../../_utils/approvalTemplateType.js";
|
|
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 currentTypeOption = computed(() => typeOptions.value[activeTab.value]);
|
|
const currentSource = computed(() => {
|
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(() => {
|
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 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([
|
fetchApprovalTemplateTypes(),
|
loadCustomTemplates(),
|
]);
|
typeOptions.value = opts;
|
typeLabelMap.value = buildTypeLabelMap(opts);
|
activeTab.value = getDefaultTypeTabIndex(opts);
|
} catch {
|
typeOptions.value = [];
|
typeLabelMap.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;
|
}
|
uni.navigateTo({
|
url: `${OA_NAV.approveListApply}?templateId=${item.id}`,
|
});
|
};
|
|
onLoad(() => {
|
initPage();
|
});
|
</script>
|
|
<style scoped lang="scss">
|
@import "@/styles/sales-common.scss";
|
|
.template-select-page {
|
display: flex;
|
flex-direction: column;
|
min-height: 100vh;
|
}
|
|
.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>
|