添加指导价法明细表
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle" width="1120">
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="编辑测算参数" width="92%">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
@@ -11,7 +11,7 @@
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="归属类型">
|
||||
<el-input :model-value="ownershipTypeLabel" disabled />
|
||||
<el-input :model-value="getOwnershipTypeLabel(formData.ownershipType)" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
@@ -22,7 +22,7 @@
|
||||
placeholder="请选择产值计算方式"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in calculationMethodOptions"
|
||||
v-for="item in CALCULATION_METHOD_OPTIONS"
|
||||
:key="String(item.value)"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
@@ -55,35 +55,6 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="设计部位" prop="designPart">
|
||||
<el-select
|
||||
v-model="formData.designPart"
|
||||
class="!w-1/1"
|
||||
clearable
|
||||
placeholder="请选择设计部位"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in DESIGN_PART_OPTIONS"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="建筑类型" prop="buildingType">
|
||||
<el-input
|
||||
v-model="formData.buildingType"
|
||||
maxlength="100"
|
||||
placeholder="请输入建筑类型"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-divider content-position="left">测算参数</el-divider>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
@@ -99,7 +70,13 @@
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="工程总面积(m²)" prop="planningArea">
|
||||
<el-input
|
||||
v-if="showGuideDetailSection"
|
||||
:model-value="formatAmountText(guideDetailSummary.designArea)"
|
||||
disabled
|
||||
/>
|
||||
<el-input-number
|
||||
v-else
|
||||
v-model="formData.planningArea"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
@@ -119,13 +96,9 @@
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="设计阶段" prop="designStage">
|
||||
<el-select
|
||||
v-model="formData.designStage"
|
||||
class="!w-1/1"
|
||||
placeholder="请选择设计阶段"
|
||||
>
|
||||
<el-select v-model="formData.designStage" class="!w-1/1" placeholder="请选择设计阶段">
|
||||
<el-option
|
||||
v-for="item in designStageOptions"
|
||||
v-for="item in DESIGN_STAGE_OPTIONS"
|
||||
:key="String(item.value)"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
@@ -133,9 +106,6 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="本次设计阶段比例(%)" prop="currentDesignStageRatio">
|
||||
<el-input-number
|
||||
@@ -148,6 +118,9 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="审核审定是否外包" prop="reviewOutsourceFlag">
|
||||
<el-switch
|
||||
@@ -170,9 +143,6 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="16">
|
||||
<el-col v-if="showCalculationRatioField" :span="8">
|
||||
<el-form-item :label="`${calculationRatioLabel}(%)`" prop="calculationRatio">
|
||||
<el-input-number
|
||||
@@ -185,7 +155,10 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="showCalculationRatioField ? 8 : 12">
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="总分配(%)" prop="totalDistributionAmount">
|
||||
<el-input-number
|
||||
v-model="totalDistributionAmountPercent"
|
||||
@@ -199,7 +172,7 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<template v-if="showMajorFactorFields">
|
||||
<template v-if="showParentMajorFactorFields">
|
||||
<el-divider content-position="left">专业所测算参数</el-divider>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
@@ -219,7 +192,7 @@
|
||||
<el-input-number
|
||||
v-model="formData.drawingSetFactor"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:precision="4"
|
||||
:step="0.01"
|
||||
class="!w-1/1"
|
||||
controls-position="right"
|
||||
@@ -231,7 +204,7 @@
|
||||
<el-input-number
|
||||
v-model="formData.scaleFactor"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:precision="4"
|
||||
:step="0.01"
|
||||
class="!w-1/1"
|
||||
controls-position="right"
|
||||
@@ -245,7 +218,7 @@
|
||||
<el-input-number
|
||||
v-model="formData.modificationFactor"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:precision="4"
|
||||
:step="0.01"
|
||||
class="!w-1/1"
|
||||
controls-position="right"
|
||||
@@ -267,18 +240,171 @@
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<template v-if="showInternalGuidanceUnitPriceField">
|
||||
<el-divider content-position="left">指导价法参数</el-divider>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="内部指导单价(元/m²)" prop="internalGuidanceUnitPrice">
|
||||
<!-- 优化后的指导价法明细块 -->
|
||||
<template v-if="showGuideDetailSection">
|
||||
<el-divider content-position="left">指导价法明细</el-divider>
|
||||
<div class="mb-12px flex items-center justify-between gap-12px">
|
||||
<el-button plain type="primary" @click="addGuideDetailRow">
|
||||
<template #icon><i class="el-icon-plus"></i></template>新增明细
|
||||
</el-button>
|
||||
<span class="text-12px text-gray-400">💡 提示:数字字段已隐藏加减箭头以优化显示,支持直接输入或复制粘贴</span>
|
||||
</div>
|
||||
|
||||
<el-table :data="guideDetails" border max-height="460" class="optimized-table">
|
||||
<!-- 1. 冻结核心上下文列 -->
|
||||
<el-table-column align="center" label="序号" width="65" fixed="left">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="formData.internalGuidanceUnitPrice"
|
||||
v-model="row.sortNo"
|
||||
:min="1"
|
||||
:precision="0"
|
||||
:controls="false"
|
||||
class="!w-1/1"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="设计部位" min-width="120" fixed="left">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.designPart" class="!w-1/1" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item in DESIGN_PART_OPTIONS"
|
||||
:key="String(item.value)"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="建筑类型" min-width="140" fixed="left">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.buildingType" maxlength="100" placeholder="建筑类型" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<!-- 2. 取消 controls 的核心数值列 -->
|
||||
<el-table-column align="center" label="指导单价(元)" min-width="110">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.internalGuidanceUnitPrice"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:step="1"
|
||||
:controls="false"
|
||||
class="!w-1/1"
|
||||
controls-position="right"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="设计面积(m²)" min-width="110">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.designArea"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
class="!w-1/1"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="楼栋/户型数" min-width="100">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.buildingOrUnitCount"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
:controls="false"
|
||||
class="!w-1/1"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<!-- 3. 多级表头折叠系数配置 -->
|
||||
<el-table-column label="调整系数配置" align="center">
|
||||
<el-table-column align="center" label="套图" min-width="85">
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.drawingSetFactor" :min="0" :precision="4" :controls="false" class="!w-1/1" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="规模" min-width="85">
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.scaleFactor" :min="0" :precision="4" :controls="false" class="!w-1/1" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="修改" min-width="85">
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.modificationFactor" :min="0" :precision="4" :controls="false" class="!w-1/1" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="复杂(%)" min-width="90">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
:model-value="toPercentValue(row.complexityFactor)"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
class="!w-1/1"
|
||||
@update:model-value="setGuideDetailPercentValue(row, 'complexityFactor', $event)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="合计" min-width="80">
|
||||
<template #default="{ row }">
|
||||
<span class="text-gray-500">{{ formatFactorText(getGuideDetailTotalAdjustmentFactor(row)) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column align="center" label="设计占比(%)" min-width="100">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
:model-value="toPercentValue(row.designRatio)"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
class="!w-1/1"
|
||||
@update:model-value="setGuideDetailPercentValue(row, 'designRatio', $event)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<!-- 4. 结果列靠右显示,用浅色背景区分 -->
|
||||
<el-table-column align="right" label="考核面积(m²)" min-width="110" class-name="bg-gray-50">
|
||||
<template #default="{ row }">
|
||||
<span class="font-bold">{{ formatAmountText(getGuideDetailAssessmentArea(row)) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="考核产值(元)" min-width="120" class-name="bg-gray-50">
|
||||
<template #default="{ row }">
|
||||
<span class="font-bold text-primary">{{ formatAmountText(getGuideDetailAssessmentOutputValue(row)) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column align="center" label="备注" min-width="130">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" maxlength="500" placeholder="备注信息" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" fixed="right" label="操作" width="70">
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" @click="removeGuideDetailRow($index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-row :gutter="16" class="mt-16px">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="汇总设计面积(m²)">
|
||||
<el-input :model-value="formatAmountText(guideDetailSummary.designArea)" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="汇总考核面积(m²)">
|
||||
<el-input :model-value="formatAmountText(guideDetailSummary.assessmentArea)" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="汇总考核产值(元)">
|
||||
<el-input
|
||||
:model-value="formatAmountText(guideDetailSummary.assessmentOutputValue)"
|
||||
disabled
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -296,7 +422,7 @@
|
||||
placeholder="请选择虚拟产值计算方式"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in virtualCalculationMethodOptions"
|
||||
v-for="item in VIRTUAL_CALCULATION_METHOD_OPTIONS"
|
||||
:key="String(item.value)"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
@@ -359,6 +485,7 @@
|
||||
</el-row>
|
||||
</template>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">保存</el-button>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
@@ -367,19 +494,22 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// [原有的 Script 逻辑不变,直接粘贴原本的逻辑即可]
|
||||
import type { FormRules } from 'element-plus'
|
||||
import * as PlanningApi from '@/api/tjt/planning'
|
||||
import * as PlanningGuideDetailApi from '@/api/tjt/planningGuideDetail'
|
||||
import {
|
||||
CALCULATION_METHOD_OPTIONS,
|
||||
DEFAULT_WORKING_DAY_UNIT_PRICE,
|
||||
DESIGN_PART_OPTIONS,
|
||||
DESIGN_STAGE_OPTIONS,
|
||||
OWNERSHIP_TYPE_OPTIONS,
|
||||
VIRTUAL_CALCULATION_METHOD_OPTIONS,
|
||||
formatAmountText,
|
||||
formatPercentText,
|
||||
fromPercentValue,
|
||||
getCalculationRatioDefaultPercent,
|
||||
getCalculationRatioLabel,
|
||||
getOwnershipTypeLabel,
|
||||
getReviewOutsourceDefaultPercent,
|
||||
isComprehensiveOwnership,
|
||||
isContractPriceMethod,
|
||||
@@ -399,90 +529,64 @@ import {
|
||||
|
||||
defineOptions({ name: 'TjtPlanningOutputForm' })
|
||||
|
||||
const DESIGN_PART_OPTIONS = [
|
||||
{ label: '地上部分', value: '地上部分' },
|
||||
{ label: '地下部分', value: '地下部分' }
|
||||
]
|
||||
type GuideDetailRow = PlanningGuideDetailApi.ProjectPlanningGuideDetailVO
|
||||
|
||||
const OWNERSHIP_TYPE_LABELS = ['专业所', '综合所', '专业分包']
|
||||
const CALCULATION_METHOD_LABELS = ['指导价法', '合同价法', '虚拟产值法']
|
||||
const DESIGN_STAGE_LABELS = ['方案', '施工图', '方案+施工图']
|
||||
const VIRTUAL_CALCULATION_METHOD_LABELS = ['指导单价法', '指导总价法', '工日法']
|
||||
|
||||
const createDisplayOptions = (
|
||||
source: Array<{ label: string; value: string }>,
|
||||
labels: string[]
|
||||
) => source.map((item, index) => ({ label: labels[index] || item.label, value: item.value }))
|
||||
|
||||
const ownershipTypeOptions = createDisplayOptions(OWNERSHIP_TYPE_OPTIONS, OWNERSHIP_TYPE_LABELS)
|
||||
const calculationMethodOptions = createDisplayOptions(
|
||||
CALCULATION_METHOD_OPTIONS,
|
||||
CALCULATION_METHOD_LABELS
|
||||
)
|
||||
const designStageOptions = createDisplayOptions(DESIGN_STAGE_OPTIONS, DESIGN_STAGE_LABELS)
|
||||
const virtualCalculationMethodOptions = createDisplayOptions(
|
||||
VIRTUAL_CALCULATION_METHOD_OPTIONS,
|
||||
VIRTUAL_CALCULATION_METHOD_LABELS
|
||||
)
|
||||
|
||||
const { t } = useI18n()
|
||||
const message = useMessage()
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const dialogTitle = ref('')
|
||||
const formLoading = ref(false)
|
||||
const formRef = ref()
|
||||
const formData = ref<PlanningApi.ProjectPlanningVO>({
|
||||
|
||||
const createFormData = (): PlanningApi.ProjectPlanningVO => ({
|
||||
id: undefined,
|
||||
projectId: 0,
|
||||
ownershipType: '',
|
||||
calculationMethod: '',
|
||||
planningContent: ''
|
||||
planningContent: '',
|
||||
planningAmount: undefined,
|
||||
managementFeeRate: undefined,
|
||||
managementFee: undefined,
|
||||
implementationTeam: '',
|
||||
planningStartYear: undefined,
|
||||
planningArea: undefined,
|
||||
designStage: undefined,
|
||||
currentDesignStageRatio: undefined,
|
||||
reviewOutsourceFlag: false,
|
||||
reviewOutsourceRatio: undefined,
|
||||
totalDistributionAmount: 1,
|
||||
progressRemark: '',
|
||||
allocatedAmount: undefined,
|
||||
pendingAmount: undefined,
|
||||
buildingOrUnitCount: undefined,
|
||||
drawingSetFactor: undefined,
|
||||
scaleFactor: undefined,
|
||||
modificationFactor: undefined,
|
||||
complexityFactor: undefined,
|
||||
internalGuidanceUnitPrice: undefined,
|
||||
virtualCalculationMethod: undefined,
|
||||
workingDayCount: undefined,
|
||||
workingDayUnitPrice: undefined,
|
||||
guidanceUnitPrice: undefined,
|
||||
guidanceTotalPrice: undefined,
|
||||
calculationRatio: undefined,
|
||||
contractUnitPrice: undefined,
|
||||
totalAdjustmentFactor: undefined,
|
||||
assessmentArea: undefined,
|
||||
virtualOutputValue: undefined,
|
||||
assessmentOutputValue: undefined,
|
||||
createTime: undefined
|
||||
})
|
||||
|
||||
const getOptionLabel = (options: Array<{ label: string; value: string }>, value?: string) =>
|
||||
options.find((item) => item.value === value)?.label || value || ''
|
||||
|
||||
const ownershipTypeLabel = computed(() =>
|
||||
getOptionLabel(ownershipTypeOptions, formData.value.ownershipType)
|
||||
)
|
||||
|
||||
const normalizeFormData = (data: PlanningApi.ProjectPlanningVO): PlanningApi.ProjectPlanningVO => ({
|
||||
...data,
|
||||
ownershipType: normalizeOwnershipType(data.ownershipType) || data.ownershipType || '',
|
||||
calculationMethod: normalizeCalculationMethod(data.calculationMethod) || data.calculationMethod || '',
|
||||
designPart: DESIGN_PART_OPTIONS.find((item) => item.value === data.designPart)?.value || undefined,
|
||||
designStage: normalizeDesignStage(data.designStage),
|
||||
virtualCalculationMethod: normalizeVirtualCalculationMethod(data.virtualCalculationMethod),
|
||||
reviewOutsourceFlag: data.reviewOutsourceFlag ?? false,
|
||||
totalDistributionAmount: data.totalDistributionAmount ?? 1,
|
||||
progressRemark: data.progressRemark ?? '',
|
||||
drawingSetFactor: data.drawingSetFactor ?? 1,
|
||||
scaleFactor: data.scaleFactor ?? 1,
|
||||
modificationFactor: data.modificationFactor ?? 1,
|
||||
complexityFactor: data.complexityFactor ?? 1,
|
||||
workingDayUnitPrice:
|
||||
data.workingDayUnitPrice ??
|
||||
(isWorkingDayMethod(data.virtualCalculationMethod) ? DEFAULT_WORKING_DAY_UNIT_PRICE : undefined),
|
||||
guidanceTotalPrice: data.guidanceTotalPrice
|
||||
})
|
||||
const formData = ref<PlanningApi.ProjectPlanningVO>(createFormData())
|
||||
const guideDetails = ref<GuideDetailRow[]>([])
|
||||
|
||||
const planningStartYearValue = computed({
|
||||
get: () =>
|
||||
formData.value.planningStartYear ? String(formData.value.planningStartYear) : undefined,
|
||||
get: () => (formData.value.planningStartYear ? String(formData.value.planningStartYear) : undefined),
|
||||
set: (value?: string) => {
|
||||
formData.value.planningStartYear = value ? Number(value) : undefined
|
||||
}
|
||||
})
|
||||
|
||||
const contractUnitPricePreview = computed(() => {
|
||||
const planningAmount = Number(formData.value.planningAmount || 0)
|
||||
const planningArea = Number(formData.value.planningArea || 0)
|
||||
if (!planningArea) {
|
||||
return formatAmountText(0)
|
||||
}
|
||||
return formatAmountText(planningAmount / planningArea)
|
||||
})
|
||||
|
||||
const createPercentModel = (field: keyof PlanningApi.ProjectPlanningVO, digits = 2) =>
|
||||
computed({
|
||||
get: () => toPercentValue(formData.value[field] as number | undefined, digits),
|
||||
@@ -502,23 +606,25 @@ const showCalculationRatioField = computed(
|
||||
isComprehensiveOwnership(formData.value.ownershipType) ||
|
||||
isSubcontractOwnership(formData.value.ownershipType)
|
||||
)
|
||||
const calculationRatioLabel = computed(() => getCalculationRatioLabel(formData.value.ownershipType))
|
||||
const showMajorFactorFields = computed(
|
||||
() =>
|
||||
isMajorOwnership(formData.value.ownershipType) &&
|
||||
(isGuidancePriceMethod(formData.value.calculationMethod) ||
|
||||
isContractPriceMethod(formData.value.calculationMethod))
|
||||
)
|
||||
const showInternalGuidanceUnitPriceField = computed(
|
||||
|
||||
const showGuideDetailSection = computed(
|
||||
() =>
|
||||
isMajorOwnership(formData.value.ownershipType) &&
|
||||
isGuidancePriceMethod(formData.value.calculationMethod)
|
||||
)
|
||||
|
||||
const showParentMajorFactorFields = computed(
|
||||
() =>
|
||||
isMajorOwnership(formData.value.ownershipType) &&
|
||||
isContractPriceMethod(formData.value.calculationMethod)
|
||||
)
|
||||
|
||||
const showVirtualOutputSection = computed(
|
||||
() =>
|
||||
isMajorOwnership(formData.value.ownershipType) &&
|
||||
isVirtualOutputMethod(formData.value.calculationMethod)
|
||||
)
|
||||
|
||||
const showWorkingDayFields = computed(() => isWorkingDayMethod(formData.value.virtualCalculationMethod))
|
||||
const showGuidanceUnitPriceField = computed(
|
||||
() => isVirtualGuidanceMethod(formData.value.virtualCalculationMethod)
|
||||
@@ -526,13 +632,188 @@ const showGuidanceUnitPriceField = computed(
|
||||
const showGuidanceTotalPriceField = computed(
|
||||
() => isVirtualGuidanceTotalPriceMethod(formData.value.virtualCalculationMethod)
|
||||
)
|
||||
const calculationRatioLabel = computed(() => getCalculationRatioLabel(formData.value.ownershipType))
|
||||
|
||||
const normalizeFormData = (data: PlanningApi.ProjectPlanningVO): PlanningApi.ProjectPlanningVO => ({
|
||||
...createFormData(),
|
||||
...data,
|
||||
ownershipType: normalizeOwnershipType(data.ownershipType) || data.ownershipType || '',
|
||||
calculationMethod: normalizeCalculationMethod(data.calculationMethod) || data.calculationMethod || '',
|
||||
designStage: normalizeDesignStage(data.designStage),
|
||||
virtualCalculationMethod: normalizeVirtualCalculationMethod(data.virtualCalculationMethod),
|
||||
reviewOutsourceFlag: data.reviewOutsourceFlag ?? false,
|
||||
totalDistributionAmount: data.totalDistributionAmount ?? 1,
|
||||
progressRemark: data.progressRemark ?? '',
|
||||
workingDayUnitPrice:
|
||||
data.workingDayUnitPrice ??
|
||||
(isWorkingDayMethod(data.virtualCalculationMethod) ? DEFAULT_WORKING_DAY_UNIT_PRICE : undefined)
|
||||
})
|
||||
|
||||
const createGuideDetailRow = (sortNo: number): GuideDetailRow => ({
|
||||
planningId: formData.value.id || 0,
|
||||
sortNo,
|
||||
designPart: undefined,
|
||||
buildingType: '',
|
||||
designArea: undefined,
|
||||
internalGuidanceUnitPrice: undefined,
|
||||
buildingOrUnitCount: undefined,
|
||||
drawingSetFactor: undefined,
|
||||
scaleFactor: undefined,
|
||||
modificationFactor: undefined,
|
||||
complexityFactor: undefined,
|
||||
totalAdjustmentFactor: undefined,
|
||||
designRatio: undefined,
|
||||
assessmentArea: undefined,
|
||||
assessmentOutputValue: undefined,
|
||||
remark: ''
|
||||
})
|
||||
|
||||
const normalizeGuideDetailList = (
|
||||
list: PlanningGuideDetailApi.ProjectPlanningGuideDetailVO[] | undefined
|
||||
): GuideDetailRow[] =>
|
||||
[...(list || [])]
|
||||
.sort(
|
||||
(a, b) =>
|
||||
Number(a.sortNo ?? Number.MAX_SAFE_INTEGER) - Number(b.sortNo ?? Number.MAX_SAFE_INTEGER)
|
||||
)
|
||||
.map((item, index) => ({
|
||||
...item,
|
||||
planningId: item.planningId || formData.value.id || 0,
|
||||
sortNo: item.sortNo ?? index + 1,
|
||||
buildingType: item.buildingType ?? '',
|
||||
remark: item.remark ?? ''
|
||||
}))
|
||||
|
||||
const addGuideDetailRow = () => {
|
||||
guideDetails.value.push(createGuideDetailRow(guideDetails.value.length + 1))
|
||||
}
|
||||
|
||||
const removeGuideDetailRow = (index: number) => {
|
||||
guideDetails.value.splice(index, 1)
|
||||
resetGuideDetailSortNo()
|
||||
}
|
||||
|
||||
const resetGuideDetailSortNo = () => {
|
||||
guideDetails.value = guideDetails.value.map((item, index) => ({
|
||||
...item,
|
||||
sortNo: index + 1
|
||||
}))
|
||||
}
|
||||
|
||||
const getGuideDetailTotalAdjustmentFactor = (row: GuideDetailRow) => {
|
||||
if (
|
||||
row.drawingSetFactor === undefined ||
|
||||
row.scaleFactor === undefined ||
|
||||
row.modificationFactor === undefined ||
|
||||
row.complexityFactor === undefined
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
return Number(
|
||||
(
|
||||
Number(row.drawingSetFactor) *
|
||||
Number(row.scaleFactor) *
|
||||
Number(row.modificationFactor) *
|
||||
Number(row.complexityFactor)
|
||||
).toFixed(4)
|
||||
)
|
||||
}
|
||||
|
||||
const getGuideDetailAssessmentArea = (row: GuideDetailRow) => {
|
||||
const totalAdjustmentFactor = getGuideDetailTotalAdjustmentFactor(row)
|
||||
if (row.designArea === undefined || totalAdjustmentFactor === undefined) {
|
||||
return undefined
|
||||
}
|
||||
return Number((Number(row.designArea) * totalAdjustmentFactor).toFixed(2))
|
||||
}
|
||||
|
||||
const getGuideDetailAssessmentOutputValue = (row: GuideDetailRow) => {
|
||||
const assessmentArea = getGuideDetailAssessmentArea(row)
|
||||
if (
|
||||
row.internalGuidanceUnitPrice === undefined ||
|
||||
row.designRatio === undefined ||
|
||||
assessmentArea === undefined
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
return Number(
|
||||
(
|
||||
Number(row.internalGuidanceUnitPrice) *
|
||||
assessmentArea *
|
||||
Number(row.designRatio) *
|
||||
(1 - Number(formData.value.reviewOutsourceRatio || 0))
|
||||
).toFixed(2)
|
||||
)
|
||||
}
|
||||
|
||||
const guideDetailSummary = computed(() =>
|
||||
guideDetails.value.reduce(
|
||||
(summary, row) => ({
|
||||
designArea: Number((summary.designArea + Number(row.designArea || 0)).toFixed(2)),
|
||||
assessmentArea: Number(
|
||||
(summary.assessmentArea + Number(getGuideDetailAssessmentArea(row) || 0)).toFixed(2)
|
||||
),
|
||||
assessmentOutputValue: Number(
|
||||
(
|
||||
summary.assessmentOutputValue + Number(getGuideDetailAssessmentOutputValue(row) || 0)
|
||||
).toFixed(2)
|
||||
)
|
||||
}),
|
||||
{
|
||||
designArea: 0,
|
||||
assessmentArea: 0,
|
||||
assessmentOutputValue: 0
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
const contractUnitPricePreview = computed(() => {
|
||||
const planningAmount = Number(formData.value.planningAmount || 0)
|
||||
const planningArea = showGuideDetailSection.value
|
||||
? Number(guideDetailSummary.value.designArea || 0)
|
||||
: Number(formData.value.planningArea || 0)
|
||||
if (!planningArea) {
|
||||
return formatAmountText(0)
|
||||
}
|
||||
return formatAmountText(planningAmount / planningArea)
|
||||
})
|
||||
|
||||
const formatFactorText = (value?: number, digits = 4) => {
|
||||
if (value === undefined || value === null) {
|
||||
return '-'
|
||||
}
|
||||
return Number(value).toFixed(digits)
|
||||
}
|
||||
|
||||
const setGuideDetailPercentValue = (
|
||||
row: GuideDetailRow,
|
||||
field: 'complexityFactor' | 'designRatio',
|
||||
value?: number | string | null
|
||||
) => {
|
||||
row[field] = fromPercentValue(value, 4)
|
||||
}
|
||||
|
||||
const hasValue = (value: unknown) => value !== undefined && value !== null && value !== ''
|
||||
|
||||
const formRules = reactive<FormRules>({
|
||||
calculationMethod: [{ required: true, message: '产值计算方式不能为空', trigger: 'change' }],
|
||||
designPart: [{ required: true, message: '设计部位不能为空', trigger: 'change' }],
|
||||
buildingType: [{ required: true, message: '建筑类型不能为空', trigger: 'blur' }],
|
||||
planningStartYear: [{ required: true, message: '开始年度不能为空', trigger: 'change' }],
|
||||
planningArea: [{ required: true, message: '工程总面积不能为空', trigger: 'blur' }],
|
||||
planningArea: [
|
||||
{
|
||||
validator: (_rule, value, callback) => {
|
||||
if (showGuideDetailSection.value) {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
if (!hasValue(value)) {
|
||||
callback(new Error('工程总面积不能为空'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
designStage: [{ required: true, message: '设计阶段不能为空', trigger: 'change' }],
|
||||
currentDesignStageRatio: [
|
||||
{ required: true, message: '本次设计阶段比例不能为空', trigger: 'blur' }
|
||||
@@ -541,7 +822,7 @@ const formRules = reactive<FormRules>({
|
||||
calculationRatio: [
|
||||
{
|
||||
validator: (_rule, value, callback) => {
|
||||
if (showCalculationRatioField.value && (value === undefined || value === null || value === '')) {
|
||||
if (showCalculationRatioField.value && !hasValue(value)) {
|
||||
callback(new Error(`${calculationRatioLabel.value}不能为空`))
|
||||
return
|
||||
}
|
||||
@@ -550,21 +831,6 @@ const formRules = reactive<FormRules>({
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
internalGuidanceUnitPrice: [
|
||||
{
|
||||
validator: (_rule, value, callback) => {
|
||||
if (
|
||||
showInternalGuidanceUnitPriceField.value &&
|
||||
(value === undefined || value === null || value === '')
|
||||
) {
|
||||
callback(new Error('内部指导单价不能为空'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
virtualCalculationMethod: [
|
||||
{
|
||||
validator: (_rule, value, callback) => {
|
||||
@@ -580,7 +846,7 @@ const formRules = reactive<FormRules>({
|
||||
workingDayCount: [
|
||||
{
|
||||
validator: (_rule, value, callback) => {
|
||||
if (showWorkingDayFields.value && (value === undefined || value === null || value === '')) {
|
||||
if (showWorkingDayFields.value && !hasValue(value)) {
|
||||
callback(new Error('工日不能为空'))
|
||||
return
|
||||
}
|
||||
@@ -592,7 +858,7 @@ const formRules = reactive<FormRules>({
|
||||
workingDayUnitPrice: [
|
||||
{
|
||||
validator: (_rule, value, callback) => {
|
||||
if (showWorkingDayFields.value && (value === undefined || value === null || value === '')) {
|
||||
if (showWorkingDayFields.value && !hasValue(value)) {
|
||||
callback(new Error('工日单价不能为空'))
|
||||
return
|
||||
}
|
||||
@@ -604,10 +870,7 @@ const formRules = reactive<FormRules>({
|
||||
guidanceUnitPrice: [
|
||||
{
|
||||
validator: (_rule, value, callback) => {
|
||||
if (
|
||||
showGuidanceUnitPriceField.value &&
|
||||
(value === undefined || value === null || value === '')
|
||||
) {
|
||||
if (showGuidanceUnitPriceField.value && !hasValue(value)) {
|
||||
callback(new Error('指导单价不能为空'))
|
||||
return
|
||||
}
|
||||
@@ -619,10 +882,7 @@ const formRules = reactive<FormRules>({
|
||||
guidanceTotalPrice: [
|
||||
{
|
||||
validator: (_rule, value, callback) => {
|
||||
if (
|
||||
showGuidanceTotalPriceField.value &&
|
||||
(value === undefined || value === null || value === '')
|
||||
) {
|
||||
if (showGuidanceTotalPriceField.value && !hasValue(value)) {
|
||||
callback(new Error('指导总价不能为空'))
|
||||
return
|
||||
}
|
||||
@@ -668,31 +928,39 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
watch(showGuideDetailSection, (value) => {
|
||||
if (value && !guideDetails.value.length) {
|
||||
addGuideDetailRow()
|
||||
}
|
||||
})
|
||||
|
||||
const buildSavePayload = (): PlanningApi.ProjectPlanningSaveVO => ({
|
||||
id: formData.value.id,
|
||||
projectId: formData.value.projectId,
|
||||
ownershipType: formData.value.ownershipType,
|
||||
designPart: formData.value.designPart,
|
||||
buildingType: formData.value.buildingType,
|
||||
calculationMethod: formData.value.calculationMethod,
|
||||
planningContent: formData.value.planningContent,
|
||||
planningAmount: formData.value.planningAmount,
|
||||
managementFeeRate: formData.value.managementFeeRate,
|
||||
implementationTeam: formData.value.implementationTeam,
|
||||
planningStartYear: formData.value.planningStartYear,
|
||||
planningArea: formData.value.planningArea,
|
||||
planningArea: showGuideDetailSection.value
|
||||
? guideDetailSummary.value.designArea
|
||||
: formData.value.planningArea,
|
||||
designStage: formData.value.designStage,
|
||||
currentDesignStageRatio: formData.value.currentDesignStageRatio,
|
||||
reviewOutsourceFlag: formData.value.reviewOutsourceFlag,
|
||||
reviewOutsourceRatio: formData.value.reviewOutsourceRatio,
|
||||
totalDistributionAmount: formData.value.totalDistributionAmount,
|
||||
progressRemark: formData.value.progressRemark,
|
||||
buildingOrUnitCount: formData.value.buildingOrUnitCount,
|
||||
drawingSetFactor: formData.value.drawingSetFactor,
|
||||
scaleFactor: formData.value.scaleFactor,
|
||||
modificationFactor: formData.value.modificationFactor,
|
||||
complexityFactor: formData.value.complexityFactor,
|
||||
internalGuidanceUnitPrice: formData.value.internalGuidanceUnitPrice,
|
||||
buildingOrUnitCount: showGuideDetailSection.value ? undefined : formData.value.buildingOrUnitCount,
|
||||
drawingSetFactor: showGuideDetailSection.value ? undefined : formData.value.drawingSetFactor,
|
||||
scaleFactor: showGuideDetailSection.value ? undefined : formData.value.scaleFactor,
|
||||
modificationFactor: showGuideDetailSection.value ? undefined : formData.value.modificationFactor,
|
||||
complexityFactor: showGuideDetailSection.value ? undefined : formData.value.complexityFactor,
|
||||
internalGuidanceUnitPrice: showGuideDetailSection.value
|
||||
? undefined
|
||||
: formData.value.internalGuidanceUnitPrice,
|
||||
virtualCalculationMethod: formData.value.virtualCalculationMethod,
|
||||
workingDayCount: formData.value.workingDayCount,
|
||||
workingDayUnitPrice: formData.value.workingDayUnitPrice,
|
||||
@@ -701,21 +969,98 @@ const buildSavePayload = (): PlanningApi.ProjectPlanningSaveVO => ({
|
||||
calculationRatio: formData.value.calculationRatio
|
||||
})
|
||||
|
||||
const buildGuideDetailPayload = (): PlanningGuideDetailApi.ProjectPlanningGuideDetailBatchSaveVO => ({
|
||||
planningId: formData.value.id!,
|
||||
details: guideDetails.value
|
||||
.map((item, index) => ({
|
||||
id: item.id,
|
||||
designPart: item.designPart,
|
||||
buildingType: item.buildingType,
|
||||
designArea: item.designArea,
|
||||
internalGuidanceUnitPrice: item.internalGuidanceUnitPrice,
|
||||
buildingOrUnitCount: item.buildingOrUnitCount,
|
||||
drawingSetFactor: item.drawingSetFactor,
|
||||
scaleFactor: item.scaleFactor,
|
||||
modificationFactor: item.modificationFactor,
|
||||
complexityFactor: item.complexityFactor,
|
||||
designRatio: item.designRatio,
|
||||
sortNo: item.sortNo ?? index + 1,
|
||||
remark: item.remark
|
||||
}))
|
||||
.sort((a, b) => Number(a.sortNo || 0) - Number(b.sortNo || 0))
|
||||
})
|
||||
|
||||
const validateGuideDetails = () => {
|
||||
if (!guideDetails.value.length) {
|
||||
message.warning('请至少维护一条指导价法明细')
|
||||
return false
|
||||
}
|
||||
for (let index = 0; index < guideDetails.value.length; index++) {
|
||||
const row = guideDetails.value[index]
|
||||
const rowText = `第 ${index + 1} 行`
|
||||
if (!row.designPart) {
|
||||
message.warning(`${rowText}设计部位不能为空`)
|
||||
return false
|
||||
}
|
||||
if (!row.buildingType) {
|
||||
message.warning(`${rowText}建筑类型不能为空`)
|
||||
return false
|
||||
}
|
||||
if (!hasValue(row.designArea)) {
|
||||
message.warning(`${rowText}设计面积不能为空`)
|
||||
return false
|
||||
}
|
||||
if (!hasValue(row.internalGuidanceUnitPrice)) {
|
||||
message.warning(`${rowText}内部指导单价不能为空`)
|
||||
return false
|
||||
}
|
||||
if (!hasValue(row.drawingSetFactor)) {
|
||||
message.warning(`${rowText}套图系数不能为空`)
|
||||
return false
|
||||
}
|
||||
if (!hasValue(row.scaleFactor)) {
|
||||
message.warning(`${rowText}规模系数不能为空`)
|
||||
return false
|
||||
}
|
||||
if (!hasValue(row.modificationFactor)) {
|
||||
message.warning(`${rowText}修改系数不能为空`)
|
||||
return false
|
||||
}
|
||||
if (!hasValue(row.complexityFactor)) {
|
||||
message.warning(`${rowText}复杂系数不能为空`)
|
||||
return false
|
||||
}
|
||||
if (!hasValue(row.designRatio)) {
|
||||
message.warning(`${rowText}设计占比不能为空`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const open = async (id: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.update')
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = await PlanningApi.getProjectPlanning(id)
|
||||
formData.value = normalizeFormData(data)
|
||||
guideDetails.value = showGuideDetailSection.value
|
||||
? normalizeGuideDetailList(
|
||||
await PlanningGuideDetailApi.getProjectPlanningGuideDetailListByPlanningId(id)
|
||||
)
|
||||
: []
|
||||
applyCalculationRatioDefault()
|
||||
if (formData.value.reviewOutsourceRatio === undefined || formData.value.reviewOutsourceRatio === null) {
|
||||
applyReviewOutsourceDefault()
|
||||
}
|
||||
if (showGuideDetailSection.value && !guideDetails.value.length) {
|
||||
addGuideDetailRow()
|
||||
}
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
@@ -728,10 +1073,17 @@ const submitForm = async () => {
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
if (showGuideDetailSection.value && !validateGuideDetails()) {
|
||||
return
|
||||
}
|
||||
|
||||
formLoading.value = true
|
||||
try {
|
||||
await PlanningApi.updateProjectPlanning(buildSavePayload())
|
||||
message.success(t('common.updateSuccess'))
|
||||
if (showGuideDetailSection.value && formData.value.id) {
|
||||
await PlanningGuideDetailApi.batchSaveProjectPlanningGuideDetail(buildGuideDetailPayload())
|
||||
}
|
||||
message.success('保存成功')
|
||||
dialogVisible.value = false
|
||||
emit('success')
|
||||
} finally {
|
||||
@@ -739,3 +1091,17 @@ const submitForm = async () => {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 优化无控制按钮的 Input Number 显示样式 */
|
||||
:deep(.optimized-table .el-input-number .el-input__inner) {
|
||||
text-align: left;
|
||||
}
|
||||
/* 给只读计算结果列添加淡淡的背景色用于视觉区分 */
|
||||
:deep(.bg-gray-50) {
|
||||
background-color: #f9fafc !important;
|
||||
}
|
||||
:deep(.text-primary) {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user