2025-08-28 00:57:52 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<view class="approval-progress">
|
|
|
|
|
|
<view class="progress-title">
|
|
|
|
|
|
<text class="applicant-name">审批进度</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="divider"></view>
|
2025-09-15 22:40:32 +08:00
|
|
|
|
<view class="progress-list" v-if="approvalList.length > 0">
|
2025-08-28 00:57:52 +08:00
|
|
|
|
<view class="progress-item" v-for="(approver, index) in approvalList" :key="index">
|
|
|
|
|
|
<view class="progress-item-row">
|
|
|
|
|
|
<view class="item-avatar">
|
|
|
|
|
|
<image
|
|
|
|
|
|
:src="approver.avatar || '/static/base/home/11222.png'"
|
|
|
|
|
|
class="w-full h-full"
|
|
|
|
|
|
></image>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="item-middle">
|
|
|
|
|
|
<text class="item-name">{{ approver.userName }}</text>
|
|
|
|
|
|
<text class="item-detail">{{ getSpTypeText(approver.spType) }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="item-right">
|
|
|
|
|
|
<text class="item-time" v-if="approver.approveTime">{{ formatTime(approver.approveTime) }}</text>
|
|
|
|
|
|
<text class="item-status" :class="getStatusClass(approver.approveStatus)">
|
2025-09-05 22:25:34 +08:00
|
|
|
|
{{ getStatusText(approver.spType, approver.approveStatus) }}
|
2025-08-28 00:57:52 +08:00
|
|
|
|
</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="progress-item-line" v-if="index < approvalList.length - 1"></view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
2025-09-15 22:40:32 +08:00
|
|
|
|
<view class="empty-state" v-else>
|
|
|
|
|
|
<view class="empty-icon">
|
|
|
|
|
|
<uni-icons type="info" size="40" color="#999"></uni-icons>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="empty-text">暂无审批进度信息</view>
|
|
|
|
|
|
<view class="empty-subtext">可能审批流程尚未启动或数据加载失败</view>
|
|
|
|
|
|
<view class="retry-button" @click="loadApprovalProcess" v-if="hasError">
|
|
|
|
|
|
<text class="retry-text">重新加载</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
2025-08-28 00:57:52 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import dayjs from "dayjs";
|
2025-09-15 22:40:32 +08:00
|
|
|
|
import { getByYwIdAndYwTypeApi } from "@/api/base/lcglSpApi";
|
2025-08-28 00:57:52 +08:00
|
|
|
|
|
|
|
|
|
|
// 接收外部传入属性
|
|
|
|
|
|
const props = withDefaults(defineProps<{
|
2025-09-15 22:40:32 +08:00
|
|
|
|
ywId: string,
|
|
|
|
|
|
ywType: string,
|
2025-09-15 23:49:21 +08:00
|
|
|
|
showSqr?: boolean,
|
|
|
|
|
|
showSpr?: boolean,
|
|
|
|
|
|
showCsr?: boolean,
|
2025-08-28 00:57:52 +08:00
|
|
|
|
}>(), {
|
2025-09-15 22:40:32 +08:00
|
|
|
|
ywId: '',
|
|
|
|
|
|
ywType: '',
|
|
|
|
|
|
showSqr: true,
|
|
|
|
|
|
showSpr: true,
|
|
|
|
|
|
showCsr: false
|
2025-08-28 00:57:52 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 审批流程数据
|
|
|
|
|
|
const approvalList = ref<any[]>([]);
|
2025-09-15 22:40:32 +08:00
|
|
|
|
// 错误状态标识
|
|
|
|
|
|
const hasError = ref(false);
|
2025-08-28 00:57:52 +08:00
|
|
|
|
|
|
|
|
|
|
// 获取审批流程
|
|
|
|
|
|
const loadApprovalProcess = async () => {
|
2025-09-15 22:40:32 +08:00
|
|
|
|
if (!props.ywId || !props.ywType) return;
|
2025-08-28 00:57:52 +08:00
|
|
|
|
|
|
|
|
|
|
try {
|
2025-09-15 22:40:32 +08:00
|
|
|
|
// 重置错误状态
|
|
|
|
|
|
hasError.value = false;
|
2025-08-28 00:57:52 +08:00
|
|
|
|
|
2025-09-15 22:40:32 +08:00
|
|
|
|
// 调用后端API获取审批流程数据
|
|
|
|
|
|
const res = await getByYwIdAndYwTypeApi(props.ywId, props.ywType);
|
|
|
|
|
|
approvalList.value = [];
|
2025-08-28 00:57:52 +08:00
|
|
|
|
if (res.resultCode === 1 && res.result) {
|
|
|
|
|
|
// 转换为前端显示格式(后端已按sort字段排序)
|
2025-09-15 22:40:32 +08:00
|
|
|
|
const list = res.result.map((item: any) => ({
|
2025-08-28 00:57:52 +08:00
|
|
|
|
userName: item.userName || getDefaultUserName(item.spType),
|
|
|
|
|
|
spType: item.spType,
|
|
|
|
|
|
approveStatus: item.approveStatus,
|
|
|
|
|
|
approveTime: item.approveTime,
|
|
|
|
|
|
approveRemark: item.approveRemark,
|
|
|
|
|
|
avatar: item.avatar || '/static/base/home/11222.png'
|
|
|
|
|
|
}));
|
2025-09-15 22:40:32 +08:00
|
|
|
|
for (let item of list) {
|
|
|
|
|
|
if ((item.spType === 'SQ' && !props.showSqr)
|
|
|
|
|
|
|| (item.spType === 'SP' && !props.showSpr)
|
|
|
|
|
|
|| (item.spType === 'CC' && !props.showCsr)) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
approvalList.value.push(item);
|
|
|
|
|
|
}
|
2025-08-28 00:57:52 +08:00
|
|
|
|
} else {
|
2025-09-15 22:40:32 +08:00
|
|
|
|
hasError.value = true;
|
2025-08-28 00:57:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取审批流程失败:', error);
|
2025-09-15 22:40:32 +08:00
|
|
|
|
approvalList.value = [];
|
|
|
|
|
|
hasError.value = true;
|
2025-08-28 00:57:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 获取默认用户名
|
|
|
|
|
|
const getDefaultUserName = (spType: string) => {
|
|
|
|
|
|
switch (spType) {
|
2025-09-15 22:40:32 +08:00
|
|
|
|
case 'SQ': return '申请人';
|
|
|
|
|
|
case 'SP': return '审批人';
|
|
|
|
|
|
case 'CC': return '抄送人';
|
|
|
|
|
|
case 'DK': return '代课老师';
|
|
|
|
|
|
case 'JWC': return '教科处';
|
2025-08-28 00:57:52 +08:00
|
|
|
|
default: return '未知';
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 获取审批类型文本
|
|
|
|
|
|
const getSpTypeText = (spType: string) => {
|
|
|
|
|
|
switch (spType) {
|
|
|
|
|
|
case 'SQ': return '申请人';
|
|
|
|
|
|
case 'SP': return '审批人';
|
|
|
|
|
|
case 'CC': return '抄送人';
|
2025-09-15 22:40:32 +08:00
|
|
|
|
case 'DK': return '代课老师';
|
|
|
|
|
|
case 'JWC': return '教科处';
|
2025-08-28 00:57:52 +08:00
|
|
|
|
default: return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 获取状态文本
|
2025-09-05 22:25:34 +08:00
|
|
|
|
const getStatusText = (spType: string, status: string) => {
|
2025-08-28 00:57:52 +08:00
|
|
|
|
switch (status) {
|
|
|
|
|
|
case 'apply': return '已申请';
|
2025-09-05 22:25:34 +08:00
|
|
|
|
case 'pending': return spType === 'CC' ? '待抄送' : '待处理';
|
|
|
|
|
|
case 'approved': return spType === 'CC' ? '已抄送' : '已同意';
|
2025-08-28 00:57:52 +08:00
|
|
|
|
case 'rejected': return '已拒绝';
|
|
|
|
|
|
case 'cc_sent': return '已抄送';
|
|
|
|
|
|
default: return '未知';
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 获取状态样式类
|
|
|
|
|
|
const getStatusClass = (status: string) => {
|
|
|
|
|
|
switch (status) {
|
|
|
|
|
|
case 'pending': return 'status-pending';
|
|
|
|
|
|
case 'approved': return 'status-approved';
|
|
|
|
|
|
case 'rejected': return 'status-rejected';
|
|
|
|
|
|
case 'cc_sent': return 'status-cc';
|
|
|
|
|
|
default: return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 格式化时间
|
|
|
|
|
|
const formatTime = (time: string | Date) => {
|
|
|
|
|
|
if (!time) return '';
|
|
|
|
|
|
return dayjs(time).format('YYYY-MM-DD HH:mm');
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-09-15 22:40:32 +08:00
|
|
|
|
// 监听ywId变化
|
|
|
|
|
|
watch(() => props.ywId, (newVal) => {
|
2025-08-28 00:57:52 +08:00
|
|
|
|
if (newVal) {
|
|
|
|
|
|
loadApprovalProcess();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化
|
2025-09-15 22:40:32 +08:00
|
|
|
|
if (props.ywId) {
|
2025-08-28 00:57:52 +08:00
|
|
|
|
loadApprovalProcess();
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
.approval-progress {
|
|
|
|
|
|
margin: 15px;
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
padding: 15px;
|
|
|
|
|
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
|
|
|
|
|
|
|
|
|
|
|
.progress-title {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
|
|
|
|
|
|
.applicant-name {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.divider {
|
|
|
|
|
|
height: 1px;
|
|
|
|
|
|
background-color: #eee;
|
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.progress-list {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
|
|
|
|
|
|
.progress-item {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
|
|
|
|
|
|
.progress-item-row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding: 10px 0;
|
|
|
|
|
|
|
|
|
|
|
|
.item-avatar {
|
|
|
|
|
|
width: 40px;
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
margin-right: 12px;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.item-middle {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
|
|
|
|
|
|
.item-name {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.item-detail {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.item-right {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: flex-end;
|
|
|
|
|
|
|
|
|
|
|
|
.item-time {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.item-status {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
padding: 2px 8px;
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
|
|
|
|
|
|
&.status-pending {
|
|
|
|
|
|
background-color: #fff7e6;
|
|
|
|
|
|
color: #fa8c16;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.status-approved {
|
|
|
|
|
|
background-color: #f6ffed;
|
|
|
|
|
|
color: #52c41a;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.status-rejected {
|
|
|
|
|
|
background-color: #fff2f0;
|
|
|
|
|
|
color: #ff4d4f;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.status-cc {
|
|
|
|
|
|
background-color: #f0f5ff;
|
|
|
|
|
|
color: #1890ff;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.progress-item-line {
|
|
|
|
|
|
height: 20px;
|
|
|
|
|
|
width: 2px;
|
|
|
|
|
|
background-color: #e8e8e8;
|
|
|
|
|
|
margin-left: 19px;
|
|
|
|
|
|
margin-top: -10px;
|
|
|
|
|
|
margin-bottom: -10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-15 22:40:32 +08:00
|
|
|
|
|
|
|
|
|
|
.empty-state {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
padding: 40px 20px;
|
|
|
|
|
|
|
|
|
|
|
|
.empty-icon {
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.empty-text {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.empty-subtext {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.retry-button {
|
|
|
|
|
|
padding: 8px 24px;
|
|
|
|
|
|
background-color: #007AFF;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
|
|
|
|
|
|
.retry-text {
|
|
|
|
|
|
color: #FFFFFF;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-28 00:57:52 +08:00
|
|
|
|
}
|
2025-09-15 22:40:32 +08:00
|
|
|
|
</style>
|