|
|
<template> <div class="mod-config exp-apply-page" v-loading="sendLoading" element-loading-text="正在发送邮件,请稍候..." element-loading-spinner="el-icon-loading" element-loading-background="rgba(0, 0, 0, 0.6)"> <!-- 查询条件表单 --> <el-form :inline="true" label-position="top" class="query-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-input v-model="queryHeaderData.projectLeader" placeholder="支持模糊查询" clearable style="width: 120px"></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-select v-model="queryHeaderData.status" placeholder="请选择" clearable style="width: 120px"> <el-option label="全部" value=""></el-option> <el-option label="草稿" value="草稿"></el-option> <el-option label="已下达" value="已下达"></el-option> <el-option label="已批准" value="已批准"></el-option> <el-option label="生产中" value="生产中"></el-option> <el-option label="样品确认" value="样品确认"></el-option> <el-option label="已完成" value="已完成"></el-option> <el-option label="已取消" value="已取消"></el-option> </el-select> </el-form-item>
<el-form-item label="创建日期"> <el-date-picker v-model="queryHeaderData.createStartDate" type="date" format="yyyy-MM-dd" value-format="yyyy-MM-dd" placeholder="开始日期" style="width: 140px"> </el-date-picker> </el-form-item>
<el-form-item label="至"> <el-date-picker v-model="queryHeaderData.createEndDate" type="date" format="yyyy-MM-dd" value-format="yyyy-MM-dd" placeholder="结束日期" style="width: 140px"> </el-date-picker> </el-form-item>
<el-form-item label=" " style="margin-top: -11px"> <el-button @click="getDataList('Y')" type="primary" plain class="search-btn">查询</el-button> <el-button @click="resetQuery()" plain class="reset-btn">重置</el-button> </el-form-item>
<el-form-item label=" " style="margin-top: -11px"> <el-button @click="openCreateDialog()" type="success" v-if="isAuth('erf:apply:add')" plain class="add-btn">新增试验单</el-button> <el-button @click="deleteApplyFromToolbar()" :disabled="!currentRow.applyNo || currentRow.status !== '草稿'" type="danger" plain class="delete-btn">删除</el-button> <el-button @click="withdrawApplyFromToolbar()" :disabled="!currentRow.applyNo || currentRow.status === '草稿' || currentRow.status === '已完成' || currentRow.status === '已驳回' || currentRow.status === '已取消'" type="warning" plain class="withdraw-btn">撤回</el-button> <el-button @click="cancelApplyFromToolbar()" :disabled="!currentRow.applyNo || currentRow.status === '草稿' || currentRow.status === '已完成' || currentRow.status === '已取消'" type="info" plain class="cancel-btn">取消</el-button> <el-button @click="openCopyDialog()" :disabled="!currentRow.applyNo" type="primary" plain class="copy-btn">复制</el-button> <el-button @click="urgeApproval()" :disabled="!currentRow.applyNo || ['草稿', '已完成', '已取消', '已驳回'].includes(currentRow.status)" :loading="urgeLoading" type="warning" plain class="urge-btn">催办</el-button> </el-form-item> </el-form>
<!-- 数据表格 --> <el-table ref="dataTable" @row-click="handleRowClick" highlight-current-row :data="dataList" v-loading="dataListLoading" border class="data-table" style="width: 100%;" :height="tableHeight">
<el-table-column label="操作" width="150" align="center" header-align="center"> <template slot-scope="scope"> <a v-if="scope.row.status === '草稿' || scope.row.status === '已驳回'" @click="editApply(scope.row)">修改</a> <a v-if="scope.row.status === '草稿' || scope.row.status === '已驳回'" @click="submitApply(scope.row)">{{ scope.row.status === '已驳回' ? '重新下达' : '下达' }}</a> <a v-if="showSampleConfirmBtn(scope.row)" @click="openSampleConfirm(scope.row)">样品确认</a> </template> </el-table-column> <el-table-column prop="buDesc" label="事业部" width="70" align="center" header-align="center"> </el-table-column>
<el-table-column prop="applyNo" label="试验单号" width="130" align="center" header-align="center" show-overflow-tooltip> </el-table-column> <el-table-column prop="status" label="状态" width="80" align="center" header-align="center"> </el-table-column><!-- <el-table-column prop="currentStep" label="当前步骤" width="90" align="center" header-align="center"> </el-table-column>--> <el-table-column prop="experimentType" label="试验类型" width="100" align="center" show-overflow-tooltip header-align="center"> </el-table-column>
<el-table-column prop="title" label="试验名称" min-width="120" align="left" header-align="center" show-overflow-tooltip> </el-table-column>
<el-table-column prop="projectNo" label="项目编号" width="100" align="center" header-align="center" show-overflow-tooltip> </el-table-column> <el-table-column prop="umName" label="计量单位" min-width="80" align="left" header-align="center" show-overflow-tooltip> </el-table-column><!-- <el-table-column prop="productType" label="产品型号" min-width="220" align="center" header-align="center" show-overflow-tooltip> </el-table-column>-->
<el-table-column prop="expectedFinishDate" label="期望完成日期" width="120" align="center" header-align="center"> </el-table-column>
<el-table-column prop="actualFinishDate" label="最终完成日期" width="120" align="center" header-align="center"> </el-table-column> <el-table-column prop="finalQuantity" label="入库数量" width="80" align="center" header-align="center"> </el-table-column> <el-table-column prop="projectLeader" label="试验负责人" width="100" align="center" header-align="center"> </el-table-column>
<el-table-column prop="createTime" label="创建时间" min-width="100" align="center" show-overflow-tooltip header-align="center"> </el-table-column> </el-table>
<!-- 分页组件 --> <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" style="margin-top: 10px; text-align: right;"> </el-pagination>
<!-- 下方Tab区域 --> <el-tabs class="customer-tab" v-model="activeName" type="border-card" style="margin-top: 10px; min-height: 280px;" @tab-click="handleTabClick">
<!-- Tab 1: 项目详情 --><!-- <el-tab-pane :label="getProjectDetailLabel()" name="projectDetail"> <exp-project-detail v-if="currentRow.applyNo" ref="projectDetail" :apply-no="currentRow.applyNo" :status="currentRow.status" :height="detailHeight" @refresh="handleDetailRefresh"> </exp-project-detail> <div v-else class="empty-tip"> 请在上方表格中选择一条试验单记录 </div> </el-tab-pane>--> <el-tab-pane label="附件上传" name="attachment"> <erf-attachment-manager v-if="currentRow.applyNo" ref="attachmentManager" :apply-no="currentRow.applyNo" :created-by="currentRow.projectLeader" :disabled="false" :height="detailHeight"> </erf-attachment-manager> <div v-else class="empty-tip"> 请在上方表格中选择一条试验单记录 </div> </el-tab-pane> <!-- Tab: 三方确认 --> <el-tab-pane label="三方确认" v-if="currentRow.experimentType==='High Risk'" name="triConfirm"> <exp-tri-confirm v-if="currentRow.applyNo&¤tRow.experimentType==='High Risk'" ref="triConfirm" :apply-no="currentRow.applyNo" :bu-no="currentRow.buNo" :experiment-type="currentRow.experimentType" :height="detailHeight"> </exp-tri-confirm> <div v-else class="empty-tip"> 请在上方表格中选择一条试验单记录 </div> </el-tab-pane>
<!-- Tab: 原材料清单 --> <el-tab-pane label="原材料清单" name="rawMaterial"> <exp-raw-material-list v-if="currentRow.applyNo" ref="rawMaterialList" :apply-no="currentRow.applyNo" :site="currentRow.site || $store.state.user.site" :buNo="currentRow.buNo" :project-leader="currentRow.projectLeader" :project-leader-name="currentRow.projectLeaderName" :disabled="currentRow.status === '已完成' || currentRow.status === '已取消'" :height="detailHeight"> </exp-raw-material-list> <div v-else class="empty-tip"> <i class="el-icon-document" style="font-size: 40px; color: #C0C4CC; margin-bottom: 10px"></i> <p style="font-size: 13px">请选择试验单查看原材料清单</p> </div> </el-tab-pane>
<!-- Tab: 审批状态和日志 --> <el-tab-pane label="审批状态和日志" name="approvalStatus"> <div v-if="currentRow.applyNo" :style="{height: detailHeight + 'px', overflowY: 'auto', padding: '0px 10px'}">
<!-- 两栏布局:审批流程 | 审批日志 --> <div class="two-column-layout">
<!-- 左栏:审批流程 --> <div class="stages-column"> <div class="column-header"> <i class="el-icon-s-order"></i> <span>审批流程</span> <span class="progress-badge">{{ flowStatus.progressPercent || 0 }}%</span> </div> <div class="stages-list"> <div v-for="(stage, index) in flowStatus.stages" :key="index" class="stage-item" :class="'stage-' + stage.status"> <div class="stage-icon"> <i :class="getStageIcon(stage.status)"></i> </div> <div class="stage-content"> <div class="stage-name">{{ stage.nodeName }}</div> <div class="stage-meta"> <el-tag :type="getStageTagType(stage.status)" size="mini" effect="plain"> {{ getStageStatusText(stage.status) }} </el-tag> <span v-if="stage.completeTime" class="stage-time">{{ stage.completeTime }}</span> </div> </div> </div> </div> </div>
<!-- 右栏:审批日志(表格) --> <div class="logs-column"> <div class="column-header"> <i class="el-icon-tickets"></i> <span>审批日志</span> <span class="logs-count">{{ flowStatus.approvalLogs ? flowStatus.approvalLogs.length : 0 }}条</span> </div> <div class="logs-table-wrapper"> <el-table :data="flowStatus.approvalLogs" size="small" class="approval-logs-table" style="width: 100%" height="34vh">
<el-table-column prop="logTime" label="时间" width="150" align="center"> <template slot-scope="scope"> <span style="font-size: 12px">{{ formatDateTime(scope.row.logTime) }}</span> </template> </el-table-column>
<el-table-column prop="action" label="操作" width="90" align="center"> <template slot-scope="scope"> <el-tag :type="getActionTagType(scope.row.action)" size="mini"> {{ scope.row.action }} </el-tag> </template> </el-table-column>
<el-table-column prop="nodeCode" label="节点" width="150" align="center"> <template slot-scope="scope"> <span style="font-size: 12px">{{ scope.row.nodeCode }}</span> </template> </el-table-column>
<el-table-column prop="operatorName" label="操作人" width="100" align="center"> <template slot-scope="scope"> <span style="font-size: 12px">{{ scope.row.operatorName }}</span> </template> </el-table-column>
<el-table-column prop="comment" label="备注" min-width="180" show-overflow-tooltip> <template slot-scope="scope"> <span style="font-size: 12px; color: #606266">{{ scope.row.comment || '-' }}</span> </template> </el-table-column> </el-table> </div> </div>
</div> </div> <div v-else class="empty-tip"> <i class="el-icon-document" style="font-size: 40px; color: #C0C4CC; margin-bottom: 10px"></i> <p style="font-size: 13px">请选择试验单查看审批状态</p> </div> </el-tab-pane>
<!-- Tab: 备注 --> <el-tab-pane label="备注" name="remark"> <div v-if="currentRow.applyNo" style="padding: 16px;"> <el-input type="textarea" v-model="remarkText" :rows="10" placeholder="请输入备注内容" style="width: 100%" ></el-input> <div style="margin-top: 12px; text-align: left;"> <el-button type="primary" size="small" :loading="remarkSaving" @click="saveRemarkHandle">保存备注</el-button> </div> </div> <div v-else class="empty-tip"> <i class="el-icon-edit-outline" style="font-size: 40px; color: #C0C4CC; margin-bottom: 10px"></i> <p style="font-size: 13px">请选择试验单填写备注</p> </div> </el-tab-pane>
</el-tabs>
<!-- 新增/编辑弹窗 --> <el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="550px" :close-on-click-modal="false" v-drag>
<exp-apply-form v-if="dialogVisible" ref="applyForm" :apply-data="currentApply" :readonly="dialogReadonly"> </exp-apply-form>
<el-footer style="height: 40px; margin-top: 50px; text-align: center"> <el-button type="primary" @click="saveApply" v-if="!dialogReadonly" :loading="saveLoading"> {{ saveLoading ? '保存中...' : '保存' }} </el-button> <el-button type="primary" @click="dialogVisible = false">关闭</el-button> </el-footer> </el-dialog>
<!-- 样品确认弹窗 --> <el-dialog title="样品确认" :visible.sync="sampleConfirmVisible" width="320px" :close-on-click-modal="false"> <div style="margin-top: 1px;margin-left: 10px; color: #909399; font-size: 12px"> 不输入或输入0表示样品报废,大于0表示正常入库 </div> <el-form :model="sampleConfirmData" label-width="100px" size="small"> <el-form-item label="试验单号"> <el-tag type="primary">{{ sampleConfirmData.applyNo }}</el-tag> </el-form-item>
<el-form-item label="样品数量" required> <el-input v-model="sampleConfirmData.sampleQuantity" :min="0" :precision="0" placeholder="请输入样品数量" style="width: 80%"> </el-input> </el-form-item> <el-form-item required> <span style="cursor: pointer" slot="label" @click="getBaseList(510)"><a herf="#">计量单位</a></span> <el-input v-model="sampleConfirmData.umid" style="width: 128px"></el-input> </el-form-item> <el-form-item label="完成日期" required> <el-date-picker v-model="sampleConfirmData.finalFinishDate" type="date" format="yyyy-MM-dd" value-format="yyyy-MM-dd" placeholder="请选择最终完成日期" style="width: 80%"> </el-date-picker> </el-form-item>
<el-form-item label="样品状态"> <el-tag :type="getSampleStatusTagType()"> {{ getSampleStatusText() }} </el-tag> </el-form-item> </el-form>
<div slot="footer" class="dialog-footer"> <el-button type="primary" @click="confirmSampleSubmit" :loading="sampleConfirmLoading"> {{ sampleConfirmLoading ? '确认中...' : '确认' }} </el-button> <el-button @click="sampleConfirmVisible = false">关闭</el-button> </div> </el-dialog>
<!-- 复制试验单弹窗 --> <el-dialog title="复制试验单" :visible.sync="copyDialogVisible" width="400px" :close-on-click-modal="false"> <div style="margin-bottom: 15px;margin-left: 40px; color: #606266; font-size: 13px"> <i class="el-icon-info" style="color: #409EFF"></i> 选择需要复制的内容,将创建一个新的草稿试验单 </div> <el-form :model="copyData" label-width="120px" size="small"> <el-form-item label="源试验单号"> <el-tag type="primary">{{ copyData.sourceApplyNo }}</el-tag> </el-form-item> <el-form-item label="试验单号"> <el-input v-model="copyData.applyNo" placeholder="请输入试验单号"></el-input> </el-form-item>
<el-form-item label="复制选项"> <el-checkbox-group v-model="copyData.copyOptions" class="checkbox-vertical"> <el-checkbox v-if="currentRow.experimentType === 'High Risk'" label="triConfirm">三方确认信息</el-checkbox> <el-checkbox label="rawMaterialList">原材料清单</el-checkbox> <el-checkbox label="attachment">附件</el-checkbox> </el-checkbox-group>
</el-form-item> </el-form>
<div slot="footer" class="dialog-footer"> <el-button type="primary" @click="confirmCopy" :loading="copyLoading"> {{ copyLoading ? '复制中...' : '确认复制' }} </el-button> <el-button @click="copyDialogVisible = false">取消</el-button> </div> </el-dialog>
<!-- 下达确认弹窗 - 选择审批人 --> <el-dialog title="确认审批人信息" :visible.sync="submitDialogVisible" width="500px" :close-on-click-modal="false">
<el-form :model="submitData" label-width="80px" size="small"> <!-- 技术经理 --> <el-form-item label="技术经理"> <el-tag type="success" size="medium"> {{ submitData.techManagerName }} </el-tag> <span style="margin-left: 10px; color: #909399; font-size: 12px"> (根据发起人角色自动分配) </span> </el-form-item>
<!-- 生产经理 --> <el-form-item label="生产经理" required> <el-select class="manager-select" v-model="submitData.prodManagerIds" multiple placeholder="请选择生产经理" style="width: 100%"> <el-option v-for="manager in prodManagerList" :key="manager.userId" :label="manager.userDisplay || manager.username" :value="manager.userId"> </el-option> </el-select> <div style="margin-top: 1px; color: #909399; font-size: 12px"> 可多选,所有选中的生产经理都必须审批通过后才会流转到计划员排产 </div> </el-form-item>
<!-- 质量经理 --> <el-form-item label="质量经理" required> <el-select class="manager-select" v-model="submitData.qualityManagerIds" multiple placeholder="请选择质量经理" style="width: 100%"> <el-option v-for="manager in qualityManagerList" :key="manager.userId" :label="manager.userDisplay || manager.username" :value="manager.userId"> </el-option> </el-select> <div style="margin-top: 1px; color: #909399; font-size: 12px"> 可多选,所有选中的质量经理都必须审批通过后才会流转到计划员排产 </div> </el-form-item>
<!-- 计划员 --> <el-form-item label="计划员" required> <el-select class="manager-select" v-model="submitData.plannerIds" placeholder="请选择计划员(可多选)" style="width: 100%" multiple clearable @remove-tag="handlePlannerRemoveTag"> <el-option v-for="planner in plannerList" :key="planner.userId" :label="planner.userDisplay || planner.username" :value="planner.userId"> </el-option> </el-select> <div style="margin-top: 1px; color: #909399; font-size: 12px"> 经理审批全部通过后,任意选定的计划员均可排产 </div> </el-form-item> </el-form>
<div slot="footer" class="dialog-footer"> <el-button @click="submitDialogVisible = false">取消</el-button> <el-button type="primary" @click="confirmSubmit" :loading="submitLoading"> {{ submitLoading ? '下达中...' : '确认下达' }} </el-button> </div> </el-dialog> <!-- chooseList模态框 --> <Chooselist ref="baseList" @getBaseData="getBaseData"></Chooselist> </div></template>
<script>import { searchExpApplyList, submitExpApply, deleteExpApply, withdrawExpApply, cancelExpApply, copyExpApply, getSubmitApprovers, getFlowStatus, getTriConfirmList, confirmSample, saveRemark, urgeApproval } from '@/api/erf/erf'import { getBuList } from '@/api/factory/site'import { queryOss } from '@/api/oss/oss'import ExpApplyForm from './components/expApplyForm.vue'import ExpProjectDetail from './components/expProjectDetail.vue'import ExpTriConfirm from './components/expTriConfirm.vue'import ErfAttachmentManager from './components/erfAttachmentManager.vue'import ExpRawMaterialList from './components/expRawMaterialList.vue'import Chooselist from '@/views/modules/common/Chooselist_eam'export default { name: 'ExpApplyList',
components: { Chooselist, ExpApplyForm, ExpProjectDetail, ExpTriConfirm, ErfAttachmentManager, ExpRawMaterialList },
data() { return { tagNo:'', sendLoading: false, // 发送邮件的加载状态
buList: [], // 查询条件
queryHeaderData: { applyNo: '', buNo: '', experimentType: '', status: '', creatorName: '', createStartDate: '', createEndDate: '', page: 1, limit: 20 },
// 数据列表
dataList: [],
// 分页参数
pageIndex: 1, pageSize: 20, totalPage: 0, dataListLoading: false,
// 弹窗相关
dialogVisible: false, dialogTitle: '新增试验单', dialogReadonly: false, currentApply: {}, saveLoading: false,
// 当前选中行
currentRow: {},
// 备注
remarkText: '', remarkSaving: false,
// Tab页签
activeName: 'attachment',
// 表格高度
tableHeight: (window.innerHeight - 260)/2, detailHeight: '35vh',
// 下达确认弹窗
submitDialogVisible: false, submitLoading: false, submitData: { applyNo: '', buNo: '', techManagerId: null, techManagerName: '', prodManagerIds: [], qualityManagerIds: [], plannerIds: [] }, prodManagerList: [], // 生产经理候选列表
qualityManagerList: [], // 质量经理候选列表
plannerList: [], // 计划员候选列表
lockedPlannerUserId: null, // 默认计划员(郑宇),不可移除
// 流程状态数据
flowStatus: { applyNo: '', currentStatus: '', currentStep: '', progressPercent: 0, stages: [], approvalLogs: [] },
// 样品确认弹窗
sampleConfirmVisible: false, sampleConfirmLoading: false, sampleConfirmData: { applyNo: '', sampleQuantity: null, finalFinishDate: '', umid:'' },
// 复制试验单弹窗
copyDialogVisible: false, copyLoading: false, copyData: { sourceApplyNo: '', applyNo: '', copyOptions: ['triConfirm','rawMaterialList'] // 默认全选
},
// 催办
urgeLoading: false } },
activated() { this.loadBuList() this.getDataList() },
methods: { // ======== chooseList相关方法 ========
/** * 获取基础数据列表S * @param val * @param type */ getBaseList (val, type) { this.tagNo = val this.$nextTick(() => { let strVal = '' let conSql = '' if (val === 510) { strVal = this.sampleConfirmData.umid?this.sampleConfirmData.umid:'' conSql = " and site = '" + (this.currentRow.buNo[1]?this.currentRow.buNo[1]:'1') + "'"
} this.$refs.baseList.init(val, strVal, conSql) }) }, /** * 列表方法的回调 * @param val */ getBaseData (val) { if (this.tagNo === 510) { this.sampleConfirmData.umid = val.UMID
} }, /** * 加载事业部列表 */ 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.dataListLoading = true
searchExpApplyList(this.queryHeaderData).then(({data}) => { this.dataListLoading = false if (data && data.code === 0) { this.dataList = data.page.list || [] this.totalPage = data.page.totalCount || 0
// 如果有数据,处理行选中
if (this.dataList.length > 0) { // 尝试找到之前选中的行
if (this.currentRow.applyNo) { const selectedRow = this.dataList.find(item => item.applyNo === this.currentRow.applyNo) if (selectedRow) { // 如果找到之前选中的行,重新高亮
this.$nextTick(() => { this.handleRowClick(selectedRow) }) } else { // 如果没找到,选中第一行
this.handleRowClick(this.dataList[0]) } } else { // 如果之前没有选中任何行,默认选中第一行
this.handleRowClick(this.dataList[0]) } } else { // 没有数据,清空当前行
this.currentRow = {} } } else { this.dataList = [] this.totalPage = 0 this.currentRow = {} this.$message.error(data.msg || '查询失败') } }).catch(error => { this.dataListLoading = false this.$message.error('查询异常') }) },
/** * 重置查询条件 */ resetQuery() { this.queryHeaderData = { applyNo: '', buNo: '', experimentType: '', status: '', creatorName: '', createStartDate: '', createEndDate: '', page: 1, limit: 20 } this.getDataList('Y') },
/** * 打开新增对话框 */ openCreateDialog() { this.dialogTitle = '新增试验单' this.dialogReadonly = false this.currentApply = {} this.dialogVisible = true },
/** * 查看详情 */ viewDetail(row) { this.dialogTitle = '查看试验单' this.dialogReadonly = true this.currentApply = { ...row } this.dialogVisible = true },
/** * 编辑试验单 */ editApply(row) { this.dialogTitle = '修改试验单' this.dialogReadonly = false this.currentApply = { ...row } this.dialogVisible = true },
/** * 保存试验单 */ saveApply() { const formData = this.$refs.applyForm.getFormData() if (!formData) { return }
this.saveLoading = true this.$refs.applyForm.save().then(() => { this.saveLoading = false this.$message.success('保存成功') this.dialogVisible = false this.getDataList() }).catch(error => { this.saveLoading = false }) },
/** * 下达试验单 - 打开审批人确认弹窗 */ submitApply(row) { // 第一步:所有试验单都必须验证附件
console.log('🔍 开始验证附件...') this.validateAttachmentByApi(row.applyNo).then(hasAttachment => { if (!hasAttachment) { this.$alert('下达前必须至少上传一个附件', '操作提示', { confirmButtonText: '确定', type: 'warning' }) return }
// 第二步:如果是High Risk,继续验证工序
if (row.experimentType === 'High Risk') { this.validateHighRiskProcessByApi(row.applyNo).then(isValid => { if (!isValid) { this.$alert('High Risk试验单必须至少有一条完整的工序(包含车间、质量、技术三个负责人)才能下达', '操作提示', { confirmButtonText: '确定', type: 'warning' }) return }
this.proceedSubmit(row) }).catch(error => { this.$message.error('验证工序失败,请重试') }) } else { // Low Risk 附件验证通过后直接下达
this.proceedSubmit(row) } }).catch(error => { this.$message.error('验证附件失败,请重试') }) },
/** * 通过API验证试验单是否已上传附件 * * @param {String} applyNo 试验单号 * @return {Promise<Boolean>} 验证结果 */ validateAttachmentByApi(applyNo) { return new Promise((resolve, reject) => { queryOss({ orderRef1: 'ERF', orderRef2: applyNo, orderRef6: 'EXP_APPLY' }).then(({data}) => { if (data && data.code === 0) { const fileList = data.rows || [] console.log('📎 查询到附件数量:', fileList.length) resolve(fileList.length > 0) } else { reject(new Error(data.msg || '查询附件列表失败')) } }).catch(error => { reject(error) }) }) },
/** * 通过API验证High Risk工序 * * @param {String} applyNo 试验单号 * @return {Promise<Boolean>} 验证结果 */ validateHighRiskProcessByApi(applyNo) { return new Promise((resolve, reject) => { // 调用API查询工序列表
getTriConfirmList({ applyNo: applyNo }).then(({data}) => { if (data && data.code === 0) { const processList = data.list || [] console.log('📊 查询到工序数量:', processList.length)
// 检查是否至少有一条完整的工序
const completeProcesses = processList.filter(process => { const isComplete = process.prodApproverName && process.qaApproverName && process.techApproverName
if (isComplete) { console.log('✅ 完整工序:', process.processStep, { 车间: process.prodApproverName, 质量: process.qaApproverName, 技术: process.techApproverName }) }
return isComplete })
console.log(`📈 完整工序数量: ${completeProcesses.length}/${processList.length}`)
// 至少需要一条完整的工序
resolve(completeProcesses.length > 0) } else { reject(new Error(data.msg || '查询工序列表失败')) } }).catch(error => { reject(error) }) }) },
/** * 执行下达流程 */ proceedSubmit(row) { // 一次性获取所有审批人信息
getSubmitApprovers({ userId: this.$store.state.user.id, buNo: row.buNo }).then(({data}) => { if (data && data.code === 0) { // 设置生产经理、质量经理、计划员列表
this.prodManagerList = data.prodManagers || [] this.qualityManagerList = data.qualityManagers || [] this.plannerList = data.planners || [] // 记录默认计划员郑宇的 userId,用于锁定不可移除
const joyce = this.plannerList.find(p => p.userDisplay && p.userDisplay.includes('郑宇')) this.lockedPlannerUserId = joyce ? joyce.userId : null // 根据事业部设置默认生产经理(使用user_display模糊匹配)
const defaultProdManagerIds = [] if (row.buDesc === 'RFID') { // RFID默认Tony
const tony = this.prodManagerList.find(m => m.userDisplay && m.userDisplay.toLowerCase().includes('tony') ) if (tony) defaultProdManagerIds.push(tony.userId) } else if (row.buDesc === 'RF') { // RF默认Charles
const charles = this.prodManagerList.find(m => m.userDisplay && m.userDisplay.toLowerCase().includes('charles') ) if (charles) defaultProdManagerIds.push(charles.userId) }
// 根据事业部设置默认质量经理(使用user_display模糊匹配)
const defaultQualityManagerIds = [] if (row.buDesc === 'RFID') { // RFID默认Victor
const victor = this.qualityManagerList.find(m => m.userDisplay && m.userDisplay.toLowerCase().includes('victor') ) if (victor) defaultQualityManagerIds.push(victor.userId) } else if (row.buDesc === 'RF') { // RF默认尹君
const yin = this.qualityManagerList.find(m => m.userDisplay && (m.userDisplay.includes('尹君') || m.userDisplay.toLowerCase().includes('yin')) ) if (yin) defaultQualityManagerIds.push(yin.userId) }
// 设置表单数据
this.submitData = { applyNo: row.applyNo, buNo: row.buNo, techManagerId: data.techManager.managerId, techManagerName: data.techManager.managerName, prodManagerIds: defaultProdManagerIds, qualityManagerIds: defaultQualityManagerIds, plannerIds: (() => { // 默认选中Joyce(中文名郑宇);若无郑宇则单人列表时默认全选
const joyce = this.plannerList.find(p => p.userDisplay && p.userDisplay.toLowerCase().includes('郑宇') ) if (joyce) return [joyce.userId] return this.plannerList.length === 1 ? [this.plannerList[0].userId] : [] })() }
// 显示弹窗
this.submitDialogVisible = true } else { this.$message.error(data.msg || '获取审批人信息失败') } }).catch(() => { this.$message.error('获取审批人信息异常') }) },
/** * 计划员移除标签拦截:默认计划员,不允许被移除 */ handlePlannerRemoveTag(removedId) { if (this.lockedPlannerUserId && removedId === this.lockedPlannerUserId) { this.$nextTick(() => { if (!this.submitData.plannerIds.includes(this.lockedPlannerUserId)) { this.submitData.plannerIds = [this.lockedPlannerUserId, ...this.submitData.plannerIds] } }) this.$message.warning('该计划员不可移除') } },
/** * 确认下达 */ confirmSubmit() { // 验证必填项
if (!this.submitData.techManagerId) { this.$message.warning('技术经理未分配') return } if (!this.submitData.prodManagerIds || this.submitData.prodManagerIds.length === 0) { this.$message.warning('请至少选择一位生产经理') return } if (!this.submitData.qualityManagerIds || this.submitData.qualityManagerIds.length === 0) { this.$message.warning('请至少选择一位质量经理') return } if (!this.submitData.plannerIds || this.submitData.plannerIds.length === 0) { this.$message.warning('请至少选择一位计划员') return }
this.submitLoading = true
// 调用下达API
submitExpApply({ applyNo: this.submitData.applyNo, techManagerId: this.submitData.techManagerId, prodManagerIds: this.submitData.prodManagerIds, qualityManagerIds: this.submitData.qualityManagerIds, plannerUserIds: this.submitData.plannerIds }).then(({data}) => { this.submitLoading = false if (data && data.code === 0) { this.$message.success('下达成功,已通知相关审批人') this.submitDialogVisible = false this.getDataList()
// 触发全局审批通知检查(通知审批人)
this.triggerApprovalNotification() } else { this.$message.error(data.msg || '下达失败') } }).catch(() => { this.submitLoading = false }) },
/** * 触发全局审批通知检查 * 在试验单下达成功后调用,立即通知审批人 */ triggerApprovalNotification() { try { // 通过 $root 访问 App.vue 的方法
if (this.$root && this.$root.$children && this.$root.$children[0]) { const app = this.$root.$children[0] if (app.checkApprovalNotifications) { // 延迟1秒后检查,确保后端数据已更新
setTimeout(() => { //app.checkApprovalNotifications()
console.log('[审批通知] 已触发手动检查') }, 1000) } } } catch (error) { console.log('[审批通知] 触发通知检查失败:', error.message) } },
/** * 删除试验单 */ deleteApply(row) { this.$confirm('确定删除该试验单?', '操作提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { deleteExpApply({ applyNo: row.applyNo }).then(({data}) => { if (data && data.code === 0) { this.$message.success('删除成功') this.getDataList() } else { this.$message.error(data.msg || '删除失败') } }) }) },
/** * 撤回试验单 */ withdrawApply(row) { this.$confirm('确定撤回该试验单?撤回后将变为草稿状态', '操作提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { withdrawExpApply({ applyNo: row.applyNo, currentUserId: this.$store.state.user.id }).then(({data}) => { if (data && data.code === 0) { this.$message.success('撤回成功') this.getDataList() } else { this.$message.error(data.msg || '撤回失败') } }) }) },
/** * 获取状态类型 */ getStatusType(status) { const types = { '草稿': 'info', '已下达': 'warning', '已批准': 'success', '生产中': '', '已完成': 'success', '已取消': 'danger', '已驳回': 'danger' } return types[status] || 'info' },
/** * 获取状态文本(直接返回中文状态) */ getStatusText(status) { return status || '' },
/** * 分页大小改变 */ sizeChangeHandle(val) { this.pageSize = val this.pageIndex = 1 this.getDataList() },
/** * 当前页改变 */ currentChangeHandle(val) { this.pageIndex = val this.getDataList() },
/** * 保存备注 */ saveRemarkHandle() { if (!this.currentRow.applyNo) { this.$message.warning('请先选择试验单') return } this.remarkSaving = true saveRemark({ applyNo: this.currentRow.applyNo, remark: this.remarkText }).then(({data}) => { this.remarkSaving = false if (data && data.code === 0) { this.$message.success('备注保存成功') this.getDataList('Y') //this.currentRow.remark = this.remarkText
} else { this.$message.error(data.msg || '保存失败') } }).catch(() => { this.remarkSaving = false this.$message.error('保存异常') }) },
/** * 行点击事件 */ handleRowClick(row) { this.currentRow = JSON.parse(JSON.stringify(row)) this.remarkText = row.remark || '' // 设置表格当前行高亮
this.$nextTick(() => { this.$refs.dataTable.setCurrentRow(row) })
// 根据当前tab刷新对应的数据,切换行后保持在当前tab
if (this.activeName === 'approvalStatus') { this.loadFlowStatus() } else if (this.activeName === 'triConfirm') { this.$nextTick(() => { if (this.$refs.triConfirm && this.$refs.triConfirm.loadProcessList) { this.$refs.triConfirm.loadProcessList() } }) } },
/** * Tab切换事件 */ handleTabClick(tab) { // Tab切换时可以做一些特殊处理
console.log('当前Tab:', tab.name)
// 切换到审批状态tab时加载流程状态
if (tab.name === 'approvalStatus' && this.currentRow.applyNo) { this.loadFlowStatus() }
// 切换到三方确认tab时刷新数据
if (tab.name === 'triConfirm' && this.currentRow.applyNo) { this.$nextTick(() => { if (this.$refs.triConfirm && this.$refs.triConfirm.loadProcessList) { console.log('🔄 Tab切换,刷新三方确认数据') this.$refs.triConfirm.loadProcessList() } }) } },
/** * 项目详情刷新事件 */ handleDetailRefresh() { // 刷新当前行数据
this.getDataList() },
/** * 获取项目详情Tab标签 */ getProjectDetailLabel() { if (this.currentRow.applyNo) { return `项目详情 [${this.currentRow.applyNo}]` } return '项目详情' },
/** * 加载流程状态 */ loadFlowStatus() { if (!this.currentRow.applyNo) { return }
getFlowStatus({ applyNo: this.currentRow.applyNo }).then(({data}) => { if (data && data.code === 0) { this.flowStatus = data.data || { stages: [], approvalLogs: [] } } }) },
/** * 获取阶段颜色 */ getStageColor(status) { const colors = { 'pending': '#C0C4CC', 'current': '#409EFF', 'completed': '#67C23A', 'rejected': '#F56C6C' } return colors[status] || '#C0C4CC' },
/** * 获取阶段图标 */ getStageIcon(status) { const icons = { 'pending': 'el-icon-time', 'current': 'el-icon-loading', 'completed': 'el-icon-success', 'rejected': 'el-icon-error' } return icons[status] || 'el-icon-time' },
/** * 获取阶段标签类型 */ getStageTagType(status) { const types = { 'pending': 'info', 'current': '', 'completed': 'success', 'rejected': 'danger' } return types[status] || 'info' },
/** * 获取阶段状态文本 */ getStageStatusText(status) { const texts = { 'pending': '未开始', 'current': '进行中', 'completed': '已完成', 'rejected': '已驳回' } return texts[status] || '未知' },
/** * 获取操作类型标签 */ getActionTagType(action) { const types = { '下达': 'primary', '批准': 'success', '确认': 'success', '驳回': 'danger', '撤回': 'warning', '提醒': 'info', '已排产': 'success', '录入最终结果': 'success', '样品确认': 'success', } return types[action] || 'info' },
/** * 格式化日期时间 */ formatDateTime(dateTime) { if (!dateTime) { return '' }
if (typeof dateTime === 'string') { return dateTime }
const date = new Date(dateTime) const year = date.getFullYear() const month = String(date.getMonth() + 1).padStart(2, '0') const day = String(date.getDate()).padStart(2, '0') const hours = String(date.getHours()).padStart(2, '0') const minutes = String(date.getMinutes()).padStart(2, '0') const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}` },
/** * 判断是否显示样品确认按钮 * Low Risk: 计划员排产完成后(currentStep="已完成"且status="生产中")显示 * High Risk: 三方确认阶段(currentStep="三方确认"且status="生产中")显示 */ showSampleConfirmBtn(row) { if (row.experimentType === 'Low Risk') { return row.currentStep === '样品确认' } else if (row.experimentType === 'High Risk') { return row.currentStep === '样品确认' } return false },
/** * 打开样品确认对话框 */ openSampleConfirm(row) { this.sampleConfirmData = { applyNo: row.applyNo, umid: row.umid, sampleQuantity: null, finalFinishDate: '' } this.sampleConfirmVisible = true console.log('打开样品确认对话框:'+this.sampleConfirmData) },
/** * 获取样品状态标签类型 */ getSampleStatusTagType() { if (!this.sampleConfirmData.sampleQuantity || this.sampleConfirmData.sampleQuantity === 0) { return 'danger' } return 'success' },
/** * 获取样品状态文本 */ getSampleStatusText() { if (!this.sampleConfirmData.sampleQuantity || this.sampleConfirmData.sampleQuantity === 0) { return '报废' } return '正常入库' },
/** * 提交样品确认 */ confirmSampleSubmit() { // 验证最终完成日期必填
if (!this.sampleConfirmData.finalFinishDate) { this.$message.warning('请选择最终完成日期') return } // 确定样品状态
const finalStatus = (!this.sampleConfirmData.sampleQuantity || this.sampleConfirmData.sampleQuantity === 0) ? '报废' : '正常入库'
const finalQuantity = this.sampleConfirmData.sampleQuantity || 0 this.sampleConfirmLoading = true confirmSample({ applyNo: this.sampleConfirmData.applyNo, finalQuantity: finalQuantity, finalStatus: finalStatus, actualFinishDate: this.sampleConfirmData.finalFinishDate, umid: this.sampleConfirmData.umid }).then(({data}) => { this.sampleConfirmLoading = false if (data && data.code === 0) { this.$message.success('样品确认成功') this.sampleConfirmVisible = false this.getDataList() } else { this.$message.error(data.msg || '样品确认失败') } }).catch(() => { this.sampleConfirmLoading = false this.$message.error('样品确认异常') }) },
/** * 工具栏删除试验单 */ deleteApplyFromToolbar() { if (!this.currentRow.applyNo) { this.$message.warning('请先选择一条试验单记录') return }
if (this.currentRow.status !== '草稿') { this.$message.warning('只能删除草稿状态的试验单') return }
this.deleteApply(this.currentRow) },
/** * 工具栏撤回试验单 */ withdrawApplyFromToolbar() { if (!this.currentRow.applyNo) { this.$message.warning('请先选择一条试验单记录') return }
if (this.currentRow.status === '草稿' || this.currentRow.status === '已完成' || this.currentRow.status === '已驳回' || this.currentRow.status === '已取消') { this.$message.warning('该状态下无法撤回') return }
this.withdrawApply(this.currentRow) },
/** * 工具栏取消试验单 */ cancelApplyFromToolbar() { if (!this.currentRow.applyNo) { this.$message.warning('请先选择一条试验单记录') return }
if (this.currentRow.status === '草稿') { this.$message.warning('草稿状态的试验单请直接删除') return }
if (this.currentRow.status === '已完成' || this.currentRow.status === '已取消') { this.$message.warning('该状态下无法取消') return }
this.$confirm('确定取消该试验单?取消后将终止所有待审批流程,状态变更为已取消', '操作提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { this.sendLoading = true cancelExpApply({ applyNo: this.currentRow.applyNo, currentUserId: this.$store.state.user.id }).then(({data}) => { this.sendLoading = false if (data && data.code === 0) { this.$message.success(data.msg || '取消成功') this.getDataList() } else { this.$message.error(data.msg || '取消失败') } }).catch(() => { this.sendLoading = false this.$message.error('取消异常') }) }) },
/** * 打开复制试验单弹窗 */ openCopyDialog() { if (!this.currentRow.applyNo) { this.$message.warning('请先选择一条试验单记录') return }
// 根据试验类型判断是否显示三方确认选项
const defaultOptions = ['rawMaterialList'] if (this.currentRow.experimentType === 'High Risk') { defaultOptions.unshift('triConfirm') }
this.copyData = { sourceApplyNo: this.currentRow.applyNo, copyOptions: defaultOptions } this.copyDialogVisible = true },
/** * 确认复制试验单 */ confirmCopy() { if (!this.copyData.applyNo) { this.$message.warning('请输入新的试验单号') return } this.copyLoading = true const copyParams = { sourceApplyNo: this.copyData.sourceApplyNo, applyNo: this.copyData.applyNo, copyTriConfirm: this.copyData.copyOptions.includes('triConfirm'), copyAttachment: this.copyData.copyOptions.includes('attachment'), copyRawMaterialList: this.copyData.copyOptions.includes('rawMaterialList'), currentUserId: this.$store.state.user.id }
copyExpApply(copyParams).then(({data}) => { this.copyLoading = false if (data && data.code === 0) { this.$message.success('复制成功,已创建新的草稿试验单:' + data.newApplyNo) this.copyDialogVisible = false this.getDataList() } else { this.$message.error(data.msg || '复制失败') } }).catch(() => { this.copyLoading = false this.$message.error('复制异常') }) },
/** * 催办:向该试验单所有未确认的审批人(技术经理、生产经理、质量经理、计划员)发送催办邮件 */ urgeApproval() { if (!this.currentRow.applyNo) { this.$message.warning('请先选择一条试验单记录') return } this.$confirm( `确定向试验单【${this.currentRow.applyNo}】的所有未确认审批人发送催办邮件?`, '催办确认', { confirmButtonText: '确定发送', cancelButtonText: '取消', type: 'warning' } ).then(() => { this.urgeLoading = true this.sendLoading = true urgeApproval({ applyNo: this.currentRow.applyNo }).then(({data}) => { this.urgeLoading = false this.sendLoading = false if (data && data.code === 0) { this.$message.success(data.msg || '催办邮件已发送') } else { this.$message.error(data.msg || '催办失败') } }).catch(() => { this.urgeLoading = false this.sendLoading = false this.$message.error('催办请求异常') }) }).catch(() => {}) } }}</script>
<style scoped>.mod-config {
}
.el-form { margin-bottom: 10px;}
.dialog-footer { text-align: center;}
.empty-tip { padding: 40px; text-align: center; color: #999; font-size: 14px;}
/* ==================== 页面专属样式 - 不影响全局 ==================== */
/* 查询表单样式 */.exp-apply-page .query-form { background-color: #FFFFFF; padding: 15px 15px 5px 15px; border-radius: 4px;}
.exp-apply-page .query-form >>> .el-form-item__label { color: #333333; font-size: 13px; padding-bottom: 5px;}
.exp-apply-page .query-form >>> .el-input__inner { height: 32px; line-height: 32px; border-radius: 4px; border: 1px solid #DCDFE6; font-size: 13px;}
.exp-apply-page .query-form >>> .el-input__inner::placeholder { color: #C0C4CC; font-size: 13px;}
.exp-apply-page .query-form >>> .el-select .el-input__inner { border-radius: 4px;}
.exp-apply-page .query-form >>> .el-date-editor .el-input__inner { border-radius: 4px;}
/* 按钮样式 - 扁平化风格 */.exp-apply-page >>> .el-button { height: 32px; padding: 0 15px; font-size: 13px; border-radius: 4px;}
/* 查询按钮 - 蓝色扁平 */.exp-apply-page .search-btn { background-color: #ECF5FF; border-color: #B3D8FF; color: #409EFF;}
.exp-apply-page .search-btn:hover { background-color: #409EFF; border-color: #409EFF; color: #FFFFFF;}
/* 重置按钮 - 灰色扁平 */.exp-apply-page .reset-btn { background-color: #F5F7FA; border-color: #D3D4D6; color: #606266;}
.exp-apply-page .reset-btn:hover { background-color: #909399; border-color: #909399; color: #FFFFFF;}
/* 新增按钮 - 绿色扁平 */.exp-apply-page .add-btn { background-color: #F0F9FF; border-color: #C0E6C7; color: #67C23A;}
.exp-apply-page .add-btn:hover { background-color: #67C23A; border-color: #67C23A; color: #FFFFFF;}
/* 删除按钮 - 红色扁平 */.exp-apply-page .delete-btn { background-color: #FEF0F0; border-color: #FAB6B6; color: #F56C6C;}
.exp-apply-page .delete-btn:hover:not(:disabled) { background-color: #F56C6C; border-color: #F56C6C; color: #FFFFFF;}
/* 撤回按钮 - 橙色扁平 */.exp-apply-page .withdraw-btn { background-color: #FDF6EC; border-color: #F5DAB1; color: #E6A23C;}
.exp-apply-page .withdraw-btn:hover:not(:disabled) { background-color: #E6A23C; border-color: #E6A23C; color: #FFFFFF;}
/* 取消按钮 - 灰色扁平 */.exp-apply-page .cancel-btn { background-color: #F4F4F5; border-color: #D3D4D6; color: #909399;}
.exp-apply-page .cancel-btn:hover:not(:disabled) { background-color: #909399; border-color: #909399; color: #FFFFFF;}
/* 复制按钮 - 蓝色扁平 */.exp-apply-page .copy-btn { background-color: #ECF5FF; border-color: #B3D8FF; color: #409EFF;}
.exp-apply-page .copy-btn:hover:not(:disabled) { background-color: #409EFF; border-color: #409EFF; color: #FFFFFF;}
/* 催办按钮 - 橙黄色扁平 */.exp-apply-page .urge-btn { background-color: #FDF6EC; border-color: #FAECD8; color: #E6A23C;}
.exp-apply-page .urge-btn:hover:not(:disabled) { background-color: #E6A23C; border-color: #E6A23C; color: #FFFFFF;}
/* 禁用按钮样式 */.exp-apply-page .el-button:disabled { opacity: 0.5; cursor: not-allowed;}
/* 数据表格样式 */.exp-apply-page .data-table { background-color: #FFFFFF; border-radius: 4px;}
.exp-apply-page .data-table >>> .el-table__header-wrapper th { background-color: #F5F7FA !important; color: #333333; font-weight: 600; font-size: 16px; border-color: #EBEEF5; padding: 10px 0; height: auto;}
/* 固定列表头也使用相同背景色 */.exp-apply-page .data-table >>> .el-table__fixed-header-wrapper th { background-color: #F5F7FA !important; color: #333333; font-weight: 600; font-size: 16px; border-color: #EBEEF5; padding: 12px 0; height: auto;}
.exp-apply-page .data-table >>> .el-table__header-wrapper .cell { text-align: center; padding: 0 10px; //line-height: 2;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px !important;}
.exp-apply-page .data-table >>> .el-table__fixed-header-wrapper .cell { text-align: center; padding: 0 10px; //line-height: 2;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}
.exp-apply-page .data-table >>> .el-table__body-wrapper td { border-color: #EBEEF5; padding: 12px 0; font-size: 16px; color: #606266; height: auto;}
/* 固定列数据行也使用相同样式 */.exp-apply-page .data-table >>> .el-table__fixed-body-wrapper td { border-color: #EBEEF5; padding: 12px 0; font-size: 16px; color: #606266; height: auto;}
.exp-apply-page .data-table >>> .el-table__body-wrapper .cell { padding: 0 10px; //line-height: 2;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px !important;}
.exp-apply-page .data-table >>> .el-table__fixed-body-wrapper .cell { padding: 0 10px; //line-height: 2;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}
/* 操作列链接样式 - 统一蓝色样式 */.exp-apply-page .data-table >>> .el-table__body-wrapper a { text-decoration: none; cursor: pointer; font-size: 13px; color: #409EFF !important; white-space: nowrap;}
.exp-apply-page .data-table >>> .el-table__body-wrapper a:hover { color: #66B1FF !important; text-decoration: underline;}
.exp-apply-page .data-table >>> .el-table__body tr:hover > td { background-color: #F5F7FA !important;}
.exp-apply-page .data-table >>> .el-table__body tr.current-row > td { background-color: #ECF5FF !important;}
/* Tab样式优化 - 两栏布局 */
/* 两栏布局容器 */.two-column-layout { display: flex; gap: 10px; height: 100%;}
/* 统一的列头样式 */.column-header { display: flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 600; color: #606266; padding: 8px 12px; background: #f5f7fa; border-bottom: 2px solid #409EFF;}
.column-header i { color: #409EFF; font-size: 14px;}
.progress-badge { margin-left: auto; font-size: 14px; color: #409EFF; font-weight: bold;}
/* ==================== 左栏:审批流程 ==================== */.stages-column { flex: 0 0 380px; background: white; border: 1px solid #e4e7ed; border-radius: 6px; overflow: hidden; display: flex; flex-direction: column;}
.stages-list { padding: 8px; overflow-y: auto; flex: 1;}
.stage-item { display: flex; align-items: flex-start; gap: 8px; padding: 8px; margin-bottom: 6px; border-radius: 6px; background: #fafafa; transition: all 0.2s;}
.stage-item:last-child { margin-bottom: 0;}
.stage-item:hover { background: #f0f2f5; transform: translateX(2px);}
.stage-icon { width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 14px; flex-shrink: 0; margin-top: 2px;}
.stage-pending .stage-icon { background: #e8e8e8; color: #909399;}
.stage-current .stage-icon { background: #409EFF; color: white; animation: pulse-stage 2s infinite;}
@keyframes pulse-stage { 0%, 100% { box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.3); } 50% { box-shadow: 0 0 0 5px rgba(64, 158, 255, 0.1); }}
.stage-completed .stage-icon { background: #67C23A; color: white;}
.stage-rejected .stage-icon { background: #F56C6C; color: white;}
.stage-content { flex: 1; min-width: 0;}
.stage-name { font-size: 13px; font-weight: 600; color: #303133; margin-bottom: 5px;}
.stage-meta { display: flex; align-items: center; gap: 6px;}
.stage-time { font-size: 11px; color: #909399;}
.stage-current { background: #ecf5ff !important; border: 1px solid #b3d8ff;}
.stage-completed { background: #f0f9ff !important;}
.stage-rejected { background: #fef0f0 !important; border: 1px solid #fbc4c4;}
/* ==================== 右栏:审批日志(表格) ==================== */.logs-column { flex: 1; background: white; border: 1px solid #e4e7ed; border-radius: 6px; overflow: hidden; display: flex; flex-direction: column;}
.logs-column .column-header { display: flex; justify-content: space-between; align-items: center; flex-shrink: 0;}
.logs-count { font-size: 11px; color: #909399; font-weight: normal; margin-left: auto;}
.logs-table-wrapper { flex: 1; overflow: hidden;}
/* ==================== 审批日志表格专用样式 ==================== */
/* 表头样式 - 深灰色背景,白色文字 */.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;}
/* 空状态 */.empty-tip { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 50px 20px; color: #909399;}
.empty-tip p { margin: 0;}/deep/ .manager-select .el-input__inner { height: 30px !important; line-height: 30px !important;}
/deep/ .checkbox-vertical .el-checkbox { display: block; margin-left: 0; margin-bottom: 8px;}</style>
|