From 083c8f18868ce75f93ad253425e0c1828e25cefe Mon Sep 17 00:00:00 2001 From: wintsa <770775984@qq.com> Date: Wed, 18 Mar 2026 10:17:41 +0800 Subject: [PATCH] fix --- src/components/ht/zxFw.vue | 115 ++++++++++++------ .../pricing/InvestmentScalePricingPane.vue | 2 +- .../pricing/LandScalePricingPane.vue | 2 +- .../pricing/WorkloadPricingPane.vue | 2 +- src/components/shared/HourlyFeeGrid.vue | 2 +- src/components/shared/HtFeeGrid.vue | 2 +- src/components/shared/WorkContentGrid.vue | 8 +- src/components/shared/XmFactorGrid.vue | 2 +- src/layout/typeLine.vue | 8 +- src/style.css | 3 +- 10 files changed, 95 insertions(+), 51 deletions(-) diff --git a/src/components/ht/zxFw.vue b/src/components/ht/zxFw.vue index 34e32b3..1a0d7c3 100644 --- a/src/components/ht/zxFw.vue +++ b/src/components/ht/zxFw.vue @@ -5,9 +5,9 @@ import { AgGridVue } from 'ag-grid-vue3' import type { ColDef, GridOptions, ICellRendererParams } from 'ag-grid-community' import { myTheme, gridOptions, agGridWrapClass, agGridStyle } from '@/lib/diyAgGridOptions' import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale' -import { addNumbers } from '@/lib/decimal' +import { addNumbers, roundTo } from '@/lib/decimal' import { parseNumberOrNull } from '@/lib/number' -import { formatThousandsFlexible } from '@/lib/numberFormat' +import { formatThousands, formatThousandsFlexible } from '@/lib/numberFormat' import { ensurePricingMethodDetailRowsForServices, getPricingMethodTotalsForService, @@ -51,6 +51,7 @@ interface DetailRow { workload: number | null hourly: number | null subtotal?: number | null + finalFee?: number | null actions?: unknown } @@ -154,6 +155,7 @@ const detailRows = computed(() => workload: typeof row.workload === 'number' ? row.workload : null, hourly: typeof row.hourly === 'number' ? row.hourly : null, subtotal: typeof row.subtotal === 'number' ? row.subtotal : null, + finalFee: typeof (row as any).finalFee === 'number' ? (row as any).finalFee : null, actions: row.actions })) ) @@ -174,6 +176,7 @@ const getCurrentContractState = (): ZxFwViewState => { workload: typeof row.workload === 'number' ? row.workload : null, hourly: typeof row.hourly === 'number' ? row.hourly : null, subtotal: typeof row.subtotal === 'number' ? row.subtotal : null, + finalFee: typeof (row as any).finalFee === 'number' ? (row as any).finalFee : null, actions: row.actions })) } @@ -457,36 +460,21 @@ const clearRowValues = async (row: DetailRow) => { }) const sanitizedTotals = sanitizePricingTotalsByService(row.id, totals) const currentState = getCurrentContractState() - const clearedRows = currentState.detailRows.map(item => - item.id !== row.id - ? item - : { - ...item, - investScale: sanitizedTotals.investScale, - landScale: sanitizedTotals.landScale, - workload: sanitizedTotals.workload, - hourly: sanitizedTotals.hourly - } - ) - const nextInvestScale = getMethodTotalFromRows(clearedRows, 'investScale') - const nextLandScale = getMethodTotalFromRows(clearedRows, 'landScale') - const nextWorkload = getMethodTotalFromRows(clearedRows, 'workload') - const nextHourly = getMethodTotalFromRows(clearedRows, 'hourly') - const nextRows = clearedRows.map(item => - isFixedRow(item) - ? { - ...item, - investScale: nextInvestScale, - landScale: nextLandScale, - workload: nextWorkload, - hourly: nextHourly, - subtotal: sumNullableNumbers([nextInvestScale, nextLandScale, nextWorkload, nextHourly]) - } - : item - ) + const clearedRows = currentState.detailRows.map(item => { + if (item.id !== row.id) return item + const newSubtotal = sumNullableNumbers([sanitizedTotals.investScale, sanitizedTotals.landScale, sanitizedTotals.workload, sanitizedTotals.hourly]) + return { + ...item, + investScale: sanitizedTotals.investScale, + landScale: sanitizedTotals.landScale, + workload: sanitizedTotals.workload, + hourly: sanitizedTotals.hourly, + finalFee: newSubtotal != null ? roundTo(newSubtotal, 2) : null + } + }) await setCurrentContractState({ ...currentState, - detailRows: nextRows + detailRows: applyFixedRowTotals(clearedRows) }) } @@ -728,6 +716,35 @@ const columnDefs: ColDef[] = [ }, valueFormatter: params => (params.value == null ? '' : formatThousandsFlexible(params.value, 3)) }, + { + headerName: '确认金额', + field: 'finalFee', + headerClass: 'ag-right-aligned-header', + flex: 3, + minWidth: 140, + cellClass: 'ag-right-aligned-cell', + editable: params => !isFixedRow(params.data), + valueGetter: params => { + if (!params.data) return null + if (isFixedRow(params.data)) { + return sumNullableNumbers( + detailRows.value.filter(r => !isFixedRow(r)).map(r => r.finalFee) + ) + } + if (params.data.finalFee != null) return params.data.finalFee + return sumNullableNumbers([ + params.data.investScale, + params.data.landScale, + params.data.workload, + params.data.hourly + ]) + }, + valueParser: params => { + const parsed = parseNumberOrNull(params.newValue, { precision: 2 }) + return parsed != null ? roundTo(parsed, 2) : null + }, + valueFormatter: params => (params.value == null ? '' : formatThousands(params.value, 2)) + }, { headerName: '操作', field: 'actions', @@ -791,7 +808,16 @@ const applyFixedRowTotals = (rows: DetailRow[]) => { const nextLandScale = getMethodTotalFromRows(rows, 'landScale') const nextWorkload = getMethodTotalFromRows(rows, 'workload') const nextHourly = getMethodTotalFromRows(rows, 'hourly') - return rows.map(row => + // 先更新普通行:finalFee 跟随小计(若未手动编辑过或值为 null 则同步) + const updatedRows = rows.map(row => { + if (isFixedRow(row)) return row + const rowSubtotal = sumNullableNumbers([row.investScale, row.landScale, row.workload, row.hourly]) + const nextFinalFee = row.finalFee != null ? row.finalFee : (rowSubtotal != null ? roundTo(rowSubtotal, 2) : null) + return { ...row, finalFee: nextFinalFee } + }) + // 再计算固定行汇总 + const totalFinalFee = sumNullableNumbers(updatedRows.filter(r => !isFixedRow(r)).map(r => r.finalFee)) + return updatedRows.map(row => isFixedRow(row) ? { ...row, @@ -799,7 +825,8 @@ const applyFixedRowTotals = (rows: DetailRow[]) => { landScale: nextLandScale, workload: nextWorkload, hourly: nextHourly, - subtotal: sumNullableNumbers([nextInvestScale, nextLandScale, nextWorkload, nextHourly]) + subtotal: sumNullableNumbers([nextInvestScale, nextLandScale, nextWorkload, nextHourly]), + finalFee: totalFinalFee != null ? roundTo(totalFinalFee, 2) : null } : row ) @@ -856,12 +883,14 @@ const fillPricingTotalsForServiceIds = async (serviceIds: string[]) => { const totalsRaw = totalsByServiceId.get(String(row.id)) const totals = totalsRaw ? sanitizePricingTotalsByService(String(row.id), totalsRaw) : null if (!totals) return row + const newSubtotal = sumNullableNumbers([totals.investScale, totals.landScale, totals.workload, totals.hourly]) return { ...row, investScale: totals.investScale, landScale: totals.landScale, workload: totals.workload, - hourly: totals.hourly + hourly: totals.hourly, + finalFee: newSubtotal != null ? roundTo(newSubtotal, 2) : null } }) @@ -899,7 +928,8 @@ const applySelection = async (codes: string[]) => { investScale: nextValues.investScale, landScale: nextValues.landScale, workload: nextValues.workload, - hourly: nextValues.hourly + hourly: nextValues.hourly, + finalFee: typeof old?.finalFee === 'number' ? old.finalFee : null } }) .filter((row): row is DetailRow => row !== null) @@ -918,6 +948,7 @@ const applySelection = async (codes: string[]) => { workload: typeof fixedOld?.workload === 'number' ? fixedOld.workload : null, hourly: typeof fixedOld?.hourly === 'number' ? fixedOld.hourly : null, subtotal: null, + finalFee: null, actions: null } @@ -1092,6 +1123,7 @@ const initializeContractState = async () => { workload: null, hourly: null, subtotal: null, + finalFee: null, actions: null }]) }) @@ -1117,7 +1149,20 @@ watch(serviceIdSignature, () => { } }) -const handleCellValueChanged = () => {} +const handleCellValueChanged = async (event: any) => { + if (event.colDef?.field !== 'finalFee') return + const row = event.data as DetailRow | undefined + if (!row || isFixedRow(row)) return + const newValue = event.newValue != null ? roundTo(Number(event.newValue), 2) : null + const currentState = getCurrentContractState() + const nextRows = currentState.detailRows.map(item => + item.id === row.id ? { ...item, finalFee: newValue } : item + ) + await setCurrentContractState({ + ...currentState, + detailRows: applyFixedRowTotals(nextRows) + }) +} onMounted(async () => { await loadProjectIndustry() diff --git a/src/components/pricing/InvestmentScalePricingPane.vue b/src/components/pricing/InvestmentScalePricingPane.vue index 952bdcb..2be2187 100644 --- a/src/components/pricing/InvestmentScalePricingPane.vue +++ b/src/components/pricing/InvestmentScalePricingPane.vue @@ -918,7 +918,7 @@ const columnDefs: Array | ColGroupDef> = [ if (!params.node?.group && !params.node?.rowPinned && !params.value) return '点击输入' return params.value || '' }, - cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line remark-wrap-cell' : ''), + cellClass: params => (!params.node?.group && !params.node?.rowPinned ? ' remark-wrap-cell' : ''), cellClassRules: { 'editable-cell-empty': params => !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '') diff --git a/src/components/pricing/LandScalePricingPane.vue b/src/components/pricing/LandScalePricingPane.vue index 6bd0acf..6e10601 100644 --- a/src/components/pricing/LandScalePricingPane.vue +++ b/src/components/pricing/LandScalePricingPane.vue @@ -772,7 +772,7 @@ const columnDefs: Array | ColGroupDef> = [ if (!params.node?.group && !params.node?.rowPinned && !params.value) return '点击输入' return params.value || '' }, - cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line remark-wrap-cell' : ''), + cellClass: params => (!params.node?.group && !params.node?.rowPinned ? ' remark-wrap-cell' : ''), cellClassRules: { 'editable-cell-empty': params => !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '') diff --git a/src/components/pricing/WorkloadPricingPane.vue b/src/components/pricing/WorkloadPricingPane.vue index 217f5f8..7ea61ef 100644 --- a/src/components/pricing/WorkloadPricingPane.vue +++ b/src/components/pricing/WorkloadPricingPane.vue @@ -386,7 +386,7 @@ const columnDefs: ColDef[] = [ return params.value || '' }, - cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line remark-wrap-cell' : ''), + cellClass: params => (!params.node?.group && !params.node?.rowPinned ? ' remark-wrap-cell' : ''), cellClassRules: { 'editable-cell-empty': params => !params.node?.group && diff --git a/src/components/shared/HourlyFeeGrid.vue b/src/components/shared/HourlyFeeGrid.vue index 01f7fb3..00dfe79 100644 --- a/src/components/shared/HourlyFeeGrid.vue +++ b/src/components/shared/HourlyFeeGrid.vue @@ -413,7 +413,7 @@ const columnDefs: (ColDef | ColGroupDef)[] = [ if (!params.node?.group && !params.node?.rowPinned && !params.value) return '点击输入' return params.value || '' }, - cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line remark-wrap-cell' : ''), + cellClass: params => (!params.node?.group && !params.node?.rowPinned ? ' remark-wrap-cell' : ''), cellClassRules: { 'editable-cell-empty': params => !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '') diff --git a/src/components/shared/HtFeeGrid.vue b/src/components/shared/HtFeeGrid.vue index b30420b..cbe4ef3 100644 --- a/src/components/shared/HtFeeGrid.vue +++ b/src/components/shared/HtFeeGrid.vue @@ -351,7 +351,7 @@ const columnDefs: ColDef[] = [ autoHeight: true, cellStyle: { whiteSpace: 'normal', lineHeight: '1.4' }, valueFormatter: formatEditableText, - cellClass: 'editable-cell-line remark-wrap-cell', + cellClass: ' remark-wrap-cell', cellClassRules: { 'editable-cell-empty': params => params.value == null || params.value === '' } diff --git a/src/components/shared/WorkContentGrid.vue b/src/components/shared/WorkContentGrid.vue index c795a2a..09525ae 100644 --- a/src/components/shared/WorkContentGrid.vue +++ b/src/components/shared/WorkContentGrid.vue @@ -205,13 +205,11 @@ const columnDefs: ColDef[] = [ minWidth: 320, flex: 2, editable: params => Boolean(params.data?.custom), - cellClass: params => (params.data?.custom ? 'editable-cell-line' : ''), - cellClassRules: { - 'editable-cell-empty': params => Boolean(params.data?.custom) && (params.value == null || params.value === '') - }, + valueParser: params => String(params.newValue || '').trim(), wrapText: true, autoHeight: true, + cellStyle: { whiteSpace: 'normal', lineHeight: '1.5' }, cellRenderer: contentCellRenderer }, @@ -233,7 +231,7 @@ const columnDefs: ColDef[] = [ wrapText: true, autoHeight: true, cellStyle: { whiteSpace: 'normal', lineHeight: '1.4' }, - cellClass: 'editable-cell-line remark-wrap-cell', + cellClass: 'remark-wrap-cell', cellClassRules: { 'editable-cell-empty': params => params.value == null || params.value === '' }, diff --git a/src/components/shared/XmFactorGrid.vue b/src/components/shared/XmFactorGrid.vue index 2a63ee7..9756e0e 100644 --- a/src/components/shared/XmFactorGrid.vue +++ b/src/components/shared/XmFactorGrid.vue @@ -201,7 +201,7 @@ const columnDefs: ColDef[] = [ cellStyle: { whiteSpace: 'normal', lineHeight: '1.4' }, editable: true, valueFormatter: params => params.value || '点击输入', - cellClass: 'editable-cell-line remark-wrap-cell', + cellClass: ' remark-wrap-cell', cellClassRules: { 'editable-cell-empty': params => params.value == null || params.value === '' } diff --git a/src/layout/typeLine.vue b/src/layout/typeLine.vue index 98b8f26..85cc3d2 100644 --- a/src/layout/typeLine.vue +++ b/src/layout/typeLine.vue @@ -198,8 +198,8 @@ useMotionValueEvent(