-
+
+
+
+
+
+
+
+
+
+
{{ summaryStats.bigPackageCount }}
+
大包数量
+
+
+
+
+
+
+
+
+
+
+
+
{{ summaryStats.mediumPackageCount }}
+
中包数量
+
+
+
+
+
+
+
+
+
+
+
+
{{ formatAmount(summaryStats.totalAllocatedAmount) }}
+
总分配金额(万元)
+
+
+
+
+
+
+
+
+
+
+
+
{{ formatAmount(summaryStats.totalReportedAmount) }}
+
总上报金额(万元)
+
+
+
+
+
+
+
+
+
+ 按大包统计
+ 按部门统计
+ 按经济分类统计
+
+
+
+
+
+
-
+
-
- {{ scope.row.packageName }}
-
+ {{ scope.row.bigPackageName }}
+
+
+
+
+ {{ scope.row.mediumPackageName }}
+
+
+
+
+ {{ formatAmount(scope.row.allocated_amount) }}
+
+
+
+
+
+ {{ formatAmount(scope.row.reported_amount) }}
+
+
+ {{ scope.row.submission_status === 'no_submission' ? '未申报' : '0' }}
+
+
+
+
+
+ {{ formatAmount(scope.row.totalAllocated) }}
+
+
+
+
+ {{ formatAmount(scope.row.totalReported) }}
-
-
- {{ formatCurrency(scope.row['amount_' + classification.code]) }}
+
+ {{ scope.row.economicAmounts && scope.row.economicAmounts[classification.code] && scope.row.economicAmounts[classification.code] > 0
+ ? formatAmount(scope.row.economicAmounts[classification.code])
+ : '-' }}
- -
-
-
+
+
+
+
+
+ {{ scope.row.departmentName }}
+
+
+
+
+ {{ scope.row.projectFullName }}
+
+
+
+
+ {{ formatAmount(scope.row.allocated_amount) }}
+
+
+
+
+
+ {{ formatAmount(scope.row.reported_amount) }}
+
+
+ {{ scope.row.submission_status === 'no_submission' ? '未申报' : '0' }}
+
+
+
+
+
+ {{ formatAmount(scope.row.deptTotalAllocated) }}
+
+
+
+
+ {{ formatAmount(scope.row.deptTotalReported) }}
+
+
+
-
- {{ formatCurrency(scope.row.totalAmount) }}
+
+ {{ scope.row.economicAmounts && scope.row.economicAmounts[classification.code] && scope.row.economicAmounts[classification.code] > 0
+ ? formatAmount(scope.row.economicAmounts[classification.code])
+ : '-' }}
+
+
+
+
+
+ {{ scope.row.economicClassName }}
+
+
+
+
+ {{ scope.row.departmentName }}
+
+
+
+
+ {{ scope.row.projectFullName ? scope.row.projectFullName : scope.row.projectName }}
+
+
+
+
+ {{ formatAmount(scope.row.projectAmount) }}
+
+
+
+
+ {{ formatAmount(scope.row.deptTotal) }}
+
+
+
+
+ {{ formatAmount(scope.row.economicTotal) }}
+
+
+
@@ -497,7 +678,8 @@ import {
deleteBudgetCollectionPackage,
batchDeleteBudgetCollectionPackage,
getBudgetCollectionPackageOptions,
- getBudgetCollectionPackageSummary
+ getBudgetCollectionPackageSummary,
+ getEconomicClassifications
} from '@/api/budget/budgetCollectionPackage.js'
import { getBudgetCollectionYearOptions } from '@/api/budget/budgetCollectionYear.js'
import { listdeptNoAuth } from '@/api/system/department.js'
@@ -578,7 +760,20 @@ export default {
summaryLoading: false,
crossTableData: [],
economicClassifications: [],
- crossTableSummary: null
+ crossTableSummary: null,
+ // 新增预算表格数据
+ rawBudgetData: [], // 原始数据,深拷贝保存避免引用共享
+ budgetTableData: [],
+ budgetTableFlatData: [], // 扁平化的表格数据用于el-table展示
+ budgetTableFlatDataByDept: [], // 按部门统计的扁平化数据
+ budgetTableFlatDataByEconomic: [], // 按经济分类统计的扁平化数据
+ summaryMode: 'package', // 统计模式:package、department 或 economic
+ summaryStats: {
+ bigPackageCount: 0,
+ mediumPackageCount: 0,
+ totalAllocatedAmount: 0,
+ totalReportedAmount: 0
+ }
}
},
computed: {
@@ -600,6 +795,21 @@ export default {
}
return [...this.crossTableData, summaryRow]
+ },
+
+ // 按大包统计合并方法
+ packageMergeMethod() {
+ return this.mergeMethod
+ },
+
+ // 按部门统计合并方法
+ departmentMergeMethod() {
+ return this.mergeMethodByDept
+ },
+
+ // 按经济分类统计合并方法
+ economicMergeMethod() {
+ return this.mergeMethodByEconomic
}
},
created() {
@@ -655,19 +865,33 @@ export default {
try {
// 加载预算年度选项
const yearResponse = await getBudgetCollectionYearOptions()
- if (yearResponse && yearResponse.length) {
- this.yearOptions = yearResponse
+ if (yearResponse && yearResponse.code === 0) {
+ let yearData = yearResponse.data
+
+ // 如果data是字符串,尝试解析JSON
+ if (typeof yearData === 'string') {
+ try {
+ yearData = JSON.parse(yearData)
+ } catch (e) {
+ console.error('解析年度选项JSON失败:', e)
+ yearData = []
+ }
+ }
+
+ if (Array.isArray(yearData)) {
+ this.yearOptions = yearData
+ }
}
// 加载部门选项
const deptResponse = await listdeptNoAuth()
- if (deptResponse && Array.isArray(deptResponse)) {
+ if (deptResponse) {
this.departmentOptions = deptResponse
}
console.log('选项加载完成:', {
yearOptions: this.yearOptions.length,
- departmentOptions: this.departmentOptions.length
+ //departmentOptions: this.departmentOptions.length
})
} catch (error) {
console.error('加载选项失败:', error)
@@ -951,11 +1175,11 @@ export default {
},
formatCurrency(amount) {
- if (amount === null || amount === undefined || amount === '') return '0.0000'
+ if (amount === null || amount === undefined || amount === '') return '0.0'
const num = Number(amount)
return isFinite(num)
- ? num.toLocaleString('zh-CN', { minimumFractionDigits: 4, maximumFractionDigits: 4 })
- : '0.0000'
+ ? num.toLocaleString('zh-CN', { minimumFractionDigits: 1, maximumFractionDigits: 1 })
+ : '0.0'
},
formatDate(dateStr) {
@@ -1008,37 +1232,684 @@ export default {
async loadSummaryData() {
this.summaryLoading = true
try {
- const params = {
- year_id: this.searchFields.year_id,
- level: 3, // 只获取第三层小包数据
- cross_table: true // 请求交叉表数据
- }
+ // 并行获取经济分类和预算数据
+ const [packageResponse, economicResponse] = await Promise.all([
+ getBudgetCollectionPackageList({
+ year_id: this.searchFields.year_id,
+ show_tree: true
+ }),
+ getEconomicClassifications({
+ year_id: this.searchFields.year_id
+ }).catch((error) => {
+ console.error('获取经济分类失败:', error)
+ return { code: 0, data: [] }
+ })
+ ])
+
+ console.log('获取到的原始预算数据:', packageResponse)
+ console.log('获取到的经济分类响应:', economicResponse)
+
+ // 处理经济分类数据
+ let economicData = economicResponse || []
- const response = await getBudgetCollectionPackageSummary(params)
+ this.economicClassifications = economicData.map(item => ({
+ id: item.id,
+ code: item.id.toString(), // 使用ID作为code
+ name: item.name,
+ sort_order: item.sort_order
+ })).sort((a, b) => (a.sort_order || 0) - (b.sort_order || 0)) // 按sort_order排序
- if (response && response.data) {
- // 设置经济分类列
- this.economicClassifications = response.data.economicClassifications || []
+ console.log('处理后的经济分类:', this.economicClassifications)
+
+ if (packageResponse) {
+ let rawData = packageResponse.data || packageResponse
+ if (!Array.isArray(rawData)) {
+ rawData = []
+ }
+
+ console.log('处理原始数据:', rawData)
+
+ // 保存原始数据
+ this.rawBudgetData = rawData
- // 设置交叉表数据
- this.crossTableData = response.data.crossTableData || []
+ // 转换为预算表格数据格式
+ this.budgetTableData = this.convertToBudgetTableDataNew(rawData)
- // 计算合计行
- this.calculateCrossTableSummary()
+ // 转换为扁平化表格数据
+ this.budgetTableFlatData = this.convertToFlatTableDataNew(this.budgetTableData)
+
+ // 转换为按部门统计的扁平化数据
+ this.budgetTableFlatDataByDept = this.convertToFlatTableDataByDept(rawData)
+
+ // 转换为按经济分类统计的扁平化数据
+ this.budgetTableFlatDataByEconomic = this.convertToFlatTableDataByEconomic(rawData)
+
+ // 计算统计数据
+ this.calculateSummaryStatsNew(rawData)
+
+ console.log('当前统计模式:', this.summaryMode)
+ console.log('统计数据:', this.summaryStats)
}
} catch (error) {
- console.error('加载交叉汇总数据失败:', error)
- this.$Message.error('加载交叉汇总数据失败')
+ console.error('加载汇总数据失败:', error)
+ this.$Message.error('加载汇总数据失败')
// 设置默认数据结构避免页面报错
+ this.budgetTableData = []
+ this.budgetTableFlatData = []
+ this.budgetTableFlatDataByDept = []
+ this.budgetTableFlatDataByEconomic = []
+ this.summaryStats = {
+ bigPackageCount: 0,
+ mediumPackageCount: 0,
+ totalAllocatedAmount: 0,
+ totalReportedAmount: 0
+ }
this.economicClassifications = []
- this.crossTableData = []
- this.crossTableSummary = null
} finally {
this.summaryLoading = false
}
},
+ // 新的数据转换方法 - 根据新的数据结构
+ convertToBudgetTableDataNew(rawData) {
+ const budgetData = []
+
+ rawData.forEach(bigPackage => {
+ if (bigPackage.level === 1) {
+ const processedBigPackage = {
+ id: bigPackage.id,
+ name: bigPackage.name,
+ allocated_amount: parseFloat(bigPackage.allocated_amount || 0),
+ children: [],
+ totalAllocated: 0,
+ totalReported: 0
+ }
+
+ if (bigPackage.children && bigPackage.children.length > 0) {
+ bigPackage.children.forEach(mediumPackage => {
+ if (mediumPackage.level === 2) {
+ const allocatedAmount = parseFloat(mediumPackage.allocated_amount || 0)
+ const reportedAmount = mediumPackage.submission && mediumPackage.submission.status !== "draft" ? parseFloat(mediumPackage.submission.total_amount || 0) : 0
+
+ // 从submission中提取经济分类数据(确保完全独立)
+ const economicAmounts = {}
+ if (mediumPackage.submission && mediumPackage.submission.economic_items) {
+ console.log(`处理中包 ${mediumPackage.name} 的经济分类数据:`, mediumPackage.submission.economic_items)
+ mediumPackage.submission.economic_items.forEach(item => {
+ const classificationId = String(item.economic_classification_id) // 强制转换为字符串避免引用
+ const amount = parseFloat(item.amount || 0)
+ economicAmounts[classificationId] = amount
+ console.log(`经济分类 ${classificationId} 金额: ${amount}`)
+ })
+ } else {
+ console.log(`中包 ${mediumPackage.name} 没有submission或economic_items数据`)
+ }
+
+ const processedMedium = {
+ id: mediumPackage.id,
+ name: mediumPackage.name,
+ allocated_amount: allocatedAmount,
+ reported_amount: reportedAmount,
+ decomposed_amount: parseFloat(mediumPackage.decomposed_amount || 0),
+ submission_status: mediumPackage.submission ? mediumPackage.submission.status : 'no_submission',
+ economicAmounts: economicAmounts
+ }
+
+ processedBigPackage.children.push(processedMedium)
+ processedBigPackage.totalAllocated += allocatedAmount
+ processedBigPackage.totalReported += reportedAmount
+ }
+ })
+ }
+
+ budgetData.push(processedBigPackage)
+ }
+ })
+
+ return budgetData
+ },
+
+ // 原有的转换方法(保持兼容性)
+ convertToBudgetTableData(rawData) {
+ return this.convertToBudgetTableDataNew(rawData)
+ },
+
+ // 新的扁平化数据转换方法
+ convertToFlatTableDataNew(budgetData) {
+ const flatData = []
+
+ budgetData.forEach((bigPackage, bigIndex) => {
+ if (bigPackage.children && bigPackage.children.length > 0) {
+ bigPackage.children.forEach((mediumPackage, mediumIndex) => {
+ flatData.push({
+ bigPackageIndex: bigIndex,
+ mediumPackageIndex: mediumIndex,
+ bigPackageName: bigPackage.name,
+ mediumPackageName: mediumPackage.name,
+ allocated_amount: mediumPackage.allocated_amount,
+ reported_amount: mediumPackage.reported_amount,
+ decomposed_amount: mediumPackage.decomposed_amount,
+ submission_status: mediumPackage.submission_status,
+ totalAllocated: bigPackage.totalAllocated,
+ totalReported: bigPackage.totalReported,
+ economicAmounts: mediumPackage.economicAmounts,
+ isFirstOfBigPackage: mediumIndex === 0,
+ bigPackageChildrenCount: bigPackage.children.length
+ })
+ })
+ } else {
+ // 如果大包没有子包,也要显示一行
+ flatData.push({
+ bigPackageIndex: bigIndex,
+ mediumPackageIndex: 0,
+ bigPackageName: bigPackage.name,
+ mediumPackageName: '-',
+ allocated_amount: bigPackage.allocated_amount,
+ reported_amount: 0,
+ decomposed_amount: 0,
+ submission_status: 'no_submission',
+ totalAllocated: bigPackage.allocated_amount,
+ totalReported: 0,
+ economicAmounts: {},
+ isFirstOfBigPackage: true,
+ bigPackageChildrenCount: 1
+ })
+ }
+ })
+
+ return flatData
+ },
+
+ // 原有的扁平化方法(保持兼容性)
+ convertToFlatTableData(budgetData) {
+ return this.convertToFlatTableDataNew(budgetData)
+ },
+
+ // 转换为按部门统计的扁平化数据
+ convertToFlatTableDataByDept(rawData) {
+ const deptData = {}
+ const flatData = []
+
+ // 首先按部门分组数据
+ rawData.forEach(bigPackage => {
+ if (bigPackage.level === 1 && bigPackage.children && bigPackage.children.length > 0) {
+ bigPackage.children.forEach(mediumPackage => {
+ if (mediumPackage.level === 2) {
+ const deptId = mediumPackage.department_id
+ const deptName = this.getDepartmentName(deptId)
+
+ if (!deptData[deptId]) {
+ deptData[deptId] = {
+ departmentId: deptId,
+ departmentName: deptName,
+ projects: [],
+ totalAllocated: 0,
+ totalReported: 0
+ }
+ }
+
+ const allocatedAmount = parseFloat(mediumPackage.allocated_amount || 0)
+ const reportedAmount = mediumPackage.submission && mediumPackage.submission.status !== "draft"
+ ? parseFloat(mediumPackage.submission.total_amount || 0) : 0
+
+ // 从submission中提取经济分类数据(确保完全独立)
+ const economicAmounts = {}
+ if (mediumPackage.submission && mediumPackage.submission.economic_items) {
+ mediumPackage.submission.economic_items.forEach(item => {
+ const classificationId = String(item.economic_classification_id) // 强制转换为字符串避免引用
+ economicAmounts[classificationId] = parseFloat(item.amount || 0)
+ })
+ }
+
+ deptData[deptId].projects.push({
+ id: mediumPackage.id,
+ bigPackageName: bigPackage.name,
+ mediumPackageName: mediumPackage.name,
+ projectFullName: `${bigPackage.name} - ${mediumPackage.name}`,
+ allocated_amount: allocatedAmount,
+ reported_amount: reportedAmount,
+ submission_status: mediumPackage.submission ? mediumPackage.submission.status : 'no_submission',
+ economicAmounts: economicAmounts
+ })
+
+ deptData[deptId].totalAllocated += allocatedAmount
+ deptData[deptId].totalReported += reportedAmount
+ }
+ })
+ }
+ })
+
+ // 转换为扁平化数据
+ Object.values(deptData).forEach((dept, deptIndex) => {
+ dept.projects.forEach((project, projectIndex) => {
+ flatData.push({
+ departmentId: dept.departmentId,
+ departmentName: dept.departmentName,
+ projectFullName: project.projectFullName,
+ bigPackageName: project.bigPackageName,
+ mediumPackageName: project.mediumPackageName,
+ allocated_amount: project.allocated_amount,
+ reported_amount: project.reported_amount,
+ submission_status: project.submission_status,
+ deptTotalAllocated: dept.totalAllocated,
+ deptTotalReported: dept.totalReported,
+ economicAmounts: project.economicAmounts,
+ isFirstOfDept: projectIndex === 0,
+ deptProjectCount: dept.projects.length,
+ deptIndex: deptIndex,
+ projectIndex: projectIndex
+ })
+ })
+ })
+
+ return flatData
+ },
+
+ // 获取部门名称
+ getDepartmentName(departmentId) {
+ console.log('departmentOptions:', this.departmentOptions)
+ const dept = this.departmentOptions.find(d => d.id === departmentId)
+ return dept ? dept.name : '未知部门'
+ },
+
+ // 转换为按经济分类统计的扁平化数据
+ convertToFlatTableDataByEconomic(rawData) {
+ const economicData = {}
+ const flatData = []
+
+ // 收集所有项目数据,按经济分类分组
+ rawData.forEach(bigPackage => {
+ if (bigPackage.level === 1 && bigPackage.children && bigPackage.children.length > 0) {
+ bigPackage.children.forEach(mediumPackage => {
+ if (mediumPackage.level === 2) {
+ const deptId = mediumPackage.department_id
+ const deptName = this.getDepartmentName(deptId)
+
+ // 处理每个项目的经济分类数据
+ if (mediumPackage.submission && mediumPackage.submission.economic_items && mediumPackage.submission.economic_items.length > 0) {
+ mediumPackage.submission.economic_items.forEach(economicItem => {
+ const economicId = economicItem.economic_classification_id
+ const amount = parseFloat(economicItem.amount || 0)
+
+ // 确保经济分类存在(防御性编程)
+ if (!economicData[economicId]) {
+ const economicName = this.getEconomicClassName(economicId)
+ economicData[economicId] = {
+ economicId: economicId,
+ economicName: economicName,
+ departments: {},
+ totalAmount: 0,
+ sortOrder: 999
+ }
+ }
+
+ // 初始化部门
+ if (!economicData[economicId].departments[deptId]) {
+ economicData[economicId].departments[deptId] = {
+ departmentId: deptId,
+ departmentName: deptName,
+ projects: [],
+ deptTotal: 0
+ }
+ }
+
+ // 添加项目数据
+ economicData[economicId].departments[deptId].projects.push({
+ id: mediumPackage.id,
+ bigPackageName: bigPackage.name || '未知大包',
+ mediumPackageName: mediumPackage.name || '未知项目',
+ projectFullName: `${bigPackage.name}` + '-' + (mediumPackage.name || '未知项目'), // 只显示中包名称
+ economicAmount: amount,
+ submission_status: mediumPackage.submission ? mediumPackage.submission.status : 'draft'
+ })
+
+ // 累计金额
+ economicData[economicId].departments[deptId].deptTotal += amount
+ economicData[economicId].totalAmount += amount
+ })
+ console.log('--------------------------------经济分类数据:', economicData)
+ }
+ }
+ })
+ }
+ })
+
+ // 转换为扁平化表格数据(按HTML表格的合并逻辑)
+ // 按照经济分类的sort_order排序
+ const sortedEconomicGroups = Object.values(economicData).sort((a, b) => a.sortOrder - b.sortOrder)
+
+ sortedEconomicGroups.forEach((economicGroup) => {
+ const departments = Object.values(economicGroup.departments)
+
+ // 如果该经济分类没有任何项目数据,添加一个空行显示
+ if (departments.length === 0) {
+ flatData.push({
+ economicClassName: economicGroup.economicName,
+ departmentName: '-',
+ projectName: '-',
+ projectAmount: 0,
+ deptTotal: 0,
+ economicTotal: 0,
+
+ // 用于表格合并的标记
+ isFirstEconomic: true,
+ isFirstDept: true,
+ economicRowSpan: 1,
+ deptRowSpan: 1,
+
+ // 其他信息
+ economicId: economicGroup.economicId,
+ departmentId: null,
+ submission_status: 'no_data'
+ })
+ return
+ }
+
+ // 计算该经济分类需要合并的总行数(所有部门的所有项目)
+ let economicTotalRows = 0
+ departments.forEach(dept => {
+ economicTotalRows += dept.projects.length
+ })
+
+ // 按HTML示例逻辑:经济分类合并所有行,部门合并自己的行
+ let economicRowIndex = 0 // 用于跟踪当前是该经济分类的第几行
+
+ departments.forEach((dept) => {
+ const deptTotalRows = dept.projects.length
+
+ dept.projects.forEach((project, projectIndex) => {
+ const isFirstEconomicRow = economicRowIndex === 0 // 只有第一行显示经济分类
+ const isFirstDeptRow = projectIndex === 0 // 只有每个部门的第一行显示部门名
+
+ flatData.push({
+ economicClassName: economicGroup.economicName,
+ departmentName: dept.departmentName,
+ projectName: project.projectFullName, // 项目名称(中包)
+ projectAmount: project.economicAmount, // 金额
+ deptTotal: dept.deptTotal, // 按科室汇总
+ economicTotal: economicGroup.totalAmount, // 按经济分类汇总
+
+ // 用于表格合并的标记(参照HTML示例)
+ isFirstEconomic: isFirstEconomicRow,
+ isFirstDept: isFirstDeptRow,
+ economicRowSpan: isFirstEconomicRow ? economicTotalRows : 0, // 经济分类合并所有行
+ deptRowSpan: isFirstDeptRow ? deptTotalRows : 0, // 部门合并该部门的所有项目行
+
+ // 其他信息
+ economicId: economicGroup.economicId,
+ departmentId: dept.departmentId,
+ submission_status: project.submission_status
+ })
+
+ economicRowIndex++ // 递增该经济分类下的总行索引
+ })
+ })
+ })
+
+ console.log('按HTML格式重构的经济分类统计数据:', flatData)
+ return flatData
+ },
+
+ // 获取经济分类名称
+ getEconomicClassName(economicId) {
+ const economic = this.economicClassifications.find(e => e.id === economicId)
+ return economic ? economic.name : '未知分类'
+ },
+
+ // 获取经济分类下的项目总数
+ getEconomicProjectCount(economicGroup) {
+ let count = 0
+ Object.values(economicGroup.departments).forEach(dept => {
+ count += dept.projects.length
+ })
+ return count
+ },
+
+ // 表格合并方法
+ mergeMethod({ row, column, rowIndex, columnIndex }) {
+ // 大包列合并
+ if (columnIndex === 0) {
+ if (row.isFirstOfBigPackage) {
+ return {
+ rowspan: row.bigPackageChildrenCount,
+ colspan: 1
+ }
+ } else {
+ return {
+ rowspan: 0,
+ colspan: 0
+ }
+ }
+ }
+
+ // 按大包累计分配金额列合并
+ if (columnIndex === 4) {
+ if (row.isFirstOfBigPackage) {
+ return {
+ rowspan: row.bigPackageChildrenCount,
+ colspan: 1
+ }
+ } else {
+ return {
+ rowspan: 0,
+ colspan: 0
+ }
+ }
+ }
+
+ // 按大包累计上报金额列合并
+ if (columnIndex === 5) {
+ if (row.isFirstOfBigPackage) {
+ return {
+ rowspan: row.bigPackageChildrenCount,
+ colspan: 1
+ }
+ } else {
+ return {
+ rowspan: 0,
+ colspan: 0
+ }
+ }
+ }
+ },
+
+ // 按部门统计的表格合并方法
+ mergeMethodByDept({ row, column, rowIndex, columnIndex }) {
+ // 部门列合并
+ if (columnIndex === 0) {
+ if (row.isFirstOfDept) {
+ return {
+ rowspan: row.deptProjectCount,
+ colspan: 1
+ }
+ } else {
+ return {
+ rowspan: 0,
+ colspan: 0
+ }
+ }
+ }
+
+ // 按部门累计分配金额列合并
+ if (columnIndex === 4) {
+ if (row.isFirstOfDept) {
+ return {
+ rowspan: row.deptProjectCount,
+ colspan: 1
+ }
+ } else {
+ return {
+ rowspan: 0,
+ colspan: 0
+ }
+ }
+ }
+
+ // 按部门累计上报金额列合并
+ if (columnIndex === 5) {
+ if (row.isFirstOfDept) {
+ return {
+ rowspan: row.deptProjectCount,
+ colspan: 1
+ }
+ } else {
+ return {
+ rowspan: 0,
+ colspan: 0
+ }
+ }
+ }
+ },
+
+ // 按经济分类统计的表格合并方法
+ mergeMethodByEconomic({ row, column, rowIndex, columnIndex }) {
+ // 第0列:经济分类列 - 按HTML示例:物业费合并整个经济分类的所有行
+ if (columnIndex === 0) {
+ if (row.isFirstEconomic && row.economicRowSpan > 0) {
+ return {
+ rowspan: row.economicRowSpan,
+ colspan: 1
+ }
+ } else if (!row.isFirstEconomic) {
+ return {
+ rowspan: 0,
+ colspan: 0
+ }
+ }
+ }
+
+ // 第1列:科室列 - 按HTML示例:综合科合并22行,财审科合并22行
+ if (columnIndex === 1) {
+ if (row.isFirstDept && row.deptRowSpan > 0) {
+ return {
+ rowspan: row.deptRowSpan,
+ colspan: 1
+ }
+ } else if (!row.isFirstDept) {
+ return {
+ rowspan: 0,
+ colspan: 0
+ }
+ }
+ }
+
+ // 第2列:项目(中包)列 - 不合并,每行显示一个项目
+ // 第3列:金额列 - 不合并,每行显示一个金额
+
+ // 第4列:按科室汇总列 - 按HTML示例:综合科显示3,财审科显示7
+ if (columnIndex === 4) {
+ if (row.isFirstDept && row.deptRowSpan > 0) {
+ return {
+ rowspan: row.deptRowSpan,
+ colspan: 1
+ }
+ } else if (!row.isFirstDept) {
+ return {
+ rowspan: 0,
+ colspan: 0
+ }
+ }
+ }
+
+ // 第5列:按经济分类汇总列 - 按HTML示例:物业费显示10
+ if (columnIndex === 5) {
+ if (row.isFirstEconomic && row.economicRowSpan > 0) {
+ return {
+ rowspan: row.economicRowSpan,
+ colspan: 1
+ }
+ } else if (!row.isFirstEconomic) {
+ return {
+ rowspan: 0,
+ colspan: 0
+ }
+ }
+ }
+
+ // 默认不合并
+ return {
+ rowspan: 1,
+ colspan: 1
+ }
+ },
+
+ // 新的统计数据计算方法
+ calculateSummaryStatsNew(rawData) {
+ let bigPackageCount = 0
+ let mediumPackageCount = 0
+ let totalAllocatedAmount = 0
+ let totalReportedAmount = 0
+
+ rawData.forEach(bigPackage => {
+ if (bigPackage.level === 1) {
+ bigPackageCount++
+
+
+ if (bigPackage.children && bigPackage.children.length > 0) {
+ bigPackage.children.forEach(mediumPackage => {
+ if (mediumPackage.level === 2) {
+ totalAllocatedAmount += parseFloat(mediumPackage.allocated_amount || 0)
+ mediumPackageCount++
+ // 从submission获取真实上报金额
+ console.log('mediumPackage:', mediumPackage)
+ if (mediumPackage.submission && mediumPackage.submission.status !== "draft") {
+ totalReportedAmount += parseFloat(mediumPackage.submission.total_amount || 0)
+ }
+ }
+ })
+ }
+ }
+ })
+
+ this.summaryStats = {
+ bigPackageCount,
+ mediumPackageCount,
+ totalAllocatedAmount,
+ totalReportedAmount
+ }
+ },
+
+ // 原有的统计方法(保持兼容性)
+ calculateSummaryStats(rawData) {
+ return this.calculateSummaryStatsNew(rawData)
+ },
+
+ // 重新处理当前模式的数据
+ reprocessCurrentModeData() {
+ if (!this.rawBudgetData || this.rawBudgetData.length === 0) {
+ console.log('原始数据不可用')
+ return
+ }
+
+ console.log('重新处理', this.summaryMode, '模式数据')
+
+ try {
+ if (this.summaryMode === 'package') {
+ this.budgetTableData = this.convertToBudgetTableDataNew(this.rawBudgetData)
+ this.budgetTableFlatData = this.convertToFlatTableDataNew(this.budgetTableData)
+ console.log('重新处理按大包统计完成:', this.budgetTableFlatData?.length)
+ } else if (this.summaryMode === 'department') {
+ this.budgetTableFlatDataByDept = this.convertToFlatTableDataByDept(this.rawBudgetData)
+ console.log('重新处理按部门统计完成:', this.budgetTableFlatDataByDept?.length)
+ } else if (this.summaryMode === 'economic') {
+ this.budgetTableFlatDataByEconomic = this.convertToFlatTableDataByEconomic(this.rawBudgetData)
+ console.log('重新处理按经济分类统计完成:', this.budgetTableFlatDataByEconomic?.length)
+ }
+ } catch (error) {
+ console.error('重新处理数据出错:', error)
+ }
+ },
+
+ // 格式化金额显示
+ formatAmount(amount) {
+ if (amount === null || amount === undefined || amount === '') return '0'
+ const num = Number(amount)
+ return isFinite(num)
+ ? num.toLocaleString('zh-CN', { maximumFractionDigits: 1 })
+ : '0.0'
+ },
+
// 计算交叉表合计
calculateCrossTableSummary() {
if (!this.crossTableData.length || !this.economicClassifications.length) {
@@ -1106,51 +1977,113 @@ export default {
return typeMap[level] || 'info'
},
- // 导出交叉表
- exportCrossTable() {
- if (!this.crossTableData.length) {
- this.$Message.warning('暂无数据可导出')
- return
- }
-
+ // 导出预算分配表
+ exportBudgetTable() {
try {
- // 构建CSV数据
- const headers = ['小包名称']
- this.economicClassifications.forEach(cls => {
- headers.push(cls.name)
- })
- headers.push('合计')
+ let headers = []
+ let data = []
+ let fileName = ''
- let csvContent = headers.join(',') + '\n'
-
- // 添加数据行
- this.crossTableData.forEach(row => {
- const rowData = [row.packageName]
+ if (this.summaryMode === 'package') {
+ // 按大包统计模式
+ if (!this.budgetTableFlatData.length) {
+ this.$Message.warning('暂无数据可导出')
+ return
+ }
+
+ headers = ['大包', '项目(中包)', '分配金额', '上报金额', '按大包累计分配金额', '按大包累计上报金额']
this.economicClassifications.forEach(cls => {
- const value = row['amount_' + cls.code] || 0
- rowData.push(value > 0 ? value : '')
+ headers.push(cls.name)
})
- rowData.push(row.totalAmount || 0)
- csvContent += rowData.join(',') + '\n'
- })
-
- // 添加合计行
- if (this.crossTableSummary) {
- const summaryRow = [`合计(${this.crossTableData.length}个小包)`]
+
+ data = this.budgetTableFlatData.map(row => {
+ const rowData = [
+ row.isFirstOfBigPackage ? row.bigPackageName : '',
+ row.mediumPackageName === '-' ? '(无子包)' : row.mediumPackageName,
+ this.formatAmount(row.allocated_amount),
+ row.reported_amount > 0 ? this.formatAmount(row.reported_amount) : (row.submission_status === 'no_submission' ? '未申报' : '0'),
+ row.isFirstOfBigPackage ? this.formatAmount(row.totalAllocated) : '',
+ row.isFirstOfBigPackage ? this.formatAmount(row.totalReported) : ''
+ ]
+
+ // 添加经济分类金额
+ this.economicClassifications.forEach(cls => {
+ const amount = row.economicAmounts && row.economicAmounts[cls.code]
+ rowData.push(amount && amount > 0 ? this.formatAmount(amount) : '-')
+ })
+
+ return rowData
+ })
+
+ fileName = '预算分配汇总表_按大包统计'
+
+ } else if (this.summaryMode === 'department') {
+ // 按部门统计模式
+ if (!this.budgetTableFlatDataByDept.length) {
+ this.$Message.warning('暂无数据可导出')
+ return
+ }
+
+ headers = ['部门', '项目', '分配金额', '上报金额', '按部门累计分配金额', '按部门累计上报金额']
this.economicClassifications.forEach(cls => {
- summaryRow.push(this.crossTableSummary['amount_' + cls.code] || 0)
+ headers.push(cls.name)
})
- summaryRow.push(this.crossTableSummary.totalAmount || 0)
- csvContent += summaryRow.join(',') + '\n'
+
+ data = this.budgetTableFlatDataByDept.map(row => {
+ const rowData = [
+ row.isFirstOfDept ? (row.departmentName || '') : '',
+ row.projectFullName || '',
+ this.formatAmount(row.allocated_amount),
+ row.reported_amount > 0 ? this.formatAmount(row.reported_amount) : (row.submission_status === 'no_submission' ? '未申报' : '0'),
+ row.isFirstOfDept ? this.formatAmount(row.deptTotalAllocated) : '',
+ row.isFirstOfDept ? this.formatAmount(row.deptTotalReported) : ''
+ ]
+
+ // 添加经济分类金额
+ this.economicClassifications.forEach(cls => {
+ const amount = row.economicAmounts && row.economicAmounts[cls.code]
+ rowData.push(amount && amount > 0 ? this.formatAmount(amount) : '-')
+ })
+
+ return rowData
+ })
+
+ fileName = '预算分配汇总表_按部门统计'
+
+ } else if (this.summaryMode === 'economic') {
+ // 按经济分类统计模式
+ if (!this.budgetTableFlatDataByEconomic.length) {
+ this.$Message.warning('暂无数据可导出')
+ return
+ }
+
+ headers = ['经济分类', '科室', '项目 (中包)', '金额', '按科室汇总', '按经济分类汇总']
+
+ data = this.budgetTableFlatDataByEconomic.map(row => [
+ row.isFirstEconomic ? (row.economicClassName || '') : '',
+ row.isFirstDept ? (row.departmentName || '') : '',
+ row.projectName || '',
+ this.formatAmount(row.projectAmount),
+ row.isFirstDept ? this.formatAmount(row.deptTotal) : '',
+ row.isFirstEconomic ? this.formatAmount(row.economicTotal) : ''
+ ])
+
+ fileName = '预算分配汇总表_按经济分类统计'
}
+ // 构建CSV内容
+ let csvContent = headers.join(',') + '\n'
+ data.forEach(row => {
+ csvContent += row.join(',') + '\n'
+ })
+
// 下载文件
const blob = new Blob(['\ufeff' + csvContent], { type: 'text/csv;charset=utf-8;' })
const link = document.createElement('a')
if (link.download !== undefined) {
const url = URL.createObjectURL(blob)
link.setAttribute('href', url)
- link.setAttribute('download', `小包与经济分类交叉表_${new Date().toISOString().slice(0, 10)}.csv`)
+ link.setAttribute('download', `${fileName}_${new Date().toISOString().slice(0, 10)}.csv`)
link.style.visibility = 'hidden'
document.body.appendChild(link)
link.click()
@@ -1305,7 +2238,202 @@ export default {
/* 汇总视图样式 */
.summary-container {
- min-height: 400px;
+ padding: 20px 0;
+}
+
+.stats-row {
+ margin-bottom: 30px;
+}
+
+/* 模式切换样式 */
+.mode-switch-wrapper {
+ text-align: center;
+ margin-bottom: 20px;
+}
+
+.mode-switch-wrapper >>> .el-radio-group {
+ background: #f8f9fa;
+ border-radius: 8px;
+ padding: 4px;
+ display: inline-block;
+}
+
+.mode-switch-wrapper >>> .el-radio-button__inner {
+ border: none;
+ background: transparent;
+ color: #606266;
+ font-weight: 500;
+ transition: all 0.3s ease;
+ padding: 12px 24px;
+ border-radius: 6px;
+}
+
+.mode-switch-wrapper >>> .el-radio-button__orig-radio:checked + .el-radio-button__inner {
+ background: #409EFF;
+ color: white;
+ box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
+}
+
+.mode-switch-wrapper >>> .el-radio-button:hover .el-radio-button__inner {
+ color: #409EFF;
+}
+
+/* 统计卡片样式 */
+.stat-card {
+ height: 120px;
+ border: none;
+ border-radius: 12px;
+ box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.08);
+ transition: all 0.3s ease;
+ overflow: hidden;
+ position: relative;
+}
+
+.stat-card:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 8px 30px 0 rgba(0, 0, 0, 0.12);
+}
+
+.stat-card-primary {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+}
+
+.stat-card-success {
+ background: linear-gradient(135deg, #42d392 0%, #647eff 100%);
+}
+
+.stat-card-warning {
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
+}
+
+.stat-card-info {
+ background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
+}
+
+.stat-card >>> .el-card__body {
+ padding: 0;
+ height: 100%;
+ background: transparent;
+}
+
+.stat-content {
+ display: flex;
+ align-items: center;
+ height: 100%;
+ padding: 20px;
+ color: white;
+}
+
+.stat-icon {
+ width: 60px;
+ height: 60px;
+ border-radius: 50%;
+ background: rgba(255, 255, 255, 0.2);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 16px;
+ flex-shrink: 0;
+}
+
+.stat-icon i {
+ font-size: 28px;
+ color: white;
+}
+
+.stat-info {
+ flex: 1;
+}
+
+.stat-info h3 {
+ margin: 0 0 6px 0;
+ font-size: 28px;
+ font-weight: 700;
+ color: white;
+ line-height: 1.2;
+}
+
+.stat-info p {
+ margin: 0;
+ font-size: 14px;
+ color: rgba(255, 255, 255, 0.9);
+ font-weight: 500;
+}
+
+/* 表格样式优化 */
+.table-wrapper {
+ margin-top: 20px;
+}
+
+.table-wrapper >>> .el-table {
+ font-size: 14px;
+ color: #303133;
+}
+
+.table-wrapper >>> .el-table th {
+ background-color: #f8f9fa;
+ color: #606266;
+ font-weight: 600;
+ font-size: 14px;
+ padding: 16px 0;
+ white-space: nowrap;
+ border-bottom: 2px solid #e4e7ed;
+}
+
+.table-wrapper >>> .el-table td {
+ padding: 14px 0;
+ font-size: 14px;
+ border-bottom: 1px solid #f0f0f0;
+}
+
+.table-wrapper >>> .el-table--border td,
+.table-wrapper >>> .el-table--border th {
+ border-right: 1px solid #ebeef5;
+}
+
+.table-wrapper >>> .el-table--striped .el-table__body tr.el-table__row--striped td {
+ background: #fafafa;
+}
+
+.table-wrapper >>> .el-table__body tr:hover > td {
+ background-color: #f5f7fa !important;
+}
+
+/* 单元格内容样式 */
+.package-name-text {
+ color: #303133;
+ font-weight: 600;
+ font-size: 14px;
+}
+
+.project-name-text {
+ color: #303133;
+ font-weight: 500;
+ font-size: 14px;
+}
+
+.amount-text {
+ color: #409EFF;
+ font-weight: 600;
+ font-size: 14px;
+}
+
+.total-amount-text {
+ color: #E6A23C;
+ font-weight: 700;
+ font-size: 14px;
+}
+
+.economic-amount-text {
+ color: #67C23A;
+ font-weight: 600;
+ font-size: 14px;
+}
+
+.no-submission-text {
+ color: #909399;
+ font-weight: 500;
+ font-size: 13px;
}
.cross-table-wrapper {
@@ -1381,6 +2509,21 @@ export default {
text-overflow: ellipsis !important;
}
+/* 经济分类统计表格的单元格不换行 */
+.cross-table >>> .table-cell-package .cell,
+.cross-table >>> .table-cell-project .cell,
+.cross-table >>> .table-cell-amount .cell,
+.cross-table >>> .table-cell-total .cell {
+ white-space: nowrap !important;
+ overflow: hidden !important;
+ text-overflow: ellipsis !important;
+}
+
+/* 项目名称列允许更宽显示,防止省略 */
+.cross-table >>> .table-cell-project .cell {
+ max-width: none !important;
+}
+
/* 主表格预算包名称列样式 */
.main-package-name-wrapper {
white-space: nowrap !important;