add 付款计划 汇总图表

add 新增待付款
add 项目档案
pull/853/head
wersd 2025-07-26 14:14:50 +08:00
parent 6c1472dbb4
commit d436704756
8 changed files with 149 additions and 8 deletions

View File

@ -10,15 +10,29 @@ export interface ProjectVO {
ownerUserId: string // 项目合伙人
ratio: string // 比例
investment: string // 投资额
cooperationAgreement: number // 场地租赁合同或合作协议
cooperationAgreement: number // 场地租赁合同
purchaseContractId: number // 设备采购合同ID
epcContractId: number // EPC施工合同ID
cooperationAgreementName?: String // 场地租赁合同名称
purchaseContractName?: String // 设备采购合同名称
epcContractName?: String // EPC施工合同名称
buildPermit: string // 同意建桩证明
ownershipProof: string // 场地权属证明
projectRecord: string // 项目备案
powerSupplyReply: string // 供电答复单
investmentAgreement: string // 合作投资协议
investmentShareAgreement: string // 合作投资分成协议
supplementaryMaterials: string // 其他补充材料文件路径
cooperationAgreementConfig?: number // 场地租赁合同配置
purchaseContractConfig?: number // 设备采购合同配置
epcContractConfig?: number // EPC施工合同配置
buildPermitConfig?: number // 同意建桩证明配置
ownershipProofConfig?: number // 场地权属证明配置
projectRecordConfig?: number // 项目备案配置
powerSupplyReplyConfig?: number // 供电答复单配置
investmentAgreementConfig?: number // 合作投资协议配置
investmentShareAgreementConfig?: number // 合作投资分成协议配置
supplementaryMaterialsConfig?: number // 其他补充材料配置
type: number // 类型
partners: []
RevenueSharePartners: []

View File

@ -84,3 +84,8 @@ export const submitReceivablePlan = async (id: number) => {
return await request.put({ url: `/crm/receivable-plan/submit?id=${id}`})
}
// 获取汇总数据
export const getWaitPayData = async () => {
return await request.get({ url: `/crm/receivable-plan/getWaitPayData`})
}

View File

@ -224,6 +224,7 @@ export enum DICT_TYPE {
CRM_CONTRACT_ATTRIBUTE = 'crm_contract_attribute', // CRM 合同属性
CRM_PROJECT_TYPE = 'crm_project_type', // CRM 项目类型
CRM_PROJECT_POWER_SUPPLY_TYPE = 'crm_project_power_supply_type', // CRM 项目供电模式
CRM_PROJECT_DOCUMENT_CONFIG = 'crm_project_document_config', // CRM 项目文档配置
// ========== ERP - 企业资源计划模块 ==========
ERP_AUDIT_STATUS = 'erp_audit_status', // ERP 审批状态

View File

@ -416,7 +416,6 @@ const resetForm = () => {
const handleCustomerChange = () => {
formData.value.businessId = undefined
formData.value.signContactId = undefined
formData.value.products = []
}

View File

@ -123,6 +123,7 @@
</el-col>
</el-row>
{{ formData }}
<el-row>
<el-col :span="12">
<el-form-item label="设备合同" prop="purchaseContractId">
@ -238,6 +239,17 @@
/>
</el-form-item>
</el-col>
<el-col v-if="formData.type === 3" :span="12">
<el-form-item label="合作分成协议" prop="investmentShareAgreement" label-width="120px">
<UploadFile
v-model="formData.investmentShareAgreement"
:isShowTip="false"
:limit="1"
:file-size="20"
:file-type="['pdf']"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item v-if="formData.powerSupplyType === 2" label="供电答复单" prop="powerSupplyReply" label-width="120px">
<UploadFile
@ -292,6 +304,7 @@ const formData = ref({
projectRecord: undefined,
powerSupplyReply: undefined,
investmentAgreement: undefined,
investmentShareAgreement: undefined,
supplementaryMaterials: [],
partners:[],
revenueSharePartners:[],
@ -306,8 +319,6 @@ const formRules = computed(() => ({
type: [{ required: true, message: '类型不能为空', trigger: 'change' }],
projectStatus: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
powerSupplyType: [{ required: true, message: '供电类型不能为空', trigger: 'blur' }],
powerSupplyReply : formData.value.powerSupplyType === 2 ? [{ required: true, message: '供电答复单不能为空', trigger: 'blur' }] : [],
investmentAgreement: formData.value.type === 2 ?[{ required: true, message: '合作投资协议不能为空', trigger: 'blur' }] : [],
cooperationAgreement: [{ required: true, message: '场地合同不能为空', trigger: 'blur' }],
epcContractId: [{ required: true, message: 'EPC合同不能为空', trigger: 'blur' }],
purchaseContractId: [{ required: true, message: '设备合同不能为空', trigger: 'blur' }],
@ -411,6 +422,7 @@ const resetForm = () => {
projectRecord: undefined,
powerSupplyReply: undefined,
investmentAgreement: undefined,
investmentShareAgreement: undefined,
supplementaryMaterials: [],
partners:[],
revenueSharePartners:[],

View File

@ -19,7 +19,7 @@
filterable
clearable
placeholder="请选择"
style="width: 150px"
style="min-width: 150px"
>
<el-option
v-for="user in userOptions"

View File

@ -19,7 +19,7 @@
filterable
clearable
placeholder="请选择"
style="width: 150px"
style="min-width: 150px"
>
<el-option
v-for="user in customerOptions"

View File

@ -90,6 +90,12 @@
<Icon class="mr-5px" icon="ep:download" />
导出
</el-button>
<el-button plain type="warning" @click="openSummaryDialog">
<Icon class="mr-5px" icon="ep:view" />
查看汇总
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
@ -207,14 +213,14 @@
>
创建付款
</el-button>
<!-- <el-button
<el-button
v-hasPermi="['crm:receivable-plan:update']"
link
type="primary"
@click="openForm('update', scope.row.id)"
>
编辑
</el-button> -->
</el-button>
<el-button
v-hasPermi="['crm:receivable-plan:delete']"
link
@ -238,6 +244,13 @@
<!-- 表单弹窗添加/修改 -->
<ReceivablePlanForm ref="formRef" @success="getList" />
<ReceivableForm ref="receivableFormRef" @success="getList" />
<!-- 汇总弹窗 -->
<Dialog v-model="summaryDialogVisible" title="收款计划汇总" @open="fetchSummaryData">
<div v-loading="summaryLoading" style="height:400px;">
<div v-if="!summaryLoading && summaryChartData.length === 0" style="text-align:center;padding:100px 0;"></div>
<div v-else ref="summaryChartRef" style="width:100%;height:100%;"></div>
</div>
</Dialog>
</template>
<script lang="ts" setup>
@ -252,7 +265,104 @@ import ReceivableForm from '@/views/crm/receivable/ReceivableForm.vue'
import ReceivablePlanForm from './ReceivablePlanForm.vue'
import ContractList from '../../contract/components/ContractList.vue'
import * as echarts from 'echarts'
defineOptions({ name: 'ReceivablePlan' })
//
const summaryDialogVisible = ref(false)
const summaryLoading = ref(false)
const summaryChartRef = ref()
const summaryChartData = ref<any[]>([])
const openSummaryDialog = () => {
summaryDialogVisible.value = true
}
const fetchSummaryData = async () => {
summaryLoading.value = true
try {
const res = await ReceivablePlanApi.getWaitPayData()
summaryChartData.value = Array.isArray(res) ? res : (res.data || [])
// DialogECharts0
setTimeout(() => {
nextTick(() => renderSummaryChart())
}, 200)
} finally {
summaryLoading.value = false
}
}
const renderSummaryChart = () => {
if (!summaryChartRef.value) return
//
let chart = echarts.getInstanceByDom(summaryChartRef.value)
if (chart) chart.dispose()
chart = echarts.init(summaryChartRef.value)
const xAxis = [
'未付款',
'1个月内',
'2个月内',
'3个月内',
'4个月内',
'5个月内',
'6个月内',
'7个月内',
'8个月内',
'9个月内',
'10个月内',
'11个月内',
'12个月内'
]
const series = summaryChartData.value.map(item => ({
name: item.deptName || `部门${item.deptId}`,
type: 'bar',
stack: '金额',
emphasis: { focus: 'series' },
barWidth: 32,
itemStyle: {
borderRadius: [0, 0, 0, 0]
},
data: [
item.passPrice || 0,
item.month1Price || 0,
item.month2Price || 0,
item.month3Price || 0,
item.month4Price || 0,
item.month5Price || 0,
item.month6Price || 0,
item.month7Price || 0,
item.month8Price || 0,
item.month9Price || 0,
item.month10Price || 0,
item.month11Price || 0,
item.month12Price || 0
]
}))
chart.setOption({
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
legend: { data: summaryChartData.value.map(item => item.deptName || `部门${item.deptId}`) },
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: {
type: 'category',
data: xAxis,
axisTick: { alignWithLabel: true },
axisLabel: { fontWeight: 'bold' }
},
yAxis: {
type: 'value',
name: '金额(万元)',
axisLabel: { fontWeight: 'bold' }
},
series
})
}
watch(summaryDialogVisible, (val) => {
if (!val && summaryChartRef.value) {
const chart = echarts.getInstanceByDom(summaryChartRef.value)
if (chart) chart.dispose()
}
})
const message = useMessage() //
const { t } = useI18n() //