<!--
|
OA / 审批管理 / 审批模板详情
|
路由:/pages/oa/ApproveManage/approve-template/detail
|
-->
|
<template>
|
<view class="template-detail-page">
|
<PageHeader title="模板详情"
|
@back="goBack" />
|
|
<scroll-view class="detail-scroll"
|
scroll-y
|
:show-scrollbar="false">
|
<view v-if="loading"
|
class="loading-wrap">
|
<up-loading-icon mode="circle" />
|
<text class="loading-text">加载中...</text>
|
</view>
|
<template v-else-if="detail">
|
<view class="section">
|
<view class="section-title">基本信息</view>
|
<view class="info-list">
|
<view class="info-item">
|
<text class="info-label">模板名称</text>
|
<text class="info-value">{{ detail.templateName || "-" }}</text>
|
</view>
|
<view class="info-item">
|
<text class="info-label">审批类型</text>
|
<text class="info-value">{{ businessTypeText(detail.businessType) }}</text>
|
</view>
|
<view class="info-item">
|
<text class="info-label">启用状态</text>
|
<text class="info-value"
|
:class="enabledClass(detail.enabled)">
|
{{ enabledText(detail.enabled) }}
|
</text>
|
</view>
|
<view class="info-item">
|
<text class="info-label">模板说明</text>
|
<text class="info-value">{{ detail.description || "-" }}</text>
|
</view>
|
<view class="info-item">
|
<text class="info-label">创建人</text>
|
<text class="info-value">{{ detail.createdUserName || "-" }}</text>
|
</view>
|
<view class="info-item">
|
<text class="info-label">创建时间</text>
|
<text class="info-value">{{ detail.createTime || "-" }}</text>
|
</view>
|
</view>
|
</view>
|
|
<view class="section">
|
<view class="section-title">填报配置</view>
|
<view class="info-list">
|
<view class="info-item">
|
<text class="info-label">填报提示</text>
|
<text class="info-value">{{ formConfigData.prompt || "-" }}</text>
|
</view>
|
</view>
|
<view v-if="formConfigData.fields.length"
|
class="field-block">
|
<view v-for="(field, index) in formConfigData.fields"
|
:key="field.key || index"
|
class="field-card">
|
<view class="field-card-head">
|
<text class="field-card-name">{{ field.label }}</text>
|
<text class="field-tag">{{ fieldTypeLabel(field.type) }}</text>
|
<text v-if="field.required"
|
class="field-tag field-tag--req">必填</text>
|
</view>
|
<text v-if="field.defaultValue"
|
class="field-card-default">
|
默认值:{{ field.defaultValue }}
|
</text>
|
</view>
|
</view>
|
<view v-else
|
class="empty-hint">暂无填报项</view>
|
</view>
|
|
<view class="section">
|
<view class="section-title">审批流程</view>
|
<view v-if="detail.nodes?.length"
|
class="flow-list">
|
<view v-for="(node, index) in detail.nodes"
|
:key="node.id || index"
|
class="flow-card">
|
<view class="flow-card-head">
|
<text class="flow-level">第{{ levelLabel(node.levelNo || index + 1) }}级</text>
|
<text class="flow-type">{{ approveTypeText(node.approveType) }}</text>
|
</view>
|
<view class="approver-tags">
|
<text v-for="(approver, aIdx) in node.approvers || []"
|
:key="approver.id || aIdx"
|
class="approver-tag">
|
{{ approver.approverName || "-" }}
|
</text>
|
<text v-if="!(node.approvers || []).length"
|
class="empty-hint inline">暂无审批人</text>
|
</view>
|
</view>
|
</view>
|
<view v-else
|
class="empty-hint">暂无审批节点</view>
|
</view>
|
</template>
|
<view v-else
|
class="empty-wrap">
|
<up-empty mode="data"
|
text="未获取到模板详情" />
|
</view>
|
</scroll-view>
|
|
<FooterButtons v-if="!loading && detail"
|
cancel-text="返回"
|
confirm-text="编辑"
|
@cancel="goBack"
|
@confirm="goEdit" />
|
</view>
|
</template>
|
|
<script setup>
|
import { computed, ref } from "vue";
|
import { onLoad } from "@dcloudio/uni-app";
|
import PageHeader from "@/components/PageHeader.vue";
|
import FooterButtons from "@/components/FooterButtons.vue";
|
import { getApprovalTemplateDetail } from "@/api/oa/approvalTemplate.js";
|
import { getFieldEditorTypeLabel } from "../../_utils/approvalFormField.js";
|
import {
|
buildTypeLabelMap,
|
fetchApprovalTemplateTypes,
|
getTemplateTypeLabel,
|
} from "../../_utils/approvalTemplateType.js";
|
|
const EDIT_STORAGE_KEY = "oa_approve_template_edit_row";
|
const LEVEL_TEXT = ["", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十"];
|
|
const templateId = ref("");
|
const detail = ref(null);
|
const loading = ref(false);
|
const typeLabelMap = ref({});
|
|
const formConfigData = computed(() => {
|
const raw = detail.value?.formConfig;
|
if (!raw) return { prompt: "", fields: [] };
|
try {
|
const obj = typeof raw === "string" ? JSON.parse(raw) : raw;
|
return {
|
prompt: obj?.prompt || "",
|
fields: Array.isArray(obj?.fields) ? obj.fields : [],
|
};
|
} catch {
|
return { prompt: "", fields: [] };
|
}
|
});
|
|
const levelLabel = n => LEVEL_TEXT[Number(n)] || String(n);
|
|
const businessTypeText = type =>
|
getTemplateTypeLabel(type, typeLabelMap.value);
|
|
const enabledText = enabled => {
|
const val = String(enabled ?? "");
|
if (val === "1") return "启用";
|
if (val === "0") return "停用";
|
return "-";
|
};
|
|
const enabledClass = enabled => {
|
const val = String(enabled ?? "");
|
if (val === "1") return "status-on";
|
if (val === "0") return "status-off";
|
return "";
|
};
|
|
const fieldTypeLabel = type => getFieldEditorTypeLabel(type);
|
|
const approveTypeText = type => (type === "OR" ? "或签" : "会签");
|
|
const goBack = () => {
|
uni.navigateBack();
|
};
|
|
const goEdit = () => {
|
if (!templateId.value || !detail.value) return;
|
uni.setStorageSync(EDIT_STORAGE_KEY, detail.value);
|
uni.navigateTo({
|
url: `/pages/oa/ApproveManage/approve-template/edit?id=${templateId.value}`,
|
});
|
};
|
|
const loadDetail = () => {
|
if (!templateId.value) return;
|
loading.value = true;
|
detail.value = null;
|
getApprovalTemplateDetail(templateId.value)
|
.then(res => {
|
detail.value = res?.data || null;
|
if (!detail.value) {
|
uni.showToast({ title: "未获取到详情", icon: "none" });
|
}
|
})
|
.catch(() => {
|
uni.showToast({ title: "获取详情失败", icon: "none" });
|
})
|
.finally(() => {
|
loading.value = false;
|
});
|
};
|
|
onLoad(options => {
|
fetchApprovalTemplateTypes()
|
.then(opts => {
|
typeLabelMap.value = buildTypeLabelMap(opts);
|
})
|
.catch(() => {});
|
if (options?.id) {
|
templateId.value = options.id;
|
loadDetail();
|
}
|
});
|
</script>
|
|
<style scoped lang="scss">
|
.template-detail-page {
|
display: flex;
|
flex-direction: column;
|
min-height: 100vh;
|
background: #f0f3f8;
|
}
|
|
.detail-scroll {
|
flex: 1;
|
height: 0;
|
padding: 10px 12px calc(96px + 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;
|
}
|
|
.section {
|
background: #fff;
|
border-radius: 12px;
|
margin-bottom: 10px;
|
overflow: hidden;
|
box-shadow: 0 2px 12px rgba(31, 45, 61, 0.05);
|
}
|
|
.section-title {
|
padding: 12px 16px;
|
font-size: 15px;
|
font-weight: 600;
|
color: #1f2d3d;
|
border-bottom: 1px solid #f2f4f7;
|
border-left: 3px solid #2979ff;
|
padding-left: 13px;
|
}
|
|
.info-list {
|
padding: 4px 0;
|
}
|
|
.info-item {
|
display: flex;
|
align-items: flex-start;
|
padding: 11px 16px;
|
border-bottom: 1px solid #f5f7fa;
|
gap: 12px;
|
|
&:last-child {
|
border-bottom: none;
|
}
|
}
|
|
.info-label {
|
flex-shrink: 0;
|
width: 88px;
|
font-size: 14px;
|
color: #606266;
|
}
|
|
.info-value {
|
flex: 1;
|
font-size: 14px;
|
color: #303133;
|
text-align: right;
|
word-break: break-all;
|
}
|
|
.status-on {
|
color: #18a058;
|
}
|
|
.status-off {
|
color: #909399;
|
}
|
|
.field-block {
|
padding: 0 12px 12px;
|
}
|
|
.field-card {
|
padding: 10px 12px;
|
margin-bottom: 8px;
|
background: #f8fafc;
|
border-radius: 8px;
|
border: 1px solid #eef2f6;
|
|
&:last-child {
|
margin-bottom: 0;
|
}
|
}
|
|
.field-card-head {
|
display: flex;
|
align-items: center;
|
flex-wrap: wrap;
|
gap: 6px;
|
}
|
|
.field-card-name {
|
font-size: 14px;
|
font-weight: 500;
|
color: #303133;
|
}
|
|
.field-tag {
|
font-size: 11px;
|
padding: 2px 8px;
|
border-radius: 4px;
|
color: #2979ff;
|
background: #ecf5ff;
|
|
&--req {
|
color: #f56c6c;
|
background: #fef0f0;
|
}
|
}
|
|
.field-card-default {
|
display: block;
|
margin-top: 6px;
|
font-size: 12px;
|
color: #909399;
|
}
|
|
.flow-list {
|
padding: 12px;
|
}
|
|
.flow-card {
|
padding: 12px;
|
margin-bottom: 8px;
|
background: #f8fafc;
|
border-radius: 8px;
|
border: 1px solid #eef2f6;
|
|
&:last-child {
|
margin-bottom: 0;
|
}
|
}
|
|
.flow-card-head {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
margin-bottom: 8px;
|
}
|
|
.flow-level {
|
font-size: 14px;
|
font-weight: 600;
|
color: #303133;
|
}
|
|
.flow-type {
|
font-size: 13px;
|
color: #2979ff;
|
}
|
|
.approver-tags {
|
display: flex;
|
flex-wrap: wrap;
|
gap: 8px;
|
}
|
|
.approver-tag {
|
padding: 4px 10px;
|
font-size: 13px;
|
color: #303133;
|
background: #fff;
|
border: 1px solid #dce8f8;
|
border-radius: 16px;
|
}
|
|
.empty-hint {
|
padding: 12px 16px 16px;
|
font-size: 13px;
|
color: #909399;
|
|
&.inline {
|
padding: 0;
|
}
|
}
|
|
.empty-wrap {
|
padding: 48px 20px;
|
}
|
</style>
|