1528 lines
40 KiB
Vue
1528 lines
40 KiB
Vue
<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>
|