Files
hzdyjs/mobile/src/components/Process/approvalRecord.vue
T

757 lines
23 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>
<u-popup
:show="visible"
mode="center"
:round="12"
:custom-style="{
width: '95vw',
height: '85vh',
maxWidth: '800px',
maxHeight: '700px',
}"
@close="handleClose"
>
<view class="approval-record-container">
<view class="header">
<text class="title">审批记录</text>
<u-icon name="close" size="24" @click="handleClose" />
</view>
<view class="content">
<u-tabs
v-model="tabActiveName"
:list="tabList"
:is-scroll="false"
active-color="#1989fa"
inactive-color="#666"
bar-width="60"
@change="handleTabChange"
/>
<!-- 流程图标签页 -->
<!-- <view v-if="tabActiveName === 0" class="tab-content flow-chart-tab">
<u-loading-page v-if="loading" />
<view v-else-if="!insId" class="empty">
<u-icon name="info-circle" size="48" color="#ccc" />
<text class="empty-text">暂无流程图数据</text>
</view>
<flowChart v-else :ins-id="insId" class="flow-chart" />
</view> -->
<!-- 审批信息标签页 -->
<view v-if="tabActiveName === 0" class="tab-content approval-info-tab">
<u-loading-page v-if="loading" />
<view v-else-if="historyList.length === 0" class="empty">
<u-icon name="info-circle" size="48" color="#ccc" />
<text class="empty-text">暂无审批记录</text>
</view>
<scroll-view v-else scroll-y class="record-list">
<view
v-for="(record, index) in historyList"
:key="index"
class="record-card"
>
<!-- 卡片头部 -->
<view class="card-header">
<view class="task-info">
<view class="task-index">#{{ index + 1 }}</view>
<view class="task-details">
<text class="task-name">
{{ record.nodeName || '未知任务' }}
</text>
<text class="task-duration">
{{ record.runDuration || '-' }}
</text>
</view>
</view>
<view :class="['status-badge', getStatusClass(record.flowStatus)]">
{{ getStatusText(record.flowStatus) }}
</view>
</view>
<!-- 办理人信息 -->
<view class="approver-section">
<text class="section-label">办理人:</text>
<view class="approver-tags">
<template v-if="record.approveName">
<u-tag
v-for="(name, nameIndex) in record.approveName.split(
',',
)"
:key="nameIndex"
:text="name.trim()"
:type="getApproverType()"
size="mini"
class="approver-tag"
/>
</template>
<u-tag
v-else
text="无"
:type="getNoApproverType()"
size="mini"
/>
</view>
</view>
<!-- 审批意见 -->
<view v-if="record.message" class="comment-section">
<text class="section-label">审批意见:</text>
<text class="comment-text">{{ record.message }}</text>
</view>
<!-- 时间信息 -->
<view class="time-section">
<view class="time-row">
<text class="time-label">开始时间:</text>
<text class="time-value">
{{ formatTime(record.createTime) }}
</text>
</view>
<view v-if="record.updateTime" class="time-row">
<text class="time-label">结束时间:</text>
<text class="time-value">
{{ formatTime(record.updateTime) }}
</text>
</view>
</view>
<!-- 附件信息 -->
<view
v-if="record.attachmentList && record.attachmentList.length > 0"
class="attachment-section"
>
<text class="section-label">附件:</text>
<view class="attachment-list">
<view
v-for="(file, fileIndex) in record.attachmentList"
:key="fileIndex"
class="attachment-item"
@click="handleDownload(file.ossId, file.originalName)"
>
<u-icon name="download" size="16" color="#1989fa" />
<text class="attachment-name">{{ file.originalName }}</text>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
</view>
</u-popup>
</template>
<script>
import { request, downloadFile } from '@/common/request.js';
import { useDict, getDictLabel } from '@/common/dict.js';
import FlowChart from './flowChart.vue';
export default {
name: 'ApprovalRecord',
components: {
FlowChart,
},
data() {
return {
visible: false,
loading: false,
historyList: [],
insId: null,
tabActiveName: 0, // 由于只有审批信息标签页可用,使用索引0
tabList: [{ name: '审批信息' }], // 只显示审批信息标签页(流程图被注释)
currentBusinessId: '',
// 字典数据
dictionaries: {},
};
},
created() {
this.initDictionaries();
},
methods: {
// 初始化字典数据
initDictionaries() {
console.log('🔍 初始化审批记录字典数据');
// 加载工作流任务状态字典
const dictManager = useDict('wf_task_status');
// 设置响应式更新回调(Vue 2必须)
dictManager.setUpdateCallback((dictType, data) => {
this.$set(this.dictionaries, dictType, data);
console.log(`📋 字典 ${dictType} 数据更新完成:`, data);
});
},
// 初始化审批记录(与web端API保持一致)
async init(businessId) {
console.log('=== 审批记录组件初始化 ===');
console.log('传入的businessId:', businessId);
console.log('businessId类型:', typeof businessId);
this.currentBusinessId = businessId.toString();
console.log('设置currentBusinessId为:', this.currentBusinessId);
this.visible = true;
console.log('设置visible为true');
this.loading = true;
this.tabActiveName = 0; // 默认显示审批信息标签页(索引0,因为只有一个标签页)
this.historyList = [];
this.insId = null;
console.log('开始加载审批记录...');
await this.loadApprovalRecords();
console.log('审批记录加载完成');
},
// 关闭弹窗
handleClose() {
this.visible = false;
this.historyList = [];
this.insId = null;
},
// 标签页切换
handleTabChange(index) {
this.tabActiveName = index;
},
// 加载审批记录(使用与web端相同的API)
async loadApprovalRecords() {
console.log('=== 加载审批记录 ===');
console.log('currentBusinessId:', this.currentBusinessId);
if (!this.currentBusinessId) {
console.error('currentBusinessId为空,无法加载审批记录');
return;
}
try {
this.loading = true;
console.log('设置loading为true');
// 使用与web端相同的API路径和防缓存参数
const apiUrl = `/workflow/instance/flowHisTaskList/${
this.currentBusinessId
}?t=${Math.random()}`;
console.log('API请求URL:', apiUrl);
const response = await request({
url: apiUrl,
method: 'GET',
});
console.log('API响应:', response);
if (response.code === 200 && response.data) {
this.historyList = response.data.list || [];
this.insId = response.data.instanceId;
console.log('审批记录列表:', this.historyList);
console.log('实例ID:', this.insId);
console.log('记录数量:', this.historyList.length);
// 处理附件信息(与web端保持一致)
if (this.historyList.length > 0) {
console.log('开始处理附件信息...');
for (const item of this.historyList) {
if (item.ext) {
try {
const attachmentRes = await this.getAttachmentsByIds(item.ext);
item.attachmentList = attachmentRes.data || [];
} catch (error) {
console.error('获取附件失败:', error);
item.attachmentList = [];
}
} else {
item.attachmentList = [];
}
}
console.log('附件信息处理完成');
}
} else {
console.error('API响应异常:', response);
uni.showToast({
title: response.msg || '获取审批记录失败',
icon: 'none',
});
}
} catch (error) {
console.error('获取审批记录失败:', error);
uni.showToast({
title: '获取审批记录失败',
icon: 'none',
});
} finally {
this.loading = false;
console.log('设置loading为false');
console.log('当前visible状态:', this.visible);
}
},
// 获取附件信息(使用与web端相同的API)
async getAttachmentsByIds(ids) {
const response = await request({
url: `/resource/oss/listByIds/${ids}`,
method: 'GET',
});
return response;
},
// 获取状态样式类(使用字典方式)
getStatusClass(flowStatus) {
console.log('flowStatus:', flowStatus);
// 使用字典获取状态样式类
const dictData = this.dictionaries.wf_task_status || [];
console.log('dictData:', dictData);
const dictItem = dictData.find((item) => String(item.value) === String(flowStatus));
console.log('dictItem:', dictItem);
// 如果字典中有配置样式类,则使用字典的elTagClass
if (dictItem && dictItem.elTagClass) {
return dictItem.elTagClass;
}
// 兜底逻辑:保持原有的switch逻辑作为备用
switch (flowStatus) {
case '3': // 完成
case 'complete':
return 'status-complete';
case '2': // 进行中
case 'waiting':
return 'status-waiting';
case '4': // 拒绝
case 'reject':
return 'status-reject';
case '5': // 终止
case 'terminate':
return 'status-terminate';
case '6': // 回退
case 'back':
return 'status-back';
case '7': // 委托
case 'delegate':
return 'status-delegate';
case '8': // 转办
case 'transfer':
return 'status-transfer';
case '9': // 加签
case 'countersign':
return 'status-countersign';
default:
return 'status-pending';
}
},
// 获取状态文本(使用字典方式)
getStatusText(flowStatus) {
// 使用字典获取状态文本
const dictData = this.dictionaries.wf_task_status || [];
const statusText = getDictLabel(dictData, flowStatus);
// 如果字典中没有找到对应值,使用兜底逻辑
if (statusText && statusText !== flowStatus) {
return statusText;
}
// 兜底逻辑:保持原有的switch逻辑作为备用
switch (flowStatus) {
case '3':
case 'complete':
return '完成';
case '2':
case 'waiting':
return '进行中';
case '4':
case 'reject':
return '拒绝';
case '5':
case 'terminate':
return '终止';
case '6':
case 'back':
return '回退';
case '7':
case 'delegate':
return '委托';
case '8':
case 'transfer':
return '转办';
case '9':
case 'countersign':
return '加签';
case '1':
case 'draft':
return '草稿';
default:
return '待处理';
}
},
// 格式化时间
formatTime(time) {
if (!time) return '-';
return new Date(time).toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
});
},
// 下载附件(使用统一的downloadFile方法)
async handleDownload(ossId, originalName) {
try {
uni.showLoading({
title: '正在下载...',
mask: true,
});
console.log('开始下载附件:', { ossId, originalName });
// 使用统一的downloadFile方法,传入文件名以支持H5端下载
const downloadUrl = `/resource/oss/download/${ossId}`;
const result = await downloadFile(downloadUrl, originalName);
console.log('下载完成:', result);
// 根据平台处理下载结果
// #ifdef APP-PLUS
// App端保存文件
if (result.statusCode === 200) {
uni.saveFile({
tempFilePath: result.tempFilePath,
success: function (res) {
uni.showToast({
title: '下载成功',
icon: 'success',
});
},
fail: function () {
uni.showToast({
title: '保存失败',
icon: 'none',
});
},
});
}
// #endif
// #ifdef H5
// H5端已通过downloadFile处理并直接触发浏览器下载,无需额外处理
console.log('H5端下载已由downloadFile方法处理');
// #endif
// #ifdef MP-WEIXIN
// 小程序端提示用户
uni.showModal({
title: '提示',
content: '小程序暂不支持文件下载,请在App或网页端操作',
showCancel: false,
});
// #endif
} catch (error) {
console.error('下载失败:', error);
uni.showToast({
title: error.message || '下载失败',
icon: 'none',
});
} finally {
uni.hideLoading();
}
},
// 获取审批人员标签类型
getApproverType() {
return 'success'; // 审批人员使用成功样式
},
// 获取无审批人员标签类型
getNoApproverType() {
return 'info'; // 无审批人员使用信息样式
},
// 注:getDownloadUrl方法已被移除,现在统一使用request.js中的downloadFile方法
},
};
</script>
<style scoped>
.approval-record-container {
height: 100%;
display: flex;
flex-direction: column;
background: #fff;
border-radius: 12rpx;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.content {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.tab-content {
flex: 1;
overflow: hidden;
}
.flow-chart-tab {
padding: 20rpx;
}
.flow-chart {
width: 100%;
height: 100%;
min-height: 400rpx;
}
.approval-info-tab {
padding: 0;
}
.empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
padding: 60rpx;
}
.empty-text {
margin-top: 20rpx;
font-size: 28rpx;
color: #999;
}
.record-list {
height: 100%;
padding: 20rpx 12rpx;
}
.record-card {
background: #f8f9fa;
border-radius: 12rpx;
padding: 24rpx;
margin: 0 8rpx 20rpx 8rpx;
border: 1rpx solid #e9ecef;
width: calc(100% - 16px);
box-sizing: border-box;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 20rpx;
}
.task-info {
display: flex;
align-items: flex-start;
gap: 12rpx;
flex: 1;
}
.task-index {
background: #1989fa;
color: #fff;
padding: 4rpx 8rpx;
border-radius: 6rpx;
font-size: 22rpx;
font-weight: 500;
min-width: 32rpx;
text-align: center;
}
.task-details {
display: flex;
flex-direction: column;
gap: 4rpx;
flex: 1;
}
.task-name {
font-size: 28rpx;
font-weight: 500;
color: #333;
line-height: 1.4;
}
.task-duration {
font-size: 24rpx;
color: #666;
}
.status-badge {
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-size: 24rpx;
font-weight: 500;
white-space: nowrap;
}
.status-complete {
background: #e8f5e8;
color: #52c41a;
}
.status-waiting {
background: #fff7e6;
color: #fa8c16;
}
.status-reject {
background: #fff2f0;
color: #ff4d4f;
}
.status-terminate {
background: #f6f6f6;
color: #666;
}
.status-back {
background: #fff2e8;
color: #fa541c;
}
.status-delegate {
background: #f0f5ff;
color: #1890ff;
}
.status-transfer {
background: #f9f0ff;
color: #722ed1;
}
.status-countersign {
background: #e6fffb;
color: #13c2c2;
}
.status-pending {
background: #f0f0f0;
color: #999;
}
.approver-section,
.comment-section,
.time-section,
.attachment-section {
margin-bottom: 16rpx;
}
.section-label {
font-size: 26rpx;
color: #666;
font-weight: 500;
margin-bottom: 8rpx;
display: block;
}
.approver-tags {
display: flex;
flex-wrap: wrap;
gap: 8rpx;
}
.approver-tag {
margin: 0;
}
.comment-text {
font-size: 26rpx;
color: #333;
line-height: 1.5;
padding: 12rpx;
background: #fff;
border-radius: 8rpx;
display: block;
}
.time-section {
padding: 12rpx;
background: #fff;
border-radius: 8rpx;
}
.time-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8rpx;
}
.time-row:last-child {
margin-bottom: 0;
}
.time-label {
font-size: 24rpx;
color: #666;
}
.time-value {
font-size: 24rpx;
color: #333;
}
.attachment-section {
padding: 12rpx;
background: #fff;
border-radius: 8rpx;
}
.attachment-list {
display: flex;
flex-direction: column;
gap: 12rpx;
}
.attachment-item {
display: flex;
align-items: center;
gap: 8rpx;
padding: 8rpx 12rpx;
background: #f8f9fa;
border-radius: 6rpx;
border: 1rpx solid #e9ecef;
}
.attachment-name {
font-size: 24rpx;
color: #1989fa;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 标签页样式调整 */
/* #ifdef APP-PLUS || H5 */
.flow-chart-tab {
background: #f8f9fa;
}
/* #endif */
</style>