添加合计、考核产值预算表、专业间年度季度记取表、工程负责人记取表预览

This commit is contained in:
lzm
2026-06-03 14:42:03 +08:00
parent 81d259b44a
commit 4048454ff5
7 changed files with 1021 additions and 497 deletions

View File

@@ -133,13 +133,90 @@ export interface ProjectBudgetPreviewQuarterRow {
}
export interface ProjectQuarterOutputExportReqVO {
planningId: number
projectId: number
year?: number
}
export interface ProjectLeadQuarterOutputExportReqVO {
planningId: number
export interface ProjectQuarterOutputPreviewRespVO {
projectCode?: string
projectName?: string
year?: number
rows?: ProjectQuarterOutputPreviewRow[]
}
export interface ProjectQuarterOutputPreviewRow {
serialNo?: number
totalRow?: boolean
placeholderRow?: boolean
outputType?: string
designContent?: string
quarterOneAmountWan?: number
quarterTwoAmountWan?: number
quarterThreeAmountWan?: number
quarterFourAmountWan?: number
yearTotalAmountWan?: number
projectLeadRatio?: number
projectLeadAssessmentOutputWan?: number
projectLeadQuarterOneAmountWan?: number
projectLeadQuarterTwoAmountWan?: number
projectLeadQuarterThreeAmountWan?: number
projectLeadQuarterFourAmountWan?: number
projectLeadYearTotalAmountWan?: number
officeRatio?: number
officeAssessmentOutputWan?: number
officeQuarterOneAmountWan?: number
officeQuarterTwoAmountWan?: number
officeQuarterThreeAmountWan?: number
officeQuarterFourAmountWan?: number
officeYearTotalAmountWan?: number
archRatio?: number
decorRatio?: number
structRatio?: number
waterRatio?: number
hvacRatio?: number
elecRatio?: number
digitalRatio?: number
archAssessmentOutputWan?: number
decorAssessmentOutputWan?: number
structAssessmentOutputWan?: number
waterAssessmentOutputWan?: number
hvacAssessmentOutputWan?: number
elecAssessmentOutputWan?: number
digitalAssessmentOutputWan?: number
}
export interface ProjectLeadQuarterOutputExportReqVO {
projectId: number
year?: number
}
export interface ProjectLeadQuarterOutputPreviewRespVO {
projectName?: string
year?: number
projectManagerNames?: string
engineeringPrincipalNames?: string
rows?: ProjectLeadQuarterOutputPreviewRow[]
}
export interface ProjectLeadQuarterOutputPreviewRow {
serialNo?: number
outputType?: string
designContent?: string
subtotalRow?: boolean
projectManagerNames?: string
projectManagerRatio?: number
engineeringPrincipalNames?: string
engineeringPrincipalRatio?: number
projectManagerQuarterOneAmountWan?: number
projectManagerQuarterTwoAmountWan?: number
projectManagerQuarterThreeAmountWan?: number
projectManagerQuarterFourAmountWan?: number
projectManagerYearTotalAmountWan?: number
engineeringPrincipalQuarterOneAmountWan?: number
engineeringPrincipalQuarterTwoAmountWan?: number
engineeringPrincipalQuarterThreeAmountWan?: number
engineeringPrincipalQuarterFourAmountWan?: number
engineeringPrincipalYearTotalAmountWan?: number
}
export interface SpecialtyPersonOutputExportReqVO {
@@ -225,6 +302,13 @@ export const exportProjectQuarterOutput = (params: ProjectQuarterOutputExportReq
})
}
export const getProjectQuarterOutputPreview = (params: ProjectQuarterOutputExportReqVO) => {
return request.get<ProjectQuarterOutputPreviewRespVO>({
url: '/tjt/report/project-quarter-output/preview',
params: { ...params, _t: Date.now() }
})
}
export const exportProjectLeadQuarterOutput = (params: ProjectLeadQuarterOutputExportReqVO) => {
return request.download({
url: '/tjt/report/project-lead-quarter-output/export-excel',
@@ -232,6 +316,13 @@ export const exportProjectLeadQuarterOutput = (params: ProjectLeadQuarterOutputE
})
}
export const getProjectLeadQuarterOutputPreview = (params: ProjectLeadQuarterOutputExportReqVO) => {
return request.get<ProjectLeadQuarterOutputPreviewRespVO>({
url: '/tjt/report/project-lead-quarter-output/preview',
params: { ...params, _t: Date.now() }
})
}
export const exportSpecialtyPersonOutput = (params: SpecialtyPersonOutputExportReqVO) => {
return request.download({ url: '/tjt/report/specialty-person-output/export-excel', params })
}

View File

@@ -98,6 +98,12 @@
</el-table-column>
<el-table-column align="center" label="排序" prop="sortNo" width="80" />
</el-table>
<PlanningOwnershipSummary
:planning-list="planningList"
amount-field="assessmentOutputValue"
amount-label="考核产值()"
title="归属类型考核产值合计"
/>
</ContentWrap>
</template>
@@ -287,6 +293,7 @@ import * as ProjectApi from '@/api/tjt/project'
import * as PlanningApi from '@/api/tjt/planning'
import * as PlanningQuarterApi from '@/api/tjt/planningQuarter'
import * as OutputSplitApi from '@/api/tjt/outputSplit'
import PlanningOwnershipSummary from '@/views/tjt/shared/PlanningOwnershipSummary.vue'
import SplitPane from '@/views/tjt/shared/SplitPane.vue'
import {
formatAmountText,

View File

@@ -161,6 +161,12 @@
</el-table-column>
<el-table-column align="center" label="排序" prop="sortNo" width="80" />
</el-table>
<PlanningOwnershipSummary
:planning-list="planningList"
amount-field="assessmentOutputValue"
amount-label="考核产值()"
title="归属类型考核产值合计"
/>
</ContentWrap>
</template>
@@ -464,6 +470,7 @@ 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 PlanningOwnershipSummary from '@/views/tjt/shared/PlanningOwnershipSummary.vue'
import SplitPane from '@/views/tjt/shared/SplitPane.vue'
import {
CONTRACT_SIGN_OPTIONS,

View File

@@ -0,0 +1,495 @@
<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="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" />
<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-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>
<ContentWrap>
<div v-loading="previewLoading" class="min-h-320px">
<template v-if="currentProject">
<div class="mb-16px flex flex-wrap items-center justify-between gap-12px">
<div>
<div class="text-16px font-600">工程负责人年度表预览</div>
<div class="mt-6px text-12px text-[var(--el-text-color-secondary)]">
{{ currentProject?.projectName || '-' }}
</div>
</div>
<div class="flex items-center gap-10px">
<el-date-picker
v-model="selectedYearValue"
class="!w-150px"
clearable
placeholder="计取年度"
type="year"
value-format="YYYY"
/>
<el-button :loading="previewLoading" @click="loadProjectLeadQuarterPreview">
刷新
</el-button>
<el-button
v-hasPermi="['tjt:report-project-lead-quarter:export']"
:disabled="!selectedYear"
:loading="exportLoading"
plain
type="success"
@click="handleExportProjectLeadQuarter"
>
<Icon class="mr-5px" icon="ep:download" />
导出工程负责人年度表
</el-button>
</div>
</div>
<el-descriptions :column="2" border class="mb-16px">
<el-descriptions-item label="项目名称">
{{ previewData?.projectName || currentProject?.projectName || '-' }}
</el-descriptions-item>
<el-descriptions-item label="预览年度">
{{ previewData?.year || selectedYear || '-' }}
</el-descriptions-item>
<el-descriptions-item label="项目经理人员">
{{ previewData?.projectManagerNames || '-' }}
</el-descriptions-item>
<el-descriptions-item label="工程负责人人员">
{{ previewData?.engineeringPrincipalNames || '-' }}
</el-descriptions-item>
<el-descriptions-item label="金额单位">万元</el-descriptions-item>
</el-descriptions>
<div class="mb-12px text-14px font-600">工作量分配预览</div>
<el-table
:data="previewRows"
:empty-text="selectedYear ? '暂无工作量分配预览数据' : '请先选择年度'"
border
class="mb-20px"
max-height="360"
>
<el-table-column align="center" label="序号" width="70" prop="serialNo" />
<el-table-column align="center" label="项目名称" min-width="180">
<template #default>
{{ previewData?.projectName || currentProject?.projectName || '-' }}
</template>
</el-table-column>
<el-table-column align="center" label="产值类型" min-width="150" prop="outputType" />
<el-table-column
align="center"
label="设计内容"
min-width="180"
prop="designContent"
show-overflow-tooltip
/>
<el-table-column
align="center"
:label="previewData?.projectManagerNames || '项目经理人员'"
min-width="150"
>
<template #default="scope">
{{ formatNullablePercent(scope.row.projectManagerRatio) }}
</template>
</el-table-column>
<el-table-column
align="center"
:label="previewData?.engineeringPrincipalNames || '工程负责人人员'"
min-width="150"
>
<template #default="scope">
{{ formatNullablePercent(scope.row.engineeringPrincipalRatio) }}
</template>
</el-table-column>
</el-table>
<div class="mb-12px text-14px font-600">季度考核产值预览</div>
<el-table
:data="quarterPreviewRows"
:empty-text="selectedYear ? '暂无季度考核产值预览数据' : '请先选择年度'"
:row-class-name="getPreviewRowClassName"
border
class="project-lead-quarter-preview-table"
max-height="520"
>
<el-table-column align="center" fixed="left" label="序号" width="70">
<template #default="scope">
{{ scope.row.subtotalRow ? '' : scope.row.serialNo }}
</template>
</el-table-column>
<el-table-column align="center" fixed="left" label="项目名称" min-width="180">
<template #default="scope">
{{ scope.row.subtotalRow ? '' : previewData?.projectName || currentProject?.projectName }}
</template>
</el-table-column>
<el-table-column align="center" label="产值类型" min-width="150" prop="outputType" />
<el-table-column
align="center"
label="设计内容"
min-width="180"
prop="designContent"
show-overflow-tooltip
/>
<el-table-column
align="center"
:label="previewData?.projectManagerNames || '项目经理人员'"
>
<el-table-column align="center" label="一季度" min-width="110">
<template #default="scope">{{ formatNullableAmount(scope.row.projectManagerQuarterOneAmountWan) }}</template>
</el-table-column>
<el-table-column align="center" label="二季度" min-width="110">
<template #default="scope">{{ formatNullableAmount(scope.row.projectManagerQuarterTwoAmountWan) }}</template>
</el-table-column>
<el-table-column align="center" label="三季度" min-width="110">
<template #default="scope">{{ formatNullableAmount(scope.row.projectManagerQuarterThreeAmountWan) }}</template>
</el-table-column>
<el-table-column align="center" label="四季度" min-width="110">
<template #default="scope">{{ formatNullableAmount(scope.row.projectManagerQuarterFourAmountWan) }}</template>
</el-table-column>
<el-table-column align="center" label="本年度小计" min-width="120">
<template #default="scope">{{ formatNullableAmount(scope.row.projectManagerYearTotalAmountWan) }}</template>
</el-table-column>
</el-table-column>
<el-table-column
align="center"
:label="previewData?.engineeringPrincipalNames || '工程负责人人员'"
>
<el-table-column align="center" label="一季度" min-width="110">
<template #default="scope">{{ formatNullableAmount(scope.row.engineeringPrincipalQuarterOneAmountWan) }}</template>
</el-table-column>
<el-table-column align="center" label="二季度" min-width="110">
<template #default="scope">{{ formatNullableAmount(scope.row.engineeringPrincipalQuarterTwoAmountWan) }}</template>
</el-table-column>
<el-table-column align="center" label="三季度" min-width="110">
<template #default="scope">{{ formatNullableAmount(scope.row.engineeringPrincipalQuarterThreeAmountWan) }}</template>
</el-table-column>
<el-table-column align="center" label="四季度" min-width="110">
<template #default="scope">{{ formatNullableAmount(scope.row.engineeringPrincipalQuarterFourAmountWan) }}</template>
</el-table-column>
<el-table-column align="center" label="本年度小计" min-width="120">
<template #default="scope">{{ formatNullableAmount(scope.row.engineeringPrincipalYearTotalAmountWan) }}</template>
</el-table-column>
</el-table-column>
</el-table>
</template>
<el-empty v-else-if="!previewLoading" description="请选择项目后查看工程负责人年度表预览" />
</div>
</ContentWrap>
</template>
<script lang="ts" setup>
import * as ProjectApi from '@/api/tjt/project'
import * as ReportApi from '@/api/tjt/report'
import download from '@/utils/download'
import { formatAmountText, formatPercentText } from '@/views/tjt/shared/planning'
defineOptions({ name: 'TjtReportProjectLeadQuarter' })
const message = useMessage()
const currentYear = new Date().getFullYear()
const loading = ref(false)
const previewLoading = ref(false)
const exportLoading = ref(false)
const selectedYear = ref<number | undefined>(currentYear)
const total = ref(0)
const projectList = ref<ProjectApi.ProjectVO[]>([])
const currentProject = ref<ProjectApi.ProjectVO>()
const previewData = ref<ReportApi.ProjectLeadQuarterOutputPreviewRespVO>()
const queryFormRef = ref()
const projectTableRef = ref()
let previewRequestSeq = 0
const queryParams = reactive<ProjectApi.ProjectPageReqVO>({
pageNo: 1,
pageSize: 10,
projectName: undefined,
projectStartYear: undefined
})
const previewRows = computed(() => previewData.value?.rows || [])
const getProjectRowIndex = (index: number) =>
((queryParams.pageNo || 1) - 1) * (queryParams.pageSize || 10) + index + 1
const queryProjectStartYearValue = computed({
get: () => (queryParams.projectStartYear ? String(queryParams.projectStartYear) : undefined),
set: (value?: string) => {
queryParams.projectStartYear = value ? Number(value) : undefined
}
})
const selectedYearValue = computed({
get: () => (selectedYear.value ? String(selectedYear.value) : undefined),
set: (value?: string) => {
selectedYear.value = value ? Number(value) : undefined
}
})
const getProjectLeadText = (projectManagerName?: string, engineeringLeaderName?: string) =>
[projectManagerName, engineeringLeaderName].filter(Boolean).join(' / ') || '-'
const formatNullableAmount = (value?: number | string | null) => {
if (value === undefined || value === null || value === '') {
return ''
}
return formatAmountText(value)
}
const formatNullablePercent = (value?: number | string | null) => {
if (value === undefined || value === null || value === '') {
return ''
}
return formatPercentText(value)
}
const addAmount = (left?: number | string | null, right?: number | string | null) =>
Number((Number(left || 0) + Number(right || 0)).toFixed(2))
const buildSubtotalRow = (
rows: ReportApi.ProjectLeadQuarterOutputPreviewRow[]
): ReportApi.ProjectLeadQuarterOutputPreviewRow => {
const subtotal: ReportApi.ProjectLeadQuarterOutputPreviewRow = {
subtotalRow: true,
designContent: '总计'
}
rows.forEach((row) => {
subtotal.projectManagerQuarterOneAmountWan = addAmount(
subtotal.projectManagerQuarterOneAmountWan,
row.projectManagerQuarterOneAmountWan
)
subtotal.projectManagerQuarterTwoAmountWan = addAmount(
subtotal.projectManagerQuarterTwoAmountWan,
row.projectManagerQuarterTwoAmountWan
)
subtotal.projectManagerQuarterThreeAmountWan = addAmount(
subtotal.projectManagerQuarterThreeAmountWan,
row.projectManagerQuarterThreeAmountWan
)
subtotal.projectManagerQuarterFourAmountWan = addAmount(
subtotal.projectManagerQuarterFourAmountWan,
row.projectManagerQuarterFourAmountWan
)
subtotal.projectManagerYearTotalAmountWan = addAmount(
subtotal.projectManagerYearTotalAmountWan,
row.projectManagerYearTotalAmountWan
)
subtotal.engineeringPrincipalQuarterOneAmountWan = addAmount(
subtotal.engineeringPrincipalQuarterOneAmountWan,
row.engineeringPrincipalQuarterOneAmountWan
)
subtotal.engineeringPrincipalQuarterTwoAmountWan = addAmount(
subtotal.engineeringPrincipalQuarterTwoAmountWan,
row.engineeringPrincipalQuarterTwoAmountWan
)
subtotal.engineeringPrincipalQuarterThreeAmountWan = addAmount(
subtotal.engineeringPrincipalQuarterThreeAmountWan,
row.engineeringPrincipalQuarterThreeAmountWan
)
subtotal.engineeringPrincipalQuarterFourAmountWan = addAmount(
subtotal.engineeringPrincipalQuarterFourAmountWan,
row.engineeringPrincipalQuarterFourAmountWan
)
subtotal.engineeringPrincipalYearTotalAmountWan = addAmount(
subtotal.engineeringPrincipalYearTotalAmountWan,
row.engineeringPrincipalYearTotalAmountWan
)
})
return subtotal
}
const quarterPreviewRows = computed(() => {
const rows = previewRows.value
if (!rows.length) {
return []
}
return [...rows, buildSubtotalRow(rows)]
})
const getPreviewRowClassName = ({ row }: { row: ReportApi.ProjectLeadQuarterOutputPreviewRow }) =>
row?.subtotalRow ? 'report-total-row' : ''
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
previewData.value = undefined
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 loadProjectLeadQuarterPreview = async () => {
const projectId = currentProject.value?.id
const year = selectedYear.value
const requestSeq = ++previewRequestSeq
if (!projectId || !year) {
previewData.value = undefined
previewLoading.value = false
return
}
previewLoading.value = true
try {
const data = await ReportApi.getProjectLeadQuarterOutputPreview({
projectId,
year
})
if (requestSeq === previewRequestSeq) {
previewData.value = data
}
} catch {
if (requestSeq === previewRequestSeq) {
previewData.value = undefined
}
} finally {
if (requestSeq === previewRequestSeq) {
previewLoading.value = false
}
}
}
const handleQuery = () => {
queryParams.pageNo = 1
getProjectList()
}
const resetQuery = () => {
queryFormRef.value?.resetFields()
handleQuery()
}
const handleCurrentProjectChange = (row?: ProjectApi.ProjectVO) => {
currentProject.value = row || undefined
if (!row?.id) {
previewData.value = undefined
return
}
selectedYear.value = row.projectStartYear || currentYear
previewData.value = undefined
}
const handleExportProjectLeadQuarter = async () => {
if (!currentProject.value?.id) {
message.warning('请先选择项目')
return
}
if (!selectedYear.value) {
message.warning('请选择年度')
return
}
try {
await message.exportConfirm()
exportLoading.value = true
const data = await ReportApi.exportProjectLeadQuarterOutput({
projectId: currentProject.value.id,
year: selectedYear.value
})
download.excel(data, `${currentProject.value?.projectName || '项目'}_${selectedYear.value}_工程负责人年度表.xlsx`)
} finally {
exportLoading.value = false
}
}
watch(
[() => currentProject.value?.id, selectedYear],
() => {
loadProjectLeadQuarterPreview()
}
)
let activatedOnce = false
onMounted(() => {
getProjectList()
})
onActivated(() => {
// KeepAlive 首次挂载也会触发 onActivated跳过首轮避免重复请求项目列表。
if (!activatedOnce) {
activatedOnce = true
return
}
getProjectList()
})
</script>
<style scoped>
.project-lead-quarter-preview-table :deep(.report-total-row) {
background: var(--el-fill-color-light);
font-weight: 700;
}
</style>

View File

@@ -71,240 +71,201 @@
/>
</ContentWrap>
<SplitPane>
<template #left>
<ContentWrap>
<div class="mb-12px text-14px font-600">
{{ currentProject?.projectName || '合约规划列表' }}
</div>
<el-table
ref="planningTableRef"
v-loading="planningLoading"
:data="planningList"
highlight-current-row
@current-change="handleCurrentPlanningChange"
>
<el-table-column align="center" label="项目任务包" min-width="180" prop="planningContent" />
<el-table-column align="center" label="归属类型" min-width="110">
<template #default="scope">
{{ getOwnershipTypeLabel(scope.row.ownershipType) }}
</template>
</el-table-column>
<el-table-column align="center" label="开始年度" prop="planningStartYear" width="100" />
<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>
<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 v-loading="previewLoading" class="min-h-320px">
<template v-if="currentProject">
<div class="mb-16px flex flex-wrap items-center justify-between gap-12px">
<div>
<div class="text-16px font-600">{{ currentPlanning.planningContent }}</div>
<div class="text-16px font-600">专业间年度表预览</div>
<div class="mt-6px text-12px text-[var(--el-text-color-secondary)]">
{{ currentProject?.projectName || '-' }}
</div>
<div class="flex items-center gap-12px">
</div>
<div class="flex items-center gap-10px">
<el-date-picker
v-model="selectedYearValue"
class="!w-150px"
clearable
placeholder="计取年度"
type="year"
value-format="YYYY"
/>
<el-button :loading="previewLoading" @click="loadProjectQuarterPreview">
刷新
</el-button>
<el-button
v-hasPermi="['tjt:report-project-quarter:export']"
:loading="exportQuarterLoading"
:disabled="!selectedYear"
:loading="exportLoading"
plain
type="success"
@click="handleExportProjectQuarter"
>
<Icon class="mr-5px" icon="ep:download" />
导出专业间年度季度计取
</el-button>
<el-button
v-hasPermi="['tjt:report-project-quarter:export']"
:loading="exportLeadLoading"
plain
type="success"
@click="handleExportProjectLeadQuarter"
>
<Icon class="mr-5px" icon="ep:download" />
导出工程负责人计取表
导出专业间年度表
</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 :column="2" border class="mb-16px">
<el-descriptions-item label="项目名称">
{{ previewData?.projectName || currentProject?.projectName || '-' }}
</el-descriptions-item>
<el-descriptions-item label="考核产值(元)">
{{ formatAmountText(formData.assessmentOutputValue) }}
<el-descriptions-item label="预览年度">
{{ previewData?.year || selectedYear || '-' }}
</el-descriptions-item>
<el-descriptions-item label="金额单位">万元</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="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"
<el-table
:data="previewRows"
:empty-text="selectedYear ? '暂无专业间年度表预览数据' : '请先选择年度'"
:row-class-name="getPreviewRowClassName"
border
class="project-quarter-preview-table"
max-height="620"
>
{{ 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>
</div>
</el-col>
</el-row>
<el-table :data="annualDistributionRows" border>
<el-table-column align="center" label="分配年度" min-width="120" prop="distributionYear" />
<el-table-column align="center" fixed="left" label="序号" width="70">
<template #default="scope">
{{ scope.row.totalRow ? '' : scope.row.serialNo }}
</template>
</el-table-column>
<el-table-column align="center" fixed="left" label="项目名称" min-width="180">
<template #default="scope">
{{ scope.row.totalRow ? '合计' : previewData?.projectName || currentProject?.projectName }}
</template>
</el-table-column>
<el-table-column align="center" label="产值类型" min-width="150" prop="outputType" />
<el-table-column
v-for="quarter in QUARTER_OPTIONS"
:key="String(quarter.value)"
align="center"
:label="quarter.label"
min-width="150"
>
label="设计内容"
min-width="180"
prop="designContent"
show-overflow-tooltip
/>
<el-table-column align="center" label="本年度项目考核产值(万元)">
<el-table-column align="center" label="一季度" min-width="110">
<template #default="scope">{{ formatNullableAmount(scope.row.quarterOneAmountWan) }}</template>
</el-table-column>
<el-table-column align="center" label="二季度" min-width="110">
<template #default="scope">{{ formatNullableAmount(scope.row.quarterTwoAmountWan) }}</template>
</el-table-column>
<el-table-column align="center" label="三季度" min-width="110">
<template #default="scope">{{ formatNullableAmount(scope.row.quarterThreeAmountWan) }}</template>
</el-table-column>
<el-table-column align="center" label="四季度" min-width="110">
<template #default="scope">{{ formatNullableAmount(scope.row.quarterFourAmountWan) }}</template>
</el-table-column>
<el-table-column align="center" label="本年度小计" min-width="120">
<template #default="scope">{{ formatNullableAmount(scope.row.yearTotalAmountWan) }}</template>
</el-table-column>
</el-table-column>
<el-table-column align="center" label="项目经理/工程负责人年度/季度项目考核产值">
<el-table-column align="center" label="占比" min-width="100">
<template #default="scope">
{{ formatAmountText(scope.row.quarterAmounts[Number(quarter.value)]) }}
{{ scope.row.totalRow ? '' : formatNullablePercent(scope.row.projectLeadRatio) }}
</template>
</el-table-column>
<el-table-column align="center" label="年度合计(元)" min-width="160">
<el-table-column align="center" label="总考核产值" min-width="120">
<template #default="scope">{{ formatNullableAmount(scope.row.projectLeadAssessmentOutputWan) }}</template>
</el-table-column>
<el-table-column align="center" label="一季度" min-width="110">
<template #default="scope">{{ formatNullableAmount(scope.row.projectLeadQuarterOneAmountWan) }}</template>
</el-table-column>
<el-table-column align="center" label="二季度" min-width="110">
<template #default="scope">{{ formatNullableAmount(scope.row.projectLeadQuarterTwoAmountWan) }}</template>
</el-table-column>
<el-table-column align="center" label="三季度" min-width="110">
<template #default="scope">{{ formatNullableAmount(scope.row.projectLeadQuarterThreeAmountWan) }}</template>
</el-table-column>
<el-table-column align="center" label="四季度" min-width="110">
<template #default="scope">{{ formatNullableAmount(scope.row.projectLeadQuarterFourAmountWan) }}</template>
</el-table-column>
<el-table-column align="center" label="本年度总计" min-width="120">
<template #default="scope">{{ formatNullableAmount(scope.row.projectLeadYearTotalAmountWan) }}</template>
</el-table-column>
</el-table-column>
<el-table-column align="center" label="六大专业年度/季度项目考核产值合计">
<el-table-column align="center" label="占比" min-width="100">
<template #default="scope">
{{ formatAmountText(scope.row.yearTotal) }}
{{ scope.row.totalRow ? '' : formatNullablePercent(scope.row.officeRatio) }}
</template>
</el-table-column>
<el-table-column align="center" label="总考核产值" min-width="120">
<template #default="scope">{{ formatNullableAmount(scope.row.officeAssessmentOutputWan) }}</template>
</el-table-column>
<el-table-column align="center" label="一季度" min-width="110">
<template #default="scope">{{ formatNullableAmount(scope.row.officeQuarterOneAmountWan) }}</template>
</el-table-column>
<el-table-column align="center" label="二季度" min-width="110">
<template #default="scope">{{ formatNullableAmount(scope.row.officeQuarterTwoAmountWan) }}</template>
</el-table-column>
<el-table-column align="center" label="三季度" min-width="110">
<template #default="scope">{{ formatNullableAmount(scope.row.officeQuarterThreeAmountWan) }}</template>
</el-table-column>
<el-table-column align="center" label="四季度" min-width="110">
<template #default="scope">{{ formatNullableAmount(scope.row.officeQuarterFourAmountWan) }}</template>
</el-table-column>
<el-table-column align="center" label="本年度总计" min-width="120">
<template #default="scope">{{ formatNullableAmount(scope.row.officeYearTotalAmountWan) }}</template>
</el-table-column>
</el-table-column>
<el-table-column align="center" label="各专业考核产值占比">
<el-table-column
v-for="item in specialtyColumns"
:key="`${item.key}-ratio`"
align="center"
:label="item.label"
min-width="110"
>
<template #default="scope">
{{ scope.row.totalRow ? '' : formatNullablePercent(scope.row[item.ratioKey]) }}
</template>
</el-table-column>
</el-table-column>
<el-table-column align="center" label="各专业考核产值(万元)">
<el-table-column
v-for="item in specialtyColumns"
:key="`${item.key}-amount`"
align="center"
:label="item.label"
min-width="120"
>
<template #default="scope">
{{ formatNullableAmount(scope.row[item.amountKey]) }}
</template>
</el-table-column>
</el-table-column>
</el-table>
</template>
<el-empty v-else-if="!quarterLoading" description="请选择合约规划后查看导出预览" />
<el-empty v-else-if="!previewLoading" description="请选择项目后查看专业间年度表预览" />
</div>
</ContentWrap>
</template>
</SplitPane>
<Dialog v-model="exportDialogVisible" :title="exportDialogTitle" width="420">
<el-form label-width="88px">
<el-form-item label="年度">
<el-date-picker
v-model="exportYearValue"
class="!w-220px"
clearable
placeholder="请选择年度"
type="year"
value-format="YYYY"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="exportDialogVisible = false">取消</el-button>
<el-button :loading="currentExportLoading" type="primary" @click="submitExport">
确定导出
</el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import * as OutputSplitApi from '@/api/tjt/outputSplit'
import * as PlanningApi from '@/api/tjt/planning'
import * as PlanningQuarterApi from '@/api/tjt/planningQuarter'
import * as ProjectApi from '@/api/tjt/project'
import * as ReportApi from '@/api/tjt/report'
import download from '@/utils/download'
import SplitPane from '@/views/tjt/shared/SplitPane.vue'
import {
formatAmountText,
getOwnershipTypeLabel,
OUTPUT_SPLIT_SPECIALTY,
OUTPUT_SPLIT_SPECIALTY_OPTIONS,
QUARTER_OPTIONS,
toPercentValue
} from '@/views/tjt/shared/planning'
import { formatAmountText, formatPercentText } from '@/views/tjt/shared/planning'
defineOptions({ name: 'TjtReportProjectQuarter' })
type AnnualCategoryKey = (typeof OUTPUT_SPLIT_SPECIALTY)[keyof typeof OUTPUT_SPLIT_SPECIALTY]
interface QuarterYearRow {
distributionYear: number
quarters: PlanningQuarterApi.ProjectPlanningQuarterVO[]
}
type ExportDialogType = 'projectQuarter' | 'projectLeadQuarter'
const annualCategoryOptions: { label: string; value: AnnualCategoryKey }[] = [
{ label: '项目经理/工程负责人', value: OUTPUT_SPLIT_SPECIALTY.projectLead },
{ label: '建筑专业', value: OUTPUT_SPLIT_SPECIALTY.arch },
{ label: '装修专业', value: OUTPUT_SPLIT_SPECIALTY.decor },
{ label: '结构专业', value: OUTPUT_SPLIT_SPECIALTY.struct },
{ label: '水专业', value: OUTPUT_SPLIT_SPECIALTY.water },
{ label: '电气专业', value: OUTPUT_SPLIT_SPECIALTY.elec },
{ label: '暖通专业', value: OUTPUT_SPLIT_SPECIALTY.hvac },
{ label: '数字化设计专业', value: OUTPUT_SPLIT_SPECIALTY.digital }
]
const message = useMessage()
const currentYear = new Date().getFullYear()
const loading = ref(false)
const planningLoading = ref(false)
const quarterLoading = ref(false)
const exportQuarterLoading = ref(false)
const exportLeadLoading = ref(false)
const exportDialogVisible = ref(false)
const exportDialogType = ref<ExportDialogType>('projectQuarter')
const exportYear = ref<number>()
const previewLoading = ref(false)
const exportLoading = ref(false)
const selectedYear = ref<number | undefined>(currentYear)
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 formData = ref<OutputSplitApi.ProjectOutputSplitVO>()
const quarterRows = ref<QuarterYearRow[]>([])
const selectedAnnualCategory = ref<AnnualCategoryKey>(OUTPUT_SPLIT_SPECIALTY.projectLead)
const previewData = ref<ReportApi.ProjectQuarterOutputPreviewRespVO>()
const queryFormRef = ref()
const projectTableRef = ref()
const planningTableRef = ref()
let previewRequestSeq = 0
const queryParams = reactive<ProjectApi.ProjectPageReqVO>({
pageNo: 1,
@@ -313,8 +274,25 @@ const queryParams = reactive<ProjectApi.ProjectPageReqVO>({
projectStartYear: undefined
})
const specialtyColumns = [
{ key: 'arch', label: '建筑', ratioKey: 'archRatio', amountKey: 'archAssessmentOutputWan' },
{ key: 'decor', label: '精装', ratioKey: 'decorRatio', amountKey: 'decorAssessmentOutputWan' },
{ key: 'struct', label: '结构', ratioKey: 'structRatio', amountKey: 'structAssessmentOutputWan' },
{ key: 'water', label: '给排水', ratioKey: 'waterRatio', amountKey: 'waterAssessmentOutputWan' },
{ key: 'hvac', label: '暖通', ratioKey: 'hvacRatio', amountKey: 'hvacAssessmentOutputWan' },
{ key: 'elec', label: '电气', ratioKey: 'elecRatio', amountKey: 'elecAssessmentOutputWan' },
{
key: 'digital',
label: '数字化设计',
ratioKey: 'digitalRatio',
amountKey: 'digitalAssessmentOutputWan'
}
] as const
const previewRows = computed(() => previewData.value?.rows || [])
const getProjectRowIndex = (index: number) =>
(queryParams.pageNo - 1) * queryParams.pageSize + index + 1
((queryParams.pageNo || 1) - 1) * (queryParams.pageSize || 10) + index + 1
const queryProjectStartYearValue = computed({
get: () => (queryParams.projectStartYear ? String(queryParams.projectStartYear) : undefined),
@@ -323,185 +301,32 @@ const queryProjectStartYearValue = computed({
}
})
const exportDialogTitle = computed(() =>
exportDialogType.value === 'projectQuarter' ? '导出专业间年度季度计取表' : '导出工程负责人计取表'
)
const currentExportLoading = computed(() =>
exportDialogType.value === 'projectQuarter'
? exportQuarterLoading.value
: exportLeadLoading.value
)
const exportYearValue = computed({
get: () => (exportYear.value ? String(exportYear.value) : undefined),
const selectedYearValue = computed({
get: () => (selectedYear.value ? String(selectedYear.value) : undefined),
set: (value?: string) => {
exportYear.value = value ? Number(value) : undefined
selectedYear.value = value ? Number(value) : undefined
}
})
const toNumeric = (value?: number | string | null) => {
const numericValue = Number(value ?? 0)
return Number.isNaN(numericValue) ? 0 : numericValue
}
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 []
const formatNullableAmount = (value?: number | string | null) => {
if (value === undefined || value === null || value === '') {
return ''
}
return [
{
label: '项目经理/工程负责人',
percentText: formatRatioText(model.projectLeadRatio),
amount: model.projectLeadAmount
},
{
label: '专业所',
percentText: formatRatioText(model.officeRatio),
amount: model.officeAmount
}
]
return formatAmountText(value)
}
const buildSpecialtyRows = (model?: OutputSplitApi.ProjectOutputSplitVO) => {
if (!model) {
return []
const formatNullablePercent = (value?: number | string | null) => {
if (value === undefined || value === null || value === '') {
return ''
}
const amountMap: Record<string, number | undefined> = {
arch: model.archAmount,
decor: model.decorAmount,
struct: model.structAmount,
water: model.waterAmount,
elec: model.elecAmount,
hvac: model.hvacAmount,
digital: model.digitalAmount
}
return OUTPUT_SPLIT_SPECIALTY_OPTIONS.map((item) => ({
label: item.label,
percentText: formatRatioText(getRatioValue(model, `${item.value}Ratio`)),
amount: amountMap[item.value]
}))
return formatPercentText(value)
}
const projectResultRows = computed(() => buildProjectRows(formData.value))
const specialtyResultRows = computed(() => buildSpecialtyRows(formData.value))
const annualCategoryMeta = computed(() => {
const model = formData.value
const option = annualCategoryOptions.find((item) => item.value === selectedAnnualCategory.value)
if (!model || !option) {
return { ratio: 0 }
}
if (selectedAnnualCategory.value === OUTPUT_SPLIT_SPECIALTY.projectLead) {
return {
ratio: Number(toNumeric(model.projectLeadRatio).toFixed(4))
}
}
const specialtyRatio = toNumeric(getRatioValue(model, `${selectedAnnualCategory.value}Ratio`))
return {
ratio: Number((toNumeric(model.officeRatio) * specialtyRatio).toFixed(4))
}
})
const annualDistributionRows = computed(() =>
quarterRows.value.map((row) => {
const quarterAmounts: Record<number, number> = {}
let yearTotal = 0
QUARTER_OPTIONS.forEach((quarter) => {
const quarterNo = Number(quarter.value)
const quarterAmount = multiplyAmount(
row.quarters.find((item) => item.quarterNo === quarterNo)?.distributionAmount,
annualCategoryMeta.value.ratio
)
quarterAmounts[quarterNo] = quarterAmount
yearTotal += quarterAmount
})
return {
distributionYear: row.distributionYear,
quarterAmounts,
yearTotal: Number(yearTotal.toFixed(2))
}
})
)
const annualSummaryCards = computed(() => [
{
label: '总分配',
amount: multiplyAmount(
currentPlanning.value?.assessmentOutputValue,
currentPlanning.value?.totalDistributionAmount,
annualCategoryMeta.value.ratio
)
},
{
label: '已分配',
amount: multiplyAmount(
currentPlanning.value?.assessmentOutputValue,
currentPlanning.value?.allocatedAmount,
annualCategoryMeta.value.ratio
)
},
{
label: '待分配',
amount: multiplyAmount(
currentPlanning.value?.assessmentOutputValue,
currentPlanning.value?.pendingAmount,
annualCategoryMeta.value.ratio
)
}
])
const buildQuarterRows = (
planning: PlanningApi.ProjectPlanningVO,
quarters: PlanningQuarterApi.ProjectPlanningQuarterVO[]
) => {
const yearSet = new Set<number>()
if (planning.planningStartYear) {
yearSet.add(planning.planningStartYear)
}
quarters.forEach((item) => yearSet.add(item.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) =>
item.distributionYear === distributionYear && Number(item.quarterNo) === quarterNo
)
return (
match || {
planningId: planning.id!,
distributionYear,
quarterNo,
distributionRatio: undefined,
distributionAmount: 0
}
)
})
}))
}
const getPreviewRowClassName = ({ row }: { row: ReportApi.ProjectQuarterOutputPreviewRow }) =>
row?.totalRow ? 'report-total-row' : row?.placeholderRow ? 'report-placeholder-row' : ''
const getProjectList = async () => {
loading.value = true
@@ -511,10 +336,7 @@ const getProjectList = async () => {
total.value = data.total
if (!projectList.value.length) {
currentProject.value = undefined
planningList.value = []
currentPlanning.value = undefined
formData.value = undefined
quarterRows.value = []
previewData.value = undefined
return
}
const targetProjectId = currentProject.value?.id || projectList.value[0].id
@@ -527,49 +349,33 @@ const getProjectList = async () => {
}
}
const getPlanningList = async () => {
if (!currentProject.value?.id) {
planningList.value = []
currentPlanning.value = undefined
formData.value = undefined
quarterRows.value = []
const loadProjectQuarterPreview = async () => {
const projectId = currentProject.value?.id
const year = selectedYear.value
const requestSeq = ++previewRequestSeq
if (!projectId || !year) {
previewData.value = undefined
previewLoading.value = false
return
}
planningLoading.value = true
previewLoading.value = true
try {
const list = await PlanningApi.getProjectPlanningListByProjectId(currentProject.value.id)
planningList.value = list
if (!planningList.value.length) {
currentPlanning.value = undefined
formData.value = undefined
quarterRows.value = []
return
const data = await ReportApi.getProjectQuarterOutputPreview({
projectId,
year
})
if (requestSeq === previewRequestSeq) {
previewData.value = data
}
} catch {
if (requestSeq === previewRequestSeq) {
previewData.value = undefined
}
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
if (requestSeq === previewRequestSeq) {
previewLoading.value = false
}
}
const loadPlanningRelatedData = async (planning: PlanningApi.ProjectPlanningVO) => {
if (!planning.id) {
return
}
quarterLoading.value = true
try {
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
}
}
const handleQuery = () => {
@@ -582,100 +388,44 @@ const resetQuery = () => {
handleQuery()
}
const handleCurrentProjectChange = async (row?: ProjectApi.ProjectVO) => {
const handleCurrentProjectChange = (row?: ProjectApi.ProjectVO) => {
currentProject.value = row || undefined
await getPlanningList()
}
const handleCurrentPlanningChange = async (row?: PlanningApi.ProjectPlanningVO) => {
currentPlanning.value = row || undefined
if (!row?.id) {
formData.value = undefined
quarterRows.value = []
previewData.value = undefined
return
}
await loadPlanningRelatedData(row)
selectedYear.value = row.projectStartYear || currentYear
previewData.value = undefined
}
const handleExportProjectQuarter = async () => {
if (!currentPlanning.value?.id) {
message.warning('请先选择合约规划')
if (!currentProject.value?.id) {
message.warning('请先选择项目')
return
}
exportDialogType.value = 'projectQuarter'
exportYear.value = currentPlanning.value.planningStartYear || currentYear
exportDialogVisible.value = true
}
const handleExportProjectLeadQuarter = async () => {
if (!currentPlanning.value?.id) {
message.warning('请先选择合约规划')
return
}
exportDialogType.value = 'projectLeadQuarter'
exportYear.value = currentPlanning.value.planningStartYear || currentYear
exportDialogVisible.value = true
}
const submitExport = async () => {
if (exportDialogType.value === 'projectQuarter') {
await submitProjectQuarterExport()
return
}
await submitProjectLeadQuarterExport()
}
const submitProjectQuarterExport = async () => {
if (!currentPlanning.value?.id) {
message.warning('请先选择合约规划')
return
}
if (!exportYear.value) {
if (!selectedYear.value) {
message.warning('请选择年度')
return
}
try {
await message.exportConfirm()
exportQuarterLoading.value = true
exportLoading.value = true
const data = await ReportApi.exportProjectQuarterOutput({
planningId: currentPlanning.value.id,
year: exportYear.value
projectId: currentProject.value.id,
year: selectedYear.value
})
download.excel(
data,
`${currentProject.value?.projectName || '项目'}_${exportYear.value}_专业间年度季度计取表.xlsx`
)
exportDialogVisible.value = false
download.excel(data, `${currentProject.value?.projectName || '项目'}_${selectedYear.value}_专业间年度表.xlsx`)
} finally {
exportQuarterLoading.value = false
exportLoading.value = false
}
}
const submitProjectLeadQuarterExport = async () => {
if (!currentPlanning.value?.id) {
message.warning('请先选择合约规划')
return
watch(
[() => currentProject.value?.id, selectedYear],
() => {
loadProjectQuarterPreview()
}
if (!exportYear.value) {
message.warning('请选择年度')
return
}
try {
await message.exportConfirm()
exportLeadLoading.value = true
const data = await ReportApi.exportProjectLeadQuarterOutput({
planningId: currentPlanning.value.id,
year: exportYear.value
})
download.excel(
data,
`${currentProject.value?.projectName || '项目'}_${exportYear.value}_工程负责人年度季度计取表.xlsx`
)
exportDialogVisible.value = false
} finally {
exportLeadLoading.value = false
}
}
let activatedOnce = false
@@ -684,7 +434,7 @@ onMounted(() => {
})
onActivated(() => {
// KeepAlive 首次挂载也会触发 onActivated跳过首轮避免重复请求项目与合约规划列表。
// KeepAlive 首次挂载也会触发 onActivated跳过首轮避免重复请求项目列表。
if (!activatedOnce) {
activatedOnce = true
return
@@ -692,3 +442,14 @@ onActivated(() => {
getProjectList()
})
</script>
<style scoped>
.project-quarter-preview-table :deep(.report-total-row) {
background: var(--el-fill-color-light);
font-weight: 700;
}
.project-quarter-preview-table :deep(.report-placeholder-row) {
color: var(--el-text-color-secondary);
}
</style>

View File

@@ -0,0 +1,156 @@
<template>
<div class="planning-ownership-summary">
<div class="summary-header">
<span class="summary-title">{{ title }}</span>
<span class="summary-subtitle">{{ amountLabel }}</span>
</div>
<div class="summary-grid">
<div v-for="item in summaryRows" :key="item.key" class="summary-item">
<span class="summary-label">{{ item.label }}合计</span>
<span class="summary-value">{{ formatAmountText(item.amount) }}</span>
</div>
<div class="summary-item summary-total">
<span class="summary-label">总合计</span>
<span class="summary-value">{{ formatAmountText(totalAmount) }}</span>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import type { ProjectPlanningVO } from '@/api/tjt/planning'
import {
getOwnershipTypeLabel,
OWNERSHIP_TYPE_OPTIONS,
formatAmountText
} from '@/views/tjt/shared/planning'
type AmountField = keyof Pick<
ProjectPlanningVO,
'planningAmount' | 'assessmentOutputValue' | 'projectBudgetOutputValue'
>
const props = withDefaults(
defineProps<{
planningList?: ProjectPlanningVO[]
amountField: AmountField
title?: string
amountLabel?: string
}>(),
{
planningList: () => [],
title: '归属类型产值合计',
amountLabel: '金额(元)'
}
)
const toAmountNumber = (value?: number | string | null) => {
const numericValue = Number(value ?? 0)
return Number.isNaN(numericValue) ? 0 : numericValue
}
const roundAmount = (value: number) => Math.round((Number(value) || 0) * 100) / 100
const groupedAmountMap = computed(() => {
const map = new Map<string, number>()
props.planningList.forEach((planning) => {
const ownershipType = planning.ownershipType || ''
const amount = toAmountNumber(planning[props.amountField] as number | string | null | undefined)
map.set(ownershipType, roundAmount((map.get(ownershipType) || 0) + amount))
})
return map
})
const summaryRows = computed(() => {
const knownRows = OWNERSHIP_TYPE_OPTIONS.map((option) => ({
key: String(option.value),
label: option.label,
amount: groupedAmountMap.value.get(String(option.value)) || 0
}))
const knownKeys = new Set(knownRows.map((item) => item.key))
const unknownRows = Array.from(groupedAmountMap.value.entries())
.filter(([key]) => key && !knownKeys.has(key))
.map(([key, amount]) => ({
key,
label: getOwnershipTypeLabel(key),
amount
}))
return [...knownRows, ...unknownRows]
})
const totalAmount = computed(() =>
roundAmount(summaryRows.value.reduce((sum, item) => sum + item.amount, 0))
)
</script>
<style scoped>
.planning-ownership-summary {
margin-top: 12px;
border: 1px solid var(--el-border-color-lighter);
border-radius: 10px;
background:
linear-gradient(135deg, rgba(64, 158, 255, 0.08), transparent 42%),
var(--el-fill-color-blank);
padding: 12px;
}
.summary-header {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 12px;
margin-bottom: 10px;
}
.summary-title {
color: var(--el-text-color-primary);
font-size: 14px;
font-weight: 600;
}
.summary-subtitle {
color: var(--el-text-color-secondary);
font-size: 12px;
}
.summary-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 8px;
}
.summary-item {
min-width: 0;
border-radius: 8px;
background: rgba(255, 255, 255, 0.72);
padding: 9px 10px;
}
.summary-label {
display: block;
overflow: hidden;
color: var(--el-text-color-secondary);
font-size: 12px;
text-overflow: ellipsis;
white-space: nowrap;
}
.summary-value {
display: block;
margin-top: 4px;
color: var(--el-text-color-primary);
font-size: 14px;
font-weight: 600;
}
.summary-total {
background: var(--el-color-primary-light-9);
}
.summary-total .summary-value {
color: var(--el-color-primary);
}
</style>

View File

@@ -100,6 +100,12 @@
</el-table-column>
<el-table-column align="center" label="排序" prop="sortNo" width="80" />
</el-table>
<PlanningOwnershipSummary
:planning-list="planningList"
amount-field="assessmentOutputValue"
amount-label="考核产值()"
title="归属类型考核产值合计"
/>
</ContentWrap>
</template>
@@ -354,6 +360,7 @@ import * as EmployeeApi from '@/api/tjt/employee'
import * as ProjectApi from '@/api/tjt/project'
import * as PlanningApi from '@/api/tjt/planning'
import * as SpecialtyRoleSplitApi from '@/api/tjt/specialtyRoleSplit'
import PlanningOwnershipSummary from '@/views/tjt/shared/PlanningOwnershipSummary.vue'
import SplitPane from '@/views/tjt/shared/SplitPane.vue'
import {
formatAmountText,