diff --git a/src/api/budget/budgetCollectionPackage.js b/src/api/budget/budgetCollectionPackage.js index 6ff7c12..1bd69af 100644 --- a/src/api/budget/budgetCollectionPackage.js +++ b/src/api/budget/budgetCollectionPackage.js @@ -147,4 +147,13 @@ export function getPackageStatusOptions() { { value: 'closed', label: '关闭' }, { value: 'suspended', label: '暂停' } ]) +} + +// 获取经济分类列表 +export function getEconomicClassifications(params) { + return request({ + url: '/api/admin/economic-categories', + method: 'get', + params + }) } \ No newline at end of file diff --git a/src/views/budget/collection/components/BudgetSubmissionForm.vue b/src/views/budget/collection/components/BudgetSubmissionForm.vue index ca5d39c..00fa3f6 100644 --- a/src/views/budget/collection/components/BudgetSubmissionForm.vue +++ b/src/views/budget/collection/components/BudgetSubmissionForm.vue @@ -1,5 +1,14 @@ @@ -144,7 +145,7 @@ 0) { this.departmentAllocations = this.packageData.children.map(child => ({ + packageId: child.id, departmentId: child.department.id, departmentName: child.department.name, allocatedAmount: child.allocated_amount, @@ -508,6 +510,7 @@ export default { const allocation = { department_id: item.departmentId, + package_id: item.packageId, allocated_amount: parseFloat(item.allocatedAmount), package_name: item.childPackageName, description: item.description || '' diff --git a/src/views/budget/collection/components/SummarySubmitForm.vue b/src/views/budget/collection/components/SummarySubmitForm.vue index 55df591..e6e0f99 100644 --- a/src/views/budget/collection/components/SummarySubmitForm.vue +++ b/src/views/budget/collection/components/SummarySubmitForm.vue @@ -84,6 +84,19 @@ + +
+ + 克隆子包填报数据 + + 一键将子包数据填充到汇总表单中 +
+
@@ -441,6 +454,125 @@ 请填写申请资金相关信息,系统将根据您的填写内容进行记录
+ + +
+
+ + 子包申请资金数据 + ({{ subPackagesData.length }}个子包) +
+ +
+
+
+

{{ subPackage.departmentName }}

+
+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
@@ -748,6 +880,161 @@ 提示:年度金额必须等于项目内容分解中所有选中经济分类项目的金额合计 + + +
+
+ + 子包项目实施进度安排 + ({{ subPackagesData.length }}个子包) +
+ +
+
+
+

{{ subPackage.departmentName }}

+
+ +
+ +
+
{{ subPackage.implementationSchedule }}
+
+ + +
+ + + + + + + + + + + + + +
+ + +
+
+ + {{ formData.budgetYear || '当年' }}分月实施进度明细 +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + 该子包暂无项目实施进度安排数据 +
+
+
+
+
@@ -927,6 +1214,81 @@ + + +
+
+ + 子包项目预计取得绩效 + ({{ subPackagesData.length }}个子包) +
+ +
+
+
+

{{ subPackage.departmentName }}

+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + 该子包暂无项目预计取得绩效数据 +
+
+
+
+
@@ -951,6 +1313,37 @@
备注:需同时提供申请依据和相关支撑材料。
+ + +
+
+ + 子包其他需要说明事项 + ({{ subPackagesData.length }}个子包) +
+ +
+
+
+

{{ subPackage.departmentName }}

+
+ +
+
{{ subPackage.otherNotes }}
+ + +
+ + 该子包暂无其他需要说明事项 +
+
+
+
+
@@ -1010,6 +1403,62 @@ + + +
+
+ + 子包附件上传 + ({{ subPackagesData.length }}个子包) +
+ +
+
+
+

{{ subPackage.departmentName }}

+
+ +
+
+
+ + + {{ file.name }} + + {{ formatFileSize(file.size) }} + + 下载 + +
+
+ + +
+ + 该子包暂无附件 +
+
+
+
+
@@ -1130,7 +1579,8 @@ export default { uploadHeaders: { 'Authorization': 'Bearer ' + (this.$store.state.user.token || localStorage.getItem('token') || '') }, - subPackagesData: [] // 存储子包的经济分类提交数据 + subPackagesData: [], // 存储子包的经济分类提交数据 + cloneLoading: false // 克隆数据加载状态 } }, computed: { @@ -1138,6 +1588,13 @@ export default { return this.mode === 'view' }, + // 显示克隆按钮的条件:不是查看模式 且 只有一个子包 + showCloneButton() { + return !this.isViewMode && + this.packageData.children && + this.packageData.children.length === 1 + }, + allocatedAmountNum() { return parseFloat(this.formData.allocatedAmount) || 0 }, @@ -1290,10 +1747,10 @@ export default { // 加载父级package的submission信息 this.parentSubmissionInfo = { - application_basis: (this.packageData.parent && this.packageData.parent.application_basis) || '', - application_purpose: (this.packageData.parent && this.packageData.parent.application_purpose) || '', - application_reason: (this.packageData.parent && this.packageData.parent.application_reason) || '', - project_content: (this.packageData.parent && this.packageData.parent.project_content) || '' + application_basis: this.packageData.submission ? this.packageData.submission.application_basis : '', + application_purpose: this.packageData.submission ? this.packageData.submission.application_purpose : '', + application_reason: this.packageData.submission ? this.packageData.submission.application_reason : '', + project_content: this.packageData.submission ? this.packageData.submission.project_content : '' } // 调试信息 @@ -2187,12 +2644,55 @@ export default { try { // 获取每个子包的分解表数据 const response = await getPackageDecomposition(subPackage.id) - if (response && !response.errcode && response.economic_items) { - return { - package_id: subPackage.id, - department_name: subPackage.department ? subPackage.department.name : '未知科室', - economic_items: response.economic_items || [] + if (response && !response.errcode) { + const subPackageData = { + packageId: subPackage.id, + departmentName: subPackage.department ? subPackage.department.name : '未知科室', + economic_items: response.economic_items || [], + fundingData: null } + + // 提取资金相关数据(如果存在) + if (response.new_project_total_amount !== undefined || + response.old_project_contract_total !== undefined) { + subPackageData.fundingData = { + new_project_total_amount: response.new_project_total_amount, + new_project_year_current: response.new_project_year_current, + new_project_year_next: response.new_project_year_next, + new_project_has_fixed_assets: response.new_project_has_fixed_assets, + new_project_no_fixed_assets: response.new_project_no_fixed_assets, + old_project_contract_total: response.old_project_contract_total, + old_project_previous_years_total: response.old_project_previous_years_total, + old_project_previous_year_balance: response.old_project_previous_year_balance, + old_project_contract_number: response.old_project_contract_number, + old_project_year_current: response.old_project_year_current, + old_project_has_fixed_assets: response.old_project_has_fixed_assets, + old_project_no_fixed_assets: response.old_project_no_fixed_assets + } + } + + // 提取项目实施进度安排数据 + if (response.implementation_schedule || response.implementation_schedule_detail) { + subPackageData.implementationSchedule = response.implementation_schedule + subPackageData.implementationScheduleDetail = response.implementation_schedule_detail + } + + // 提取绩效指标数据 + if (response.performance_indicators && response.performance_indicators.length > 0) { + subPackageData.performanceIndicators = response.performance_indicators + } + + // 提取其他说明事项 + if (response.other_notes) { + subPackageData.otherNotes = response.other_notes + } + + // 提取附件数据 + if (response.attachments && response.attachments.length > 0) { + subPackageData.attachments = response.attachments + } + + return subPackageData } } catch (error) { console.error(`加载子包${subPackage.id}数据失败:`, error) @@ -2213,6 +2713,276 @@ export default { this.subPackagesData = [] } }, + + // 克隆子包数据到汇总表单 + async cloneSubPackageData() { + if (!this.packageData.children || this.packageData.children.length !== 1) { + this.$message.warning('只有在有且仅有一个子包时才能使用克隆功能') + return + } + + try { + this.cloneLoading = true + const subPackageId = this.packageData.children[0].id + + // 获取子包的完整数据 + const response = await getPackageDecomposition(subPackageId) + if (!response || response.errcode) { + throw new Error('获取子包数据失败') + } + + // 确认对话框 + const confirmResult = await this.$confirm( + '是否确认使用子包数据覆盖当前汇总表单?此操作将会覆盖已填写的数据。', + '确认克隆', + { + confirmButtonText: '确认克隆', + cancelButtonText: '取消', + type: 'warning' + } + ) + + if (confirmResult !== 'confirm') { + return + } + + // 克隆项目实施进度安排 + if (response.implementation_schedule) { + this.formData.implementationSchedule = response.implementation_schedule + } + + // 克隆项目实施进度安排详细表格 + if (response.implementation_schedule_detail) { + this.formData.implementationScheduleDetail = { + currentYearContent: response.implementation_current_year_content || '', + currentYearAmount: parseFloat(response.implementation_current_year_amount) || 0, + currentYearMonth1: parseFloat(response.implementation_current_year_month_1) || 0, + currentYearMonth2: parseFloat(response.implementation_current_year_month_2) || 0, + currentYearMonth3: parseFloat(response.implementation_current_year_month_3) || 0, + currentYearMonth4: parseFloat(response.implementation_current_year_month_4) || 0, + currentYearMonth5: parseFloat(response.implementation_current_year_month_5) || 0, + currentYearMonth6: parseFloat(response.implementation_current_year_month_6) || 0, + currentYearMonth7: parseFloat(response.implementation_current_year_month_7) || 0, + currentYearMonth8: parseFloat(response.implementation_current_year_month_8) || 0, + currentYearMonth9: parseFloat(response.implementation_current_year_month_9) || 0, + currentYearMonth10: parseFloat(response.implementation_current_year_month_10) || 0, + currentYearMonth11: parseFloat(response.implementation_current_year_month_11) || 0, + currentYearMonth12: parseFloat(response.implementation_current_year_month_12) || 0, + nextYearContent: response.implementation_next_year_content || '', + thirdYearContent: response.implementation_third_year_content || '' + } + } + + // 克隆项目预计取得绩效 + if (response.performance_indicators && response.performance_indicators.length > 0) { + this.formData.performanceIndicators = this.processPerformanceIndicatorsData(response.performance_indicators) + } + + // 克隆其他需要说明事项 + if (response.other_notes) { + this.formData.otherNotes = response.other_notes + } + + // 克隆申请资金表格数据 + if (response.new_project_total_amount !== undefined) { + this.fundApplicationData[0].newProject.totalAmount = response.new_project_total_amount || 0 + this.fundApplicationData[0].newProject.current = response.new_project_year_current || 0 + this.fundApplicationData[0].newProject.next = response.new_project_year_next || 0 + this.fundApplicationData[0].newProject.hasFixedAssets = response.new_project_has_fixed_assets || false + this.fundApplicationData[0].newProject.noFixedAssets = !response.new_project_has_fixed_assets + } + + if (response.old_project_contract_total !== undefined) { + this.fundApplicationData[0].oldProject.contractTotal = response.old_project_contract_total || 0 + this.fundApplicationData[0].oldProject.previousYearsTotal = response.old_project_previous_years_total || 0 + this.fundApplicationData[0].oldProject.previousYearBalance = response.old_project_previous_year_balance || 0 + this.fundApplicationData[0].oldProject.contractNumber = response.old_project_contract_number || '' + this.fundApplicationData[0].oldProject.current = response.old_project_year_current || 0 + this.fundApplicationData[0].oldProject.hasFixedAssets = response.old_project_has_fixed_assets || false + this.fundApplicationData[0].oldProject.noFixedAssets = !response.old_project_has_fixed_assets + } + + // 克隆项目内容分解(经济分类数据) + if (response.economic_items && response.economic_items.length > 0) { + console.log('子包经济分类数据:', response.economic_items) + console.log('当前汇总经济分类数据:', this.economicCategoriesWithData) + + // 清空现有的选择和数据 + this.economicCategoriesWithData.forEach(item => { + item.selected = false + item.amount = '' + item.calculationBasis = '' + item.fileList = [] + }) + + // 根据子包数据设置选择和填充数据 + response.economic_items.forEach(economicItem => { + console.log('处理经济分类项:', economicItem) + // 兼容不同的字段名格式 + const categoryId = economicItem.categoryId || economicItem.category_id + const amount = economicItem.amount + const calculationBasis = economicItem.calculationBasis || economicItem.calculation_basis + + const targetItem = this.economicCategoriesWithData.find(item => + item.categoryId === categoryId + ) + console.log('找到目标项:', targetItem) + if (targetItem && amount) { + targetItem.selected = true + targetItem.amount = amount || '' + targetItem.calculationBasis = calculationBasis || '' + console.log('更新后的目标项:', targetItem) + // 注意:附件不克隆,需要用户重新上传 + } + }) + + // 更新全选状态 + this.updateSelectAllState() + console.log('克隆后的经济分类数据:', this.economicCategoriesWithData) + } + + // 克隆附件数据(一般附件) + if (response.general_files && response.general_files.length > 0) { + this.formData.generalFiles = response.general_files + } + + this.$message.success('子包数据克隆成功!') + + } catch (error) { + console.error('克隆子包数据失败:', error) + if (error.message !== '用户取消操作') { + this.$message.error('克隆子包数据失败:' + (error.message || '未知错误')) + } + } finally { + this.cloneLoading = false + } + }, + + // 检查子包是否有申请资金数据 + hasSubPackageFundingData(subPackage) { + return subPackage.fundingData && ( + this.hasSubPackageNewProjectData(subPackage.fundingData) || + this.hasSubPackageOldProjectData(subPackage.fundingData) + ) + }, + + // 检查子包新项目是否有数据 + hasSubPackageNewProjectData(fundingData) { + if (!fundingData) return false + return (fundingData.new_project_total_amount && fundingData.new_project_total_amount > 0) || + (fundingData.new_project_year_current && fundingData.new_project_year_current > 0) || + (fundingData.new_project_year_next && fundingData.new_project_year_next > 0) || + fundingData.new_project_has_fixed_assets || + fundingData.new_project_no_fixed_assets + }, + + // 检查子包老项目是否有数据 + hasSubPackageOldProjectData(fundingData) { + if (!fundingData) return false + return (fundingData.old_project_contract_total && fundingData.old_project_contract_total > 0) || + (fundingData.old_project_previous_years_total && fundingData.old_project_previous_years_total > 0) || + (fundingData.old_project_previous_year_balance && fundingData.old_project_previous_year_balance > 0) || + (fundingData.old_project_year_current && fundingData.old_project_year_current > 0) || + fundingData.old_project_contract_number || + fundingData.old_project_has_fixed_assets || + fundingData.old_project_no_fixed_assets + }, + + // 获取子包申请资金表格数据(转换为表格需要的格式) + getSubPackageFundingTableData(subPackage) { + if (!subPackage.fundingData) { + return { + newProject: { + totalAmount: 0, + current: 0, + next: 0, + hasFixedAssets: false, + noFixedAssets: false + }, + oldProject: { + contractTotal: 0, + previousYearsTotal: 0, + previousYearBalance: 0, + contractNumber: '', + current: 0, + hasFixedAssets: false, + noFixedAssets: false + } + } + } + + const data = subPackage.fundingData + return { + newProject: { + totalAmount: data.new_project_total_amount || 0, + current: data.new_project_year_current || 0, + next: data.new_project_year_next || 0, + hasFixedAssets: data.new_project_has_fixed_assets || false, + noFixedAssets: data.new_project_no_fixed_assets || false + }, + oldProject: { + contractTotal: data.old_project_contract_total || 0, + previousYearsTotal: data.old_project_previous_years_total || 0, + previousYearBalance: data.old_project_previous_year_balance || 0, + contractNumber: data.old_project_contract_number || '', + current: data.old_project_year_current || 0, + hasFixedAssets: data.old_project_has_fixed_assets || false, + noFixedAssets: data.old_project_no_fixed_assets || false + } + } + }, + + + // 检查子包是否有项目实施进度安排数据 + hasSubPackageImplementationData(subPackage) { + return (subPackage.implementationSchedule && subPackage.implementationSchedule.trim() !== '') || + this.hasSubPackageImplementationDetailData(subPackage) + }, + + // 检查子包是否有实施进度详细数据 + hasSubPackageImplementationDetailData(subPackage) { + if (!subPackage.implementationScheduleDetail) return false + const detail = subPackage.implementationScheduleDetail + return detail.currentYearContent || + detail.nextYearContent || + detail.thirdYearContent || + detail.currentYearAmount > 0 || + this.hasMonthlyData(detail) + }, + + // 检查是否有分月数据 + hasMonthlyData(scheduleDetail) { + if (!scheduleDetail) return false + for (let i = 1; i <= 12; i++) { + if (scheduleDetail[`currentYearMonth${i}`] && scheduleDetail[`currentYearMonth${i}`] > 0) { + return true + } + } + return false + }, + + // 检查子包是否有绩效指标数据 + hasSubPackagePerformanceData(subPackage) { + return subPackage.performanceIndicators && subPackage.performanceIndicators.length > 0 + }, + + // 格式化文件大小 + formatFileSize(bytes) { + if (!bytes) return '0 B' + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] + }, + + // 根据ID获取经济分类名称 + getEconomicCategoryName(categoryId) { + if (!this.economicCategories || this.economicCategories.length === 0) { + return '未知分类' + } + const category = this.economicCategories.find(cat => cat.id === categoryId) + return category ? category.name : '未知分类' + }, } } @@ -2605,4 +3375,531 @@ export default { text-align: center; font-size: 12px; } + +/* 子包填报数据样式 */ +.sub-package-funding-section { + margin-top: 20px; + padding: 16px; + background-color: #f8f9fa; + border-radius: 6px; + border: 1px solid #e9ecef; +} + +.sub-section-title { + display: flex; + align-items: center; + font-size: 14px; + font-weight: 600; + color: #495057; + margin-bottom: 16px; +} + +.sub-section-title i { + margin-right: 8px; + color: #007bff; +} + +.sub-package-count { + margin-left: 8px; + font-size: 12px; + color: #6c757d; + font-weight: normal; +} + +.sub-package-funding-container { + background-color: #ffffff; + border-radius: 4px; + overflow: hidden; +} + +.sub-package-funding-item .el-collapse-item__header { + background-color: #f8f9fa; + border-bottom: 1px solid #e9ecef; + font-weight: 500; + color: #495057; +} + +.sub-package-funding-content { + padding: 20px; + background-color: #ffffff; +} + +.funding-data-section, .economic-data-section { + margin-bottom: 20px; +} + +.data-title { + display: flex; + align-items: center; + font-size: 14px; + font-weight: 600; + color: #495057; + margin-bottom: 12px; + margin-top: 0; +} + +.data-title i { + margin-right: 6px; + color: #007bff; +} + +.funding-details { + background-color: #f8f9fa; + border-radius: 4px; + padding: 12px; +} + +.project-section { + margin-bottom: 16px; +} + +.project-section:last-child { + margin-bottom: 0; +} + +.project-header { + font-size: 13px; + font-weight: 600; + color: #495057; + margin-bottom: 8px; + padding-bottom: 4px; + border-bottom: 1px solid #dee2e6; +} + +.funding-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 4px 0; + font-size: 12px; +} + +.funding-item .label { + color: #6c757d; + font-weight: 500; + min-width: 120px; +} + +.funding-item .value { + color: #495057; + font-weight: 500; + text-align: right; +} + +.economic-items { + background-color: #f8f9fa; + border-radius: 4px; + padding: 12px; +} + +.economic-item { + margin-bottom: 12px; + padding: 8px; + background-color: #ffffff; + border-radius: 4px; + border: 1px solid #e9ecef; +} + +.economic-item:last-child { + margin-bottom: 0; +} + +.economic-item-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 6px; +} + +.category-name { + font-size: 12px; + font-weight: 600; + color: #495057; +} + +.amount { + font-size: 12px; + font-weight: 600; + color: #007bff; +} + +.calculation-basis .label { + font-size: 11px; + color: #6c757d; + font-weight: 500; +} + +.basis-content { + margin-top: 4px; + padding: 6px 8px; + background-color: #f1f3f4; + border-radius: 3px; + font-size: 11px; + line-height: 1.4; + color: #495057; + word-break: break-word; +} + +.no-data { + display: flex; + align-items: center; + justify-content: center; + padding: 20px; + color: #6c757d; + font-style: italic; + font-size: 12px; +} + +.no-data i { + margin-right: 6px; + color: #adb5bd; +} + +/* 子包通用样式 */ +.sub-package-header { + margin-bottom: 12px; + padding: 8px 12px; + background-color: #e9ecef; + border-radius: 4px; + border-left: 4px solid #007bff; +} + +.sub-package-name { + margin: 0; + font-size: 15px; + font-weight: 600; + color: #495057; +} + +.sub-package-funding-item, +.sub-package-implementation-item, +.sub-package-performance-item, +.sub-package-notes-item, +.sub-package-attachments-item { + margin-bottom: 16px; + padding: 16px; + background-color: #ffffff; + border: 1px solid #e9ecef; + border-radius: 6px; +} + +.sub-package-funding-item:last-child, +.sub-package-implementation-item:last-child, +.sub-package-performance-item:last-child, +.sub-package-notes-item:last-child, +.sub-package-attachments-item:last-child { + margin-bottom: 0; +} + +/* 子包表格样式 */ +.sub-fund-application-table-container { + margin-top: 8px; +} + +.sub-fund-application-table { + font-size: 14px; +} + +.sub-fund-application-table .el-table__header th { + background-color: #f5f7fa; + color: #606266; + font-weight: 600; + font-size: 14px; +} + +.no-funding-data { + display: flex; + align-items: center; + justify-content: center; + padding: 16px; + color: #6c757d; + font-style: italic; + font-size: 14px; + background-color: #f8f9fa; + border-radius: 4px; + margin: 8px 0; +} + +.no-funding-data i { + margin-right: 6px; + color: #adb5bd; +} + +/* 项目实施进度安排子包样式 */ +.sub-package-implementation-section { + margin-top: 20px; + padding: 16px; + background-color: #f8f9fa; + border-radius: 6px; + border: 1px solid #e9ecef; +} + +.sub-package-implementation-container { + background-color: #ffffff; + border-radius: 4px; + overflow: hidden; +} + +.implementation-text { + margin-bottom: 12px; + padding: 8px 12px; + background-color: #f8f9fa; + border-radius: 4px; + border-left: 3px solid #007bff; +} + +.text-content { + font-size: 14px; + line-height: 1.5; + color: #495057; +} + +.sub-implementation-table-container { + margin-top: 8px; +} + +.sub-implementation-table { + font-size: 14px; +} + +.sub-implementation-table .el-table__header th { + font-size: 14px; +} + +.schedule-content { + font-size: 13px; + line-height: 1.4; + padding: 6px; + word-break: break-word; + min-height: 24px; +} + +.sub-monthly-detail-section { + margin-top: 12px; +} + +.section-subtitle { + display: flex; + align-items: center; + font-size: 14px; + font-weight: 600; + color: #495057; + margin-bottom: 8px; + padding: 8px 10px; + background-color: #e9ecef; + border-radius: 3px; +} + +.section-subtitle i { + margin-right: 8px; + color: #007bff; +} + +.sub-monthly-table { + font-size: 13px; +} + +.sub-monthly-table .el-table__header th { + font-size: 13px; +} + +.no-implementation-data { + display: flex; + align-items: center; + justify-content: center; + padding: 16px; + color: #6c757d; + font-style: italic; + font-size: 14px; + background-color: #f8f9fa; + border-radius: 4px; +} + +.no-implementation-data i { + margin-right: 6px; + color: #adb5bd; +} + +/* 绩效指标子包样式 */ +.sub-package-performance-section { + margin-top: 20px; + padding: 16px; + background-color: #f8f9fa; + border-radius: 6px; + border: 1px solid #e9ecef; +} + +.sub-package-performance-container { + background-color: #ffffff; + border-radius: 4px; + overflow: hidden; +} + +.sub-performance-table { + font-size: 14px; +} + +.sub-performance-table .el-table__header th { + background-color: #f5f7fa; + color: #606266; + font-weight: 600; + font-size: 14px; +} + +.no-performance-data { + display: flex; + align-items: center; + justify-content: center; + padding: 16px; + color: #6c757d; + font-style: italic; + font-size: 14px; + background-color: #f8f9fa; + border-radius: 4px; +} + +.no-performance-data i { + margin-right: 6px; + color: #adb5bd; +} + +/* 其他说明事项子包样式 */ +.sub-package-notes-section { + margin-top: 20px; + padding: 16px; + background-color: #f8f9fa; + border-radius: 6px; + border: 1px solid #e9ecef; +} + +.sub-package-notes-container { + background-color: #ffffff; + border-radius: 4px; + overflow: hidden; +} + +.other-notes-content { + padding: 14px; + background-color: #f8f9fa; + border-radius: 4px; + border-left: 3px solid #28a745; + font-size: 14px; + line-height: 1.5; + color: #495057; + word-break: break-word; +} + +.no-notes-data { + display: flex; + align-items: center; + justify-content: center; + padding: 16px; + color: #6c757d; + font-style: italic; + font-size: 14px; + background-color: #f8f9fa; + border-radius: 4px; +} + +.no-notes-data i { + margin-right: 6px; + color: #adb5bd; +} + +/* 附件子包样式 */ +.sub-package-attachments-section { + margin-top: 20px; + padding: 16px; + background-color: #f8f9fa; + border-radius: 6px; + border: 1px solid #e9ecef; +} + +.sub-package-attachments-container { + background-color: #ffffff; + border-radius: 4px; + overflow: hidden; +} + +.attachment-item { + display: flex; + align-items: center; + padding: 10px 14px; + background-color: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 4px; + margin-bottom: 8px; + font-size: 14px; +} + +.attachment-item:last-child { + margin-bottom: 0; +} + +.attachment-item i { + margin-right: 10px; + color: #007bff; + font-size: 16px; +} + +.file-link { + flex: 1; + color: #495057; + font-weight: 500; + font-size: 14px; +} + +.file-size { + margin-left: 12px; + color: #6c757d; + font-size: 13px; +} + +.download-btn { + margin-left: 10px; + padding: 4px 10px; + font-size: 13px; +} + +.no-attachments-data { + display: flex; + align-items: center; + justify-content: center; + padding: 16px; + color: #6c757d; + font-style: italic; + font-size: 14px; + background-color: #f8f9fa; + border-radius: 4px; +} + +.no-attachments-data i { + margin-right: 6px; + color: #adb5bd; +} + +/* 克隆按钮区域样式 */ +.clone-section { + display: flex; + align-items: center; + justify-content: center; + padding: 20px; + margin-bottom: 20px; + background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); + border: 2px dashed #28a745; + border-radius: 8px; + text-align: center; +} + +.clone-hint { + margin-left: 12px; + color: #6c757d; + font-size: 14px; + font-style: italic; +} + +.clone-section:hover { + background: linear-gradient(135deg, #e9ecef 0%, #dee2e6 100%); + border-color: #20c997; +} \ No newline at end of file diff --git a/src/views/budget/collection/leadDepartment.vue b/src/views/budget/collection/leadDepartment.vue index 861523b..5e30d47 100644 --- a/src/views/budget/collection/leadDepartment.vue +++ b/src/views/budget/collection/leadDepartment.vue @@ -593,6 +593,8 @@ export default { const processedNode = { id: node.id, name: node.name, + level: node.level, + parent: node.parent, parent_name: parentName, // 保存父级名称 allocated_amount: node.allocated_amount, decomposed_amount: node.decomposed_amount, diff --git a/src/views/budget/collection/package.vue b/src/views/budget/collection/package.vue index e29a1be..f4332c3 100644 --- a/src/views/budget/collection/package.vue +++ b/src/views/budget/collection/package.vue @@ -289,71 +289,252 @@ - +
-
+ + + + +
+
+ +
+
+

{{ summaryStats.bigPackageCount }}

+

大包数量

+
+
+
+
+ + +
+
+ +
+
+

{{ summaryStats.mediumPackageCount }}

+

中包数量

+
+
+
+
+ + +
+
+ +
+
+

{{ formatAmount(summaryStats.totalAllocatedAmount) }}

+

总分配金额(万元)

+
+
+
+
+ + +
+
+ +
+
+

{{ formatAmount(summaryStats.totalReportedAmount) }}

+

总上报金额(万元)

+
+
+
+
+
+ + +
+ + 按大包统计 + 按部门统计 + 按经济分类统计 + +
+ + +
+ - + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
@@ -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;