|
|
<template>
|
|
|
<div class="budget-submission-form">
|
|
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px">
|
|
|
|
|
|
<!-- 基本信息 -->
|
|
|
<div class="form-section">
|
|
|
<div class="section-title">
|
|
|
<i class="el-icon-info"></i>
|
|
|
基本信息
|
|
|
</div>
|
|
|
|
|
|
<el-row :gutter="20">
|
|
|
<el-col :span="8">
|
|
|
<div class="info-display-item">
|
|
|
<label class="info-label">申请科室:</label>
|
|
|
<div class="info-content">{{ formData.departmentName || '暂无' }}</div>
|
|
|
</div>
|
|
|
</el-col>
|
|
|
<el-col :span="8">
|
|
|
<div class="info-display-item">
|
|
|
<label class="info-label">预算年度:</label>
|
|
|
<div class="info-content">{{ formData.budgetYear || '暂无' }}</div>
|
|
|
</div>
|
|
|
</el-col>
|
|
|
<el-col :span="8">
|
|
|
<div class="info-display-item">
|
|
|
<label class="info-label">分配金额:</label>
|
|
|
<div class="info-content">{{ formData.allocatedAmount || '0.00' }} 万元</div>
|
|
|
</div>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
|
|
|
<el-row :gutter="20">
|
|
|
<el-col :span="12">
|
|
|
<div class="info-display-item">
|
|
|
<label class="info-label">子项名称:</label>
|
|
|
<div class="info-content">{{ formData.subItemName || '暂无' }}</div>
|
|
|
</div>
|
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
|
<div class="info-display-item">
|
|
|
<label class="info-label">大项目名称:</label>
|
|
|
<div class="info-content">{{ formData.projectName || '暂无' }}</div>
|
|
|
</div>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
</div>
|
|
|
|
|
|
<!-- 申请信息(只读展示) -->
|
|
|
<div v-if="showApplicationInfo" class="form-section">
|
|
|
<div class="section-title">
|
|
|
<i class="el-icon-document"></i>
|
|
|
申请信息
|
|
|
</div>
|
|
|
|
|
|
<el-row :gutter="20">
|
|
|
<el-col :span="12">
|
|
|
<div class="info-display-item">
|
|
|
<label class="info-label">申请依据:</label>
|
|
|
<div class="info-content">{{ parentSubmissionInfo.application_basis || '暂无申请依据' }}</div>
|
|
|
</div>
|
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
|
<div class="info-display-item">
|
|
|
<label class="info-label">申请目的:</label>
|
|
|
<div class="info-content">{{ parentSubmissionInfo.application_purpose || '暂无申请目的' }}</div>
|
|
|
</div>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
|
|
|
<el-row :gutter="20">
|
|
|
<el-col :span="12">
|
|
|
<div class="info-display-item">
|
|
|
<label class="info-label">申请理由:</label>
|
|
|
<div class="info-content">{{ parentSubmissionInfo.application_reason || '暂无申请理由' }}</div>
|
|
|
</div>
|
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
|
<div class="info-display-item">
|
|
|
<label class="info-label">项目内容:</label>
|
|
|
<div class="info-content">{{ parentSubmissionInfo.project_content || '暂无项目内容' }}</div>
|
|
|
</div>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
</div>
|
|
|
|
|
|
<!-- 项目内容分解 -->
|
|
|
<div class="form-section">
|
|
|
<div class="section-title">
|
|
|
<i class="el-icon-s-grid"></i>
|
|
|
项目内容分解
|
|
|
<div v-if="!isViewMode" style="margin-left: 20px; display: flex; align-items: center;">
|
|
|
<el-checkbox
|
|
|
v-model="selectAll"
|
|
|
@change="onSelectAllChange"
|
|
|
style="margin-right: 10px;"
|
|
|
>
|
|
|
全选
|
|
|
</el-checkbox>
|
|
|
<span style="color: #666; font-size: 12px;">勾选需要填写的经济分类</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="economic-table-container">
|
|
|
<el-table
|
|
|
:data="economicCategoriesWithData"
|
|
|
border
|
|
|
class="economic-table"
|
|
|
:summary-method="getSummaries"
|
|
|
show-summary
|
|
|
style="width: 100%;"
|
|
|
>
|
|
|
<el-table-column v-if="!isViewMode" label="选择" width="60" align="center">
|
|
|
<template slot-scope="scope">
|
|
|
<el-checkbox
|
|
|
v-model="scope.row.selected"
|
|
|
@change="onEconomicItemToggle(scope.row, scope.$index)"
|
|
|
/>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column type="index" label="序号" width="60" align="center" />
|
|
|
|
|
|
<el-table-column label="经济分类" min-width="130">
|
|
|
<template slot-scope="scope">
|
|
|
<span>{{ scope.row.categoryName }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column label="金额(万元)" width="150">
|
|
|
<template slot-scope="scope">
|
|
|
<el-input
|
|
|
v-if="!isViewMode && scope.row.selected"
|
|
|
v-model.number="scope.row.amount"
|
|
|
type="number"
|
|
|
:min="0"
|
|
|
:step="0.1"
|
|
|
placeholder="请输入金额"
|
|
|
@input="validateTotal"
|
|
|
/>
|
|
|
<span v-else-if="scope.row.selected">{{ formatCurrency(scope.row.amount) }}</span>
|
|
|
<span v-else class="disabled-text">-</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column label="测算依据" min-width="300">
|
|
|
<template slot-scope="scope">
|
|
|
<el-input
|
|
|
v-if="!isViewMode && scope.row.selected"
|
|
|
v-model="scope.row.calculationBasis"
|
|
|
type="textarea"
|
|
|
:rows="2"
|
|
|
placeholder="请输入测算依据..."
|
|
|
maxlength="500"
|
|
|
/>
|
|
|
<div v-else-if="scope.row.selected" style="word-break: break-all;">{{ scope.row.calculationBasis || '-' }}</div>
|
|
|
<span v-else class="disabled-text">-</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column label="附件上传" width="300" align="center">
|
|
|
<template slot-scope="scope">
|
|
|
<div v-if="!isViewMode && scope.row.selected">
|
|
|
<el-upload
|
|
|
:action="uploadAction"
|
|
|
:headers="uploadHeaders"
|
|
|
:data="{ categoryId: scope.row.categoryId }"
|
|
|
:file-list="scope.row.fileList || []"
|
|
|
:on-success="(response, file, fileList) => handleUploadSuccess(response, file, fileList, scope.row)"
|
|
|
:on-remove="(file, fileList) => handleUploadRemove(file, fileList, scope.row)"
|
|
|
:on-error="handleUploadError"
|
|
|
multiple
|
|
|
:limit="5"
|
|
|
accept=".pdf,.doc,.docx,.xls,.xlsx,.jpg,.jpeg,.png"
|
|
|
>
|
|
|
<el-button size="mini" type="primary" icon="el-icon-upload">上传</el-button>
|
|
|
</el-upload>
|
|
|
</div>
|
|
|
<div v-else-if="scope.row.selected && scope.row.fileList && scope.row.fileList.length > 0">
|
|
|
<div v-for="file in scope.row.fileList" :key="file.uid" class="file-item">
|
|
|
<i :class="getFileIcon(file.name || file.original_name)"></i>
|
|
|
<el-link
|
|
|
type="primary"
|
|
|
@click="openFile(file)"
|
|
|
:underline="false"
|
|
|
class="file-link"
|
|
|
>
|
|
|
{{ file.name || file.original_name || '附件' }}
|
|
|
</el-link>
|
|
|
<el-button
|
|
|
type="text"
|
|
|
size="mini"
|
|
|
@click="downloadFile(file)"
|
|
|
class="download-btn"
|
|
|
>
|
|
|
下载
|
|
|
</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
<span v-else class="disabled-text">-</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
</div>
|
|
|
|
|
|
<!-- 分配验证提示 -->
|
|
|
<div v-if="!isAmountValid" class="amount-warning">
|
|
|
<i class="el-icon-warning"></i>
|
|
|
<span>项目内容分解金额总计:{{ formatCurrency(totalEconomicAmount) }} 万元,</span>
|
|
|
<span :class="{ 'error': totalEconomicAmount !== allocatedAmountNum }">
|
|
|
{{ totalEconomicAmount > allocatedAmountNum ? '超出' : '不足' }}分配金额
|
|
|
{{ formatCurrency(Math.abs(totalEconomicAmount - allocatedAmountNum)) }} 万元
|
|
|
</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 申请资金表格 -->
|
|
|
|
|
|
|
|
|
<!-- 项目周期(一次性、阶段性、长期性) -->
|
|
|
<div class="form-section">
|
|
|
<div class="section-title">
|
|
|
<i class="el-icon-time"></i>
|
|
|
项目周期(一次性、阶段性、长期性)
|
|
|
</div>
|
|
|
|
|
|
<el-form-item prop="implementationSchedule" class="no-label-margin">
|
|
|
<el-input
|
|
|
v-model="formData.implementationSchedule"
|
|
|
type="textarea"
|
|
|
:rows="4"
|
|
|
placeholder="请详细说明项目周期(一次性、阶段性、长期性)..."
|
|
|
:readonly="isViewMode"
|
|
|
maxlength="1000"
|
|
|
show-word-limit
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
|
|
|
<!-- 项目周期(一次性、阶段性、长期性)详细表格 -->
|
|
|
<div class="implementation-schedule-table-container">
|
|
|
<el-table
|
|
|
:data="[formData.implementationScheduleDetail]"
|
|
|
border
|
|
|
class="implementation-schedule-table"
|
|
|
style="width: 100%;"
|
|
|
>
|
|
|
<!-- 当年实施进度安排 -->
|
|
|
|
|
|
<!-- 下一年实施进度安排 -->
|
|
|
|
|
|
<!-- 第三年实施进度安排 -->
|
|
|
|
|
|
</el-table>
|
|
|
</div>
|
|
|
|
|
|
<!-- 当年分月明细表格 -->
|
|
|
|
|
|
|
|
|
<!-- 当年月度金额验证提示 -->
|
|
|
|
|
|
|
|
|
<!-- 年度金额与项目内容分解合计验证提示 -->
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<!-- 项目预计取得绩效 -->
|
|
|
<div class="form-section">
|
|
|
<div class="section-title">
|
|
|
<i class="el-icon-s-data"></i>
|
|
|
项目预计取得绩效
|
|
|
<el-button
|
|
|
v-if="!isViewMode"
|
|
|
type="primary"
|
|
|
size="mini"
|
|
|
@click="addPerformanceIndicator"
|
|
|
style="margin-left: 10px;"
|
|
|
>
|
|
|
添加指标
|
|
|
</el-button>
|
|
|
</div>
|
|
|
|
|
|
<div class="performance-table-container">
|
|
|
<el-table
|
|
|
:data="formData.performanceIndicators"
|
|
|
border
|
|
|
class="performance-table"
|
|
|
size="mini"
|
|
|
style="width: 100%;"
|
|
|
:row-class-name="getRowClassName"
|
|
|
>
|
|
|
<el-table-column type="index" label="序号" width="60" align="center" />
|
|
|
|
|
|
<el-table-column label="一级指标" width="100" min-width="100">
|
|
|
<template slot-scope="scope">
|
|
|
<el-select
|
|
|
v-if="!isViewMode && !scope.row.isRequired"
|
|
|
v-model="scope.row.level1"
|
|
|
placeholder="选择"
|
|
|
size="mini"
|
|
|
style="width: 100%;"
|
|
|
@change="onLevel1Change(scope.$index)"
|
|
|
>
|
|
|
<el-option
|
|
|
v-for="option in getFilteredLevel1Options()"
|
|
|
:key="option.id"
|
|
|
:label="option.name"
|
|
|
:value="option.name"
|
|
|
/>
|
|
|
</el-select>
|
|
|
<span v-else>{{ scope.row.level1 || '-' }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column label="二级指标" width="120" min-width="120">
|
|
|
<template slot-scope="scope">
|
|
|
<el-select
|
|
|
v-if="!isViewMode && !scope.row.isRequired"
|
|
|
v-model="scope.row.level2"
|
|
|
placeholder="选择"
|
|
|
size="mini"
|
|
|
style="width: 100%;"
|
|
|
@change="onLevel2Change(scope.$index)"
|
|
|
>
|
|
|
<el-option
|
|
|
v-for="option in getFilteredLevel2Options(scope.row.level1)"
|
|
|
:key="option.id"
|
|
|
:label="option.name"
|
|
|
:value="option.name"
|
|
|
/>
|
|
|
</el-select>
|
|
|
<span v-else>{{ scope.row.level2 || '-' }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column label="三级指标" min-width="200">
|
|
|
<template slot-scope="scope">
|
|
|
<div v-if="!isViewMode && !scope.row.isRequired">
|
|
|
<el-select
|
|
|
v-if="getFilteredLevel3Options(scope.row.level1, scope.row.level2).length > 0"
|
|
|
v-model="scope.row.level3"
|
|
|
placeholder="选择三级指标"
|
|
|
size="mini"
|
|
|
style="width: 100%;"
|
|
|
filterable
|
|
|
>
|
|
|
<el-option
|
|
|
v-for="option in getFilteredLevel3Options(scope.row.level1, scope.row.level2)"
|
|
|
:key="option.id"
|
|
|
:label="option.name"
|
|
|
:value="option.name"
|
|
|
/>
|
|
|
</el-select>
|
|
|
<el-input
|
|
|
v-else
|
|
|
v-model="scope.row.level3"
|
|
|
placeholder="请输入三级指标名称"
|
|
|
size="mini"
|
|
|
style="width: 100%;"
|
|
|
maxlength="100"
|
|
|
/>
|
|
|
</div>
|
|
|
<span v-else>{{ scope.row.level3 || '-' }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column label="计算符号" width="80" min-width="80">
|
|
|
<template slot-scope="scope">
|
|
|
<el-select
|
|
|
v-if="!isViewMode"
|
|
|
v-model="scope.row.symbol"
|
|
|
placeholder="符号"
|
|
|
size="mini"
|
|
|
style="width: 100%;"
|
|
|
>
|
|
|
<el-option label=">" value=">" />
|
|
|
<el-option label="=" value="=" />
|
|
|
<el-option label="≥" value="≥" />
|
|
|
<el-option label="<" value="<" />
|
|
|
<el-option label="≤" value="≤" />
|
|
|
<el-option label="定性" value="定性" />
|
|
|
</el-select>
|
|
|
<span v-else>{{ scope.row.symbol || '-' }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column label="半年指标值" width="130" min-width="130">
|
|
|
<template slot-scope="scope">
|
|
|
<el-input
|
|
|
v-if="!isViewMode"
|
|
|
v-model="scope.row.halfYearValue"
|
|
|
size="mini"
|
|
|
placeholder="指标值"
|
|
|
maxlength="50"
|
|
|
/>
|
|
|
<span v-else>{{ scope.row.halfYearValue || '-' }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column label="全年指标值" width="130" min-width="130">
|
|
|
<template slot-scope="scope">
|
|
|
<el-input
|
|
|
v-if="!isViewMode"
|
|
|
v-model="scope.row.fullYearValue"
|
|
|
size="mini"
|
|
|
placeholder="指标值"
|
|
|
maxlength="50"
|
|
|
/>
|
|
|
<span v-else>{{ scope.row.fullYearValue || '-' }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column label="计量单位" width="80" min-width="80">
|
|
|
<template slot-scope="scope">
|
|
|
<el-input
|
|
|
v-if="!isViewMode"
|
|
|
v-model="scope.row.unit"
|
|
|
size="mini"
|
|
|
placeholder="单位"
|
|
|
maxlength="20"
|
|
|
/>
|
|
|
<span v-else>{{ scope.row.unit || '-' }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column v-if="!isViewMode" label="操作" width="60" align="center">
|
|
|
<template slot-scope="scope">
|
|
|
<el-button
|
|
|
v-if="!scope.row.isRequired"
|
|
|
type="danger"
|
|
|
size="mini"
|
|
|
@click="removePerformanceIndicator(scope.$index)"
|
|
|
icon="el-icon-delete"
|
|
|
/>
|
|
|
<el-tooltip v-else content="必填指标不可删除" placement="top">
|
|
|
<el-button
|
|
|
type="info"
|
|
|
size="mini"
|
|
|
icon="el-icon-lock"
|
|
|
disabled
|
|
|
/>
|
|
|
</el-tooltip>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 其他需要说明事项 -->
|
|
|
<div class="form-section">
|
|
|
<div class="section-title">
|
|
|
<i class="el-icon-edit-outline"></i>
|
|
|
其他需要说明事项
|
|
|
</div>
|
|
|
|
|
|
<el-form-item class="no-label-margin">
|
|
|
<el-input
|
|
|
v-model="formData.otherNotes"
|
|
|
type="textarea"
|
|
|
:rows="3"
|
|
|
placeholder="请说明其他需要说明的事项..."
|
|
|
:readonly="isViewMode"
|
|
|
maxlength="500"
|
|
|
show-word-limit
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
|
|
|
<div class="note-text">
|
|
|
<strong>备注:</strong>需同时提供申请依据和相关支撑材料。
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 附件上传 -->
|
|
|
<div class="form-section">
|
|
|
<div class="section-title">
|
|
|
<i class="el-icon-folder-opened"></i>
|
|
|
附件上传
|
|
|
</div>
|
|
|
|
|
|
<el-form-item class="no-label-margin">
|
|
|
<!-- 编辑模式下的上传组件 -->
|
|
|
<el-upload
|
|
|
v-if="!isViewMode"
|
|
|
:action="uploadAction"
|
|
|
:headers="uploadHeaders"
|
|
|
:data="{ type: 'general' }"
|
|
|
:file-list="formData.generalFiles || []"
|
|
|
:on-success="(response, file, fileList) => handleGeneralUploadSuccess(response, file, fileList)"
|
|
|
:on-remove="(file, fileList) => handleGeneralUploadRemove(file, fileList)"
|
|
|
:on-error="handleUploadError"
|
|
|
multiple
|
|
|
:limit="10"
|
|
|
accept=".pdf,.doc,.docx,.xls,.xlsx,.jpg,.jpeg,.png,.zip,.rar"
|
|
|
drag
|
|
|
>
|
|
|
<i class="el-icon-upload"></i>
|
|
|
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
|
|
<div class="el-upload__tip" slot="tip">支持 PDF、Word、Excel、图片、压缩包等格式,单个文件不超过50MB</div>
|
|
|
</el-upload>
|
|
|
|
|
|
<!-- 查看模式下的文件列表 -->
|
|
|
<div v-else>
|
|
|
<div v-if="formData.generalFiles && formData.generalFiles.length > 0" class="general-files-list">
|
|
|
<div v-for="file in formData.generalFiles" :key="file.uid || file.id" class="general-file-item">
|
|
|
<i :class="getFileIcon(file.name || file.original_name)"></i>
|
|
|
<el-link
|
|
|
type="primary"
|
|
|
@click="openFile(file)"
|
|
|
:underline="false"
|
|
|
class="file-link"
|
|
|
>
|
|
|
{{ file.name || file.original_name || '附件' }}
|
|
|
</el-link>
|
|
|
<el-button
|
|
|
type="text"
|
|
|
size="mini"
|
|
|
@click="downloadFile(file)"
|
|
|
class="download-btn"
|
|
|
>
|
|
|
下载
|
|
|
</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div v-else class="no-files-text">
|
|
|
<i class="el-icon-folder-opened"></i>
|
|
|
<span>暂无附件</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</el-form-item>
|
|
|
</div>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
<!-- 操作按钮 -->
|
|
|
<div class="form-actions">
|
|
|
<el-button @click="cancel">{{ isViewMode ? '关闭' : '取消' }}</el-button>
|
|
|
<el-button
|
|
|
v-if="!isViewMode"
|
|
|
type="primary"
|
|
|
@click="submit"
|
|
|
:disabled="!canSubmit"
|
|
|
>
|
|
|
{{ mode === 'create' ? '提交' : '保存修改' }}
|
|
|
</el-button>
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
import {
|
|
|
getEconomicCategories,
|
|
|
getPackageDecomposition,
|
|
|
getPackageProjectNames,
|
|
|
getPerformanceIndicatorTree,
|
|
|
getRequiredPerformanceIndicators
|
|
|
} from '@/api/budget/departmentSubmission.js'
|
|
|
|
|
|
export default {
|
|
|
name: 'BudgetSubmissionForm',
|
|
|
props: {
|
|
|
packageData: {
|
|
|
type: Object,
|
|
|
required: true
|
|
|
},
|
|
|
mode: {
|
|
|
type: String,
|
|
|
default: 'create' // create, edit, view
|
|
|
},
|
|
|
showApplicationInfo: {
|
|
|
type: Boolean,
|
|
|
default: true // 默认显示申请信息,在抽屉中可以设置为false
|
|
|
}
|
|
|
},
|
|
|
data() {
|
|
|
return {
|
|
|
formData: {
|
|
|
packageId: '',
|
|
|
departmentName: '',
|
|
|
budgetYear: '',
|
|
|
allocatedAmount: '',
|
|
|
subItemName: '',
|
|
|
projectName: '',
|
|
|
economicItems: [],
|
|
|
implementationSchedule: '',
|
|
|
implementationScheduleDetail: {
|
|
|
currentYearContent: '',
|
|
|
currentYearAmount: 0,
|
|
|
currentYearMonth1: 0,
|
|
|
currentYearMonth2: 0,
|
|
|
currentYearMonth3: 0,
|
|
|
currentYearMonth4: 0,
|
|
|
currentYearMonth5: 0,
|
|
|
currentYearMonth6: 0,
|
|
|
currentYearMonth7: 0,
|
|
|
currentYearMonth8: 0,
|
|
|
currentYearMonth9: 0,
|
|
|
currentYearMonth10: 0,
|
|
|
currentYearMonth11: 0,
|
|
|
currentYearMonth12: 0,
|
|
|
nextYearContent: '',
|
|
|
thirdYearContent: ''
|
|
|
},
|
|
|
performanceIndicators: [],
|
|
|
otherNotes: '',
|
|
|
generalFiles: []
|
|
|
},
|
|
|
fundApplicationData: [{
|
|
|
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
|
|
|
}
|
|
|
}],
|
|
|
parentSubmissionInfo: {
|
|
|
application_basis: '',
|
|
|
application_purpose: '',
|
|
|
application_reason: '',
|
|
|
project_content: ''
|
|
|
},
|
|
|
economicCategories: [],
|
|
|
economicCategoriesWithData: [],
|
|
|
selectAll: false,
|
|
|
// 绩效指标相关数据
|
|
|
performanceIndicatorTree: [],
|
|
|
level1Options: [],
|
|
|
level2OptionsMap: {},
|
|
|
level3OptionsMap: {},
|
|
|
requiredIndicators: [],
|
|
|
formRules: {
|
|
|
implementationSchedule: [
|
|
|
{ required: true, message: '请输入项目周期(一次性、阶段性、长期性)', trigger: 'blur' }
|
|
|
]
|
|
|
},
|
|
|
uploadAction: process.env.VUE_APP_UPLOAD_API || '/api/upload', // 上传接口地址
|
|
|
uploadHeaders: {
|
|
|
'Authorization': 'Bearer ' + (this.$store.state.user.token || localStorage.getItem('token') || '')
|
|
|
},
|
|
|
}
|
|
|
},
|
|
|
computed: {
|
|
|
isViewMode() {
|
|
|
return this.mode === 'view'
|
|
|
},
|
|
|
|
|
|
allocatedAmountNum() {
|
|
|
return parseFloat(this.formData.allocatedAmount) || 0
|
|
|
},
|
|
|
|
|
|
totalEconomicAmount() {
|
|
|
return this.economicCategoriesWithData.reduce((total, item) => {
|
|
|
if (item.selected) {
|
|
|
return total + (parseFloat(item.amount) || 0)
|
|
|
}
|
|
|
return total
|
|
|
}, 0)
|
|
|
},
|
|
|
|
|
|
isAmountValid() {
|
|
|
// 第一阶段不限制总计分解金额与分配金额的对等
|
|
|
return true
|
|
|
},
|
|
|
|
|
|
newProjectTotal() {
|
|
|
return parseFloat(this.fundApplicationData[0].newProject.totalAmount) || 0
|
|
|
},
|
|
|
|
|
|
newProjectYearlyTotal() {
|
|
|
return parseFloat(this.fundApplicationData[0].newProject.current) + parseFloat(this.fundApplicationData[0].newProject.next)
|
|
|
},
|
|
|
|
|
|
isNewProjectAmountValid() {
|
|
|
return Math.abs(this.newProjectYearlyTotal - this.newProjectTotal) < 0.01
|
|
|
},
|
|
|
|
|
|
nextYearLabel() {
|
|
|
// 预算年度如"2025年",则 nextYearLabel 为"2026年"
|
|
|
if (this.formData.budgetYear && /^\d{4}年$/.test(this.formData.budgetYear)) {
|
|
|
const year = parseInt(this.formData.budgetYear)
|
|
|
if (!isNaN(year)) {
|
|
|
return (year + 1) + '年'
|
|
|
}
|
|
|
}
|
|
|
return '次年'
|
|
|
},
|
|
|
|
|
|
thirdYearLabel() {
|
|
|
// 预算年度如"2025年",则 thirdYearLabel 为"2027年"
|
|
|
if (this.formData.budgetYear && /^\d{4}年$/.test(this.formData.budgetYear)) {
|
|
|
const year = parseInt(this.formData.budgetYear)
|
|
|
if (!isNaN(year)) {
|
|
|
return (year + 2) + '年'
|
|
|
}
|
|
|
}
|
|
|
return '第三年'
|
|
|
},
|
|
|
|
|
|
currentYearMonthlyTotal() {
|
|
|
return this.formData.implementationScheduleDetail.currentYearMonth1 +
|
|
|
this.formData.implementationScheduleDetail.currentYearMonth2 +
|
|
|
this.formData.implementationScheduleDetail.currentYearMonth3 +
|
|
|
this.formData.implementationScheduleDetail.currentYearMonth4 +
|
|
|
this.formData.implementationScheduleDetail.currentYearMonth5 +
|
|
|
this.formData.implementationScheduleDetail.currentYearMonth6 +
|
|
|
this.formData.implementationScheduleDetail.currentYearMonth7 +
|
|
|
this.formData.implementationScheduleDetail.currentYearMonth8 +
|
|
|
this.formData.implementationScheduleDetail.currentYearMonth9 +
|
|
|
this.formData.implementationScheduleDetail.currentYearMonth10 +
|
|
|
this.formData.implementationScheduleDetail.currentYearMonth11 +
|
|
|
this.formData.implementationScheduleDetail.currentYearMonth12
|
|
|
},
|
|
|
|
|
|
isCurrentYearAmountValid() {
|
|
|
// 如果当年金额为0,则不进行校验(允许用户先填写月度金额)
|
|
|
if (this.formData.implementationScheduleDetail.currentYearAmount === 0) {
|
|
|
return true
|
|
|
}
|
|
|
// 如果月度金额总计为0,则不进行校验(允许用户先填写当年金额)
|
|
|
if (this.currentYearMonthlyTotal === 0) {
|
|
|
return true
|
|
|
}
|
|
|
// 进行精度校验,允许0.01万元的误差
|
|
|
return Math.abs(this.currentYearMonthlyTotal - this.formData.implementationScheduleDetail.currentYearAmount) < 0.01
|
|
|
},
|
|
|
|
|
|
// 年度金额与项目内容分解合计是否一致
|
|
|
isYearAmountEqualToEconomicTotal() {
|
|
|
const currentYearAmount = parseFloat(this.formData.implementationScheduleDetail.currentYearAmount) || 0
|
|
|
const economicTotal = this.totalEconomicAmount
|
|
|
// 如果其中一个为0,则不进行校验(允许用户先填写一部分)
|
|
|
if (currentYearAmount === 0 || economicTotal === 0) {
|
|
|
return true
|
|
|
}
|
|
|
// 进行精度校验,允许0.01万元的误差
|
|
|
return Math.abs(currentYearAmount - economicTotal) < 0.01
|
|
|
},
|
|
|
|
|
|
canSubmit() {
|
|
|
// 检查是否有选中的经济分类项目
|
|
|
const selectedItems = this.economicCategoriesWithData.filter(item => item.selected)
|
|
|
if (selectedItems.length === 0) return false
|
|
|
|
|
|
// 检查项目周期(一次性、阶段性、长期性)是否填写
|
|
|
if (!this.formData.implementationSchedule || this.formData.implementationSchedule.trim() === '') return false
|
|
|
|
|
|
// 检查选中的经济分类项目是否都有有效的金额和测算依据
|
|
|
const hasValidEconomicItems = selectedItems.every(item => {
|
|
|
const amount = parseFloat(item.amount)
|
|
|
return !isNaN(amount) &&
|
|
|
amount >= 0 &&
|
|
|
item.calculationBasis &&
|
|
|
item.calculationBasis.trim() !== ''
|
|
|
})
|
|
|
|
|
|
// 检查金额分配是否正确
|
|
|
return hasValidEconomicItems && this.isAmountValid
|
|
|
}
|
|
|
},
|
|
|
async created() {
|
|
|
await this.initFormData()
|
|
|
await this.loadEconomicCategories()
|
|
|
await this.loadPerformanceIndicators()
|
|
|
|
|
|
if (this.mode === 'edit' || this.mode === 'view') {
|
|
|
await this.loadExistingData()
|
|
|
}
|
|
|
|
|
|
// 初始化选择状态
|
|
|
this.updateSelectAllState()
|
|
|
},
|
|
|
methods: {
|
|
|
async initFormData() {
|
|
|
this.formData.packageId = this.packageData.id
|
|
|
this.formData.departmentName = this.packageData.department ? this.packageData.department.name : ''
|
|
|
this.formData.budgetYear = this.packageData.budget_year ? this.packageData.budget_year.year + '年' : ''
|
|
|
this.formData.allocatedAmount = this.formatCurrency(this.packageData.allocated_amount)
|
|
|
|
|
|
// 从后端获取项目名称信息
|
|
|
try {
|
|
|
const response = await getPackageProjectNames(this.packageData.id)
|
|
|
if (response && !response.errcode) {
|
|
|
this.formData.subItemName = response.sub_item_name
|
|
|
this.formData.projectName = response.project_name
|
|
|
} else if (response && response.errcode) {
|
|
|
// 静默处理,不显示错误提示,允许用户继续操作
|
|
|
console.warn('获取项目名称信息失败:', response.errmsg)
|
|
|
}
|
|
|
} catch (error) {
|
|
|
// 静默处理HTTP错误,不显示错误提示
|
|
|
console.warn('获取项目名称失败:', error.message)
|
|
|
}
|
|
|
|
|
|
// 加载package的submission信息
|
|
|
if (this.packageData.level == 1 || this.packageData.level == 2) {
|
|
|
this.parentSubmissionInfo = {
|
|
|
application_basis: (this.packageData.application_basis) || '',
|
|
|
application_purpose: (this.packageData.application_purpose) || '',
|
|
|
application_reason: (this.packageData.application_reason) || '',
|
|
|
project_content: (this.packageData.project_content) || ''
|
|
|
}
|
|
|
} else if (this.packageData.level == 3) {
|
|
|
this.parentSubmissionInfo = {
|
|
|
application_basis: (this.packageData.parent.submission && this.packageData.parent.submission.application_basis) || '',
|
|
|
application_purpose: (this.packageData.parent.submission && this.packageData.parent.submission.application_purpose) || '',
|
|
|
application_reason: (this.packageData.parent.submission && this.packageData.parent.submission.application_reason) || '',
|
|
|
project_content: (this.packageData.parent.submission && this.packageData.parent.submission.project_content) || ''
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
// 调试信息
|
|
|
console.log('packageData:', this.packageData)
|
|
|
console.log('parentSubmissionInfo:', this.parentSubmissionInfo)
|
|
|
|
|
|
},
|
|
|
|
|
|
async loadEconomicCategories() {
|
|
|
try {
|
|
|
const response = await getEconomicCategories()
|
|
|
if (response && !response.errcode) {
|
|
|
this.economicCategories = response || []
|
|
|
this.initEconomicCategoriesWithData()
|
|
|
} else if (response && response.errcode) {
|
|
|
// 经济分类是必需的,但提供更友好的错误提示
|
|
|
if (response.errmsg.includes('权限')) {
|
|
|
this.$message.warning('暂无权限获取经济分类数据,请联系管理员或稍后重试')
|
|
|
} else {
|
|
|
this.$message.error(response.errmsg || '加载经济分类失败')
|
|
|
}
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error('加载经济分类失败:', error)
|
|
|
this.$message.warning('网络异常,无法加载经济分类数据,请检查网络连接或稍后重试')
|
|
|
}
|
|
|
},
|
|
|
|
|
|
initEconomicCategoriesWithData() {
|
|
|
this.economicCategoriesWithData = this.economicCategories.map(category => ({
|
|
|
categoryId: category.id,
|
|
|
categoryName: category.name,
|
|
|
selected: false,
|
|
|
amount: '',
|
|
|
calculationBasis: '',
|
|
|
fileList: [] // 添加文件列表字段
|
|
|
}))
|
|
|
},
|
|
|
|
|
|
async loadExistingData() {
|
|
|
try {
|
|
|
const response = await getPackageDecomposition(this.packageData.id)
|
|
|
if (response && !response.errcode) {
|
|
|
const data = response
|
|
|
|
|
|
// 填充表单数据 - 注意:subItemName 和 projectName 不应该从这里获取,而应该从 initFormData 中的 getPackageProjectNames 获取
|
|
|
this.formData = {
|
|
|
...this.formData,
|
|
|
implementationSchedule: data.implementation_schedule || '',
|
|
|
implementationScheduleDetail: {
|
|
|
currentYearContent: data.implementation_current_year_content || '',
|
|
|
currentYearAmount: parseFloat(data.implementation_current_year_amount) || 0,
|
|
|
currentYearMonth1: parseFloat(data.implementation_current_year_month_1) || 0,
|
|
|
currentYearMonth2: parseFloat(data.implementation_current_year_month_2) || 0,
|
|
|
currentYearMonth3: parseFloat(data.implementation_current_year_month_3) || 0,
|
|
|
currentYearMonth4: parseFloat(data.implementation_current_year_month_4) || 0,
|
|
|
currentYearMonth5: parseFloat(data.implementation_current_year_month_5) || 0,
|
|
|
currentYearMonth6: parseFloat(data.implementation_current_year_month_6) || 0,
|
|
|
currentYearMonth7: parseFloat(data.implementation_current_year_month_7) || 0,
|
|
|
currentYearMonth8: parseFloat(data.implementation_current_year_month_8) || 0,
|
|
|
currentYearMonth9: parseFloat(data.implementation_current_year_month_9) || 0,
|
|
|
currentYearMonth10: parseFloat(data.implementation_current_year_month_10) || 0,
|
|
|
currentYearMonth11: parseFloat(data.implementation_current_year_month_11) || 0,
|
|
|
currentYearMonth12: parseFloat(data.implementation_current_year_month_12) || 0,
|
|
|
nextYearContent: data.implementation_next_year_content || '',
|
|
|
thirdYearContent: data.implementation_third_year_content || ''
|
|
|
},
|
|
|
performanceIndicators: this.processPerformanceIndicatorsData(data.performance_indicators || []),
|
|
|
otherNotes: data.other_notes || '',
|
|
|
generalFiles: data.general_files || [] // 填充附件列表
|
|
|
}
|
|
|
|
|
|
// 数据加载完成后,检查并修复当年金额与月度金额的一致性
|
|
|
this.$nextTick(() => {
|
|
|
this.checkAndFixCurrentYearAmount()
|
|
|
})
|
|
|
|
|
|
// 填充申请资金表格数据
|
|
|
if (data.new_project_total_amount !== undefined) {
|
|
|
this.fundApplicationData[0].newProject.totalAmount = data.new_project_total_amount || 0
|
|
|
this.fundApplicationData[0].newProject.current = data.new_project_year_current || 0
|
|
|
this.fundApplicationData[0].newProject.next = data.new_project_year_next || 0
|
|
|
this.fundApplicationData[0].newProject.hasFixedAssets = data.new_project_has_fixed_assets || false
|
|
|
this.fundApplicationData[0].newProject.noFixedAssets = !data.new_project_has_fixed_assets
|
|
|
this.fundApplicationData[0].oldProject.contractTotal = data.old_project_contract_total || 0
|
|
|
this.fundApplicationData[0].oldProject.previousYearsTotal = data.old_project_previous_years_total || 0
|
|
|
this.fundApplicationData[0].oldProject.previousYearBalance = data.old_project_previous_year_balance || 0
|
|
|
this.fundApplicationData[0].oldProject.contractNumber = data.old_project_contract_number || ''
|
|
|
this.fundApplicationData[0].oldProject.current = data.old_project_year_current || 0
|
|
|
this.fundApplicationData[0].oldProject.hasFixedAssets = data.old_project_has_fixed_assets || false
|
|
|
this.fundApplicationData[0].oldProject.noFixedAssets = !data.old_project_has_fixed_assets
|
|
|
}
|
|
|
|
|
|
// 填充经济分类数据
|
|
|
if (data.economic_items && data.economic_items.length > 0) {
|
|
|
data.economic_items.forEach(item => {
|
|
|
const category = this.economicCategoriesWithData.find(cat => cat.categoryId === item.categoryId)
|
|
|
if (category) {
|
|
|
category.selected = true
|
|
|
category.amount = item.amount
|
|
|
category.calculationBasis = item.calculationBasis
|
|
|
// 加载附件信息
|
|
|
if (item.files && item.files.length > 0) {
|
|
|
category.fileList = item.files.map(file => ({
|
|
|
uid: file.id,
|
|
|
name: file.original_name || file.name,
|
|
|
url: file.url,
|
|
|
fileId: file.id
|
|
|
}))
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
|
|
|
// 加载通用附件信息
|
|
|
if (data.general_files && data.general_files.length > 0) {
|
|
|
this.formData.generalFiles = data.general_files.map(file => ({
|
|
|
uid: file.id,
|
|
|
name: file.original_name || file.name,
|
|
|
url: file.url,
|
|
|
fileId: file.id
|
|
|
}))
|
|
|
}
|
|
|
} else if (response && response.errcode) {
|
|
|
// 静默处理,只在控制台记录
|
|
|
console.warn('加载分解表数据失败:', response.errmsg)
|
|
|
// 只有在编辑或查看模式下且不是权限问题时才显示错误
|
|
|
if (this.mode !== 'create' && !response.errmsg.includes('权限')) {
|
|
|
this.$message.warning('无法加载已有的分解表数据,将使用空白表单')
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 加载完现有数据后,处理必填指标
|
|
|
if (this.requiredIndicators.length > 0) {
|
|
|
this.addRequiredIndicators()
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.warn('加载分解表数据失败:', error.message)
|
|
|
// 静默处理HTTP错误,不显示任何提示
|
|
|
|
|
|
// 即使加载失败,也要处理必填指标
|
|
|
if (this.requiredIndicators.length > 0) {
|
|
|
this.addRequiredIndicators()
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// 处理绩效指标数据,为已有数据添加必要的标识
|
|
|
processPerformanceIndicatorsData(performanceIndicators) {
|
|
|
return performanceIndicators.map(item => ({
|
|
|
...item,
|
|
|
isRequired: false, // 先设置为非必填,后续会根据实际情况更新
|
|
|
indicatorId: item.indicator_id || null
|
|
|
}))
|
|
|
},
|
|
|
|
|
|
onSelectAllChange(value) {
|
|
|
this.economicCategoriesWithData.forEach(category => {
|
|
|
category.selected = value
|
|
|
})
|
|
|
},
|
|
|
|
|
|
onEconomicItemToggle(item, index) {
|
|
|
// 清空数据当取消选择时
|
|
|
if (!item.selected) {
|
|
|
item.amount = ''
|
|
|
item.calculationBasis = ''
|
|
|
}
|
|
|
|
|
|
// 更新全选状态
|
|
|
this.updateSelectAllState()
|
|
|
},
|
|
|
|
|
|
updateSelectAllState() {
|
|
|
const selectedCount = this.economicCategoriesWithData.filter(item => item.selected).length
|
|
|
this.selectAll = selectedCount === this.economicCategoriesWithData.length && selectedCount > 0
|
|
|
},
|
|
|
|
|
|
// 申请资金表格相关方法
|
|
|
calculateNewProjectTotal() {
|
|
|
// 新项目总额 = 当年 + 次年
|
|
|
const current = parseFloat(this.fundApplicationData[0].newProject.current) || 0
|
|
|
const next = parseFloat(this.fundApplicationData[0].newProject.next) || 0
|
|
|
this.fundApplicationData[0].newProject.totalAmount = current + next
|
|
|
},
|
|
|
|
|
|
validateNewProjectAmounts() {
|
|
|
this.calculateNewProjectTotal()
|
|
|
},
|
|
|
|
|
|
onNewProjectFixedAssetsChange() {
|
|
|
// 确保只能选择一个选项
|
|
|
if (this.fundApplicationData[0].newProject.hasFixedAssets) {
|
|
|
this.fundApplicationData[0].newProject.noFixedAssets = false
|
|
|
}
|
|
|
if (this.fundApplicationData[0].newProject.noFixedAssets) {
|
|
|
this.fundApplicationData[0].newProject.hasFixedAssets = false
|
|
|
}
|
|
|
},
|
|
|
|
|
|
onOldProjectFixedAssetsChange() {
|
|
|
// 确保只能选择一个选项
|
|
|
if (this.fundApplicationData[0].oldProject.hasFixedAssets) {
|
|
|
this.fundApplicationData[0].oldProject.noFixedAssets = false
|
|
|
}
|
|
|
if (this.fundApplicationData[0].oldProject.noFixedAssets) {
|
|
|
this.fundApplicationData[0].oldProject.hasFixedAssets = false
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// 项目周期(一次性、阶段性、长期性)相关方法
|
|
|
calculateCurrentYearTotal() {
|
|
|
// 当年金额 = 1月到12月的总和
|
|
|
this.formData.implementationScheduleDetail.currentYearAmount = this.currentYearMonthlyTotal
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* 检查并修复当年金额与月度金额的一致性
|
|
|
* 在修改模式下,如果当年金额为0但月度金额有值,则自动计算当年金额
|
|
|
*/
|
|
|
checkAndFixCurrentYearAmount() {
|
|
|
const monthlyTotal = this.currentYearMonthlyTotal
|
|
|
const currentYearAmount = this.formData.implementationScheduleDetail.currentYearAmount
|
|
|
|
|
|
// 如果当年金额为0但月度金额有值,则自动设置当年金额
|
|
|
if (currentYearAmount === 0 && monthlyTotal > 0) {
|
|
|
this.formData.implementationScheduleDetail.currentYearAmount = monthlyTotal
|
|
|
console.log('自动修复当年金额:', monthlyTotal)
|
|
|
}
|
|
|
// 如果当年金额与月度金额不一致,给出提示但不强制修复
|
|
|
else if (Math.abs(monthlyTotal - currentYearAmount) > 0.01) {
|
|
|
console.warn('当年金额与月度金额不一致:', {
|
|
|
currentYearAmount,
|
|
|
monthlyTotal,
|
|
|
difference: Math.abs(monthlyTotal - currentYearAmount)
|
|
|
})
|
|
|
}
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* 当年金额变化时的处理
|
|
|
*/
|
|
|
onCurrentYearAmountChange() {
|
|
|
// 当年金额变化时,不自动修改月度金额,让用户手动调整
|
|
|
// 这样可以避免覆盖用户已填写的数据
|
|
|
console.log('当年金额已更新:', this.formData.implementationScheduleDetail.currentYearAmount)
|
|
|
},
|
|
|
|
|
|
// 加载绩效指标数据
|
|
|
async loadPerformanceIndicators() {
|
|
|
try {
|
|
|
console.log('开始加载绩效指标数据...')
|
|
|
|
|
|
// 获取完整的指标树
|
|
|
const treeResponse = await getPerformanceIndicatorTree()
|
|
|
console.log('指标树响应:', treeResponse)
|
|
|
|
|
|
if (treeResponse) {
|
|
|
this.performanceIndicatorTree = treeResponse || []
|
|
|
this.processIndicatorTree(treeResponse || [])
|
|
|
console.log('指标树处理完成:', this.level1Options.length, '个一级指标')
|
|
|
} else {
|
|
|
console.error('获取指标树失败:', treeResponse)
|
|
|
this.performanceIndicatorTree = []
|
|
|
}
|
|
|
|
|
|
// 获取必须填报的指标
|
|
|
const requiredResponse = await getRequiredPerformanceIndicators()
|
|
|
console.log('必填指标响应:', requiredResponse)
|
|
|
|
|
|
if (requiredResponse) {
|
|
|
this.requiredIndicators = requiredResponse || []
|
|
|
this.initRequiredIndicators()
|
|
|
console.log('必填指标处理完成:', this.requiredIndicators.length, '个必填指标')
|
|
|
} else {
|
|
|
console.error('获取必填指标失败:', requiredResponse)
|
|
|
this.requiredIndicators = [] // 确保即使失败也有默认值
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error('加载绩效指标数据失败:', error)
|
|
|
// 确保异常情况下也有默认值
|
|
|
if (!Array.isArray(this.requiredIndicators)) {
|
|
|
this.requiredIndicators = []
|
|
|
}
|
|
|
if (!Array.isArray(this.performanceIndicatorTree)) {
|
|
|
this.performanceIndicatorTree = []
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// 处理指标树数据,构建选项映射
|
|
|
processIndicatorTree(tree) {
|
|
|
if (!Array.isArray(tree)) {
|
|
|
console.error('指标树数据格式错误:', tree)
|
|
|
return
|
|
|
}
|
|
|
|
|
|
this.level1Options = tree.map(item => ({
|
|
|
id: item.id,
|
|
|
name: item.name,
|
|
|
required: item.required || false
|
|
|
}))
|
|
|
|
|
|
console.log('一级指标选项:', this.level1Options)
|
|
|
|
|
|
// 构建二级和三级选项映射
|
|
|
tree.forEach(level1 => {
|
|
|
if (level1.children && Array.isArray(level1.children)) {
|
|
|
this.level2OptionsMap[level1.id] = level1.children.map(item => ({
|
|
|
id: item.id,
|
|
|
name: item.name,
|
|
|
required: item.required || false
|
|
|
}))
|
|
|
|
|
|
level1.children.forEach(level2 => {
|
|
|
if (level2.children && Array.isArray(level2.children)) {
|
|
|
this.level3OptionsMap[level2.id] = level2.children.map(item => ({
|
|
|
id: item.id,
|
|
|
name: item.name,
|
|
|
required: item.required || false
|
|
|
}))
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
})
|
|
|
|
|
|
console.log('二级选项映射:', this.level2OptionsMap)
|
|
|
console.log('三级选项映射:', this.level3OptionsMap)
|
|
|
},
|
|
|
|
|
|
// 初始化必须填报的指标
|
|
|
initRequiredIndicators() {
|
|
|
// 在编辑模式下,先不初始化必填指标,等待loadExistingData完成后再处理
|
|
|
if (this.mode === 'edit' || this.mode === 'view') {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
this.addRequiredIndicators()
|
|
|
},
|
|
|
|
|
|
// 添加必须填报的指标
|
|
|
addRequiredIndicators() {
|
|
|
console.log('开始处理必填指标,总数:', this.requiredIndicators.length)
|
|
|
|
|
|
if (!Array.isArray(this.requiredIndicators) || this.requiredIndicators.length === 0) {
|
|
|
console.log('没有必填指标需要处理')
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// 获取三级必填指标
|
|
|
const requiredLevel3Indicators = this.requiredIndicators.filter(indicator => {
|
|
|
return indicator.required && this.isLevel3Indicator(indicator)
|
|
|
})
|
|
|
|
|
|
console.log('找到的三级必填指标:', requiredLevel3Indicators)
|
|
|
|
|
|
requiredLevel3Indicators.forEach(indicator => {
|
|
|
const level1 = this.findLevel1ByIndicatorId(indicator.id)
|
|
|
const level2 = this.findLevel2ByIndicatorId(indicator.id)
|
|
|
|
|
|
console.log(`指标 ${indicator.name} 的层级信息:`, { level1: level1?.name, level2: level2?.name })
|
|
|
|
|
|
if (level1 && level2) {
|
|
|
// 检查是否已存在相同的指标(通过指标ID或者三个层级名称匹配)
|
|
|
const existingIndex = this.formData.performanceIndicators.findIndex(item =>
|
|
|
item.indicatorId === indicator.id ||
|
|
|
(item.level1 === level1.name && item.level2 === level2.name && item.level3 === indicator.name)
|
|
|
)
|
|
|
|
|
|
if (existingIndex === -1) {
|
|
|
// 不存在,添加到最前面
|
|
|
const newIndicator = {
|
|
|
level1: level1.name,
|
|
|
level2: level2.name,
|
|
|
level3: indicator.name,
|
|
|
symbol: '',
|
|
|
halfYearValue: '',
|
|
|
fullYearValue: '',
|
|
|
unit: '',
|
|
|
isRequired: true,
|
|
|
indicatorId: indicator.id
|
|
|
}
|
|
|
this.formData.performanceIndicators.unshift(newIndicator)
|
|
|
console.log('添加新的必填指标:', newIndicator)
|
|
|
} else {
|
|
|
// 已存在,标记为必填并移到前面
|
|
|
const existingItem = this.formData.performanceIndicators[existingIndex]
|
|
|
existingItem.isRequired = true
|
|
|
existingItem.indicatorId = indicator.id
|
|
|
|
|
|
// 移动到最前面
|
|
|
this.formData.performanceIndicators.splice(existingIndex, 1)
|
|
|
this.formData.performanceIndicators.unshift(existingItem)
|
|
|
console.log('更新现有指标为必填:', existingItem)
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
|
|
|
console.log('必填指标处理完成,当前指标总数:', this.formData.performanceIndicators.length)
|
|
|
},
|
|
|
|
|
|
// 判断是否为三级指标
|
|
|
isLevel3Indicator(indicator) {
|
|
|
if (!indicator || !indicator.id) {
|
|
|
return false
|
|
|
}
|
|
|
|
|
|
// 通过在performanceIndicatorTree中查找该指标来判断层级
|
|
|
if (!Array.isArray(this.performanceIndicatorTree)) {
|
|
|
return false
|
|
|
}
|
|
|
|
|
|
for (const level1 of this.performanceIndicatorTree) {
|
|
|
if (level1.children && Array.isArray(level1.children)) {
|
|
|
for (const level2 of level1.children) {
|
|
|
if (level2.children && Array.isArray(level2.children)) {
|
|
|
for (const level3 of level2.children) {
|
|
|
if (level3.id === indicator.id) {
|
|
|
return true
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return false
|
|
|
},
|
|
|
|
|
|
// 根据指标ID查找一级指标
|
|
|
findLevel1ByIndicatorId(indicatorId) {
|
|
|
if (!Array.isArray(this.performanceIndicatorTree) || !indicatorId) {
|
|
|
return null
|
|
|
}
|
|
|
|
|
|
for (const level1 of this.performanceIndicatorTree) {
|
|
|
if (level1.children && Array.isArray(level1.children)) {
|
|
|
for (const level2 of level1.children) {
|
|
|
if (level2.children && Array.isArray(level2.children)) {
|
|
|
for (const level3 of level2.children) {
|
|
|
if (level3.id === indicatorId) {
|
|
|
return level1
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return null
|
|
|
},
|
|
|
|
|
|
// 根据指标ID查找二级指标
|
|
|
findLevel2ByIndicatorId(indicatorId) {
|
|
|
if (!Array.isArray(this.performanceIndicatorTree) || !indicatorId) {
|
|
|
return null
|
|
|
}
|
|
|
|
|
|
for (const level1 of this.performanceIndicatorTree) {
|
|
|
if (level1.children && Array.isArray(level1.children)) {
|
|
|
for (const level2 of level1.children) {
|
|
|
if (level2.children && Array.isArray(level2.children)) {
|
|
|
for (const level3 of level2.children) {
|
|
|
if (level3.id === indicatorId) {
|
|
|
return level2
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return null
|
|
|
},
|
|
|
|
|
|
// 获取过滤后的一级选项(排除已被必填指标占用的)
|
|
|
getFilteredLevel1Options() {
|
|
|
if (!Array.isArray(this.level1Options)) {
|
|
|
return []
|
|
|
}
|
|
|
|
|
|
// 获取所有必填指标的一级指标名称
|
|
|
const usedLevel1Names = this.formData.performanceIndicators
|
|
|
.filter(item => item.isRequired && item.level1)
|
|
|
.map(item => item.level1)
|
|
|
|
|
|
return this.level1Options.filter(option => !usedLevel1Names.includes(option.name))
|
|
|
},
|
|
|
|
|
|
// 获取过滤后的二级选项
|
|
|
getFilteredLevel2Options(level1Name) {
|
|
|
if (!level1Name || !Array.isArray(this.level1Options)) {
|
|
|
return []
|
|
|
}
|
|
|
|
|
|
const level1 = this.level1Options.find(item => item.name === level1Name)
|
|
|
if (!level1 || !this.level2OptionsMap[level1.id]) {
|
|
|
return []
|
|
|
}
|
|
|
|
|
|
// 获取所有必填指标中该一级指标下已使用的二级指标名称
|
|
|
const usedLevel2Names = this.formData.performanceIndicators
|
|
|
.filter(item => item.isRequired && item.level1 === level1Name && item.level2)
|
|
|
.map(item => item.level2)
|
|
|
|
|
|
return this.level2OptionsMap[level1.id].filter(option => !usedLevel2Names.includes(option.name))
|
|
|
},
|
|
|
|
|
|
// 获取过滤后的三级选项
|
|
|
getFilteredLevel3Options(level1Name, level2Name) {
|
|
|
if (!level1Name || !level2Name || !Array.isArray(this.level1Options)) {
|
|
|
return []
|
|
|
}
|
|
|
|
|
|
const level1 = this.level1Options.find(item => item.name === level1Name)
|
|
|
if (!level1 || !this.level2OptionsMap[level1.id]) {
|
|
|
return []
|
|
|
}
|
|
|
|
|
|
const level2Options = this.level2OptionsMap[level1.id]
|
|
|
const level2 = level2Options.find(item => item.name === level2Name)
|
|
|
if (!level2 || !this.level3OptionsMap[level2.id]) {
|
|
|
return []
|
|
|
}
|
|
|
|
|
|
// 获取所有必填指标中该二级指标下已使用的三级指标名称
|
|
|
const usedLevel3Names = this.formData.performanceIndicators
|
|
|
.filter(item => item.isRequired && item.level1 === level1Name && item.level2 === level2Name && item.level3)
|
|
|
.map(item => item.level3)
|
|
|
|
|
|
return this.level3OptionsMap[level2.id].filter(option => !usedLevel3Names.includes(option.name))
|
|
|
},
|
|
|
|
|
|
// 获取二级选项(保留原方法以兼容性)
|
|
|
getLevel2Options(level1Name) {
|
|
|
return this.getFilteredLevel2Options(level1Name)
|
|
|
},
|
|
|
|
|
|
// 获取三级选项(保留原方法以兼容性)
|
|
|
getLevel3Options(level1Name, level2Name) {
|
|
|
return this.getFilteredLevel3Options(level1Name, level2Name)
|
|
|
},
|
|
|
|
|
|
// 一级指标变化时清空下级选项
|
|
|
onLevel1Change(index) {
|
|
|
const indicator = this.formData.performanceIndicators[index]
|
|
|
indicator.level2 = ''
|
|
|
indicator.level3 = ''
|
|
|
// 强制刷新组件以更新下拉选项
|
|
|
this.$forceUpdate()
|
|
|
},
|
|
|
|
|
|
// 二级指标变化时清空三级选项
|
|
|
onLevel2Change(index) {
|
|
|
const indicator = this.formData.performanceIndicators[index]
|
|
|
indicator.level3 = ''
|
|
|
// 强制刷新组件以更新三级选项
|
|
|
this.$forceUpdate()
|
|
|
},
|
|
|
|
|
|
// 获取表格行的样式类名
|
|
|
getRowClassName({row, rowIndex}) {
|
|
|
return row.isRequired ? 'required-row' : ''
|
|
|
},
|
|
|
|
|
|
addPerformanceIndicator() {
|
|
|
this.formData.performanceIndicators.push({
|
|
|
level1: '',
|
|
|
level2: '',
|
|
|
level3: '',
|
|
|
symbol: '',
|
|
|
halfYearValue: '',
|
|
|
fullYearValue: '',
|
|
|
unit: '',
|
|
|
isRequired: false,
|
|
|
indicatorId: null
|
|
|
})
|
|
|
},
|
|
|
|
|
|
removePerformanceIndicator(index) {
|
|
|
// 如果是必填指标,不允许删除
|
|
|
const indicator = this.formData.performanceIndicators[index]
|
|
|
if (indicator.isRequired) {
|
|
|
this.$message.warning('必填指标不可删除')
|
|
|
return
|
|
|
}
|
|
|
this.formData.performanceIndicators.splice(index, 1)
|
|
|
},
|
|
|
|
|
|
validateTotal() {
|
|
|
// 触发计算属性重新计算
|
|
|
this.$forceUpdate()
|
|
|
},
|
|
|
|
|
|
getSummaries(param) {
|
|
|
const { columns, data } = param
|
|
|
const sums = []
|
|
|
columns.forEach((column, index) => {
|
|
|
if (index === 0 || (index === 1 && !this.isViewMode)) {
|
|
|
sums[index] = '合计'
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// 金额列的索引(考虑是否有选择列)
|
|
|
const amountColumnIndex = this.isViewMode ? 2 : 3
|
|
|
if (index === amountColumnIndex) {
|
|
|
const values = data.filter(item => item.selected).map(item => Number(item.amount) || 0)
|
|
|
const total = values.reduce((prev, curr) => {
|
|
|
const value = Number(curr)
|
|
|
if (!isNaN(value)) {
|
|
|
return prev + curr
|
|
|
} else {
|
|
|
return prev
|
|
|
}
|
|
|
}, 0)
|
|
|
sums[index] = this.formatCurrency(total)
|
|
|
} else {
|
|
|
sums[index] = ''
|
|
|
}
|
|
|
})
|
|
|
return sums
|
|
|
},
|
|
|
|
|
|
formatCurrency(amount) {
|
|
|
if (amount === null || amount === undefined || amount === '') return '0.00'
|
|
|
const num = Number(amount)
|
|
|
return isFinite(num) ? num.toFixed(1) : '0.0'
|
|
|
},
|
|
|
|
|
|
submit() {
|
|
|
this.$refs.formRef.validate((valid) => {
|
|
|
if (valid) {
|
|
|
// 获取选中的经济分类项目
|
|
|
const selectedItems = this.economicCategoriesWithData.filter(item => item.selected)
|
|
|
|
|
|
// 1. 验证经济分类项目选择
|
|
|
if (selectedItems.length === 0) {
|
|
|
this.$message.error('请至少选择一个经济分类项目')
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// 2. 验证经济分类项目完整性
|
|
|
const invalidItems = selectedItems.filter(item => {
|
|
|
const amount = parseFloat(item.amount)
|
|
|
return item.amount === '' ||
|
|
|
item.amount === null ||
|
|
|
item.amount === undefined ||
|
|
|
isNaN(amount) ||
|
|
|
amount < 0 ||
|
|
|
!item.calculationBasis ||
|
|
|
item.calculationBasis.trim() === ''
|
|
|
})
|
|
|
|
|
|
if (invalidItems.length > 0) {
|
|
|
this.$message.error('请完善所有选中经济分类项目的信息(金额必须为有效数字且不能小于0,测算依据不能为空)')
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// 3. 第一阶段不限制金额分配验证
|
|
|
// 金额分配验证已移除,允许用户自由填写
|
|
|
|
|
|
// 4. 验证绩效指标完整性
|
|
|
const invalidPerformanceItems = this.formData.performanceIndicators.filter(item =>
|
|
|
!item.level1 ||
|
|
|
!item.level2 ||
|
|
|
!item.level3 ||
|
|
|
!item.symbol ||
|
|
|
(!item.halfYearValue && !item.fullYearValue)
|
|
|
)
|
|
|
|
|
|
if (invalidPerformanceItems.length > 0) {
|
|
|
this.$message.error('请完善所有绩效指标的信息(一级指标、二级指标、三级指标、计算符号必填,半年或全年指标值至少填写一个)')
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// 5. 验证项目周期(一次性、阶段性、长期性)字数
|
|
|
if (this.formData.implementationSchedule && this.formData.implementationSchedule.length > 1000) {
|
|
|
this.$message.error('项目周期(一次性、阶段性、长期性)不能超过1000个字符')
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// 6. 验证其他说明事项字数
|
|
|
if (this.formData.otherNotes && this.formData.otherNotes.length > 500) {
|
|
|
this.$message.error('其他需要说明事项不能超过500个字符')
|
|
|
return
|
|
|
}
|
|
|
|
|
|
|
|
|
// 构造提交数据 - 只提交必要字段
|
|
|
const submitData = {
|
|
|
packageId: this.formData.packageId,
|
|
|
implementationSchedule: this.formData.implementationSchedule.trim(),
|
|
|
implementationScheduleDetail: this.formData.implementationScheduleDetail,
|
|
|
otherNotes: this.formData.otherNotes ? this.formData.otherNotes.trim() : '',
|
|
|
economicItems: selectedItems.map(item => ({
|
|
|
categoryId: item.categoryId,
|
|
|
amount: parseFloat(item.amount),
|
|
|
calculationBasis: item.calculationBasis.trim(),
|
|
|
fileIds: item.fileList ? item.fileList.map(f => f.fileId).filter(Boolean) : [] // 提交文件ID列表
|
|
|
})),
|
|
|
performanceIndicators: this.formData.performanceIndicators.map(item => ({
|
|
|
level1: item.level1,
|
|
|
level2: item.level2,
|
|
|
level3: item.level3.trim(),
|
|
|
symbol: item.symbol,
|
|
|
halfYearValue: item.halfYearValue ? item.halfYearValue.trim() : '',
|
|
|
fullYearValue: item.fullYearValue ? item.fullYearValue.trim() : '',
|
|
|
unit: item.unit ? item.unit.trim() : ''
|
|
|
})),
|
|
|
// 修复字段映射问题 - 将前端字段映射为后端期望的字段名
|
|
|
fundApplication: {
|
|
|
newProject: {
|
|
|
totalAmount: this.fundApplicationData[0].newProject.totalAmount,
|
|
|
yearCurrent: this.fundApplicationData[0].newProject.current, // current -> yearCurrent
|
|
|
yearNext: this.fundApplicationData[0].newProject.next, // next -> yearNext
|
|
|
hasFixedAssets: this.fundApplicationData[0].newProject.hasFixedAssets
|
|
|
},
|
|
|
oldProject: {
|
|
|
contractTotal: this.fundApplicationData[0].oldProject.contractTotal,
|
|
|
previousYearsTotal: this.fundApplicationData[0].oldProject.previousYearsTotal,
|
|
|
previousYearBalance: this.fundApplicationData[0].oldProject.previousYearBalance,
|
|
|
contractNumber: this.fundApplicationData[0].oldProject.contractNumber,
|
|
|
yearCurrent: this.fundApplicationData[0].oldProject.current, // current -> yearCurrent
|
|
|
hasFixedAssets: this.fundApplicationData[0].oldProject.hasFixedAssets
|
|
|
}
|
|
|
},
|
|
|
generalFileIds: this.formData.generalFiles ? this.formData.generalFiles.map(f => f.fileId).filter(Boolean) : [] // 提交通用附件ID列表
|
|
|
}
|
|
|
|
|
|
this.$emit('submit', submitData)
|
|
|
} else {
|
|
|
this.$message.error('请完善必填的表单信息')
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
|
|
|
cancel() {
|
|
|
this.$emit('cancel')
|
|
|
},
|
|
|
|
|
|
// 附件上传相关方法
|
|
|
handleUploadSuccess(response, file, fileList, row) {
|
|
|
// response 本身就是处理后的数据,包含文件ID
|
|
|
console.log('response', response)
|
|
|
if (response && response.data) {
|
|
|
// 保存文件信息到行数据
|
|
|
row.fileList = fileList.map(f => ({
|
|
|
...f,
|
|
|
fileId: f.response.data.id
|
|
|
}))
|
|
|
this.$message.success('附件上传成功')
|
|
|
} else {
|
|
|
this.$message.error('附件上传失败')
|
|
|
}
|
|
|
},
|
|
|
|
|
|
handleUploadRemove(file, fileList, row) {
|
|
|
// 从文件列表中移除
|
|
|
const index = fileList.findIndex(f => f.uid === file.uid)
|
|
|
if (index !== -1) {
|
|
|
fileList.splice(index, 1)
|
|
|
row.fileList = fileList
|
|
|
}
|
|
|
},
|
|
|
|
|
|
handleGeneralUploadSuccess(response, file, fileList) {
|
|
|
// response 本身就是处理后的数据,包含文件ID
|
|
|
if (response && response.data) {
|
|
|
// 保存文件信息
|
|
|
this.formData.generalFiles = fileList.map(f => ({
|
|
|
...f,
|
|
|
fileId: f.response.data.id
|
|
|
}))
|
|
|
this.$message.success('附件上传成功')
|
|
|
} else {
|
|
|
this.$message.error('附件上传失败')
|
|
|
}
|
|
|
},
|
|
|
|
|
|
handleGeneralUploadRemove(file, fileList) {
|
|
|
// 从文件列表中移除
|
|
|
const index = fileList.findIndex(f => f.uid === file.uid)
|
|
|
if (index !== -1) {
|
|
|
fileList.splice(index, 1)
|
|
|
this.formData.generalFiles = fileList
|
|
|
}
|
|
|
},
|
|
|
|
|
|
handleUploadError(err, file, fileList) {
|
|
|
console.error('附件上传失败:', err)
|
|
|
this.$message.error('附件上传失败,请检查网络或文件大小')
|
|
|
},
|
|
|
|
|
|
// 打开文件方法
|
|
|
openFile(fileInfo) {
|
|
|
if (!fileInfo.url) {
|
|
|
this.$message.warning('文件链接不存在,无法打开')
|
|
|
return
|
|
|
}
|
|
|
try {
|
|
|
window.open(fileInfo.url, '_blank')
|
|
|
} catch (error) {
|
|
|
console.error('打开文件失败:', error)
|
|
|
this.$message.error('打开文件失败')
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// 文件下载方法
|
|
|
downloadFile(fileInfo) {
|
|
|
if (!fileInfo.url) {
|
|
|
this.$message.warning('文件链接不存在,无法下载')
|
|
|
return
|
|
|
}
|
|
|
try {
|
|
|
const fileName = fileInfo.name || fileInfo.original_name || '附件'
|
|
|
const a = document.createElement('a')
|
|
|
a.setAttribute('href', fileInfo.url)
|
|
|
a.setAttribute('download', fileName)
|
|
|
a.setAttribute('target', '_blank')
|
|
|
a.click()
|
|
|
} catch (error) {
|
|
|
console.error('下载文件失败:', error)
|
|
|
this.$message.error('下载文件失败')
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// 根据文件名获取文件图标
|
|
|
getFileIcon(fileName) {
|
|
|
if (!fileName) return 'el-icon-document'
|
|
|
|
|
|
const extension = fileName.split('.').pop().toLowerCase()
|
|
|
const iconMap = {
|
|
|
pdf: 'el-icon-document',
|
|
|
doc: 'el-icon-document-copy',
|
|
|
docx: 'el-icon-document-copy',
|
|
|
xls: 'el-icon-s-grid',
|
|
|
xlsx: 'el-icon-s-grid',
|
|
|
jpg: 'el-icon-picture',
|
|
|
jpeg: 'el-icon-picture',
|
|
|
png: 'el-icon-picture',
|
|
|
gif: 'el-icon-picture',
|
|
|
zip: 'el-icon-folder-opened',
|
|
|
rar: 'el-icon-folder-opened',
|
|
|
txt: 'el-icon-document',
|
|
|
default: 'el-icon-document'
|
|
|
}
|
|
|
return iconMap[extension] || iconMap.default
|
|
|
},
|
|
|
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
.budget-submission-form {
|
|
|
padding: 20px;
|
|
|
max-height: 75vh;
|
|
|
overflow-y: auto;
|
|
|
}
|
|
|
|
|
|
.form-section {
|
|
|
margin-bottom: 30px;
|
|
|
border: 1px solid #ebeef5;
|
|
|
border-radius: 4px;
|
|
|
padding: 20px;
|
|
|
}
|
|
|
|
|
|
.section-title {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
font-size: 16px;
|
|
|
font-weight: bold;
|
|
|
color: #303133;
|
|
|
margin-bottom: 20px;
|
|
|
padding-bottom: 8px;
|
|
|
border-bottom: 2px solid #409EFF;
|
|
|
}
|
|
|
|
|
|
.section-title i {
|
|
|
margin-right: 8px;
|
|
|
color: #409EFF;
|
|
|
}
|
|
|
|
|
|
/* 申请信息只读展示样式 */
|
|
|
.info-display-item {
|
|
|
margin-bottom: 16px;
|
|
|
}
|
|
|
|
|
|
.info-label {
|
|
|
font-weight: bold;
|
|
|
color: #606266;
|
|
|
margin-bottom: 8px;
|
|
|
display: inline-block;
|
|
|
}
|
|
|
|
|
|
.info-content {
|
|
|
color: #303133;
|
|
|
line-height: 1.6;
|
|
|
padding: 8px 12px;
|
|
|
background-color: #f5f7fa;
|
|
|
border-radius: 4px;
|
|
|
min-height: 20px;
|
|
|
}
|
|
|
|
|
|
|
|
|
.economic-table-container,
|
|
|
.performance-table-container {
|
|
|
margin: 15px 0;
|
|
|
}
|
|
|
|
|
|
.fund-application-table-container {
|
|
|
margin: 15px 0;
|
|
|
overflow-x: auto;
|
|
|
width: 100%;
|
|
|
}
|
|
|
|
|
|
.fund-application-table-container .el-table {
|
|
|
width: 100% !important;
|
|
|
min-width: 1200px; /* 设置最小宽度确保内容不被压缩 */
|
|
|
}
|
|
|
|
|
|
.fund-application-table {
|
|
|
font-size: 14px;
|
|
|
width: 100%;
|
|
|
}
|
|
|
|
|
|
.implementation-schedule-table-container {
|
|
|
margin: 15px 0;
|
|
|
overflow-x: auto;
|
|
|
width: 100%;
|
|
|
}
|
|
|
|
|
|
.implementation-schedule-table-container .el-table {
|
|
|
width: 100% !important;
|
|
|
min-width: 1400px; /* 设置最小宽度确保内容不被压缩 */
|
|
|
}
|
|
|
|
|
|
.implementation-schedule-table {
|
|
|
font-size: 14px;
|
|
|
width: 100%;
|
|
|
}
|
|
|
|
|
|
.economic-table,
|
|
|
.performance-table,
|
|
|
.fund-application-table {
|
|
|
font-size: 14px;
|
|
|
}
|
|
|
|
|
|
.economic-table .el-input,
|
|
|
.performance-table .el-input,
|
|
|
.fund-application-table .el-input {
|
|
|
font-size: 12px;
|
|
|
}
|
|
|
|
|
|
.amount-warning,
|
|
|
.fund-warning {
|
|
|
margin-top: 10px;
|
|
|
padding: 10px;
|
|
|
background-color: #fdf6ec;
|
|
|
border: 1px solid #f5dab1;
|
|
|
border-radius: 4px;
|
|
|
color: #e6a23c;
|
|
|
}
|
|
|
|
|
|
.fund-info {
|
|
|
margin-top: 10px;
|
|
|
padding: 10px;
|
|
|
background-color: #f0f9ff;
|
|
|
border: 1px solid #b3d8ff;
|
|
|
border-radius: 4px;
|
|
|
color: #409EFF;
|
|
|
}
|
|
|
|
|
|
.section-subtitle {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
font-size: 14px;
|
|
|
font-weight: bold;
|
|
|
color: #606266;
|
|
|
margin-bottom: 15px;
|
|
|
padding-bottom: 5px;
|
|
|
border-bottom: 1px solid #e4e7ed;
|
|
|
}
|
|
|
|
|
|
.section-subtitle i {
|
|
|
margin-right: 8px;
|
|
|
color: #909399;
|
|
|
}
|
|
|
|
|
|
.monthly-detail-table-container {
|
|
|
margin: 15px 0;
|
|
|
overflow-x: auto;
|
|
|
width: 100%;
|
|
|
}
|
|
|
|
|
|
.monthly-detail-table-container .el-table {
|
|
|
width: 100% !important;
|
|
|
min-width: 1200px;
|
|
|
}
|
|
|
|
|
|
.monthly-detail-table {
|
|
|
font-size: 14px;
|
|
|
width: 100%;
|
|
|
}
|
|
|
|
|
|
.file-item {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
margin-bottom: 5px;
|
|
|
font-size: 12px;
|
|
|
padding: 2px 0;
|
|
|
}
|
|
|
|
|
|
.file-item i {
|
|
|
margin-right: 8px;
|
|
|
color: #409EFF;
|
|
|
font-size: 14px;
|
|
|
}
|
|
|
|
|
|
.file-link {
|
|
|
color: #409EFF;
|
|
|
font-size: 12px;
|
|
|
margin-right: 8px;
|
|
|
cursor: pointer;
|
|
|
flex: 1;
|
|
|
text-align: left;
|
|
|
}
|
|
|
|
|
|
.file-link:hover {
|
|
|
color: #66b1ff;
|
|
|
}
|
|
|
|
|
|
.download-btn {
|
|
|
padding: 2px 4px;
|
|
|
font-size: 10px;
|
|
|
color: #909399;
|
|
|
height: auto;
|
|
|
line-height: 1;
|
|
|
}
|
|
|
|
|
|
.download-btn:hover {
|
|
|
color: #409EFF;
|
|
|
}
|
|
|
|
|
|
.file-name {
|
|
|
color: #606266;
|
|
|
word-break: break-all;
|
|
|
}
|
|
|
|
|
|
.note-text {
|
|
|
margin-top: 10px;
|
|
|
color: #666;
|
|
|
font-size: 12px;
|
|
|
}
|
|
|
|
|
|
.form-actions {
|
|
|
margin-top: 30px;
|
|
|
text-align: right;
|
|
|
padding-top: 20px;
|
|
|
border-top: 1px solid #ebeef5;
|
|
|
}
|
|
|
|
|
|
/* 表格样式优化 */
|
|
|
.economic-table >>> .el-table__footer-wrapper .el-table__footer {
|
|
|
font-weight: bold;
|
|
|
}
|
|
|
|
|
|
.performance-table >>> .el-table__cell {
|
|
|
padding: 4px 0;
|
|
|
}
|
|
|
|
|
|
.performance-table >>> .el-input__inner {
|
|
|
height: 28px;
|
|
|
line-height: 28px;
|
|
|
}
|
|
|
|
|
|
.performance-table >>> .el-select .el-input__inner {
|
|
|
height: 28px;
|
|
|
line-height: 28px;
|
|
|
}
|
|
|
|
|
|
/* 必填指标行样式 */
|
|
|
.performance-table >>> .required-row {
|
|
|
background-color: #f0f9ff !important;
|
|
|
border-left: 3px solid #409eff !important;
|
|
|
}
|
|
|
|
|
|
.performance-table >>> .required-row td {
|
|
|
background-color: #f0f9ff !important;
|
|
|
}
|
|
|
|
|
|
.performance-table >>> .required-row:hover td {
|
|
|
background-color: #ecf5ff !important;
|
|
|
}
|
|
|
|
|
|
/* 取消标签左边距 */
|
|
|
.no-label-margin >>> .el-form-item__label {
|
|
|
width: 0 !important;
|
|
|
margin-left: 0 !important;
|
|
|
}
|
|
|
|
|
|
.no-label-margin >>> .el-form-item__content {
|
|
|
margin-left: 0 !important;
|
|
|
}
|
|
|
|
|
|
/* 禁用状态的文本样式 */
|
|
|
.disabled-text {
|
|
|
color: #c0c4cc;
|
|
|
font-style: italic;
|
|
|
}
|
|
|
|
|
|
/* 通用附件列表样式 */
|
|
|
.general-files-list {
|
|
|
border: 1px solid #dcdfe6;
|
|
|
border-radius: 4px;
|
|
|
padding: 12px;
|
|
|
background-color: #fafafa;
|
|
|
}
|
|
|
|
|
|
.general-file-item {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
padding: 8px 0;
|
|
|
border-bottom: 1px solid #ebeef5;
|
|
|
font-size: 14px;
|
|
|
}
|
|
|
|
|
|
.general-file-item:last-child {
|
|
|
border-bottom: none;
|
|
|
}
|
|
|
|
|
|
.general-file-item i {
|
|
|
margin-right: 12px;
|
|
|
color: #409EFF;
|
|
|
font-size: 16px;
|
|
|
}
|
|
|
|
|
|
.general-file-item .file-link {
|
|
|
flex: 1;
|
|
|
text-align: left;
|
|
|
font-size: 14px;
|
|
|
}
|
|
|
|
|
|
.general-file-item .download-btn {
|
|
|
padding: 4px 8px;
|
|
|
font-size: 12px;
|
|
|
}
|
|
|
|
|
|
.no-files-text {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
padding: 40px 20px;
|
|
|
color: #909399;
|
|
|
font-size: 14px;
|
|
|
border: 1px dashed #dcdfe6;
|
|
|
border-radius: 4px;
|
|
|
}
|
|
|
|
|
|
.no-files-text i {
|
|
|
font-size: 32px;
|
|
|
margin-bottom: 8px;
|
|
|
color: #c0c4cc;
|
|
|
}
|
|
|
</style>
|