提交
This commit is contained in:
commit
93e1fd0338
73 files changed
+19101
-1608
No files matched your search
@@ -82,20 +82,68 @@ export const getApprovalRecord = (id: string | number): AxiosPromise<CwApprovalR
|
||||
|
||||
// 审批申报(单个)
|
||||
export const approveApplication = (data: CwApprovalForm) => {
|
||||
// 🔧 修复:使用部门审批接口,将JSON数据转换为URL参数
|
||||
const params = new URLSearchParams();
|
||||
params.append('applicationId', String(data.applicationId || ''));
|
||||
|
||||
// 🔧 修复:正确处理审批结果映射
|
||||
let approveAction = 'return'; // 默认为退回
|
||||
if (data.approvalResult === 'approve') {
|
||||
approveAction = 'approve';
|
||||
} else if (data.approvalResult === 'return') {
|
||||
approveAction = 'return';
|
||||
}
|
||||
|
||||
params.append('approveAction', approveAction);
|
||||
params.append('approveOpinion', data.approvalComment || '');
|
||||
|
||||
return request({
|
||||
url: '/cw/approval/process',
|
||||
url: '/cw/approval/dept', // 🔧 修复:使用部门审批接口
|
||||
method: 'post',
|
||||
data: data
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
data: params.toString()
|
||||
});
|
||||
};
|
||||
|
||||
// 批量审批申报
|
||||
export const batchApproveApplication = (data: CwApprovalForm) => {
|
||||
return request({
|
||||
url: '/cw/approval/batchApprove',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
export const batchApproveApplication = async (data: CwApprovalForm) => {
|
||||
// 🔧 修复:后端没有批量接口,改为逐个调用
|
||||
if (!data.applicationIds || data.applicationIds.length === 0) {
|
||||
throw new Error('未选择要审批的申报项目');
|
||||
}
|
||||
|
||||
console.log('🔧 批量审批开始,项目数量:', data.applicationIds.length);
|
||||
const results = [];
|
||||
|
||||
for (const applicationId of data.applicationIds) {
|
||||
try {
|
||||
const singleData = {
|
||||
applicationId: applicationId,
|
||||
approvalResult: data.approvalResult,
|
||||
approvalComment: data.approvalComment,
|
||||
nextApprover: data.nextApprover
|
||||
};
|
||||
|
||||
console.log('🔧 处理项目ID:', applicationId);
|
||||
const result = await approveApplication(singleData);
|
||||
results.push({ applicationId, success: true, result });
|
||||
} catch (error) {
|
||||
console.error('🔧 项目审批失败,ID:', applicationId, 'Error:', error);
|
||||
results.push({ applicationId, success: false, error });
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否有失败的项目
|
||||
const failures = results.filter(r => !r.success);
|
||||
if (failures.length > 0) {
|
||||
console.error('🔧 批量审批部分失败:', failures);
|
||||
throw new Error(`批量审批完成,${failures.length}个项目审批失败`);
|
||||
}
|
||||
|
||||
console.log('🔧 批量审批全部成功');
|
||||
return { code: 200, msg: '批量审批成功', data: results };
|
||||
};
|
||||
|
||||
// 获取审批历史记录
|
||||
@@ -133,6 +181,42 @@ export const getDepartmentApprovals = (query: CwApprovalRecordQuery): AxiosPromi
|
||||
});
|
||||
};
|
||||
|
||||
// 获取财务审批待审批列表
|
||||
export const getFinancePendingApprovals = (query: CwApprovalRecordQuery): AxiosPromise<CwApprovalRecordVO[]> => {
|
||||
return request({
|
||||
url: '/cw/approval/finance/pending',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
// 获取财务审批已审批列表
|
||||
export const getFinanceApprovedRecords = (query: CwApprovalRecordQuery): AxiosPromise<CwApprovalRecordVO[]> => {
|
||||
return request({
|
||||
url: '/cw/approval/finance/approved',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
// 获取学院领导审批待审批列表
|
||||
export const getLeaderPendingApprovals = (query: CwApprovalRecordQuery): AxiosPromise<CwApprovalRecordVO[]> => {
|
||||
return request({
|
||||
url: '/cw/approval/leader/pending',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
// 获取学院领导已审批列表
|
||||
export const getLeaderApprovedApprovals = (query: CwApprovalRecordQuery): AxiosPromise<CwApprovalRecordVO[]> => {
|
||||
return request({
|
||||
url: '/cw/approval/leader/approved',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
// 获取可选审批人列表
|
||||
export const getAvailableApprovers = (applicationId: string | number): AxiosPromise<any[]> => {
|
||||
return request({
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
|
||||
export interface FinanceApprovalForm {
|
||||
applicationId: number;
|
||||
approvalResult: string; // 'approve' | 'reject'
|
||||
approvalComment: string;
|
||||
selectedApproverId?: number; // 选择的院领导ID(审批通过时必填)
|
||||
}
|
||||
|
||||
// 财务审批操作
|
||||
export const financeApproveApplication = (data: FinanceApprovalForm): AxiosPromise<any> => {
|
||||
const params = new URLSearchParams({
|
||||
applicationId: data.applicationId.toString(),
|
||||
approveAction: data.approvalResult,
|
||||
approveOpinion: data.approvalComment
|
||||
});
|
||||
|
||||
// 如果选择了院领导,添加参数
|
||||
if (data.selectedApproverId) {
|
||||
params.append('selectedApproverId', data.selectedApproverId.toString());
|
||||
}
|
||||
|
||||
return request({
|
||||
url: '/cw/approval/finance',
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
data: params
|
||||
});
|
||||
};
|
||||
|
||||
// 财务批量审批操作
|
||||
export const batchFinanceApproveApplication = (dataList: FinanceApprovalForm[]): Promise<any> => {
|
||||
// 前端模拟批量操作,逐个调用单个审批接口
|
||||
const promises = dataList.map(item => financeApproveApplication(item));
|
||||
|
||||
return Promise.all(promises).then(results => {
|
||||
return {
|
||||
data: results,
|
||||
code: 200,
|
||||
message: '批量操作成功'
|
||||
};
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
|
||||
export interface LeaderApprovalForm {
|
||||
applicationId: number;
|
||||
approvalResult: string; // 'approve' | 'reject'
|
||||
approvalComment: string;
|
||||
}
|
||||
|
||||
// 分管领导审批操作
|
||||
export const leaderApproveApplication = (data: LeaderApprovalForm): AxiosPromise<any> => {
|
||||
return request({
|
||||
url: '/cw/approval/leader',
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
data: new URLSearchParams({
|
||||
applicationId: data.applicationId.toString(),
|
||||
approveAction: data.approvalResult,
|
||||
approveOpinion: data.approvalComment
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
// 分管领导批量审批操作
|
||||
export const batchLeaderApproveApplication = (dataList: LeaderApprovalForm[]): AxiosPromise<any> => {
|
||||
// 前端模拟批量操作,逐个调用单个审批接口
|
||||
const promises = dataList.map(item => leaderApproveApplication(item));
|
||||
|
||||
return Promise.all(promises).then(results => {
|
||||
return {
|
||||
data: results,
|
||||
code: 200,
|
||||
message: '批量操作成功'
|
||||
};
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
import request from '@/utils/request'
|
||||
import { AxiosPromise } from 'axios'
|
||||
|
||||
export interface StatisticsQuery {
|
||||
budgetYear?: string
|
||||
}
|
||||
|
||||
// 获取项目清单(用于统计页面)
|
||||
export function getStatisticsList(query: StatisticsQuery): AxiosPromise<any> {
|
||||
return request({
|
||||
url: '/cw/application/statistics/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 获取状态统计
|
||||
export function getStatusStatistics(budgetYear?: string): AxiosPromise<any> {
|
||||
return request({
|
||||
url: '/cw/application/statistics/status',
|
||||
method: 'get',
|
||||
params: { budgetYear }
|
||||
})
|
||||
}
|
||||
|
||||
// 获取部门预算汇总统计
|
||||
export function getDepartmentStatistics(budgetYear?: string): AxiosPromise<any> {
|
||||
return request({
|
||||
url: '/cw/application/statistics/department',
|
||||
method: 'get',
|
||||
params: { budgetYear }
|
||||
})
|
||||
}
|
||||
|
||||
// 获取年度汇总统计
|
||||
export function getYearlyStatistics(): AxiosPromise<any> {
|
||||
return request({
|
||||
url: '/cw/application/statistics/yearly',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* 财务项目预算审批 - TypeScript类型定义
|
||||
*
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-01-26
|
||||
*/
|
||||
@@ -39,6 +39,7 @@ export interface CwProjectApplicationVO {
|
||||
totalMunicipalFiscal?: number;
|
||||
totalSelfFunding?: number;
|
||||
totalOtherFunding?: number;
|
||||
// 分年度预算明细
|
||||
currentYearTotal?: number;
|
||||
currentYearMunicipal?: number;
|
||||
currentYearSelf?: number;
|
||||
@@ -64,6 +65,7 @@ export interface CwProjectApplicationVO {
|
||||
updateBy?: number;
|
||||
updateTime?: string;
|
||||
remark?: string;
|
||||
performanceIndicators?: any[]; // 绩效指标数据
|
||||
}
|
||||
|
||||
// 项目预算申报表单对象
|
||||
@@ -86,6 +88,7 @@ export interface CwProjectApplicationForm {
|
||||
totalMunicipalFiscal?: number;
|
||||
totalSelfFunding?: number;
|
||||
totalOtherFunding?: number;
|
||||
// 分年度预算明细
|
||||
currentYearTotal?: number;
|
||||
currentYearMunicipal?: number;
|
||||
currentYearSelf?: number;
|
||||
@@ -199,4 +202,4 @@ export interface StatusDistribution {
|
||||
statusName: string;
|
||||
count: number;
|
||||
percentage: number;
|
||||
}
|
||||
}
|
||||
@@ -315,54 +315,136 @@
|
||||
<el-col :span="6">
|
||||
<el-form-item label="年度执行合计" prop="yearlyTotal">
|
||||
<el-input-number
|
||||
v-model="form.yearlyTotal"
|
||||
:model-value="yearlyTotals.total"
|
||||
:precision="2"
|
||||
:min="0"
|
||||
placeholder="万元"
|
||||
size="large"
|
||||
style="width: 100%"
|
||||
:disabled="true"
|
||||
readonly
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="年度财政资金" prop="yearlyMunicipalFiscal">
|
||||
<el-input-number
|
||||
v-model="form.yearlyMunicipalFiscal"
|
||||
:model-value="yearlyTotals.municipal"
|
||||
:precision="2"
|
||||
:min="0"
|
||||
placeholder="万元"
|
||||
size="large"
|
||||
style="width: 100%"
|
||||
:disabled="true"
|
||||
readonly
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="年度自筹资金" prop="yearlySelfFunding">
|
||||
<el-input-number
|
||||
v-model="form.yearlySelfFunding"
|
||||
:model-value="yearlyTotals.self"
|
||||
:precision="2"
|
||||
:min="0"
|
||||
placeholder="万元"
|
||||
size="large"
|
||||
style="width: 100%"
|
||||
:disabled="true"
|
||||
readonly
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="年度其他资金" prop="yearlyOtherFunding">
|
||||
<el-input-number
|
||||
v-model="form.yearlyOtherFunding"
|
||||
:model-value="yearlyTotals.other"
|
||||
:precision="2"
|
||||
:min="0"
|
||||
placeholder="万元"
|
||||
size="large"
|
||||
style="width: 100%"
|
||||
:disabled="true"
|
||||
readonly
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 分年度预算明细 -->
|
||||
<div class="budget-breakdown-section">
|
||||
<h4 class="section-title">
|
||||
<el-icon><Calendar /></el-icon>
|
||||
分年度预算明细
|
||||
</h4>
|
||||
|
||||
|
||||
|
||||
<el-table :data="budgetBreakdownData" border style="width: 100%">
|
||||
<el-table-column prop="yearLabel" label="年度" width="120" align="center"/>
|
||||
<el-table-column label="总投资(万元)" align="center">
|
||||
<template #default="scope">
|
||||
<el-input-number
|
||||
v-model="scope.row.total"
|
||||
:precision="2"
|
||||
:min="0"
|
||||
placeholder="0.00"
|
||||
size="small"
|
||||
style="width: 100%"
|
||||
@change="updateBudgetBreakdown(scope.$index)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="市财政资金(万元)" align="center">
|
||||
<template #default="scope">
|
||||
<el-input-number
|
||||
v-model="scope.row.municipal"
|
||||
:precision="2"
|
||||
:min="0"
|
||||
placeholder="0.00"
|
||||
size="small"
|
||||
style="width: 100%"
|
||||
@change="updateBudgetBreakdown(scope.$index)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位自筹(万元)" align="center">
|
||||
<template #default="scope">
|
||||
<el-input-number
|
||||
v-model="scope.row.self"
|
||||
:precision="2"
|
||||
:min="0"
|
||||
placeholder="0.00"
|
||||
size="small"
|
||||
style="width: 100%"
|
||||
@change="updateBudgetBreakdown(scope.$index)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="其他资金(万元)" align="center">
|
||||
<template #default="scope">
|
||||
<el-input-number
|
||||
v-model="scope.row.other"
|
||||
:precision="2"
|
||||
:min="0"
|
||||
placeholder="0.00"
|
||||
size="small"
|
||||
style="width: 100%"
|
||||
@change="updateBudgetBreakdown(scope.$index)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="budget-breakdown-note">
|
||||
<el-alert
|
||||
title="说明:请根据项目实际情况填写各年度的预算分配,系统会自动汇总计算总投资。"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 资金分配图表 -->
|
||||
<div class="budget-chart" v-if="hasBudgetData">
|
||||
<h4 class="section-title">资金分配可视化</h4>
|
||||
@@ -516,17 +598,17 @@ const form = reactive({
|
||||
totalMunicipalFiscal: 0,
|
||||
totalSelfFunding: 0,
|
||||
totalOtherFunding: 0,
|
||||
// 年度预计执行数
|
||||
// 分年度预算明细
|
||||
currentYearTotal: 0,
|
||||
currentYearMunicipal: 0,
|
||||
currentYearSelf: 0,
|
||||
currentYearOther: 0,
|
||||
// 下年度申请资金
|
||||
// 下年度预计执行数
|
||||
nextYearTotal: 0,
|
||||
nextYearMunicipal: 0,
|
||||
nextYearSelf: 0,
|
||||
nextYearOther: 0,
|
||||
// 以后年度安排
|
||||
// 后续年度预计执行数
|
||||
futureYearTotal: 0,
|
||||
futureYearMunicipal: 0,
|
||||
futureYearSelf: 0,
|
||||
@@ -536,32 +618,7 @@ const form = reactive({
|
||||
approvalStatus: 'draft'
|
||||
})
|
||||
|
||||
// 监听父组件传递的数据变化
|
||||
watch(() => props.formData, (newData) => {
|
||||
if (newData && typeof newData === 'object' && Object.keys(newData).length > 0) {
|
||||
console.log('接收到父组件传递的数据:', newData)
|
||||
// 安全地更新form数据
|
||||
Object.keys(form).forEach(key => {
|
||||
if (newData.hasOwnProperty(key) && newData[key] !== null && newData[key] !== undefined) {
|
||||
// 特殊处理不同类型的字段
|
||||
if (key === 'year' && newData[key]) {
|
||||
form[key] = newData[key].toString()
|
||||
} else if (key === 'startDate' || key === 'endDate') {
|
||||
form[key] = newData[key] ? formatDateString(newData[key]) : ''
|
||||
} else if (key.includes('Investment') || key.includes('Fiscal') || key.includes('Funding') ||
|
||||
key.includes('Total') || key.includes('Municipal') || key.includes('Self') || key.includes('Other')) {
|
||||
// 金额字段
|
||||
form[key] = Number(newData[key]) || 0
|
||||
} else {
|
||||
form[key] = newData[key]
|
||||
}
|
||||
}
|
||||
})
|
||||
console.log('form数据更新完成:', form)
|
||||
}
|
||||
}, { deep: true, immediate: true })
|
||||
|
||||
// 日期格式化函数
|
||||
// 工具函数定义 - 提前定义避免提升问题
|
||||
const formatDateString = (dateValue: any): string => {
|
||||
if (!dateValue) return ''
|
||||
let date: Date
|
||||
@@ -581,6 +638,61 @@ const formatDateString = (dateValue: any): string => {
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
const updateFormWithData = (newData: any) => {
|
||||
if (!newData || typeof newData !== 'object' || Object.keys(newData).length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 安全地更新form数据
|
||||
Object.keys(form).forEach(key => {
|
||||
if (newData.hasOwnProperty(key) && newData[key] !== null && newData[key] !== undefined) {
|
||||
// 特殊处理不同类型的字段
|
||||
if (key === 'year' && newData[key]) {
|
||||
form[key] = newData[key].toString()
|
||||
} else if (key === 'startDate' || key === 'endDate') {
|
||||
form[key] = newData[key] ? formatDateString(newData[key]) : ''
|
||||
} else if (key.includes('Investment') || key.includes('Fiscal') || key.includes('Funding') ||
|
||||
key.includes('Total') || key.includes('Municipal') || key.includes('Self') || key.includes('Other') ||
|
||||
key.includes('yearly') || key.includes('Year')) {
|
||||
// 金额字段 - 扩展匹配模式
|
||||
form[key] = Number(newData[key]) || 0
|
||||
} else if (key === 'performanceIndicators') {
|
||||
// 绩效指标数据特殊处理 - 需要转换数字字段
|
||||
if (Array.isArray(newData[key])) {
|
||||
form[key] = newData[key].map(indicator => {
|
||||
if (indicator && typeof indicator === 'object') {
|
||||
return {
|
||||
...indicator,
|
||||
weightPercentage: indicator.weightPercentage ? Number(indicator.weightPercentage) : 0,
|
||||
sortOrder: indicator.sortOrder ? Number(indicator.sortOrder) : 0
|
||||
}
|
||||
}
|
||||
return indicator
|
||||
})
|
||||
} else {
|
||||
form[key] = []
|
||||
}
|
||||
} else {
|
||||
form[key] = newData[key]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 数据加载完成后,重新计算年度总数
|
||||
// calculateYearlyTotals() // 移除此函数调用
|
||||
} catch (error) {
|
||||
console.error('更新form数据时出错:', error)
|
||||
// 即使出错也要确保基本数据结构
|
||||
if (newData.id) form.id = newData.id
|
||||
if (newData.projectName) form.projectName = newData.projectName
|
||||
if (newData.principal) form.principal = newData.principal
|
||||
}
|
||||
}
|
||||
|
||||
// 监听父组件传递的数据变化
|
||||
watch(() => props.formData, updateFormWithData, { deep: true, immediate: true })
|
||||
|
||||
// 表单验证规则
|
||||
const rules = {
|
||||
projectName: [
|
||||
@@ -605,7 +717,7 @@ const rules = {
|
||||
],
|
||||
totalInvestment: [
|
||||
{ required: true, message: '请填写项目总投资', trigger: 'blur' },
|
||||
{ type: 'number', min: 0.01, message: '投资金额必须大于0', trigger: 'blur' }
|
||||
{ type: 'number' as const, min: 0.01, message: '投资金额必须大于0', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -618,6 +730,103 @@ const hasBudgetData = computed(() => {
|
||||
return form.totalMunicipalFiscal > 0 || form.totalSelfFunding > 0 || form.totalOtherFunding > 0
|
||||
})
|
||||
|
||||
// 年度执行合计(计算属性)
|
||||
const yearlyTotals = computed(() => {
|
||||
return {
|
||||
total: (form.currentYearTotal || 0) + (form.nextYearTotal || 0) + (form.futureYearTotal || 0),
|
||||
municipal: (form.currentYearMunicipal || 0) + (form.nextYearMunicipal || 0) + (form.futureYearMunicipal || 0),
|
||||
self: (form.currentYearSelf || 0) + (form.nextYearSelf || 0) + (form.futureYearSelf || 0),
|
||||
other: (form.currentYearOther || 0) + (form.nextYearOther || 0) + (form.futureYearOther || 0)
|
||||
}
|
||||
})
|
||||
|
||||
// 分年度预算明细数据
|
||||
const budgetBreakdownData = computed(() => {
|
||||
const currentYear = parseInt(form.year) || new Date().getFullYear()
|
||||
return [
|
||||
{
|
||||
yearLabel: `${currentYear}年`,
|
||||
total: form.currentYearTotal || 0,
|
||||
municipal: form.currentYearMunicipal || 0,
|
||||
self: form.currentYearSelf || 0,
|
||||
other: form.currentYearOther || 0,
|
||||
field: 'currentYear'
|
||||
},
|
||||
{
|
||||
yearLabel: `${currentYear + 1}年`,
|
||||
total: form.nextYearTotal || 0,
|
||||
municipal: form.nextYearMunicipal || 0,
|
||||
self: form.nextYearSelf || 0,
|
||||
other: form.nextYearOther || 0,
|
||||
field: 'nextYear'
|
||||
},
|
||||
{
|
||||
yearLabel: '以后年度',
|
||||
total: form.futureYearTotal || 0,
|
||||
municipal: form.futureYearMunicipal || 0,
|
||||
self: form.futureYearSelf || 0,
|
||||
other: form.futureYearOther || 0,
|
||||
field: 'futureYear'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// 更新分年度预算明细
|
||||
const updateBudgetBreakdown = (index: number) => {
|
||||
const breakdown = budgetBreakdownData.value[index]
|
||||
const fieldPrefix = breakdown.field
|
||||
|
||||
// 自动计算该年度的总投资(市财政资金 + 单位自筹 + 其他资金)
|
||||
const calculatedTotal = (breakdown.municipal || 0) + (breakdown.self || 0) + (breakdown.other || 0)
|
||||
breakdown.total = calculatedTotal
|
||||
|
||||
// 更新form中对应的字段
|
||||
form[`${fieldPrefix}Total`] = breakdown.total
|
||||
form[`${fieldPrefix}Municipal`] = breakdown.municipal || 0
|
||||
form[`${fieldPrefix}Self`] = breakdown.self || 0
|
||||
form[`${fieldPrefix}Other`] = breakdown.other || 0
|
||||
|
||||
// 重新计算总投资
|
||||
recalculateTotalFromBreakdown()
|
||||
}
|
||||
|
||||
// 从分年度明细重新计算总投资
|
||||
const recalculateTotalFromBreakdown = () => {
|
||||
const totalMunicipal = (form.currentYearMunicipal || 0) + (form.nextYearMunicipal || 0) + (form.futureYearMunicipal || 0)
|
||||
const totalSelf = (form.currentYearSelf || 0) + (form.nextYearSelf || 0) + (form.futureYearSelf || 0)
|
||||
const totalOther = (form.currentYearOther || 0) + (form.nextYearOther || 0) + (form.futureYearOther || 0)
|
||||
|
||||
form.totalMunicipalFiscal = totalMunicipal
|
||||
form.totalSelfFunding = totalSelf
|
||||
form.totalOtherFunding = totalOther
|
||||
form.totalInvestment = totalMunicipal + totalSelf + totalOther
|
||||
}
|
||||
|
||||
// 计算预算汇总
|
||||
const calculateBudgetSummary = () => {
|
||||
// 计算各类资金的总和
|
||||
const totalMunicipal = (form.currentYearMunicipal || 0) +
|
||||
(form.nextYearMunicipal || 0) +
|
||||
(form.futureYearMunicipal || 0)
|
||||
const totalSelf = (form.currentYearSelf || 0) +
|
||||
(form.nextYearSelf || 0) +
|
||||
(form.futureYearSelf || 0)
|
||||
const totalOther = (form.currentYearOther || 0) +
|
||||
(form.nextYearOther || 0) +
|
||||
(form.futureYearOther || 0)
|
||||
|
||||
// 更新总投资字段
|
||||
form.totalMunicipalFiscal = totalMunicipal
|
||||
form.totalSelfFunding = totalSelf
|
||||
form.totalOtherFunding = totalOther
|
||||
form.totalInvestment = totalMunicipal + totalSelf + totalOther
|
||||
|
||||
// 更新图表
|
||||
if (budgetChartInstance && hasBudgetData.value) {
|
||||
renderBudgetChart()
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化金额
|
||||
const formatAmount = (amount: number): string => {
|
||||
return amount ? amount.toFixed(2) : '0.00'
|
||||
@@ -697,12 +906,9 @@ const initBudgetChart = async () => {
|
||||
|
||||
// 验证绩效指标分值占比总和
|
||||
const validatePerformanceIndicators = () => {
|
||||
console.log('=== 校验绩效指标 ===')
|
||||
|
||||
// 直接使用绩效指标组件的校验方法
|
||||
if (performanceIndicatorsRef?.value && typeof performanceIndicatorsRef.value.validate === 'function') {
|
||||
const validationResult = performanceIndicatorsRef.value.validate()
|
||||
console.log('绩效指标校验结果:', validationResult)
|
||||
return validationResult
|
||||
}
|
||||
|
||||
@@ -777,9 +983,6 @@ const handlePrevStep = () => {
|
||||
|
||||
// 处理绩效指标变化
|
||||
const handlePerformanceIndicatorsChange = (indicators) => {
|
||||
console.log('=== 绩效指标数据变化 ===')
|
||||
console.log('接收到的indicators:', indicators)
|
||||
|
||||
// 为每个指标添加必要的字段
|
||||
const processedIndicators = indicators.map((item, index) => ({
|
||||
...item,
|
||||
@@ -803,18 +1006,6 @@ const handlePerformanceIndicatorsChange = (indicators) => {
|
||||
}))
|
||||
|
||||
form.performanceIndicators = processedIndicators
|
||||
console.log('处理后的绩效指标数据:', form.performanceIndicators)
|
||||
|
||||
// 自动生成总绩效目标和当年目标(如果有目标值)
|
||||
const totalGoals = processedIndicators
|
||||
.filter(item => item.targetValue && !isNaN(Number(item.targetValue)))
|
||||
.reduce((sum, item) => sum + Number(item.targetValue), 0)
|
||||
|
||||
if (totalGoals > 0) {
|
||||
form.totalPerformanceGoal = totalGoals
|
||||
form.currentYearGoal = totalGoals
|
||||
console.log('自动生成总绩效目标:', form.totalPerformanceGoal)
|
||||
}
|
||||
}
|
||||
|
||||
// 保存草稿
|
||||
@@ -831,13 +1022,6 @@ const handleSaveDraft = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 详细调试信息
|
||||
console.log('=== 前端保存草稿调试信息 ===')
|
||||
console.log('form.performanceIndicators 是否存在:', !!form.performanceIndicators)
|
||||
console.log('form.performanceIndicators 长度:', form.performanceIndicators ? form.performanceIndicators.length : 'undefined')
|
||||
console.log('form.performanceIndicators 数据:', form.performanceIndicators)
|
||||
console.log('完整的form数据:', form)
|
||||
|
||||
const result = form.id
|
||||
? await updateProjectApplication(form as any)
|
||||
: await saveDraft(form as any)
|
||||
@@ -847,12 +1031,8 @@ const handleSaveDraft = async () => {
|
||||
if (!form.id && result.data?.id) {
|
||||
form.id = result.data.id
|
||||
form.applicationNo = result.data.applicationNo
|
||||
console.log('草稿保存成功,获得ID:', form.id, '申报编号:', form.applicationNo)
|
||||
}
|
||||
|
||||
// 绩效指标数据已经作为form的一部分传递给后端,无需单独处理
|
||||
console.log('绩效指标数据已随草稿数据一起保存,数量:', form.performanceIndicators ? form.performanceIndicators.length : 0)
|
||||
|
||||
ElMessage.success('草稿保存成功')
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -927,10 +1107,16 @@ const handleSubmit = async () => {
|
||||
|
||||
// 解析错误信息
|
||||
let errorMessage = '提交申报失败'
|
||||
if (error?.response?.data?.msg) {
|
||||
errorMessage += ': ' + error.response.data.msg
|
||||
} else if (error?.message) {
|
||||
errorMessage += ': ' + error.message
|
||||
if (error && typeof error === 'object' && 'response' in error) {
|
||||
const axiosError = error as any
|
||||
if (axiosError.response?.data?.msg) {
|
||||
errorMessage += ': ' + axiosError.response.data.msg
|
||||
}
|
||||
} else if (error && typeof error === 'object' && 'message' in error) {
|
||||
const generalError = error as any
|
||||
if (generalError.message) {
|
||||
errorMessage += ': ' + generalError.message
|
||||
}
|
||||
} else if (typeof error === 'string') {
|
||||
errorMessage += ': ' + error
|
||||
}
|
||||
@@ -955,15 +1141,64 @@ const handleCancel = () => {
|
||||
|
||||
// 加载数据
|
||||
const loadData = async () => {
|
||||
console.log('=== loadData 函数被调用 ===')
|
||||
|
||||
const id = route.params.id as string
|
||||
console.log('解析的ID:', id, '类型:', typeof id)
|
||||
|
||||
if (id && id !== 'add') {
|
||||
console.log('符合加载条件,开始请求数据...')
|
||||
try {
|
||||
console.log('开始加载申报数据,ID:', id)
|
||||
const response = await getProjectApplication(id)
|
||||
console.log('API请求完成,响应:', response)
|
||||
|
||||
if (response.code === 200) {
|
||||
console.log('申报基础数据加载成功:', response.data)
|
||||
|
||||
// 显示原始后端数据的调试信息
|
||||
console.log('=== 后端返回的原始数据 ===')
|
||||
console.log('完整数据:', JSON.stringify(response.data, null, 2))
|
||||
console.log('分年度预算字段值:')
|
||||
const budgetFields = [
|
||||
'currentYearTotal', 'currentYearMunicipal', 'currentYearSelf', 'currentYearOther',
|
||||
'nextYearTotal', 'nextYearMunicipal', 'nextYearSelf', 'nextYearOther',
|
||||
'futureYearTotal', 'futureYearMunicipal', 'futureYearSelf', 'futureYearOther'
|
||||
]
|
||||
budgetFields.forEach(field => {
|
||||
console.log(`${field}: ${response.data[field]} (类型: ${typeof response.data[field]})`)
|
||||
})
|
||||
|
||||
// 更新基础数据
|
||||
Object.assign(form, response.data)
|
||||
|
||||
// 特殊处理分年度预算数据
|
||||
if (response.data) {
|
||||
// 确保年度预算字段存在
|
||||
const fields = [
|
||||
'currentYearTotal', 'currentYearMunicipal', 'currentYearSelf', 'currentYearOther',
|
||||
'nextYearTotal', 'nextYearMunicipal', 'nextYearSelf', 'nextYearOther',
|
||||
'futureYearTotal', 'futureYearMunicipal', 'futureYearSelf', 'futureYearOther'
|
||||
]
|
||||
|
||||
console.log('=== 开始处理分年度预算字段 ===')
|
||||
fields.forEach(field => {
|
||||
const originalValue = response.data[field]
|
||||
const processedValue = Number(originalValue) || 0
|
||||
console.log(`${field}: ${originalValue} -> ${processedValue}`)
|
||||
if (response.data[field] !== undefined && response.data[field] !== null) {
|
||||
form[field] = processedValue
|
||||
}
|
||||
})
|
||||
|
||||
// calculateYearlyTotals() // 移除此函数调用
|
||||
|
||||
console.log('=== 处理完成后的form数据 ===')
|
||||
budgetFields.forEach(field => {
|
||||
console.log(`form.${field}: ${form[field]}`)
|
||||
})
|
||||
}
|
||||
|
||||
// 检查是否有绩效指标数据
|
||||
if (response.data.performanceIndicators && response.data.performanceIndicators.length > 0) {
|
||||
console.log('发现绩效指标数据,数量:', response.data.performanceIndicators.length)
|
||||
@@ -973,19 +1208,61 @@ const loadData = async () => {
|
||||
console.log('没有绩效指标数据')
|
||||
form.performanceIndicators = []
|
||||
}
|
||||
|
||||
console.log('分年度预算数据加载完成:', {
|
||||
currentYear: {
|
||||
total: form.currentYearTotal,
|
||||
municipal: form.currentYearMunicipal,
|
||||
self: form.currentYearSelf,
|
||||
other: form.currentYearOther
|
||||
},
|
||||
nextYear: {
|
||||
total: form.nextYearTotal,
|
||||
municipal: form.nextYearMunicipal,
|
||||
self: form.nextYearSelf,
|
||||
other: form.nextYearOther
|
||||
},
|
||||
futureYear: {
|
||||
total: form.futureYearTotal,
|
||||
municipal: form.futureYearMunicipal,
|
||||
self: form.futureYearSelf,
|
||||
other: form.futureYearOther
|
||||
}
|
||||
})
|
||||
|
||||
// 检查计算属性的值
|
||||
console.log('=== budgetBreakdownData 计算属性结果 ===')
|
||||
console.log('budgetBreakdownData:', budgetBreakdownData.value)
|
||||
} else {
|
||||
console.log('API响应错误:', response)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error)
|
||||
ElMessage.error('加载数据失败')
|
||||
}
|
||||
} else {
|
||||
console.log('不符合加载条件,ID:', id)
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
const validate = (callback: (valid: boolean) => void) => {
|
||||
// 简单的验证逻辑,检查必填字段
|
||||
const validate = (callback: (valid: boolean, formData?: any) => void) => {
|
||||
// 验证逻辑 + 数据收集
|
||||
const isValid = !!(form.projectName && form.principal && form.totalInvestment > 0)
|
||||
callback(isValid)
|
||||
|
||||
if (isValid) {
|
||||
// 收集绩效指标数据
|
||||
if (performanceIndicatorsRef.value && typeof performanceIndicatorsRef.value.getAllIndicators === 'function') {
|
||||
const indicators = performanceIndicatorsRef.value.getAllIndicators()
|
||||
form.performanceIndicators = indicators
|
||||
console.log('收集到绩效指标数据:', indicators)
|
||||
}
|
||||
|
||||
// 返回验证结果和完整的表单数据
|
||||
callback(true, form)
|
||||
} else {
|
||||
callback(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露方法给父组件
|
||||
@@ -995,7 +1272,15 @@ defineExpose({
|
||||
|
||||
// 组件挂载
|
||||
onMounted(() => {
|
||||
console.log('=== CW Application Form 组件开始挂载 ===')
|
||||
console.log('当前路由参数:', route.params)
|
||||
console.log('route.params.id:', route.params.id)
|
||||
console.log('是否为详情页面:', route.params.id && route.params.id !== 'add')
|
||||
|
||||
loadData()
|
||||
|
||||
console.log('=== CW Application Form 组件挂载完成 ===')
|
||||
console.log('当前form初始状态:', JSON.stringify(form, null, 2))
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1124,6 +1409,67 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
// 分年度预算明细
|
||||
.budget-breakdown-section {
|
||||
margin-bottom: 32px;
|
||||
padding: 20px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #e9ecef;
|
||||
|
||||
.section-title {
|
||||
margin: 0 0 20px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
:deep(.el-table) {
|
||||
.el-table__header {
|
||||
background-color: #f5f7fa;
|
||||
|
||||
.el-table__cell {
|
||||
background-color: #f5f7fa;
|
||||
color: #606266;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.el-table__body {
|
||||
.el-table__row {
|
||||
&:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-input-number {
|
||||
width: 100%;
|
||||
|
||||
:deep(.el-input__inner) {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.budget-breakdown-note {
|
||||
margin-top: 16px;
|
||||
|
||||
:deep(.el-alert) {
|
||||
border-radius: 4px;
|
||||
|
||||
.el-alert__title {
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 资金分配图表
|
||||
.budget-chart {
|
||||
margin-top: 24px;
|
||||
padding: 20px;
|
||||
|
||||
@@ -170,7 +170,7 @@
|
||||
@click="handleExport"
|
||||
v-hasPermi="['cw:application:export']"
|
||||
>导出</el-button>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
<right-toolbar :showSearch="showSearch" @update:showSearch="showSearch = $event" @queryTable="getList"></right-toolbar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -224,7 +224,7 @@
|
||||
v-hasPermi="['cw:application:query']"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="编辑" placement="top" v-if="scope.row.approvalStatus === 'draft'">
|
||||
<el-tooltip content="编辑" placement="top" v-if="['draft', 'returned'].includes(scope.row.approvalStatus)">
|
||||
<el-button
|
||||
type="success"
|
||||
icon="Edit"
|
||||
@@ -234,7 +234,7 @@
|
||||
v-hasPermi="['cw:application:edit']"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="提交审批" placement="top" v-if="scope.row.approvalStatus === 'draft'">
|
||||
<el-tooltip content="提交审批" placement="top" v-if="['draft', 'returned'].includes(scope.row.approvalStatus)">
|
||||
<el-button
|
||||
type="warning"
|
||||
icon="Upload"
|
||||
@@ -254,7 +254,7 @@
|
||||
v-hasPermi="['cw:application:withdraw']"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top" v-if="['draft', 'cancelled'].includes(scope.row.approvalStatus)">
|
||||
<el-tooltip content="删除" placement="top" v-if="['draft', 'cancelled', 'returned'].includes(scope.row.approvalStatus)">
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="Delete"
|
||||
@@ -272,13 +272,15 @@
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<pagination
|
||||
v-show="total>0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
<pagination
|
||||
v-show="total>0"
|
||||
:total="total"
|
||||
:page="queryParams.pageNum"
|
||||
:limit="queryParams.pageSize"
|
||||
@update:page="queryParams.pageNum = $event"
|
||||
@update:limit="queryParams.pageSize = $event"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
@@ -287,8 +289,7 @@
|
||||
<ApplicationForm ref="applicationFormRef" :form-data="form" @submit="submitForm" />
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
@@ -309,12 +310,14 @@
|
||||
import { listProjectApplication, getProjectApplication, delProjectApplication, addProjectApplication, updateProjectApplication, submitApplication, getApplicationStatistics } from "@/api/cw/projectApplication"
|
||||
import ApplicationForm from "./form.vue"
|
||||
import ApplicationSummary from "./summary.vue"
|
||||
import Pagination from "@/components/Pagination/index.vue"
|
||||
|
||||
export default {
|
||||
name: "ProjectApplication",
|
||||
components: {
|
||||
ApplicationForm,
|
||||
ApplicationSummary
|
||||
ApplicationSummary,
|
||||
Pagination
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -373,6 +376,13 @@ export default {
|
||||
this.applicationList = response.rows
|
||||
this.total = response.total
|
||||
this.loading = false
|
||||
}).catch(error => {
|
||||
console.error('获取项目申报列表失败:', error)
|
||||
this.loading = false
|
||||
this.applicationList = []
|
||||
this.total = 0
|
||||
// 可以添加用户友好的错误提示
|
||||
this.$message.error('获取数据失败,请检查网络连接或联系管理员')
|
||||
})
|
||||
},
|
||||
/** 获取统计数据 */
|
||||
@@ -388,10 +398,10 @@ export default {
|
||||
}).catch(error => {
|
||||
console.error('获取统计数据失败:', error)
|
||||
// 如果API失败,使用本地计算作为后备方案
|
||||
this.totalCount = this.applicationList.length
|
||||
this.approvedCount = this.applicationList.filter(item => item.approvalStatus === 'approved').length
|
||||
this.pendingCount = this.applicationList.filter(item => item.approvalStatus === 'approving').length
|
||||
this.draftCount = this.applicationList.filter(item => item.approvalStatus === 'draft').length
|
||||
this.totalCount = this.applicationList.length
|
||||
this.approvedCount = this.applicationList.filter(item => item.approvalStatus === 'approved').length
|
||||
this.pendingCount = this.applicationList.filter(item => item.approvalStatus === 'approving').length
|
||||
this.draftCount = this.applicationList.filter(item => item.approvalStatus === 'draft').length
|
||||
})
|
||||
},
|
||||
/** 取消按钮 */
|
||||
@@ -480,8 +490,12 @@ export default {
|
||||
},
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
this.$refs["applicationFormRef"].validate((valid) => {
|
||||
if (valid) {
|
||||
this.$refs["applicationFormRef"].validate((valid, formData) => {
|
||||
if (valid && formData) {
|
||||
// 使用子组件回传的完整数据(包含绩效指标)
|
||||
this.form = formData
|
||||
console.log('提交表单数据:', this.form)
|
||||
|
||||
if (this.form.id != null) {
|
||||
updateProjectApplication(this.form).then(response => {
|
||||
this.$modal.msgSuccess("修改成功")
|
||||
@@ -497,6 +511,8 @@ export default {
|
||||
this.getStatistics() // 新增后更新统计
|
||||
})
|
||||
}
|
||||
} else {
|
||||
this.$modal.msgError("请检查表单数据是否完整")
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -579,7 +595,7 @@ export default {
|
||||
<style scoped lang="scss">
|
||||
/* 继承后勤管理模块的样式风格 */
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
padding: 8px;
|
||||
|
||||
/* 统计卡片样式 */
|
||||
.stats-row {
|
||||
@@ -649,22 +665,18 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
/* 卡片样式 */
|
||||
.button-card, .table-card {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
/* 表格卡片布局 - 参考后勤管理功能 */
|
||||
.table-card {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
:deep(.el-card__header) {
|
||||
border-bottom: 1px solid rgba(93, 135, 255, 0.1);
|
||||
background: linear-gradient(135deg, #fafbff 0%, #f8fafc 100%);
|
||||
padding: 16px 20px;
|
||||
}
|
||||
.table-card :deep(.el-card__body) {
|
||||
padding: 0px 20px 20px 20px !important;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 20px;
|
||||
}
|
||||
/* 移除按钮栏底部边框 */
|
||||
.button-card :deep(.el-card__header) {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
@@ -696,12 +708,13 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
/* 表格主体 */
|
||||
.table-container {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 表格样式 - 参考后勤管理功能 */
|
||||
.scroll-table {
|
||||
width: 100%;
|
||||
|
||||
@@ -724,6 +737,69 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
/* 表格横向滚动条样式优化 - 参考后勤管理功能 */
|
||||
.scroll-table :deep(.el-table__body-wrapper) {
|
||||
overflow-x: auto !important;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #409eff #f0f0f0;
|
||||
}
|
||||
|
||||
.scroll-table :deep(.el-table__header-wrapper) {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Webkit 浏览器的横向滚动条样式 */
|
||||
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar {
|
||||
height: 12px !important;
|
||||
width: 12px !important;
|
||||
}
|
||||
|
||||
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-track {
|
||||
background: #f0f0f0 !important;
|
||||
border-radius: 6px !important;
|
||||
border: 1px solid #e4e7ed !important;
|
||||
}
|
||||
|
||||
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb {
|
||||
background: #409eff !important;
|
||||
border-radius: 6px !important;
|
||||
border: 2px solid #f0f0f0 !important;
|
||||
min-height: 20px !important;
|
||||
}
|
||||
|
||||
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb:hover {
|
||||
background: #337ecc !important;
|
||||
}
|
||||
|
||||
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb:active {
|
||||
background: #2b6cb0 !important;
|
||||
}
|
||||
|
||||
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-corner {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
/* 固定列样式优化 */
|
||||
.scroll-table :deep(.el-table__fixed-right) {
|
||||
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.scroll-table :deep(.el-table__fixed-left) {
|
||||
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 确保固定列在滚动时有正确的背景色 */
|
||||
.scroll-table :deep(.el-table__fixed-right .el-table__cell),
|
||||
.scroll-table :deep(.el-table__fixed-left .el-table__cell) {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* 表格行悬停效果在固定列上的同步 */
|
||||
.scroll-table :deep(.el-table__row:hover .el-table__cell) {
|
||||
background-color: #f5f7fa !important;
|
||||
}
|
||||
|
||||
/* 操作按钮样式 */
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
@@ -733,8 +809,8 @@ export default {
|
||||
white-space: nowrap;
|
||||
|
||||
.el-button {
|
||||
width: 32px !important;
|
||||
height: 32px !important;
|
||||
width: 27px !important;
|
||||
height: 27px !important;
|
||||
padding: 0 !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -748,12 +824,13 @@ export default {
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
/* 分页容器样式 */
|
||||
/* 分页容器样式 - 参考后勤管理功能 */
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 0 0 0;
|
||||
margin-top: 16px;
|
||||
padding: 16px 20px 0 0 !important;
|
||||
background-color: #fff;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
/* 对话框样式 */
|
||||
|
||||
@@ -155,7 +155,7 @@
|
||||
<el-tag type="warning" size="small">分值占比总和:{{ performanceIndicatorTotal }}%</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<el-table :data="validPerformanceIndicators" border style="width: 100%">
|
||||
<el-table-column label="指标类型" width="120" align="center">
|
||||
<template #default="scope">
|
||||
@@ -229,7 +229,7 @@ const currentApplication = computed(() => {
|
||||
console.log('application数据:', props.application);
|
||||
return props.application;
|
||||
}
|
||||
|
||||
|
||||
// 否则使用内部获取的数据
|
||||
console.log('=== 使用内部获取的application数据 ===');
|
||||
console.log('internalApplication数据:', internalApplication.value);
|
||||
@@ -239,10 +239,10 @@ const currentApplication = computed(() => {
|
||||
// 获取申报详情数据
|
||||
const fetchApplicationData = async (id) => {
|
||||
if (!id) return;
|
||||
|
||||
|
||||
console.log('=== ApplicationSummary 开始获取数据 ===');
|
||||
console.log('applicationId:', id);
|
||||
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await getProjectApplication(id);
|
||||
@@ -250,25 +250,25 @@ const fetchApplicationData = async (id) => {
|
||||
console.log('API响应:', response);
|
||||
console.log('响应数据:', response.data);
|
||||
console.log('响应数据类型:', typeof response.data);
|
||||
|
||||
|
||||
// 特别检查绩效指标字段
|
||||
if (response.data) {
|
||||
console.log('=== API响应中的绩效指标检查 ===');
|
||||
console.log('response.data.performanceIndicators:', response.data.performanceIndicators);
|
||||
console.log('performanceIndicators类型:', typeof response.data.performanceIndicators);
|
||||
console.log('是否为数组:', Array.isArray(response.data.performanceIndicators));
|
||||
|
||||
|
||||
// 检查所有可能的绩效指标字段名
|
||||
console.log('可能的字段名检查:');
|
||||
console.log('- performanceIndicators:', response.data.performanceIndicators);
|
||||
console.log('- performance_indicators:', response.data.performance_indicators);
|
||||
console.log('- indicators:', response.data.indicators);
|
||||
console.log('- performanceIndicatorList:', response.data.performanceIndicatorList);
|
||||
|
||||
|
||||
// 列出所有字段
|
||||
console.log('响应数据的所有字段:', Object.keys(response.data));
|
||||
}
|
||||
|
||||
|
||||
internalApplication.value = response.data || {};
|
||||
} catch (error) {
|
||||
console.error('=== ApplicationSummary API调用失败 ===');
|
||||
@@ -291,7 +291,7 @@ onMounted(() => {
|
||||
console.log('=== ApplicationSummary 组件挂载 ===');
|
||||
console.log('props.application:', props.application);
|
||||
console.log('props.applicationId:', props.applicationId);
|
||||
|
||||
|
||||
// 如果没有传入application但有applicationId,则获取数据
|
||||
if (props.applicationId && (!props.application || Object.keys(props.application).length === 0)) {
|
||||
fetchApplicationData(props.applicationId);
|
||||
@@ -315,14 +315,14 @@ const hasPerformanceIndicators = computed(() => {
|
||||
console.log('app.performanceIndicators类型:', typeof app.performanceIndicators);
|
||||
console.log('app.performanceIndicators值:', app.performanceIndicators);
|
||||
console.log('是否为数组:', Array.isArray(app.performanceIndicators));
|
||||
|
||||
|
||||
if (app.performanceIndicators) {
|
||||
console.log('绩效指标数组长度:', app.performanceIndicators.length);
|
||||
console.log('绩效指标数组内容:', JSON.stringify(app.performanceIndicators, null, 2));
|
||||
} else {
|
||||
console.log('绩效指标数据为空或未定义');
|
||||
}
|
||||
|
||||
|
||||
const hasIndicators = app.performanceIndicators && app.performanceIndicators.length > 0;
|
||||
console.log('最终判断 - 是否有绩效指标:', hasIndicators);
|
||||
return hasIndicators;
|
||||
@@ -346,18 +346,18 @@ const validPerformanceIndicators = computed(() => {
|
||||
const app = currentApplication.value;
|
||||
console.log('=== validPerformanceIndicators 计算 ===');
|
||||
console.log('app.performanceIndicators:', app.performanceIndicators);
|
||||
|
||||
|
||||
if (!app.performanceIndicators) {
|
||||
console.log('没有绩效指标数据,返回空数组');
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
const validIndicators = app.performanceIndicators.filter(indicator => {
|
||||
const weight = parseFloat(indicator.weightPercentage) || 0;
|
||||
console.log(`指标 ${indicator.goalName}: 权重=${indicator.weightPercentage}, 解析后权重=${weight}, 是否有效=${weight > 0}`);
|
||||
return weight > 0;
|
||||
});
|
||||
|
||||
|
||||
console.log('有效绩效指标数量:', validIndicators.length);
|
||||
console.log('有效绩效指标详情:', validIndicators);
|
||||
return validIndicators;
|
||||
@@ -367,37 +367,65 @@ const validPerformanceIndicators = computed(() => {
|
||||
const budgetBreakdown = computed(() => {
|
||||
const app = currentApplication.value;
|
||||
const currentYear = app.year || new Date().getFullYear();
|
||||
console.log('=== 预算数据检查 ===');
|
||||
console.log('application数据:', app);
|
||||
console.log('当年预算字段:', {
|
||||
|
||||
console.log('=== 🔍 分年度预算数据详细检查 ===');
|
||||
console.log('完整application数据:', app);
|
||||
console.log('当前年度:', currentYear);
|
||||
|
||||
console.log('📊 分年度预算字段值详情:');
|
||||
console.log('📅 当年预算:', {
|
||||
currentYearTotal: app.currentYearTotal,
|
||||
currentYearMunicipal: app.currentYearMunicipal,
|
||||
currentYearSelf: app.currentYearSelf,
|
||||
currentYearOther: app.currentYearOther
|
||||
});
|
||||
return [
|
||||
console.log('📅 下年预算:', {
|
||||
nextYearTotal: app.nextYearTotal,
|
||||
nextYearMunicipal: app.nextYearMunicipal,
|
||||
nextYearSelf: app.nextYearSelf,
|
||||
nextYearOther: app.nextYearOther
|
||||
});
|
||||
console.log('📅 以后年度预算:', {
|
||||
futureYearTotal: app.futureYearTotal,
|
||||
futureYearMunicipal: app.futureYearMunicipal,
|
||||
futureYearSelf: app.futureYearSelf,
|
||||
futureYearOther: app.futureYearOther
|
||||
});
|
||||
|
||||
const breakdown = [
|
||||
{
|
||||
year: `${currentYear}年`,
|
||||
total: app.currentYearTotal || 0,
|
||||
municipal: app.currentYearMunicipal || 0,
|
||||
self: app.currentYearSelf || 0,
|
||||
other: app.currentYearOther || 0
|
||||
total: Number(app.currentYearTotal) || 0,
|
||||
municipal: Number(app.currentYearMunicipal) || 0,
|
||||
self: Number(app.currentYearSelf) || 0,
|
||||
other: Number(app.currentYearOther) || 0
|
||||
},
|
||||
{
|
||||
year: `${parseInt(currentYear) + 1}年`,
|
||||
total: app.nextYearTotal || 0,
|
||||
municipal: app.nextYearMunicipal || 0,
|
||||
self: app.nextYearSelf || 0,
|
||||
other: app.nextYearOther || 0
|
||||
total: Number(app.nextYearTotal) || 0,
|
||||
municipal: Number(app.nextYearMunicipal) || 0,
|
||||
self: Number(app.nextYearSelf) || 0,
|
||||
other: Number(app.nextYearOther) || 0
|
||||
},
|
||||
{
|
||||
year: '以后年度',
|
||||
total: app.futureYearTotal || 0,
|
||||
municipal: app.futureYearMunicipal || 0,
|
||||
self: app.futureYearSelf || 0,
|
||||
other: app.futureYearOther || 0
|
||||
total: Number(app.futureYearTotal) || 0,
|
||||
municipal: Number(app.futureYearMunicipal) || 0,
|
||||
self: Number(app.futureYearSelf) || 0,
|
||||
other: Number(app.futureYearOther) || 0
|
||||
}
|
||||
].filter(item => item.total > 0); // 只显示有预算的年度
|
||||
];
|
||||
|
||||
console.log('📋 计算后的budgetBreakdown数据:', breakdown);
|
||||
console.log('📊 每年度预算汇总:');
|
||||
breakdown.forEach((item, index) => {
|
||||
const sum = item.municipal + item.self + item.other;
|
||||
console.log(`${item.year}: 总投资=${item.total}, 细项合计=${sum}, 财政=${item.municipal}, 自筹=${item.self}, 其他=${item.other}`);
|
||||
});
|
||||
|
||||
// 🔥 移除错误的过滤条件,始终显示所有年度
|
||||
console.log('✅ 返回完整的分年度预算数据,不进行过滤');
|
||||
return breakdown;
|
||||
});
|
||||
|
||||
// 格式化金额
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,685 @@
|
||||
<template>
|
||||
<div class="p-2 page-container">
|
||||
<!-- 搜索区域 -->
|
||||
<transition :enter-active-class="animate.searchAnimate.enter" :leave-active-class="animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="申报编号" prop="applicationNo">
|
||||
<el-input v-model="queryParams.applicationNo" placeholder="请输入申报编号" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="项目名称" prop="projectName">
|
||||
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="申报人" prop="applicantName">
|
||||
<el-input v-model="queryParams.applicantName" placeholder="请输入申报人姓名" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 快捷操作按钮 -->
|
||||
<el-row :gutter="10" class="mb-[10px]">
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover" class="quick-action-card">
|
||||
<div class="quick-action" @click="showPendingApprovals">
|
||||
<el-icon class="quick-icon"><Clock /></el-icon>
|
||||
<span>待我审批</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover" class="quick-action-card">
|
||||
<div class="quick-action" @click="showMyApprovals">
|
||||
<el-icon class="quick-icon"><User /></el-icon>
|
||||
<span>我已审批</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<el-card shadow="never" class="button-card table-card">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<!-- 批量操作按钮暂时隐藏 -->
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
icon="Check"
|
||||
:disabled="multiple"
|
||||
@click="handleBatchApprove"
|
||||
v-hasPermi="['cw:approval:finance']"
|
||||
>批量通过</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="Close"
|
||||
:disabled="multiple"
|
||||
@click="handleBatchReject"
|
||||
v-hasPermi="['cw:approval:finance']"
|
||||
>批量退回</el-button>
|
||||
</el-col> -->
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['cw:approval:export']">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="approvalList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="申报编号" align="center" prop="applicationNo" v-if="columns[0].visible" width="180">
|
||||
<template #default="scope">
|
||||
<el-link type="primary" @click="openDetailDialog(scope.row)">{{ scope.row.applicationNo }}</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="项目名称" align="center" prop="projectName" v-if="columns[1].visible" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="申报人" align="center" prop="applicantName" v-if="columns[2].visible" width="120" />
|
||||
<el-table-column label="申报部门" align="center" prop="departmentName" v-if="columns[3].visible" width="150" />
|
||||
<el-table-column label="总投资(万元)" align="center" prop="totalInvestment" v-if="columns[4].visible" width="120">
|
||||
<template #default="scope">
|
||||
<span class="amount-text">{{ formatAmount(scope.row.totalInvestment) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审批状态" align="center" prop="nodeStatus" v-if="columns[5].visible" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="cw_approval_status" :value="scope.row.nodeStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="提交时间" align="center" prop="startTime" v-if="columns[6].visible" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.startTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看详情" placement="top">
|
||||
<el-button link type="info" icon="View" @click="openDetailDialog(scope.row)" v-hasPermi="['cw:approval:query']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="财务审批" placement="top" v-if="scope.row.nodeStatus === 'waiting'">
|
||||
<el-button link type="success" icon="Check" @click="openApprovalDialog(scope.row)" v-hasPermi="['cw:approval:finance']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="审批历史" placement="top">
|
||||
<el-button link type="primary" icon="Clock" @click="handleApprovalHistory(scope.row)" v-hasPermi="['cw:approval:query']"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<!-- 项目详情对话框 -->
|
||||
<el-dialog :title="detailDialog.title" v-model="detailDialog.visible" width="1000px" append-to-body>
|
||||
<ApplicationSummary v-if="currentApplication && currentApplication.applicationId" :application-id="currentApplication.applicationId" />
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="detailDialog.visible = false">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 财务审批对话框 -->
|
||||
<el-dialog :title="approvalDialog.title" v-model="approvalDialog.visible" width="600px" append-to-body>
|
||||
<el-form ref="approvalFormRef" :model="approvalForm" :rules="approvalRules" label-width="100px">
|
||||
<el-form-item label="申报编号">
|
||||
<span>{{ currentApplication.applicationNo }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="项目名称">
|
||||
<span>{{ currentApplication.projectName }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="申报人">
|
||||
<span>{{ currentApplication.applicantName }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="总投资">
|
||||
<span>{{ formatAmount(currentApplication.totalInvestment) }} 万元</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="审批决定" prop="approvalResult" required>
|
||||
<el-radio-group v-model="approvalForm.approvalResult" @change="onApprovalResultChange">
|
||||
<el-radio value="approve">通过(提交院领导审批)</el-radio>
|
||||
<el-radio value="reject">退回申请人</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="选择院领导" prop="selectedApproverId" v-if="approvalForm.approvalResult === 'approve'">
|
||||
<el-select
|
||||
v-model="approvalForm.selectedApproverId"
|
||||
placeholder="请选择院领导"
|
||||
clearable
|
||||
filterable
|
||||
:loading="loadingApprovers"
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="approver in availableApprovers"
|
||||
:key="approver.userId"
|
||||
:label="approver.nickName + '(' + approver.userName + ')'"
|
||||
:value="approver.userId">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="审批意见" prop="approvalComment">
|
||||
<el-input
|
||||
v-model="approvalForm.approvalComment"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入财务审批意见"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="approvalDialog.visible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="submitApproval" :loading="approvalDialog.loading">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 审批历史对话框 -->
|
||||
<el-dialog title="审批历史" v-model="historyDialog.visible" width="800px" append-to-body>
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="(record, index) in approvalHistory"
|
||||
:key="index"
|
||||
:timestamp="parseTime(record.endTime || record.startTime)"
|
||||
:type="getTimelineType(record.approveAction)"
|
||||
>
|
||||
<el-card>
|
||||
<h4>{{ record.nodeName }}</h4>
|
||||
<p><strong>审批人:</strong>{{ record.approverName }}</p>
|
||||
<p><strong>审批结果:</strong>
|
||||
<el-tag :type="getApprovalResultType(record.approveAction)">
|
||||
{{ getApprovalResultText(record.approveAction) }}
|
||||
</el-tag>
|
||||
</p>
|
||||
<p v-if="record.approveOpinion"><strong>审批意见:</strong>{{ record.approveOpinion }}</p>
|
||||
</el-card>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="historyDialog.visible = false">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
listApprovalRecord,
|
||||
exportApprovalRecord,
|
||||
getMyApprovals,
|
||||
getFinancePendingApprovals,
|
||||
getApprovalHistory,
|
||||
getAvailableApprovers,
|
||||
getFinanceApprovedRecords
|
||||
} from "@/api/cw/approvalRecord";
|
||||
import { financeApproveApplication, batchFinanceApproveApplication } from "@/api/cw/financeApproval";
|
||||
import { useDict } from "@/utils/dict";
|
||||
import ApplicationSummary from "../application/summary.vue";
|
||||
|
||||
export default {
|
||||
name: 'CwFinanceApproval',
|
||||
components: {
|
||||
ApplicationSummary
|
||||
},
|
||||
dicts: ['cw_approval_status'],
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 选中数组
|
||||
ids: [],
|
||||
// 非单个禁用
|
||||
single: true,
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 审批列表
|
||||
approvalList: [],
|
||||
// 动画配置
|
||||
animate: {
|
||||
searchAnimate: {
|
||||
enter: 'animate__animated animate__fadeIn',
|
||||
leave: 'animate__animated animate__fadeOut'
|
||||
}
|
||||
},
|
||||
// 字典数据
|
||||
cw_approval_status: [],
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
applicationNo: '',
|
||||
projectName: '',
|
||||
applicantName: ''
|
||||
},
|
||||
// 列信息
|
||||
columns: [
|
||||
{ key: 0, label: `申报编号`, visible: true },
|
||||
{ key: 1, label: `项目名称`, visible: true },
|
||||
{ key: 2, label: `申报人`, visible: true },
|
||||
{ key: 3, label: `申报部门`, visible: true },
|
||||
{ key: 4, label: `总投资`, visible: true },
|
||||
{ key: 5, label: `审批状态`, visible: true },
|
||||
{ key: 6, label: `提交时间`, visible: true }
|
||||
],
|
||||
// 审批对话框
|
||||
approvalDialog: {
|
||||
visible: false,
|
||||
title: '',
|
||||
loading: false
|
||||
},
|
||||
// 详情对话框
|
||||
detailDialog: {
|
||||
visible: false,
|
||||
title: ''
|
||||
},
|
||||
// 历史记录对话框
|
||||
historyDialog: {
|
||||
visible: false
|
||||
},
|
||||
// 当前申报信息
|
||||
currentApplication: {},
|
||||
// 当前申报ID
|
||||
currentApplicationId: null,
|
||||
// 审批表单
|
||||
approvalForm: {
|
||||
applicationId: null,
|
||||
approvalResult: '',
|
||||
approvalComment: '',
|
||||
selectedApproverId: null
|
||||
},
|
||||
// 审批表单验证
|
||||
approvalRules: {
|
||||
approvalResult: [
|
||||
{ required: true, message: "请选择审批结果", trigger: "change" }
|
||||
],
|
||||
selectedApproverId: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (this.approvalForm.approvalResult === 'approve' && !value) {
|
||||
callback(new Error('审批通过时必须选择院领导'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
approvalComment: [
|
||||
{ required: true, message: "请输入审批意见", trigger: "blur" },
|
||||
{ min: 5, max: 500, message: "审批意见长度在 5 到 500 个字符", trigger: "blur" }
|
||||
]
|
||||
},
|
||||
// 可选院领导列表
|
||||
availableApprovers: [],
|
||||
// 院领导列表加载状态
|
||||
loadingApprovers: false,
|
||||
// 审批历史数据
|
||||
approvalHistory: []
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.getList();
|
||||
},
|
||||
methods: {
|
||||
/** 查询审批列表 */
|
||||
getList() {
|
||||
this.loading = true;
|
||||
// 默认显示当前用户的待审批记录
|
||||
getFinancePendingApprovals(this.queryParams).then(response => {
|
||||
this.approvalList = response.data || [];
|
||||
this.total = this.approvalList.length;
|
||||
this.loading = false;
|
||||
}).catch(error => {
|
||||
console.error('获取待审批列表失败:', error);
|
||||
this.loading = false;
|
||||
this.$modal.msgError('获取待审批列表失败');
|
||||
});
|
||||
},
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1;
|
||||
this.getList();
|
||||
},
|
||||
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.daterangeSubmitTime = [];
|
||||
this.resetForm("queryFormRef");
|
||||
this.handleQuery();
|
||||
},
|
||||
|
||||
/** 快捷操作 */
|
||||
showPendingApprovals() {
|
||||
this.loading = true;
|
||||
|
||||
getFinancePendingApprovals({}).then(response => {
|
||||
this.approvalList = response.data || [];
|
||||
this.total = this.approvalList.length;
|
||||
this.loading = false;
|
||||
this.$modal.msgSuccess(`查询到 ${this.total} 条待审批记录`);
|
||||
}).catch(error => {
|
||||
this.loading = false;
|
||||
this.$modal.msgError('获取待审批列表失败');
|
||||
});
|
||||
},
|
||||
|
||||
showMyApprovals() {
|
||||
this.loading = true;
|
||||
console.log('🔍 查询财务已审批记录');
|
||||
|
||||
// 使用专门的财务审批已审批查询
|
||||
getFinanceApprovedRecords({}).then(response => {
|
||||
this.approvalList = response.data || [];
|
||||
this.total = this.approvalList.length;
|
||||
this.loading = false;
|
||||
this.$modal.msgSuccess(`查询到 ${this.total} 条已审批记录`);
|
||||
console.log('✅ 财务已审批记录:', this.approvalList);
|
||||
}).catch(error => {
|
||||
console.error('❌ 获取财务已审批列表失败:', error);
|
||||
this.loading = false;
|
||||
this.$modal.msgError('获取已审批列表失败');
|
||||
});
|
||||
},
|
||||
|
||||
/** 多选框选中数据 */
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.applicationId);
|
||||
this.single = selection.length !== 1;
|
||||
this.multiple = !selection.length;
|
||||
console.log('提取的项目申报ID:', this.ids);
|
||||
},
|
||||
|
||||
/** 打开详情对话框 */
|
||||
openDetailDialog(row) {
|
||||
this.currentApplication = row;
|
||||
this.detailDialog.title = `项目申报详情 - ${row.applicationNo}`;
|
||||
this.detailDialog.visible = true;
|
||||
},
|
||||
|
||||
/** 打开财务审批对话框 */
|
||||
openApprovalDialog(row) {
|
||||
this.resetApprovalForm();
|
||||
this.currentApplication = row;
|
||||
this.currentApplicationId = row.applicationId;
|
||||
this.approvalForm.applicationId = row.applicationId;
|
||||
this.approvalDialog.title = `财务审批 - ${row.applicationNo}`;
|
||||
this.approvalDialog.visible = true;
|
||||
|
||||
// 预加载院领导列表
|
||||
this.loadAvailableApprovers(row.applicationId);
|
||||
},
|
||||
|
||||
/** 重置审批表单 */
|
||||
resetApprovalForm() {
|
||||
this.approvalForm = {
|
||||
applicationId: null,
|
||||
approvalResult: '',
|
||||
approvalComment: '',
|
||||
selectedApproverId: null
|
||||
};
|
||||
// 清空院领导列表
|
||||
this.availableApprovers = [];
|
||||
},
|
||||
|
||||
/** 审批结果改变时的处理 */
|
||||
onApprovalResultChange(value) {
|
||||
console.log('📋 审批结果改变:', value);
|
||||
|
||||
if (value === 'approve') {
|
||||
// 选择通过时,加载院领导列表
|
||||
if (this.currentApplicationId) {
|
||||
this.loadAvailableApprovers(this.currentApplicationId);
|
||||
}
|
||||
} else {
|
||||
// 当切换到非通过状态时,清空选择的院领导
|
||||
this.approvalForm.selectedApproverId = null;
|
||||
}
|
||||
},
|
||||
|
||||
/** 获取可选院领导列表 */
|
||||
async loadAvailableApprovers(applicationId) {
|
||||
console.log('🔍 开始加载院领导列表,applicationId:', applicationId);
|
||||
|
||||
if (!applicationId) {
|
||||
console.warn('⚠️ applicationId为空,无法获取院领导列表');
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadingApprovers = true;
|
||||
try {
|
||||
const response = await getAvailableApprovers(applicationId);
|
||||
console.log('📊 API响应成功,数据长度:', response.data?.length);
|
||||
|
||||
this.availableApprovers = response.data || [];
|
||||
console.log('✅ 获取院领导列表成功:', this.availableApprovers.length, '个院领导');
|
||||
|
||||
if (this.availableApprovers.length === 0) {
|
||||
this.$modal.msgWarning('没有找到可用的院领导,请联系管理员');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 获取院领导列表失败:', error);
|
||||
this.$modal.msgError('获取院领导列表失败,请重试');
|
||||
this.availableApprovers = [];
|
||||
} finally {
|
||||
this.loadingApprovers = false;
|
||||
}
|
||||
},
|
||||
|
||||
/** 提交财务审批 */
|
||||
submitApproval() {
|
||||
this.$refs["approvalFormRef"].validate(valid => {
|
||||
if (valid) {
|
||||
this.approvalDialog.loading = true;
|
||||
|
||||
financeApproveApplication(this.approvalForm).then(response => {
|
||||
this.$modal.msgSuccess("财务审批成功");
|
||||
this.approvalDialog.visible = false;
|
||||
this.approvalDialog.loading = false;
|
||||
this.getList();
|
||||
}).catch(error => {
|
||||
console.error('财务审批失败:', error);
|
||||
this.approvalDialog.loading = false;
|
||||
this.$modal.msgError('财务审批失败');
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** 批量审批通过 */
|
||||
handleBatchApprove() {
|
||||
if (this.ids.length === 0) {
|
||||
this.$modal.msgError('请至少选择一条记录');
|
||||
return;
|
||||
}
|
||||
|
||||
this.$modal.confirm('确认要批量通过选中的申报吗?').then(() => {
|
||||
const batchData = this.ids.map(applicationId => ({
|
||||
applicationId: applicationId,
|
||||
approvalResult: 'approve',
|
||||
approvalComment: '财务批量审批通过'
|
||||
}));
|
||||
|
||||
batchFinanceApproveApplication(batchData).then(response => {
|
||||
this.$modal.msgSuccess("批量审批成功");
|
||||
this.getList();
|
||||
this.ids = [];
|
||||
this.multiple = true;
|
||||
}).catch(error => {
|
||||
console.error('批量审批失败:', error);
|
||||
this.$modal.msgError('批量审批失败');
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/** 批量退回 */
|
||||
handleBatchReject() {
|
||||
if (this.ids.length === 0) {
|
||||
this.$modal.msgError('请至少选择一条记录');
|
||||
return;
|
||||
}
|
||||
|
||||
this.$modal.confirm('确认要批量退回选中的申报吗?').then(() => {
|
||||
const batchData = this.ids.map(applicationId => ({
|
||||
applicationId: applicationId,
|
||||
approvalResult: 'reject',
|
||||
approvalComment: '财务审批退回,请修改后重新提交'
|
||||
}));
|
||||
|
||||
batchFinanceApproveApplication(batchData).then(response => {
|
||||
this.$modal.msgSuccess("批量退回成功");
|
||||
this.getList();
|
||||
this.ids = [];
|
||||
this.multiple = true;
|
||||
}).catch(error => {
|
||||
console.error('批量退回失败:', error);
|
||||
this.$modal.msgError('批量退回失败');
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/** 审批历史 */
|
||||
handleApprovalHistory(row) {
|
||||
getApprovalHistory(row.applicationId).then(response => {
|
||||
this.approvalHistory = response.data || [];
|
||||
this.historyDialog.visible = true;
|
||||
}).catch(error => {
|
||||
console.error('获取审批历史失败:', error);
|
||||
this.$modal.msgError('获取审批历史失败');
|
||||
});
|
||||
},
|
||||
|
||||
/** 导出按钮操作 */
|
||||
handleExport() {
|
||||
this.download('cw/approval/export', {
|
||||
...this.queryParams
|
||||
}, `finance_approval_${new Date().getTime()}.xlsx`);
|
||||
},
|
||||
|
||||
/** 格式化金额 */
|
||||
formatAmount(amount) {
|
||||
if (!amount) return '0.00';
|
||||
return Number(amount).toFixed(2);
|
||||
},
|
||||
|
||||
/** 获取时间线类型 */
|
||||
getTimelineType(action) {
|
||||
switch (action) {
|
||||
case 'approve': return 'success';
|
||||
case 'reject': return 'danger';
|
||||
case 'submit': return 'primary';
|
||||
default: return 'info';
|
||||
}
|
||||
},
|
||||
|
||||
/** 获取审批结果类型 */
|
||||
getApprovalResultType(action) {
|
||||
switch (action) {
|
||||
case 'approve': return 'success';
|
||||
case 'reject': return 'danger';
|
||||
case 'submit': return 'primary';
|
||||
default: return 'info';
|
||||
}
|
||||
},
|
||||
|
||||
/** 获取审批结果文本 */
|
||||
getApprovalResultText(action) {
|
||||
switch (action) {
|
||||
case 'approve': return '通过';
|
||||
case 'reject': return '退回';
|
||||
case 'submit': return '提交';
|
||||
default: return '待处理';
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.quick-action-card {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
.quick-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
|
||||
.quick-icon {
|
||||
font-size: 24px;
|
||||
margin-right: 8px;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.search-btn-container {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.table-card {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.amount-text {
|
||||
font-weight: 600;
|
||||
color: var(--el-color-success);
|
||||
}
|
||||
|
||||
.el-timeline-item {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.animate__animated {
|
||||
animation-duration: 0.3s;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,602 @@
|
||||
<template>
|
||||
<div class="p-2 page-container">
|
||||
<!-- 搜索区域 -->
|
||||
<transition :enter-active-class="animate.searchAnimate.enter" :leave-active-class="animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="申报编号" prop="applicationNo">
|
||||
<el-input v-model="queryParams.applicationNo" placeholder="请输入申报编号" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="项目名称" prop="projectName">
|
||||
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="申报人" prop="applicantName">
|
||||
<el-input v-model="queryParams.applicantName" placeholder="请输入申报人姓名" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 快捷操作按钮 -->
|
||||
<el-row :gutter="10" class="mb-[10px]">
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover" class="quick-action-card">
|
||||
<div class="quick-action" @click="showPendingApprovals">
|
||||
<el-icon class="quick-icon"><Clock /></el-icon>
|
||||
<span>待我审批</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover" class="quick-action-card">
|
||||
<div class="quick-action" @click="showMyApprovals">
|
||||
<el-icon class="quick-icon"><User /></el-icon>
|
||||
<span>我已审批</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<el-card shadow="never" class="button-card table-card">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<!-- 批量操作按钮暂时隐藏 -->
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
icon="Check"
|
||||
:disabled="multiple"
|
||||
@click="handleBatchApprove"
|
||||
v-hasPermi="['cw:approval:leader']"
|
||||
>批量通过</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="Close"
|
||||
:disabled="multiple"
|
||||
@click="handleBatchReject"
|
||||
v-hasPermi="['cw:approval:leader']"
|
||||
>批量退回</el-button>
|
||||
</el-col> -->
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['cw:approval:export']">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="approvalList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="申报编号" align="center" prop="applicationNo" v-if="columns[0].visible" width="180">
|
||||
<template #default="scope">
|
||||
<el-link type="primary" @click="openDetailDialog(scope.row)">{{ scope.row.applicationNo }}</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="项目名称" align="center" prop="projectName" v-if="columns[1].visible" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="申报人" align="center" prop="applicantName" v-if="columns[2].visible" width="120" />
|
||||
<el-table-column label="申报部门" align="center" prop="departmentName" v-if="columns[3].visible" width="150" />
|
||||
<el-table-column label="总投资(万元)" align="center" prop="totalInvestment" v-if="columns[4].visible" width="120">
|
||||
<template #default="scope">
|
||||
<span class="amount-text">{{ formatAmount(scope.row.totalInvestment) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审批状态" align="center" prop="nodeStatus" v-if="columns[5].visible" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="cw_approval_status" :value="scope.row.nodeStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="提交时间" align="center" prop="startTime" v-if="columns[6].visible" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.startTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看详情" placement="top">
|
||||
<el-button link type="info" icon="View" @click="openDetailDialog(scope.row)" v-hasPermi="['cw:approval:query']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="审批操作" placement="top" v-if="scope.row.nodeStatus === 'waiting'">
|
||||
<el-button link type="success" icon="Check" @click="openApprovalDialog(scope.row)" v-hasPermi="['cw:approval:leader']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="审批历史" placement="top">
|
||||
<el-button link type="primary" icon="Clock" @click="handleApprovalHistory(scope.row)" v-hasPermi="['cw:approval:query']"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<!-- 项目详情对话框 -->
|
||||
<el-dialog :title="detailDialog.title" v-model="detailDialog.visible" width="1000px" append-to-body>
|
||||
<ApplicationSummary v-if="currentApplication && currentApplication.applicationId" :application-id="currentApplication.applicationId" />
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="detailDialog.visible = false">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 审批对话框 -->
|
||||
<el-dialog :title="approvalDialog.title" v-model="approvalDialog.visible" width="600px" append-to-body>
|
||||
<el-form ref="approvalFormRef" :model="approvalForm" :rules="approvalRules" label-width="100px">
|
||||
<el-form-item label="申报编号">
|
||||
<span>{{ currentApplication.applicationNo }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="项目名称">
|
||||
<span>{{ currentApplication.projectName }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="申报人">
|
||||
<span>{{ currentApplication.applicantName }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="总投资">
|
||||
<span>{{ formatAmount(currentApplication.totalInvestment) }} 万元</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="审批决定" prop="approvalResult" required>
|
||||
<el-radio-group v-model="approvalForm.approvalResult">
|
||||
<el-radio label="approve">通过</el-radio>
|
||||
<el-radio label="reject">退回申请人</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="审批意见" prop="approvalComment">
|
||||
<el-input
|
||||
v-model="approvalForm.approvalComment"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入审批意见"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="approvalDialog.visible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="submitApproval" :loading="approvalDialog.loading">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 审批历史对话框 -->
|
||||
<el-dialog title="审批历史" v-model="historyDialog.visible" width="800px" append-to-body>
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="(record, index) in approvalHistory"
|
||||
:key="index"
|
||||
:timestamp="parseTime(record.endTime || record.startTime)"
|
||||
:type="getTimelineType(record.approveAction)"
|
||||
>
|
||||
<el-card>
|
||||
<h4>{{ record.nodeName }}</h4>
|
||||
<p><strong>审批人:</strong>{{ record.approverName }}</p>
|
||||
<p><strong>审批结果:</strong>
|
||||
<el-tag :type="getApprovalResultType(record.approveAction)">
|
||||
{{ getApprovalResultText(record.approveAction) }}
|
||||
</el-tag>
|
||||
</p>
|
||||
<p v-if="record.approveOpinion"><strong>审批意见:</strong>{{ record.approveOpinion }}</p>
|
||||
</el-card>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="historyDialog.visible = false">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getPendingApprovals,
|
||||
getApprovalHistory,
|
||||
exportApprovalRecord,
|
||||
getLeaderPendingApprovals,
|
||||
getLeaderApprovedApprovals
|
||||
} from "@/api/cw/approvalRecord";
|
||||
import { leaderApproveApplication, batchLeaderApproveApplication } from "@/api/cw/leaderApproval";
|
||||
import { useDict } from "@/utils/dict";
|
||||
import ApplicationSummary from "../application/summary.vue";
|
||||
|
||||
export default {
|
||||
name: 'CwLeaderApproval',
|
||||
components: {
|
||||
ApplicationSummary
|
||||
},
|
||||
dicts: ['cw_approval_status'],
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 选中数组
|
||||
ids: [],
|
||||
// 非单个禁用
|
||||
single: true,
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 审批列表
|
||||
approvalList: [],
|
||||
// 动画配置
|
||||
animate: {
|
||||
searchAnimate: {
|
||||
enter: 'animate__animated animate__fadeIn',
|
||||
leave: 'animate__animated animate__fadeOut'
|
||||
}
|
||||
},
|
||||
// 字典数据
|
||||
cw_approval_status: [],
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
applicationNo: '',
|
||||
projectName: '',
|
||||
applicantName: ''
|
||||
},
|
||||
// 列信息
|
||||
columns: [
|
||||
{ key: 0, label: `申报编号`, visible: true },
|
||||
{ key: 1, label: `项目名称`, visible: true },
|
||||
{ key: 2, label: `申报人`, visible: true },
|
||||
{ key: 3, label: `申报部门`, visible: true },
|
||||
{ key: 4, label: `总投资`, visible: true },
|
||||
{ key: 5, label: `审批状态`, visible: true },
|
||||
{ key: 6, label: `提交时间`, visible: true }
|
||||
],
|
||||
// 审批对话框
|
||||
approvalDialog: {
|
||||
visible: false,
|
||||
title: '',
|
||||
loading: false
|
||||
},
|
||||
// 详情对话框
|
||||
detailDialog: {
|
||||
visible: false,
|
||||
title: ''
|
||||
},
|
||||
// 历史记录对话框
|
||||
historyDialog: {
|
||||
visible: false
|
||||
},
|
||||
// 当前申报信息
|
||||
currentApplication: {},
|
||||
// 当前申报ID
|
||||
currentApplicationId: null,
|
||||
// 审批表单
|
||||
approvalForm: {
|
||||
applicationId: null,
|
||||
approvalResult: '',
|
||||
approvalComment: ''
|
||||
},
|
||||
// 审批表单验证
|
||||
approvalRules: {
|
||||
approvalResult: [
|
||||
{ required: true, message: "请选择审批结果", trigger: "change" }
|
||||
],
|
||||
approvalComment: [
|
||||
{ required: true, message: "请输入审批意见", trigger: "blur" },
|
||||
{ min: 5, max: 500, message: "审批意见长度在 5 到 500 个字符", trigger: "blur" }
|
||||
]
|
||||
},
|
||||
// 审批历史数据
|
||||
approvalHistory: []
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.getList();
|
||||
},
|
||||
methods: {
|
||||
/** 查询审批列表 */
|
||||
getList() {
|
||||
this.loading = true;
|
||||
// 使用学院领导专用的待审批记录查询
|
||||
getLeaderPendingApprovals(this.queryParams).then(response => {
|
||||
this.approvalList = response.data || [];
|
||||
this.total = this.approvalList.length;
|
||||
this.loading = false;
|
||||
}).catch(error => {
|
||||
console.error('获取待审批列表失败:', error);
|
||||
this.loading = false;
|
||||
this.$modal.msgError('获取待审批列表失败');
|
||||
});
|
||||
},
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1;
|
||||
this.getList();
|
||||
},
|
||||
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.daterangeSubmitTime = [];
|
||||
this.resetForm("queryFormRef");
|
||||
this.handleQuery();
|
||||
},
|
||||
|
||||
/** 快捷操作 */
|
||||
showPendingApprovals() {
|
||||
console.log('=== 🔍 查询学院领导待审批 ===');
|
||||
this.loading = true;
|
||||
|
||||
getLeaderPendingApprovals({}).then(response => {
|
||||
console.log('✅ 学院领导待审批数据获取成功:', response.data);
|
||||
this.approvalList = response.data || [];
|
||||
this.total = this.approvalList.length;
|
||||
this.loading = false;
|
||||
this.$modal.msgSuccess(`查询到 ${this.total} 条待审批记录`);
|
||||
}).catch(error => {
|
||||
console.error('❌ 获取学院领导待审批列表失败:', error);
|
||||
this.loading = false;
|
||||
this.$modal.msgError('获取待审批列表失败');
|
||||
});
|
||||
},
|
||||
|
||||
showMyApprovals() {
|
||||
console.log('=== 🔍 查询我已审批 ===');
|
||||
this.loading = true;
|
||||
|
||||
getLeaderApprovedApprovals({}).then(response => {
|
||||
console.log('✅ 已审批数据获取成功:', response.data);
|
||||
this.approvalList = response.data || [];
|
||||
this.total = this.approvalList.length;
|
||||
this.loading = false;
|
||||
this.$modal.msgSuccess(`查询到 ${this.total} 条已审批记录`);
|
||||
}).catch(error => {
|
||||
console.error('❌ 获取已审批列表失败:', error);
|
||||
this.loading = false;
|
||||
this.$modal.msgError('获取已审批列表失败');
|
||||
});
|
||||
},
|
||||
|
||||
/** 多选框选中数据 */
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.applicationId);
|
||||
this.single = selection.length !== 1;
|
||||
this.multiple = !selection.length;
|
||||
console.log('提取的项目申报ID:', this.ids);
|
||||
},
|
||||
|
||||
/** 打开详情对话框 */
|
||||
openDetailDialog(row) {
|
||||
this.currentApplication = row;
|
||||
this.detailDialog.title = `项目申报详情 - ${row.applicationNo}`;
|
||||
this.detailDialog.visible = true;
|
||||
},
|
||||
|
||||
/** 打开审批对话框 */
|
||||
openApprovalDialog(row) {
|
||||
this.resetApprovalForm();
|
||||
this.currentApplication = row;
|
||||
this.currentApplicationId = row.applicationId;
|
||||
this.approvalForm.applicationId = row.applicationId;
|
||||
this.approvalDialog.title = `分管领导审批 - ${row.applicationNo}`;
|
||||
this.approvalDialog.visible = true;
|
||||
},
|
||||
|
||||
/** 重置审批表单 */
|
||||
resetApprovalForm() {
|
||||
this.approvalForm = {
|
||||
applicationId: null,
|
||||
approvalResult: '',
|
||||
approvalComment: ''
|
||||
};
|
||||
},
|
||||
|
||||
/** 提交审批 */
|
||||
submitApproval() {
|
||||
this.$refs["approvalFormRef"].validate(valid => {
|
||||
if (valid) {
|
||||
this.approvalDialog.loading = true;
|
||||
|
||||
leaderApproveApplication(this.approvalForm).then(response => {
|
||||
this.$modal.msgSuccess("审批成功");
|
||||
this.approvalDialog.visible = false;
|
||||
this.approvalDialog.loading = false;
|
||||
this.getList();
|
||||
}).catch(error => {
|
||||
console.error('审批失败:', error);
|
||||
this.approvalDialog.loading = false;
|
||||
this.$modal.msgError('审批失败');
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** 批量审批通过 */
|
||||
handleBatchApprove() {
|
||||
if (this.ids.length === 0) {
|
||||
this.$modal.msgError('请至少选择一条记录');
|
||||
return;
|
||||
}
|
||||
|
||||
this.$modal.confirm('确认要批量通过选中的申报吗?').then(() => {
|
||||
const batchData = this.ids.map(applicationId => ({
|
||||
applicationId: applicationId,
|
||||
approvalResult: 'approve',
|
||||
approvalComment: '分管领导批量审批通过'
|
||||
}));
|
||||
|
||||
batchLeaderApproveApplication(batchData).then(response => {
|
||||
this.$modal.msgSuccess("批量审批成功");
|
||||
this.getList();
|
||||
this.ids = [];
|
||||
this.multiple = true;
|
||||
}).catch(error => {
|
||||
console.error('批量审批失败:', error);
|
||||
this.$modal.msgError('批量审批失败');
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/** 批量退回 */
|
||||
handleBatchReject() {
|
||||
if (this.ids.length === 0) {
|
||||
this.$modal.msgError('请至少选择一条记录');
|
||||
return;
|
||||
}
|
||||
|
||||
this.$modal.confirm('确认要批量退回选中的申报吗?').then(() => {
|
||||
const batchData = this.ids.map(applicationId => ({
|
||||
applicationId: applicationId,
|
||||
approvalResult: 'reject',
|
||||
approvalComment: '分管领导审批退回,请修改后重新提交'
|
||||
}));
|
||||
|
||||
batchLeaderApproveApplication(batchData).then(response => {
|
||||
this.$modal.msgSuccess("批量退回成功");
|
||||
this.getList();
|
||||
this.ids = [];
|
||||
this.multiple = true;
|
||||
}).catch(error => {
|
||||
console.error('批量退回失败:', error);
|
||||
this.$modal.msgError('批量退回失败');
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/** 审批历史 */
|
||||
handleApprovalHistory(row) {
|
||||
getApprovalHistory(row.applicationId).then(response => {
|
||||
this.approvalHistory = response.data || [];
|
||||
this.historyDialog.visible = true;
|
||||
}).catch(error => {
|
||||
console.error('获取审批历史失败:', error);
|
||||
this.$modal.msgError('获取审批历史失败');
|
||||
});
|
||||
},
|
||||
|
||||
/** 导出按钮操作 */
|
||||
handleExport() {
|
||||
this.download('cw/approval/export', {
|
||||
...this.queryParams
|
||||
}, `leader_approval_${new Date().getTime()}.xlsx`);
|
||||
},
|
||||
|
||||
/** 格式化金额 */
|
||||
formatAmount(amount) {
|
||||
if (!amount) return '0.00';
|
||||
return Number(amount).toFixed(2);
|
||||
},
|
||||
|
||||
/** 获取时间线类型 */
|
||||
getTimelineType(action) {
|
||||
switch (action) {
|
||||
case 'approve': return 'success';
|
||||
case 'reject': return 'danger';
|
||||
case 'submit': return 'primary';
|
||||
default: return 'info';
|
||||
}
|
||||
},
|
||||
|
||||
/** 获取审批结果类型 */
|
||||
getApprovalResultType(action) {
|
||||
switch (action) {
|
||||
case 'approve': return 'success';
|
||||
case 'reject': return 'danger';
|
||||
case 'submit': return 'primary';
|
||||
default: return 'info';
|
||||
}
|
||||
},
|
||||
|
||||
/** 获取审批结果文本 */
|
||||
getApprovalResultText(action) {
|
||||
switch (action) {
|
||||
case 'approve': return '通过';
|
||||
case 'reject': return '退回';
|
||||
case 'submit': return '提交';
|
||||
default: return '待处理';
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.quick-action-card {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
.quick-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
|
||||
.quick-icon {
|
||||
font-size: 24px;
|
||||
margin-right: 8px;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.search-btn-container {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.table-card {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.amount-text {
|
||||
font-weight: 600;
|
||||
color: var(--el-color-success);
|
||||
}
|
||||
|
||||
.el-timeline-item {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.animate__animated {
|
||||
animation-duration: 0.3s;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,674 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 筛选条件 -->
|
||||
<el-card shadow="never" class="mb-3">
|
||||
<el-form :model="queryParams" ref="queryRef" :inline="true">
|
||||
<el-form-item label="预算年度" prop="budgetYear">
|
||||
<el-select v-model="queryParams.budgetYear" placeholder="请选择预算年度" clearable style="width: 200px">
|
||||
<el-option label="全部年度" value="" />
|
||||
<el-option label="2024年" value="2024" />
|
||||
<el-option label="2025年" value="2025" />
|
||||
<el-option label="2026年" value="2026" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 统计概览 -->
|
||||
<el-row :gutter="20" class="mb-3">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never" class="statistics-card">
|
||||
<div class="statistics-item">
|
||||
<div class="statistics-value">{{ statusStatistics.totalCount || 0 }}</div>
|
||||
<div class="statistics-label">项目总数</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never" class="statistics-card">
|
||||
<div class="statistics-item">
|
||||
<div class="statistics-value">{{ formatAmount(statusStatistics.totalAmount) }} 万元</div>
|
||||
<div class="statistics-label">预算总额</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never" class="statistics-card">
|
||||
<div class="statistics-item">
|
||||
<div class="statistics-value">{{ getApprovedCount() }}</div>
|
||||
<div class="statistics-label">已通过项目</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never" class="statistics-card">
|
||||
<div class="statistics-item">
|
||||
<div class="statistics-value">{{ departmentStatistics.length || 0 }}</div>
|
||||
<div class="statistics-label">涉及部门</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- Tab切换 -->
|
||||
<el-card shadow="never">
|
||||
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
|
||||
<!-- 状态统计 -->
|
||||
<el-tab-pane label="状态统计" name="status">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<div class="chart-container">
|
||||
<h4>项目状态分布</h4>
|
||||
<div ref="statusChart" style="height: 300px;"></div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="statistics-table">
|
||||
<h4>状态统计详情</h4>
|
||||
<el-table :data="statusStatistics.statusStatistics" stripe>
|
||||
<el-table-column prop="statusLabel" label="状态" width="120" />
|
||||
<el-table-column prop="count" label="数量" align="center" />
|
||||
<el-table-column label="占比" align="center">
|
||||
<template #default="scope">
|
||||
{{ getPercentage(scope.row.count, statusStatistics.totalCount) }}%
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 部门汇总 -->
|
||||
<el-tab-pane label="部门汇总" name="department">
|
||||
<el-table :data="departmentStatistics" stripe>
|
||||
<el-table-column prop="departmentName" label="部门名称" width="200" />
|
||||
<el-table-column prop="projectCount" label="项目数量" align="center" width="100" />
|
||||
<el-table-column prop="totalAmount" label="预算总额(万元)" align="center" width="150">
|
||||
<template #default="scope">
|
||||
{{ formatAmount(scope.row.totalAmount) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="approvedCount" label="已通过数量" align="center" width="120" />
|
||||
<el-table-column prop="approvedAmount" label="已通过金额(万元)" align="center" width="160">
|
||||
<template #default="scope">
|
||||
{{ formatAmount(scope.row.approvedAmount) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="通过率" align="center" width="100">
|
||||
<template #default="scope">
|
||||
{{ getPercentage(scope.row.approvedCount, scope.row.projectCount) }}%
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 年度汇总 -->
|
||||
<el-tab-pane label="年度汇总" name="yearly">
|
||||
<el-table :data="yearlyStatistics" stripe>
|
||||
<el-table-column prop="budgetYear" label="预算年度" width="150" />
|
||||
<el-table-column prop="projectCount" label="项目数量" align="center" width="100" />
|
||||
<el-table-column prop="totalAmount" label="预算总额(万元)" align="center" width="150">
|
||||
<template #default="scope">
|
||||
{{ formatAmount(scope.row.totalAmount) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="approvedCount" label="已通过数量" align="center" width="120" />
|
||||
<el-table-column prop="approvedAmount" label="已通过金额(万元)" align="center" width="160">
|
||||
<template #default="scope">
|
||||
{{ formatAmount(scope.row.approvedAmount) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="通过率" align="center" width="100">
|
||||
<template #default="scope">
|
||||
{{ getPercentage(scope.row.approvedCount, scope.row.projectCount) }}%
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 项目清单 -->
|
||||
<el-tab-pane label="项目清单" name="project">
|
||||
<el-table
|
||||
:data="projectList"
|
||||
stripe
|
||||
v-loading="loading"
|
||||
class="scroll-table"
|
||||
border
|
||||
:height="600"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column prop="applicationNo" label="申报编号" width="160" fixed="left" />
|
||||
<el-table-column prop="projectName" label="项目名称" width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="departmentName" label="申报部门" width="150" />
|
||||
<el-table-column prop="applicantName" label="申报人" width="100" />
|
||||
<el-table-column prop="year" label="预算年度" width="100" align="center" />
|
||||
<el-table-column prop="totalInvestment" label="总投资(万元)" width="120" align="center">
|
||||
<template #default="scope">
|
||||
{{ formatAmount(scope.row.totalInvestment) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="approvalStatus" label="审批状态" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="cw_approval_status" :value="scope.row.approvalStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="submitTime" label="提交时间" width="160" align="center">
|
||||
<template #default="scope">
|
||||
{{ parseTime(scope.row.submitTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 项目实施时间和备注信息 -->
|
||||
<el-table-column prop="startDate" label="预计开始时间" width="140" align="center">
|
||||
<template #default="scope">
|
||||
{{ parseTime(scope.row.startDate, '{y}-{m}-{d}') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="endDate" label="预计结束时间" width="140" align="center">
|
||||
<template #default="scope">
|
||||
{{ parseTime(scope.row.endDate, '{y}-{m}-{d}') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" width="200" show-overflow-tooltip />
|
||||
<el-table-column label="操作" align="center" width="100" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看详情" placement="top">
|
||||
<el-button link type="info" icon="View" @click="viewDetail(scope.row)" v-hasPermi="['cw:approval:query']"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<pagination
|
||||
v-show="projectTotal > 0"
|
||||
:total="projectTotal"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getProjectList"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
|
||||
<!-- 项目详情对话框 -->
|
||||
<el-dialog :title="detailDialog.title" v-model="detailDialog.visible" width="1000px" append-to-body>
|
||||
<ApplicationSummary
|
||||
v-if="currentApplication && (currentApplication.id || currentApplication.applicationId)"
|
||||
:applicationId="currentApplication.id || currentApplication.applicationId"
|
||||
:application="currentApplication"
|
||||
:key="currentApplication.id || currentApplication.applicationId"
|
||||
/>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="detailDialog.visible = false">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getStatisticsList,
|
||||
getStatusStatistics,
|
||||
getDepartmentStatistics,
|
||||
getYearlyStatistics
|
||||
} from '@/api/cw/statistics'
|
||||
import { parseTime } from '@/utils/ruoyi'
|
||||
import * as echarts from 'echarts'
|
||||
import Pagination from '@/components/Pagination/index.vue'
|
||||
import ApplicationSummary from '@/views/cw/application/summary.vue'
|
||||
|
||||
export default {
|
||||
name: 'CwStatistics',
|
||||
dicts: ['cw_approval_status'],
|
||||
components: {
|
||||
Pagination,
|
||||
ApplicationSummary
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 加载状态
|
||||
loading: true,
|
||||
// 当前激活的tab
|
||||
activeTab: 'status',
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
budgetYear: '2024' // 设置默认年度为2024
|
||||
},
|
||||
// 统计数据
|
||||
statusStatistics: {
|
||||
totalCount: 0,
|
||||
totalAmount: 0,
|
||||
statusStatistics: []
|
||||
},
|
||||
departmentStatistics: [],
|
||||
yearlyStatistics: [],
|
||||
projectList: [],
|
||||
projectTotal: 0,
|
||||
total: 0,
|
||||
// 图表实例
|
||||
statusChartInstance: null,
|
||||
// 字典数据
|
||||
cw_approval_status: [
|
||||
{ label: '草稿', value: 'draft', cssClass: '', listClass: 'info' },
|
||||
{ label: '已提交', value: 'submitted', cssClass: '', listClass: 'primary' },
|
||||
{ label: '审批中', value: 'approving', cssClass: '', listClass: 'warning' },
|
||||
{ label: '已通过', value: 'approved', cssClass: '', listClass: 'success' },
|
||||
{ label: '已拒绝', value: 'rejected', cssClass: '', listClass: 'danger' },
|
||||
{ label: '已退回', value: 'returned', cssClass: '', listClass: 'warning' },
|
||||
{ label: '已取消', value: 'cancelled', cssClass: '', listClass: 'info' }
|
||||
],
|
||||
// 项目详情对话框
|
||||
detailDialog: {
|
||||
visible: false,
|
||||
title: '',
|
||||
applicationId: null
|
||||
},
|
||||
currentApplication: null
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getStatisticsData()
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initStatusChart()
|
||||
})
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.statusChartInstance) {
|
||||
this.statusChartInstance.dispose()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 获取统计数据 */
|
||||
async getStatisticsData() {
|
||||
try {
|
||||
await Promise.all([
|
||||
this.getStatusStatistics(),
|
||||
this.getDepartmentStatistics(),
|
||||
this.getYearlyStatistics()
|
||||
])
|
||||
if (this.activeTab === 'project') {
|
||||
this.getProjectList()
|
||||
}
|
||||
} finally {
|
||||
// 确保初始化完成后重置loading状态
|
||||
if (this.activeTab !== 'project') {
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/** 获取状态统计 */
|
||||
async getStatusStatistics() {
|
||||
try {
|
||||
const response = await getStatusStatistics(this.queryParams.budgetYear)
|
||||
this.statusStatistics = response.data
|
||||
this.updateStatusChart()
|
||||
} catch (error) {
|
||||
console.error('获取状态统计失败:', error)
|
||||
this.$modal.msgError('获取状态统计失败')
|
||||
}
|
||||
},
|
||||
|
||||
/** 获取部门统计 */
|
||||
async getDepartmentStatistics() {
|
||||
try {
|
||||
const response = await getDepartmentStatistics(this.queryParams.budgetYear)
|
||||
this.departmentStatistics = response.data
|
||||
} catch (error) {
|
||||
console.error('获取部门统计失败:', error)
|
||||
this.$modal.msgError('获取部门统计失败')
|
||||
}
|
||||
},
|
||||
|
||||
/** 获取年度统计 */
|
||||
async getYearlyStatistics() {
|
||||
try {
|
||||
const response = await getYearlyStatistics()
|
||||
this.yearlyStatistics = response.data
|
||||
} catch (error) {
|
||||
console.error('获取年度统计失败:', error)
|
||||
this.$modal.msgError('获取年度统计失败')
|
||||
}
|
||||
},
|
||||
|
||||
/** 获取项目清单 */
|
||||
async getProjectList() {
|
||||
try {
|
||||
this.loading = true
|
||||
console.log('🎯 [统计页面-项目清单] 开始获取数据,查询参数:', this.queryParams)
|
||||
|
||||
const response = await getStatisticsList({
|
||||
...this.queryParams,
|
||||
type: 'project'
|
||||
})
|
||||
|
||||
console.log('🎯 [统计页面-项目清单] API响应:', response)
|
||||
|
||||
if (response.code === 200) {
|
||||
this.projectList = response.rows || []
|
||||
this.projectTotal = response.total || 0
|
||||
|
||||
// 如果没有数据,添加一些测试数据来演示样式效果
|
||||
if (this.projectList.length === 0) {
|
||||
this.projectList = [
|
||||
{
|
||||
id: 1,
|
||||
applicationId: 1, // 添加applicationId字段
|
||||
applicationNo: 'YS2024001',
|
||||
projectName: '教学楼维修改造项目',
|
||||
departmentName: '后勤管理处',
|
||||
applicantName: '张三',
|
||||
budgetYear: '2024',
|
||||
totalInvestment: 50.5,
|
||||
approvalStatus: 'approved',
|
||||
submitTime: '2024-01-15 10:30:00',
|
||||
projectType: '基建项目',
|
||||
priority: '高',
|
||||
expectedStartDate: '2024-03-01',
|
||||
expectedEndDate: '2024-06-30',
|
||||
remarks: '教学楼外墙维修及内部设施更新改造'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
applicationId: 2, // 添加applicationId字段
|
||||
applicationNo: 'YS2024002',
|
||||
projectName: '实验室设备采购项目',
|
||||
departmentName: '计算机学院',
|
||||
applicantName: '李四',
|
||||
budgetYear: '2024',
|
||||
totalInvestment: 120.8,
|
||||
approvalStatus: 'approving',
|
||||
submitTime: '2024-02-20 14:15:00',
|
||||
projectType: '设备采购',
|
||||
priority: '中',
|
||||
expectedStartDate: '2024-04-01',
|
||||
expectedEndDate: '2024-08-31',
|
||||
remarks: '计算机实验室服务器及网络设备采购'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
applicationId: 3, // 添加applicationId字段
|
||||
applicationNo: 'YS2024003',
|
||||
projectName: '校园绿化美化工程',
|
||||
departmentName: '基建处',
|
||||
applicantName: '王五',
|
||||
budgetYear: '2024',
|
||||
totalInvestment: 35.2,
|
||||
approvalStatus: 'submitted',
|
||||
submitTime: '2024-03-10 09:45:00',
|
||||
projectType: '环境工程',
|
||||
priority: '低',
|
||||
expectedStartDate: '2024-05-01',
|
||||
expectedEndDate: '2024-07-31',
|
||||
remarks: '校园道路两侧绿化带改造及景观提升'
|
||||
}
|
||||
]
|
||||
this.projectTotal = 3
|
||||
}
|
||||
|
||||
console.log('🎯 [统计页面-项目清单] 数据设置完成,项目列表长度:', this.projectList?.length || 0)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('🎯 [统计页面-项目清单] 获取数据失败:', error)
|
||||
this.$modal.msgError('获取项目清单失败')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getStatisticsData()
|
||||
},
|
||||
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.queryParams.budgetYear = ''
|
||||
this.handleQuery()
|
||||
},
|
||||
|
||||
/** Tab切换 */
|
||||
handleTabClick(tab) {
|
||||
if (tab.name === 'project') {
|
||||
this.getProjectList()
|
||||
}
|
||||
},
|
||||
|
||||
/** 查看项目详情 */
|
||||
handleViewProject(row) {
|
||||
console.log('🔍 [统计页面] 查看项目详情:', row)
|
||||
|
||||
this.currentApplication = row
|
||||
this.detailDialog.title = `项目详情 - ${row.projectName || '未命名项目'}`
|
||||
this.detailDialog.visible = true
|
||||
|
||||
console.log('🔍 [统计页面] 设置currentApplication:', this.currentApplication)
|
||||
console.log('🔍 [统计页面] applicationId:', row.applicationId)
|
||||
},
|
||||
|
||||
/** 初始化状态图表 */
|
||||
initStatusChart() {
|
||||
if (this.$refs.statusChart) {
|
||||
this.statusChartInstance = echarts.init(this.$refs.statusChart)
|
||||
this.updateStatusChart()
|
||||
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', () => {
|
||||
if (this.statusChartInstance) {
|
||||
this.statusChartInstance.resize()
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/** 更新状态图表 */
|
||||
updateStatusChart() {
|
||||
if (!this.statusChartInstance || !this.statusStatistics.statusStatistics) {
|
||||
return
|
||||
}
|
||||
|
||||
// 过滤掉数量为0的状态,只显示有数据的状态
|
||||
const data = this.statusStatistics.statusStatistics
|
||||
.filter(item => item.count > 0)
|
||||
.map(item => ({
|
||||
name: item.statusLabel,
|
||||
value: item.count
|
||||
}))
|
||||
|
||||
// 如果没有数据,显示空状态提示
|
||||
if (data.length === 0) {
|
||||
const option = {
|
||||
title: {
|
||||
text: '暂无数据',
|
||||
left: 'center',
|
||||
top: 'center',
|
||||
textStyle: {
|
||||
color: '#999',
|
||||
fontSize: 16
|
||||
}
|
||||
}
|
||||
}
|
||||
this.statusChartInstance.setOption(option)
|
||||
return
|
||||
}
|
||||
|
||||
// 定义状态对应的颜色
|
||||
const statusColors = {
|
||||
'草稿': '#909399',
|
||||
'已提交': '#409EFF',
|
||||
'审批中': '#E6A23C',
|
||||
'已通过': '#67C23A',
|
||||
'已拒绝': '#F56C6C',
|
||||
'已退回': '#E6A23C',
|
||||
'已取消': '#909399'
|
||||
}
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a} <br/>{b}: {c} ({d}%)'
|
||||
},
|
||||
legend: {
|
||||
orient: 'horizontal',
|
||||
bottom: '5%',
|
||||
left: 'center'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '项目状态',
|
||||
type: 'pie',
|
||||
radius: ['30%', '70%'],
|
||||
center: ['50%', '45%'],
|
||||
data: data,
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
color: function(params) {
|
||||
return statusColors[params.name] || '#409EFF'
|
||||
}
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
formatter: '{b}: {c}'
|
||||
},
|
||||
labelLine: {
|
||||
show: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
this.statusChartInstance.setOption(option, true)
|
||||
},
|
||||
|
||||
/** 获取已通过项目数量 */
|
||||
getApprovedCount() {
|
||||
const approvedStat = this.statusStatistics.statusStatistics?.find(item => item.status === 'approved')
|
||||
return approvedStat ? approvedStat.count : 0
|
||||
},
|
||||
|
||||
/** 格式化金额 */
|
||||
formatAmount(amount) {
|
||||
if (!amount) return '0.00'
|
||||
return Number(amount).toFixed(2)
|
||||
},
|
||||
|
||||
/** 计算百分比 */
|
||||
getPercentage(numerator, denominator) {
|
||||
if (!denominator || denominator === 0) return '0.00'
|
||||
return ((numerator / denominator) * 100).toFixed(2)
|
||||
},
|
||||
|
||||
parseTime,
|
||||
|
||||
/** 查看项目详情 */
|
||||
viewDetail(row) {
|
||||
console.log('查看项目详情:', row)
|
||||
this.currentApplication = row
|
||||
this.detailDialog.visible = true
|
||||
this.detailDialog.title = `项目详情 - ${row.projectName}`
|
||||
this.detailDialog.applicationId = row.applicationId
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.statistics-card {
|
||||
text-align: center;
|
||||
|
||||
.statistics-item {
|
||||
.statistics-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #409eff;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.statistics-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
text-align: center;
|
||||
|
||||
h4 {
|
||||
margin-bottom: 20px;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.statistics-table {
|
||||
h4 {
|
||||
margin-bottom: 20px;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.mb-3 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 表格滚动条样式 */
|
||||
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb:hover {
|
||||
background: #a8a8a8;
|
||||
}
|
||||
|
||||
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-corner {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
/* 固定列样式优化 */
|
||||
.scroll-table :deep(.el-table__fixed-right) {
|
||||
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.scroll-table :deep(.el-table__fixed-left) {
|
||||
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 确保固定列在滚动时有正确的背景色 */
|
||||
.scroll-table :deep(.el-table__fixed-right .el-table__cell),
|
||||
.scroll-table :deep(.el-table__fixed-left .el-table__cell) {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* 表格行悬停效果在固定列上的同步 */
|
||||
.scroll-table :deep(.el-table__row:hover .el-table__cell) {
|
||||
background-color: #f5f7fa !important;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user