Files
tjt_czjs_ui/src/views/tjt/project/index.vue

544 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<ContentWrap>
<el-form
ref="queryFormRef"
:inline="true"
:model="queryParams"
class="-mb-15px"
label-width="88px"
>
<el-form-item label="项目名称" prop="projectName">
<el-input
v-model="queryParams.projectName"
class="!w-240px"
clearable
placeholder="请输入项目名称"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="是否签约" prop="contractSignedFlag">
<el-select
v-model="queryParams.contractSignedFlag"
class="!w-180px"
clearable
placeholder="请选择"
>
<el-option
v-for="item in contractSignOptions"
:key="String(item.value)"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="开始年度" prop="projectStartYear">
<el-date-picker
v-model="queryProjectStartYearValue"
class="!w-180px"
clearable
placeholder="请选择年度"
type="year"
value-format="YYYY"
/>
</el-form-item>
<el-form-item label="项目状态" prop="projectStatus">
<el-select
v-model="queryParams.projectStatus"
class="!w-180px"
clearable
placeholder="请选择项目状态"
>
<el-option
v-for="item in projectStatusOptions"
:key="String(item.value)"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-260px"
end-placeholder="结束时间"
start-placeholder="开始时间"
type="datetimerange"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon class="mr-5px" icon="ep:search" />
搜索
</el-button>
<el-button @click="resetQuery">
<Icon class="mr-5px" icon="ep:refresh" />
重置
</el-button>
<el-button
v-hasPermi="['tjt:project:create']"
plain
type="primary"
@click="openProjectForm('create')"
>
<Icon class="mr-5px" icon="ep:plus" />
新增项目
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<ContentWrap>
<el-table
ref="projectTableRef"
v-loading="loading"
:data="list"
highlight-current-row
@current-change="handleCurrentProjectChange"
>
<el-table-column
:index="getProjectRowIndex"
align="center"
label="序号"
type="index"
width="80"
/>
<el-table-column align="center" label="项目名称" min-width="220" prop="projectName" />
<el-table-column align="center" label="项目经理" min-width="140" prop="projectManagerName" />
<el-table-column
align="center"
label="工程负责人"
min-width="140"
prop="engineeringPrincipalName"
/>
<el-table-column align="center" label="开始年度" prop="projectStartYear" width="110" />
<el-table-column align="center" label="项目状态" width="110">
<template #default="scope">
<el-tag :type="projectStatusTagType(scope.row.projectStatus)">
{{ getProjectStatusText(scope.row.projectStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="是否封档" width="90">
<template #default="scope">
{{ scope.row.archiveFlag ? '是' : '否' }}
</template>
</el-table-column>
<el-table-column align="center" label="创建时间" prop="createTime" width="180">
<template #default="scope">
{{ formatDate(scope.row.createTime as any) }}
</template>
</el-table-column>
<el-table-column align="center" label="排序" prop="sortNo" width="80" />
<el-table-column align="center" fixed="right" label="操作" width="150">
<template #default="scope">
<el-button
v-hasPermi="['tjt:project:update']"
link
type="primary"
@click="openProjectForm('update', scope.row.id)"
>
编辑
</el-button>
<el-button
v-hasPermi="['tjt:project:delete']"
link
type="danger"
@click="handleDeleteProject(scope.row.id)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<Pagination
v-model:limit="queryParams.pageSize"
v-model:page="queryParams.pageNo"
:total="total"
@pagination="getList"
/>
</ContentWrap>
<ContentWrap v-if="currentProject">
<div class="mb-16px flex items-center justify-between gap-16px">
<div>
<div class="text-16px font-600">{{ currentProject.projectName }}</div>
<div class="mt-4px text-13px text-[var(--el-text-color-secondary)]">
建设单位{{ currentProject.constructionUnitName || '-' }}项目经理{{
currentProject.projectManagerName || '-'
}}工程负责人{{ currentProject.engineeringPrincipalName || '-' }}
</div>
</div>
<el-button
v-hasPermi="['tjt:planning:create']"
plain
type="primary"
@click="openPlanningForm('create')"
>
<Icon class="mr-5px" icon="ep:plus" />
新增合约规划
</el-button>
</div>
<el-row :gutter="16">
<el-col :span="8">
<el-descriptions :column="1" border title="项目概况">
<el-descriptions-item label="项目名称">{{ currentProject.projectName }}</el-descriptions-item>
<el-descriptions-item label="工程类型">
{{ getProjectTypeText(currentProject.projectType) }}
</el-descriptions-item>
<el-descriptions-item label="设计类型">
{{ getProjectCategoryText(currentProject.projectCategory) }}
</el-descriptions-item>
<el-descriptions-item label="合同签订日期">
{{ currentProject.contractSigningDate || '-' }}
</el-descriptions-item>
<el-descriptions-item label="建设单位联系人">
{{ currentProject.contactName || '-' }}
</el-descriptions-item>
<el-descriptions-item label="建设单位联系电话">
{{ currentProject.contactPhone || '-' }}
</el-descriptions-item>
<el-descriptions-item label="项目状态">
{{ getProjectStatusText(currentProject.projectStatus) }}
</el-descriptions-item>
<el-descriptions-item label="封档时间">
{{ currentProject.archiveTime ? formatDate(currentProject.archiveTime as any) : '-' }}
</el-descriptions-item>
<el-descriptions-item
v-if="normalizeProjectStatus(currentProject.projectStatus) === pausedStatusValue"
label="暂停原因"
>
{{ currentProject.pauseReason || '-' }}
</el-descriptions-item>
<el-descriptions-item
v-if="normalizeProjectStatus(currentProject.projectStatus) === terminatedStatusValue"
label="中止原因"
>
{{ currentProject.terminateReason || '-' }}
</el-descriptions-item>
<el-descriptions-item label="合同总产值(元)">
{{ formatAmountText(currentProject.contractAmount) }}
</el-descriptions-item>
<el-descriptions-item label="建筑面积(m²)">
{{ formatAreaText(currentProject.totalConstructionArea) }}
</el-descriptions-item>
</el-descriptions>
</el-col>
<el-col :span="16">
<el-table v-loading="planningLoading" :data="planningList">
<el-table-column align="center" label="序号" type="index" width="70" />
<el-table-column
align="center"
label="项目任务包"
min-width="260"
prop="planningContent"
show-overflow-tooltip
/>
<el-table-column align="center" label="归属类型" width="100">
<template #default="scope">
{{ getOwnershipTypeText(scope.row.ownershipType) }}
</template>
</el-table-column>
<el-table-column align="center" label="合同产值数量" width="130">
<template #default="scope">
{{ scope.row.contractValueQuantity ?? '-' }}
</template>
</el-table-column>
<el-table-column align="center" label="合同产值单价" width="130">
<template #default="scope">
{{ formatAmountText(scope.row.contractValueUnitPrice) }}
</template>
</el-table-column>
<el-table-column align="center" label="分项合同产值(元)" width="130">
<template #default="scope">
{{ formatAmountText(scope.row.planningAmount) }}
</template>
</el-table-column>
<el-table-column align="center" label="管理费率" width="110">
<template #default="scope">
{{ formatPercentText(scope.row.managementFeeRate) }}
</template>
</el-table-column>
<el-table-column align="center" label="管理费(元)" width="120">
<template #default="scope">
{{ formatAmountText(scope.row.managementFee) }}
</template>
</el-table-column>
<el-table-column align="center" label="增值税率" width="100">
<template #default="scope">
{{ formatPercentText(scope.row.vatRate) }}
</template>
</el-table-column>
<el-table-column align="center" label="增值税(元)" width="120">
<template #default="scope">
{{ formatAmountText(scope.row.vatAmount) }}
</template>
</el-table-column>
<el-table-column align="center" label="项目预算产值(元)" width="140">
<template #default="scope">
{{ formatAmountText(scope.row.projectBudgetOutputValue) }}
</template>
</el-table-column>
<el-table-column
align="center"
label="意向实施团队"
min-width="140"
prop="implementationTeam"
show-overflow-tooltip
/>
<el-table-column align="center" label="排序" prop="sortNo" width="80" />
<el-table-column align="center" fixed="right" label="操作" width="140">
<template #default="scope">
<el-button
v-hasPermi="['tjt:planning:update']"
link
type="primary"
@click="openPlanningForm('update', scope.row.id)"
>
编辑
</el-button>
<el-button
v-hasPermi="['tjt:planning:delete']"
link
type="danger"
@click="handleDeletePlanning(scope.row.id)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</el-col>
</el-row>
</ContentWrap>
<ContentWrap v-else>
<el-empty description="请选择项目后查看合约规划" />
</ContentWrap>
<ProjectForm ref="projectFormRef" @success="handleProjectFormSuccess" />
<PlanningForm ref="planningFormRef" @success="handlePlanningFormSuccess" />
</template>
<script lang="ts" setup>
import { formatDate } from '@/utils/formatTime'
import * as ProjectApi from '@/api/tjt/project'
import * as PlanningApi from '@/api/tjt/planning'
import ProjectForm from './ProjectForm.vue'
import PlanningForm from './PlanningForm.vue'
import {
CONTRACT_SIGN_OPTIONS,
OWNERSHIP_TYPE_OPTIONS,
PROJECT_CATEGORY_OPTIONS,
PROJECT_STATUS_OPTIONS,
PROJECT_TYPE_OPTIONS,
formatAmountText,
formatAreaText,
formatPercentText,
normalizeProjectStatus
} from '@/views/tjt/shared/planning'
defineOptions({ name: 'TjtProject' })
const message = useMessage()
const { t } = useI18n()
const cleanOptionLabels = <T,>(options: Array<{ label: string; value: T }>, labels: string[]) =>
options.map((item, index) => ({
label: labels[index] || item.label,
value: item.value
}))
const contractSignOptions = cleanOptionLabels(CONTRACT_SIGN_OPTIONS, ['已签约', '未签约'])
const ownershipTypeOptions = OWNERSHIP_TYPE_OPTIONS
const projectTypeOptions = cleanOptionLabels(PROJECT_TYPE_OPTIONS, [
'建筑工程',
'精装工程',
'综合工程',
'专项设计',
'BIM设计',
'其他'
])
const projectCategoryOptions = cleanOptionLabels(PROJECT_CATEGORY_OPTIONS, [
'住宅',
'公建',
'工业',
'园林景观',
'其他'
])
const projectStatusOptions = cleanOptionLabels(PROJECT_STATUS_OPTIONS, [
'进行中',
'已完成',
'已暂停',
'已中止'
])
const completedStatusValue = projectStatusOptions[1]?.value
const pausedStatusValue = projectStatusOptions[2]?.value
const terminatedStatusValue = projectStatusOptions[3]?.value
const getOptionLabel = <T,>(options: Array<{ label: string; value: T }>, value?: T) =>
options.find((item) => item.value === value)?.label || '-'
const getOwnershipTypeText = (value?: string) => getOptionLabel(ownershipTypeOptions, value)
const getProjectTypeText = (value?: string) => getOptionLabel(projectTypeOptions, value)
const getProjectCategoryText = (value?: string) => getOptionLabel(projectCategoryOptions, value)
const getProjectStatusText = (value?: string) => getOptionLabel(projectStatusOptions, value)
const loading = ref(false)
const planningLoading = ref(false)
const total = ref(0)
const list = ref<ProjectApi.ProjectVO[]>([])
const planningList = ref<PlanningApi.ProjectPlanningVO[]>([])
const currentProject = ref<ProjectApi.ProjectVO>()
const queryFormRef = ref()
const projectTableRef = ref()
const queryParams = reactive<ProjectApi.ProjectPageReqVO>({
pageNo: 1,
pageSize: 10,
projectName: undefined,
contractSignedFlag: undefined,
projectStartYear: undefined,
projectStatus: undefined,
createTime: []
})
const getProjectRowIndex = (index: number) =>
((queryParams.pageNo ?? 1) - 1) * (queryParams.pageSize ?? 10) + index + 1
const queryProjectStartYearValue = computed({
get: () => (queryParams.projectStartYear ? String(queryParams.projectStartYear) : undefined),
set: (value?: string) => {
queryParams.projectStartYear = value ? Number(value) : undefined
}
})
const projectStatusTagType = (status?: string) => {
const normalizedStatus = normalizeProjectStatus(status)
if (normalizedStatus === completedStatusValue) {
return 'success'
}
if (normalizedStatus === pausedStatusValue) {
return 'warning'
}
if (normalizedStatus === terminatedStatusValue) {
return 'danger'
}
return 'primary'
}
const getList = async () => {
loading.value = true
try {
const data = await ProjectApi.getProjectPage(queryParams)
list.value = data.list
total.value = data.total
if (!list.value.length) {
currentProject.value = undefined
planningList.value = []
return
}
const targetProjectId = currentProject.value?.id || list.value[0].id
const targetProject = list.value.find((item) => item.id === targetProjectId) || list.value[0]
await nextTick()
projectTableRef.value?.setCurrentRow(targetProject)
} finally {
loading.value = false
}
}
const getPlanningList = async () => {
if (!currentProject.value?.id) {
planningList.value = []
return
}
planningLoading.value = true
try {
planningList.value = await PlanningApi.getProjectPlanningListByProjectId(currentProject.value.id)
} finally {
planningLoading.value = false
}
}
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
const resetQuery = () => {
queryFormRef.value?.resetFields()
handleQuery()
}
const handleCurrentProjectChange = async (row?: ProjectApi.ProjectVO) => {
currentProject.value = row || undefined
await getPlanningList()
}
const projectFormRef = ref()
const openProjectForm = (type: 'create' | 'update', id?: number) => {
projectFormRef.value.open(type, id)
}
const planningFormRef = ref()
const openPlanningForm = (type: string, id?: number) => {
if (type === 'create') {
if (!currentProject.value?.id) {
message.warning('请先选择项目')
return
}
planningFormRef.value.open(type, {
projectId: currentProject.value.id,
defaultPlanningAmount: currentProject.value.contractAmount
})
return
}
planningFormRef.value.open(type, { id })
}
const handleDeleteProject = async (id: number) => {
try {
await message.delConfirm()
await ProjectApi.deleteProject(id)
message.success(t('common.delSuccess'))
await getList()
} catch {}
}
const handleDeletePlanning = async (id: number) => {
try {
await message.delConfirm()
await PlanningApi.deleteProjectPlanning(id)
message.success(t('common.delSuccess'))
await getPlanningList()
} catch {}
}
const handleProjectFormSuccess = async () => {
await getList()
}
const handlePlanningFormSuccess = async () => {
await getPlanningList()
}
let activatedOnce = false
onMounted(() => {
getList()
})
onActivated(() => {
// KeepAlive 首次挂载也会触发 onActivated跳过首轮避免重复请求列表。
if (!activatedOnce) {
activatedOnce = true
return
}
getList()
})
</script>