|
|
|
|
@ -106,26 +106,19 @@
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
|
|
|
|
<el-table-column fixed="right" label="操作" width="220" align="center">
|
|
|
|
|
<el-table-column fixed="right" label="操作" width="300" align="center">
|
|
|
|
|
<template slot-scope="scope">
|
|
|
|
|
<div class="action-group">
|
|
|
|
|
<el-button type="primary" size="mini" @click="viewDetail(scope.row)">查看详情</el-button>
|
|
|
|
|
<el-button
|
|
|
|
|
v-if="scope.row.status === 'submitted'"
|
|
|
|
|
type="success"
|
|
|
|
|
type="warning"
|
|
|
|
|
size="mini"
|
|
|
|
|
@click="approveSubmission(scope.row)"
|
|
|
|
|
@click="openReviewDialog(scope.row)"
|
|
|
|
|
>
|
|
|
|
|
通过
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button
|
|
|
|
|
v-if="scope.row.status === 'submitted'"
|
|
|
|
|
type="danger"
|
|
|
|
|
size="mini"
|
|
|
|
|
@click="returnSubmission(scope.row)"
|
|
|
|
|
>
|
|
|
|
|
退回
|
|
|
|
|
审核
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button size="mini" @click="openHistoryDialog(scope.row)">审核记录</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
@ -158,6 +151,109 @@
|
|
|
|
|
@cancel="detailDialogVisible = false"
|
|
|
|
|
/>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
<el-dialog
|
|
|
|
|
title="执行科室分解审核"
|
|
|
|
|
:visible.sync="reviewDialogVisible"
|
|
|
|
|
width="96%"
|
|
|
|
|
:close-on-click-modal="false"
|
|
|
|
|
top="3vh"
|
|
|
|
|
custom-class="submission-dialog review-dialog"
|
|
|
|
|
>
|
|
|
|
|
<div v-if="reviewDialogVisible && currentReviewRow && currentPackage" class="review-layout">
|
|
|
|
|
<div class="review-detail-panel">
|
|
|
|
|
<budget-submission-form
|
|
|
|
|
:package-data="currentPackage"
|
|
|
|
|
mode="view"
|
|
|
|
|
@cancel="reviewDialogVisible = false"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="review-action-panel">
|
|
|
|
|
<div class="panel-title">审核操作</div>
|
|
|
|
|
<el-descriptions :column="1" border size="small" class="review-summary">
|
|
|
|
|
<el-descriptions-item label="牵头项目">{{ getParentPackageName(currentReviewRow) }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="执行科室包">{{ getPackageName(currentReviewRow) }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="执行科室">{{ getDepartmentName(currentReviewRow) }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="提交状态">{{ getStatusText(currentReviewRow.status) }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="提交时间">{{ formatDate(currentReviewRow.submitted_at) }}</el-descriptions-item>
|
|
|
|
|
</el-descriptions>
|
|
|
|
|
|
|
|
|
|
<el-form label-position="top" class="review-form">
|
|
|
|
|
<el-form-item label="审核结论">
|
|
|
|
|
<el-radio-group v-model="reviewForm.action">
|
|
|
|
|
<el-radio label="approve">通过</el-radio>
|
|
|
|
|
<el-radio label="return">退回</el-radio>
|
|
|
|
|
</el-radio-group>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item :label="reviewForm.action === 'return' ? '退回意见' : '审核备注(选填)'">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="reviewForm.approval_notes"
|
|
|
|
|
type="textarea"
|
|
|
|
|
:rows="8"
|
|
|
|
|
:maxlength="1000"
|
|
|
|
|
:placeholder="reviewForm.action === 'return' ? '请输入退回修改意见' : '可填写审核说明'"
|
|
|
|
|
show-word-limit
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
|
|
|
|
|
<div class="panel-title panel-title-spaced">最近审核记录</div>
|
|
|
|
|
<div v-if="reviewHistories(currentReviewRow).length > 0" class="inline-history-list">
|
|
|
|
|
<div
|
|
|
|
|
v-for="history in reviewHistories(currentReviewRow).slice(0, 5)"
|
|
|
|
|
:key="history.id"
|
|
|
|
|
class="inline-history-item"
|
|
|
|
|
>
|
|
|
|
|
<div class="inline-history-head">
|
|
|
|
|
<span>{{ getHistoryActionText(history) }}</span>
|
|
|
|
|
<span>{{ formatDate(history.operated_at) }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="inline-history-meta">
|
|
|
|
|
{{ history.operator ? history.operator.name : '-' }} · {{ getHistoryStatusFlow(history) }}
|
|
|
|
|
</div>
|
|
|
|
|
<div v-if="history.notes" class="inline-history-notes">{{ history.notes }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<el-empty v-else :image-size="60" description="暂无审核记录" />
|
|
|
|
|
|
|
|
|
|
<div class="review-action-footer">
|
|
|
|
|
<el-button @click="reviewDialogVisible = false">取消</el-button>
|
|
|
|
|
<el-button type="primary" :loading="reviewSubmitting" @click="submitReview">确认审核</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
<el-dialog
|
|
|
|
|
title="审核记录"
|
|
|
|
|
:visible.sync="historyDialogVisible"
|
|
|
|
|
width="760px"
|
|
|
|
|
:close-on-click-modal="false"
|
|
|
|
|
>
|
|
|
|
|
<div v-if="currentHistoryRow">
|
|
|
|
|
<el-descriptions :column="2" border size="small" class="history-summary">
|
|
|
|
|
<el-descriptions-item label="牵头项目">{{ getParentPackageName(currentHistoryRow) }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="执行科室包">{{ getPackageName(currentHistoryRow) }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="执行科室">{{ getDepartmentName(currentHistoryRow) }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="当前状态">{{ getStatusText(currentHistoryRow.status) }}</el-descriptions-item>
|
|
|
|
|
</el-descriptions>
|
|
|
|
|
<el-timeline v-if="reviewHistories(currentHistoryRow).length > 0" class="history-timeline">
|
|
|
|
|
<el-timeline-item
|
|
|
|
|
v-for="history in reviewHistories(currentHistoryRow)"
|
|
|
|
|
:key="history.id"
|
|
|
|
|
:timestamp="formatDate(history.operated_at)"
|
|
|
|
|
placement="top"
|
|
|
|
|
>
|
|
|
|
|
<div class="timeline-card">
|
|
|
|
|
<div class="timeline-title">{{ getHistoryActionText(history) }}</div>
|
|
|
|
|
<div class="timeline-meta">{{ history.operator ? history.operator.name : '-' }} · {{ getHistoryStatusFlow(history) }}</div>
|
|
|
|
|
<div v-if="history.notes" class="timeline-notes">{{ history.notes }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</el-timeline-item>
|
|
|
|
|
</el-timeline>
|
|
|
|
|
<el-empty v-else :image-size="80" description="暂无审核记录" />
|
|
|
|
|
</div>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
@ -188,7 +284,16 @@ export default {
|
|
|
|
|
pageSize: 15,
|
|
|
|
|
yearOptions: [],
|
|
|
|
|
detailDialogVisible: false,
|
|
|
|
|
currentPackage: null
|
|
|
|
|
reviewDialogVisible: false,
|
|
|
|
|
historyDialogVisible: false,
|
|
|
|
|
currentPackage: null,
|
|
|
|
|
currentReviewRow: null,
|
|
|
|
|
currentHistoryRow: null,
|
|
|
|
|
reviewSubmitting: false,
|
|
|
|
|
reviewForm: {
|
|
|
|
|
action: 'approve',
|
|
|
|
|
approval_notes: ''
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
created() {
|
|
|
|
|
@ -262,35 +367,47 @@ export default {
|
|
|
|
|
this.detailDialogVisible = true
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async approveSubmission(row) {
|
|
|
|
|
try {
|
|
|
|
|
await reviewDepartmentSubmission(row.id, { action: 'approve' })
|
|
|
|
|
this.$message.success('审核通过')
|
|
|
|
|
this.load()
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('执行科室分解审核失败:', error)
|
|
|
|
|
openReviewDialog(row) {
|
|
|
|
|
if (!row.package) {
|
|
|
|
|
this.$message.warning('预算包数据不存在')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
this.currentReviewRow = row
|
|
|
|
|
this.currentPackage = { ...row.package }
|
|
|
|
|
this.reviewForm = {
|
|
|
|
|
action: 'approve',
|
|
|
|
|
approval_notes: ''
|
|
|
|
|
}
|
|
|
|
|
this.reviewDialogVisible = true
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async returnSubmission(row) {
|
|
|
|
|
openHistoryDialog(row) {
|
|
|
|
|
this.currentHistoryRow = row
|
|
|
|
|
this.historyDialogVisible = true
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async submitReview() {
|
|
|
|
|
if (!this.currentReviewRow) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if (this.reviewForm.action === 'return' && !this.reviewForm.approval_notes.trim()) {
|
|
|
|
|
this.$message.warning('请输入退回修改意见')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.reviewSubmitting = true
|
|
|
|
|
try {
|
|
|
|
|
const result = await this.$prompt('请输入退回修改意见', '退回执行科室分解', {
|
|
|
|
|
confirmButtonText: '确认退回',
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
inputValidator: value => Boolean(value && value.trim()),
|
|
|
|
|
inputErrorMessage: '退回意见不能为空'
|
|
|
|
|
await reviewDepartmentSubmission(this.currentReviewRow.id, {
|
|
|
|
|
action: this.reviewForm.action,
|
|
|
|
|
approval_notes: this.reviewForm.approval_notes.trim()
|
|
|
|
|
})
|
|
|
|
|
await reviewDepartmentSubmission(row.id, {
|
|
|
|
|
action: 'return',
|
|
|
|
|
approval_notes: result.value.trim()
|
|
|
|
|
})
|
|
|
|
|
this.$message.success('已退回执行科室修改')
|
|
|
|
|
this.load()
|
|
|
|
|
this.$message.success(this.reviewForm.action === 'approve' ? '审核通过' : '已退回执行科室修改')
|
|
|
|
|
this.reviewDialogVisible = false
|
|
|
|
|
await this.load()
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (error === 'cancel' || error === 'close') {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
console.error('退回执行科室分解失败:', error)
|
|
|
|
|
console.error('执行科室分解审核失败:', error)
|
|
|
|
|
} finally {
|
|
|
|
|
this.reviewSubmitting = false
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
@ -355,6 +472,28 @@ export default {
|
|
|
|
|
approved: '审核通过'
|
|
|
|
|
}
|
|
|
|
|
return textMap[status] || '未知'
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
reviewHistories(row) {
|
|
|
|
|
return (row.review_histories || []).slice().sort((a, b) => {
|
|
|
|
|
return new Date(b.operated_at || 0).getTime() - new Date(a.operated_at || 0).getTime()
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
getHistoryActionText(history) {
|
|
|
|
|
const actionMap = {
|
|
|
|
|
submit: '提交审核',
|
|
|
|
|
resubmit: '重新提交',
|
|
|
|
|
approve: '审核通过',
|
|
|
|
|
return: '退回修改'
|
|
|
|
|
}
|
|
|
|
|
return actionMap[history.action] || history.action || '未知操作'
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
getHistoryStatusFlow(history) {
|
|
|
|
|
const fromText = this.getStatusText(history.from_status)
|
|
|
|
|
const toText = this.getStatusText(history.to_status)
|
|
|
|
|
return `${fromText} -> ${toText}`
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -373,16 +512,113 @@ export default {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.filters,
|
|
|
|
|
.actions,
|
|
|
|
|
.action-group {
|
|
|
|
|
.actions {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.process-note {
|
|
|
|
|
.action-group {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
flex-wrap: nowrap;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.review-layout {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 16px;
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.review-detail-panel {
|
|
|
|
|
min-width: 0;
|
|
|
|
|
flex: 1 1 auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.review-action-panel {
|
|
|
|
|
width: 360px;
|
|
|
|
|
padding: 16px;
|
|
|
|
|
border: 1px solid #ebeef5;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
background: #fafbfd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.panel-title {
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
color: #303133;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.panel-title-spaced {
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.review-summary,
|
|
|
|
|
.history-summary {
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.review-form {
|
|
|
|
|
margin-top: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.inline-history-list {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.inline-history-item,
|
|
|
|
|
.timeline-card {
|
|
|
|
|
padding: 12px;
|
|
|
|
|
border: 1px solid #ebeef5;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
background: #fff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.inline-history-head {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
margin-bottom: 6px;
|
|
|
|
|
color: #303133;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.inline-history-meta,
|
|
|
|
|
.timeline-meta {
|
|
|
|
|
color: #909399;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.inline-history-notes,
|
|
|
|
|
.timeline-notes {
|
|
|
|
|
margin-top: 8px;
|
|
|
|
|
color: #606266;
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
word-break: break-all;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.timeline-title {
|
|
|
|
|
margin-bottom: 6px;
|
|
|
|
|
color: #303133;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.history-timeline {
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.review-action-footer {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.package-name {
|
|
|
|
|
@ -394,4 +630,14 @@ export default {
|
|
|
|
|
color: #409EFF;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 1400px) {
|
|
|
|
|
.review-layout {
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.review-action-panel {
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
|