1210 lines
36 KiB
Vue
1210 lines
36 KiB
Vue
<template>
|
||
<div class="p-2 page-container">
|
||
<!-- 页面头部 -->
|
||
<el-card shadow="never" class="header-card">
|
||
<div class="header-content">
|
||
<div class="header-info">
|
||
<h2 class="page-title">
|
||
<el-icon><Document /></el-icon>
|
||
{{ form.id ? '修改' : '新增' }}项目预算申报
|
||
</h2>
|
||
<p class="page-desc">填写项目预算申报信息,提交审批流程</p>
|
||
</div>
|
||
<div class="header-actions">
|
||
<el-button @click="handleCancel">取消</el-button>
|
||
<el-button type="primary" @click="handleSaveDraft" :loading="savingDraft">保存草稿</el-button>
|
||
<el-button type="success" @click="handleSubmit" :loading="submitting" v-if="canSubmit">提交申报</el-button>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
|
||
<!-- 进度步骤 -->
|
||
<el-card shadow="never" class="steps-card mb-[10px]">
|
||
<el-steps :active="currentStep" finish-status="success" align-center>
|
||
<el-step title="基本信息" description="填写项目基本信息">
|
||
<template #icon>
|
||
<el-icon><User /></el-icon>
|
||
</template>
|
||
</el-step>
|
||
<el-step title="项目内容" description="填写项目详细内容">
|
||
<template #icon>
|
||
<el-icon><Document /></el-icon>
|
||
</template>
|
||
</el-step>
|
||
<el-step title="资金预算" description="填写各项资金预算">
|
||
<template #icon>
|
||
<el-icon><Money /></el-icon>
|
||
</template>
|
||
</el-step>
|
||
<el-step title="绩效指标" description="填写绩效目标指标">
|
||
<template #icon>
|
||
<el-icon><TrendCharts /></el-icon>
|
||
</template>
|
||
</el-step>
|
||
<el-step title="提交确认" description="确认信息并提交">
|
||
<template #icon>
|
||
<el-icon><Check /></el-icon>
|
||
</template>
|
||
</el-step>
|
||
</el-steps>
|
||
</el-card>
|
||
|
||
<!-- 表单内容区域 -->
|
||
<div class="form-content">
|
||
<!-- 第一步:基本信息 -->
|
||
<el-card shadow="never" class="form-card" v-show="currentStep === 0">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<el-icon><User /></el-icon>
|
||
<span>项目基本信息</span>
|
||
</div>
|
||
</template>
|
||
|
||
<el-form ref="basicFormRef" :model="form" :rules="rules" label-width="120px" label-position="top">
|
||
<el-row :gutter="20">
|
||
<el-col :span="12">
|
||
<el-form-item label="项目名称" prop="projectName">
|
||
<el-input
|
||
v-model="form.projectName"
|
||
placeholder="请输入项目名称"
|
||
size="large"
|
||
:prefix-icon="Document"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="项目负责人" prop="principal">
|
||
<el-input
|
||
v-model="form.principal"
|
||
placeholder="请输入项目负责人"
|
||
size="large"
|
||
:prefix-icon="User"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<el-row :gutter="20">
|
||
<el-col :span="12">
|
||
<el-form-item label="联系电话" prop="contactPhone">
|
||
<el-input
|
||
v-model="form.contactPhone"
|
||
placeholder="请输入联系电话"
|
||
size="large"
|
||
:prefix-icon="Phone"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="预算年度" prop="year">
|
||
<el-date-picker
|
||
v-model="form.year"
|
||
type="year"
|
||
placeholder="选择年份"
|
||
value-format="YYYY"
|
||
size="large"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<el-row :gutter="20">
|
||
<el-col :span="12">
|
||
<el-form-item label="项目开始日期" prop="startDate">
|
||
<el-date-picker
|
||
v-model="form.startDate"
|
||
type="date"
|
||
placeholder="选择开始日期"
|
||
value-format="YYYY-MM-DD"
|
||
size="large"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="项目结束日期" prop="endDate">
|
||
<el-date-picker
|
||
v-model="form.endDate"
|
||
type="date"
|
||
placeholder="选择结束日期"
|
||
value-format="YYYY-MM-DD"
|
||
size="large"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<!-- 项目特征选项 -->
|
||
<div class="feature-section">
|
||
<h4 class="section-title">项目特征</h4>
|
||
<el-row :gutter="20">
|
||
<el-col :span="8">
|
||
<div class="feature-item">
|
||
<el-switch
|
||
v-model="form.targetSme"
|
||
size="large"
|
||
active-text="面向中小企业"
|
||
inactive-text="不面向中小企业"
|
||
/>
|
||
</div>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<div class="feature-item">
|
||
<el-switch
|
||
v-model="form.targetMicroSme"
|
||
size="large"
|
||
active-text="面向小微企业"
|
||
inactive-text="不面向小微企业"
|
||
/>
|
||
</div>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<div class="feature-item">
|
||
<el-switch
|
||
v-model="form.importEquipment"
|
||
size="large"
|
||
active-text="采购进口设备"
|
||
inactive-text="不采购进口设备"
|
||
/>
|
||
</div>
|
||
</el-col>
|
||
</el-row>
|
||
</div>
|
||
</el-form>
|
||
</el-card>
|
||
|
||
<!-- 第二步:项目内容 -->
|
||
<el-card shadow="never" class="form-card" v-show="currentStep === 1">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<el-icon><Document /></el-icon>
|
||
<span>项目详细内容</span>
|
||
</div>
|
||
</template>
|
||
|
||
<el-form ref="contentFormRef" :model="form" :rules="rules" label-width="120px" label-position="top">
|
||
<el-form-item label="项目申请原因" prop="applicationReason">
|
||
<el-input
|
||
v-model="form.applicationReason"
|
||
type="textarea"
|
||
:rows="5"
|
||
placeholder="请详细说明项目申请原因,包括设立依据、可行性及必要性"
|
||
maxlength="1000"
|
||
show-word-limit
|
||
/>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="项目建设内容" prop="constructionContent">
|
||
<el-input
|
||
v-model="form.constructionContent"
|
||
type="textarea"
|
||
:rows="5"
|
||
placeholder="请详细描述项目建设内容"
|
||
maxlength="1000"
|
||
show-word-limit
|
||
/>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="绩效总目标" prop="totalPerformanceGoal">
|
||
<el-input
|
||
v-model="form.totalPerformanceGoal"
|
||
type="textarea"
|
||
:rows="4"
|
||
placeholder="请描述项目的总体绩效目标"
|
||
maxlength="500"
|
||
show-word-limit
|
||
/>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="当年绩效目标" prop="currentYearGoal">
|
||
<el-input
|
||
v-model="form.currentYearGoal"
|
||
type="textarea"
|
||
:rows="4"
|
||
placeholder="请描述当年需要达成的绩效目标"
|
||
maxlength="500"
|
||
show-word-limit
|
||
/>
|
||
</el-form-item>
|
||
</el-form>
|
||
</el-card>
|
||
|
||
<!-- 第三步:资金预算 -->
|
||
<el-card shadow="never" class="form-card" v-show="currentStep === 2">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<el-icon><Money /></el-icon>
|
||
<span>资金预算明细</span>
|
||
<el-tag type="warning" size="small">单位:万元</el-tag>
|
||
</div>
|
||
</template>
|
||
|
||
<el-form ref="budgetFormRef" :model="form" :rules="rules" label-width="120px" label-position="top">
|
||
<!-- 总投资情况 -->
|
||
<div class="budget-section">
|
||
<h4 class="section-title">
|
||
<el-icon><TrendCharts /></el-icon>
|
||
总投资情况
|
||
</h4>
|
||
<el-row :gutter="20">
|
||
<el-col :span="6">
|
||
<el-form-item label="项目总投资" prop="totalInvestment">
|
||
<el-input-number
|
||
v-model="form.totalInvestment"
|
||
:precision="2"
|
||
:min="0"
|
||
:max="99999999"
|
||
placeholder="万元"
|
||
size="large"
|
||
style="width: 100%"
|
||
@change="calculateTotalInvestment"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="市财政资金" prop="totalMunicipalFiscal">
|
||
<el-input-number
|
||
v-model="form.totalMunicipalFiscal"
|
||
:precision="2"
|
||
:min="0"
|
||
placeholder="万元"
|
||
size="large"
|
||
style="width: 100%"
|
||
@change="calculateTotalInvestment"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="单位自筹" prop="totalSelfFunding">
|
||
<el-input-number
|
||
v-model="form.totalSelfFunding"
|
||
:precision="2"
|
||
:min="0"
|
||
placeholder="万元"
|
||
size="large"
|
||
style="width: 100%"
|
||
@change="calculateTotalInvestment"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="其他资金" prop="totalOtherFunding">
|
||
<el-input-number
|
||
v-model="form.totalOtherFunding"
|
||
:precision="2"
|
||
:min="0"
|
||
placeholder="万元"
|
||
size="large"
|
||
style="width: 100%"
|
||
@change="calculateTotalInvestment"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
</div>
|
||
|
||
<!-- 年度执行预算 -->
|
||
<div class="budget-section">
|
||
<h4 class="section-title">
|
||
<el-icon><Calendar /></el-icon>
|
||
年度预计执行数
|
||
</h4>
|
||
<el-row :gutter="20">
|
||
<el-col :span="6">
|
||
<el-form-item label="年度执行合计" prop="yearlyTotal">
|
||
<el-input-number
|
||
v-model="form.yearlyTotal"
|
||
:precision="2"
|
||
:min="0"
|
||
placeholder="万元"
|
||
size="large"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="年度财政资金" prop="yearlyMunicipalFiscal">
|
||
<el-input-number
|
||
v-model="form.yearlyMunicipalFiscal"
|
||
:precision="2"
|
||
:min="0"
|
||
placeholder="万元"
|
||
size="large"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="年度自筹资金" prop="yearlySelfFunding">
|
||
<el-input-number
|
||
v-model="form.yearlySelfFunding"
|
||
:precision="2"
|
||
:min="0"
|
||
placeholder="万元"
|
||
size="large"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="年度其他资金" prop="yearlyOtherFunding">
|
||
<el-input-number
|
||
v-model="form.yearlyOtherFunding"
|
||
:precision="2"
|
||
:min="0"
|
||
placeholder="万元"
|
||
size="large"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
</div>
|
||
|
||
<!-- 资金分配图表 -->
|
||
<div class="budget-chart" v-if="hasBudgetData">
|
||
<h4 class="section-title">资金分配可视化</h4>
|
||
<div ref="budgetChart" style="height: 300px;"></div>
|
||
</div>
|
||
</el-form>
|
||
</el-card>
|
||
|
||
<!-- 第四步:绩效指标 -->
|
||
<el-card shadow="never" class="form-card" v-show="currentStep === 3">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<el-icon><TrendCharts /></el-icon>
|
||
<span>绩效指标设置</span>
|
||
</div>
|
||
</template>
|
||
|
||
<el-form ref="performanceFormRef" :model="form" label-width="120px" label-position="top">
|
||
<PerformanceIndicators
|
||
ref="performanceIndicatorsRef"
|
||
v-model="form.performanceIndicators"
|
||
@change="handlePerformanceIndicatorsChange"
|
||
/>
|
||
</el-form>
|
||
</el-card>
|
||
|
||
<!-- 第五步:确认提交 -->
|
||
<el-card shadow="never" class="form-card" v-show="currentStep === 4">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<el-icon><Check /></el-icon>
|
||
<span>信息确认</span>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- 信息预览 -->
|
||
<div class="confirmation-content">
|
||
<div class="confirmation-section">
|
||
<h4>基本信息</h4>
|
||
<el-descriptions :column="2" border>
|
||
<el-descriptions-item label="项目名称">{{ form.projectName }}</el-descriptions-item>
|
||
<el-descriptions-item label="项目负责人">{{ form.principal }}</el-descriptions-item>
|
||
<el-descriptions-item label="联系电话">{{ form.contactPhone }}</el-descriptions-item>
|
||
<el-descriptions-item label="预算年度">{{ form.year }}</el-descriptions-item>
|
||
<el-descriptions-item label="项目周期">{{ form.startDate }} 至 {{ form.endDate }}</el-descriptions-item>
|
||
<el-descriptions-item label="总投资">{{ formatAmount(form.totalInvestment) }} 万元</el-descriptions-item>
|
||
</el-descriptions>
|
||
</div>
|
||
|
||
<div class="confirmation-actions">
|
||
<el-alert
|
||
title="提交确认"
|
||
description="请仔细核对上述信息,提交后将进入审批流程,无法直接修改。"
|
||
type="warning"
|
||
show-icon
|
||
:closable="false"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
</div>
|
||
|
||
<!-- 底部操作按钮 -->
|
||
<el-card shadow="never" class="footer-card">
|
||
<div class="footer-actions">
|
||
<el-button @click="handlePrevStep" v-if="currentStep > 0">
|
||
<el-icon><ArrowLeft /></el-icon>
|
||
上一步
|
||
</el-button>
|
||
<div class="action-group">
|
||
<el-button @click="handleCancel">取消</el-button>
|
||
<el-button type="info" @click="handleSaveDraft" :loading="savingDraft">保存草稿</el-button>
|
||
<el-button type="primary" @click="handleNextStep" v-if="currentStep < 4">
|
||
下一步
|
||
<el-icon><ArrowRight /></el-icon>
|
||
</el-button>
|
||
<el-button type="success" @click="handleSubmit" :loading="submitting" v-if="currentStep === 4">
|
||
<el-icon><Check /></el-icon>
|
||
提交申报
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, reactive, computed, onMounted, nextTick, watch } from 'vue'
|
||
import { useRouter, useRoute } from 'vue-router'
|
||
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus'
|
||
import {
|
||
Document, User, Phone, Money, TrendCharts, Check, Calendar,
|
||
ArrowLeft, ArrowRight
|
||
} from '@element-plus/icons-vue'
|
||
import * as echarts from 'echarts'
|
||
import {
|
||
getProjectApplication,
|
||
addProjectApplication,
|
||
updateProjectApplication,
|
||
submitApplication,
|
||
saveDraft
|
||
} from '@/api/cw/projectApplication'
|
||
import PerformanceIndicators from './components/PerformanceIndicators.vue'
|
||
|
||
// 接收父组件传递的数据
|
||
const props = defineProps<{
|
||
formData?: any
|
||
}>()
|
||
|
||
const router = useRouter()
|
||
const route = useRoute()
|
||
|
||
// 表单引用
|
||
const basicFormRef = ref()
|
||
const contentFormRef = ref()
|
||
const budgetFormRef = ref()
|
||
const performanceFormRef = ref()
|
||
const performanceIndicatorsRef = ref()
|
||
|
||
// 当前步骤
|
||
const currentStep = ref(0)
|
||
const activeTab = ref('quantity')
|
||
|
||
// 加载状态
|
||
const savingDraft = ref(false)
|
||
const submitting = ref(false)
|
||
|
||
// 图表引用
|
||
const budgetChart = ref()
|
||
let budgetChartInstance: echarts.ECharts | null = null
|
||
|
||
// 表单数据
|
||
const form = reactive({
|
||
id: undefined,
|
||
applicationNo: '',
|
||
projectName: '',
|
||
principal: '',
|
||
contactPhone: '',
|
||
year: new Date().getFullYear().toString(),
|
||
startDate: '',
|
||
endDate: '',
|
||
targetSme: false,
|
||
targetMicroSme: false,
|
||
importEquipment: false,
|
||
applicationReason: '',
|
||
constructionContent: '',
|
||
totalPerformanceGoal: '',
|
||
currentYearGoal: '',
|
||
// 项目总投资
|
||
totalInvestment: 0,
|
||
totalMunicipalFiscal: 0,
|
||
totalSelfFunding: 0,
|
||
totalOtherFunding: 0,
|
||
// 年度预计执行数
|
||
currentYearTotal: 0,
|
||
currentYearMunicipal: 0,
|
||
currentYearSelf: 0,
|
||
currentYearOther: 0,
|
||
// 下年度申请资金
|
||
nextYearTotal: 0,
|
||
nextYearMunicipal: 0,
|
||
nextYearSelf: 0,
|
||
nextYearOther: 0,
|
||
// 以后年度安排
|
||
futureYearTotal: 0,
|
||
futureYearMunicipal: 0,
|
||
futureYearSelf: 0,
|
||
futureYearOther: 0,
|
||
// 绩效指标
|
||
performanceIndicators: [],
|
||
approvalStatus: 'draft'
|
||
})
|
||
|
||
// 监听父组件传递的数据变化
|
||
watch(() => props.formData, (newData) => {
|
||
if (newData && typeof newData === 'object' && Object.keys(newData).length > 0) {
|
||
console.log('接收到父组件传递的数据:', newData)
|
||
// 安全地更新form数据
|
||
Object.keys(form).forEach(key => {
|
||
if (newData.hasOwnProperty(key) && newData[key] !== null && newData[key] !== undefined) {
|
||
// 特殊处理不同类型的字段
|
||
if (key === 'year' && newData[key]) {
|
||
form[key] = newData[key].toString()
|
||
} else if (key === 'startDate' || key === 'endDate') {
|
||
form[key] = newData[key] ? formatDateString(newData[key]) : ''
|
||
} else if (key.includes('Investment') || key.includes('Fiscal') || key.includes('Funding') ||
|
||
key.includes('Total') || key.includes('Municipal') || key.includes('Self') || key.includes('Other')) {
|
||
// 金额字段
|
||
form[key] = Number(newData[key]) || 0
|
||
} else {
|
||
form[key] = newData[key]
|
||
}
|
||
}
|
||
})
|
||
console.log('form数据更新完成:', form)
|
||
}
|
||
}, { deep: true, immediate: true })
|
||
|
||
// 日期格式化函数
|
||
const formatDateString = (dateValue: any): string => {
|
||
if (!dateValue) return ''
|
||
let date: Date
|
||
if (typeof dateValue === 'string') {
|
||
date = new Date(dateValue)
|
||
} else if (dateValue instanceof Date) {
|
||
date = dateValue
|
||
} else {
|
||
return ''
|
||
}
|
||
|
||
if (isNaN(date.getTime())) return ''
|
||
|
||
const year = date.getFullYear()
|
||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||
const day = String(date.getDate()).padStart(2, '0')
|
||
return `${year}-${month}-${day}`
|
||
}
|
||
|
||
// 表单验证规则
|
||
const rules = {
|
||
projectName: [
|
||
{ required: true, message: '请输入项目名称', trigger: 'blur' },
|
||
{ min: 2, max: 100, message: '项目名称长度在 2 到 100 个字符', trigger: 'blur' }
|
||
],
|
||
principal: [
|
||
{ required: true, message: '请输入项目负责人', trigger: 'blur' }
|
||
],
|
||
contactPhone: [
|
||
{ required: true, message: '请输入联系电话', trigger: 'blur' },
|
||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
|
||
],
|
||
year: [
|
||
{ required: true, message: '请选择预算年度', trigger: 'change' }
|
||
],
|
||
startDate: [
|
||
{ required: true, message: '请选择项目开始日期', trigger: 'change' }
|
||
],
|
||
endDate: [
|
||
{ required: true, message: '请选择项目结束日期', trigger: 'change' }
|
||
],
|
||
totalInvestment: [
|
||
{ required: true, message: '请填写项目总投资', trigger: 'blur' },
|
||
{ type: 'number', min: 0.01, message: '投资金额必须大于0', trigger: 'blur' }
|
||
]
|
||
}
|
||
|
||
// 计算属性
|
||
const canSubmit = computed(() => {
|
||
return form.projectName && form.principal && form.totalInvestment > 0
|
||
})
|
||
|
||
const hasBudgetData = computed(() => {
|
||
return form.totalMunicipalFiscal > 0 || form.totalSelfFunding > 0 || form.totalOtherFunding > 0
|
||
})
|
||
|
||
// 格式化金额
|
||
const formatAmount = (amount: number): string => {
|
||
return amount ? amount.toFixed(2) : '0.00'
|
||
}
|
||
|
||
// 计算总投资
|
||
const calculateTotalInvestment = () => {
|
||
form.totalInvestment = (form.totalMunicipalFiscal || 0) +
|
||
(form.totalSelfFunding || 0) +
|
||
(form.totalOtherFunding || 0)
|
||
|
||
// 更新图表
|
||
if (budgetChartInstance && hasBudgetData.value) {
|
||
renderBudgetChart()
|
||
}
|
||
}
|
||
|
||
// 渲染预算图表
|
||
const renderBudgetChart = () => {
|
||
if (!budgetChartInstance) return
|
||
|
||
const option = {
|
||
tooltip: {
|
||
trigger: 'item',
|
||
formatter: '{a} <br/>{b}: {c} 万元 ({d}%)'
|
||
},
|
||
legend: {
|
||
orient: 'vertical',
|
||
left: 'left'
|
||
},
|
||
series: [
|
||
{
|
||
name: '资金构成',
|
||
type: 'pie',
|
||
radius: ['50%', '70%'],
|
||
data: [
|
||
{
|
||
value: form.totalMunicipalFiscal,
|
||
name: '市财政资金',
|
||
itemStyle: { color: '#409EFF' }
|
||
},
|
||
{
|
||
value: form.totalSelfFunding,
|
||
name: '单位自筹',
|
||
itemStyle: { color: '#67C23A' }
|
||
},
|
||
{
|
||
value: form.totalOtherFunding,
|
||
name: '其他资金',
|
||
itemStyle: { color: '#E6A23C' }
|
||
}
|
||
].filter(item => item.value > 0),
|
||
emphasis: {
|
||
itemStyle: {
|
||
shadowBlur: 10,
|
||
shadowOffsetX: 0,
|
||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||
}
|
||
}
|
||
}
|
||
]
|
||
}
|
||
|
||
budgetChartInstance.setOption(option)
|
||
}
|
||
|
||
// 初始化图表
|
||
const initBudgetChart = async () => {
|
||
await nextTick()
|
||
if (budgetChart.value) {
|
||
budgetChartInstance = echarts.init(budgetChart.value)
|
||
if (hasBudgetData.value) {
|
||
renderBudgetChart()
|
||
}
|
||
}
|
||
}
|
||
|
||
// 验证绩效指标分值占比总和
|
||
const validatePerformanceIndicators = () => {
|
||
console.log('=== 校验绩效指标 ===')
|
||
|
||
// 直接使用绩效指标组件的校验方法
|
||
if (performanceIndicatorsRef?.value && typeof performanceIndicatorsRef.value.validate === 'function') {
|
||
const validationResult = performanceIndicatorsRef.value.validate()
|
||
console.log('绩效指标校验结果:', validationResult)
|
||
return validationResult
|
||
}
|
||
|
||
// 回退方案:检查form中的数据
|
||
if (!form.performanceIndicators || form.performanceIndicators.length === 0) {
|
||
return { valid: false, message: '请至少添加一个绩效指标' }
|
||
}
|
||
|
||
const totalWeight = form.performanceIndicators.reduce((sum, item) => {
|
||
return sum + (Number(item.weightPercentage) || 0)
|
||
}, 0)
|
||
|
||
if (Math.abs(totalWeight - 100) > 0.1) {
|
||
return {
|
||
valid: false,
|
||
message: `绩效指标分值占比总和必须等于100%,当前为${totalWeight}%`
|
||
}
|
||
}
|
||
|
||
return { valid: true }
|
||
}
|
||
|
||
// 步骤操作
|
||
const handleNextStep = async () => {
|
||
const formRefs = [basicFormRef, contentFormRef, budgetFormRef, performanceFormRef]
|
||
const currentFormRef = formRefs[currentStep.value]
|
||
|
||
if (currentFormRef?.value) {
|
||
try {
|
||
await currentFormRef.value.validate()
|
||
|
||
// 如果是绩效指标步骤(第3步),额外校验分值占比
|
||
if (currentStep.value === 3) {
|
||
const validationResult = validatePerformanceIndicators()
|
||
if (!validationResult.valid) {
|
||
ElMessage.error(validationResult.message)
|
||
return
|
||
}
|
||
}
|
||
|
||
currentStep.value++
|
||
|
||
// 初始化图表
|
||
if (currentStep.value === 2) {
|
||
setTimeout(initBudgetChart, 100)
|
||
}
|
||
} catch (error) {
|
||
ElMessage.error('请完善当前步骤的必填信息')
|
||
}
|
||
} else {
|
||
// 如果是绩效指标步骤(第3步),即使没有表单ref也要校验分值占比
|
||
if (currentStep.value === 3) {
|
||
const validationResult = validatePerformanceIndicators()
|
||
if (!validationResult.valid) {
|
||
ElMessage.error(validationResult.message)
|
||
return
|
||
}
|
||
}
|
||
|
||
currentStep.value++
|
||
|
||
// 初始化图表
|
||
if (currentStep.value === 2) {
|
||
setTimeout(initBudgetChart, 100)
|
||
}
|
||
}
|
||
}
|
||
|
||
const handlePrevStep = () => {
|
||
currentStep.value--
|
||
}
|
||
|
||
// 处理绩效指标变化
|
||
const handlePerformanceIndicatorsChange = (indicators) => {
|
||
console.log('=== 绩效指标数据变化 ===')
|
||
console.log('接收到的indicators:', indicators)
|
||
|
||
// 为每个指标添加必要的字段
|
||
const processedIndicators = indicators.map((item, index) => ({
|
||
...item,
|
||
applicationId: form.id || null,
|
||
applicationNo: form.applicationNo || '',
|
||
sortOrder: index,
|
||
status: '1',
|
||
// 确保weightPercentage是BigDecimal兼容的数字
|
||
weightPercentage: Number(item.weightPercentage) || 0,
|
||
// 确保必填字段不为空
|
||
goalName: item.goalName || '',
|
||
targetValue: item.targetValue || '',
|
||
indicatorType: item.indicatorType || '',
|
||
indicatorTypeName: item.indicatorTypeName || '',
|
||
// 可选字段设置默认值
|
||
unit: item.unit || '',
|
||
description: item.description || '',
|
||
measurementMethod: item.measurementMethod || '',
|
||
dataSource: item.dataSource || '',
|
||
isQuantitative: item.isQuantitative !== undefined ? item.isQuantitative : true
|
||
}))
|
||
|
||
form.performanceIndicators = processedIndicators
|
||
console.log('处理后的绩效指标数据:', form.performanceIndicators)
|
||
|
||
// 自动生成总绩效目标和当年目标(如果有目标值)
|
||
const totalGoals = processedIndicators
|
||
.filter(item => item.targetValue && !isNaN(Number(item.targetValue)))
|
||
.reduce((sum, item) => sum + Number(item.targetValue), 0)
|
||
|
||
if (totalGoals > 0) {
|
||
form.totalPerformanceGoal = totalGoals
|
||
form.currentYearGoal = totalGoals
|
||
console.log('自动生成总绩效目标:', form.totalPerformanceGoal)
|
||
}
|
||
}
|
||
|
||
// 保存草稿
|
||
const handleSaveDraft = async () => {
|
||
try {
|
||
savingDraft.value = true
|
||
|
||
// 如果当前在绩效指标步骤(第3步),校验分值占比总和
|
||
if (currentStep.value === 3 && form.performanceIndicators && form.performanceIndicators.length > 0) {
|
||
const validationResult = validatePerformanceIndicators()
|
||
if (!validationResult.valid) {
|
||
ElMessage.warning(`保存提醒:${validationResult.message}`)
|
||
// 注意:这里只是警告,不阻止保存草稿
|
||
}
|
||
}
|
||
|
||
// 详细调试信息
|
||
console.log('=== 前端保存草稿调试信息 ===')
|
||
console.log('form.performanceIndicators 是否存在:', !!form.performanceIndicators)
|
||
console.log('form.performanceIndicators 长度:', form.performanceIndicators ? form.performanceIndicators.length : 'undefined')
|
||
console.log('form.performanceIndicators 数据:', form.performanceIndicators)
|
||
console.log('完整的form数据:', form)
|
||
|
||
const result = form.id
|
||
? await updateProjectApplication(form as any)
|
||
: await saveDraft(form as any)
|
||
|
||
if (result.code === 200) {
|
||
// 如果是新建草稿,获取生成的ID和申报编号
|
||
if (!form.id && result.data?.id) {
|
||
form.id = result.data.id
|
||
form.applicationNo = result.data.applicationNo
|
||
console.log('草稿保存成功,获得ID:', form.id, '申报编号:', form.applicationNo)
|
||
}
|
||
|
||
// 绩效指标数据已经作为form的一部分传递给后端,无需单独处理
|
||
console.log('绩效指标数据已随草稿数据一起保存,数量:', form.performanceIndicators ? form.performanceIndicators.length : 0)
|
||
|
||
ElMessage.success('草稿保存成功')
|
||
}
|
||
} catch (error) {
|
||
console.error('保存草稿失败:', error)
|
||
ElMessage.error('保存草稿失败')
|
||
} finally {
|
||
savingDraft.value = false
|
||
}
|
||
}
|
||
|
||
// 提交申报
|
||
const handleSubmit = async () => {
|
||
try {
|
||
submitting.value = true
|
||
|
||
console.log('开始提交申报流程,form.id:', form.id)
|
||
|
||
// 先保存数据
|
||
if (!form.id) {
|
||
console.log('新增申报,调用addProjectApplication')
|
||
const saveResult = await addProjectApplication(form as any)
|
||
console.log('新增申报结果:', saveResult)
|
||
if (saveResult.code === 200) {
|
||
if (saveResult.data && saveResult.data.id) {
|
||
form.id = saveResult.data.id
|
||
form.applicationNo = saveResult.data.applicationNo
|
||
console.log('新增成功,获得ID:', form.id, '申报编号:', form.applicationNo)
|
||
} else {
|
||
console.error('新增申报返回的数据异常:', saveResult)
|
||
ElMessage.error('保存申报数据失败: 返回数据格式异常')
|
||
return
|
||
}
|
||
} else {
|
||
console.error('新增申报失败:', saveResult)
|
||
ElMessage.error('保存申报数据失败: ' + (saveResult.msg || '未知错误'))
|
||
return
|
||
}
|
||
} else {
|
||
console.log('更新申报,调用updateProjectApplication,ID:', form.id)
|
||
const updateResult = await updateProjectApplication(form as any)
|
||
console.log('更新申报结果:', updateResult)
|
||
if (updateResult.code !== 200) {
|
||
console.error('更新申报失败:', updateResult)
|
||
ElMessage.error('更新申报数据失败: ' + (updateResult.msg || '未知错误'))
|
||
return
|
||
}
|
||
}
|
||
|
||
// 绩效指标数据会作为form的一部分自动传递给后端,无需单独保存
|
||
console.log('绩效指标数据将随申报数据一起提交,数量:', form.performanceIndicators ? form.performanceIndicators.length : 0)
|
||
|
||
// 再提交审批
|
||
if (!form.id) {
|
||
console.error('申报ID为空,无法提交审批')
|
||
ElMessage.error('申报ID为空,无法提交审批')
|
||
return
|
||
}
|
||
|
||
console.log('开始提交审批,ID:', form.id)
|
||
const submitResult = await submitApplication(form.id)
|
||
console.log('提交审批结果:', submitResult)
|
||
|
||
if (submitResult.code === 200) {
|
||
ElMessage.success('申报提交成功')
|
||
router.push('/cw/application')
|
||
} else {
|
||
console.error('提交审批失败:', submitResult)
|
||
ElMessage.error('提交申报失败: ' + (submitResult.msg || '未知错误'))
|
||
}
|
||
} catch (error) {
|
||
console.error('提交申报失败详细错误:', error)
|
||
|
||
// 解析错误信息
|
||
let errorMessage = '提交申报失败'
|
||
if (error?.response?.data?.msg) {
|
||
errorMessage += ': ' + error.response.data.msg
|
||
} else if (error?.message) {
|
||
errorMessage += ': ' + error.message
|
||
} else if (typeof error === 'string') {
|
||
errorMessage += ': ' + error
|
||
}
|
||
|
||
console.error('最终错误信息:', errorMessage)
|
||
ElMessage.error(errorMessage)
|
||
} finally {
|
||
submitting.value = false
|
||
}
|
||
}
|
||
|
||
// 取消操作
|
||
const handleCancel = () => {
|
||
ElMessageBox.confirm('确认取消吗?未保存的数据将丢失。', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}).then(() => {
|
||
router.push('/cw/application')
|
||
})
|
||
}
|
||
|
||
// 加载数据
|
||
const loadData = async () => {
|
||
const id = route.params.id as string
|
||
if (id && id !== 'add') {
|
||
try {
|
||
console.log('开始加载申报数据,ID:', id)
|
||
const response = await getProjectApplication(id)
|
||
if (response.code === 200) {
|
||
console.log('申报基础数据加载成功:', response.data)
|
||
Object.assign(form, response.data)
|
||
|
||
// 检查是否有绩效指标数据
|
||
if (response.data.performanceIndicators && response.data.performanceIndicators.length > 0) {
|
||
console.log('发现绩效指标数据,数量:', response.data.performanceIndicators.length)
|
||
form.performanceIndicators = response.data.performanceIndicators
|
||
console.log('绩效指标数据已加载到form.performanceIndicators:', form.performanceIndicators)
|
||
} else {
|
||
console.log('没有绩效指标数据')
|
||
form.performanceIndicators = []
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('加载数据失败:', error)
|
||
ElMessage.error('加载数据失败')
|
||
}
|
||
}
|
||
}
|
||
|
||
// 暴露给父组件的方法
|
||
const validate = (callback: (valid: boolean) => void) => {
|
||
// 简单的验证逻辑,检查必填字段
|
||
const isValid = !!(form.projectName && form.principal && form.totalInvestment > 0)
|
||
callback(isValid)
|
||
}
|
||
|
||
// 暴露方法给父组件
|
||
defineExpose({
|
||
validate
|
||
})
|
||
|
||
// 组件挂载
|
||
onMounted(() => {
|
||
loadData()
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.page-container {
|
||
min-height: calc(100vh - 84px);
|
||
}
|
||
|
||
// 页面头部
|
||
.header-card {
|
||
border-radius: 8px;
|
||
border: 1px solid #e4e7ed;
|
||
margin-bottom: 10px;
|
||
|
||
.header-content {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
|
||
.header-info {
|
||
.page-title {
|
||
margin: 0 0 8px 0;
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
color: #2c3e50;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.page-desc {
|
||
margin: 0;
|
||
color: #909399;
|
||
font-size: 14px;
|
||
}
|
||
}
|
||
|
||
.header-actions {
|
||
display: flex;
|
||
gap: 12px;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 步骤卡片
|
||
.steps-card {
|
||
border-radius: 8px;
|
||
border: 1px solid #e4e7ed;
|
||
|
||
:deep(.el-steps) {
|
||
.el-step__title {
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 表单卡片
|
||
.form-card {
|
||
border-radius: 8px;
|
||
border: 1px solid #e4e7ed;
|
||
margin-bottom: 10px;
|
||
|
||
.card-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-weight: 600;
|
||
color: #2c3e50;
|
||
}
|
||
|
||
// 表单项样式
|
||
:deep(.el-form-item) {
|
||
margin-bottom: 24px;
|
||
|
||
.el-form-item__label {
|
||
font-weight: 600;
|
||
color: #2c3e50;
|
||
margin-bottom: 8px;
|
||
}
|
||
}
|
||
|
||
// 特征选项
|
||
.feature-section {
|
||
margin-top: 24px;
|
||
padding-top: 24px;
|
||
border-top: 1px solid #f0f0f0;
|
||
|
||
.section-title {
|
||
margin: 0 0 16px 0;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #2c3e50;
|
||
}
|
||
|
||
.feature-item {
|
||
text-align: center;
|
||
padding: 16px;
|
||
border: 1px solid #e4e7ed;
|
||
border-radius: 6px;
|
||
background: #fafafa;
|
||
|
||
:deep(.el-switch) {
|
||
.el-switch__label {
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 预算部分
|
||
.budget-section {
|
||
margin-bottom: 32px;
|
||
padding: 20px;
|
||
background: #fafbfc;
|
||
border-radius: 6px;
|
||
border: 1px solid #e1e8ed;
|
||
|
||
.section-title {
|
||
margin: 0 0 20px 0;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #2c3e50;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
}
|
||
|
||
.budget-chart {
|
||
margin-top: 24px;
|
||
padding: 20px;
|
||
background: white;
|
||
border-radius: 6px;
|
||
border: 1px solid #e4e7ed;
|
||
|
||
.section-title {
|
||
margin: 0 0 16px 0;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #2c3e50;
|
||
}
|
||
}
|
||
|
||
// 绩效指标部分
|
||
.performance-section {
|
||
:deep(.el-tabs) {
|
||
.el-tabs__header {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.el-tab-pane {
|
||
padding: 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 确认信息
|
||
.confirmation-content {
|
||
.confirmation-section {
|
||
margin-bottom: 24px;
|
||
|
||
h4 {
|
||
margin: 0 0 16px 0;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #2c3e50;
|
||
}
|
||
}
|
||
|
||
.confirmation-actions {
|
||
margin-top: 24px;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 底部操作
|
||
.footer-card {
|
||
border-radius: 8px;
|
||
border: 1px solid #e4e7ed;
|
||
|
||
.footer-actions {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
|
||
.action-group {
|
||
display: flex;
|
||
gap: 12px;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 响应式设计
|
||
@media (max-width: 768px) {
|
||
.header-content {
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 16px;
|
||
}
|
||
|
||
.footer-actions {
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
|
||
.action-group {
|
||
width: 100%;
|
||
justify-content: center;
|
||
}
|
||
}
|
||
}
|
||
</style>
|