2025-09-24 11:34:23 +08:00

555 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="lcgl-info">
<view class="flex-row items-center justify-between mb-15" v-if="showTitle">
<view class="dk-title">
<BasicTitle line title="审批流程" :isBorder="false" />
</view>
</view>
<!-- 申请人 -->
<view class="info-section sqr">
<view class="section-title">申请人</view>
<view class="sp-list">
<view class="sp-item">
<view class="sp-info">
<text class="name">{{ sqrSp.userName }}</text>
<text class="dept">{{ sqrSp.deptName }}</text>
<text class="status" :class="getSqrStatusClass(sqrSp.approveStatus)">
{{ getSqrStatusText(sqrSp.approveStatus) }}
</text>
</view>
<!-- 显示审批意见和审批时间 -->
<view class="sp-detail" v-if="sqrSp.approveRemark || sqrSp.approveTime">
<view class="approval-info" v-if="sqrSp.approveRemark">
<text class="info-label">申请意见</text>
<text class="info-value">{{ sqrSp.approveRemark }}</text>
</view>
<view class="approval-info" v-if="sqrSp.approveTime">
<text class="info-label">申请时间</text>
<text class="info-value">{{ formatTime(sqrSp.approveTime) }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 审批人 -->
<view class="info-section spr" v-if="sprSpList.length > 0">
<view class="section-title">审批人{{ sprSpList.length || 0 }}</view>
<view class="sp-list">
<view v-for="spr in sprSpList" :key="spr.id" class="sp-item">
<view class="sp-info">
<text class="name">{{ spr.userName }}</text>
<text class="dept">{{ spr.deptName }}</text>
<text class="status" :class="getSprStatusClass(spr.approveStatus)">
{{ getSprStatusText(spr.approveStatus) }}
</text>
</view>
<!-- 显示审批意见和审批时间 -->
<view class="sp-detail" v-if="spr.approveStatus !== 'pending'">
<view class="approval-info" v-if="spr.approveRemark">
<text class="info-label">审批意见</text>
<text class="info-value">{{ spr.approveRemark }}</text>
</view>
<view class="approval-info" v-if="spr.approveTime">
<text class="info-label">审批时间</text>
<text class="info-value">{{ formatTime(spr.approveTime) }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 抄送人 -->
<view class="info-section csr" v-if="csrSpList.length > 0">
<view class="section-title">抄送人{{ csrSpList.length || 0 }}</view>
<view class="sp-list">
<view v-for="csr in displayedCsrList" :key="csr.id" class="sp-item">
<view class="sp-info">
<text class="name">{{ csr.userName }}</text>
<text class="dept">{{ csr.deptName }}</text>
<text class="status" :class="getCsrStatusClass(csr.approveStatus)">
{{ getCsrStatusText(csr.approveStatus) }}
</text>
</view>
</view>
</view>
<!-- 更多按钮 -->
<view v-if="csrSpList.length > 2" class="more-button" @click="toggleCsrExpanded">
<text class="more-text">
{{ csrExpanded ? '收起' : `更多(${csrSpList.length - 2})` }}
</text>
<text class="more-icon" :class="{ expanded: csrExpanded }"></text>
</view>
</view>
<!-- 操作记录 -->
<view class="info-section log" v-if="logList.length > 0">
<view class="section-title">操作记录{{ logList.length || 0 }}</view>
<view class="sp-list">
<view v-for="log in displayedLogList" :key="log.id" class="sp-item">
<view class="log-header">
<text class="operator">{{ log.operatorName }}</text>
<text class="time">{{ formatTime(log.operationTime) }}</text>
</view>
<view class="log-content">
<text class="content">{{ log.operationContent }}</text>
</view>
<view class="log-detail" v-if="log.beforeChange || log.afterChange">
<u-button text="详情" size="mini" @click="showLogDetail(log)" />
</view>
</view>
</view>
<!-- 更多按钮 -->
<view v-if="logList.length > 2" class="more-button" @click="toggleLogExpanded">
<text class="more-text">
{{ logExpanded ? '收起' : `更多(${logList.length - 2})` }}
</text>
<text class="more-icon" :class="{ expanded: logExpanded }"></text>
</view>
</view>
<!-- 操作记录详情弹窗 -->
<u-popup :show="showLogDetailModal" @close="showLogDetailModal = false" mode="center">
<view class="detail-modal">
<view class="detail-header">
<text class="detail-title">操作详情</text>
<u-button text="关闭" @click="showLogDetailModal = false" />
</view>
<view class="detail-content">
<view class="detail-item" v-if="currentLog.beforeChange">
<text class="detail-label">变更前</text>
<text class="detail-value">{{ currentLog.beforeChange }}</text>
</view>
<view class="detail-item" v-if="currentLog.afterChange">
<text class="detail-label">变更后</text>
<text class="detail-value">{{ currentLog.afterChange }}</text>
</view>
<view class="detail-item" v-if="currentLog.remark">
<text class="detail-label">备注</text>
<text class="detail-value">{{ currentLog.remark }}</text>
</view>
</view>
</view>
</u-popup>
</view>
</template>
<script setup lang="ts">
import dayjs from "dayjs";
import { getLcglApi } from "@/api/base/lcglSpApi";
import { useDataStore } from "@/store/modules/data";
const { setLcgl } = useDataStore();
// 接收外部传入属性
const props = withDefaults(defineProps<{
ywId: string,
ywType: string,
showTitle?: boolean
}>(), {
ywId: '',
ywType: '',
showTitle: false
});
const sqrSp = ref<any>({});
const sprSpList = ref<any>([]);
const csrSpList = ref<any>([]);
const logList = ref<any>([]);
// 抄送人展开状态
const csrExpanded = ref(false);
// 操作记录展开状态
const logExpanded = ref(false);
// 计算属性:显示的抄送人列表
const displayedCsrList = computed(() => {
if (csrExpanded.value || csrSpList.value.length <= 2) {
return csrSpList.value;
}
return csrSpList.value.slice(0, 2);
});
// 计算属性:显示的操作记录列表
const displayedLogList = computed(() => {
if (logExpanded.value || logList.value.length <= 2) {
return logList.value;
}
return logList.value.slice(0, 2);
});
const showLogDetailModal = ref(false);
const currentLog = ref<any>({});
// 显示操作日志详情
const showLogDetail = (log: any) => {
currentLog.value = log;
showLogDetailModal.value = true;
};
// 切换抄送人展开状态
const toggleCsrExpanded = () => {
csrExpanded.value = !csrExpanded.value;
};
// 切换操作记录展开状态
const toggleLogExpanded = () => {
logExpanded.value = !logExpanded.value;
};
// 获取审批人状态文本
const getSqrStatusText = (status: any) => {
const statusMap: Record<string, string> = {
apply: "已申请",
};
return statusMap[status] || "未知";
};
// 获取抄送人状态样式类
const getSqrStatusClass = (status: any) => {
const statusMap: Record<string, string> = {
apply: "status-read",
};
return statusMap[status] || "status-default";
};
// 获取审批人状态样式类
const getSprStatusClass = (status: any) => {
const statusMap: Record<string, string> = {
pending: "status-pending",
approved: "status-approved",
rejected: "status-rejected",
skipped: "status-skipped",
stop: "status-stop",
transfer: "status-transfer",
};
return statusMap[status] || "status-default";
};
// 获取审批人状态文本
const getSprStatusText = (status: any) => {
const statusMap: Record<string, string> = {
pending: "待审批",
approved: "已同意",
rejected: "已驳回",
skipped: "已跳过",
stop: "已终止",
transfer: "已转办",
};
return statusMap[status] || "未知";
};
// 获取抄送人状态样式类
const getCsrStatusClass = (status: any) => {
const statusMap: Record<string, string> = {
pending: "status-pending",
approved: "status-approved",
unread: "status-unread",
read: "status-read",
cc_sent: "status-approved",
};
return statusMap[status] || "status-default";
};
// 获取抄送人状态文本
const getCsrStatusText = (status: any) => {
const statusMap: Record<string, string> = {
pending: "待抄送",
approved: "已抄送",
cc_sent: "已抄送",
unread: "未读",
read: "已读",
};
return statusMap[status] || "未知";
};
// 获取审批流程
const loadLcgl = async () => {
if (!props.ywId || !props.ywType) return;
try {
const params = {
ywId: props.ywId,
ywType: props.ywType,
}
// 调用后端API获取审批流程数据
const res = await getLcglApi(params);
sqrSp.value = {};
sqrSp.value = [];
csrSpList.value = [];
logList.value = [];
setLcgl(res.result || {});
if (res.resultCode === 1 && res.result) {
sqrSp.value = res.result.sqrSp || {};
sprSpList.value = res.result.sprSpList || [];
csrSpList.value = res.result.csrSpList || [];
logList.value = res.result.logList || [];
}
} catch (error) {
}
};
// 格式化时间
const formatTime = (time: string | Date) => {
if (!time) return '';
return dayjs(time).format('YYYY-MM-DD HH:mm');
};
// 监听ywId变化
watch(() => props.ywId, (newVal) => {
if (newVal) {
loadLcgl();
}
});
// 初始化
if (props.ywId) {
loadLcgl();
}
</script>
<style lang="scss" scoped>
.lcgl-info {
padding: 0 30rpx;
.info-section {
margin-bottom: 30rpx;
padding: 30rpx;
background: #fff;
border-radius: 16rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
&.sqr {
/* 申请人区域特殊样式可以在这里添加 */
background-color: white;
}
&.spr {
/* 审批人区域特殊样式可以在这里添加 */
background-color: white;
}
&.csr {
/* 抄送人区域特殊样式可以在这里添加 */
background-color: white;
}
&.log {
margin-bottom: 80rpx; // 操作记录区域增加底部间距
}
}
.section-title {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 30rpx;
color: #333;
border-bottom: 4rpx solid #007aff;
padding-bottom: 10rpx;
}
.sp-list {
/* 列表容器样式 */
background-color: white;
}
.sp-item {
display: flex;
flex-direction: column;
padding: 20rpx;
border: 2rpx solid #eee;
border-radius: 8rpx;
margin-bottom: 20rpx;
.sp-info {
display: flex;
align-items: center;
.name {
font-weight: 500;
margin-right: 16rpx;
}
.dept {
color: #666;
font-size: 24rpx;
margin-right: 16rpx;
}
.status {
padding: 4rpx 12rpx;
border-radius: 8rpx;
font-size: 24rpx;
margin-left: auto;
&.status-pending {
background-color: #fff7e6;
color: #fa8c16;
}
&.status-approved {
background-color: #f6ffed;
color: #52c41a;
}
&.status-rejected {
background-color: #fff1f0;
color: #f5222d;
}
&.status-withdrawn {
background-color: #f9f9f9;
color: #8c8c8c;
}
&.status-skipped {
background-color: #f0f5ff;
color: #2f54eb;
}
&.status-stop {
background-color: #fff1f0;
color: #ff4d4f;
}
&.status-transfer {
background-color: #f9f0ff;
color: #722ed1;
}
&.status-default {
background-color: #fafafa;
color: #595959;
}
}
}
}
.sp-detail {
margin-top: 20rpx;
padding: 20rpx;
background: #f8f9fa;
border-radius: 8rpx;
border-left: 6rpx solid #007aff;
.approval-info {
display: flex;
margin-bottom: 16rpx;
&:last-child {
margin-bottom: 0;
}
.info-label {
width: 160rpx;
color: #666;
font-size: 24rpx;
flex-shrink: 0;
}
.info-value {
flex: 1;
color: #333;
font-size: 24rpx;
word-break: break-all;
}
}
}
.log-header {
display: flex;
justify-content: space-between;
margin-bottom: 16rpx;
.operator {
font-weight: 500;
color: #007aff;
}
.time {
font-size: 24rpx;
color: #666;
}
}
.log-content {
margin-bottom: 16rpx;
.content {
color: #333;
}
}
.log-detail {
margin-top: 10rpx;
}
.more-button {
display: flex;
align-items: center;
justify-content: center;
padding: 16rpx 32rpx;
margin-top: 20rpx;
margin-bottom: 40rpx;
background: #f8f9fa;
border: 2rpx solid #e9ecef;
border-radius: 12rpx;
cursor: pointer;
transition: all 0.3s ease;
&:active {
background: #e9ecef;
transform: translateY(1px);
}
.more-text {
font-size: 28rpx;
color: #007aff;
margin-right: 12rpx;
}
.more-icon {
font-size: 24rpx;
color: #007aff;
transition: transform 0.3s ease;
&.expanded {
transform: rotate(180deg);
}
}
}
.detail-modal {
width: 80vw;
max-width: 800rpx;
padding: 40rpx;
.detail-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 40rpx;
.detail-title {
font-size: 32rpx;
font-weight: bold;
}
}
.detail-content {
.detail-item {
margin-bottom: 30rpx;
.detail-label {
font-weight: 500;
color: #666;
margin-right: 20rpx;
}
.detail-value {
color: #333;
word-break: break-all;
}
}
}
}
}
</style>