diff --git a/release/JGJS2026-dist.zip b/release/JGJS2026-dist.zip new file mode 100644 index 0000000..444c6a1 Binary files /dev/null and b/release/JGJS2026-dist.zip differ diff --git a/src/features/ht/components/Ht.vue b/src/features/ht/components/Ht.vue index 4d7512d..ffbe398 100644 --- a/src/features/ht/components/Ht.vue +++ b/src/features/ht/components/Ht.vue @@ -54,6 +54,7 @@ import { SERVICE_KEY_PREFIX, type ContractSegmentPackage } from '@/lib/contractSegment' +import { buildDefaultProjectScaleState } from '@/lib/projectWorkspace' import { getIndustryDisplayName, industryTypeList } from '@/sql' import { roundTo, sumNullableNumbers, toFiniteNumber } from '@/lib/decimal' import { formatThousands } from '@/lib/numberFormat' @@ -517,15 +518,21 @@ const saveContracts = async () => { } const initializeContractScaleData = async (contractId: string) => { - const source = await kvStore.getItem(PROJECT_SCALE_KEY) - const payload: XmScaleState = { - detailRows: Array.isArray(source?.detailRows) ? cloneJson(source.detailRows) : [], - roughCalcEnabled: Boolean(source?.roughCalcEnabled), - totalAmount: - typeof source?.totalAmount === 'number' && Number.isFinite(source.totalAmount) - ? source.totalAmount - : null - } + const [source, projectInfo] = await Promise.all([ + kvStore.getItem(PROJECT_SCALE_KEY), + kvStore.getItem(PROJECT_INFO_KEY) + ]) + const industry = typeof projectInfo?.projectIndustry === 'string' ? projectInfo.projectIndustry.trim() : '' + const payload: XmScaleState = industry + ? buildDefaultProjectScaleState(industry, source) + : { + detailRows: Array.isArray(source?.detailRows) ? cloneJson(source.detailRows) : [], + roughCalcEnabled: Boolean(source?.roughCalcEnabled), + totalAmount: + typeof source?.totalAmount === 'number' && Number.isFinite(source.totalAmount) + ? source.totalAmount + : null + } await kvStore.setItem(`${CONTRACT_KEY_PREFIX}${contractId}`, payload) } diff --git a/src/features/ht/components/HtContractSummary.vue b/src/features/ht/components/HtContractSummary.vue index 44cc5a1..8677012 100644 --- a/src/features/ht/components/HtContractSummary.vue +++ b/src/features/ht/components/HtContractSummary.vue @@ -250,7 +250,7 @@ const totalRow = computed(() => { landScale: sumField(row => row.landScale), workload: sumField(row => row.workload), hourly: sumField(row => row.hourly), - subtotal: sumField(row => row.subtotal), + subtotal: null, finalFee: sumField(row => row.finalFee) } }) @@ -328,7 +328,7 @@ const columnDefs: ColDef[] = [ }, colSpan: params => { if (!params.data) return 1 - if (params.data.rowType === 'total') return 4 + if (params.data.rowType === 'total') return 5 if (params.data.rowType === 'additional' || params.data.rowType === 'reserve') return 5 return 1 }, diff --git a/src/features/pricing/components/InvestmentScalePricingPane.vue b/src/features/pricing/components/InvestmentScalePricingPane.vue index 0f3ad7e..666d58d 100644 --- a/src/features/pricing/components/InvestmentScalePricingPane.vue +++ b/src/features/pricing/components/InvestmentScalePricingPane.vue @@ -63,7 +63,9 @@ import { buildProjectScopedSessionKey } from '@/lib/pricingPersistControl' import { buildContractScaleIdMap, buildContractScaleMap, + buildContractScaleProjectTotals, getContractScaleRowByMajor, + getContractScaleProjectTotalsByRow, makeProjectMajorKey, normalizeChangedScaleRowIds, parseProjectIndexFromPathKey, @@ -328,8 +330,15 @@ const buildDefaultRows = (projectCountValue = getTargetProjectCount()): DetailRo }) } -const calcOnlyCostScaleAmountFromRows = (rows?: Array<{ amount?: unknown }>) => - sumByNumber(rows || [], row => (typeof row?.amount === 'number' ? row.amount : null)) +const calcOnlyCostScaleAmountFromRows = ( + rows?: Array<{ amount?: unknown; isGroupRow?: unknown }>, + totalAmount?: number | null +) => { + if (typeof totalAmount === 'number' && Number.isFinite(totalAmount)) return totalAmount + const summaryRow = (rows || []).find(row => row?.isGroupRow === true) + if (typeof summaryRow?.amount === 'number' && Number.isFinite(summaryRow.amount)) return summaryRow.amount + return sumByNumber(rows || [], row => (typeof row?.amount === 'number' ? row.amount : null)) +} const getOnlyCostScaleMajorEntry = () => { const industryId = String(activeIndustryCode.value || '').trim() @@ -405,10 +414,22 @@ const buildOnlyCostScaleRow = ( const buildOnlyCostScaleRows = ( rowsFromDb?: Array & Pick>, - options?: { projectCount?: number; cloneFromProjectOne?: boolean } + options?: { + projectCount?: number + cloneFromProjectOne?: boolean + totalAmount?: number | null + preferSummaryAmountWhenSingleRow?: boolean + } ): DetailRow[] => { const targetProjectCount = normalizeProjectCount(options?.projectCount ?? getTargetProjectCount()) const onlyCostMajorId = getOnlyCostScaleMajorEntry()?.id || ONLY_COST_SCALE_ROW_ID + const singleRowSummaryAmount = + options?.preferSummaryAmountWhenSingleRow + ? calcOnlyCostScaleAmountFromRows( + rowsFromDb as Array<{ amount?: unknown; isGroupRow?: unknown }>, + options.totalAmount + ) + : null const dbValueMap = new Map & Pick>() for (const row of rowsFromDb || []) { const projectIndex = resolveRowProjectIndex(row) @@ -428,11 +449,11 @@ const buildOnlyCostScaleRows = ( (options?.cloneFromProjectOne && projectIndex > 1 ? dbValueMap.get(firstProjectKey) : undefined) const fallbackAmount = options?.cloneFromProjectOne && projectIndex > 1 && fromDb == null - ? calcOnlyCostScaleAmountFromRows(rowsFromDb) + ? calcOnlyCostScaleAmountFromRows(rowsFromDb as Array<{ amount?: unknown; isGroupRow?: unknown }>, options.totalAmount) : null result.push( buildOnlyCostScaleRow( - typeof fromDb?.amount === 'number' ? fromDb.amount : fallbackAmount, + singleRowSummaryAmount ?? (typeof fromDb?.amount === 'number' ? fromDb.amount : fallbackAmount), projectIndex, fromDb ) @@ -608,22 +629,24 @@ const getBudgetFeeSplit = ( ) => getScaleBudgetFeeSplitByRow(row, 'cost') const restoreAmountColumnDefaults = async () => { - const htData = await kvStore.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value) + const htData = await kvStore.getItem<{ detailRows: SourceRow[]; totalAmount?: number | null }>(HT_DB_KEY.value) const sourceRows = Array.isArray(htData?.detailRows) ? htData.detailRows : [] const sourceRowMap = buildContractScaleMap(sourceRows) const sourceRowIdMap = buildContractScaleIdMap(sourceRows) + const projectTotals = buildContractScaleProjectTotals(sourceRows, htData?.totalAmount) + const useSummaryAmount = detailRows.value.length === 1 || isOnlyCostScaleService.value const onlyCostScaleFallbackAmount = isOnlyCostScaleService.value - ? calcOnlyCostScaleAmountFromRows(sourceRows as Array<{ amount?: unknown }>) + ? calcOnlyCostScaleAmountFromRows(sourceRows as Array<{ amount?: unknown; isGroupRow?: unknown }>, htData?.totalAmount) : null await restoreScaleColumnDefaults({ gridApi: gridApi.value, rows: detailRows.value, getCurrentValue: row => row.amount, getNextValue: row => { + if (!row.hasCost) return null + if (useSummaryAmount) return getContractScaleProjectTotalsByRow(row, projectTotals).amount const sourceRow = getContractScaleRowByMajor(row, sourceRowMap, sourceRowIdMap) - return row.hasCost - ? (typeof sourceRow?.amount === 'number' ? sourceRow.amount : onlyCostScaleFallbackAmount) - : null + return typeof sourceRow?.amount === 'number' ? sourceRow.amount : onlyCostScaleFallbackAmount }, isSameValue: isSameNullableNumber, applyValue: (row, nextValue) => { @@ -812,24 +835,28 @@ const syncLinkedFieldsFromContractAndFactors = async () => { const syncLinkedScaleValuesFromContract = async (changedRowIds?: string[]) => { if (detailRows.value.length === 0) return - const htData = await kvStore.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value) + const htData = await kvStore.getItem<{ detailRows: SourceRow[]; totalAmount?: number | null }>(HT_DB_KEY.value) const sourceRows = Array.isArray(htData?.detailRows) ? htData.detailRows : [] const sourceRowMap = buildContractScaleMap(sourceRows) const sourceRowIdMap = buildContractScaleIdMap(sourceRows) + const projectTotals = buildContractScaleProjectTotals(sourceRows, htData?.totalAmount) + const useSummaryAmount = detailRows.value.length === 1 || isOnlyCostScaleService.value const onlyCostScaleFallbackAmount = isOnlyCostScaleService.value - ? calcOnlyCostScaleAmountFromRows(sourceRows as Array<{ amount?: unknown }>) + ? calcOnlyCostScaleAmountFromRows(sourceRows as Array<{ amount?: unknown; isGroupRow?: unknown }>, htData?.totalAmount) : null const changedRowIdSet = changedRowIds?.length ? normalizeChangedScaleRowIds(changedRowIds) : null let changed = false for (const row of detailRows.value) { - if (changedRowIdSet) { + if (changedRowIdSet && !useSummaryAmount) { const rowMajorId = resolveRowMajorDictId(row) if (!changedRowIdSet.has(rowMajorId)) continue } const sourceRow = getContractScaleRowByMajor(row, sourceRowMap, sourceRowIdMap) const nextAmount = row.hasCost - ? (typeof sourceRow?.amount === 'number' ? sourceRow.amount : onlyCostScaleFallbackAmount) + ? (useSummaryAmount + ? getContractScaleProjectTotalsByRow(row, projectTotals).amount + : (typeof sourceRow?.amount === 'number' ? sourceRow.amount : onlyCostScaleFallbackAmount)) : null if (isSameNullableNumber(row.amount, nextAmount)) continue row.amount = nextAmount @@ -866,12 +893,21 @@ const buildRowsFromImportDefaultSource = async ( ): Promise => { // 与“使用默认数据”同源:先强制刷新系数,再按合同卡片默认带出。 await loadFactorDefaults() - const htData = await kvStore.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value) + const htData = await kvStore.getItem<{ detailRows: SourceRow[]; totalAmount?: number | null }>(HT_DB_KEY.value) const hasContractRows = Array.isArray(htData?.detailRows) && htData.detailRows.length > 0 if (isOnlyCostScaleService.value) { return hasContractRows - ? buildOnlyCostScaleRows(htData!.detailRows as any, { projectCount: targetProjectCount, cloneFromProjectOne: true }) - : buildOnlyCostScaleRows(undefined, { projectCount: targetProjectCount }) + ? buildOnlyCostScaleRows(htData!.detailRows as any, { + projectCount: targetProjectCount, + cloneFromProjectOne: true, + totalAmount: htData?.totalAmount ?? null, + preferSummaryAmountWhenSingleRow: true + }) + : buildOnlyCostScaleRows(undefined, { + projectCount: targetProjectCount, + totalAmount: htData?.totalAmount ?? null, + preferSummaryAmountWhenSingleRow: true + }) } return hasContractRows ? mergeWithDictRows(htData!.detailRows, { diff --git a/src/features/pricing/components/LandScalePricingPane.vue b/src/features/pricing/components/LandScalePricingPane.vue index 0612243..15e910b 100644 --- a/src/features/pricing/components/LandScalePricingPane.vue +++ b/src/features/pricing/components/LandScalePricingPane.vue @@ -63,7 +63,9 @@ import { buildProjectScopedSessionKey } from '@/lib/pricingPersistControl' import { buildContractScaleIdMap, buildContractScaleMap, + buildContractScaleProjectTotals, getContractScaleRowByMajor, + getContractScaleProjectTotalsByRow, makeProjectMajorKey, normalizeChangedScaleRowIds, parseProjectIndexFromPathKey, @@ -487,17 +489,21 @@ const formatEditableFlexibleNumber = (params: any) => }) const restoreLandAreaColumnDefaults = async () => { - const htData = await kvStore.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value) + const htData = await kvStore.getItem<{ detailRows: SourceRow[]; totalAmount?: number | null }>(HT_DB_KEY.value) const sourceRows = Array.isArray(htData?.detailRows) ? htData.detailRows : [] const sourceRowMap = buildContractScaleMap(sourceRows) const sourceRowIdMap = buildContractScaleIdMap(sourceRows) + const projectTotals = buildContractScaleProjectTotals(sourceRows, htData?.totalAmount) + const useSummaryLandArea = detailRows.value.length === 1 await restoreScaleColumnDefaults({ gridApi: gridApi.value, rows: detailRows.value, getCurrentValue: row => row.landArea, getNextValue: row => { + if (!row.hasArea) return null + if (useSummaryLandArea) return getContractScaleProjectTotalsByRow(row, projectTotals).landArea const sourceRow = getContractScaleRowByMajor(row, sourceRowMap, sourceRowIdMap) - return row.hasArea ? (typeof sourceRow?.landArea === 'number' ? sourceRow.landArea : null) : null + return typeof sourceRow?.landArea === 'number' ? sourceRow.landArea : null }, isSameValue: isSameNullableNumber, applyValue: (row, nextValue) => { @@ -690,20 +696,26 @@ const syncLinkedFieldsFromContractAndFactors = async () => { const syncLinkedScaleValuesFromContract = async (changedRowIds?: string[]) => { if (detailRows.value.length === 0) return - const htData = await kvStore.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value) + const htData = await kvStore.getItem<{ detailRows: SourceRow[]; totalAmount?: number | null }>(HT_DB_KEY.value) const sourceRows = Array.isArray(htData?.detailRows) ? htData.detailRows : [] const sourceRowMap = buildContractScaleMap(sourceRows) const sourceRowIdMap = buildContractScaleIdMap(sourceRows) + const projectTotals = buildContractScaleProjectTotals(sourceRows, htData?.totalAmount) + const useSummaryLandArea = detailRows.value.length === 1 const changedRowIdSet = changedRowIds?.length ? normalizeChangedScaleRowIds(changedRowIds) : null let changed = false for (const row of detailRows.value) { - if (changedRowIdSet) { + if (changedRowIdSet && !useSummaryLandArea) { const rowMajorId = resolveRowMajorDictId(row) if (!changedRowIdSet.has(rowMajorId)) continue } const sourceRow = getContractScaleRowByMajor(row, sourceRowMap, sourceRowIdMap) - const nextLandArea = row.hasArea ? (typeof sourceRow?.landArea === 'number' ? sourceRow.landArea : null) : null + const nextLandArea = row.hasArea + ? (useSummaryLandArea + ? getContractScaleProjectTotalsByRow(row, projectTotals).landArea + : (typeof sourceRow?.landArea === 'number' ? sourceRow.landArea : null)) + : null if (isSameNullableNumber(row.landArea, nextLandArea)) continue row.landArea = nextLandArea changed = true diff --git a/src/features/pricing/components/ScaleFormulaReadonlyPane.vue b/src/features/pricing/components/ScaleFormulaReadonlyPane.vue index abd30fe..bd64301 100644 --- a/src/features/pricing/components/ScaleFormulaReadonlyPane.vue +++ b/src/features/pricing/components/ScaleFormulaReadonlyPane.vue @@ -77,11 +77,21 @@ const numberFormatter = (params: { value?: unknown }) => ? formatThousandsFlexible(params.value, 3) : '' +const scaleValueColumnField = computed(() => + isInvestmentFormula.value ? 'amount' : 'landArea' +) + +const scaleValueColumnHeader = computed(() => + isInvestmentFormula.value + ? t('pricingScale.columns.investAmount') + : t('pricingScale.columns.landArea') +) + const columnDefs = computed[]>(() => withReadonlyAutoHeight([ { - headerName: t('zxFwView.formulaColumns.amount'), - field: 'budgetFee', + headerName: scaleValueColumnHeader.value, + field: scaleValueColumnField.value, minWidth: 130, flex: 1, headerClass: 'ag-right-aligned-header', @@ -92,7 +102,7 @@ const columnDefs = computed[]>(() => }, { headerName: t('pricingScale.columns.basicWork'), - field: 'budgetFeeBasic', + field: 'benchmarkBudgetBasic', minWidth: 130, flex: 1, headerClass: 'ag-right-aligned-header', @@ -109,7 +119,7 @@ const columnDefs = computed[]>(() => }, { headerName: t('pricingScale.columns.optionalWork'), - field: 'budgetFeeOptional', + field: 'benchmarkBudgetOptional', minWidth: 130, flex: 1, headerClass: 'ag-right-aligned-header', diff --git a/src/features/tab/types.ts b/src/features/tab/types.ts index 65d367f..51f61ef 100644 --- a/src/features/tab/types.ts +++ b/src/features/tab/types.ts @@ -141,7 +141,6 @@ export interface FactorRowLike { } export interface ExportScaleRow { - majorid: number major: number cost: number | null area: number | null diff --git a/src/features/workbench/components/HomeEntryView.vue b/src/features/workbench/components/HomeEntryView.vue index 05aa817..e996629 100644 --- a/src/features/workbench/components/HomeEntryView.vue +++ b/src/features/workbench/components/HomeEntryView.vue @@ -14,7 +14,7 @@ import { X } from 'lucide-vue-next' import { getIndustryDisplayName, industryTypeList } from '@/sql' -import { initializeProjectFactorStates } from '@/lib/projectWorkspace' +import { initializeProjectFactorStates, initializeProjectScaleState } from '@/lib/projectWorkspace' import { SelectContent, SelectIcon, @@ -76,6 +76,7 @@ interface ProjectInfoState { const PROJECT_INFO_KEY = 'xm-base-info-v1' const PROJECT_CONSULT_CATEGORY_FACTOR_KEY = 'xm-consult-category-factor-v1' const PROJECT_MAJOR_FACTOR_KEY = 'xm-major-factor-v1' +const PROJECT_SCALE_KEY = 'xm-info-v3' const getActiveProjectId = () => readCurrentProjectId() const tabStore = useTabStore() @@ -277,6 +278,7 @@ const confirmProjectCalc = async () => { PROJECT_CONSULT_CATEGORY_FACTOR_KEY, PROJECT_MAJOR_FACTOR_KEY ) + await initializeProjectScaleState(kvAdapter, industry, PROJECT_SCALE_KEY) writeWorkspaceMode('project') window.location.href = buildProjectUrl(project.id, { forceHome: false, newProject: false }) } finally { diff --git a/src/features/workbench/components/QuickCalcWorkbenchView.vue b/src/features/workbench/components/QuickCalcWorkbenchView.vue index 8b2eb8c..e481ed1 100644 --- a/src/features/workbench/components/QuickCalcWorkbenchView.vue +++ b/src/features/workbench/components/QuickCalcWorkbenchView.vue @@ -443,45 +443,45 @@ watch(canUseLandScale, enabled => {
-