0513新功能优化

This commit is contained in:
lzm
2026-05-13 11:41:30 +08:00
parent ddc0c7db1e
commit 5cb913cb0a
20 changed files with 442 additions and 229 deletions

View File

@@ -1,4 +1,6 @@
import request from '@/config/axios'
import type { ProjectPlanningVO } from '@/api/tjt/planning'
import type { ProjectPlanningQuarterVO } from '@/api/tjt/planningQuarter'
export interface ProjectOutputSplitVO {
id?: number
@@ -45,10 +47,20 @@ export type ProjectOutputSplitSaveVO = Pick<
| 'digitalRatio'
>
export interface ProjectOutputSplitPlanningDetailVO {
planning: ProjectPlanningVO
outputSplit: ProjectOutputSplitVO
quarters: ProjectPlanningQuarterVO[]
}
export const getProjectOutputSplitByPlanningId = (planningId: number) => {
return request.get({ url: '/tjt/output-split/get-by-planning', params: { planningId } })
}
export const getProjectOutputSplitPlanningDetail = (planningId: number) => {
return request.get({ url: '/tjt/output-split/planning-detail', params: { planningId } })
}
export const saveProjectOutputSplit = (data: ProjectOutputSplitSaveVO) => {
return request.put({ url: '/tjt/output-split/save', data })
}

View File

@@ -1,4 +1,5 @@
import request from '@/config/axios'
import type { ProjectPlanningGuideDetailVO } from '@/api/tjt/planningGuideDetail'
export interface ProjectPlanningVO {
id?: number
@@ -71,6 +72,11 @@ export interface ProjectPlanningPageReqVO extends PageParam {
createTime?: string[]
}
export interface ProjectPlanningOutputEditDetailVO {
planning: ProjectPlanningVO
guideDetails: ProjectPlanningGuideDetailVO[]
}
export const getProjectPlanningPage = (params: ProjectPlanningPageReqVO) => {
return request.get({ url: '/tjt/planning/page', params })
}
@@ -79,6 +85,10 @@ export const getProjectPlanning = (id: number) => {
return request.get({ url: '/tjt/planning/get', params: { id } })
}
export const getProjectPlanningOutputEditDetail = (id: number) => {
return request.get({ url: '/tjt/planning/output-edit-detail', params: { id } })
}
export const getProjectPlanningListByProjectId = (projectId: number) => {
return request.get({ url: '/tjt/planning/list-by-project', params: { projectId } })
}

View File

@@ -1,4 +1,5 @@
import request from '@/config/axios'
import type { ProjectPlanningVO } from '@/api/tjt/planning'
export interface ProjectPlanningQuarterVO {
id?: number
@@ -15,6 +16,11 @@ export type ProjectPlanningQuarterSaveVO = Omit<
'distributionAmount' | 'createTime'
>
export interface ProjectPlanningQuarterPlanningDetailVO {
planning: ProjectPlanningVO
quarters: ProjectPlanningQuarterVO[]
}
export const getProjectPlanningQuarter = (id: number) => {
return request.get({ url: '/tjt/planning-quarter/get', params: { id } })
}
@@ -23,6 +29,13 @@ export const getProjectPlanningQuarterListByPlanningId = (planningId: number) =>
return request.get({ url: '/tjt/planning-quarter/list-by-planning', params: { planningId } })
}
export const getProjectPlanningQuarterPlanningDetail = (planningId: number) => {
return request.get({
url: '/tjt/planning-quarter/planning-detail',
params: { planningId }
})
}
export const createProjectPlanningQuarter = (data: ProjectPlanningQuarterSaveVO) => {
return request.post({ url: '/tjt/planning-quarter/create', data })
}

View File

@@ -34,6 +34,7 @@ export interface SpecialtyRoleSplitSaveItemVO {
export interface SpecialtyRoleSplitBatchSaveVO {
planningId: number
items: SpecialtyRoleSplitSaveItemVO[]
temporarySave?: boolean
}
export const getSpecialtyRoleSplitListByPlanningId = (planningId: number) => {

View File

@@ -450,11 +450,18 @@ const handleDelete = async (id: number) => {
} catch {}
}
let activatedOnce = false
onMounted(() => {
getList()
})
onActivated(() => {
// KeepAlive 首次挂载也会触发 onActivated跳过首轮避免重复请求列表。
if (!activatedOnce) {
activatedOnce = true
return
}
getList()
})
</script>

View File

@@ -451,11 +451,18 @@ const handleDelete = async (id: number) => {
} catch {}
}
let activatedOnce = false
onMounted(() => {
getList()
})
onActivated(() => {
// KeepAlive 首次挂载也会触发 onActivated跳过首轮避免重复请求列表。
if (!activatedOnce) {
activatedOnce = true
return
}
getList()
})
</script>

View File

@@ -504,12 +504,19 @@ const handleDelete = async (id: number) => {
} catch {}
}
let activatedOnce = false
onMounted(async () => {
await loadOfficeOptions()
await getList()
})
onActivated(() => {
// KeepAlive 首次挂载也会触发 onActivated跳过首轮避免重复请求列表。
if (!activatedOnce) {
activatedOnce = true
return
}
getList()
})
</script>

View File

@@ -236,11 +236,18 @@ const handleDelete = async (id: number) => {
} catch {}
}
let activatedOnce = false
onMounted(() => {
getList()
})
onActivated(() => {
// KeepAlive 首次挂载也会触发 onActivated跳过首轮避免重复请求列表。
if (!activatedOnce) {
activatedOnce = true
return
}
getList()
})
</script>

View File

@@ -103,108 +103,126 @@
</el-col>
<el-col :span="16">
<ContentWrap v-if="currentPlanning && formData">
<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)]">
年度{{ formData.year || '-' }}考核产值{{ formatAmountText(formData.assessmentOutputValue) }}
<ContentWrap>
<div v-loading="quarterLoading" class="min-h-320px">
<template v-if="currentPlanning && formData">
<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)]">
年度{{ formData.year || '-' }}考核产值{{
formatAmountText(formData.assessmentOutputValue)
}}
</div>
</div>
<div class="flex items-center gap-12px">
<el-button
v-hasPermi="['tjt:output-split:update']"
plain
type="primary"
@click="openEditDialog"
>
<Icon class="mr-5px" icon="ep:edit" />
编辑比例
</el-button>
</div>
</div>
</div>
<div class="flex items-center gap-12px">
<el-button
v-hasPermi="['tjt:output-split:update']"
plain
type="primary"
@click="openEditDialog"
>
<Icon class="mr-5px" icon="ep:edit" />
编辑比例
</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="工程负责人">
{{ getProjectLeadText(formData.projectManagerName, formData.engineeringLeaderName) }}
</el-descriptions-item>
</el-descriptions>
<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="工程负责人">
{{
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="160" prop="label" />
<el-table-column align="center" label="比例" min-width="120">
<template #default="scope">
{{ scope.row.percentText }}
</template>
</el-table-column>
<el-table-column align="center" label="金额(元)" min-width="140">
<template #default="scope">
{{ formatAmountText(scope.row.amount) }}
</template>
</el-table-column>
</el-table>
<el-divider content-position="left">项目层结果</el-divider>
<el-table :data="projectResultRows" border>
<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 }}
</template>
</el-table-column>
<el-table-column align="center" label="金额(元)" min-width="140">
<template #default="scope">
{{ formatAmountText(scope.row.amount) }}
</template>
</el-table-column>
</el-table>
<el-divider content-position="left">专业层结果</el-divider>
<el-table :data="specialtyResultRows" border>
<el-table-column align="center" label="专业" min-width="140" prop="label" />
<el-table-column align="center" label="比例" min-width="120">
<template #default="scope">
{{ scope.row.percentText }}
</template>
</el-table-column>
<el-table-column align="center" label="金额(元)" min-width="140">
<template #default="scope">
{{ formatAmountText(scope.row.amount) }}
</template>
</el-table-column>
</el-table>
<el-divider content-position="left">专业层结果</el-divider>
<el-table :data="specialtyResultRows" border>
<el-table-column align="center" label="专业" min-width="140" prop="label" />
<el-table-column align="center" label="比例" min-width="120">
<template #default="scope">
{{ scope.row.percentText }}
</template>
</el-table-column>
<el-table-column align="center" label="金额(元)" min-width="140">
<template #default="scope">
{{ formatAmountText(scope.row.amount) }}
</template>
</el-table-column>
</el-table>
<el-divider content-position="left">年度分配信息</el-divider>
<div class="mb-12px">
<el-radio-group v-model="selectedAnnualCategory" size="small">
<el-radio-button
v-for="item in annualCategoryOptions"
:key="item.value"
:label="item.value"
>
{{ item.label }}
</el-radio-button>
</el-radio-group>
</div>
<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>
<el-divider content-position="left">年度分配信息</el-divider>
<div class="mb-12px">
<el-radio-group v-model="selectedAnnualCategory" size="small">
<el-radio-button
v-for="item in annualCategoryOptions"
:key="item.value"
:label="item.value"
>
{{ item.label }}
</el-radio-button>
</el-radio-group>
</div>
</el-col>
</el-row>
<el-table v-loading="quarterLoading" :data="annualDistributionRows" border>
<el-table-column align="center" label="分配年度" min-width="120" prop="distributionYear" />
<el-table-column
v-for="quarter in QUARTER_OPTIONS"
:key="String(quarter.value)"
align="center"
:label="quarter.label"
min-width="150"
>
<template #default="scope">
{{ formatAmountText(scope.row.quarterAmounts[Number(quarter.value)]) }}
</template>
</el-table-column>
<el-table-column align="center" label="年度合计(元)" min-width="160">
<template #default="scope">
{{ formatAmountText(scope.row.yearTotal) }}
</template>
</el-table-column>
</el-table>
</ContentWrap>
<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>
</el-col>
</el-row>
<el-table :data="annualDistributionRows" border>
<el-table-column
align="center"
label="分配年度"
min-width="120"
prop="distributionYear"
/>
<el-table-column
v-for="quarter in QUARTER_OPTIONS"
:key="String(quarter.value)"
align="center"
:label="quarter.label"
min-width="150"
>
<template #default="scope">
{{ formatAmountText(scope.row.quarterAmounts[Number(quarter.value)]) }}
</template>
</el-table-column>
<el-table-column align="center" label="年度合计(元)" min-width="160">
<template #default="scope">
{{ formatAmountText(scope.row.yearTotal) }}
</template>
</el-table-column>
</el-table>
</template>
<ContentWrap v-else>
<el-empty description="请选择合约规划后查看分配结果" />
<el-empty v-else-if="!quarterLoading" description="请选择合约规划后查看分配结果" />
</div>
</ContentWrap>
</el-col>
</el-row>
@@ -618,14 +636,12 @@ const loadPlanningRelatedData = async (planning: PlanningApi.ProjectPlanningVO)
}
quarterLoading.value = true
try {
const [planningDetail, outputSplit, quarterList] = await Promise.all([
PlanningApi.getProjectPlanning(planning.id),
OutputSplitApi.getProjectOutputSplitByPlanningId(planning.id),
PlanningQuarterApi.getProjectPlanningQuarterListByPlanningId(planning.id)
])
currentPlanning.value = planningDetail
formData.value = outputSplit
quarterRows.value = buildQuarterRows(planningDetail, quarterList)
const detail = await OutputSplitApi.getProjectOutputSplitPlanningDetail(planning.id)
currentPlanning.value = detail?.planning
formData.value = detail?.outputSplit
quarterRows.value = detail?.planning
? buildQuarterRows(detail.planning, detail.quarters || [])
: []
} finally {
quarterLoading.value = false
}
@@ -723,11 +739,18 @@ const handleSave = async () => {
}
}
let activatedOnce = false
onMounted(() => {
getProjectList()
})
onActivated(() => {
// KeepAlive 首次挂载也会触发 onActivated跳过首轮避免重复请求项目与合约规划列表。
if (!activatedOnce) {
activatedOnce = true
return
}
getProjectList()
})
</script>

View File

@@ -1192,12 +1192,17 @@ const open = async (id: number) => {
dialogVisible.value = true
formLoading.value = true
try {
const data = await PlanningApi.getProjectPlanning(id)
formData.value = normalizeFormData(data)
const detail = await PlanningApi.getProjectPlanningOutputEditDetail(id)
if (!detail?.planning) {
formData.value = createFormData()
guideDetails.value = []
dialogVisible.value = false
message.warning('合约规划不存在或已被删除')
return
}
formData.value = normalizeFormData(detail.planning)
guideDetails.value = showGuideDetailSection.value
? normalizeGuideDetailList(
await PlanningGuideDetailApi.getProjectPlanningGuideDetailListByPlanningId(id)
)
? normalizeGuideDetailList(detail.guideDetails)
: []
applyCalculationRatioDefault()
if (formData.value.reviewOutsourceRatio === undefined || formData.value.reviewOutsourceRatio === null) {

View File

@@ -205,7 +205,24 @@ const open = async (id: number) => {
loading.value = true
deletedQuarterIds.value = []
try {
const planning = await PlanningApi.getProjectPlanning(id)
const detail = await PlanningQuarterApi.getProjectPlanningQuarterPlanningDetail(id)
if (!detail?.planning) {
planningId.value = undefined
formData.value = {
projectId: 0,
ownershipType: '',
calculationMethod: '',
planningContent: '',
totalDistributionAmount: 1,
progressRemark: ''
}
quarterRows.value = []
deletedQuarterIds.value = []
dialogVisible.value = false
message.warning('合约规划不存在或已被删除')
return
}
const planning = detail.planning
formData.value = {
...planning,
contractValueQuantity: planning.contractValueQuantity ?? 1,
@@ -214,8 +231,7 @@ const open = async (id: number) => {
totalDistributionAmount: planning.totalDistributionAmount ?? 1,
progressRemark: planning.progressRemark ?? ''
}
const quarterList = await PlanningQuarterApi.getProjectPlanningQuarterListByPlanningId(id)
quarterRows.value = buildQuarterRows(formData.value, quarterList)
quarterRows.value = buildQuarterRows(formData.value, detail.quarters || [])
} finally {
loading.value = false
}

View File

@@ -166,7 +166,9 @@
</el-col>
<el-col :span="15">
<ContentWrap v-if="currentPlanning">
<div v-loading="quarterLoading" class="min-h-320px">
<template v-if="currentPlanning">
<ContentWrap>
<div class="mb-16px flex items-center justify-between gap-16px">
<div>
<div class="text-16px font-600">{{ currentPlanning.planningContent }}</div>
@@ -298,7 +300,7 @@
</el-descriptions>
</ContentWrap>
<ContentWrap v-if="currentPlanning">
<ContentWrap>
<div class="mb-16px flex items-center justify-between gap-16px">
<div>
<div class="text-14px font-600">季度分配</div>
@@ -348,7 +350,7 @@
</el-col>
</el-row>
<el-table v-loading="quarterLoading" :data="quarterRows" border>
<el-table :data="quarterRows" border>
<el-table-column align="center" label="分配年度" width="150" prop="distributionYear" />
<el-table-column
v-for="quarter in QUARTER_OPTIONS"
@@ -366,10 +368,12 @@
</el-table-column>
</el-table>
</ContentWrap>
</template>
<ContentWrap v-else>
<ContentWrap v-else-if="!quarterLoading">
<el-empty description="请选择合约规划后查看测算结果和季度分配" />
</ContentWrap>
</div>
</el-col>
</el-row>
@@ -581,12 +585,11 @@ const getPlanningList = async () => {
}
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)
const detail = await PlanningQuarterApi.getProjectPlanningQuarterPlanningDetail(planningId)
currentPlanning.value = detail?.planning
quarterRows.value = detail?.planning ? buildQuarterRows(detail.planning, detail.quarters || []) : []
} finally {
quarterLoading.value = false
}
@@ -643,11 +646,18 @@ const handlePlanningOutputFormSuccess = async () => {
}
}
let activatedOnce = false
onMounted(() => {
getProjectList()
})
onActivated(() => {
// KeepAlive 首次挂载也会触发 onActivated跳过首轮避免重复请求项目与合约规划列表。
if (!activatedOnce) {
activatedOnce = true
return
}
getProjectList()
})
</script>

View File

@@ -159,71 +159,73 @@
/>
</ContentWrap>
<ContentWrap v-if="currentProfit">
<div class="mb-16px flex items-center justify-between gap-16px">
<div class="text-16px font-600">{{ currentProfit.projectName }}</div>
<el-button plain type="primary" @click="openProfitEditDialog">
<Icon class="mr-5px" icon="ep:edit" />
编辑盈亏参数
</el-button>
<ContentWrap>
<div v-loading="detailLoading" class="min-h-220px">
<template v-if="currentProfit">
<div class="mb-16px flex items-center justify-between gap-16px">
<div class="text-16px font-600">{{ currentProfit.projectName }}</div>
<el-button plain type="primary" @click="openProfitEditDialog">
<Icon class="mr-5px" icon="ep:edit" />
编辑盈亏参数
</el-button>
</div>
<el-descriptions :column="3" border>
<el-descriptions-item label="合同产值(元)">
{{ formatAmountText(currentProfit.contractAmount) }}
</el-descriptions-item>
<el-descriptions-item label="最终结算金额(元)">
{{ formatAmountText(currentProfit.finalSettlementAmount) }}
</el-descriptions-item>
<el-descriptions-item label="测算采用金额(元)">
<el-tooltip
v-if="isUsingContractAmount(currentProfit)"
content="最终结算金额未填写,当前暂按合同产值测算"
placement="top"
>
<span>{{ formatAmountText(currentProfit.effectiveSettlementAmount) }}</span>
</el-tooltip>
<span v-else>{{ formatAmountText(currentProfit.effectiveSettlementAmount) }}</span>
</el-descriptions-item>
<el-descriptions-item label="项目开始年度">
{{ currentProfit.projectStartYear || '-' }}
</el-descriptions-item>
<el-descriptions-item label="综合所协作金额(元)">
{{ formatAmountText(currentProfit.comprehensivePlanningAmount) }}
</el-descriptions-item>
<el-descriptions-item label="专业分包金额(元)">
{{ formatAmountText(currentProfit.subcontractPlanningAmount) }}
</el-descriptions-item>
<el-descriptions-item label="专业所产值(元)">
{{ formatAmountText(currentProfit.majorOutputValue) }}
</el-descriptions-item>
<el-descriptions-item label="专业所预计绩效(元)">
{{ formatAmountText(currentProfit.majorExpectedPerformance) }}
</el-descriptions-item>
<el-descriptions-item label="科创产值比例">
{{ formatPercentText(currentProfit.innovationOutputRate) }}
</el-descriptions-item>
<el-descriptions-item label="科创产值(元)">
{{ formatAmountText(currentProfit.innovationOutputValue) }}
</el-descriptions-item>
<el-descriptions-item label="其他成本(元)">
{{ formatAmountText(currentProfit.otherCost) }}
</el-descriptions-item>
<el-descriptions-item label="盈亏值(元)">
<span :class="profitLossClass(currentProfit.profitLossValue)">
{{ formatAmountText(currentProfit.profitLossValue) }}
</span>
</el-descriptions-item>
<el-descriptions-item label="盈亏百分比">
<span :class="profitLossClass(currentProfit.profitLossValue)">
{{ formatPercentText(currentProfit.profitLossRate) }}
</span>
</el-descriptions-item>
</el-descriptions>
</template>
<el-empty v-else-if="!detailLoading" description="请选择项目后查看盈亏详情" />
</div>
<el-descriptions :column="3" border>
<el-descriptions-item label="合同产值(元)">
{{ formatAmountText(currentProfit.contractAmount) }}
</el-descriptions-item>
<el-descriptions-item label="最终结算金额(元)">
{{ formatAmountText(currentProfit.finalSettlementAmount) }}
</el-descriptions-item>
<el-descriptions-item label="测算采用金额(元)">
<el-tooltip
v-if="isUsingContractAmount(currentProfit)"
content="最终结算金额未填写,当前暂按合同产值测算"
placement="top"
>
<span>{{ formatAmountText(currentProfit.effectiveSettlementAmount) }}</span>
</el-tooltip>
<span v-else>{{ formatAmountText(currentProfit.effectiveSettlementAmount) }}</span>
</el-descriptions-item>
<el-descriptions-item label="项目开始年度">
{{ currentProfit.projectStartYear || '-' }}
</el-descriptions-item>
<el-descriptions-item label="综合所协作金额(元)">
{{ formatAmountText(currentProfit.comprehensivePlanningAmount) }}
</el-descriptions-item>
<el-descriptions-item label="专业分包金额(元)">
{{ formatAmountText(currentProfit.subcontractPlanningAmount) }}
</el-descriptions-item>
<el-descriptions-item label="专业所产值(元)">
{{ formatAmountText(currentProfit.majorOutputValue) }}
</el-descriptions-item>
<el-descriptions-item label="专业所预计绩效(元)">
{{ formatAmountText(currentProfit.majorExpectedPerformance) }}
</el-descriptions-item>
<el-descriptions-item label="科创产值比例">
{{ formatPercentText(currentProfit.innovationOutputRate) }}
</el-descriptions-item>
<el-descriptions-item label="科创产值(元)">
{{ formatAmountText(currentProfit.innovationOutputValue) }}
</el-descriptions-item>
<el-descriptions-item label="其他成本(元)">
{{ formatAmountText(currentProfit.otherCost) }}
</el-descriptions-item>
<el-descriptions-item label="盈亏值(元)">
<span :class="profitLossClass(currentProfit.profitLossValue)">
{{ formatAmountText(currentProfit.profitLossValue) }}
</span>
</el-descriptions-item>
<el-descriptions-item label="盈亏百分比">
<span :class="profitLossClass(currentProfit.profitLossValue)">
{{ formatPercentText(currentProfit.profitLossRate) }}
</span>
</el-descriptions-item>
</el-descriptions>
</ContentWrap>
<ContentWrap v-else>
<el-empty description="请选择项目后查看盈亏详情" />
</ContentWrap>
<Dialog v-model="dialogVisible" title="编辑盈亏参数" width="520">
@@ -286,6 +288,7 @@ const message = useMessage()
const { t } = useI18n()
const loading = ref(false)
const detailLoading = ref(false)
const total = ref(0)
const list = ref<ProfitApi.ProjectProfitVO[]>([])
const currentProfit = ref<ProfitApi.ProjectProfitVO>()
@@ -337,21 +340,24 @@ const queryProjectStartYearValue = computed({
const getList = async () => {
loading.value = true
let targetProfit: ProfitApi.ProjectProfitVO | undefined
try {
const data = await ProfitApi.getProjectProfitPage(queryParams)
list.value = data.list
total.value = data.total
if (!list.value.length) {
currentProfit.value = undefined
return
} else {
const targetProjectId = currentProfit.value?.projectId || list.value[0].projectId
targetProfit = list.value.find((item) => item.projectId === targetProjectId) || list.value[0]
}
const targetProjectId = currentProfit.value?.projectId || list.value[0].projectId
const targetProfit = list.value.find((item) => item.projectId === targetProjectId) || list.value[0]
await nextTick()
profitTableRef.value?.setCurrentRow(targetProfit)
} finally {
loading.value = false
}
if (targetProfit) {
await nextTick()
profitTableRef.value?.setCurrentRow(targetProfit)
}
}
const handleQuery = () => {
@@ -369,14 +375,24 @@ const handleCurrentProfitChange = async (row?: ProfitApi.ProjectProfitVO) => {
currentProfit.value = undefined
return
}
currentProfit.value = await ProfitApi.getProjectProfit(row.projectId)
detailLoading.value = true
try {
currentProfit.value = await ProfitApi.getProjectProfit(row.projectId)
} finally {
detailLoading.value = false
}
}
const refreshCurrentProfit = async () => {
if (!currentProfit.value?.projectId) {
return
}
currentProfit.value = await ProfitApi.getProjectProfit(currentProfit.value.projectId)
detailLoading.value = true
try {
currentProfit.value = await ProfitApi.getProjectProfit(currentProfit.value.projectId)
} finally {
detailLoading.value = false
}
await getList()
}
@@ -440,11 +456,18 @@ const profitLossClass = (value?: number) => {
const isUsingContractAmount = (row?: ProfitApi.ProjectProfitVO) =>
!!row && Number(row.finalSettlementAmount || 0) <= 0 && Number(row.contractAmount || 0) > 0
let activatedOnce = false
onMounted(() => {
getList()
})
onActivated(() => {
// KeepAlive 首次挂载也会触发 onActivated跳过首轮避免重复请求列表。
if (!activatedOnce) {
activatedOnce = true
return
}
getList()
})
</script>

View File

@@ -526,11 +526,18 @@ const handlePlanningFormSuccess = async () => {
await getPlanningList()
}
let activatedOnce = false
onMounted(() => {
getList()
})
onActivated(() => {
// KeepAlive 首次挂载也会触发 onActivated跳过首轮避免重复请求列表。
if (!activatedOnce) {
activatedOnce = true
return
}
getList()
})
</script>

View File

@@ -384,11 +384,18 @@ const submitProjectBudgetExport = async () => {
}
}
let activatedOnce = false
onMounted(() => {
getProjectList()
})
onActivated(() => {
// KeepAlive 首次挂载也会触发 onActivated跳过首轮避免重复请求项目与合约规划列表。
if (!activatedOnce) {
activatedOnce = true
return
}
getProjectList()
})
</script>

View File

@@ -103,7 +103,9 @@
</el-col>
<el-col :span="16">
<ContentWrap v-if="currentPlanning && formData">
<ContentWrap>
<div v-loading="quarterLoading" class="min-h-320px">
<template v-if="currentPlanning && formData">
<div class="mb-16px flex items-center justify-between gap-16px">
<div>
<div class="text-16px font-600">{{ currentPlanning.planningContent }}</div>
@@ -193,7 +195,7 @@
</div>
</el-col>
</el-row>
<el-table v-loading="quarterLoading" :data="annualDistributionRows" border>
<el-table :data="annualDistributionRows" border>
<el-table-column align="center" label="分配年度" min-width="120" prop="distributionYear" />
<el-table-column
v-for="quarter in QUARTER_OPTIONS"
@@ -212,10 +214,10 @@
</template>
</el-table-column>
</el-table>
</ContentWrap>
</template>
<ContentWrap v-else>
<el-empty description="请选择合约规划后查看导出预览" />
<el-empty v-else-if="!quarterLoading" description="请选择合约规划后查看导出预览" />
</div>
</ContentWrap>
</el-col>
</el-row>
@@ -566,14 +568,12 @@ const loadPlanningRelatedData = async (planning: PlanningApi.ProjectPlanningVO)
}
quarterLoading.value = true
try {
const [planningDetail, outputSplit, quarterList] = await Promise.all([
PlanningApi.getProjectPlanning(planning.id),
OutputSplitApi.getProjectOutputSplitByPlanningId(planning.id),
PlanningQuarterApi.getProjectPlanningQuarterListByPlanningId(planning.id)
])
currentPlanning.value = planningDetail
formData.value = outputSplit
quarterRows.value = buildQuarterRows(planningDetail, quarterList)
const detail = await OutputSplitApi.getProjectOutputSplitPlanningDetail(planning.id)
currentPlanning.value = detail?.planning
formData.value = detail?.outputSplit
quarterRows.value = detail?.planning
? buildQuarterRows(detail.planning, detail.quarters || [])
: []
} finally {
quarterLoading.value = false
}
@@ -684,11 +684,18 @@ const submitProjectLeadQuarterExport = async () => {
}
}
let activatedOnce = false
onMounted(() => {
getProjectList()
})
onActivated(() => {
// KeepAlive 首次挂载也会触发 onActivated跳过首轮避免重复请求项目与合约规划列表。
if (!activatedOnce) {
activatedOnce = true
return
}
getProjectList()
})
</script>

View File

@@ -105,7 +105,9 @@
</el-col>
<el-col :span="16">
<ContentWrap v-if="currentPlanning && currentGroup">
<ContentWrap>
<div v-loading="previewLoading" class="min-h-320px">
<template v-if="currentPlanning && currentGroup">
<div class="mb-16px flex items-center justify-between gap-12px">
<div>
<div class="text-16px font-600">{{ currentPlanning.planningContent }}</div>
@@ -252,10 +254,10 @@
</el-table-column>
</el-table-column>
</el-table>
</ContentWrap>
</template>
<ContentWrap v-else>
<el-empty description="请选择合约规划后查看导出预览" />
<el-empty v-else-if="!previewLoading" description="请选择合约规划后查看导出预览" />
</div>
</ContentWrap>
</el-col>
</el-row>
@@ -545,11 +547,18 @@ watch(
}
)
let activatedOnce = false
onMounted(() => {
getProjectList()
})
onActivated(() => {
// KeepAlive 首次挂载也会触发 onActivated跳过首轮避免重复请求项目与合约规划列表。
if (!activatedOnce) {
activatedOnce = true
return
}
getProjectList()
})
</script>

View File

@@ -565,7 +565,14 @@ watch(
}
)
let activatedOnce = false
onActivated(() => {
// immediate watch 已经负责首次预览加载,跳过 KeepAlive 首轮激活避免重复请求。
if (!activatedOnce) {
activatedOnce = true
return
}
refreshCurrentPreview()
})

View File

@@ -105,7 +105,9 @@
</el-col>
<el-col :span="16">
<ContentWrap v-if="currentPlanning && currentGroup">
<ContentWrap>
<div v-loading="roleLoading" class="min-h-320px">
<template v-if="currentPlanning && currentGroup">
<div class="mb-16px flex items-center justify-between gap-12px">
<div class="text-16px font-600">{{ currentPlanning.planningContent }}</div>
<div class="flex items-center gap-12px">
@@ -172,10 +174,10 @@
</template>
</el-table-column>
</el-table>
</ContentWrap>
</template>
<ContentWrap v-else>
<el-empty description="请选择合约规划后查看人员分配" />
<el-empty v-else-if="!roleLoading" description="请选择合约规划后查看人员分配" />
</div>
</ContentWrap>
</el-col>
</el-row>
@@ -306,7 +308,8 @@
</template>
<template #footer>
<el-button :loading="saveLoading" type="primary" @click="handleSave">保存</el-button>
<el-button :loading="saveLoading" @click="handleTemporarySave">临时保存</el-button>
<el-button :loading="saveLoading" type="primary" @click="handleSave">校验保存</el-button>
<el-button @click="dialogVisible = false">取消</el-button>
</template>
</Dialog>
@@ -349,6 +352,7 @@ const message = useMessage()
const loading = ref(false)
const planningLoading = ref(false)
const roleLoading = ref(false)
const saveLoading = ref(false)
const total = ref(0)
const projectList = ref<ProjectApi.ProjectVO[]>([])
@@ -374,7 +378,7 @@ const queryParams = reactive<ProjectApi.ProjectPageReqVO>({
})
const getProjectRowIndex = (index: number) =>
(queryParams.pageNo - 1) * queryParams.pageSize + index + 1
(Number(queryParams.pageNo || 1) - 1) * Number(queryParams.pageSize || 10) + index + 1
const queryProjectStartYearValue = computed({
get: () => (queryParams.projectStartYear ? String(queryParams.projectStartYear) : undefined),
@@ -681,7 +685,8 @@ const updateRoleRatio = (row: SpecialtyRoleSplitVO, value?: number) => {
}
const buildSavePersons = (
row: SpecialtyRoleSplitVO
row: SpecialtyRoleSplitVO,
validate: boolean
):
| { persons: SpecialtyRolePersonVO[]; error?: never }
| { persons?: never; error: string } => {
@@ -693,35 +698,39 @@ const buildSavePersons = (
if (!hasEmployeeId && !hasRatio) {
continue
}
if (!hasEmployeeId || !hasRatio) {
if (!validate && !hasEmployeeId) {
continue
}
if (validate && (!hasEmployeeId || !hasRatio)) {
return { error: `${row.roleName} 的员工和比例必须同时填写` }
}
persons.push({
employeeId: person.employeeId,
employeeName: employeeName || undefined,
personRatio: roundRatio(person.personRatio)
personRatio: roundRatio(hasRatio ? person.personRatio : 0)
})
}
return { persons }
}
const validateAndBuildSaveItems = () => {
const buildSaveItems = (validate: boolean) => {
const items: SpecialtyRoleSplitApi.SpecialtyRoleSplitSaveItemVO[] = []
for (const group of editGroups.value) {
let roleTotal = 0
for (const row of group.rows) {
const result = buildSavePersons(row)
const result = buildSavePersons(row, validate)
if (result.error) {
message.warning(result.error)
return undefined
}
const persons = result.persons || []
const personTotalRatio = sumPersonRatios(persons)
if (personTotalRatio > 1 + EPSILON) {
if (validate && personTotalRatio > 1 + EPSILON) {
message.warning(`${group.specialtyName}-${row.roleName} 的人员比例合计不能大于 100%`)
return undefined
}
if (
validate &&
row.roleCode === DESIGN_ROLE_CODE &&
Number(row.roleAmount || 0) > EPSILON &&
persons.length === 0
@@ -737,7 +746,7 @@ const validateAndBuildSaveItems = () => {
persons: isProjectLeadRow(row) ? [] : persons
})
}
if (Math.abs(roleTotal - 1) > EPSILON) {
if (validate && Math.abs(roleTotal - 1) > EPSILON) {
message.warning(`${group.specialtyName} 的角色比例合计必须等于 100%`)
return undefined
}
@@ -798,10 +807,15 @@ const getPlanningList = async () => {
}
const loadRoleList = async (planningId: number) => {
const data = await SpecialtyRoleSplitApi.getSpecialtyRoleSplitListByPlanningId(planningId)
roleList.value = cloneRoleRows(data)
roleList.value.forEach((row) => syncRoleRatiosForGroup(roleList.value, row.specialtyCode))
syncAllDerivedValues(roleList.value)
roleLoading.value = true
try {
const data = await SpecialtyRoleSplitApi.getSpecialtyRoleSplitListByPlanningId(planningId)
roleList.value = cloneRoleRows(data)
roleList.value.forEach((row) => syncRoleRatiosForGroup(roleList.value, row.specialtyCode))
syncAllDerivedValues(roleList.value)
} finally {
roleLoading.value = false
}
}
const handleQuery = () => {
@@ -841,11 +855,11 @@ const openEditDialog = () => {
dialogVisible.value = true
}
const handleSave = async () => {
const saveRoleSplit = async (temporarySave: boolean) => {
if (!currentPlanning.value?.id) {
return
}
const items = validateAndBuildSaveItems()
const items = buildSaveItems(!temporarySave)
if (!items) {
return
}
@@ -853,21 +867,35 @@ const handleSave = async () => {
try {
await SpecialtyRoleSplitApi.saveSpecialtyRoleSplitBatch({
planningId: currentPlanning.value.id,
items
items,
temporarySave
})
message.success('保存成功')
dialogVisible.value = false
message.success(temporarySave ? '临时保存成功' : '保存成功')
if (!temporarySave) {
dialogVisible.value = false
}
await loadRoleList(currentPlanning.value.id)
} finally {
saveLoading.value = false
}
}
const handleTemporarySave = () => saveRoleSplit(true)
const handleSave = () => saveRoleSplit(false)
let activatedOnce = false
onMounted(() => {
getProjectList()
})
onActivated(() => {
// KeepAlive 首次挂载也会触发 onActivated跳过首轮避免重复请求项目与合约规划列表。
if (!activatedOnce) {
activatedOnce = true
return
}
getProjectList()
})
</script>

View File

@@ -299,11 +299,18 @@ const handleDelete = async (id: number) => {
} catch {}
}
let activatedOnce = false
onMounted(() => {
getList()
})
onActivated(() => {
// KeepAlive 首次挂载也会触发 onActivated跳过首轮避免重复请求列表。
if (!activatedOnce) {
activatedOnce = true
return
}
getList()
})
</script>