添加基础表维护
This commit is contained in:
@@ -47,16 +47,14 @@
|
||||
highlight-current-row
|
||||
@current-change="handleCurrentProjectChange"
|
||||
>
|
||||
<el-table-column align="center" label="项目 ID" prop="id" width="88" />
|
||||
<el-table-column align="center" label="项目ID" prop="id" width="88" />
|
||||
<el-table-column align="center" label="工程名称" min-width="220" prop="projectName" />
|
||||
<el-table-column align="center" label="项目经理" min-width="120" prop="projectManagerName" />
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="工程负责人"
|
||||
min-width="120"
|
||||
prop="engineeringPrincipalName"
|
||||
/>
|
||||
<el-table-column align="center" label="项目开始年度" prop="projectStartYear" width="120" />
|
||||
<el-table-column align="center" label="项目负责人" min-width="180">
|
||||
<template #default="scope">
|
||||
{{ getProjectLeadText(scope.row.projectManagerName, scope.row.engineeringPrincipalName) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="开始年度" prop="projectStartYear" width="120" />
|
||||
</el-table>
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@@ -69,11 +67,8 @@
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<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 class="mb-12px text-14px font-600">
|
||||
{{ currentProject?.projectName || '专业所规划列表' }}
|
||||
</div>
|
||||
<el-table
|
||||
ref="planningTableRef"
|
||||
@@ -112,23 +107,20 @@
|
||||
<Icon class="mr-5px" icon="ep:edit" />
|
||||
编辑比例
|
||||
</el-button>
|
||||
<el-button @click="refreshCurrentPlanning">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
刷新结果
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-descriptions :column="2" border title="基础信息">
|
||||
<el-descriptions-item label="项目名称">{{ formData.projectName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="规划内容">{{ formData.planningContent || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="项目经理">{{ formData.projectManagerName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="项目负责人">{{ formData.engineeringLeaderName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="项目负责人">
|
||||
{{ getProjectLeadText(formData.projectManagerName, formData.engineeringLeaderName) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-divider content-position="left">项目层结果</el-divider>
|
||||
<el-table :data="projectResultRows" border>
|
||||
<el-table-column align="center" label="类别" min-width="140" prop="label" />
|
||||
<el-table-column align="center" label="类别" min-width="160" prop="label" />
|
||||
<el-table-column align="center" label="比例" min-width="120">
|
||||
<template #default="scope">
|
||||
{{ scope.row.percentText }}
|
||||
@@ -157,7 +149,7 @@
|
||||
</el-table>
|
||||
|
||||
<el-divider content-position="left">年度分配信息</el-divider>
|
||||
<div class="mb-12px flex flex-wrap items-start justify-between gap-12px">
|
||||
<div class="mb-12px">
|
||||
<el-radio-group v-model="selectedAnnualCategory" size="small">
|
||||
<el-radio-button
|
||||
v-for="item in annualCategoryOptions"
|
||||
@@ -171,12 +163,8 @@
|
||||
<el-row :gutter="16" class="mb-16px">
|
||||
<el-col v-for="item in annualSummaryCards" :key="item.label" :span="8">
|
||||
<div class="rounded-8px bg-[var(--el-fill-color-light)] px-16px py-12px">
|
||||
<div class="text-12px text-[var(--el-text-color-secondary)]">
|
||||
{{ item.label }}
|
||||
</div>
|
||||
<div class="mt-6px text-18px font-600">
|
||||
{{ formatAmountText(item.amount) }}
|
||||
</div>
|
||||
<div class="text-12px text-[var(--el-text-color-secondary)]">{{ item.label }}</div>
|
||||
<div class="mt-6px text-18px font-600">{{ formatAmountText(item.amount) }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -202,19 +190,18 @@
|
||||
</ContentWrap>
|
||||
|
||||
<ContentWrap v-else>
|
||||
<el-empty description="请选择专业所规划后查看页面4结果" />
|
||||
<el-empty description="请选择专业所规划后查看分配结果" />
|
||||
</ContentWrap>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<Dialog v-model="dialogVisible" title="编辑专业及项目总分配比例" width="900">
|
||||
<Dialog v-model="dialogVisible" title="编辑专业及项目总分配比例" width="920">
|
||||
<template v-if="editForm">
|
||||
|
||||
<el-divider content-position="left">项目层比例编辑</el-divider>
|
||||
<el-row :gutter="16">
|
||||
<el-col v-for="item in editProjectRatioItems" :key="item.key" :span="8">
|
||||
<div class="rounded-8px border border-solid border-[var(--el-border-color)] p-12px">
|
||||
<div class="mb-8px text-13px font-600">{{ item.label }}</div>
|
||||
<el-col v-for="item in editProjectRatioItems" :key="item.key" :span="12">
|
||||
<div class="ratio-card">
|
||||
<div class="ratio-title">{{ item.label }}</div>
|
||||
<el-input-number
|
||||
:model-value="item.percent"
|
||||
:disabled="item.key === 'officeRatio'"
|
||||
@@ -225,9 +212,7 @@
|
||||
controls-position="right"
|
||||
@update:model-value="(value) => updateProjectRatio(item.key, value)"
|
||||
/>
|
||||
<div class="mt-8px text-12px text-[var(--el-text-color-secondary)]">
|
||||
金额:{{ formatAmountText(item.amount) }} 元
|
||||
</div>
|
||||
<div class="ratio-amount">金额:{{ formatAmountText(item.amount) }} 元</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -238,8 +223,8 @@
|
||||
<el-divider content-position="left">专业层比例编辑</el-divider>
|
||||
<el-row :gutter="16">
|
||||
<el-col v-for="item in editSpecialtyRatioItems" :key="item.key" :span="8" class="mb-12px">
|
||||
<div class="rounded-8px border border-solid border-[var(--el-border-color)] p-12px">
|
||||
<div class="mb-8px text-13px font-600">{{ item.label }}</div>
|
||||
<div class="ratio-card">
|
||||
<div class="ratio-title">{{ item.label }}</div>
|
||||
<el-input-number
|
||||
:model-value="item.percent"
|
||||
:disabled="item.key === 'archRatio'"
|
||||
@@ -250,9 +235,7 @@
|
||||
controls-position="right"
|
||||
@update:model-value="(value) => updateSpecialtyRatio(item.key, value)"
|
||||
/>
|
||||
<div class="mt-8px text-12px text-[var(--el-text-color-secondary)]">
|
||||
金额:{{ formatAmountText(item.amount) }} 元
|
||||
</div>
|
||||
<div class="ratio-amount">金额:{{ formatAmountText(item.amount) }} 元</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -260,6 +243,7 @@
|
||||
专业层比例合计:{{ specialtyRatioTotalText }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<el-button :loading="saveLoading" type="primary" @click="handleSave">保存</el-button>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
@@ -284,8 +268,7 @@ import {
|
||||
defineOptions({ name: 'TjtOutputSplit' })
|
||||
|
||||
type AnnualCategoryKey =
|
||||
| 'projectManager'
|
||||
| 'engineeringLeader'
|
||||
| 'project_lead'
|
||||
| 'arch'
|
||||
| 'decor'
|
||||
| 'struct'
|
||||
@@ -300,8 +283,7 @@ interface QuarterYearRow {
|
||||
}
|
||||
|
||||
const annualCategoryOptions: { label: string; value: AnnualCategoryKey }[] = [
|
||||
{ label: '项目经理', value: 'projectManager' },
|
||||
{ label: '项目负责人', value: 'engineeringLeader' },
|
||||
{ label: '项目经理/项目负责人', value: 'project_lead' },
|
||||
{ label: '建筑专业', value: 'arch' },
|
||||
{ label: '装修专业', value: 'decor' },
|
||||
{ label: '结构专业', value: 'struct' },
|
||||
@@ -325,7 +307,7 @@ const currentPlanning = ref<PlanningApi.ProjectPlanningVO>()
|
||||
const formData = ref<OutputSplitApi.ProjectOutputSplitVO>()
|
||||
const editForm = ref<OutputSplitApi.ProjectOutputSplitVO>()
|
||||
const quarterRows = ref<QuarterYearRow[]>([])
|
||||
const selectedAnnualCategory = ref<AnnualCategoryKey>('projectManager')
|
||||
const selectedAnnualCategory = ref<AnnualCategoryKey>('project_lead')
|
||||
const dialogVisible = ref(false)
|
||||
const queryFormRef = ref()
|
||||
const projectTableRef = ref()
|
||||
@@ -350,29 +332,33 @@ const toNumeric = (value?: number | string | null) => {
|
||||
return Number.isNaN(numericValue) ? 0 : numericValue
|
||||
}
|
||||
|
||||
const multiplyAmount = (...values: Array<number | string | null | undefined>) =>
|
||||
Number(values.reduce((result, value) => result * toNumeric(value), 1).toFixed(2))
|
||||
const multiplyAmount = (...values: Array<number | string | null | undefined>) => {
|
||||
let result = 1
|
||||
values.forEach((value) => {
|
||||
result *= toNumeric(value)
|
||||
})
|
||||
return Number(result.toFixed(2))
|
||||
}
|
||||
|
||||
const formatRatioText = (value?: number | string | null) => `${(toPercentValue(value) ?? 0).toFixed(2)}%`
|
||||
|
||||
const getRatioValue = (model: OutputSplitApi.ProjectOutputSplitVO, key: string) =>
|
||||
(model as unknown as Record<string, number | undefined>)[key]
|
||||
|
||||
const getProjectLeadText = (projectManagerName?: string, engineeringLeaderName?: string) =>
|
||||
[projectManagerName, engineeringLeaderName].filter(Boolean).join(' / ') || '-'
|
||||
|
||||
const buildProjectRows = (model?: OutputSplitApi.ProjectOutputSplitVO) => {
|
||||
if (!model) {
|
||||
return []
|
||||
}
|
||||
return [
|
||||
{
|
||||
key: 'projectManagerRatio',
|
||||
label: '项目经理',
|
||||
percentText: formatRatioText(model.projectManagerRatio),
|
||||
percent: toPercentValue(model.projectManagerRatio),
|
||||
amount: model.projectManagerAmount
|
||||
},
|
||||
{
|
||||
key: 'engineeringLeaderRatio',
|
||||
label: '项目负责人',
|
||||
percentText: formatRatioText(model.engineeringLeaderRatio),
|
||||
percent: toPercentValue(model.engineeringLeaderRatio),
|
||||
amount: model.engineeringLeaderAmount
|
||||
key: 'projectLeadRatio',
|
||||
label: '项目经理/项目负责人',
|
||||
percentText: formatRatioText(model.projectLeadRatio),
|
||||
percent: toPercentValue(model.projectLeadRatio),
|
||||
amount: model.projectLeadAmount
|
||||
},
|
||||
{
|
||||
key: 'officeRatio',
|
||||
@@ -400,8 +386,8 @@ const buildSpecialtyRows = (model?: OutputSplitApi.ProjectOutputSplitVO) => {
|
||||
return OUTPUT_SPLIT_SPECIALTY_OPTIONS.map((item) => ({
|
||||
key: `${item.value}Ratio`,
|
||||
label: item.label,
|
||||
percentText: formatRatioText((model as Record<string, number | undefined>)[`${item.value}Ratio`]),
|
||||
percent: toPercentValue((model as Record<string, number | undefined>)[`${item.value}Ratio`]),
|
||||
percentText: formatRatioText(getRatioValue(model, `${item.value}Ratio`)),
|
||||
percent: toPercentValue(getRatioValue(model, `${item.value}Ratio`)),
|
||||
amount: amountMap[item.value]
|
||||
}))
|
||||
}
|
||||
@@ -413,115 +399,24 @@ const editSpecialtyRatioItems = computed(() => buildSpecialtyRows(editForm.value
|
||||
|
||||
const annualCategoryMeta = computed(() => {
|
||||
const model = formData.value
|
||||
const currentOption =
|
||||
annualCategoryOptions.find((item) => item.value === selectedAnnualCategory.value) ||
|
||||
annualCategoryOptions[0]
|
||||
if (!model) {
|
||||
const option = annualCategoryOptions.find((item) => item.value === selectedAnnualCategory.value)
|
||||
if (!model || !option) {
|
||||
return { label: '-', ratio: 0, totalAmount: 0 }
|
||||
}
|
||||
if (selectedAnnualCategory.value === 'project_lead') {
|
||||
const ratio = Number(toNumeric(model.projectLeadRatio).toFixed(4))
|
||||
return {
|
||||
label: currentOption.label,
|
||||
ratio: 0,
|
||||
percentText: '0.00%',
|
||||
totalAmount: 0,
|
||||
formulaText: '-'
|
||||
label: option.label,
|
||||
ratio,
|
||||
totalAmount: multiplyAmount(model.assessmentOutputValue, ratio)
|
||||
}
|
||||
}
|
||||
const officeRatio = toNumeric(model.officeRatio)
|
||||
const officeRatioText = formatRatioText(model.officeRatio)
|
||||
switch (selectedAnnualCategory.value) {
|
||||
case 'projectManager':
|
||||
return {
|
||||
label: currentOption.label,
|
||||
ratio: toNumeric(model.projectManagerRatio),
|
||||
percentText: formatRatioText(model.projectManagerRatio),
|
||||
totalAmount: toNumeric(model.projectManagerAmount),
|
||||
formulaText: `考核产值 × 项目经理比例 ${formatRatioText(model.projectManagerRatio)}`
|
||||
}
|
||||
case 'engineeringLeader':
|
||||
return {
|
||||
label: currentOption.label,
|
||||
ratio: toNumeric(model.engineeringLeaderRatio),
|
||||
percentText: formatRatioText(model.engineeringLeaderRatio),
|
||||
totalAmount: toNumeric(model.engineeringLeaderAmount),
|
||||
formulaText: `考核产值 × 项目负责人比例 ${formatRatioText(model.engineeringLeaderRatio)}`
|
||||
}
|
||||
case 'arch': {
|
||||
const ratio = Number((officeRatio * toNumeric(model.archRatio)).toFixed(4))
|
||||
return {
|
||||
label: currentOption.label,
|
||||
ratio,
|
||||
percentText: formatRatioText(ratio),
|
||||
totalAmount: toNumeric(model.archAmount),
|
||||
formulaText: `考核产值 × 专业所比例 ${officeRatioText} × 建筑专业比例 ${formatRatioText(model.archRatio)}`
|
||||
}
|
||||
}
|
||||
case 'decor': {
|
||||
const ratio = Number((officeRatio * toNumeric(model.decorRatio)).toFixed(4))
|
||||
return {
|
||||
label: currentOption.label,
|
||||
ratio,
|
||||
percentText: formatRatioText(ratio),
|
||||
totalAmount: toNumeric(model.decorAmount),
|
||||
formulaText: `考核产值 × 专业所比例 ${officeRatioText} × 装修专业比例 ${formatRatioText(model.decorRatio)}`
|
||||
}
|
||||
}
|
||||
case 'struct': {
|
||||
const ratio = Number((officeRatio * toNumeric(model.structRatio)).toFixed(4))
|
||||
return {
|
||||
label: currentOption.label,
|
||||
ratio,
|
||||
percentText: formatRatioText(ratio),
|
||||
totalAmount: toNumeric(model.structAmount),
|
||||
formulaText: `考核产值 × 专业所比例 ${officeRatioText} × 结构专业比例 ${formatRatioText(model.structRatio)}`
|
||||
}
|
||||
}
|
||||
case 'water': {
|
||||
const ratio = Number((officeRatio * toNumeric(model.waterRatio)).toFixed(4))
|
||||
return {
|
||||
label: currentOption.label,
|
||||
ratio,
|
||||
percentText: formatRatioText(ratio),
|
||||
totalAmount: toNumeric(model.waterAmount),
|
||||
formulaText: `考核产值 × 专业所比例 ${officeRatioText} × 水专业比例 ${formatRatioText(model.waterRatio)}`
|
||||
}
|
||||
}
|
||||
case 'elec': {
|
||||
const ratio = Number((officeRatio * toNumeric(model.elecRatio)).toFixed(4))
|
||||
return {
|
||||
label: currentOption.label,
|
||||
ratio,
|
||||
percentText: formatRatioText(ratio),
|
||||
totalAmount: toNumeric(model.elecAmount),
|
||||
formulaText: `考核产值 × 专业所比例 ${officeRatioText} × 电气专业比例 ${formatRatioText(model.elecRatio)}`
|
||||
}
|
||||
}
|
||||
case 'hvac': {
|
||||
const ratio = Number((officeRatio * toNumeric(model.hvacRatio)).toFixed(4))
|
||||
return {
|
||||
label: currentOption.label,
|
||||
ratio,
|
||||
percentText: formatRatioText(ratio),
|
||||
totalAmount: toNumeric(model.hvacAmount),
|
||||
formulaText: `考核产值 × 专业所比例 ${officeRatioText} × 暖通专业比例 ${formatRatioText(model.hvacRatio)}`
|
||||
}
|
||||
}
|
||||
case 'digital': {
|
||||
const ratio = Number((officeRatio * toNumeric(model.digitalRatio)).toFixed(4))
|
||||
return {
|
||||
label: currentOption.label,
|
||||
ratio,
|
||||
percentText: formatRatioText(ratio),
|
||||
totalAmount: toNumeric(model.digitalAmount),
|
||||
formulaText: `考核产值 × 专业所比例 ${officeRatioText} × 数字化设计专业比例 ${formatRatioText(model.digitalRatio)}`
|
||||
}
|
||||
}
|
||||
default:
|
||||
return {
|
||||
label: currentOption.label,
|
||||
ratio: 0,
|
||||
percentText: '0.00%',
|
||||
totalAmount: 0,
|
||||
formulaText: '-'
|
||||
}
|
||||
const specialtyRatio = toNumeric(getRatioValue(model, `${selectedAnnualCategory.value}Ratio`))
|
||||
const ratio = Number((toNumeric(model.officeRatio) * specialtyRatio).toFixed(4))
|
||||
return {
|
||||
label: option.label,
|
||||
ratio,
|
||||
totalAmount: multiplyAmount(model.assessmentOutputValue, ratio)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -553,8 +448,7 @@ const annualSummaryCards = computed(() => [
|
||||
currentPlanning.value?.assessmentOutputValue,
|
||||
currentPlanning.value?.totalDistributionAmount,
|
||||
annualCategoryMeta.value.ratio
|
||||
),
|
||||
percentText: formatRatioText(currentPlanning.value?.totalDistributionAmount)
|
||||
)
|
||||
},
|
||||
{
|
||||
label: '已分配',
|
||||
@@ -562,8 +456,7 @@ const annualSummaryCards = computed(() => [
|
||||
currentPlanning.value?.assessmentOutputValue,
|
||||
currentPlanning.value?.allocatedAmount,
|
||||
annualCategoryMeta.value.ratio
|
||||
),
|
||||
percentText: formatRatioText(currentPlanning.value?.allocatedAmount)
|
||||
)
|
||||
},
|
||||
{
|
||||
label: '待分配',
|
||||
@@ -571,8 +464,7 @@ const annualSummaryCards = computed(() => [
|
||||
currentPlanning.value?.assessmentOutputValue,
|
||||
currentPlanning.value?.pendingAmount,
|
||||
annualCategoryMeta.value.ratio
|
||||
),
|
||||
percentText: formatRatioText(currentPlanning.value?.pendingAmount)
|
||||
)
|
||||
}
|
||||
])
|
||||
|
||||
@@ -599,12 +491,7 @@ const recalculateOfficeRatio = () => {
|
||||
if (!editForm.value) {
|
||||
return
|
||||
}
|
||||
editForm.value.officeRatio = Number(
|
||||
Math.max(
|
||||
0,
|
||||
1 - Number(editForm.value.projectManagerRatio || 0) - Number(editForm.value.engineeringLeaderRatio || 0)
|
||||
).toFixed(4)
|
||||
)
|
||||
editForm.value.officeRatio = Number(Math.max(0, 1 - Number(editForm.value.projectLeadRatio || 0)).toFixed(4))
|
||||
}
|
||||
|
||||
const recalculateArchRatio = () => {
|
||||
@@ -771,7 +658,8 @@ const updateProjectRatio = (key: string, value?: number) => {
|
||||
if (!editForm.value || key === 'officeRatio') {
|
||||
return
|
||||
}
|
||||
;(editForm.value as Record<string, number | undefined>)[key] = fromPercentValue(value) ?? 0
|
||||
;(editForm.value as unknown as Record<string, number | undefined>)[key] =
|
||||
fromPercentValue(value) ?? 0
|
||||
recalculateOfficeRatio()
|
||||
}
|
||||
|
||||
@@ -779,7 +667,8 @@ const updateSpecialtyRatio = (key: string, value?: number) => {
|
||||
if (!editForm.value || key === 'archRatio') {
|
||||
return
|
||||
}
|
||||
;(editForm.value as Record<string, number | undefined>)[key] = fromPercentValue(value) ?? 0
|
||||
;(editForm.value as unknown as Record<string, number | undefined>)[key] =
|
||||
fromPercentValue(value) ?? 0
|
||||
recalculateArchRatio()
|
||||
}
|
||||
|
||||
@@ -788,19 +677,18 @@ const handleSave = async () => {
|
||||
return
|
||||
}
|
||||
if (Math.abs(projectRatioTotal.value - 100) >= 0.001) {
|
||||
message.warning('项目层比例合计必须等于100%')
|
||||
message.warning('项目层比例合计必须等于 100%')
|
||||
return
|
||||
}
|
||||
if (Math.abs(specialtyRatioTotal.value - 100) >= 0.001) {
|
||||
message.warning('专业层比例合计必须等于100%')
|
||||
message.warning('专业层比例合计必须等于 100%')
|
||||
return
|
||||
}
|
||||
saveLoading.value = true
|
||||
try {
|
||||
await OutputSplitApi.saveProjectOutputSplit({
|
||||
planningId: editForm.value.planningId,
|
||||
projectManagerRatio: editForm.value.projectManagerRatio,
|
||||
engineeringLeaderRatio: editForm.value.engineeringLeaderRatio,
|
||||
projectLeadRatio: editForm.value.projectLeadRatio,
|
||||
officeRatio: editForm.value.officeRatio,
|
||||
archRatio: editForm.value.archRatio,
|
||||
decorRatio: editForm.value.decorRatio,
|
||||
@@ -826,3 +714,23 @@ onActivated(() => {
|
||||
getProjectList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ratio-card {
|
||||
padding: 12px;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.ratio-title {
|
||||
margin-bottom: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ratio-amount {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user