feat: refine department review interactions

master
weizong song 2 weeks ago
parent 96b3561c08
commit bac4110db3

@ -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>

Loading…
Cancel
Save