|
|
<template> <div class="approval-container"> <!-- 页面标题和统计 --> <div class="page-header"> <div class="header-left"> <h2 class="page-title"> <i class="el-icon-tickets"></i> 经理审批 </h2> <p class="page-subtitle">试验申请审批管理</p> </div> <div class="header-right"> <div class="stat-cards"> <div class="stat-card stat-total"> <div class="stat-icon"><i class="el-icon-document"></i></div> <div class="stat-content"> <div class="stat-value">{{ totalPage }}</div> <div class="stat-label">待处理总数</div> </div> </div> <div class="stat-card stat-high-risk"> <div class="stat-icon"><i class="el-icon-warning"></i></div> <div class="stat-content"> <div class="stat-value">{{ highRiskCount }}</div> <div class="stat-label">高风险试验</div> </div> </div> <div class="stat-card stat-low-risk"> <div class="stat-icon"><i class="el-icon-success"></i></div> <div class="stat-content"> <div class="stat-value">{{ lowRiskCount }}</div> <div class="stat-label">低风险试验</div> </div> </div> </div> </div> </div>
<!-- 查询条件表单 --> <div class="search-section"> <el-collapse class="no-arrow" v-model="searchExpanded"> <el-collapse-item name="1"> <template slot="title"> <i class="el-icon-search"></i> <span style="margin-left: 8px; font-weight: 500;">筛选条件</span> </template> <el-form :inline="true" label-position="top" class="search-form"> <el-form-item label="申请单号"> <el-input v-model="queryHeaderData.applyNo" placeholder="支持模糊查询" clearable style="width: 150px"></el-input> </el-form-item>
<el-form-item label="事业部"> <el-select v-model="queryHeaderData.buNo" placeholder="请选择" clearable style="width: 120px"> <el-option label="全部" value=""></el-option> <el-option v-for="i in buList" :key="i.buNo" :label="i.buDesc" :value="i.buNo"> </el-option> </el-select> </el-form-item>
<el-form-item label="试验类型"> <el-select v-model="queryHeaderData.experimentType" placeholder="请选择" clearable style="width: 120px"> <el-option label="全部" value=""></el-option> <el-option label="High Risk" value="High Risk"></el-option> <el-option label="Low Risk" value="Low Risk"></el-option> </el-select> </el-form-item>
<el-form-item label="试验名称"> <el-input v-model="queryHeaderData.title" placeholder="支持模糊查询" clearable style="width: 150px"></el-input> </el-form-item>
<el-form-item label="项目编号"> <el-input v-model="queryHeaderData.projectNo" placeholder="支持模糊查询" clearable style="width: 150px"></el-input> </el-form-item>
<el-form-item label="产品型号"> <el-input v-model="queryHeaderData.productType" placeholder="支持模糊查询" clearable style="width: 150px"></el-input> </el-form-item>
<el-form-item label="申请人"> <el-input v-model="queryHeaderData.creatorName" placeholder="支持模糊查询" clearable style="width: 120px"></el-input> </el-form-item>
<el-form-item label="申请开始日期"> <el-date-picker v-model="queryHeaderData.createStartDate" type="date" placeholder="选择日期" value-format="yyyy-MM-dd" style="width: 150px"> </el-date-picker> </el-form-item>
<el-form-item label="申请结束日期"> <el-date-picker v-model="queryHeaderData.createEndDate" type="date" placeholder="选择日期" value-format="yyyy-MM-dd" style="width: 150px"> </el-date-picker> </el-form-item>
<el-form-item label=" "> <el-button @click="getDataList('Y')" type="primary" icon="el-icon-search">查询</el-button> <el-button @click="resetQuery()" type="default" icon="el-icon-refresh-left">重置</el-button> </el-form-item> </el-form> </el-collapse-item> </el-collapse> </div>
<!-- 卡片列表 --> <div class="cards-container" v-loading="dataListLoading"> <transition-group name="card-list" tag="div" class="cards-grid"> <div v-for="item in dataList" :key="item.applyNo" class="approval-card" :class="{'high-risk': item.experimentType === 'High Risk'}" @click="openApprovalDialog(item)">
<!-- 卡片头部 --> <div class="card-header"> <div class="card-title-area"> <el-tag :type="item.experimentType === 'High Risk' ? 'danger' : 'success'" size="middle" effect="dark" class="risk-tag"> <i :class="item.experimentType === 'High Risk' ? 'el-icon-warning' : 'el-icon-success'"></i> {{ item.experimentType }} </el-tag> <span class="apply-no">{{ item.applyNo }}</span> </div> <el-badge :value="item.attachmentCount || 0" :hidden="!item.attachmentCount" class="attachment-badge"> <el-button type="text" icon="el-icon-paperclip" class="attachment-btn" @click.stop="openAttachmentDialog(item)"> 附件 </el-button> </el-badge> </div>
<!-- 卡片主体内容 --> <div class="card-body"> <h3 class="experiment-title"> <i class="el-icon-document"></i> {{ item.title }} </h3>
<div class="card-details"> <div class="detail-row"> <span class="detail-label"> <i class="el-icon-box"></i> 项目编号 </span> <span class="detail-value">{{ item.projectNo || '-' }}</span> </div>
<div class="detail-row"> <span class="detail-label"> <i class="el-icon-goods"></i> 产品型号 </span> <span class="detail-value" :title="item.productType">{{ item.productType || '-' }}</span> </div>
<div class="detail-row"> <span class="detail-label"> <i class="el-icon-user"></i> 申请人 </span> <span class="detail-value">{{ item.creatorName }}</span> </div>
<div class="detail-row"> <span class="detail-label"> <i class="el-icon-time"></i> 申请时间 </span> <span class="detail-value">{{ item.createTime }}</span> </div> </div> </div>
<!-- 卡片底部 --> <div class="card-footer"> <div class="current-step"> <i class="el-icon-position"></i> {{ item.currentStep }} </div> <div class="action-buttons"> <el-button type="success" size="small" plain icon="el-icon-check" class="approve-btn" @click.stop="quickApprove(item)"> 通过 </el-button> <el-button type="danger" size="small" plain icon="el-icon-close" class="reject-btn" @click.stop="openRejectDialog(item)"> 驳回 </el-button> </div> </div> </div> </transition-group>
<!-- 空状态 --> <div v-if="!dataListLoading && dataList.length === 0" class="empty-state"> <i class="el-icon-document-checked empty-icon"></i> <p class="empty-text">暂无待办事项</p> <p class="empty-subtext">您已完成所有审批任务</p> </div> </div>
<!-- 分页组件 --> <div class="pagination-wrapper" v-if="dataList.length > 0"> <el-pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" :current-page="pageIndex" :page-sizes="[20, 50, 100]" :page-size="pageSize" :total="totalPage" layout="total, sizes, prev, pager, next, jumper" background> </el-pagination> </div>
<!-- 审批弹窗 --> <el-dialog title="审批申请单" :visible.sync="approvalDialogVisible" width="1100px" :close-on-click-modal="false" v-drag>
<!-- Tabs切换 --> <el-tabs v-model="activeTab" type="border-card" style="font-size: 12px; min-height: 200px"> <!-- 申请单基本信息 --> <el-tab-pane label="申请单信息" name="basic"> <el-descriptions :column="2" border size="small"> <el-descriptions-item label="申请单号">{{ currentApply.applyNo }}</el-descriptions-item> <el-descriptions-item label="事业部">{{ currentApply.buNo }}</el-descriptions-item> <el-descriptions-item label="试验类型"> <el-tag :type="currentApply.experimentType === 'High Risk' ? 'danger' : 'success'"> {{ currentApply.experimentType }} </el-tag> </el-descriptions-item> <el-descriptions-item label="项目编号">{{ currentApply.projectNo }}</el-descriptions-item> <el-descriptions-item label="试验名称" :span="2">{{ currentApply.title }}</el-descriptions-item> <el-descriptions-item label="试验目的" :span="2">{{ currentApply.purpose }}</el-descriptions-item> <el-descriptions-item label="验证方法" :span="2">{{ currentApply.justification }}</el-descriptions-item> <el-descriptions-item label="产品型号">{{ currentApply.productType }}</el-descriptions-item> <el-descriptions-item label="申请数量">{{ currentApply.quantityReq }}</el-descriptions-item> <el-descriptions-item label="期望完成日期">{{ currentApply.expectedFinishDate }}</el-descriptions-item> <el-descriptions-item label="申请人">{{ currentApply.creatorName }}</el-descriptions-item> <el-descriptions-item label="申请时间" :span="2">{{ currentApply.createTime }}</el-descriptions-item> </el-descriptions> </el-tab-pane>
<!-- 审批历史 --> <el-tab-pane label="审批历史" name="history"> <approval-history v-if="activeTab === 'history' && currentApply.applyNo" :apply-no="currentApply.applyNo"> </approval-history> </el-tab-pane> </el-tabs>
<!-- 审批操作 --> <el-divider content-position="left">审批操作</el-divider> <el-form :model="approvalData" label-position="top" style="margin-left: 5px; margin-top: -5px;"> <el-row :gutter="20"> <el-col :span="24"> <el-form-item label="审批意见" required> <el-input v-model="approvalData.comment" type="textarea" :rows="2" placeholder="请输入审批意见"> </el-input> </el-form-item> </el-col> </el-row> </el-form>
<el-footer style="height: 40px; margin-top: 40px; text-align: center"> <el-button type="success" @click="approveApply" :loading="approvalLoading"> {{ approvalLoading ? '审批中...' : '批准' }} </el-button> <el-button type="danger" @click="rejectApply" :loading="approvalLoading"> {{ approvalLoading ? '驳回中...' : '驳回' }} </el-button> <el-button type="primary" @click="approvalDialogVisible = false" :disabled="approvalLoading">关闭</el-button> </el-footer> </el-dialog>
<!-- 驳回原因弹窗 --> <el-dialog title="驳回申请" :visible.sync="rejectDialogVisible" width="500px" :close-on-click-modal="false"> <el-form label-position="top"> <el-form-item label="驳回原因" required> <el-input v-model="rejectReason" type="textarea" :rows="3" placeholder="请输入驳回原因" maxlength="500" show-word-limit> </el-input> </el-form-item> </el-form> <div slot="footer" class="dialog-footer" style="height: 40px; margin-top: 50px; text-align: center"> <el-button @click="rejectDialogVisible = false">取消</el-button> <el-button type="danger" @click="confirmReject" :loading="approvalLoading"> {{ approvalLoading ? '驳回中...' : '确认驳回' }} </el-button> </div> </el-dialog>
<!-- 附件查看弹窗 --> <el-dialog :title="`附件列表 - ${currentAttachmentApplyNo}`" :visible.sync="attachmentDialogVisible" width="900px" :close-on-click-modal="false" append-to-body> <div v-loading="attachmentLoading"> <el-table :data="attachmentList" border class="approval-logs-table" stripe style="width: 100%" max-height="500px"> <el-table-column type="index" label="序号" width="60" align="center"> </el-table-column>
<el-table-column prop="fileName" label="文件名" min-width="200" align="left" header-align="center" show-overflow-tooltip> <template slot-scope="scope"> <i :class="getFileIcon(scope.row.fileType)" style="margin-right: 5px;"></i> {{ scope.row.fileName }} </template> </el-table-column>
<el-table-column prop="fileType" label="文件类型" width="100" align="center" header-align="center"> <template slot-scope="scope"> <el-tag size="small" type="info">{{ scope.row.fileType }}</el-tag> </template> </el-table-column>
<el-table-column prop="createdBy" label="上传人" width="100" align="center" header-align="center"> </el-table-column>
<el-table-column prop="createDate" label="上传时间" width="160" align="center" header-align="center"> </el-table-column>
<el-table-column label="操作" width="150" align="center" header-align="center"> <template slot-scope="scope"> <a type="text" size="small" icon="el-icon-view" @click="previewAttachment(scope.row)"> 预览 </a> <a type="text" size="small" icon="el-icon-download" @click="downloadAttachment(scope.row)"> 下载 </a> </template> </el-table-column> </el-table>
<!-- 空状态 --> <div v-if="!attachmentLoading && attachmentList.length === 0" class="empty-attachment"> <i class="el-icon-folder-opened" style="font-size: 48px; color: #c0c4cc;"></i> <p style="margin-top: 10px; color: #909399;">暂无附件</p> </div> </div>
<div slot="footer" class="dialog-footer"> <el-button type="primary" @click="attachmentDialogVisible = false">关闭</el-button> </div> </el-dialog> </div></template>
<script>import { getPendingApplyList, approveExpApply, getCurrentNodeCode } from '@/api/erf/erf'import { getBuList } from '@/api/factory/site'import { queryOss, previewOssFileById, previewOssFileById2 } from '@/api/oss/oss'import ApprovalHistory from './components/approvalHistory.vue'
export default { name: 'ExpApplyApproval',
components: { ApprovalHistory },
data() { return { buList: [], activeTab: 'basic', searchExpanded: ['0'], // 搜索条件默认展开
// 查询条件
queryHeaderData: { applyNo: '', buNo: '', experimentType: '', title: '', projectNo: '', productType: '', creatorName: '', createStartDate: '', createEndDate: '', currentUserId: this.$store.state.user.id, pendingStatus: '已下达', // 审批节点只查询已下达状态
page: 1, limit: 20 },
// 数据列表
dataList: [],
// 分页参数
pageIndex: 1, pageSize: 20, totalPage: 0, dataListLoading: false,
// 审批弹窗
approvalDialogVisible: false, currentApply: {}, approvalData: { applyNo: '', nodeCode: '', action: '', comment: '', operatorUserId: this.$store.state.user.id, operatorName: this.$store.state.user.name }, approvalLoading: false,
// 驳回弹窗
rejectDialogVisible: false, rejectReason: '', rejectApplyData: {}, // 暂存待驳回的申请单信息
// 附件查看弹窗
attachmentDialogVisible: false, attachmentLoading: false, attachmentList: [], currentAttachmentApplyNo: '' } },
computed: { /** * 计算高风险试验数量 */ highRiskCount() { return this.dataList.filter(item => item.experimentType === 'High Risk').length },
/** * 计算低风险试验数量 */ lowRiskCount() { return this.dataList.filter(item => item.experimentType === 'Low Risk').length } },
activated() { this.loadBuList() this.getDataList() },
methods: { /** * 加载事业部列表 */ loadBuList() { const tempData = { site: this.$store.state.user.site } getBuList(tempData).then(({data}) => { if (data.code === 0) { this.buList = data.row1 } }) },
/** * 获取待办列表 */ getDataList(flag) { if (flag === 'Y') { this.pageIndex = 1 }
this.queryHeaderData.page = this.pageIndex this.queryHeaderData.limit = this.pageSize this.queryHeaderData.currentUserId = this.$store.state.user.id this.queryHeaderData.pageType = 'MANAGER' // 经理审批页面,只查询经理审批节点
this.dataListLoading = true
getPendingApplyList(this.queryHeaderData).then(({data}) => { this.dataListLoading = false if (data && data.code === 0) { this.dataList = data.page.list || [] this.totalPage = data.page.totalCount || 0
// 为每个申请单查询附件数量
this.loadAttachmentCounts() } else { this.dataList = [] this.totalPage = 0 this.$message.error(data.msg || '查询失败') } }).catch(error => { this.dataListLoading = false this.$message.error('查询异常') }) },
/** * 批量加载附件数量 */ loadAttachmentCounts() { this.dataList.forEach(item => { const params = { orderRef1: 'ERF', orderRef2: item.applyNo, orderRef6: 'EXP_APPLY' }
queryOss(params).then(({data}) => { if (data && data.code === 0) { this.$set(item, 'attachmentCount', data.rows ? data.rows.length : 0) } }).catch(() => { this.$set(item, 'attachmentCount', 0) }) }) },
/** * 重置查询条件 */ resetQuery() { this.queryHeaderData.applyNo = '' this.queryHeaderData.buNo = '' this.queryHeaderData.experimentType = '' this.queryHeaderData.title = '' this.queryHeaderData.projectNo = '' this.queryHeaderData.productType = '' this.queryHeaderData.creatorName = '' this.queryHeaderData.createStartDate = '' this.queryHeaderData.createEndDate = '' this.getDataList('Y') },
/** * 打开审批对话框 */ openApprovalDialog(row) { this.currentApply = { ...row }
// 查询当前流程实例,获取正确的节点编码
this.loadCurrentNodeCode(row.applyNo) },
/** * 快速通过 - 不需要输入原因 */ quickApprove(row) { this.$confirm('确认通过该申请单?', '操作提示', { confirmButtonText: '确定通过', cancelButtonText: '取消', type: 'success' }).then(() => { // 获取当前节点编码并执行通过操作
getCurrentNodeCode({ applyNo: row.applyNo }).then(({data}) => { if (data && data.code === 0) { const approvalData = { applyNo: row.applyNo, nodeCode: data.nodeCode, action: '批准', comment: '批准通过', // 默认审批意见
operatorUserId: this.$store.state.user.id, operatorName: this.$store.state.user.name }
this.approvalLoading = true approveExpApply(approvalData).then(({data}) => { this.approvalLoading = false if (data && data.code === 0) { this.$message.success('审批成功') this.getDataList() } else { this.$message.error(data.msg || '审批失败') } }).catch(error => { this.approvalLoading = false this.$message.error('审批异常') }) } else { this.$message.error('获取节点信息失败') } }).catch(error => { this.$message.error('获取节点信息异常') }) }).catch(() => { // 取消操作
}) },
/** * 打开驳回弹框 */ openRejectDialog(row) { this.rejectApplyData = { ...row } this.rejectReason = '' this.rejectDialogVisible = true },
/** * 确认驳回 */ confirmReject() { if (!this.rejectReason || this.rejectReason.trim() === '') { this.$message.warning('请输入驳回原因') return }
// 获取当前节点编码并执行驳回操作
getCurrentNodeCode({ applyNo: this.rejectApplyData.applyNo }).then(({data}) => { if (data && data.code === 0) { const approvalData = { applyNo: this.rejectApplyData.applyNo, nodeCode: data.nodeCode, action: '驳回', comment: this.rejectReason, operatorUserId: this.$store.state.user.id, operatorName: this.$store.state.user.name }
this.approvalLoading = true approveExpApply(approvalData).then(({data}) => { this.approvalLoading = false if (data && data.code === 0) { this.$message.success('驳回成功') this.rejectDialogVisible = false this.getDataList() } else { this.$message.error(data.msg || '驳回失败') } }).catch(error => { this.approvalLoading = false this.$message.error('驳回异常') }) } else { this.$message.error('获取节点信息失败') } }).catch(error => { this.$message.error('获取节点信息异常') }) },
/** * 加载当前节点编码并打开审批弹窗 */ loadCurrentNodeCode(applyNo) { // 调用API获取流程实例的当前节点
getCurrentNodeCode({ applyNo: applyNo }).then(({data}) => { if (data && data.code === 0) { this.approvalData = { applyNo: applyNo, nodeCode: data.nodeCode, // 使用流程实例的当前节点编码
action: '', comment: '', operatorUserId: this.$store.state.user.id, operatorName: this.$store.state.user.name } this.approvalDialogVisible = true } else { this.$message.error('获取节点信息失败') } }).catch(error => { this.$message.error('获取节点信息异常') }) },
/** * 批准操作 */ approveApply() { if (!this.approvalData.comment) { this.$message.warning('请输入审批意见') return }
this.approvalData.action = '批准' this.doApproval() },
/** * 驳回操作 */ rejectApply() { if (!this.approvalData.comment) { this.$message.warning('请输入审批意见') return }
this.approvalData.action = '驳回' this.doApproval() },
/** * 执行审批 */ doApproval() { this.approvalLoading = true
approveExpApply(this.approvalData).then(({data}) => { this.approvalLoading = false if (data && data.code === 0) { this.$message.success('审批成功') this.approvalDialogVisible = false this.getDataList() } else { this.$message.error(data.msg || '审批失败') } }).catch(error => { this.approvalLoading = false this.$message.error('审批异常') }) },
/** * 分页大小改变 */ sizeChangeHandle(val) { this.pageSize = val this.pageIndex = 1 this.getDataList() },
/** * 当前页改变 */ currentChangeHandle(val) { this.pageIndex = val this.getDataList() },
/** * 打开附件查看弹窗(智能判断:单个附件直接预览,多个附件打开列表) */ openAttachmentDialog(row) { this.currentAttachmentApplyNo = row.applyNo
// 先查询附件列表
const params = { orderRef1: 'ERF', orderRef2: row.applyNo, orderRef6: 'EXP_APPLY' }
this.attachmentLoading = true queryOss(params).then(({data}) => { this.attachmentLoading = false if (data && data.code === 0) { const attachmentList = data.rows || []
if (attachmentList.length === 0) { // 没有附件
this.$message.warning('该申请单暂无附件') } else if (attachmentList.length === 1) { // 只有一个附件,直接预览
this.previewAttachment(attachmentList[0]) } else { // 多个附件,打开列表弹窗
this.attachmentList = attachmentList this.attachmentDialogVisible = true } } else { this.$message.warning(data.msg || '查询附件失败') } }) },
/** * 加载附件列表(用于弹窗内刷新) */ loadAttachments(applyNo) { const params = { orderRef1: 'ERF', orderRef2: applyNo, orderRef6: 'EXP_APPLY' }
this.attachmentLoading = true queryOss(params).then(({data}) => { this.attachmentLoading = false if (data && data.code === 0) { this.attachmentList = data.rows || [] } else { this.attachmentList = [] this.$message.warning(data.msg || '查询附件失败') } }).catch(error => { this.attachmentLoading = false this.$message.error('查询附件异常') }) },
/** * 获取文件图标 */ getFileIcon(fileType) { const type = fileType.toLowerCase()
// 图片类型
if (['jpg', 'jpeg', 'png', 'gif', 'bmp'].includes(type)) { return 'el-icon-picture-outline' }
// PDF类型
if (type === 'pdf') { return 'el-icon-document' }
// Word类型
if (['doc', 'docx'].includes(type)) { return 'el-icon-document' }
// Excel类型
if (['xls', 'xlsx'].includes(type)) { return 'el-icon-tickets' }
// CAD类型
if (['dwg', 'dxf'].includes(type)) { return 'el-icon-edit-outline' }
// 默认文件图标
return 'el-icon-document' },
/** * 预览附件 */ previewAttachment(row) { let type = '' let fileType = row.fileType.toLowerCase()
// 图片类型
let image = ['jpg', 'jpeg', 'png', 'gif', 'bmp'] if (image.includes(fileType)) { type = 'image/' + fileType }
// PDF类型
if (fileType === 'pdf') { type = 'application/pdf;charset-UTF-8' }
// Excel类型
if (fileType === 'xlsx' || fileType === 'xls') { type = 'excel' }
// Word类型
if (fileType === 'docx') { type = 'word' }
// Office文件不支持预览
if (fileType === 'doc' || fileType === 'ppt' || fileType === 'pptx') { this.$message.warning('该文件格式暂不支持预览,请下载后查看') return }
// CAD文件不支持预览
if (fileType === 'dwg' || fileType === 'dxf') { this.$message.warning('CAD文件暂不支持预览,请下载后使用CAD软件查看') return }
if (type === '') { this.$message.warning('该文件格式暂不支持预览') return }
let params = { id: row.id, fileType: type }
previewOssFileById2(params).then(({data}) => { if (type === 'excel' || type === 'word') { type = 'application/pdf;charset-UTF-8' } const blob = new Blob([data], { type: type }) const fileURL = URL.createObjectURL(blob)
// 在新标签页中打开文件预览
window.open(fileURL, '_blank') }).catch(error => { this.$message.error('预览失败') }) },
/** * 下载附件 */ downloadAttachment(row) { let params = { id: row.id }
previewOssFileById(params).then((response) => { const blob = new Blob([response.data], { type: response.headers['content-type'] }) const link = document.createElement('a') link.href = URL.createObjectURL(blob) link.setAttribute('download', row.fileName) link.target = '_blank' link.click() URL.revokeObjectURL(link.href) this.$message.success('下载成功') }).catch(error => { this.$message.error('下载失败') }) } }}</script>
<style scoped>/* 整体容器 */.approval-container { padding: 15px; background: #f5f7fa; min-height: calc(100vh - 80px);}
/* ===== 页面头部 ===== */.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding: 5px 20px; background: #FFFFFF; border-radius: 4px; border: 1px solid #EBEEF5;}
.header-left { color: #303133;}
.page-title { margin: 0; font-size: 20px; font-weight: 600; color: #303133; display: flex; align-items: center; gap: 10px;}
.page-title i { font-size: 22px; color: #409EFF;}
.page-subtitle { margin: 6px 0 0 0; font-size: 13px; color: #909399;}
.header-right { display: flex; gap: 12px;}
/* ===== 统计卡片 ===== */.stat-cards { display: flex; gap: 12px;}
.stat-card { display: flex; align-items: center; gap: 12px; padding: 12px 20px; background: #FFFFFF; border-radius: 4px; border: 1px solid #EBEEF5; transition: all 0.3s ease; cursor: pointer; min-width: 140px;}
.stat-card:hover { border-color: #409EFF; box-shadow: 0 2px 8px rgba(64, 158, 255, 0.15);}
.stat-icon { width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; border-radius: 4px; font-size: 20px; color: white;}
.stat-total .stat-icon { background: #409EFF;}
.stat-high-risk .stat-icon { background: #F56C6C;}
.stat-low-risk .stat-icon { background: #67C23A;}
.stat-content { display: flex; flex-direction: column;}
.stat-value { font-size: 24px; font-weight: 600; color: #303133; line-height: 1;}
.stat-label { font-size: 12px; color: #909399; margin-top: 4px;}
/* ===== 搜索区域 ===== */.search-section { margin-bottom: 15px; background: white; border-radius: 4px; border: 1px solid #EBEEF5; overflow: hidden;}
.search-section >>> .el-collapse-item__header { padding: 0 15px; height: 45px; line-height: 45px; background: white; border-bottom: 1px solid #ebeef5; font-size: 13px; color: #303133; font-weight: 500;}
.search-section >>> .el-collapse-item__content { padding: 15px; background: #FFFFFF;}
.search-form { margin: 0;}
.search-form >>> .el-form-item { margin-bottom: 10px;}
/* ===== 卡片容器 ===== */.cards-container { min-height: 400px; position: relative;}
.cards-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)); gap: 15px; margin-bottom: 15px;}
/* ===== 审批卡片 ===== */.approval-card { background: white; border-radius: 4px; padding: 18px; border: 1px solid #EBEEF5; transition: all 0.3s ease; cursor: pointer; position: relative;}
.approval-card::before { content: ''; position: absolute; top: 0; left: 0; width: 1px; height: 100%; background: #67C23A; transition: all 0.3s ease;}
.approval-card.high-risk::before { background: #F56C6C;}
.approval-card:hover { border-color: #409EFF; box-shadow: 0 4px 12px rgba(64, 158, 255, 0.15);}
/* 卡片头部 */.card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 14px; padding-bottom: 10px; border-bottom: 1px solid #EBEEF5;}
.card-title-area { display: flex; align-items: center; gap: 10px;}
.risk-tag { font-weight: 500; padding: 0px 10px; font-size: 14px;}
.risk-tag i { margin-right: 4px;}
.apply-no { font-size: 13px; color: #606266; font-family: 'Courier New', monospace; font-weight: 500;}
/* 附件徽章 */.attachment-badge { cursor: pointer;}
.attachment-btn { padding: 4px 12px; font-size: 12px; color: #909399; transition: all 0.3s ease;}
.attachment-btn:hover { color: #409EFF;}
.attachment-btn i { margin-right: 4px; font-size: 14px;}
/* 附件弹窗空状态 */.empty-attachment { text-align: center; padding: 60px 20px;}
/* 卡片主体 */.card-body { //margin-bottom: 14px;
}
.experiment-title { font-size: 15px; font-weight: 600; color: #303133; margin: 0 0 14px 0; line-height: 1.5; display: flex; align-items: flex-start; gap: 6px; //min-height: 45px;
}
.experiment-title i { color: #409EFF; margin-top: 2px; font-size: 16px;}
.card-details { display: flex; flex-direction: column; gap: 8px;}
.detail-row { display: flex; justify-content: space-between; align-items: center; font-size: 13px; padding: 6px 10px; background: #F5F7FA; border-radius: 3px; transition: all 0.2s ease;}
.detail-row:hover { background: #ECF5FF;}
.detail-label { color: #909399; display: flex; align-items: center; gap: 5px; font-weight: 500;}
.detail-label i { color: #409EFF; font-size: 14px;}
.detail-value { color: #606266; font-weight: 500; text-align: right; max-width: 250px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}
/* 卡片底部 */.card-footer { display: flex; justify-content: space-between; align-items: center; padding-top: 10px; border-top: 1px solid #EBEEF5;}
.current-step { font-size: 12px; color: #909399; display: flex; align-items: center; gap: 5px;}
.current-step i { color: #409EFF;}
.action-buttons { display: flex; gap: 8px;}
.approve-btn { background: #f0f9ff; border-color: #b3e19d; color: #67C23A; font-weight: 500; padding: 7px 16px; font-size: 13px; transition: all 0.3s ease;}
.approve-btn:hover { background: #67C23A; border-color: #67C23A; color: #FFFFFF;}
.reject-btn { background: #fef0f0; border-color: #fbc4c4; color: #F56C6C; font-weight: 500; padding: 7px 16px; font-size: 13px; transition: all 0.3s ease;}
.reject-btn:hover { background: #F56C6C; border-color: #F56C6C; color: #FFFFFF;}
/* ===== 空状态 ===== */.empty-state { text-align: center; padding: 80px 20px; background: white; border-radius: 4px; border: 1px solid #EBEEF5;}
.empty-icon { font-size: 64px; color: #c0c4cc; margin-bottom: 12px;}
.empty-text { font-size: 15px; color: #606266; margin: 0 0 6px 0; font-weight: 500;}
.empty-subtext { font-size: 13px; color: #909399; margin: 0;}
/* ===== 分页 ===== */.pagination-wrapper { display: flex; justify-content: center; padding: 15px; background: white; border-radius: 4px; border: 1px solid #EBEEF5;}
/* ===== 卡片动画 ===== */.card-list-enter-active { animation: cardFadeIn 0.4s ease;}
.card-list-leave-active { animation: cardFadeOut 0.3s ease;}
@keyframes cardFadeIn { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); }}
@keyframes cardFadeOut { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(0.95); }}
/* ===== 响应式设计 ===== */@media screen and (max-width: 1600px) { .cards-grid { grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); }}
@media screen and (max-width: 1200px) { .cards-grid { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }
.page-header { flex-direction: column; gap: 15px; align-items: flex-start; }
.stat-cards { width: 100%; overflow-x: auto; }}
@media screen and (max-width: 768px) { .cards-grid { grid-template-columns: 1fr; }
.stat-cards { flex-direction: column; }
.stat-card { width: 100%; }}
/* ===== 弹窗样式优化 ===== */.dialog-footer { text-align: right;}
/* 表头样式 - 深灰色背景,白色文字 */.approval-logs-table >>> .el-table__header-wrapper th,.approval-logs-table >>> .el-table__header-wrapper .el-table__cell { background-color: #F5F7FA !important; color: #606266 !important; font-size: 12px; font-weight: 600;}
/* 数据行样式 - 更高的行高防止遮挡 */.approval-logs-table >>> .el-table__body-wrapper .el-table__row { height: 40px !important;}
.approval-logs-table >>> .el-table__body-wrapper td,.approval-logs-table >>> .el-table__body-wrapper .el-table__cell { height: 40px !important; padding: 0 !important;}
/* 关键修复:cell容器要有足够的padding和overflow可见 */.approval-logs-table >>> .el-table__body-wrapper .cell { padding: 1px 12px !important; line-height: 24px !important; overflow: visible !important; height: auto !important;}
/* 标签样式 - 确保不被遮挡 */.approval-logs-table >>> .el-tag { vertical-align: middle !important; display: inline-block !important; margin: 2px 0 !important;}
/* 悬停效果 */.approval-logs-table >>> .el-table__body tr:hover > td { background-color: #f5f7fa !important;}
/deep/ .no-arrow .el-collapse-item__header .el-collapse-item__arrow { display: none !important;}</style>
|