This commit is contained in:
wuzhen committed 2025-08-04 13:38:08 +08:00
1 parent 3b99209548
commit 8d8a8cc30c
56 files changed
+13727 -1241

No files matched your search

+46 -1
View File
@@ -85,7 +85,16 @@ export const approveApplication = (data: CwApprovalForm) => {
// 🔧 修复:使用部门审批接口,将JSON数据转换为URL参数
const params = new URLSearchParams();
params.append('applicationId', String(data.applicationId || ''));
params.append('approveAction', data.approvalResult === 'approved' ? 'approve' : 'return');
// 🔧 修复:正确处理审批结果映射
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({
@@ -172,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({
+46
View File
@@ -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: '批量操作成功'
};
});
};
+41
View File
@@ -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'
})
}
+26 -80
View File
@@ -378,16 +378,7 @@
分年度预算明细
</h4>
<!-- 调试信息 -->
<div style="background: #f5f5f5; padding: 10px; margin-bottom: 10px; border-radius: 4px; font-size: 12px;">
<strong>调试信息:</strong><br>
当前年度: {{ form.year }}<br>
分年度数据长度: {{ budgetBreakdownData.length }}<br>
当年预算: 总投资={{ form.currentYearTotal }}万, 财政={{ form.currentYearMunicipal }}万, 自筹={{ form.currentYearSelf }}万, 其他={{ form.currentYearOther }}万<br>
下年预算: 总投资={{ form.nextYearTotal }}万, 财政={{ form.nextYearMunicipal }}万, 自筹={{ form.nextYearSelf }}万, 其他={{ form.nextYearOther }}万<br>
后续预算: 总投资={{ form.futureYearTotal }}万, 财政={{ form.futureYearMunicipal }}万, 自筹={{ form.futureYearSelf }}万, 其他={{ form.futureYearOther }}万<br>
表格数据: {{ JSON.stringify(budgetBreakdownData) }}
</div>
<el-table :data="budgetBreakdownData" border style="width: 100%">
<el-table-column prop="yearLabel" label="年度" width="120" align="center"/>
@@ -638,9 +629,9 @@ const formatDateString = (dateValue: any): string => {
} else {
return ''
}
if (isNaN(date.getTime())) return ''
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
@@ -651,9 +642,7 @@ const updateFormWithData = (newData: any) => {
if (!newData || typeof newData !== 'object' || Object.keys(newData).length === 0) {
return
}
console.log('接收到父组件传递的数据:', newData)
try {
// 安全地更新form数据
Object.keys(form).forEach(key => {
@@ -663,7 +652,7 @@ const updateFormWithData = (newData: any) => {
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') ||
} 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')) {
// 金额字段 - 扩展匹配模式
@@ -692,8 +681,6 @@ const updateFormWithData = (newData: any) => {
// 数据加载完成后,重新计算年度总数
// calculateYearlyTotals() // 移除此函数调用
console.log('form数据更新完成:', form)
} catch (error) {
console.error('更新form数据时出错:', error)
// 即使出错也要确保基本数据结构
@@ -789,21 +776,16 @@ 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 || 0
form[`${fieldPrefix}Total`] = breakdown.total
form[`${fieldPrefix}Municipal`] = breakdown.municipal || 0
form[`${fieldPrefix}Self`] = breakdown.self || 0
form[`${fieldPrefix}Other`] = breakdown.other || 0
console.log(`更新${breakdown.yearLabel}预算:`, {
total: breakdown.total,
municipal: breakdown.municipal,
self: breakdown.self,
other: breakdown.other
})
// 重新计算年度执行总数
// calculateYearlyTotals() // 移除此函数调用
// 重新计算总投资
recalculateTotalFromBreakdown()
}
@@ -813,39 +795,32 @@ 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
console.log('重新计算总投资:', {
totalMunicipal,
totalSelf,
totalOther,
totalInvestment: form.totalInvestment
})
}
// 计算预算汇总
const calculateBudgetSummary = () => {
// 计算各类资金的总和
const totalMunicipal = (form.currentYearMunicipal || 0) +
(form.nextYearMunicipal || 0) +
const totalMunicipal = (form.currentYearMunicipal || 0) +
(form.nextYearMunicipal || 0) +
(form.futureYearMunicipal || 0)
const totalSelf = (form.currentYearSelf || 0) +
(form.nextYearSelf || 0) +
const totalSelf = (form.currentYearSelf || 0) +
(form.nextYearSelf || 0) +
(form.futureYearSelf || 0)
const totalOther = (form.currentYearOther || 0) +
(form.nextYearOther || 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()
@@ -931,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
}
@@ -1011,9 +983,6 @@ const handlePrevStep = () => {
// 处理绩效指标变化
const handlePerformanceIndicatorsChange = (indicators) => {
console.log('=== 绩效指标数据变化 ===')
console.log('接收到的indicators:', indicators)
// 为每个指标添加必要的字段
const processedIndicators = indicators.map((item, index) => ({
...item,
@@ -1037,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)
}
}
// 保存草稿
@@ -1065,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)
@@ -1081,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) {
@@ -1303,7 +1249,7 @@ const loadData = async () => {
const validate = (callback: (valid: boolean, formData?: any) => void) => {
// 验证逻辑 + 数据收集
const isValid = !!(form.projectName && form.principal && form.totalInvestment > 0)
if (isValid) {
// 收集绩效指标数据
if (performanceIndicatorsRef.value && typeof performanceIndicatorsRef.value.getAllIndicators === 'function') {
@@ -1311,7 +1257,7 @@ const validate = (callback: (valid: boolean, formData?: any) => void) => {
form.performanceIndicators = indicators
console.log('收集到绩效指标数据:', indicators)
}
// 返回验证结果和完整的表单数据
callback(true, form)
} else {
@@ -1484,7 +1430,7 @@ onMounted(() => {
:deep(.el-table) {
.el-table__header {
background-color: #f5f7fa;
.el-table__cell {
background-color: #f5f7fa;
color: #606266;
@@ -1502,7 +1448,7 @@ onMounted(() => {
.el-input-number {
width: 100%;
:deep(.el-input__inner) {
text-align: right;
}
@@ -1511,10 +1457,10 @@ onMounted(() => {
.budget-breakdown-note {
margin-top: 16px;
:deep(.el-alert) {
border-radius: 4px;
.el-alert__title {
font-size: 13px;
line-height: 1.6;
+91 -23
View File
@@ -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>
@@ -376,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('获取数据失败,请检查网络连接或联系管理员')
})
},
/** 获取统计数据 */
@@ -588,7 +595,7 @@ export default {
<style scoped lang="scss">
/* 继承后勤管理模块的样式风格 */
.page-container {
padding: 20px;
padding: 8px;
/* 统计卡片样式 */
.stats-row {
@@ -658,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 {
@@ -705,12 +708,13 @@ export default {
}
}
/* 表格样式 */
/* 表格主体 */
.table-container {
width: 100%;
overflow-x: auto;
margin-bottom: 0;
}
/* 表格样式 - 参考后勤管理功能 */
.scroll-table {
width: 100%;
@@ -733,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;
@@ -742,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;
@@ -757,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;
}
/* 对话框样式 */
File diff suppressed because it is too large. Load diff
+685
View File
@@ -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>
+12 -11
View File
@@ -136,7 +136,7 @@
<!-- 项目详情对话框 -->
<el-dialog :title="detailDialog.title" v-model="detailDialog.visible" width="1000px" append-to-body>
<ApplicationSummary v-if="currentApplication && currentApplication.id" :application-id="currentApplication.id" />
<ApplicationSummary v-if="currentApplication && currentApplication.applicationId" :application-id="currentApplication.applicationId" />
<template #footer>
<div class="dialog-footer">
<el-button @click="detailDialog.visible = false">关 闭</el-button>
@@ -161,7 +161,7 @@
</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="approve">通过</el-radio>
<el-radio label="reject">退回申请人</el-radio>
</el-radio-group>
</el-form-item>
@@ -217,9 +217,10 @@
<script>
import {
getPendingApprovals,
getMyApprovals,
getApprovalHistory,
exportApprovalRecord
exportApprovalRecord,
getLeaderPendingApprovals,
getLeaderApprovedApprovals
} from "@/api/cw/approvalRecord";
import { leaderApproveApplication, batchLeaderApproveApplication } from "@/api/cw/leaderApproval";
import { useDict } from "@/utils/dict";
@@ -320,8 +321,8 @@ export default {
/** 查询审批列表 */
getList() {
this.loading = true;
// 默认显示当前用户的待审批记录
getPendingApprovals(this.queryParams).then(response => {
// 使用学院领导专用的待审批记录查询
getLeaderPendingApprovals(this.queryParams).then(response => {
this.approvalList = response.data || [];
this.total = this.approvalList.length;
this.loading = false;
@@ -347,17 +348,17 @@ export default {
/** 快捷操作 */
showPendingApprovals() {
console.log('=== 🔍 查询待我审批 ===');
console.log('=== 🔍 查询学院领导待审批 ===');
this.loading = true;
getPendingApprovals({}).then(response => {
console.log('✅ 待审批数据获取成功:', response.data);
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);
console.error('❌ 获取学院领导待审批列表失败:', error);
this.loading = false;
this.$modal.msgError('获取待审批列表失败');
});
@@ -367,7 +368,7 @@ export default {
console.log('=== 🔍 查询我已审批 ===');
this.loading = true;
getMyApprovals({}).then(response => {
getLeaderApprovedApprovals({}).then(response => {
console.log('✅ 已审批数据获取成功:', response.data);
this.approvalList = response.data || [];
this.total = this.approvalList.length;
+674
View File
@@ -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>