Files
tjt_czjs_ui/src/views/tjt/output/index.vue
2026-05-26 15:36:51 +08:00

657 lines
24 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 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
:index="getProjectRowIndex"
align="center"
label="序号"
type="index"
width="80"
/>
<el-table-column
align="center"
label="项目名称"
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" />
<el-table-column align="center" label="合同总产值(元)" width="130">
<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"
/>
<el-table-column align="center" label="排序" prop="sortNo" width="80" />
</el-table>
<Pagination
v-model:limit="queryParams.pageSize"
v-model:page="queryParams.pageNo"
:total="total"
@pagination="getProjectList"
/>
</ContentWrap>
<SplitPane>
<template #left>
<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"
>
<el-table-column
align="center"
label="项目任务包"
min-width="170"
prop="planningContent"
show-overflow-tooltip
/>
<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>
<el-table-column align="center" label="分项合同产值(元)" width="120">
<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>
<el-table-column align="center" label="排序" prop="sortNo" width="80" />
</el-table>
</ContentWrap>
</template>
<template #right>
<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)]">
{{ getOwnershipTypeLabel(currentPlanning.ownershipType) }} /
{{ getCalculationMethodLabel(currentPlanning.calculationMethod) }}
</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="测算参数">
<el-descriptions-item label="分项合同产值(元)">
{{ 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>
<el-descriptions-item label="意向实施团队">
{{ 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="设计阶段">
{{ getDesignStageLabel(currentPlanning.designStage) }}
</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
v-if="showParentBuildingOrUnitCount"
label="楼栋数/户型数"
>
{{ currentPlanning.buildingOrUnitCount }}
</el-descriptions-item>
<el-descriptions-item v-if="showParentMajorFactorFields" label="套图系数">
{{ formatFactorText(currentPlanning.drawingSetFactor, 2) }}
</el-descriptions-item>
<el-descriptions-item v-if="showParentMajorFactorFields" label="规模系数">
{{ formatFactorText(currentPlanning.scaleFactor, 2) }}
</el-descriptions-item>
<el-descriptions-item v-if="showParentMajorFactorFields" label="修改系数">
{{ formatFactorText(currentPlanning.modificationFactor, 2) }}
</el-descriptions-item>
<el-descriptions-item v-if="showParentMajorFactorFields" label="复杂系数/复杂等级">
{{ formatPercentText(currentPlanning.complexityFactor) }}
</el-descriptions-item>
<el-descriptions-item
v-if="showParentInternalGuidanceUnitPrice"
label="内部指导单价(元/㎡)"
>
{{ formatAmountText(currentPlanning.internalGuidanceUnitPrice) }}
</el-descriptions-item>
<el-descriptions-item
v-if="currentPlanning.virtualCalculationMethod"
label="虚拟产值计算方式"
>
{{ getVirtualCalculationMethodLabel(currentPlanning.virtualCalculationMethod) }}
</el-descriptions-item>
<el-descriptions-item
v-if="currentPlanning.guidanceUnitPrice"
label="指导单价(元/㎡)"
>
{{ 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="计算结果">
<el-descriptions-item v-if="!showGuideDetailScene" label="合计调整系数">
{{ 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 class="mt-4px text-12px text-[var(--el-text-color-secondary)]">
总分配已分配待分配和提取进度备注属于合约规划的总体分配控制季度分配表只展示年度季度和分配比例明细
</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>
</template>
</SplitPane>
<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 SplitPane from '@/views/tjt/shared/SplitPane.vue'
import {
CONTRACT_SIGN_OPTIONS,
QUARTER_OPTIONS,
formatAmountText,
formatAreaText,
formatPercentText,
getCalculationMethodLabel,
getCalculationRatioLabel,
getDesignStageLabel,
getOwnershipTypeLabel,
getVirtualCalculationMethodLabel,
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
})
const getProjectRowIndex = (index: number) =>
(queryParams.pageNo - 1) * queryParams.pageSize + index + 1
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)
)
const showGuideDetailScene = computed(
() =>
isMajorOwnership(currentPlanning.value?.ownershipType) &&
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
)
const formatFactorText = (value?: number, digits = 4) => {
if (value === undefined || value === null) {
return '-'
}
return Number(value).toFixed(digits)
}
const getQuarterCell = (row: QuarterYearRow, quarterNo: number) => {
return row.quarters.find((item) => Number(item.quarterNo) === Number(quarterNo))
}
const formatQuarterRatio = (row: QuarterYearRow, quarterNo: number) => {
return formatPercentText(getQuarterCell(row, quarterNo)?.distributionRatio)
}
const buildQuarterRows = (
planning: PlanningApi.ProjectPlanningVO,
quarters: PlanningQuarterApi.ProjectPlanningQuarterVO[]
) => {
const yearSet = new Set<number>()
if (planning.planningStartYear) {
yearSet.add(planning.planningStartYear)
}
quarters.forEach((item) => {
const distributionYear = Number(item.distributionYear)
if (!Number.isNaN(distributionYear)) {
yearSet.add(distributionYear)
}
})
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) => {
const quarterNo = Number(option.value)
const match = quarters.find(
(item) =>
Number(item.distributionYear) === distributionYear &&
Number(item.quarterNo) === quarterNo
)
return (
match || {
planningId: planning.id!,
distributionYear,
quarterNo,
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>