You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
644 lines
19 KiB
644 lines
19 KiB
<template>
|
|
<div class="container">
|
|
<div ref="lxHeader">
|
|
<LxHeader :custom="true" icon="el-icon-s-check" text="执行科室分解审核" style="margin-bottom: 10px; border: 0;">
|
|
<div>
|
|
<div class="search-panel">
|
|
<div class="filters">
|
|
<el-select
|
|
v-model="searchFields.year_id"
|
|
placeholder="选择预算年度"
|
|
style="width: 150px"
|
|
@change="load"
|
|
>
|
|
<el-option
|
|
v-for="item in yearOptions"
|
|
:key="item.value"
|
|
:label="item.year + '年'"
|
|
:value="item.value"
|
|
/>
|
|
</el-select>
|
|
|
|
<el-select
|
|
v-model="searchFields.status"
|
|
placeholder="审核状态"
|
|
style="width: 150px"
|
|
clearable
|
|
@change="load"
|
|
>
|
|
<el-option label="草稿" value="draft" />
|
|
<el-option label="已提交" value="submitted" />
|
|
<el-option label="审核通过" value="approved" />
|
|
</el-select>
|
|
</div>
|
|
<div class="actions">
|
|
<el-button type="primary" @click="load">查询</el-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</LxHeader>
|
|
</div>
|
|
|
|
<el-table
|
|
ref="reviewTable"
|
|
v-loading="loading"
|
|
:data="tableData"
|
|
:height="tableHeight"
|
|
class="v-table"
|
|
style="width: 100%; margin-bottom: 20px;"
|
|
border
|
|
>
|
|
<el-table-column type="index" align="center" label="序号" width="60" />
|
|
|
|
<el-table-column label="预算年度" width="100" align="center">
|
|
<template slot-scope="scope">
|
|
<el-tag type="primary">{{ getBudgetYear(scope.row) }}</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column label="牵头项目" min-width="180">
|
|
<template slot-scope="scope">
|
|
<span class="package-name">{{ getParentPackageName(scope.row) }}</span>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column label="执行科室包" min-width="180">
|
|
<template slot-scope="scope">
|
|
<span class="package-name">{{ getPackageName(scope.row) }}</span>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column label="执行科室" width="140" align="center">
|
|
<template slot-scope="scope">
|
|
<el-tag type="info">{{ getDepartmentName(scope.row) }}</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column label="分配金额" width="140" align="right">
|
|
<template slot-scope="scope">
|
|
<span class="amount-text">{{ formatCurrency(getAllocatedAmount(scope.row)) }} 万元</span>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column label="提交状态" width="120" align="center">
|
|
<template slot-scope="scope">
|
|
<el-tag :type="getStatusType(scope.row.status)">
|
|
{{ getStatusText(scope.row.status) }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column label="提交人" width="120" align="center">
|
|
<template slot-scope="scope">
|
|
{{ scope.row.submitter ? scope.row.submitter.name : '-' }}
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column label="提交时间" width="170" align="center">
|
|
<template slot-scope="scope">
|
|
{{ formatDate(scope.row.submitted_at) }}
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column label="最新审核意见" min-width="220">
|
|
<template slot-scope="scope">
|
|
<span>{{ scope.row.approval_notes || '-' }}</span>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<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="warning"
|
|
size="mini"
|
|
@click="openReviewDialog(scope.row)"
|
|
>
|
|
审核
|
|
</el-button>
|
|
<el-button size="mini" @click="openHistoryDialog(scope.row)">审核记录</el-button>
|
|
</div>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
|
|
<el-pagination
|
|
v-if="total > 0"
|
|
:current-page="currentPage"
|
|
:page-sizes="[10, 15, 20, 50]"
|
|
:page-size="pageSize"
|
|
:total="total"
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
style="margin-top: 20px; text-align: right;"
|
|
@size-change="handleSizeChange"
|
|
@current-change="handleCurrentChange"
|
|
/>
|
|
|
|
<el-dialog
|
|
title="执行科室分解详情"
|
|
:visible.sync="detailDialogVisible"
|
|
width="95%"
|
|
:close-on-click-modal="false"
|
|
top="5vh"
|
|
custom-class="submission-dialog"
|
|
>
|
|
<budget-submission-form
|
|
v-if="detailDialogVisible && currentPackage"
|
|
:package-data="currentPackage"
|
|
mode="view"
|
|
@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>
|
|
|
|
<script>
|
|
import { formatMoney } from '@/utils/budgetMoney'
|
|
import LxHeader from '@/components/LxHeader/index.vue'
|
|
import BudgetSubmissionForm from '@/views/budget/collection/components/BudgetSubmissionForm.vue'
|
|
import { getBudgetCollectionYearOptions } from '@/api/budget/budgetCollectionYear.js'
|
|
import { getDepartmentSubmissions, reviewDepartmentSubmission } from '@/api/budget/leadDepartment.js'
|
|
|
|
export default {
|
|
name: 'DepartmentReviewManagement',
|
|
components: {
|
|
LxHeader,
|
|
BudgetSubmissionForm
|
|
},
|
|
data() {
|
|
return {
|
|
loading: false,
|
|
tableHeight: 0,
|
|
searchFields: {
|
|
year_id: '',
|
|
status: ''
|
|
},
|
|
tableData: [],
|
|
total: 0,
|
|
currentPage: 1,
|
|
pageSize: 15,
|
|
yearOptions: [],
|
|
detailDialogVisible: false,
|
|
reviewDialogVisible: false,
|
|
historyDialogVisible: false,
|
|
currentPackage: null,
|
|
currentReviewRow: null,
|
|
currentHistoryRow: null,
|
|
reviewSubmitting: false,
|
|
reviewForm: {
|
|
action: 'approve',
|
|
approval_notes: ''
|
|
}
|
|
}
|
|
},
|
|
created() {
|
|
this.initLoad()
|
|
this.loadYearOptions()
|
|
},
|
|
methods: {
|
|
initLoad() {
|
|
const clientHeight = document.documentElement.clientHeight
|
|
const lxHeaderHeight = 140
|
|
const alertHeight = 70
|
|
const paginationHeight = 60
|
|
const topHeight = 50
|
|
this.tableHeight = clientHeight - lxHeaderHeight - alertHeight - topHeight - paginationHeight - 20
|
|
},
|
|
|
|
async loadYearOptions() {
|
|
try {
|
|
const response = await getBudgetCollectionYearOptions()
|
|
if (response && response.length) {
|
|
this.yearOptions = response
|
|
if (!this.searchFields.year_id) {
|
|
const activeYear = this.yearOptions.find(item => item.status === 'ACTIVE')
|
|
this.searchFields.year_id = (activeYear || this.yearOptions[0]).value
|
|
}
|
|
this.$nextTick(() => this.load())
|
|
} else {
|
|
this.$message.warning('未找到可用的预算年度,请联系管理员')
|
|
}
|
|
} catch (error) {
|
|
console.error('加载年度选项失败:', error)
|
|
this.$message.error('加载年度选项失败,请刷新页面重试')
|
|
}
|
|
},
|
|
|
|
async load() {
|
|
if (!this.searchFields.year_id) {
|
|
this.$message.warning('请选择预算年度')
|
|
this.tableData = []
|
|
this.total = 0
|
|
return
|
|
}
|
|
|
|
this.loading = true
|
|
try {
|
|
const response = await getDepartmentSubmissions({
|
|
...this.searchFields,
|
|
page_size: this.pageSize,
|
|
page: this.currentPage
|
|
})
|
|
|
|
this.tableData = response && response.data ? response.data : []
|
|
this.total = response ? response.total || 0 : 0
|
|
this.currentPage = response ? response.current_page || 1 : 1
|
|
} catch (error) {
|
|
console.error('获取执行科室分解审核列表失败:', error)
|
|
this.$message.error(error.message || '获取执行科室分解审核列表失败')
|
|
this.tableData = []
|
|
this.total = 0
|
|
} finally {
|
|
this.loading = false
|
|
}
|
|
},
|
|
|
|
viewDetail(row) {
|
|
if (!row.package) {
|
|
this.$message.warning('预算包数据不存在')
|
|
return
|
|
}
|
|
this.currentPackage = { ...row.package }
|
|
this.detailDialogVisible = true
|
|
},
|
|
|
|
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
|
|
},
|
|
|
|
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 {
|
|
await reviewDepartmentSubmission(this.currentReviewRow.id, {
|
|
action: this.reviewForm.action,
|
|
approval_notes: this.reviewForm.approval_notes.trim()
|
|
})
|
|
this.$message.success(this.reviewForm.action === 'approve' ? '审核通过' : '已退回执行科室修改')
|
|
this.reviewDialogVisible = false
|
|
await this.load()
|
|
} catch (error) {
|
|
console.error('执行科室分解审核失败:', error)
|
|
} finally {
|
|
this.reviewSubmitting = false
|
|
}
|
|
},
|
|
|
|
handleSizeChange(newSize) {
|
|
this.pageSize = newSize
|
|
this.currentPage = 1
|
|
this.load()
|
|
},
|
|
|
|
handleCurrentChange(newPage) {
|
|
this.currentPage = newPage
|
|
this.load()
|
|
},
|
|
|
|
formatCurrency(amount) {
|
|
return formatMoney(amount)
|
|
},
|
|
|
|
formatDate(dateStr) {
|
|
if (!dateStr) return '-'
|
|
const date = new Date(dateStr)
|
|
return date.toLocaleDateString('zh-CN') + ' ' + date.toLocaleTimeString('zh-CN', {
|
|
hour12: false,
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
})
|
|
},
|
|
|
|
getBudgetYear(row) {
|
|
return row.package && row.package.budget_year ? `${row.package.budget_year.year}年` : '-'
|
|
},
|
|
|
|
getParentPackageName(row) {
|
|
return row.package && row.package.parent ? row.package.parent.name : '-'
|
|
},
|
|
|
|
getPackageName(row) {
|
|
return row.package ? row.package.name : '-'
|
|
},
|
|
|
|
getDepartmentName(row) {
|
|
return row.package && row.package.department ? row.package.department.name : '-'
|
|
},
|
|
|
|
getAllocatedAmount(row) {
|
|
return row.package ? row.package.allocated_amount : '0.0000'
|
|
},
|
|
|
|
getStatusType(status) {
|
|
const typeMap = {
|
|
draft: 'info',
|
|
submitted: 'warning',
|
|
approved: 'success'
|
|
}
|
|
return typeMap[status] || 'info'
|
|
},
|
|
|
|
getStatusText(status) {
|
|
const textMap = {
|
|
draft: '草稿',
|
|
submitted: '已提交',
|
|
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}`
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.container {
|
|
padding: 12px 16px 16px;
|
|
}
|
|
|
|
.search-panel {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.filters,
|
|
.actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.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 {
|
|
font-weight: 600;
|
|
color: #303133;
|
|
}
|
|
|
|
.amount-text {
|
|
color: #409EFF;
|
|
font-weight: 600;
|
|
}
|
|
|
|
@media (max-width: 1400px) {
|
|
.review-layout {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.review-action-panel {
|
|
width: 100%;
|
|
}
|
|
}
|
|
</style>
|