2025-09-08 22:33:14 +08:00

1528 lines
40 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>
<BasicLayout>
<view class="px-15 pb-15">
<!-- 公文基本信息 -->
<view class="gw-info-section">
<view class="section-title">公文信息</view>
<view class="info-item">
<text class="label">公文标题</text>
<text class="value title-bold">{{ gwInfo.title }}</text>
</view>
<view class="info-item">
<text class="label">公文类型</text>
<text class="value">{{ gwInfo.docType }}</text>
</view>
<view class="info-item">
<text class="label">紧急程度</text>
<text class="value">{{ getUrgencyText(gwInfo.urgencyLevel) }}</text>
</view>
<view class="info-item">
<text class="label">创建人</text>
<text class="value">{{ getCreatorName(gwInfo.tjrId) }}</text>
</view>
<view class="info-item">
<text class="label">创建时间</text>
<text class="value">{{ formatTime(gwInfo.createdTime) }}</text>
</view>
</view>
<!-- 文件信息 -->
<view class="file-section">
<view class="section-title">附件</view>
<view class="file-list" v-if="hasAttachments">
<!-- 处理单个附件从gwInfo直接获取 -->
<view
v-if="gwInfo.fileUrl"
class="file-item"
@click="previewSingleFile"
>
<view class="file-icon">
<text v-if="isImage(gwInfo.fileFormat || '')">🖼</text>
<text v-else-if="isVideo(gwInfo.fileFormat || '')">🎥</text>
<text v-else-if="canPreview(gwInfo.fileFormat || '')">📄</text>
<text v-else>📎</text>
</view>
<view class="file-info">
<text class="file-name">{{ gwInfo.fileName || '未知文件' }}</text>
<text class="file-type">{{ (gwInfo.fileFormat || 'unknown').toUpperCase() }}</text>
</view>
<view class="file-actions">
<u-button
v-if="canPreview(gwInfo.fileFormat || '') && !isVideo(gwInfo.fileFormat || '')"
text="预览"
size="mini"
type="primary"
@click.stop="previewSingleFile"
/>
<u-button
v-if="isVideo(gwInfo.fileFormat || '')"
text="播放"
size="mini"
type="success"
@click.stop="previewSingleFile"
/>
<u-button
v-if="isImage(gwInfo.fileFormat || '')"
text="查看"
size="mini"
type="warning"
@click.stop="previewSingleFile"
/>
<u-button
text="下载"
size="mini"
type="info"
@click.stop="downloadSingleFile"
/>
</view>
</view>
<!-- 处理多个附件从files数组获取 -->
<view
v-for="(file, index) in gwInfo.files"
:key="index"
class="file-item"
@click="previewFile(file)"
>
<view class="file-icon">
<text v-if="isImage(getFileSuffix(file))">🖼</text>
<text v-else-if="isVideo(getFileSuffix(file))">🎥</text>
<text v-else-if="canPreview(getFileSuffix(file))">📄</text>
<text v-else>📎</text>
</view>
<view class="file-info">
<text class="file-name">{{ getFileName(file) }}</text>
<text class="file-size">{{ formatFileSize(file.size) }}</text>
<text class="file-type">{{ getFileSuffix(file).toUpperCase() }}</text>
</view>
<view class="file-actions">
<u-button
v-if="canPreview(getFileSuffix(file)) && !isVideo(getFileSuffix(file))"
text="预览"
size="mini"
type="primary"
@click.stop="previewFile(file)"
/>
<u-button
v-if="isVideo(getFileSuffix(file))"
text="播放"
size="mini"
type="success"
@click.stop="previewFile(file)"
/>
<u-button
v-if="isImage(getFileSuffix(file))"
text="查看"
size="mini"
type="warning"
@click.stop="previewFile(file)"
/>
<u-button
text="下载"
size="mini"
type="info"
@click.stop="downloadFile(file)"
/>
</view>
</view>
</view>
<view v-else class="no-files">
<text>暂无附件</text>
</view>
</view>
<!-- 当前处理人 -->
<view class="approver-section">
<view class="section-title">当前审批人</view>
<view class="approver-list">
<view
v-for="approver in approvers"
:key="approver.id"
class="approver-item"
>
<view class="approver-info">
<text class="order">{{ approver.order }}</text>
<text class="name">{{ approver.userName }}</text>
<text class="dept">{{ approver.deptName }}</text>
<text class="status" :class="getApproverStatusClass(approver.approveStatus)">
{{ getApproverStatusText(approver.approveStatus) }}
</text>
</view>
<!-- 显示审批意见和审批时间 -->
<view class="approver-detail" v-if="approver.approveRemark || approver.approveTime">
<view class="approval-info" v-if="approver.approveRemark">
<text class="info-label">审批意见</text>
<text class="info-value">{{ approver.approveRemark }}</text>
</view>
<view class="approval-info" v-if="approver.approveTime">
<text class="info-label">审批时间</text>
<text class="info-value">{{ formatTime(approver.approveTime) }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 抄送人 -->
<view class="cc-section">
<view class="section-title">抄送人</view>
<view class="cc-list">
<view
v-for="ccUser in displayedCcUsers"
:key="ccUser.id"
class="cc-item"
>
<view class="cc-info">
<text class="name">{{ ccUser.userName }}</text>
<text class="dept">{{ ccUser.deptName }}</text>
<text class="status" :class="getCCStatusClass(ccUser.status)">
{{ getCCStatusText(ccUser.status) }}
</text>
</view>
</view>
</view>
<!-- 更多按钮 -->
<view
v-if="ccUsers.length > 2"
class="more-button"
@click="toggleCcExpanded"
>
<text class="more-text">
{{ ccExpanded ? '收起' : `更多(${ccUsers.length - 2})` }}
</text>
<text class="more-icon" :class="{ expanded: ccExpanded }"></text>
</view>
</view>
<!-- 操作记录 -->
<view class="log-section">
<view class="section-title">操作记录</view>
<view class="log-list">
<view
v-for="log in displayedOperationLogs"
:key="log.id"
class="log-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="type">{{ log.operationType }}</text>
<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="operationLogs.length > 2"
class="more-button"
@click="toggleLogExpanded"
>
<text class="more-text">
{{ logExpanded ? '收起' : `更多(${operationLogs.length - 2})` }}
</text>
<text class="more-icon" :class="{ expanded: logExpanded }"></text>
</view>
</view>
</view>
<!-- 底部固定按钮 -->
<view class="bottom-actions" v-if="canCurrentUserOperate">
<!-- 驳回按钮暂时隐藏 -->
<!-- <u-button
text="驳回"
type="error"
size="large"
@click="handleReject"
/> -->
<u-button
text="转办"
type="warning"
size="large"
@click="handleTransfer"
/>
<u-button
text="同意"
type="primary"
size="large"
@click="handleApprove"
/>
</view>
<!-- 操作记录详情弹窗 -->
<u-popup v-model="showLogDetailModal" 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>
</BasicLayout>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from "vue";
import BasicLayout from "@/components/BasicLayout/Layout.vue";
import { navigateTo } from "@/utils/uniapp";
import { getGwFlowByIdApi, gwApproveApi } from "@/api/routine/gw";
import dayjs from "dayjs";
import { useUserStore } from "@/store/modules/user";
import { imagUrl } from "@/utils";
import {
isVideo,
isImage,
canPreview,
previewFile as previewFileUtil,
previewVideo as previewVideoUtil,
previewImage as previewImageUtil
} from "@/utils/filePreview";
// 类型定义
interface GwInfo {
id: string;
title: string;
gwNo: string;
gwType: string;
status: string;
createdBy: string;
createdTime: Date;
files?: FileInfo[]; // 改为可选,因为实际数据中可能没有这个字段
approvers?: Approver[];
ccUsers?: CCUser[];
operationLogs?: OperationLog[];
docType: string; // 新增
urgencyLevel: string; // 新增
tjrId: string; // 新增
spRule?: string; // 新增审批规则字段
// 附件相关字段(根据实际数据结构)
fileUrl?: string; // 文件URL
fileName?: string; // 文件名
fileFormat?: string; // 文件格式
}
interface FileInfo {
name: string;
size: number;
url: string;
resourName?: string; // 资源名称
resourUrl?: string; // 资源URL
resSuf?: string; // 文件后缀
}
interface Approver {
id: string;
userId?: string; // 用户ID
userName: string;
deptName: string;
order: number;
status: string;
approveStatus?: string; // 审批状态
approveRemark?: string; // 审批意见
approveTime?: Date; // 审批时间
}
interface CCUser {
id: string;
userName: string;
deptName: string;
status: string;
}
interface OperationLog {
id: string;
operatorName: string;
operationType: string;
operationContent: string;
operationTime: Date;
beforeChange?: string;
afterChange?: string;
remark?: string;
}
// 获取页面参数
const gwId = ref("");
// 弹窗控制
const showLogDetailModal = ref(false);
// 用户store
const { getUser, getJs } = useUserStore();
// 数据
const gwInfo = ref<GwInfo>({} as GwInfo);
const approvers = ref<Approver[]>([]);
const ccUsers = ref<CCUser[]>([]);
const operationLogs = ref<OperationLog[]>([]);
const currentLog = ref<OperationLog>({} as OperationLog);
// 抄送人展开状态
const ccExpanded = ref(false);
// 操作记录展开状态
const logExpanded = ref(false);
// 计算属性:是否有附件
const hasAttachments = computed(() => {
return gwInfo.value.fileUrl || (gwInfo.value.files && gwInfo.value.files.length > 0);
});
// 计算属性:显示的抄送人列表
const displayedCcUsers = computed(() => {
if (ccExpanded.value || ccUsers.value.length <= 2) {
return ccUsers.value;
}
return ccUsers.value.slice(0, 2);
});
// 计算属性:显示的操作记录列表
const displayedOperationLogs = computed(() => {
if (logExpanded.value || operationLogs.value.length <= 2) {
return operationLogs.value;
}
return operationLogs.value.slice(0, 2);
});
// 计算属性:当前用户是否可以操作(转办/同意)
const canCurrentUserOperate = computed(() => {
const userStore = useUserStore();
const getUser = userStore.getUser;
const getJs = userStore.getJs;
const currentUserId = getJs?.id;
if (!currentUserId || !approvers.value || approvers.value.length === 0) {
return false;
}
// 查找当前用户对应的审批人记录
const currentUserApprover = approvers.value.find(approver => {
return approver.userId === currentUserId || approver.id === currentUserId;
});
if (!currentUserApprover) {
return false;
}
// 如果审批状态是approved或rejected则不能操作
const approveStatus = currentUserApprover.approveStatus;
if (approveStatus === 'approved' || approveStatus === 'rejected') {
return false;
}
return true;
});
// 获取公文信息
const getGwInfo = async () => {
try {
// 调用新的API接口获取公文流程信息
const response = await getGwFlowByIdApi(gwId.value);
if (response.resultCode === 1) {
// 设置公文信息
gwInfo.value = response.result.gwInfo;
// 设置审批人、抄送人、操作日志信息
approvers.value = response.result.approvers || [];
ccUsers.value = response.result.ccUsers || [];
operationLogs.value = response.result.operationLogs || [];
} else {
throw new Error(response.message || '获取数据失败');
}
} catch (error) {
// 清空所有数据
gwInfo.value = {} as GwInfo;
approvers.value = [];
ccUsers.value = [];
operationLogs.value = [];
}
};
// 显示操作日志详情
const showLogDetail = (log: OperationLog) => {
currentLog.value = log;
showLogDetailModal.value = true;
};
// 切换抄送人展开状态
const toggleCcExpanded = () => {
ccExpanded.value = !ccExpanded.value;
};
// 切换操作记录展开状态
const toggleLogExpanded = () => {
logExpanded.value = !logExpanded.value;
};
// 驳回处理
const handleReject = () => {
uni.showModal({
title: "驳回公文",
content: "确定要驳回这个公文吗?",
success: async (res) => {
if (res.confirm) {
try {
// 这里调用驳回API
console.log("驳回公文:", gwId.value);
uni.showToast({
title: "驳回成功",
icon: "success",
});
} catch (error) {
console.error("驳回失败:", error);
uni.showToast({
title: "驳回失败",
icon: "error",
});
}
}
},
});
};
// 转办处理
const handleTransfer = () => {
// 构建跳转参数,包含所有必要的数据
const params = {
id: gwId.value,
title: encodeURIComponent(gwInfo.value.title || ''),
xxtsInfo: encodeURIComponent(JSON.stringify({
id: gwId.value, // 这里应该是xxtsInfo的ID需要根据实际数据结构调整
// 其他xxtsInfo相关字段
})),
gwInfo: encodeURIComponent(JSON.stringify(gwInfo.value)),
approvers: encodeURIComponent(JSON.stringify(approvers.value)),
ccUsers: encodeURIComponent(JSON.stringify(ccUsers.value)) // 添加抄送人数据
};
// 同时存储到本地存储,作为备用方案
uni.setStorageSync('transferData', {
xxtsInfo: { id: gwId.value },
gwInfo: gwInfo.value,
approvers: approvers.value,
ccUsers: ccUsers.value // 添加抄送人数据
});
// 跳转到转办页面
navigateTo(`/pages/view/routine/gwlz/gwTransfer?id=${params.id}&title=${params.title}&xxtsInfo=${params.xxtsInfo}&gwInfo=${params.gwInfo}&approvers=${params.approvers}&ccUsers=${params.ccUsers}`);
};
// 同意处理
const handleApprove = () => {
uni.showModal({
title: "同意公文",
content: "请您再次确认是否同意,公文审批内容",
success: async (res) => {
if (res.confirm) {
try {
// 调用同意API
await approveGw();
} catch (error) {
console.error("同意失败:", error);
uni.showToast({
title: "同意失败",
icon: "error",
});
}
}
},
});
};
// 同意公文API调用
const approveGw = async () => {
try {
// 显示加载提示
uni.showLoading({
title: '正在处理...',
mask: true
});
// 获取当前用户ID - 应该从jsData中获取
const currentUserId = getJs?.id;
if (!currentUserId) {
throw new Error('无法获取当前用户信息');
}
// 获取当前用户在审批人列表中的ID
const currentUserApproverId = getCurrentUserApproverId(currentUserId);
if (!currentUserApproverId) {
throw new Error('无法获取当前用户的审批人记录');
}
// 构建同意数据
const approveData = {
xxtsId: gwId.value, // 这里应该是xxtsInfo的ID需要根据实际数据结构调整
gwId: gwInfo.value?.id || gwId.value,
spId: currentUserApproverId,
approveRemark: "同意", // 默认审批意见
spRule: gwInfo.value?.spRule, // 从gwInfo中获取spRule字段
currentUserId: currentUserId
};
// 调用同意API - 使用正确的API函数
const response = await gwApproveApi(approveData);
// 隐藏加载提示
uni.hideLoading();
if (response.resultCode === 1) {
uni.showToast({
title: "同意成功",
icon: "success",
duration: 1500
});
// 延迟返回上一级并刷新
setTimeout(() => {
// 返回上一级页面
uni.navigateBack({
delta: 1,
success: () => {
// 通过事件总线通知上一级页面刷新
uni.$emit('refreshGwList');
}
});
}, 1500);
} else {
throw new Error(response.message || '同意失败');
}
} catch (error: any) {
// 隐藏加载提示
uni.hideLoading();
uni.showToast({
title: error.message || "同意失败",
icon: "error",
});
}
};
// 获取当前用户的审批状态
const getCurrentUserApproverStatus = () => {
const userStore = useUserStore();
const getJs = userStore.getJs;
const currentUserId = getJs?.id;
if (!currentUserId || !approvers.value || approvers.value.length === 0) {
return '未知';
}
const currentUserApprover = approvers.value.find(approver => {
return approver.userId === currentUserId || approver.id === currentUserId;
});
return currentUserApprover?.approveStatus || '未知';
};
// 获取当前用户在审批人列表中的ID
const getCurrentUserApproverId = (currentUserId: string) => {
if (!approvers.value || approvers.value.length === 0) {
return null;
}
// 查找当前用户对应的审批人记录
const currentUserApprover = approvers.value.find(approver => {
const matchByUserId = approver.userId === currentUserId;
const matchById = approver.id === currentUserId;
return matchByUserId || matchById;
});
return currentUserApprover?.id || null;
};
// 预览单个附件从gwInfo直接获取
const previewSingleFile = () => {
if (!gwInfo.value.fileUrl) {
return;
}
const fileUrl = imagUrl(gwInfo.value.fileUrl);
const fileName = gwInfo.value.fileName || '未知文件';
const fileFormat = gwInfo.value.fileFormat || '';
// 根据文件类型选择预览方式
if (isVideo(fileFormat)) {
handlePreviewVideoSingle(fileUrl, fileName);
} else if (isImage(fileFormat)) {
handlePreviewImageSingle(fileUrl);
} else if (canPreview(fileFormat)) {
handlePreviewDocumentSingle(fileUrl, fileName, fileFormat);
} else {
// 不支持预览的文件类型,直接下载
downloadSingleFile();
}
};
// 下载单个附件
const downloadSingleFile = () => {
if (!gwInfo.value.fileUrl) {
return;
}
const fileUrl = imagUrl(gwInfo.value.fileUrl);
const fileName = gwInfo.value.fileName || '未知文件';
const fileFormat = gwInfo.value.fileFormat || '';
const fullFileName = fileFormat ? `${fileName}.${fileFormat}` : fileName;
// 方法1: 使用 fetch 和 blob 方式强制下载
fetch(fileUrl)
.then(response => {
if (!response.ok) {
throw new Error('网络请求失败');
}
return response.blob();
})
.then(blob => {
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = fullFileName; // 使用 download 属性强制下载
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(downloadUrl);
uni.showToast({
title: '开始下载',
icon: 'success'
});
})
.catch(error => {
console.error('fetch下载失败:', error);
// 方法2: 如果fetch失败尝试直接打开链接
try {
const link = document.createElement('a');
link.href = fileUrl;
link.download = fullFileName;
link.target = '_blank';
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
uni.showToast({
title: '开始下载',
icon: 'success'
});
} catch (fallbackError) {
console.error('备用下载也失败:', fallbackError);
uni.showToast({
title: '下载失败',
icon: 'error'
});
}
});
};
// 文件预览
const previewFile = (file: FileInfo) => {
// 确定文件URL和名称
const fileUrl = file.resourUrl ? imagUrl(file.resourUrl) : file.url;
const fileName = file.resourName ? `${file.resourName}.${file.resSuf}` : file.name;
const fileSuf = file.resSuf || file.name.split('.').pop() || '';
// 根据文件类型选择预览方式
if (isVideo(fileSuf)) {
handlePreviewVideo(file);
} else if (isImage(fileSuf)) {
handlePreviewImage(file);
} else if (canPreview(fileSuf)) {
handlePreviewDocument(file);
} else {
// 不支持预览的文件类型,直接下载
downloadFileAction(file);
}
};
// 文档预览
const handlePreviewDocument = (file: FileInfo) => {
const fileUrl = file.resourUrl ? imagUrl(file.resourUrl) : file.url;
const fileName = file.resourName ? `${file.resourName}.${file.resSuf}` : file.name;
const fileSuf = file.resSuf || file.name.split('.').pop() || '';
previewFileUtil(fileUrl, fileName, fileSuf)
.catch((error: any) => {
// 预览失败时尝试下载
downloadFileAction(file);
});
};
// 单个附件文档预览
const handlePreviewDocumentSingle = (fileUrl: string, fileName: string, fileFormat: string) => {
const fullFileName = fileFormat ? `${fileName}.${fileFormat}` : fileName;
previewFileUtil(fileUrl, fullFileName, fileFormat)
.catch((error: any) => {
// 预览失败时尝试下载
downloadSingleFile();
});
};
// 视频预览
const handlePreviewVideo = (file: FileInfo) => {
const videoUrl = file.resourUrl ? imagUrl(file.resourUrl) : file.url;
const videoName = file.resourName || file.name;
previewVideoUtil(videoUrl, videoName)
.catch((error: any) => {
// 视频预览失败时的处理
});
};
// 单个附件视频预览
const handlePreviewVideoSingle = (videoUrl: string, videoName: string) => {
previewVideoUtil(videoUrl, videoName)
.catch((error: any) => {
// 视频预览失败时的处理
});
};
// 图片预览
const handlePreviewImage = (file: FileInfo) => {
const imageUrl = file.resourUrl ? imagUrl(file.resourUrl) : file.url;
previewImageUtil(imageUrl)
.catch((error: any) => {
// 图片预览失败时的处理
});
};
// 单个附件图片预览
const handlePreviewImageSingle = (imageUrl: string) => {
previewImageUtil(imageUrl)
.catch((error: any) => {
// 图片预览失败时的处理
});
};
// 文件下载
const downloadFile = (file: FileInfo) => {
downloadFileAction(file);
};
// 文件下载实现
const downloadFileAction = (file: FileInfo) => {
const fileUrl = file.resourUrl ? imagUrl(file.resourUrl) : file.url;
const fileName = file.resourName ? `${file.resourName}.${file.resSuf}` : file.name;
// 方法1: 使用 fetch 和 blob 方式强制下载
fetch(fileUrl)
.then(response => {
if (!response.ok) {
throw new Error('网络请求失败');
}
return response.blob();
})
.then(blob => {
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = fileName; // 使用 download 属性强制下载
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(downloadUrl);
uni.showToast({
title: '开始下载',
icon: 'success'
});
})
.catch(error => {
console.error('fetch下载失败:', error);
// 方法2: 如果fetch失败尝试直接打开链接
try {
const link = document.createElement('a');
link.href = fileUrl;
link.download = fileName;
link.target = '_blank';
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
uni.showToast({
title: '开始下载',
icon: 'success'
});
} catch (fallbackError) {
console.error('备用下载也失败:', fallbackError);
uni.showToast({
title: '下载失败',
icon: 'error'
});
}
});
};
// 格式化时间
const formatTime = (time: any) => {
return dayjs(time).format("YYYY-MM-DD HH:mm:ss");
};
// 格式化文件大小
const formatFileSize = (size: any) => {
if (!size) return "0B";
if (size < 1024) return size + "B";
if (size < 1024 * 1024) return (size / 1024).toFixed(2) + "KB";
return (size / (1024 * 1024)).toFixed(2) + "MB";
};
// 获取文件名
const getFileName = (file: FileInfo) => {
if (file.resourName) {
return file.resourName;
}
if (file.name) {
// 如果name包含扩展名去掉扩展名
const lastDotIndex = file.name.lastIndexOf('.');
if (lastDotIndex > 0) {
return file.name.substring(0, lastDotIndex);
}
return file.name;
}
return '未知文件';
};
// 获取文件后缀
const getFileSuffix = (file: FileInfo) => {
if (file.resSuf) {
return file.resSuf;
}
if (file.name) {
const lastDotIndex = file.name.lastIndexOf('.');
if (lastDotIndex > 0) {
return file.name.substring(lastDotIndex + 1);
}
}
return 'unknown';
};
// 获取状态样式类
const getStatusClass = (status: any) => {
const statusMap: Record<string, string> = {
draft: "status-draft",
pending: "status-pending",
approved: "status-approved",
rejected: "status-rejected",
};
return statusMap[status] || "status-default";
};
// 获取状态文本
const getStatusText = (status: any) => {
const statusMap: Record<string, string> = {
draft: "草稿",
pending: "待审批",
approved: "已通过",
rejected: "已驳回",
};
return statusMap[status] || "未知";
};
// 获取审批人状态样式类
const getApproverStatusClass = (status: any) => {
const statusMap: Record<string, string> = {
pending: "status-pending",
approved: "status-approved",
rejected: "status-rejected",
skipped: "status-skipped",
};
return statusMap[status] || "status-default";
};
// 获取审批人状态文本
const getApproverStatusText = (status: any) => {
const statusMap: Record<string, string> = {
pending: "待审批",
approved: "已同意",
rejected: "已驳回",
skipped: "已跳过",
};
return statusMap[status] || "未知";
};
// 获取抄送人状态样式类
const getCCStatusClass = (status: any) => {
const statusMap: Record<string, string> = {
unread: "status-unread",
read: "status-read",
};
return statusMap[status] || "status-default";
};
// 获取抄送人状态文本
const getCCStatusText = (status: any) => {
const statusMap: Record<string, string> = {
unread: "未读",
read: "已读",
};
return statusMap[status] || "未知";
};
// 获取紧急程度样式类
const getUrgencyClass = (level: string) => {
const levelMap: Record<string, string> = {
normal: "urgency-normal",
high: "urgency-high",
urgent: "urgency-urgent",
"普通": "urgency-normal", // 添加中文值映射
"高": "urgency-high", // 添加中文值映射
"紧急": "urgency-urgent", // 添加中文值映射
};
return levelMap[level] || "urgency-default";
};
// 获取紧急程度文本
const getUrgencyText = (level: string) => {
const levelMap: Record<string, string> = {
normal: "普通",
high: "高",
urgent: "紧急",
"普通": "普通", // 添加中文值映射
"高": "高", // 添加中文值映射
"紧急": "紧急", // 添加中文值映射
};
return levelMap[level] || "未知";
};
// 获取创建人名称
const getCreatorName = (tjrId: string) => {
if (!tjrId) return "未知";
try {
// 尝试从多种缓存中获取用户信息参考gwAdd.vue的实现
const teacherCache = uni.getStorageSync('teacherCache');
const globalTeacherData = uni.getStorageSync('globalTeacherData');
const localData = uni.getStorageSync('data');
// 方式1: 从teacherCache获取
if (teacherCache && teacherCache[tjrId]) {
return teacherCache[tjrId].jsxm || tjrId;
}
// 方式2: 从globalTeacherData获取
if (globalTeacherData && globalTeacherData.length > 0) {
const teacher = globalTeacherData.find((t: any) => t.id === tjrId);
if (teacher && teacher.jsxm) {
return teacher.jsxm;
}
}
// 方式3: 从data缓存中的allJs获取参考gwAdd.vue
if (localData && localData.allJs && localData.allJs.result) {
const allTeachers = localData.allJs.result;
const teacher = allTeachers.find((t: any) => t.id === tjrId);
if (teacher && teacher.jsxm) {
return teacher.jsxm;
}
}
// 如果缓存中没有找到返回ID
return `用户${tjrId}`;
} catch (error) {
return `用户${tjrId}`;
}
};
onMounted(() => {
// 尝试多种方式获取页面参数
let pageId = "";
// 方式1: 通过 getCurrentPages() 获取
const pages = getCurrentPages();
if (pages.length > 0) {
const currentPage = pages[pages.length - 1];
// 尝试不同的属性获取参数
const options = (currentPage as any).options || {};
pageId = options.id || "";
}
// 方式2: 如果方式1没有获取到尝试从URL解析
if (!pageId) {
try {
// 获取当前页面URL
const currentUrl = window.location.href;
// 在uni-app H5环境中参数通常在hash部分
const hash = window.location.hash;
if (hash && hash.includes('?')) {
// 从hash中提取查询参数
const queryString = hash.split('?')[1];
// 解析查询参数
const urlParams = new URLSearchParams(queryString);
pageId = urlParams.get('id') || "";
} else {
// 尝试从search部分解析备用方案
const urlParams = new URLSearchParams(window.location.search);
pageId = urlParams.get('id') || "";
}
} catch (error) {
// URL解析失败
}
}
// 方式3: 如果前两种方式都没有获取到,尝试从路由参数获取
if (!pageId) {
try {
// 获取当前页面的完整路径
const currentPath = window.location.pathname;
// 从路径中提取参数(如果路径包含参数)
const pathMatch = currentPath.match(/\/gwFlow\/([^/?]+)/);
if (pathMatch) {
pageId = pathMatch[1];
}
} catch (error) {
// 路径解析失败
}
}
// 设置gwId
gwId.value = pageId;
if (gwId.value) {
getGwInfo();
}
});
</script>
<style lang="scss" scoped>
.gw-info-section,
.file-section,
.approver-section,
.cc-section,
.log-section {
margin-bottom: 20px;
padding: 15px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.log-section {
margin-bottom: 40px; // 操作记录区域增加底部间距
}
.section-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 15px;
color: #333;
border-bottom: 2px solid #007aff;
padding-bottom: 5px;
}
.info-item {
display: flex;
margin-bottom: 15px;
.label {
width: 80px;
color: #666;
font-size: 14px;
}
.value {
flex: 1;
color: #333;
font-size: 14px;
&.title-bold {
font-weight: bold;
font-size: 16px;
}
}
}
.status-tag {
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
&.status-draft { background: #f0f0f0; color: #666; }
&.status-pending { background: #fff7e6; color: #fa8c16; }
&.status-approved { background: #f6ffed; color: #52c41a; }
&.status-rejected { background: #fff2f0; color: #ff4d4f; }
}
.urgency-tag {
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
&.urgency-normal { background: #f0f0f0; color: #666; }
&.urgency-high { background: #fff7e6; color: #fa8c16; }
&.urgency-urgent { background: #fff2f0; color: #ff4d4f; }
}
.file-item {
display: flex;
align-items: center;
padding: 12px;
border: 1px solid #eee;
border-radius: 8px;
margin-bottom: 10px;
background: #fafafa;
transition: all 0.3s ease;
&:active {
transform: translateY(1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.file-icon {
margin-right: 12px;
font-size: 24px;
width: 32px;
text-align: center;
}
.file-info {
flex: 1;
.file-name {
display: block;
font-weight: 500;
margin-bottom: 4px;
color: #333;
font-size: 14px;
}
.file-size {
font-size: 12px;
color: #666;
margin-right: 8px;
}
.file-type {
font-size: 11px;
color: #999;
background: #f0f0f0;
padding: 2px 6px;
border-radius: 4px;
}
}
.file-actions {
display: flex;
gap: 4px;
flex-wrap: wrap;
justify-content: flex-end;
.u-button {
min-width: 20px;
height: 26px;
font-size: 12px;
border-radius: 13px;
padding: 0 6px;
}
}
}
.no-files {
text-align: center;
padding: 40px 20px;
color: #999;
font-size: 14px;
}
.approver-item,
.cc-item {
display: flex;
align-items: center;
padding: 10px;
border: 1px solid #eee;
border-radius: 4px;
margin-bottom: 10px;
.approver-info,
.cc-info {
flex: 1;
.order {
background: #007aff;
color: white;
padding: 2px 6px;
border-radius: 10px;
font-size: 12px;
margin-right: 8px;
}
.name {
font-weight: 500;
margin-right: 8px;
}
.dept {
color: #666;
font-size: 12px;
margin-right: 8px;
}
.status {
padding: 2px 6px;
border-radius: 4px;
font-size: 12px;
&.status-pending { background: #fff7e6; color: #fa8c16; }
&.status-approved { background: #f6ffed; color: #52c41a; }
&.status-rejected { background: #fff2f0; color: #ff4d4f; }
&.status-skipped { background: #f0f0f0; color: #666; }
&.status-unread { background: #fff7e6; color: #fa8c16; }
&.status-read { background: #f6ffed; color: #52c41a; }
}
}
}
.approver-detail {
margin-top: 10px;
padding: 10px;
background: #f8f9fa;
border-radius: 4px;
border-left: 3px solid #007aff;
.approval-info {
display: flex;
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
}
.info-label {
width: 80px;
color: #666;
font-size: 12px;
flex-shrink: 0;
}
.info-value {
flex: 1;
color: #333;
font-size: 12px;
word-break: break-all;
}
}
}
.more-button {
display: flex;
align-items: center;
justify-content: center;
padding: 8px 16px;
margin-top: 10px;
margin-bottom: 20px; // 增加底部间距,避免被底部按钮挡住
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
&:active {
background: #e9ecef;
transform: translateY(1px);
}
.more-text {
font-size: 14px;
color: #007aff;
margin-right: 6px;
}
.more-icon {
font-size: 12px;
color: #007aff;
transition: transform 0.3s ease;
&.expanded {
transform: rotate(180deg);
}
}
}
.log-item {
padding: 10px;
border: 1px solid #eee;
border-radius: 4px;
margin-bottom: 10px;
.log-header {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
.operator {
font-weight: 500;
color: #007aff;
}
.time {
font-size: 12px;
color: #666;
}
}
.log-content {
margin-bottom: 8px;
.type {
background: #f0f0f0;
padding: 2px 6px;
border-radius: 4px;
font-size: 12px;
margin-right: 8px;
}
.content {
color: #333;
}
}
}
.bottom-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: white;
padding: 15px;
border-top: 1px solid #eee;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
display: flex;
gap: 15px;
z-index: 1000;
}
.bottom-actions .u-button {
flex: 1;
height: 44px;
border-radius: 8px;
font-size: 16px;
font-weight: 500;
}
.modal-content {
padding: 20px;
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
.modal-title {
font-size: 16px;
font-weight: bold;
}
}
.search-section {
margin-bottom: 20px;
}
.position-section {
display: flex;
align-items: center;
.position-label {
margin-right: 10px;
}
}
}
.detail-modal {
width: 80vw;
max-width: 400px;
padding: 20px;
.detail-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
.detail-title {
font-size: 16px;
font-weight: bold;
}
}
.detail-content {
.detail-item {
margin-bottom: 15px;
.detail-label {
font-weight: 500;
color: #666;
margin-right: 10px;
}
.detail-value {
color: #333;
word-break: break-all;
}
}
}
}
</style>