|
|
<template> <div class="mod-config exp-apply-page"> <!-- 查询条件表单 --> <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-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-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-button @click="openCreateDialog()" type="success" plain class="add-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 @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="scope.row.status === '草稿'" @click="deleteApply(scope.row)">删除</a> <a v-if="scope.row.status !== '草稿' && scope.row.status !== '已完成' && scope.row.status !== '已驳回'" @click="withdrawApply(scope.row)">撤回</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="100" 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="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="creatorName" label="创建人" width="80" 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" :disabled="false" :height="detailHeight"> </erf-attachment-manager> <div v-else class="empty-tip"> 请在上方表格中选择一条申请单记录 </div> </el-tab-pane> <!-- Tab 2: 三方确认 --> <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" :experiment-type="currentRow.experimentType" :height="detailHeight"> </exp-tri-confirm> <div v-else class="empty-tip"> 请在上方表格中选择一条申请单记录 </div> </el-tab-pane>
<!-- Tab 3: 附件上传 -->
<!-- Tab 4: 审批状态和日志 --> <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> </el-tabs>
<!-- 新增/编辑弹窗 --> <el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="950px" :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: 40px; 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 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="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>
<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> </div></template>
<script>import { searchExpApplyList, submitExpApply, deleteExpApply, withdrawExpApply, getSubmitApprovers, getFlowStatus, getTriConfirmList, confirmSample } from '@/api/erf/erf'import { getBuList } from '@/api/factory/site'import ExpApplyForm from './components/expApplyForm.vue'import ExpProjectDetail from './components/expProjectDetail.vue'import ExpTriConfirm from './components/expTriConfirm.vue'import ErfAttachmentManager from './components/erfAttachmentManager.vue'
export default { name: 'ExpApplyList',
components: { ExpApplyForm, ExpProjectDetail, ExpTriConfirm, ErfAttachmentManager },
data() { return { buList: [], // 查询条件
queryHeaderData: { applyNo: '', buNo: '', experimentType: '', status: '', createStartDate: '', createEndDate: '', page: 1, limit: 20 },
// 数据列表
dataList: [],
// 分页参数
pageIndex: 1, pageSize: 20, totalPage: 0, dataListLoading: false,
// 弹窗相关
dialogVisible: false, dialogTitle: '新增申请单', dialogReadonly: false, currentApply: {}, saveLoading: false,
// 当前选中行
currentRow: {},
// Tab页签
activeName: 'attachment',
// 表格高度
tableHeight: (window.innerHeight - 260)/2, detailHeight: '35vh',
// 下达确认弹窗
submitDialogVisible: false, submitLoading: false, submitData: { applyNo: '', buNo: '', techManagerId: null, techManagerName: '', prodManagerIds: [], qualityManagerIds: [] }, prodManagerList: [], // 生产经理候选列表
qualityManagerList: [], // 质量经理候选列表
// 流程状态数据
flowStatus: { applyNo: '', currentStatus: '', currentStep: '', progressPercent: 0, stages: [], approvalLogs: [] },
// 样品确认弹窗
sampleConfirmVisible: false, sampleConfirmLoading: false, sampleConfirmData: { applyNo: '', sampleQuantity: null, finalFinishDate: '' } } },
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.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: '', 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) { // ✅ 如果是High Risk,先验证工序
if (row.experimentType === 'High Risk') { console.log('🔍 检测到High Risk申请单,开始验证工序...')
// 通过API查询工序列表进行验证(不依赖子组件是否已加载)
this.validateHighRiskProcessByApi(row.applyNo).then(isValid => { if (!isValid) { console.log('❌ High Risk验证失败,终止下达') this.$alert('High Risk申请单必须至少有一条完整的工序(包含车间、质量、技术三个负责人)才能下达', '操作提示', { confirmButtonText: '确定', type: 'warning' }) return }
console.log('✅ High Risk验证通过,继续下达流程') // 验证通过后继续下达流程
this.proceedSubmit(row) }).catch(error => { console.error('❌ 验证工序异常:', error) this.$message.error('验证工序失败,请重试') }) } else { // 非High Risk直接下达
console.log('📝 Low Risk申请单,直接下达') this.proceedSubmit(row) } },
/** * 通过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 || [] // 根据事业部设置默认生产经理(使用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 }
// 显示弹窗
this.submitDialogVisible = true } else { this.$message.error(data.msg || '获取审批人信息失败') } }).catch(() => { this.$message.error('获取审批人信息异常') }) },
/** * 确认下达 */ 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 }
this.submitLoading = true
// 调用下达API
submitExpApply({ applyNo: this.submitData.applyNo, techManagerId: this.submitData.techManagerId, prodManagerIds: this.submitData.prodManagerIds, qualityManagerIds: this.submitData.qualityManagerIds }).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() },
/** * 行点击事件 */ handleRowClick(row) { this.currentRow = JSON.parse(JSON.stringify(row)) // 设置表格当前行高亮
this.$nextTick(() => { this.$refs.dataTable.setCurrentRow(row) })
// 根据当前tab刷新对应的数据
if (this.activeName === 'approvalStatus') { // 刷新审批状态和日志
this.loadFlowStatus() } else if (this.activeName === 'triConfirm') { // 刷新三方确认数据
this.$nextTick(() => { if (this.$refs.triConfirm && this.$refs.triConfirm.loadProcessList) { console.log('🔄 行切换,刷新三方确认数据') this.$refs.triConfirm.loadProcessList() } }) } else { // 否则自动切换到项目详情tab
this.activeName = 'attachment' } },
/** * 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' } 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, sampleQuantity: null, finalFinishDate: '' } this.sampleConfirmVisible = true },
/** * 获取样品状态标签类型 */ 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 }).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('样品确认异常') }) } }}</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 .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;}
</style>
|