|
|
<template>
|
|
|
<div class="container">
|
|
|
<!-- 查询配置 -->
|
|
|
<div>
|
|
|
<div ref="lxHeader">
|
|
|
<LxHeader :custom="true" icon="el-icon-s-order" text="执行科室预算分解表提交" style="margin-bottom: 10px; border: 0px;">
|
|
|
<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="pending" />
|
|
|
<el-option label="草稿/已退回" value="draft" />
|
|
|
<el-option label="待科室负责人审核" value="submitted" />
|
|
|
<el-option label="待分管领导审核" value="manager_approved" />
|
|
|
<el-option label="终审通过" value="approved" />
|
|
|
</el-select>
|
|
|
</div>
|
|
|
<div class="actions">
|
|
|
<Button type="primary" @click="load">查询</Button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</LxHeader>
|
|
|
</div>
|
|
|
|
|
|
<!-- 分配给本科室的预算包列表 -->
|
|
|
<el-table
|
|
|
ref="packageTable"
|
|
|
v-loading="loading"
|
|
|
:data="packageData"
|
|
|
:height="tableHeight"
|
|
|
class="v-table"
|
|
|
style="width: 100%; margin-bottom: 20px;"
|
|
|
border
|
|
|
row-key="id"
|
|
|
>
|
|
|
<el-table-column type="index" align="center" label="序号" width="60" />
|
|
|
|
|
|
<el-table-column prop="budget_year" label="预算年度" width="100" align="center">
|
|
|
<template slot-scope="scope">
|
|
|
<el-tag type="primary">
|
|
|
{{ scope.row.budget_year ? scope.row.budget_year.year + '年' : '-' }}
|
|
|
</el-tag>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="parent_name" label="上级包名称" min-width="200">
|
|
|
<template slot-scope="scope">
|
|
|
<span style="color: #909399; font-size: 12px;">
|
|
|
{{ scope.row.parent_name || '-' }}
|
|
|
</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="executing_department" label="执行部门" min-width="150">
|
|
|
<template slot-scope="scope">
|
|
|
<span style="color: #606266; font-size: 13px;">
|
|
|
{{ scope.row.department.name || '-' }}
|
|
|
</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="allocated_amount" label="分配金额" width="160" align="right">
|
|
|
<template slot-scope="scope">
|
|
|
<span style="color: #409EFF; font-weight: bold;">
|
|
|
{{ formatCurrency(scope.row.allocated_amount) }} 万元
|
|
|
</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="submission_status" label="提交状态" width="220" align="center">
|
|
|
<template slot-scope="scope">
|
|
|
<el-tag :type="getStatusTagType(scope.row.submission_status)">
|
|
|
{{ getStatusText(scope.row.submission_status) }}
|
|
|
</el-tag>
|
|
|
<div
|
|
|
v-if="scope.row.submission_status === 'draft' && scope.row.submission && scope.row.submission.approval_notes"
|
|
|
class="return-note-wrapper"
|
|
|
>
|
|
|
<span class="return-note-text">退回意见:{{ scope.row.submission.approval_notes }}</span>
|
|
|
<el-button type="text" class="return-note-link" @click="showApprovalNotes(scope.row.submission.approval_notes)">
|
|
|
查看完整
|
|
|
</el-button>
|
|
|
</div>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="submission_date" label="提交时间" width="180" align="center">
|
|
|
<template slot-scope="scope">
|
|
|
{{ scope.row.submission_date ? formatDate(scope.row.submission_date) : '-' }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column fixed="right" label="操作" width="280">
|
|
|
<template slot-scope="scope">
|
|
|
<div class="action-group">
|
|
|
<Button
|
|
|
v-if="!scope.row.submission && canWrite(scope.row)"
|
|
|
type="primary"
|
|
|
size="small"
|
|
|
@click="openSubmissionForm(scope.row)"
|
|
|
>
|
|
|
填写分解表
|
|
|
</Button>
|
|
|
|
|
|
<Button
|
|
|
v-if="scope.row.submission && scope.row.submission.status === 'draft' && canWrite(scope.row)"
|
|
|
type="warning"
|
|
|
size="small"
|
|
|
@click="editSubmissionForm(scope.row)"
|
|
|
>
|
|
|
修改分解表
|
|
|
</Button>
|
|
|
|
|
|
<Button
|
|
|
v-if="scope.row.submission"
|
|
|
type="info"
|
|
|
size="small"
|
|
|
@click="viewSubmissionForm(scope.row)"
|
|
|
>
|
|
|
查看详情
|
|
|
</Button>
|
|
|
|
|
|
<Button
|
|
|
v-if="scope.row.submission"
|
|
|
size="small"
|
|
|
@click="openReviewHistory(scope.row)"
|
|
|
>
|
|
|
审核记录
|
|
|
</Button>
|
|
|
</div>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
</div>
|
|
|
|
|
|
<!-- 分解表填写对话框 -->
|
|
|
<el-dialog
|
|
|
:title="dialogTitle"
|
|
|
:visible.sync="submissionDialogVisible"
|
|
|
width="95%"
|
|
|
:close-on-click-modal="false"
|
|
|
top="5vh"
|
|
|
custom-class="submission-dialog"
|
|
|
>
|
|
|
<budget-submission-form
|
|
|
v-if="submissionDialogVisible"
|
|
|
:package-data="currentPackage"
|
|
|
:mode="submissionMode"
|
|
|
@save-draft="handleSubmissionDraft"
|
|
|
@submit="handleSubmissionSubmit"
|
|
|
@cancel="submissionDialogVisible = false"
|
|
|
/>
|
|
|
</el-dialog>
|
|
|
|
|
|
<el-dialog
|
|
|
title="审核记录"
|
|
|
:visible.sync="historyDialogVisible"
|
|
|
width="760px"
|
|
|
:close-on-click-modal="false"
|
|
|
>
|
|
|
<div v-if="currentHistoryPackage">
|
|
|
<el-descriptions :column="2" border size="small" class="history-summary">
|
|
|
<el-descriptions-item label="上级包名称">{{ currentHistoryPackage.parent_name || '-' }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="执行部门">{{ currentHistoryPackage.department ? currentHistoryPackage.department.name : '-' }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="提交状态">{{ getStatusText(currentHistoryPackage.submission_status) }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="提交时间">{{ currentHistoryPackage.submission_date ? formatDate(currentHistoryPackage.submission_date) : '-' }}</el-descriptions-item>
|
|
|
</el-descriptions>
|
|
|
<el-timeline v-if="reviewHistories(currentHistoryPackage).length > 0" class="history-timeline">
|
|
|
<el-timeline-item
|
|
|
v-for="history in reviewHistories(currentHistoryPackage)"
|
|
|
: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 LxHeader from '@/components/LxHeader/index.vue'
|
|
|
import { getBudgetCollectionYearOptions } from '@/api/budget/budgetCollectionYear.js'
|
|
|
import {
|
|
|
getDepartmentPackages,
|
|
|
getPackageDecomposition,
|
|
|
saveDepartmentSubmissionDraft,
|
|
|
submitDepartmentSubmission
|
|
|
} from '@/api/budget/departmentSubmission.js'
|
|
|
import BudgetSubmissionForm from './components/BudgetSubmissionForm.vue'
|
|
|
import { formatMoney } from '@/utils/budgetMoney'
|
|
|
|
|
|
export default {
|
|
|
name: 'DepartmentSubmission',
|
|
|
components: {
|
|
|
LxHeader,
|
|
|
BudgetSubmissionForm
|
|
|
},
|
|
|
data() {
|
|
|
return {
|
|
|
loading: false,
|
|
|
tableHeight: 0,
|
|
|
searchFields: {
|
|
|
year_id: '',
|
|
|
status: ''
|
|
|
},
|
|
|
packageData: [],
|
|
|
yearOptions: [],
|
|
|
submissionDialogVisible: false,
|
|
|
currentPackage: null,
|
|
|
currentHistoryPackage: null,
|
|
|
historyDialogVisible: false,
|
|
|
submissionMode: 'create' // create, edit, view
|
|
|
}
|
|
|
},
|
|
|
computed: {
|
|
|
dialogTitle() {
|
|
|
const titles = {
|
|
|
create: '填写预算分解表',
|
|
|
edit: '修改预算分解表',
|
|
|
view: '查看预算分解表'
|
|
|
}
|
|
|
return titles[this.submissionMode] || '预算分解表'
|
|
|
}
|
|
|
},
|
|
|
created() {
|
|
|
this.initLoad()
|
|
|
this.loadYearOptions()
|
|
|
},
|
|
|
methods: {
|
|
|
initLoad() {
|
|
|
const clientHeight = document.documentElement.clientHeight
|
|
|
const lxHeaderHeight = 140
|
|
|
const paginationHeight = 60
|
|
|
const topHeight = 50
|
|
|
const tableHeight = clientHeight - lxHeaderHeight - topHeight - paginationHeight - 20
|
|
|
this.tableHeight = tableHeight
|
|
|
},
|
|
|
|
|
|
async loadYearOptions() {
|
|
|
try {
|
|
|
const response = await getBudgetCollectionYearOptions()
|
|
|
if (response && response.length) {
|
|
|
this.yearOptions = response
|
|
|
if (this.yearOptions.length > 0 && !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.packageData = []
|
|
|
return
|
|
|
}
|
|
|
|
|
|
this.loading = true
|
|
|
try {
|
|
|
const params = {
|
|
|
...this.searchFields
|
|
|
}
|
|
|
|
|
|
const response = await getDepartmentPackages(params)
|
|
|
|
|
|
// 根据后端ApiResponse设计判断:无errcode表示成功
|
|
|
if (response && !response.errcode) {
|
|
|
this.packageData = Array.isArray(response) ? response : []
|
|
|
} else if (response && response.errcode) {
|
|
|
this.packageData = []
|
|
|
this.$message.warning(response.errmsg || '获取预算包列表失败')
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error('获取数据失败:', error)
|
|
|
|
|
|
if (error.response && error.response.data) {
|
|
|
const errorMessage = error.response.data.message || error.response.data.error || '获取数据失败'
|
|
|
this.$message.error(errorMessage)
|
|
|
} else {
|
|
|
this.$message.error('获取数据失败,请检查网络连接')
|
|
|
}
|
|
|
|
|
|
this.packageData = []
|
|
|
} finally {
|
|
|
this.loading = false
|
|
|
}
|
|
|
},
|
|
|
|
|
|
async openSubmissionForm(packageItem) {
|
|
|
await this.openSubmissionDialog(packageItem, 'create')
|
|
|
},
|
|
|
|
|
|
async editSubmissionForm(packageItem) {
|
|
|
if (!packageItem.submission || packageItem.submission.status !== 'draft' || !this.canWrite(packageItem)) {
|
|
|
this.$message.warning('当前申报状态不可修改')
|
|
|
return
|
|
|
}
|
|
|
await this.openSubmissionDialog(packageItem, 'edit')
|
|
|
},
|
|
|
|
|
|
async viewSubmissionForm(packageItem) {
|
|
|
await this.openSubmissionDialog(packageItem, 'view')
|
|
|
},
|
|
|
|
|
|
openReviewHistory(packageItem) {
|
|
|
this.currentHistoryPackage = packageItem
|
|
|
this.historyDialogVisible = true
|
|
|
},
|
|
|
|
|
|
async openSubmissionDialog(packageItem, mode) {
|
|
|
try {
|
|
|
const packageDetail = await getPackageDecomposition(packageItem.id)
|
|
|
this.currentPackage = {
|
|
|
...packageItem,
|
|
|
...packageDetail
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.warn('加载预算分解详情失败,回退到列表数据:', error.message)
|
|
|
this.currentPackage = { ...packageItem }
|
|
|
}
|
|
|
|
|
|
this.submissionMode = mode
|
|
|
this.submissionDialogVisible = true
|
|
|
},
|
|
|
|
|
|
async handleSubmissionSubmit(submissionData) {
|
|
|
try {
|
|
|
await submitDepartmentSubmission(this.currentPackage.id, submissionData)
|
|
|
this.$message.success('提交成功')
|
|
|
this.submissionDialogVisible = false
|
|
|
this.load()
|
|
|
} catch (error) {
|
|
|
console.error('提交失败:', error)
|
|
|
|
|
|
if (error.response && error.response.data && error.response.data.message) {
|
|
|
this.$message.error(error.response.data.message)
|
|
|
} else {
|
|
|
this.$message.error('提交失败')
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
|
|
|
async handleSubmissionDraft(submissionData) {
|
|
|
try {
|
|
|
await saveDepartmentSubmissionDraft(this.currentPackage.id, submissionData)
|
|
|
this.$message.success('草稿已保存')
|
|
|
this.submissionDialogVisible = false
|
|
|
this.load()
|
|
|
} catch (error) {
|
|
|
this.$message.error(error.message || '草稿保存失败')
|
|
|
}
|
|
|
},
|
|
|
|
|
|
formatCurrency(amount) {
|
|
|
return formatMoney(amount)
|
|
|
},
|
|
|
|
|
|
canWrite(packageItem) {
|
|
|
const year = packageItem.budget_year || {}
|
|
|
return year.status === 'ACTIVE'
|
|
|
},
|
|
|
|
|
|
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'
|
|
|
})
|
|
|
},
|
|
|
|
|
|
getStatusTagType(status) {
|
|
|
const typeMap = {
|
|
|
pending: 'warning',
|
|
|
draft: 'warning',
|
|
|
submitted: 'primary',
|
|
|
manager_approved: 'primary',
|
|
|
approved: 'success',
|
|
|
rejected: 'danger'
|
|
|
}
|
|
|
return typeMap[status] || 'info'
|
|
|
},
|
|
|
|
|
|
getStatusText(status) {
|
|
|
const textMap = {
|
|
|
pending: '待提交',
|
|
|
draft: '草稿/已退回',
|
|
|
submitted: '待科室负责人审核',
|
|
|
manager_approved: '待分管领导审核',
|
|
|
approved: '终审通过',
|
|
|
rejected: '已拒绝'
|
|
|
}
|
|
|
return textMap[status] || '未知'
|
|
|
},
|
|
|
|
|
|
showApprovalNotes(notes) {
|
|
|
this.$alert(notes || '无退回意见', '完整退回意见', {
|
|
|
confirmButtonText: '关闭'
|
|
|
})
|
|
|
},
|
|
|
|
|
|
reviewHistories(packageItem) {
|
|
|
const histories = packageItem && packageItem.submission && packageItem.submission.review_histories
|
|
|
? packageItem.submission.review_histories
|
|
|
: []
|
|
|
return histories.slice().sort((a, b) => new Date(b.operated_at || 0).getTime() - new Date(a.operated_at || 0).getTime())
|
|
|
},
|
|
|
|
|
|
getHistoryActionText(history) {
|
|
|
const actionMap = {
|
|
|
submit: '提交审核',
|
|
|
resubmit: '重新提交',
|
|
|
manager_approve: '科室负责人初审通过',
|
|
|
manager_return: '科室负责人退回修改',
|
|
|
leader_approve: '分管领导终审通过',
|
|
|
leader_return: '分管领导退回修改',
|
|
|
approve: '审核通过',
|
|
|
return: '退回修改'
|
|
|
}
|
|
|
return actionMap[history.action] || history.action || '未知操作'
|
|
|
},
|
|
|
|
|
|
getHistoryStatusFlow(history) {
|
|
|
return `${this.getStatusText(history.from_status)} -> ${this.getStatusText(history.to_status)}`
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
.return-note-wrapper {
|
|
|
margin-top: 4px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
gap: 4px;
|
|
|
}
|
|
|
|
|
|
.return-note-text {
|
|
|
font-size: 12px;
|
|
|
color: #e6a23c;
|
|
|
overflow: hidden;
|
|
|
text-overflow: ellipsis;
|
|
|
white-space: nowrap;
|
|
|
}
|
|
|
|
|
|
.return-note-link {
|
|
|
padding: 0;
|
|
|
font-size: 12px;
|
|
|
flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
.container {
|
|
|
padding: 12px 16px 16px 16px;
|
|
|
}
|
|
|
|
|
|
.v-table {
|
|
|
border: 1px solid #e6e6e6;
|
|
|
}
|
|
|
|
|
|
.search-panel {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
gap: 12px;
|
|
|
padding: 12px;
|
|
|
background: #fff;
|
|
|
border: 1px solid #ebeef5;
|
|
|
border-radius: 0;
|
|
|
}
|
|
|
|
|
|
.filters {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 12px;
|
|
|
}
|
|
|
|
|
|
.actions {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 10px;
|
|
|
}
|
|
|
|
|
|
.action-group {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
gap: 6px;
|
|
|
flex-wrap: nowrap;
|
|
|
white-space: nowrap;
|
|
|
}
|
|
|
|
|
|
/* 统一表格行高 */
|
|
|
.v-table >>> .el-table__body tr {
|
|
|
height: 50px !important;
|
|
|
}
|
|
|
|
|
|
.v-table >>> .el-table__body tr td {
|
|
|
height: 50px !important;
|
|
|
max-height: 50px !important;
|
|
|
vertical-align: middle !important;
|
|
|
padding: 8px 12px !important;
|
|
|
box-sizing: border-box !important;
|
|
|
}
|
|
|
|
|
|
/* 所有单元格内容的统一处理 */
|
|
|
.v-table >>> .el-table__body tr td .cell {
|
|
|
height: 34px !important;
|
|
|
max-height: 34px !important;
|
|
|
line-height: 18px !important;
|
|
|
overflow: hidden !important;
|
|
|
display: flex !important;
|
|
|
align-items: center !important;
|
|
|
}
|
|
|
|
|
|
/* 确保标签和按钮不会撑开行高 */
|
|
|
.v-table >>> .el-table__body tr td .el-tag {
|
|
|
height: 24px !important;
|
|
|
line-height: 22px !important;
|
|
|
}
|
|
|
|
|
|
.v-table >>> .el-table__body tr td .el-button {
|
|
|
height: 28px !important;
|
|
|
padding: 4px 8px !important;
|
|
|
}
|
|
|
|
|
|
.history-summary {
|
|
|
margin-bottom: 16px;
|
|
|
}
|
|
|
|
|
|
.history-timeline {
|
|
|
margin-top: 20px;
|
|
|
}
|
|
|
|
|
|
.timeline-card {
|
|
|
padding: 12px;
|
|
|
border: 1px solid #ebeef5;
|
|
|
border-radius: 6px;
|
|
|
background: #fff;
|
|
|
}
|
|
|
|
|
|
.timeline-title {
|
|
|
margin-bottom: 6px;
|
|
|
color: #303133;
|
|
|
font-weight: 600;
|
|
|
}
|
|
|
|
|
|
.timeline-meta {
|
|
|
color: #909399;
|
|
|
font-size: 12px;
|
|
|
}
|
|
|
|
|
|
.timeline-notes {
|
|
|
margin-top: 8px;
|
|
|
color: #606266;
|
|
|
line-height: 1.6;
|
|
|
word-break: break-all;
|
|
|
}
|
|
|
</style>
|
|
|
|
|
|
<style>
|
|
|
/* 分解表对话框样式 */
|
|
|
.submission-dialog {
|
|
|
margin-top: 5vh !important;
|
|
|
}
|
|
|
|
|
|
.submission-dialog .el-dialog__body {
|
|
|
padding: 10px 20px !important;
|
|
|
max-height: 80vh;
|
|
|
overflow-y: auto;
|
|
|
}
|
|
|
</style>
|