add 项目详细页

pull/853/head
zhuhui 2025-12-12 09:14:59 +08:00
parent cc0e165e7a
commit 3543bce085
6 changed files with 419 additions and 2 deletions

View File

@ -600,6 +600,17 @@ const remainingRouter: AppRouteRecordRaw[] = [
},
component: () => import('@/views/crm/stationsite/detail/index.vue')
},
{
path: 'project/detail/:id',
name: 'CrmProjectDetail',
meta: {
title: '项目详情',
noCache: true,
hidden: true,
activeMenu: '/crm/project'
},
component: () => import('@/views/crm/project/detail/index.vue')
},
{
path: 'project/settlement/:id',
name: 'CrmProjectSettlementDetail',

View File

@ -0,0 +1,47 @@
<template>
<ContentWrap>
<el-descriptions :column="2">
<el-descriptions-item label="同意建桩证明">
<div v-if="project.buildPermit">
<el-link type="primary" :href="project.buildPermit" target="_blank">查看文件</el-link>
</div>
<span v-else>-</span>
</el-descriptions-item>
<el-descriptions-item label="场地权属证明">
<div v-if="project.ownershipProof">
<el-link type="primary" :href="project.ownershipProof" target="_blank">查看文件</el-link>
</div>
<span v-else>-</span>
</el-descriptions-item>
<el-descriptions-item label="项目备案">
<div v-if="project.projectRecord">
<el-link type="primary" :href="project.projectRecord" target="_blank">查看文件</el-link>
</div>
<span v-else>-</span>
</el-descriptions-item>
<el-descriptions-item label="其他补充材料">
<div v-if="supplementaryMaterials.length">
<div v-for="(file, index) in supplementaryMaterials" :key="index">
<el-link type="primary" :href="file" target="_blank">文件{{ index + 1 }}</el-link>
</div>
</div>
<span v-else>-</span>
</el-descriptions-item>
</el-descriptions>
</ContentWrap>
</template>
<script setup lang="ts">
import { ProjectVO } from '@/api/crm/project'
const props = defineProps<{
project: ProjectVO
}>()
const supplementaryMaterials = computed(() => {
if (!props.project.supplementaryMaterials) return []
if (Array.isArray(props.project.supplementaryMaterials)) return props.project.supplementaryMaterials
return (props.project.supplementaryMaterials as string).split(',')
})
</script>

View File

@ -0,0 +1,179 @@
<template>
<ContentWrap>
<el-table v-loading="loading" :data="contractList" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="合同类型" fixed="left" align="center" width="120">
<template #default="scope">
{{ scope.row.contractTypeName }}
</template>
</el-table-column>
<el-table-column label="合同名称" align="center" prop="name" min-width="200">
<template #default="scope">
<el-link v-if="scope.row.id" type="primary" :underline="false" @click="openDetail(scope.row.id)">
{{ scope.row.name || '-' }}
</el-link>
<span v-else>{{ scope.row.name || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="合同编号" align="center" prop="no" width="150" />
<el-table-column
label="合同金额(元)"
align="center"
prop="totalPrice"
width="150"
:formatter="erpPriceTableColumnFormatter"
/>
<el-table-column
label="开始时间"
align="center"
prop="startTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column
label="结束时间"
align="center"
prop="endTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column align="center" label="状态" prop="auditStatus" width="100">
<template #default="scope">
<dict-tag v-if="scope.row.auditStatus !== undefined" :type="DICT_TYPE.CRM_AUDIT_STATUS" :value="scope.row.auditStatus" />
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column align="center" fixed="right" label="操作" width="120">
<template #default="scope">
<el-button
v-if="scope.row.id"
v-hasPermi="['crm:contract:update']"
link
type="primary"
@click="openForm('update', scope.row.id)"
>
编辑
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 合作投资协议如果是合作投资项目 -->
<div v-if="project.type === 2 && project.investmentAgreement" class="mt-20px">
<el-divider>合作投资协议</el-divider>
<el-link type="primary" :href="project.investmentAgreement" target="_blank">查看文件</el-link>
</div>
</ContentWrap>
<!-- 表单弹窗编辑 -->
<ContractForm ref="formRef" @success="loadContracts" />
</template>
<script setup lang="ts">
import { ProjectVO } from '@/api/crm/project'
import * as ContractApi from '@/api/crm/contract'
import ContractForm from '@/views/crm/contract/ContractForm.vue'
import { dateFormatter } from '@/utils/formatTime'
import { DICT_TYPE } from '@/utils/dict'
import { erpPriceTableColumnFormatter } from '@/utils'
const props = defineProps<{
project: ProjectVO
}>()
const loading = ref(false)
const contractList = ref<any[]>([])
const formRef = ref()
/** 加载合同信息 */
const loadContracts = async () => {
loading.value = true
try {
const contracts: any[] = []
//
if (props.project.cooperationAgreement) {
try {
const contract = await ContractApi.getContract(props.project.cooperationAgreement)
contracts.push({
...contract,
contractTypeName: '场地合同',
contractTypeId: 1
})
} catch (error) {
// ID
contracts.push({
id: props.project.cooperationAgreement,
name: props.project.cooperationAgreementName || `合同ID: ${props.project.cooperationAgreement}`,
contractTypeName: '场地合同',
contractTypeId: 1
})
}
}
// EPC
if (props.project.epcContractId) {
try {
const contract = await ContractApi.getContract(props.project.epcContractId)
contracts.push({
...contract,
contractTypeName: 'EPC合同',
contractTypeId: 2
})
} catch (error) {
contracts.push({
id: props.project.epcContractId,
name: props.project.epcContractName || `合同ID: ${props.project.epcContractId}`,
contractTypeName: 'EPC合同',
contractTypeId: 2
})
}
}
//
if (props.project.purchaseContractId) {
try {
const contract = await ContractApi.getContract(props.project.purchaseContractId)
contracts.push({
...contract,
contractTypeName: '设备合同',
contractTypeId: 3
})
} catch (error) {
contracts.push({
id: props.project.purchaseContractId,
name: props.project.purchaseContractName || `合同ID: ${props.project.purchaseContractId}`,
contractTypeName: '设备合同',
contractTypeId: 3
})
}
}
contractList.value = contracts
} finally {
loading.value = false
}
}
/** 打开合同详情 */
const { push } = useRouter()
const openDetail = (id: number) => {
push({ name: 'CrmContractDetail', params: { id } })
}
/** 打开表单 */
const openForm = (type = 'update', id?: number) => {
formRef.value?.open(type, id)
}
/** 监听项目变化,重新加载合同 */
watch(
() => props.project,
() => {
if (props.project?.id) {
loadContracts()
}
},
{ immediate: true, deep: true }
)
</script>

View File

@ -0,0 +1,65 @@
<template>
<ProjectDetailsHeader :loading="loading" :project="project">
<el-button @click="openForm('update', project.id)" type="primary">
编辑
</el-button>
</ProjectDetailsHeader>
<el-col>
<el-tabs>
<el-tab-pane label="合同信息">
<ProjectContractInfo :project="project" />
</el-tab-pane>
<el-tab-pane label="附件">
<ProjectAttachmentInfo :project="project" />
</el-tab-pane>
</el-tabs>
</el-col>
<!-- 表单弹窗添加/修改 -->
<ProjectForm ref="formRef" @success="getProjectData(id)" />
</template>
<script lang="ts" setup>
import { useTagsViewStore } from '@/store/modules/tagsView'
import * as ProjectApi from '@/api/crm/project'
import ProjectDetailsHeader from './projectDetailHeader.vue'
import ProjectContractInfo from './ProjectContractInfo.vue'
import ProjectAttachmentInfo from './ProjectAttachmentInfo.vue'
import ProjectForm from '../ProjectForm.vue'
defineOptions({ name: 'CrmProjectDetail' })
const route = useRoute()
const message = useMessage()
const id = Number(route.params.id) //
const loading = ref(true) //
const project = ref<ProjectApi.ProjectVO>({} as ProjectApi.ProjectVO) //
/** 获取详情 */
const getProjectData = async (id: number) => {
loading.value = true
try {
project.value = await ProjectApi.getProject(id)
} finally {
loading.value = false
}
}
/** 编辑 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 初始化 */
const { delView } = useTagsViewStore() //
const { currentRoute } = useRouter() //
onMounted(async () => {
if (!id) {
message.warning('参数错误,项目不能为空!')
delView(unref(currentRoute))
return
}
await getProjectData(id)
})
</script>

View File

@ -0,0 +1,98 @@
<template>
<div>
<div class="flex items-start justify-between">
<div>
<el-col>
<el-row>
<span class="text-xl font-bold">{{ project.name || '项目详情' }}</span>
</el-row>
</el-col>
</div>
<div>
<!-- 右上按钮 -->
<slot></slot>
</div>
</div>
</div>
<ContentWrap class="mt-10px">
<el-descriptions :column="4" direction="horizontal" class="mb-10px">
<el-descriptions-item label="场地名称:">
{{ siteName }}
</el-descriptions-item>
<el-descriptions-item label="项目总投资额:">
{{ project.totalInvestment }} 万元
</el-descriptions-item>
<el-descriptions-item label="状态:">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="project.projectStatus" />
</el-descriptions-item>
<el-descriptions-item label="类型:">
<dict-tag :type="DICT_TYPE.CRM_PROJECT_TYPE" :value="project.type" />
</el-descriptions-item>
<el-descriptions-item label="场地枪数:">
{{ project.estimatedPiles }}
</el-descriptions-item>
<el-descriptions-item label="部门:">
{{ deptName }}
</el-descriptions-item>
<el-descriptions-item label="供电类型:">
<dict-tag :type="DICT_TYPE.CRM_PROJECT_POWER_SUPPLY_TYPE" :value="project.powerSupplyType" />
</el-descriptions-item>
<el-descriptions-item label="供电答复单:" v-if="project.powerSupplyType === 2">
<div v-if="project.powerSupplyReply">
<el-link type="primary" :href="project.powerSupplyReply" target="_blank">查看文件</el-link>
</div>
<span v-else>-</span>
</el-descriptions-item>
</el-descriptions>
<div v-if="project.type === 2 && project.partners && project.partners.length" class="mt-20px">
<el-divider>合伙人明细</el-divider>
<el-table :data="project.partners" border style="width: 100%">
<el-table-column prop="userId" label="合伙人ID" width="120" />
<el-table-column prop="ratio" label="出资比例(%)" />
<el-table-column prop="investment" label="出资金额(万元)" />
</el-table>
</div>
</ContentWrap>
</template>
<script lang="ts" setup>
import * as ProjectApi from '@/api/crm/project'
import * as StationSiteApi from '@/api/crm/stationsite'
import * as DeptApi from '@/api/system/dept'
import { DICT_TYPE } from '@/utils/dict'
const props = defineProps<{
project: ProjectApi.ProjectVO
loading: boolean
}>()
const siteName = ref('')
const deptName = ref('')
watch(
() => props.project.siteId,
async (siteId) => {
if (siteId) {
const site = await StationSiteApi.getStationSite(siteId)
siteName.value = site.name
} else {
siteName.value = ''
}
},
{ immediate: true }
)
watch(
() => props.project.deptId,
async (deptId) => {
if (deptId) {
const dept = await DeptApi.getDept(deptId)
deptName.value = dept.name
} else {
deptName.value = ''
}
},
{ immediate: true }
)
</script>

View File

@ -75,7 +75,19 @@
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column align="center" label="项目名称" prop="name" min-width="300"/>
<el-table-column align="center" label="项目名称" prop="name" min-width="300">
<template #default="{ row }">
<el-link
v-if="row.id"
type="primary"
@click="openProjectDetail(row.id)"
:underline="false"
>
{{ row.name }}
</el-link>
<span v-else>{{ row.name }}</span>
</template>
</el-table-column>
<el-table-column label="项目总投资额(万元)" align="center" prop="totalInvestment" width="180"/>
<el-table-column label="枪数" align="center" prop="estimatedPiles" />
@ -213,6 +225,11 @@ const { push } = useRouter()
const openSiteDetail = (id: number) => {
push({ name: 'CrmStationSiteDetail', params: { id } })
}
/** 打开项目详情 */
const openProjectDetail = (id: number) => {
push({ name: 'CrmProjectDetail', params: { id: String(id) } })
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
@ -337,4 +354,4 @@ onMounted(async () => {
//
deptList.value = await DeptApi.querySimpleDeptList(3)
})
</script>
</script>