1057 lines
31 KiB
Vue
1057 lines
31 KiB
Vue
<template>
|
||
<transition name="approval-progress-fade">
|
||
<div v-if="visible" class="approval-progress-detail">
|
||
<div class="detail-panel">
|
||
<div class="detail-header">
|
||
<div class="header-left">
|
||
<el-button
|
||
type="text"
|
||
icon="el-icon-arrow-left"
|
||
class="back-button"
|
||
@click="handleBack"
|
||
>
|
||
</el-button>
|
||
<div class="header-info">
|
||
<div class="title">
|
||
审批详情
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="header-right">
|
||
<el-button
|
||
v-if="flowStatus === 'APPROVING'"
|
||
@click="handleReject"
|
||
>
|
||
退回
|
||
</el-button>
|
||
<el-button
|
||
v-if="flowStatus === 'APPROVING'"
|
||
type="primary"
|
||
@click="handleSubmit"
|
||
>
|
||
提交
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
<div class="detail-content">
|
||
<section class="block-section">
|
||
<div class="section-title">
|
||
基础信息
|
||
</div>
|
||
<div class="info-grid">
|
||
<div class="info-item">
|
||
<span class="label">流程主题</span>
|
||
<span class="value">{{ progressDetail.progressTitle || '--' }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="label">模板名称</span>
|
||
<span class="value">{{ progressDetail.contentName || '--' }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="label">版本</span>
|
||
<span class="value">{{ progressDetail.templateVersion || '--' }}</span>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="block-section">
|
||
<div class="section-title">
|
||
审批内容
|
||
</div>
|
||
<el-table :data="changeTable || []" border class="change-table">
|
||
<el-table-column label="序号" type="index" width="60" />
|
||
<el-table-column
|
||
prop="subsystemName"
|
||
label="子系统"
|
||
min-width="120"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column
|
||
prop="parameterType"
|
||
label="类型"
|
||
min-width="120"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column
|
||
prop="partsName"
|
||
label="零部件名称"
|
||
min-width="140"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column
|
||
prop="parameterName"
|
||
label="参数名称"
|
||
min-width="140"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column
|
||
prop="unit"
|
||
label="单位"
|
||
min-width="80"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column prop="remarks" label="备注" min-width="120" show-overflow-tooltip />
|
||
<el-table-column prop="modifyDetails" label="变动说明" min-width="160">
|
||
<template slot-scope="scope">
|
||
<span class="modify-details-text">
|
||
{{ scope.row.modifyDetails || '--' }}
|
||
</span>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<el-pagination
|
||
v-if="changeTableTotal > 0"
|
||
class="pagination-wrapper"
|
||
:current-page="changeTablePageNumber"
|
||
:page-size="changeTablePageSize"
|
||
:total="changeTableTotal"
|
||
layout="total, prev, pager, next, jumper"
|
||
@current-change="handlePageChange"
|
||
/>
|
||
<div v-if="progressDetail.description" class="description-block">
|
||
<span class="label">说明:</span>
|
||
<span class="value">{{ detailOption }}</span>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="block-section">
|
||
<div class="section-title">
|
||
审批流程
|
||
</div>
|
||
<div v-if="timelineNodes && timelineNodes.length > 0" class="approval-timeline">
|
||
<el-timeline>
|
||
<el-timeline-item
|
||
v-for="(node, index) in timelineNodes"
|
||
:key="index"
|
||
:color="getTimelineColor(node.result || node.status, index, timelineNodes.length)"
|
||
>
|
||
<div class="timeline-item-content">
|
||
<div class="node-title">
|
||
{{ node.title }}
|
||
</div>
|
||
<div class="node-content">
|
||
<div class="node-info">
|
||
<div class="node-user">
|
||
{{ node.userName || '--' }}
|
||
<span class="node-time">
|
||
{{ formatTime(node.operateTime) }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<div class="node-status" :class="getStatusClass(node.result || node.status)">
|
||
{{ setStatusText(node.result || node.status) }}
|
||
</div>
|
||
<div v-if="node.opinion" class="node-opinion">
|
||
审批意见:{{ node.opinion }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-timeline-item>
|
||
</el-timeline>
|
||
</div>
|
||
<div v-else class="empty-progress">
|
||
<span class="empty-text">暂无审批流程数据</span>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 审批弹窗 -->
|
||
<el-dialog
|
||
:title="approvalDialogTitle"
|
||
:visible.sync="approvalDialogVisible"
|
||
width="500px"
|
||
:close-on-click-modal="false"
|
||
append-to-body
|
||
@close="handleApprovalDialogClose"
|
||
>
|
||
<el-form ref="approvalForm" :model="approvalFormData" :rules="approvalFormRules" label-width="80px">
|
||
<el-form-item
|
||
:label="approvalActionType === 'reject' ? '退回意见:' : '提交意见:'"
|
||
prop="opinion"
|
||
>
|
||
<el-input
|
||
v-model="approvalFormData.opinion"
|
||
type="textarea"
|
||
:rows="4"
|
||
:placeholder="approvalActionType === 'reject' ? '请输入退回意见' : '请输入提交意见'"
|
||
/>
|
||
</el-form-item>
|
||
</el-form>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button @click="handleApprovalDialogClose">
|
||
取消
|
||
</el-button>
|
||
<el-button type="primary" :loading="approvalSubmitting" @click="handleApprovalSubmit">
|
||
确定
|
||
</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
</div>
|
||
</transition>
|
||
</template>
|
||
|
||
<script>
|
||
import api from '@/api'
|
||
import { getUserId } from '@/utils'
|
||
export default {
|
||
name: 'ApprovalProgressDetail',
|
||
props: {
|
||
detail: {
|
||
type: Object,
|
||
default: () => ({})
|
||
},
|
||
visible: {
|
||
type: Boolean,
|
||
default: true
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
changeTable: [],
|
||
progress: [], // 保留用于兼容
|
||
flowNodes: [], // 审批流程节点数据
|
||
progressDetailData: {}, // 审批流程详情数据
|
||
detailOption: '',
|
||
flowStatus: '',
|
||
changeTableTotal: 0,
|
||
changeTablePageNumber: 1,
|
||
changeTablePageSize: 10,
|
||
approvalDialogVisible: false,
|
||
approvalActionType: '', // 'reject' 或 'submit'
|
||
approvalDialogTitle: '审核',
|
||
approvalFormData: {
|
||
result: '',
|
||
opinion: ''
|
||
},
|
||
approvalSubmitting: false,
|
||
approvalFormRules: {
|
||
opinion: [
|
||
{ required: true, message: '请输入意见', trigger: 'blur' }
|
||
]
|
||
}
|
||
}
|
||
},
|
||
computed: {
|
||
progressDetail() {
|
||
// 优先使用从接口获取的数据,如果没有则使用默认值
|
||
if (this.progressDetailData && Object.keys(this.progressDetailData).length > 0) {
|
||
return {
|
||
progressTitle: this.progressDetailData.progressTitle || '',
|
||
contentName: this.progressDetailData.contentName || '',
|
||
templateVersion: this.progressDetailData.templateVersion || '',
|
||
explanation: this.progressDetailData.explanation || '',
|
||
description: this.progressDetailData.explanation || this.progressDetailData.description || ''
|
||
}
|
||
}
|
||
const defaultDetail = {
|
||
approvalNumber: '',
|
||
modelName: '',
|
||
applicant: '',
|
||
department: '',
|
||
submitTime: '',
|
||
description: '',
|
||
changeTable: [],
|
||
progress: []
|
||
}
|
||
return Object.assign(defaultDetail, this.detail || {})
|
||
},
|
||
// 发起审批节点
|
||
initiateNode() {
|
||
if (!this.detail || !this.detail.createBy) {
|
||
return null
|
||
}
|
||
return {
|
||
userName: this.detail.createBy,
|
||
createTime: this.detail.createdTime || this.detail.createTime,
|
||
status: 'INITIATE',
|
||
result: 'INITIATE'
|
||
}
|
||
},
|
||
// 合并所有节点为时间线数据
|
||
timelineNodes() {
|
||
const nodes = []
|
||
|
||
// 添加发起审批节点
|
||
if (this.initiateNode) {
|
||
nodes.push({
|
||
title: '提交申请',
|
||
userName: this.detail.username,
|
||
operateTime: this.detail.createdTime,
|
||
result: 'INITIATE',
|
||
status: 'INITIATE',
|
||
opinion: this.initiateNode.opinion || this.initiateNode.remark || this.initiateNode.comment || null
|
||
})
|
||
}
|
||
|
||
// 使用 flowNodes 数据构建审批流程节点
|
||
if (this.flowNodes && this.flowNodes.length > 0) {
|
||
// 按照 nodeOrder 排序
|
||
const sortedNodes = [...this.flowNodes].sort((a, b) => {
|
||
const orderA = a.nodeOrder !== undefined && a.nodeOrder !== null ? Number(a.nodeOrder) : 0
|
||
const orderB = b.nodeOrder !== undefined && b.nodeOrder !== null ? Number(b.nodeOrder) : 0
|
||
return orderA - orderB
|
||
})
|
||
|
||
// 查找是否有 REJECT 节点
|
||
const rejectIndex = sortedNodes.findIndex((node) => node.result === 'REJECT')
|
||
|
||
let processedNodes = sortedNodes
|
||
if (rejectIndex !== -1) {
|
||
// 如果有 REJECT 节点,删除该节点后面的所有节点
|
||
processedNodes = sortedNodes.slice(0, rejectIndex + 1)
|
||
} else {
|
||
// 如果没有 REJECT,将第一个 result 是"待审批"的节点改为 APPROVING
|
||
const stayIndex = processedNodes.findIndex((node) => node.result === '待审批')
|
||
if (stayIndex !== -1) {
|
||
processedNodes = processedNodes.map((node, index) => {
|
||
if (index === stayIndex) {
|
||
return {
|
||
...node,
|
||
result: 'APPROVING'
|
||
}
|
||
}
|
||
return node
|
||
})
|
||
}
|
||
}
|
||
|
||
// 转换为时间线节点格式
|
||
processedNodes.forEach((node) => {
|
||
let result = 'APPROVING'
|
||
let status = 'APPROVING'
|
||
|
||
if (node.result) {
|
||
if (node.result === 'REJECT') {
|
||
result = 'REJECT'
|
||
status = 'REJECT'
|
||
} else if (node.result === 'APPROVING') {
|
||
result = 'APPROVING'
|
||
status = 'APPROVING'
|
||
} else if (node.result === 'PASS') {
|
||
result = 'PASS'
|
||
status = 'PASS'
|
||
} else if (node.result === '待审批') {
|
||
result = 'STAY'
|
||
status = 'STAY'
|
||
}
|
||
}
|
||
|
||
// 处理审批意见:如果有 opinions 数组,取第一个;否则为空
|
||
const opinion = node.opinions && Array.isArray(node.opinions) && node.opinions.length > 0
|
||
? node.opinions[0]
|
||
: null
|
||
|
||
// 处理用户名:优先使用 todoUserName(已处理人),如果没有则使用 undoUserName(待处理人)
|
||
const userName = node.todoUserName || node.undoUserName || '--'
|
||
|
||
nodes.push({
|
||
title: node.node || '审批节点',
|
||
userName: userName,
|
||
operateTime: node.approvalTime || '',
|
||
result: result,
|
||
status: status,
|
||
opinion: opinion
|
||
})
|
||
})
|
||
} else if (this.progress && this.progress.length > 0) {
|
||
// 兼容旧逻辑:如果没有 flowNodes,则使用原来的 progress 数据
|
||
this.progress.forEach((stage) => {
|
||
if (stage.steps && stage.steps.length > 0) {
|
||
stage.steps.forEach((step) => {
|
||
nodes.push({
|
||
title: stage.title || '审批节点',
|
||
userName: step.userName,
|
||
operateTime: step.approvalTime || step.handleTime || step.createTime,
|
||
result: step.result || step.status,
|
||
status: step.result || step.status,
|
||
opinion: step.opinion || step.remark || step.comment || null
|
||
})
|
||
})
|
||
}
|
||
})
|
||
}
|
||
|
||
return nodes
|
||
},
|
||
// 判断是否可以审批(状态为审批中且当前用户是审批人)
|
||
canApprove() {
|
||
if (!this.detail || !this.detail.status) {
|
||
return false
|
||
}
|
||
// 状态为审批中
|
||
if (this.detail.status === 'APPROVING' || this.detail.status === '审批中') {
|
||
// 检查当前用户是否是审批人
|
||
if (this.detail.currentApprovers && Array.isArray(this.detail.currentApprovers)) {
|
||
const currentUserId = getUserId()
|
||
return this.detail.currentApprovers.some(
|
||
approver => approver.userId === currentUserId || approver.userNumber === currentUserId
|
||
)
|
||
}
|
||
// 如果没有 currentApprovers 字段,也可以显示按钮(由后端判断)
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
},
|
||
mounted() {
|
||
this.getApprovalChange()
|
||
this.getApprovalSpet()
|
||
},
|
||
methods: {
|
||
setStatusText(result) {
|
||
const statusMap = {
|
||
'PASS': '通过',
|
||
'REJECT': '驳回',
|
||
'REFUSE': '驳回',
|
||
'APPROVE': '批准',
|
||
'APPROVING': '审批中',
|
||
'PENDING': '待处理',
|
||
'WAITING': '待处理',
|
||
'INITIATE': '发起审批'
|
||
}
|
||
return statusMap[result] || result || '待处理'
|
||
},
|
||
formatTime(time) {
|
||
if (!time) return '--'
|
||
// 如果是时间戳,转换为日期格式
|
||
if (typeof time === 'number') {
|
||
const date = new Date(time)
|
||
return date.toLocaleString('zh-CN', {
|
||
year: 'numeric',
|
||
month: '2-digit',
|
||
day: '2-digit',
|
||
hour: '2-digit',
|
||
minute: '2-digit',
|
||
second: '2-digit'
|
||
})
|
||
}
|
||
// 如果已经是字符串格式,直接返回
|
||
return time
|
||
},
|
||
getTimelineColor(result, stepIndex, totalSteps) {
|
||
// 根据审批结果设置颜色
|
||
if (result === 'PASS' || result === 'APPROVE') {
|
||
return '#67C23A' // 绿色 - 通过
|
||
} else if (result === 'REJECT' || result === 'REFUSE') {
|
||
return '#F56C6C' // 红色 - 驳回
|
||
} else if (result === 'APPROVING' || result === 'PENDING' || result === 'WAITING') {
|
||
return '#E6A23C' // 橙色 - 审批中
|
||
} else if (result === 'INITIATE') {
|
||
return '#409EFF' // 蓝色 - 发起审批
|
||
}
|
||
// 默认颜色
|
||
return '#409EFF' // 默认蓝色
|
||
},
|
||
getStatusClass(result) {
|
||
const classMap = {
|
||
'PASS': 'status-pass',
|
||
'APPROVE': 'status-approve',
|
||
'REJECT': 'status-reject',
|
||
'REFUSE': 'status-reject',
|
||
'APPROVING': 'status-approving',
|
||
'PENDING': 'status-pending',
|
||
'WAITING': 'status-pending',
|
||
'INITIATE': 'status-initiate'
|
||
}
|
||
return classMap[result] || 'status-default'
|
||
},
|
||
getApprovalSpet() {
|
||
if (!this.detail || !this.detail.flowId) {
|
||
return
|
||
}
|
||
const request = {
|
||
...api.GET_PPROVAL_ALL,
|
||
params: {
|
||
flowId: this.detail.flowId,
|
||
templateId: this.detail.templateId
|
||
},
|
||
disableSuccessMsg: true
|
||
}
|
||
this.$request(request)
|
||
.asyncThen((resp) => {
|
||
console.log('审批流程数据:', resp)
|
||
const data = resp.data || resp
|
||
// 保存流程状态
|
||
this.flowStatus = data.flowStatus || ''
|
||
// 保存流程详情
|
||
this.progressDetailData = {
|
||
explanation: data.explanation || '',
|
||
flowStatus: data.flowStatus || '',
|
||
progressTitle: data.progressTitle || '',
|
||
templateVersion: data.templateVersion || '',
|
||
contentName: data.contentName || ''
|
||
}
|
||
// 处理说明信息
|
||
this.detailOption = data.explanation || data.description || ''
|
||
// 保存流程节点
|
||
this.flowNodes = data.flowNodes || []
|
||
|
||
// 兼容旧逻辑:如果没有 flowNodes,尝试使用旧的节点结构
|
||
if (!this.flowNodes || this.flowNodes.length === 0) {
|
||
let list = []
|
||
// 处理校对节点
|
||
if (data.proofreadNodes && Array.isArray(data.proofreadNodes) && data.proofreadNodes.length > 0) {
|
||
const steps = data.proofreadNodes
|
||
list.push({
|
||
title: '校对',
|
||
steps: this.deduplicateSteps(Array.isArray(steps) ? steps : [steps])
|
||
})
|
||
}
|
||
// 处理审核节点
|
||
if (data.reviewNodes && Array.isArray(data.reviewNodes) && data.reviewNodes.length > 0) {
|
||
const steps = data.reviewNodes
|
||
list.push({
|
||
title: '审核',
|
||
steps: this.deduplicateSteps(Array.isArray(steps) ? steps : [steps])
|
||
})
|
||
}
|
||
// 处理会签节点
|
||
if (data.countersignNodes && Array.isArray(data.countersignNodes) && data.countersignNodes.length > 0) {
|
||
const steps = data.countersignNodes
|
||
list.push({
|
||
title: '会签',
|
||
steps: this.deduplicateSteps(Array.isArray(steps) ? steps : [steps])
|
||
})
|
||
}
|
||
// 处理批准节点
|
||
if (data.approveNodes && Array.isArray(data.approveNodes) && data.approveNodes.length > 0) {
|
||
const steps = data.approveNodes
|
||
list.push({
|
||
title: '批准',
|
||
steps: this.deduplicateSteps(Array.isArray(steps) ? steps : [steps])
|
||
})
|
||
}
|
||
|
||
// 如果数据结构是直接返回数组,则直接使用
|
||
if (list.length === 0 && data) {
|
||
// 尝试直接使用返回的数据结构
|
||
if (Array.isArray(data)) {
|
||
list = data.map(item => ({
|
||
title: item.title || item.nodeType || item.name || '审批节点',
|
||
steps: this.deduplicateSteps(Array.isArray(item.steps) ? item.steps : (Array.isArray(item.nodes) ? item.nodes : []))
|
||
}))
|
||
} else if (data.nodes && Array.isArray(data.nodes)) {
|
||
list = data.nodes.map(item => ({
|
||
title: item.title || item.nodeType || item.name || '审批节点',
|
||
steps: this.deduplicateSteps(Array.isArray(item.steps) ? item.steps : (Array.isArray(item.nodes) ? item.nodes : []))
|
||
}))
|
||
} else if (data.processNodes && Array.isArray(data.processNodes)) {
|
||
list = data.processNodes.map(item => ({
|
||
title: item.title || item.nodeType || item.name || '审批节点',
|
||
steps: this.deduplicateSteps(Array.isArray(item.steps) ? item.steps : (Array.isArray(item.nodes) ? item.nodes : []))
|
||
}))
|
||
}
|
||
}
|
||
|
||
this.progress = list
|
||
console.log('处理后的流程数据(兼容模式):', this.progress)
|
||
}
|
||
})
|
||
.asyncErrorCatch((err) => {
|
||
console.error('获取审批流程数据失败:', err)
|
||
this.flowNodes = []
|
||
this.progressDetailData = {}
|
||
})
|
||
},
|
||
getApprovalChange() {
|
||
const request = {
|
||
...api.GET_APPROVAL_CHANGE,
|
||
params: {
|
||
flowId: this.detail.flowId,
|
||
templateId: this.detail.templateId,
|
||
pageNumber: this.changeTablePageNumber,
|
||
pageSize: this.changeTablePageSize
|
||
},
|
||
disableSuccessMsg: true
|
||
}
|
||
this.$request(request).asyncThen((resp) => {
|
||
console.log(resp)
|
||
this.changeTable = resp.data.result
|
||
this.changeTableTotal = resp.data.count
|
||
})
|
||
},
|
||
handleBack() {
|
||
this.$emit('close')
|
||
},
|
||
handlePageChange(page) {
|
||
this.changeTablePageNumber = page
|
||
this.getApprovalChange()
|
||
},
|
||
changeTypeClass(type) {
|
||
if (type === 'create') return 'change-create'
|
||
if (type === 'delete') return 'change-delete'
|
||
if (type === 'update') return 'change-update'
|
||
return 'change-default'
|
||
},
|
||
// 对steps数组进行去重,优先保留有approvalTime的记录
|
||
deduplicateSteps(steps) {
|
||
if (!Array.isArray(steps) || steps.length === 0) {
|
||
return []
|
||
}
|
||
|
||
// 使用Map来存储已见过的唯一标识和对应的记录
|
||
const seen = new Map()
|
||
const result = []
|
||
|
||
steps.forEach(step => {
|
||
// 生成唯一标识:优先使用id,如果没有id则使用多个字段组合
|
||
let uniqueKey = null
|
||
|
||
if (step.id) {
|
||
// 如果有id,使用id作为唯一标识
|
||
uniqueKey = `id_${step.id}`
|
||
} else {
|
||
// 如果没有id,使用多个字段组合作为唯一标识
|
||
const userName = step.userName || step.operator || step.username || step.handlerName || step.handler || ''
|
||
const approvalTime = step.approvalTime || step.handleTime || step.createTime || ''
|
||
const stepResult = step.result || step.status || ''
|
||
uniqueKey = `combo_${userName}_${approvalTime}_${stepResult}`
|
||
}
|
||
|
||
if (!uniqueKey) {
|
||
return // 跳过无法生成唯一标识的记录
|
||
}
|
||
|
||
// 检查是否已存在相同唯一标识的记录
|
||
if (seen.has(uniqueKey)) {
|
||
// 如果已存在,比较两条记录,优先保留有approvalTime的记录
|
||
const existingIndex = seen.get(uniqueKey)
|
||
const existingStep = result[existingIndex]
|
||
|
||
const currentHasTime = !!(step.approvalTime || step.handleTime || step.createTime)
|
||
const existingHasTime = !!(existingStep.approvalTime || existingStep.handleTime || existingStep.createTime)
|
||
|
||
// 如果当前记录有approvalTime而已存在的没有,则替换
|
||
if (currentHasTime && !existingHasTime) {
|
||
result[existingIndex] = step
|
||
}
|
||
// 如果两条都有或都没有,保留已存在的(第一条)
|
||
} else {
|
||
// 如果这个唯一标识还没有出现过,添加到结果中
|
||
seen.set(uniqueKey, result.length)
|
||
result.push(step)
|
||
}
|
||
})
|
||
|
||
// 对所有记录的approvalTime进行格式化
|
||
return result.map(step => {
|
||
const formattedStep = { ...step }
|
||
if (formattedStep.approvalTime) {
|
||
formattedStep.approvalTime = this.formatTime(formattedStep.approvalTime)
|
||
}
|
||
if (formattedStep.handleTime) {
|
||
formattedStep.handleTime = this.formatTime(formattedStep.handleTime)
|
||
}
|
||
if (formattedStep.createTime) {
|
||
formattedStep.createTime = this.formatTime(formattedStep.createTime)
|
||
}
|
||
return formattedStep
|
||
})
|
||
},
|
||
handleReject() {
|
||
this.approvalActionType = 'reject'
|
||
this.approvalDialogTitle = '退回'
|
||
this.approvalFormData = {
|
||
result: 'REJECT',
|
||
opinion: ''
|
||
}
|
||
this.approvalDialogVisible = true
|
||
// 清除表单验证
|
||
this.$nextTick(() => {
|
||
if (this.$refs.approvalForm) {
|
||
this.$refs.approvalForm.clearValidate()
|
||
}
|
||
})
|
||
},
|
||
handleSubmit() {
|
||
this.approvalActionType = 'submit'
|
||
this.approvalDialogTitle = '提交'
|
||
this.approvalFormData = {
|
||
result: 'PASS',
|
||
opinion: ''
|
||
}
|
||
this.approvalDialogVisible = true
|
||
// 清除表单验证
|
||
this.$nextTick(() => {
|
||
if (this.$refs.approvalForm) {
|
||
this.$refs.approvalForm.clearValidate()
|
||
}
|
||
})
|
||
},
|
||
handleApprovalDialogClose() {
|
||
this.approvalDialogVisible = false
|
||
this.approvalActionType = ''
|
||
this.approvalDialogTitle = '审核'
|
||
this.approvalFormData = {
|
||
result: '',
|
||
opinion: ''
|
||
}
|
||
// 清除表单验证
|
||
if (this.$refs.approvalForm) {
|
||
this.$refs.approvalForm.clearValidate()
|
||
}
|
||
},
|
||
handleApprovalSubmit() {
|
||
// 表单验证
|
||
this.$refs.approvalForm.validate((valid) => {
|
||
if (!valid) {
|
||
return false
|
||
}
|
||
|
||
if (!this.detail || !this.detail.flowId) {
|
||
this.$message.error('缺少审批信息')
|
||
return
|
||
}
|
||
|
||
// 构建提交数据
|
||
const submitData = {
|
||
flowId: this.detail.flowId,
|
||
userId: getUserId(),
|
||
result: this.approvalFormData.result, // 'PASS' 或 'REJECT'
|
||
opinion: this.approvalFormData.opinion || ''
|
||
}
|
||
|
||
this.approvalSubmitting = true
|
||
const request = {
|
||
...api.HANDLE_APPROVAL,
|
||
params: submitData
|
||
}
|
||
|
||
this.$request(request)
|
||
.asyncThen((resp) => {
|
||
this.handleApprovalDialogClose()
|
||
// 重新加载审批流程和变更数据
|
||
this.getApprovalSpet()
|
||
this.getApprovalChange()
|
||
// 通知父组件刷新数据
|
||
this.$emit('refresh')
|
||
this.approvalSubmitting = false
|
||
})
|
||
.asyncErrorCatch((err) => {
|
||
console.error('审批提交失败:', err)
|
||
this.$message.error('审批提交失败,请重试')
|
||
this.approvalSubmitting = false
|
||
})
|
||
.finally(() => {
|
||
this.approvalSubmitting = false
|
||
})
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.approval-progress-detail {
|
||
width: 100%;
|
||
height: 100%;
|
||
background: #fff;
|
||
display: flex;
|
||
justify-content: flex-start;
|
||
align-items: flex-start;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
overflow-y: auto;
|
||
|
||
.detail-panel {
|
||
width: 100%;
|
||
max-width: 1200px;
|
||
background: #ffffff;
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.detail-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 24px 32px 16px 32px;
|
||
border-bottom: 1px solid #ebeef5;
|
||
|
||
.header-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
}
|
||
|
||
.header-right {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.back-button {
|
||
display: flex;
|
||
align-items: center;
|
||
font-size: 14px;
|
||
color: #606266;
|
||
|
||
::v-deep .el-icon-arrow-left {
|
||
font-size: 16px;
|
||
}
|
||
}
|
||
|
||
.header-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
|
||
.title {
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
}
|
||
|
||
.subtitle {
|
||
font-size: 14px;
|
||
color: #909399;
|
||
}
|
||
}
|
||
}
|
||
|
||
.detail-content {
|
||
padding: 24px 32px 40px 32px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
overflow: auto;
|
||
gap: 24px;
|
||
}
|
||
|
||
.block-section {
|
||
border-radius: 8px;
|
||
padding: 24px 24px 16px 24px;
|
||
|
||
.section-title {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
margin-bottom: 16px;
|
||
}
|
||
}
|
||
|
||
.info-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(200px, 1fr));
|
||
gap: 16px 24px;
|
||
|
||
.info-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
|
||
.label {
|
||
font-size: 13px;
|
||
color: #909399;
|
||
}
|
||
|
||
.value {
|
||
font-size: 15px;
|
||
color: #303133;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
}
|
||
|
||
.change-table {
|
||
width: 100%;
|
||
margin-bottom: 16px;
|
||
|
||
::v-deep .el-table__header-wrapper th {
|
||
background: #f0f2f5;
|
||
color: #606266;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
|
||
.pagination-wrapper {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
margin-top: 16px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.modify-details-text {
|
||
white-space: pre-line;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.change-tag {
|
||
display: inline-block;
|
||
padding: 4px 8px;
|
||
border-radius: 12px;
|
||
font-size: 12px;
|
||
line-height: 1;
|
||
background: #f4f4f5;
|
||
color: #909399;
|
||
}
|
||
|
||
.change-create {
|
||
background: #f0f9eb;
|
||
color: #67c23a;
|
||
}
|
||
|
||
.change-update {
|
||
background: #ecf5ff;
|
||
color: #409eff;
|
||
}
|
||
|
||
.change-delete {
|
||
background: #fef0f0;
|
||
color: #f56c6c;
|
||
}
|
||
|
||
.description-block {
|
||
display: flex;
|
||
gap: 8px;
|
||
font-size: 14px;
|
||
color: #606266;
|
||
|
||
.label {
|
||
color: #909399;
|
||
}
|
||
}
|
||
|
||
.approval-timeline {
|
||
padding: 20px 0;
|
||
|
||
::v-deep .el-timeline {
|
||
padding-left: 0;
|
||
|
||
.el-timeline-item {
|
||
padding-bottom: 24px;
|
||
|
||
&:last-child {
|
||
padding-bottom: 0;
|
||
}
|
||
|
||
.el-timeline-item__node {
|
||
width: 12px;
|
||
height: 12px;
|
||
left: 0;
|
||
top: 4px;
|
||
border: 2px solid #fff;
|
||
box-shadow: 0 0 0 2px #409eff;
|
||
}
|
||
|
||
.el-timeline-item__wrapper {
|
||
top: 0;
|
||
padding-left: 28px;
|
||
|
||
.el-timeline-item__content {
|
||
padding-top: 0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.timeline-item-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0;
|
||
|
||
.node-title {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.node-content {
|
||
background: #f4f7fc;
|
||
border-radius: 4px;
|
||
padding: 12px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
|
||
.node-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
font-size: 13px;
|
||
color: #606266;
|
||
}
|
||
|
||
.node-user {
|
||
color: #303133;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.node-time {
|
||
color: #909399;
|
||
font-size: 12px;
|
||
margin-left: 8px;
|
||
}
|
||
|
||
.node-status {
|
||
display: inline-block;
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
border-radius: 2px;
|
||
width: fit-content;
|
||
|
||
&.status-initiate,
|
||
&.status-pass,
|
||
&.status-approve {
|
||
color: #67c23a;
|
||
background: transparent;
|
||
}
|
||
|
||
&.status-reject {
|
||
color: #f56c6c;
|
||
background: transparent;
|
||
}
|
||
|
||
&.status-approving,
|
||
&.status-pending {
|
||
color: #e6a23c;
|
||
background: transparent;
|
||
}
|
||
|
||
&.status-default {
|
||
color: #909399;
|
||
background: transparent;
|
||
}
|
||
}
|
||
|
||
.node-opinion {
|
||
font-size: 13px;
|
||
color: #000;
|
||
line-height: 1.6;
|
||
}
|
||
}
|
||
|
||
.empty-progress {
|
||
text-align: center;
|
||
padding: 40px 20px;
|
||
color: #909399;
|
||
font-size: 14px;
|
||
|
||
.empty-text {
|
||
display: inline-block;
|
||
}
|
||
}
|
||
}
|
||
|
||
.approval-progress-fade-enter-active,
|
||
.approval-progress-fade-leave-active {
|
||
transition: opacity 0.2s ease;
|
||
}
|
||
|
||
.approval-progress-fade-enter,
|
||
.approval-progress-fade-leave-to {
|
||
opacity: 0;
|
||
}
|
||
|
||
::v-deep .el-timeline-item__tail{
|
||
left: 7px;
|
||
top: 3px;
|
||
}
|
||
</style>
|