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

654 lines
23 KiB
Vue
Raw Normal View History

2026-04-17 18:17:42 +08:00
<template>
<ContentWrap>
<el-form
ref="queryFormRef"
:inline="true"
:model="queryParams"
class="-mb-15px"
label-width="88px"
>
2026-05-08 17:38:50 +08:00
<el-form-item label="项目名称" prop="projectName">
2026-04-17 18:17:42 +08:00
<el-input
v-model="queryParams.projectName"
class="!w-240px"
clearable
2026-05-08 17:38:50 +08:00
placeholder="请输入项目名称"
2026-04-17 18:17:42 +08:00
@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 CONTRACT_SIGN_OPTIONS"
: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>
<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-form-item>
</el-form>
</ContentWrap>
<ContentWrap>
<el-table
ref="projectTableRef"
v-loading="loading"
:data="projectList"
highlight-current-row
@current-change="handleCurrentProjectChange"
>
<el-table-column
2026-05-08 17:38:50 +08:00
:index="getProjectRowIndex"
2026-04-17 18:17:42 +08:00
align="center"
2026-05-08 17:38:50 +08:00
label="序号"
type="index"
width="80"
/>
<el-table-column
align="center"
label="项目名称"
2026-04-17 18:17:42 +08:00
min-width="220"
prop="projectName"
show-overflow-tooltip
/>
<el-table-column align="center" label="是否签约" width="100">
<template #default="scope">
<el-tag :type="scope.row.contractSignedFlag ? 'success' : 'info'">
{{ scope.row.contractSignedFlag ? '已签约' : '未签约' }}
</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="项目开始年度" prop="projectStartYear" width="120" />
2026-05-08 17:38:50 +08:00
<el-table-column align="center" label="合同产值(元)" width="130">
2026-04-17 18:17:42 +08:00
<template #default="scope">
{{ formatAmountText(scope.row.contractAmount) }}
</template>
</el-table-column>
<el-table-column align="center" label="工程总面积(㎡)" width="140">
<template #default="scope">
{{ formatAreaText(scope.row.totalConstructionArea) }}
</template>
</el-table-column>
<el-table-column
:formatter="dateFormatter"
align="center"
label="创建时间"
prop="createTime"
width="180"
/>
2026-05-08 17:38:50 +08:00
<el-table-column align="center" label="排序" prop="sortNo" width="80" />
2026-04-17 18:17:42 +08:00
</el-table>
<Pagination
v-model:limit="queryParams.pageSize"
v-model:page="queryParams.pageNo"
:total="total"
@pagination="getProjectList"
/>
</ContentWrap>
<el-row :gutter="16">
<el-col :span="9">
<ContentWrap>
<div class="mb-12px flex items-center justify-between">
<div class="text-14px font-600">
{{ currentProject?.projectName || '合约规划列表' }}
</div>
<el-button v-if="currentProject" size="small" @click="getPlanningList">刷新</el-button>
</div>
<el-table
ref="planningTableRef"
v-loading="planningLoading"
:data="planningList"
highlight-current-row
@current-change="handleCurrentPlanningChange"
>
2026-05-08 17:38:50 +08:00
<el-table-column align="center" label="序号" type="index" width="70" />
2026-04-17 18:17:42 +08:00
<el-table-column
align="center"
2026-05-08 17:38:50 +08:00
label="项目任务包"
2026-04-17 18:17:42 +08:00
min-width="170"
prop="planningContent"
show-overflow-tooltip
/>
2026-04-25 18:10:45 +08:00
<el-table-column align="center" label="归属类型" width="100">
<template #default="scope">
{{ getOwnershipTypeLabel(scope.row.ownershipType) }}
</template>
</el-table-column>
<el-table-column align="center" label="计算方式" width="110">
<template #default="scope">
{{ getCalculationMethodLabel(scope.row.calculationMethod) }}
</template>
</el-table-column>
2026-05-08 17:38:50 +08:00
<el-table-column align="center" label="分项合同产值(元)" width="120">
2026-04-17 18:17:42 +08:00
<template #default="scope">
{{ formatAmountText(scope.row.planningAmount) }}
</template>
</el-table-column>
<el-table-column align="center" label="总分配" width="100">
<template #default="scope">
{{ formatPercentText(scope.row.totalDistributionAmount) }}
</template>
</el-table-column>
<el-table-column align="center" label="考核产值(元)" width="120">
<template #default="scope">
{{ formatAmountText(scope.row.assessmentOutputValue) }}
</template>
</el-table-column>
2026-05-08 17:38:50 +08:00
<el-table-column align="center" label="排序" prop="sortNo" width="80" />
2026-04-17 18:17:42 +08:00
</el-table>
</ContentWrap>
</el-col>
<el-col :span="15">
<ContentWrap v-if="currentPlanning">
<div class="mb-16px flex items-center justify-between gap-16px">
<div>
<div class="text-16px font-600">{{ currentPlanning.planningContent }}</div>
<div class="mt-4px text-13px text-[var(--el-text-color-secondary)]">
2026-04-25 18:10:45 +08:00
{{ getOwnershipTypeLabel(currentPlanning.ownershipType) }} /
{{ getCalculationMethodLabel(currentPlanning.calculationMethod) }}
2026-04-17 18:17:42 +08:00
</div>
</div>
<div class="flex items-center gap-12px">
<el-button
v-hasPermi="['tjt:planning:update']"
plain
type="primary"
@click="openPlanningOutputForm"
>
<Icon class="mr-5px" icon="ep:edit" />
编辑测算参数
</el-button>
<el-button
v-hasPermi="['tjt:planning:update', 'tjt:planning-quarter:update', 'tjt:planning-quarter:create']"
v-if="false"
plain
type="primary"
@click="openQuarterDistributionForm"
>
<Icon class="mr-5px" icon="ep:edit-pen" />
编辑季度分配
</el-button>
</div>
</div>
<el-descriptions :column="2" border title="测算参数">
2026-05-08 17:38:50 +08:00
<el-descriptions-item label="分项合同产值(元)">
2026-04-17 18:17:42 +08:00
{{ formatAmountText(currentPlanning.planningAmount) }}
</el-descriptions-item>
<el-descriptions-item label="管理费费率">
{{ formatPercentText(currentPlanning.managementFeeRate) }}
</el-descriptions-item>
<el-descriptions-item label="管理费(元)">
{{ formatAmountText(currentPlanning.managementFee) }}
</el-descriptions-item>
2026-05-08 17:38:50 +08:00
<el-descriptions-item label="意向实施团队">
2026-04-17 18:17:42 +08:00
{{ currentPlanning.implementationTeam || '-' }}
</el-descriptions-item>
<el-descriptions-item label="开始年度">
{{ currentPlanning.planningStartYear || '-' }}
</el-descriptions-item>
<el-descriptions-item label="面积(㎡)">
{{ formatAreaText(currentPlanning.planningArea) }}
</el-descriptions-item>
<el-descriptions-item label="合同单价(元/㎡)">
{{ formatAmountText(currentPlanning.contractUnitPrice) }}
</el-descriptions-item>
<el-descriptions-item label="设计阶段">
2026-04-25 18:10:45 +08:00
{{ getDesignStageLabel(currentPlanning.designStage) }}
2026-04-17 18:17:42 +08:00
</el-descriptions-item>
<el-descriptions-item label="本次设计阶段比例">
{{ formatPercentText(currentPlanning.currentDesignStageRatio) }}
</el-descriptions-item>
<el-descriptions-item label="审核审定是否外包">
{{ currentPlanning.reviewOutsourceFlag ? '是' : '否' }}
</el-descriptions-item>
<el-descriptions-item label="审核审定占比">
{{ formatPercentText(currentPlanning.reviewOutsourceRatio) }}
</el-descriptions-item>
<el-descriptions-item v-if="showCalculationRatioField" :label="calculationRatioLabel">
{{ formatPercentText(currentPlanning.calculationRatio) }}
</el-descriptions-item>
<el-descriptions-item
2026-04-29 15:44:00 +08:00
v-if="showParentBuildingOrUnitCount"
2026-04-17 18:17:42 +08:00
label="楼栋数/户型数"
>
{{ currentPlanning.buildingOrUnitCount }}
</el-descriptions-item>
2026-04-29 15:44:00 +08:00
<el-descriptions-item v-if="showParentMajorFactorFields" label="套图系数">
2026-04-17 18:17:42 +08:00
{{ formatFactorText(currentPlanning.drawingSetFactor, 2) }}
</el-descriptions-item>
2026-04-29 15:44:00 +08:00
<el-descriptions-item v-if="showParentMajorFactorFields" label="规模系数">
2026-04-17 18:17:42 +08:00
{{ formatFactorText(currentPlanning.scaleFactor, 2) }}
</el-descriptions-item>
2026-04-29 15:44:00 +08:00
<el-descriptions-item v-if="showParentMajorFactorFields" label="修改系数">
2026-04-17 18:17:42 +08:00
{{ formatFactorText(currentPlanning.modificationFactor, 2) }}
</el-descriptions-item>
2026-04-29 15:44:00 +08:00
<el-descriptions-item v-if="showParentMajorFactorFields" label="复杂系数/复杂等级">
2026-04-17 18:17:42 +08:00
{{ formatPercentText(currentPlanning.complexityFactor) }}
</el-descriptions-item>
<el-descriptions-item
2026-04-29 15:44:00 +08:00
v-if="showParentInternalGuidanceUnitPrice"
2026-04-17 18:17:42 +08:00
label="内部指导单价(元/㎡)"
>
{{ formatAmountText(currentPlanning.internalGuidanceUnitPrice) }}
</el-descriptions-item>
<el-descriptions-item
v-if="currentPlanning.virtualCalculationMethod"
label="虚拟产值计算方式"
>
2026-04-25 18:10:45 +08:00
{{ getVirtualCalculationMethodLabel(currentPlanning.virtualCalculationMethod) }}
2026-04-17 18:17:42 +08:00
</el-descriptions-item>
2026-04-25 18:10:45 +08:00
<el-descriptions-item
v-if="currentPlanning.guidanceUnitPrice"
label="指导单价(元/㎡)"
>
2026-04-17 18:17:42 +08:00
{{ formatAmountText(currentPlanning.guidanceUnitPrice) }}
</el-descriptions-item>
<el-descriptions-item v-if="currentPlanning.guidanceTotalPrice" label="指导总价(元)">
{{ formatAmountText(currentPlanning.guidanceTotalPrice) }}
</el-descriptions-item>
<el-descriptions-item v-if="currentPlanning.workingDayCount" label="工日">
{{ formatAmountText(currentPlanning.workingDayCount) }}
</el-descriptions-item>
<el-descriptions-item v-if="currentPlanning.workingDayUnitPrice" label="工日单价(元)">
{{ formatAmountText(currentPlanning.workingDayUnitPrice) }}
</el-descriptions-item>
</el-descriptions>
<el-descriptions :column="2" border class="mt-16px" title="计算结果">
2026-04-29 15:44:00 +08:00
<el-descriptions-item v-if="!showGuideDetailScene" label="合计调整系数">
2026-04-17 18:17:42 +08:00
{{ formatFactorText(currentPlanning.totalAdjustmentFactor) }}
</el-descriptions-item>
<el-descriptions-item label="考核面积(㎡)">
{{ formatAreaText(currentPlanning.assessmentArea) }}
</el-descriptions-item>
<el-descriptions-item label="虚拟产值(元)">
{{ formatAmountText(currentPlanning.virtualOutputValue) }}
</el-descriptions-item>
<el-descriptions-item label="考核产值(元)">
{{ formatAmountText(currentPlanning.assessmentOutputValue) }}
</el-descriptions-item>
</el-descriptions>
</ContentWrap>
<ContentWrap v-if="currentPlanning">
<div class="mb-16px flex items-center justify-between gap-16px">
<div>
<div class="text-14px font-600">季度分配</div>
</div>
<el-button
v-hasPermi="['tjt:planning:update', 'tjt:planning-quarter:update', 'tjt:planning-quarter:create']"
plain
type="primary"
@click="openQuarterDistributionForm"
>
编辑季度分配
</el-button>
</div>
<el-row :gutter="16" class="mb-16px">
<el-col :span="6">
<div class="rounded-8px bg-[var(--el-fill-color-light)] px-16px py-12px">
<div class="text-12px text-[var(--el-text-color-secondary)]">总分配</div>
<div class="mt-6px text-18px font-600">
{{ formatPercentText(currentPlanning.totalDistributionAmount) }}
</div>
</div>
</el-col>
<el-col :span="6">
<div class="rounded-8px bg-[var(--el-fill-color-light)] px-16px py-12px">
<div class="text-12px text-[var(--el-text-color-secondary)]">已分配</div>
<div class="mt-6px text-18px font-600">
{{ formatPercentText(currentPlanning.allocatedAmount) }}
</div>
</div>
</el-col>
<el-col :span="6">
<div class="rounded-8px bg-[var(--el-fill-color-light)] px-16px py-12px">
<div class="text-12px text-[var(--el-text-color-secondary)]">待分配</div>
<div class="mt-6px text-18px font-600">
{{ formatPercentText(currentPlanning.pendingAmount) }}
</div>
</div>
</el-col>
<el-col :span="6">
<div class="rounded-8px bg-[var(--el-fill-color-light)] px-16px py-12px">
<div class="text-12px text-[var(--el-text-color-secondary)]">提取进度备注</div>
<div class="mt-6px min-h-44px text-13px leading-20px">
{{ currentPlanning.progressRemark || '-' }}
</div>
</div>
</el-col>
</el-row>
<el-table v-loading="quarterLoading" :data="quarterRows" border>
<el-table-column align="center" label="分配年度" width="150" prop="distributionYear" />
<el-table-column
v-for="quarter in QUARTER_OPTIONS"
:key="quarter.value"
:label="quarter.label"
min-width="220"
>
<template #default="scope">
<div class="flex flex-col gap-8px">
<div class="rounded-6px bg-[var(--el-fill-color-light)] px-10px py-8px text-12px">
分配比例{{ formatQuarterRatio(scope.row, quarter.value) }}
</div>
</div>
</template>
</el-table-column>
</el-table>
</ContentWrap>
<ContentWrap v-else>
<el-empty description="请选择合约规划后查看测算结果和季度分配" />
</ContentWrap>
</el-col>
</el-row>
<PlanningOutputForm ref="planningOutputFormRef" @success="handlePlanningOutputFormSuccess" />
<QuarterDistributionForm
ref="quarterDistributionFormRef"
@success="handlePlanningOutputFormSuccess"
/>
</template>
<script lang="ts" setup>
import { dateFormatter } from '@/utils/formatTime'
import * as ProjectApi from '@/api/tjt/project'
import * as PlanningApi from '@/api/tjt/planning'
import * as PlanningQuarterApi from '@/api/tjt/planningQuarter'
import PlanningOutputForm from './PlanningOutputForm.vue'
import QuarterDistributionForm from './QuarterDistributionForm.vue'
import {
CONTRACT_SIGN_OPTIONS,
QUARTER_OPTIONS,
formatAmountText,
formatAreaText,
formatPercentText,
2026-04-25 18:10:45 +08:00
getCalculationMethodLabel,
2026-04-17 18:17:42 +08:00
getCalculationRatioLabel,
2026-04-25 18:10:45 +08:00
getDesignStageLabel,
getOwnershipTypeLabel,
getVirtualCalculationMethodLabel,
2026-04-17 18:17:42 +08:00
isComprehensiveOwnership,
isContractPriceMethod,
isGuidancePriceMethod,
isMajorOwnership,
isSubcontractOwnership
} from '@/views/tjt/shared/planning'
defineOptions({ name: 'TjtOutput' })
interface QuarterYearRow {
distributionYear: number
quarters: PlanningQuarterApi.ProjectPlanningQuarterVO[]
}
const message = useMessage()
const loading = ref(false)
const planningLoading = ref(false)
const quarterLoading = ref(false)
const total = ref(0)
const projectList = ref<ProjectApi.ProjectVO[]>([])
const planningList = ref<PlanningApi.ProjectPlanningVO[]>([])
const currentProject = ref<ProjectApi.ProjectVO>()
const currentPlanning = ref<PlanningApi.ProjectPlanningVO>()
const quarterRows = ref<QuarterYearRow[]>([])
const queryFormRef = ref()
const projectTableRef = ref()
const planningTableRef = ref()
const queryParams = reactive<ProjectApi.ProjectPageReqVO>({
pageNo: 1,
pageSize: 10,
projectName: undefined,
contractSignedFlag: undefined,
projectStartYear: undefined
})
2026-05-08 17:38:50 +08:00
const getProjectRowIndex = (index: number) =>
(queryParams.pageNo - 1) * queryParams.pageSize + index + 1
2026-04-17 18:17:42 +08:00
const queryProjectStartYearValue = computed({
get: () => (queryParams.projectStartYear ? String(queryParams.projectStartYear) : undefined),
set: (value?: string) => {
queryParams.projectStartYear = value ? Number(value) : undefined
}
})
const calculationRatioLabel = computed(() =>
getCalculationRatioLabel(currentPlanning.value?.ownershipType)
)
const showCalculationRatioField = computed(
() =>
isComprehensiveOwnership(currentPlanning.value?.ownershipType) ||
isSubcontractOwnership(currentPlanning.value?.ownershipType)
)
2026-04-29 15:44:00 +08:00
const showGuideDetailScene = computed(
2026-04-17 18:17:42 +08:00
() =>
isMajorOwnership(currentPlanning.value?.ownershipType) &&
2026-04-29 15:44:00 +08:00
isGuidancePriceMethod(currentPlanning.value?.calculationMethod)
)
const showParentMajorFactorFields = computed(
() =>
isMajorOwnership(currentPlanning.value?.ownershipType) &&
isContractPriceMethod(currentPlanning.value?.calculationMethod)
)
const showParentBuildingOrUnitCount = computed(
() =>
showParentMajorFactorFields.value &&
currentPlanning.value?.buildingOrUnitCount !== undefined &&
currentPlanning.value?.buildingOrUnitCount !== null
)
const showParentInternalGuidanceUnitPrice = computed(
() =>
!showGuideDetailScene.value &&
currentPlanning.value?.internalGuidanceUnitPrice !== undefined &&
currentPlanning.value?.internalGuidanceUnitPrice !== null
2026-04-17 18:17:42 +08:00
)
const formatFactorText = (value?: number, digits = 4) => {
if (value === undefined || value === null) {
2026-04-29 15:44:00 +08:00
return '-'
2026-04-17 18:17:42 +08:00
}
return Number(value).toFixed(digits)
}
2026-04-25 18:10:45 +08:00
const getQuarterCell = (row: QuarterYearRow, quarterNo: number) => {
return row.quarters.find((item) => Number(item.quarterNo) === Number(quarterNo))
}
2026-04-17 18:17:42 +08:00
const formatQuarterRatio = (row: QuarterYearRow, quarterNo: number) => {
2026-04-25 18:10:45 +08:00
return formatPercentText(getQuarterCell(row, quarterNo)?.distributionRatio)
2026-04-17 18:17:42 +08:00
}
const buildQuarterRows = (
planning: PlanningApi.ProjectPlanningVO,
quarters: PlanningQuarterApi.ProjectPlanningQuarterVO[]
) => {
const yearSet = new Set<number>()
if (planning.planningStartYear) {
yearSet.add(planning.planningStartYear)
}
2026-04-25 18:10:45 +08:00
quarters.forEach((item) => {
const distributionYear = Number(item.distributionYear)
if (!Number.isNaN(distributionYear)) {
yearSet.add(distributionYear)
}
})
2026-04-17 18:17:42 +08:00
if (yearSet.size === 0) {
yearSet.add(new Date().getFullYear())
}
return Array.from(yearSet)
.sort((a, b) => a - b)
.map((distributionYear) => ({
distributionYear,
quarters: QUARTER_OPTIONS.map((option) => {
2026-04-25 18:10:45 +08:00
const quarterNo = Number(option.value)
2026-04-17 18:17:42 +08:00
const match = quarters.find(
(item) =>
2026-04-25 18:10:45 +08:00
Number(item.distributionYear) === distributionYear &&
Number(item.quarterNo) === quarterNo
2026-04-17 18:17:42 +08:00
)
return (
match || {
planningId: planning.id!,
distributionYear,
2026-04-25 18:10:45 +08:00
quarterNo,
2026-04-17 18:17:42 +08:00
distributionRatio: undefined,
distributionAmount: undefined
}
)
})
}))
}
const getProjectList = async () => {
loading.value = true
try {
const data = await ProjectApi.getProjectPage(queryParams)
projectList.value = data.list
total.value = data.total
if (!projectList.value.length) {
currentProject.value = undefined
planningList.value = []
currentPlanning.value = undefined
quarterRows.value = []
return
}
const targetProjectId = currentProject.value?.id || projectList.value[0].id
const targetProject =
projectList.value.find((item) => item.id === targetProjectId) || projectList.value[0]
await nextTick()
projectTableRef.value?.setCurrentRow(targetProject)
} finally {
loading.value = false
}
}
const getPlanningList = async () => {
if (!currentProject.value?.id) {
planningList.value = []
currentPlanning.value = undefined
quarterRows.value = []
return
}
planningLoading.value = true
try {
planningList.value = await PlanningApi.getProjectPlanningListByProjectId(currentProject.value.id)
if (!planningList.value.length) {
currentPlanning.value = undefined
quarterRows.value = []
return
}
const targetPlanningId = currentPlanning.value?.id || planningList.value[0].id
const targetPlanning =
planningList.value.find((item) => item.id === targetPlanningId) || planningList.value[0]
await nextTick()
planningTableRef.value?.setCurrentRow(targetPlanning)
} finally {
planningLoading.value = false
}
}
const loadPlanningDetail = async (planningId: number) => {
const planning = await PlanningApi.getProjectPlanning(planningId)
currentPlanning.value = planning
quarterLoading.value = true
try {
const quarterList = await PlanningQuarterApi.getProjectPlanningQuarterListByPlanningId(planningId)
quarterRows.value = buildQuarterRows(planning, quarterList)
} finally {
quarterLoading.value = false
}
}
const handleQuery = () => {
queryParams.pageNo = 1
getProjectList()
}
const resetQuery = () => {
queryFormRef.value?.resetFields()
handleQuery()
}
const handleCurrentProjectChange = async (row?: ProjectApi.ProjectVO) => {
currentProject.value = row || undefined
await getPlanningList()
}
const handleCurrentPlanningChange = async (row?: PlanningApi.ProjectPlanningVO) => {
if (!row?.id) {
currentPlanning.value = undefined
quarterRows.value = []
return
}
await loadPlanningDetail(row.id)
}
const planningOutputFormRef = ref()
const quarterDistributionFormRef = ref()
const openPlanningOutputForm = () => {
if (!currentPlanning.value?.id) {
message.warning('请先选择合约规划')
return
}
planningOutputFormRef.value.open(currentPlanning.value.id)
}
const openQuarterDistributionForm = () => {
if (!currentPlanning.value?.id) {
message.warning('请先选择合约规划')
return
}
quarterDistributionFormRef.value.open(currentPlanning.value.id)
}
const handlePlanningOutputFormSuccess = async () => {
const currentPlanningId = currentPlanning.value?.id
await getPlanningList()
if (currentPlanningId) {
await loadPlanningDetail(currentPlanningId)
}
}
onMounted(() => {
getProjectList()
})
onActivated(() => {
getProjectList()
})
</script>