From ad4e9cdee0a9aa013577b72c0b2a025bbc609e49 Mon Sep 17 00:00:00 2001 From: wintsa <770775984@qq.com> Date: Sat, 7 Mar 2026 11:47:07 +0800 Subject: [PATCH] fix someone --- src/components/common/HtFeeGrid.vue | 278 ++++++++++++++ src/components/views/Ht.vue | 2 +- src/components/views/HtAdditionalWorkFee.vue | 14 + src/components/views/HtReserveFee.vue | 14 + src/components/views/htCard.vue | 32 ++ src/components/views/info.vue | 32 +- .../views/pricingView/HourlyPricingPane.vue | 2 +- .../InvestmentScalePricingPane.vue | 354 +++++++++++------ .../pricingView/LandScalePricingPane.vue | 360 ++++++++++++------ .../views/pricingView/WorkloadPricingPane.vue | 5 +- src/components/views/zxFw.vue | 83 ++-- src/lib/diyAgGridOptions.ts | 2 + src/lib/pricingMethodTotals.ts | 154 ++++++-- src/lib/pricingScaleFee.ts | 44 ++- src/lib/xmFactorDefaults.ts | 8 +- tsconfig.tsbuildinfo | 2 +- vite.config.ts | 9 +- 17 files changed, 1084 insertions(+), 311 deletions(-) create mode 100644 src/components/common/HtFeeGrid.vue create mode 100644 src/components/views/HtAdditionalWorkFee.vue create mode 100644 src/components/views/HtReserveFee.vue diff --git a/src/components/common/HtFeeGrid.vue b/src/components/common/HtFeeGrid.vue new file mode 100644 index 0000000..aa01d26 --- /dev/null +++ b/src/components/common/HtFeeGrid.vue @@ -0,0 +1,278 @@ + + + diff --git a/src/components/views/Ht.vue b/src/components/views/Ht.vue index 7cd162a..3bb4e5e 100644 --- a/src/components/views/Ht.vue +++ b/src/components/views/Ht.vue @@ -57,7 +57,7 @@ const CONTRACT_KEY_PREFIX = 'ht-info-v3-' const SERVICE_KEY_PREFIX = 'zxFW-' const CONTRACT_CONSULT_FACTOR_KEY_PREFIX = 'ht-consult-category-factor-v1-' const CONTRACT_MAJOR_FACTOR_KEY_PREFIX = 'ht-major-factor-v1-' -const PRICING_KEY_PREFIXES = ['tzGMF-', 'ydGMF-', 'gzlF-', 'hourlyPricing-'] +const PRICING_KEY_PREFIXES = ['tzGMF-', 'ydGMF-', 'gzlF-', 'hourlyPricing-', 'htExtraFee-'] const PROJECT_INFO_KEY = 'xm-base-info-v1' diff --git a/src/components/views/HtAdditionalWorkFee.vue b/src/components/views/HtAdditionalWorkFee.vue new file mode 100644 index 0000000..0a096e9 --- /dev/null +++ b/src/components/views/HtAdditionalWorkFee.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/components/views/HtReserveFee.vue b/src/components/views/HtReserveFee.vue new file mode 100644 index 0000000..d7a73d7 --- /dev/null +++ b/src/components/views/HtReserveFee.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/components/views/htCard.vue b/src/components/views/htCard.vue index 9280c0c..0fcdb61 100644 --- a/src/components/views/htCard.vue +++ b/src/components/views/htCard.vue @@ -89,11 +89,43 @@ const majorFactorView = markRaw( }) ); +const additionalWorkFeeView = markRaw( + defineComponent({ + name: 'HtAdditionalWorkFeeWithProps', + setup() { + const AsyncHtAdditionalWorkFee = defineAsyncComponent({ + loader: () => import('@/components/views/HtAdditionalWorkFee.vue'), + onError: (err) => { + console.error('加载 HtAdditionalWorkFee 组件失败:', err); + } + }); + return () => h(AsyncHtAdditionalWorkFee, { contractId: props.contractId }); + } + }) +); + +const reserveFeeView = markRaw( + defineComponent({ + name: 'HtReserveFeeWithProps', + setup() { + const AsyncHtReserveFee = defineAsyncComponent({ + loader: () => import('@/components/views/HtReserveFee.vue'), + onError: (err) => { + console.error('加载 HtReserveFee 组件失败:', err); + } + }); + return () => h(AsyncHtReserveFee, { contractId: props.contractId }); + } + }) +); + // 4. 给分类数组添加严格类型标注 const xmCategories: XmCategoryItem[] = [ { key: 'info', label: '规模信息', component: htView }, { key: 'consult-category-factor', label: '咨询分类系数', component: consultCategoryFactorView }, { key: 'major-factor', label: '工程专业系数', component: majorFactorView }, + { key: 'additional-work-fee', label: '附加工作费', component: additionalWorkFeeView }, + { key: 'reserve-fee', label: '预备费', component: reserveFeeView }, { key: 'contract', label: '咨询服务', component: zxfwView }, ]; diff --git a/src/components/views/info.vue b/src/components/views/info.vue index ddd15d2..b1c9fdd 100644 --- a/src/components/views/info.vue +++ b/src/components/views/info.vue @@ -43,6 +43,13 @@ const DB_KEY = 'xm-base-info-v1' const DEFAULT_PROJECT_NAME = 'xxx造价咨询服务' const INDUSTRY_HINT_TEXT = '变更需要重置后重新选择' const PROJECT_INIT_CHANGED_EVENT = 'xm-project-init-changed' +const getTodayDateString = () => { + const now = new Date() + const year = String(now.getFullYear()) + const month = String(now.getMonth() + 1).padStart(2, '0') + const day = String(now.getDate()).padStart(2, '0') + return `${year}-${month}-${day}` +} const isProjectInitialized = ref(false) const showCreateDialog = ref(false) @@ -53,7 +60,7 @@ const projectIndustry = ref('') const preparedBy = ref('') const reviewedBy = ref('') const preparedCompany = ref('') -const preparedDate = ref('') +const preparedDate = ref(getTodayDateString()) const preparedDatePickerValue = ref(undefined) const normalizeDateString = (value: unknown): string => { @@ -123,7 +130,7 @@ const loadFromIndexedDB = async () => { preparedBy.value = typeof data.preparedBy === 'string' ? data.preparedBy : '' reviewedBy.value = typeof data.reviewedBy === 'string' ? data.reviewedBy : '' preparedCompany.value = typeof data.preparedCompany === 'string' ? data.preparedCompany : '' - preparedDate.value = normalizeDateString(data.preparedDate) + preparedDate.value = normalizeDateString(data.preparedDate) || getTodayDateString() syncPreparedDatePickerFromString() return } @@ -134,7 +141,7 @@ const loadFromIndexedDB = async () => { preparedBy.value = '' reviewedBy.value = '' preparedCompany.value = '' - preparedDate.value = '' + preparedDate.value = getTodayDateString() syncPreparedDatePickerFromString() } catch (error) { console.error('loadFromIndexedDB failed:', error) @@ -143,7 +150,7 @@ const loadFromIndexedDB = async () => { preparedBy.value = '' reviewedBy.value = '' preparedCompany.value = '' - preparedDate.value = '' + preparedDate.value = getTodayDateString() syncPreparedDatePickerFromString() } } @@ -182,7 +189,7 @@ const createProject = async () => { preparedBy.value = '' reviewedBy.value = '' preparedCompany.value = '' - preparedDate.value = '' + preparedDate.value = getTodayDateString() syncPreparedDatePickerFromString() isProjectInitialized.value = true showCreateDialog.value = false @@ -320,7 +327,7 @@ onMounted(async () => { - @@ -333,7 +340,7 @@ onMounted(async () => { @@ -342,7 +349,7 @@ onMounted(async () => { @@ -380,7 +387,7 @@ onMounted(async () => { {{ dateValue.day }} @@ -392,12 +399,13 @@ onMounted(async () => {
- +
diff --git a/src/components/views/pricingView/HourlyPricingPane.vue b/src/components/views/pricingView/HourlyPricingPane.vue index 73ecf20..dc2a47e 100644 --- a/src/components/views/pricingView/HourlyPricingPane.vue +++ b/src/components/views/pricingView/HourlyPricingPane.vue @@ -434,7 +434,7 @@ const handleCellValueChanged = () => { if (gridPersistTimer) clearTimeout(gridPersistTimer) gridPersistTimer = setTimeout(() => { void saveToIndexedDB() - }, 1000) + }, 300) } onMounted(async () => { diff --git a/src/components/views/pricingView/InvestmentScalePricingPane.vue b/src/components/views/pricingView/InvestmentScalePricingPane.vue index 27c8ffa..7e2b6f0 100644 --- a/src/components/views/pricingView/InvestmentScalePricingPane.vue +++ b/src/components/views/pricingView/InvestmentScalePricingPane.vue @@ -55,10 +55,14 @@ interface DetailRow { benchmarkBudget: number | null benchmarkBudgetBasic: number | null benchmarkBudgetOptional: number | null + benchmarkBudgetBasicChecked: boolean + benchmarkBudgetOptionalChecked: boolean basicFormula: string optionalFormula: string consultCategoryFactor: number | null majorFactor: number | null + workStageFactor: number | null + workRatio: number | null budgetFee: number | null budgetFeeBasic: number | null budgetFeeOptional: number | null @@ -80,6 +84,8 @@ const props = defineProps<{ }>() const DB_KEY = computed(() => `tzGMF-${props.contractId}-${props.serviceId}`) const HT_DB_KEY = computed(() => `ht-info-v3-${props.contractId}`) +const HT_CONSULT_FACTOR_KEY = computed(() => `ht-consult-category-factor-v1-${props.contractId}`) +const HT_MAJOR_FACTOR_KEY = computed(() => `ht-major-factor-v1-${props.contractId}`) const BASE_INFO_KEY = 'xm-base-info-v1' const activeIndustryCode = ref('') const PRICING_CLEAR_SKIP_PREFIX = 'pricing-clear-skip:' @@ -97,8 +103,8 @@ const getDefaultMajorFactorById = (id: string): number | null => majorFactorMap. const loadFactorDefaults = async () => { const [consultMap, majorMap] = await Promise.all([ - loadConsultCategoryFactorMap(), - loadMajorFactorMap() + loadConsultCategoryFactorMap(HT_CONSULT_FACTOR_KEY.value), + loadMajorFactorMap(HT_MAJOR_FACTOR_KEY.value) ]) consultCategoryFactorMap.value = consultMap majorFactorMap.value = majorMap @@ -216,10 +222,14 @@ const buildDefaultRows = (): DetailRow[] => { benchmarkBudget: null, benchmarkBudgetBasic: null, benchmarkBudgetOptional: null, + benchmarkBudgetBasicChecked: true, + benchmarkBudgetOptionalChecked: true, basicFormula: '', optionalFormula: '', consultCategoryFactor: null, majorFactor: null, + workStageFactor: 1, + workRatio: 100, budgetFee: null, budgetFeeBasic: null, budgetFeeOptional: null, @@ -239,10 +249,14 @@ type SourceRow = Pick & | 'benchmarkBudget' | 'benchmarkBudgetBasic' | 'benchmarkBudgetOptional' + | 'benchmarkBudgetBasicChecked' + | 'benchmarkBudgetOptionalChecked' | 'basicFormula' | 'optionalFormula' | 'consultCategoryFactor' | 'majorFactor' + | 'workStageFactor' + | 'workRatio' | 'budgetFee' | 'budgetFeeBasic' | 'budgetFeeOptional' @@ -277,6 +291,10 @@ const mergeWithDictRows = ( benchmarkBudget: typeof fromDb.benchmarkBudget === 'number' ? fromDb.benchmarkBudget : null, benchmarkBudgetBasic: typeof fromDb.benchmarkBudgetBasic === 'number' ? fromDb.benchmarkBudgetBasic : null, benchmarkBudgetOptional: typeof fromDb.benchmarkBudgetOptional === 'number' ? fromDb.benchmarkBudgetOptional : null, + benchmarkBudgetBasicChecked: + typeof fromDb.benchmarkBudgetBasicChecked === 'boolean' ? fromDb.benchmarkBudgetBasicChecked : true, + benchmarkBudgetOptionalChecked: + typeof fromDb.benchmarkBudgetOptionalChecked === 'boolean' ? fromDb.benchmarkBudgetOptionalChecked : true, basicFormula: typeof fromDb.basicFormula === 'string' ? fromDb.basicFormula : '', optionalFormula: typeof fromDb.optionalFormula === 'string' ? fromDb.optionalFormula : '', consultCategoryFactor: @@ -295,6 +313,8 @@ const mergeWithDictRows = ( : hasMajorFactor ? null : getDefaultMajorFactorById(row.id), + workStageFactor: typeof fromDb.workStageFactor === 'number' ? fromDb.workStageFactor : row.workStageFactor, + workRatio: typeof fromDb.workRatio === 'number' ? fromDb.workRatio : row.workRatio, budgetFee: typeof fromDb.budgetFee === 'number' ? fromDb.budgetFee : null, budgetFeeBasic: typeof fromDb.budgetFeeBasic === 'number' ? fromDb.budgetFeeBasic : null, budgetFeeOptional: typeof fromDb.budgetFeeOptional === 'number' ? fromDb.budgetFeeOptional : null, @@ -335,10 +355,46 @@ const formatReadonlyMoney = (params: any) => { return formatThousands(roundTo(params.value, 2)) } +type BudgetCheckField = 'benchmarkBudgetBasicChecked' | 'benchmarkBudgetOptionalChecked' + +const createBudgetCellRendererWithCheck = (checkField: BudgetCheckField) => (params: any) => { + const valueText = formatReadonlyMoney(params) + const hasValue = params.value != null && params.value !== '' + if (params.node?.group || params.node?.rowPinned || !params.data || !hasValue) { + return valueText + } + + const wrapper = document.createElement('div') + wrapper.style.display = 'flex' + wrapper.style.alignItems = 'center' + wrapper.style.justifyContent = 'flex-end' + wrapper.style.gap = '6px' + wrapper.style.width = '100%' + + const checkbox = document.createElement('input') + checkbox.type = 'checkbox' + checkbox.className = 'cursor-pointer' + + checkbox.checked = params.data[checkField] !== false + checkbox.addEventListener('click', event => event.stopPropagation()) + checkbox.addEventListener('change', () => { + params.data[checkField] = checkbox.checked + handleCellValueChanged() + }) + + const valueSpan = document.createElement('span') + valueSpan.textContent = valueText + wrapper.append(checkbox, valueSpan) + + return wrapper +} + const getBenchmarkBudgetSplitByAmount = (row?: Pick) => getBenchmarkBudgetSplitByScale(row?.amount, 'cost') -const getBudgetFee = (row?: Pick) => { +const getBudgetFee = ( + row?: Pick +) => { const benchmarkBudgetSplit = getBenchmarkBudgetSplitByAmount(row) if (!benchmarkBudgetSplit) return null @@ -346,19 +402,25 @@ const getBudgetFee = (row?: Pick) => { +const getBudgetFeeSplit = ( + row?: Pick +) => { const benchmarkBudgetSplit = getBenchmarkBudgetSplitByAmount(row) if (!benchmarkBudgetSplit) return null return getScaleBudgetFeeSplit({ benchmarkBudgetBasic: benchmarkBudgetSplit.basic, benchmarkBudgetOptional: benchmarkBudgetSplit.optional, majorFactor: row?.majorFactor, - consultCategoryFactor: row?.consultCategoryFactor + consultCategoryFactor: row?.consultCategoryFactor, + workStageFactor: row?.workStageFactor, + workRatio: row?.workRatio }) } @@ -367,7 +429,7 @@ const columnDefs: Array | ColGroupDef> = [ headerName: '造价金额(万元)', field: 'amount', headerClass: 'ag-right-aligned-header', - minWidth: 100, + minWidth: 90, flex: 2, editable: params => !params.node?.group && !params.node?.rowPinned && Boolean(params.data?.hasCost), @@ -383,37 +445,6 @@ const columnDefs: Array | ColGroupDef> = [ valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }), valueFormatter: formatEditableMoney }, - - { - headerName: '咨询分类系数', - field: 'consultCategoryFactor', - width: 80, - minWidth: 70, - maxWidth: 90, - editable: params => !params.node?.group && !params.node?.rowPinned, - cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''), - cellClassRules: { - 'editable-cell-empty': params => - !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '') - }, - valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }), - valueFormatter: formatConsultCategoryFactor - }, - { - headerName: '专业系数', - field: 'majorFactor', - width: 80, - minWidth: 70, - maxWidth: 90, - editable: params => !params.node?.group && !params.node?.rowPinned, - cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''), - cellClassRules: { - 'editable-cell-empty': params => - !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '') - }, - valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }), - valueFormatter: formatMajorFactor - }, { headerName: '基准预算(元)', marryChildren: true, @@ -431,6 +462,7 @@ const columnDefs: Array | ColGroupDef> = [ params.node?.rowPinned ? params.data?.benchmarkBudgetBasic ?? null : getBenchmarkBudgetSplitByAmount(params.data)?.basic ?? null, + cellRenderer: createBudgetCellRendererWithCheck('benchmarkBudgetBasicChecked'), valueFormatter: formatReadonlyMoney }, { @@ -446,6 +478,22 @@ const columnDefs: Array | ColGroupDef> = [ params.node?.rowPinned ? params.data?.benchmarkBudgetOptional ?? null : getBenchmarkBudgetSplitByAmount(params.data)?.optional ?? null, + cellRenderer: createBudgetCellRendererWithCheck('benchmarkBudgetOptionalChecked'), + valueFormatter: formatReadonlyMoney + }, + { + headerName: '小计', + field: 'benchmarkBudget', + colId: 'benchmarkBudgetTotal', + headerClass: 'ag-right-aligned-header', + minWidth: 100, + flex: 1, + cellClass: 'ag-right-aligned-cell', + aggFunc: decimalAggSum, + valueGetter: params => + params.node?.rowPinned + ? params.data?.benchmarkBudget ?? null + : getBenchmarkBudgetSplitByAmount(params.data)?.total ?? null, valueFormatter: formatReadonlyMoney } ] @@ -455,41 +503,71 @@ const columnDefs: Array | ColGroupDef> = [ marryChildren: true, children: [ { - headerName: '基本工作', - field: 'budgetFeeBasic', - colId: 'budgetFeeBasic', - headerClass: 'ag-right-aligned-header', - minWidth: 120, + headerName: '咨询分类系数', + field: 'consultCategoryFactor', + colId: 'consultCategoryFactor', + minWidth: 80, flex: 1, - cellClass: 'ag-right-aligned-cell', - aggFunc: decimalAggSum, - valueGetter: params => - params.node?.rowPinned - ? params.data?.budgetFeeBasic ?? null - : getBudgetFeeSplit(params.data)?.basic ?? null, - valueFormatter: formatReadonlyMoney + editable: params => !params.node?.group && !params.node?.rowPinned, + cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''), + cellClassRules: { + 'editable-cell-empty': params => + !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '') + }, + valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }), + valueFormatter: formatConsultCategoryFactor }, { - headerName: '可选工作', - field: 'budgetFeeOptional', - colId: 'budgetFeeOptional', - headerClass: 'ag-right-aligned-header', - minWidth: 120, + headerName: '专业系数', + field: 'majorFactor', + colId: 'majorFactor', + minWidth: 80, flex: 1, - cellClass: 'ag-right-aligned-cell', - aggFunc: decimalAggSum, - valueGetter: params => - params.node?.rowPinned - ? params.data?.budgetFeeOptional ?? null - : getBudgetFeeSplit(params.data)?.optional ?? null, - valueFormatter: formatReadonlyMoney + editable: params => !params.node?.group && !params.node?.rowPinned, + cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''), + cellClassRules: { + 'editable-cell-empty': params => + !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '') + }, + valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }), + valueFormatter: formatMajorFactor + }, + { + headerName: '工作环节系数(编审系数)', + field: 'workStageFactor', + colId: 'workStageFactor', + minWidth: 80, + flex: 1, + editable: params => !params.node?.group && !params.node?.rowPinned, + cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''), + cellClassRules: { + 'editable-cell-empty': params => + !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '') + }, + valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }), + valueFormatter: formatEditableNumber + }, + { + headerName: '工作占比(%)', + field: 'workRatio', + colId: 'workRatio', + minWidth: 80, + flex: 1, + editable: params => !params.node?.group && !params.node?.rowPinned, + cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''), + cellClassRules: { + 'editable-cell-empty': params => + !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '') + }, + valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }), + valueFormatter: formatEditableNumber }, { headerName: '合计', field: 'budgetFee', colId: 'budgetFeeTotal', headerClass: 'ag-right-aligned-header', - minWidth: 130, + minWidth: 120, flex: 1, cellClass: 'ag-right-aligned-cell', aggFunc: decimalAggSum, @@ -501,7 +579,7 @@ const columnDefs: Array | ColGroupDef> = [ { headerName: '说明', field: 'remark', - minWidth: 180, + minWidth: 100, flex: 1.2, cellEditor: 'agLargeTextCellEditor', wrapText: true, @@ -570,10 +648,14 @@ const pinnedTopRowData = computed(() => [ benchmarkBudget: totalBenchmarkBudget.value, benchmarkBudgetBasic: totalBenchmarkBudgetBasic.value, benchmarkBudgetOptional: totalBenchmarkBudgetOptional.value, + benchmarkBudgetBasicChecked: true, + benchmarkBudgetOptionalChecked: true, basicFormula: '', optionalFormula: '', consultCategoryFactor: null, majorFactor: null, + workStageFactor: null, + workRatio: null, budgetFee: totalBudgetFee.value, budgetFeeBasic: totalBudgetFeeBasic.value, budgetFeeOptional: totalBudgetFeeOptional.value, @@ -582,30 +664,35 @@ const pinnedTopRowData = computed(() => [ } ]) -const buildPersistDetailRows = () => - detailRows.value.map(row => { +const syncComputedValuesToDetailRows = () => { + for (const row of detailRows.value) { const benchmarkBudgetSplit = getBenchmarkBudgetSplitByAmount(row) const budgetFeeSplit = benchmarkBudgetSplit ? getScaleBudgetFeeSplit({ benchmarkBudgetBasic: benchmarkBudgetSplit.basic, benchmarkBudgetOptional: benchmarkBudgetSplit.optional, majorFactor: row.majorFactor, - consultCategoryFactor: row.consultCategoryFactor + consultCategoryFactor: row.consultCategoryFactor, + workStageFactor: row.workStageFactor, + workRatio: row.workRatio }) : null - return { - ...row, - benchmarkBudget: benchmarkBudgetSplit?.total ?? null, - benchmarkBudgetBasic: benchmarkBudgetSplit?.basic ?? null, - benchmarkBudgetOptional: benchmarkBudgetSplit?.optional ?? null, - basicFormula: benchmarkBudgetSplit?.basicFormula ?? '', - optionalFormula: benchmarkBudgetSplit?.optionalFormula ?? '', - budgetFee: budgetFeeSplit?.total ?? null, - budgetFeeBasic: budgetFeeSplit?.basic ?? null, - budgetFeeOptional: budgetFeeSplit?.optional ?? null - } - }) + row.benchmarkBudget = benchmarkBudgetSplit?.total ?? null + row.benchmarkBudgetBasic = benchmarkBudgetSplit?.basic ?? null + row.benchmarkBudgetOptional = benchmarkBudgetSplit?.optional ?? null + row.basicFormula = benchmarkBudgetSplit?.basicFormula ?? '' + row.optionalFormula = benchmarkBudgetSplit?.optionalFormula ?? '' + row.budgetFee = budgetFeeSplit?.total ?? null + row.budgetFeeBasic = budgetFeeSplit?.basic ?? null + row.budgetFeeOptional = budgetFeeSplit?.optional ?? null + } +} + +const buildPersistDetailRows = () => { + syncComputedValuesToDetailRows() + return detailRows.value.map(row => ({ ...row })) +} const saveToIndexedDB = async () => { if (shouldSkipPersist()) return @@ -637,30 +724,35 @@ const loadFromIndexedDB = async () => { await ensureFactorDefaultsLoaded() - if (shouldForceDefaultLoad()) { + const applyContractDefaultRows = async () => { const htData = await localforage.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value) - detailRows.value = htData?.detailRows - ? mergeWithDictRows(htData.detailRows, { includeAmount: false, includeFactorValues: false }) - : buildDefaultRows() + const hasContractRows = Array.isArray(htData?.detailRows) && htData.detailRows.length > 0 + detailRows.value = hasContractRows + ? mergeWithDictRows(htData!.detailRows, { includeFactorValues: true }) + : buildDefaultRows().map(row => ({ + ...row, + consultCategoryFactor: getDefaultConsultCategoryFactor(), + majorFactor: getDefaultMajorFactorById(row.id) + })) + syncComputedValuesToDetailRows() + } + if (shouldForceDefaultLoad()) { + await applyContractDefaultRows() return } const data = await localforage.getItem(DB_KEY.value) if (data) { detailRows.value = mergeWithDictRows(data.detailRows) + syncComputedValuesToDetailRows() return } - const htData = await localforage.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value) - if (htData?.detailRows) { - detailRows.value = mergeWithDictRows(htData.detailRows, { includeAmount: false, includeFactorValues: false }) - return - } - - detailRows.value = buildDefaultRows() + await applyContractDefaultRows() } catch (error) { console.error('loadFromIndexedDB failed:', error) detailRows.value = buildDefaultRows() + syncComputedValuesToDetailRows() } } @@ -687,6 +779,15 @@ const importContractData = async () => { } } +const clearAllData = async () => { + try { + detailRows.value = buildDefaultRows() + await saveToIndexedDB() + } catch (error) { + console.error('clearAllData failed:', error) + } +} + watch( () => pricingPaneReloadStore.getReloadVersion(props.contractId, props.serviceId), (nextVersion, prevVersion) => { @@ -702,10 +803,11 @@ let persistTimer: ReturnType | null = null let gridPersistTimer: ReturnType | null = null const handleCellValueChanged = () => { + syncComputedValuesToDetailRows() if (gridPersistTimer) clearTimeout(gridPersistTimer) gridPersistTimer = setTimeout(() => { void saveToIndexedDB() - }, 1000) + }, 300) } onMounted(async () => { @@ -748,28 +850,52 @@ const processCellFromClipboard = (params: any) => {

投资规模明细

- - - - - - - - 确认覆盖当前明细 - - 将使用合同默认数据覆盖当前投资规模明细,是否继续? - -
- - - - - - -
-
-
-
+
+ + + + + + + + 确认清空当前明细 + + 将清空当前投资规模明细,是否继续? + +
+ + + + + + +
+
+
+
+ + + + + + + + 确认覆盖当前明细 + + 将使用合同默认数据覆盖当前投资规模明细,是否继续? + +
+ + + + + + +
+
+
+
+
@@ -784,5 +910,3 @@ const processCellFromClipboard = (params: any) => {
- - diff --git a/src/components/views/pricingView/LandScalePricingPane.vue b/src/components/views/pricingView/LandScalePricingPane.vue index ffded25..29f744d 100644 --- a/src/components/views/pricingView/LandScalePricingPane.vue +++ b/src/components/views/pricingView/LandScalePricingPane.vue @@ -56,10 +56,14 @@ interface DetailRow { benchmarkBudget: number | null benchmarkBudgetBasic: number | null benchmarkBudgetOptional: number | null + benchmarkBudgetBasicChecked: boolean + benchmarkBudgetOptionalChecked: boolean basicFormula: string optionalFormula: string consultCategoryFactor: number | null majorFactor: number | null + workStageFactor: number | null + workRatio: number | null budgetFee: number | null budgetFeeBasic: number | null budgetFeeOptional: number | null @@ -81,6 +85,8 @@ const props = defineProps<{ }>() const DB_KEY = computed(() => `ydGMF-${props.contractId}-${props.serviceId}`) const HT_DB_KEY = computed(() => `ht-info-v3-${props.contractId}`) +const HT_CONSULT_FACTOR_KEY = computed(() => `ht-consult-category-factor-v1-${props.contractId}`) +const HT_MAJOR_FACTOR_KEY = computed(() => `ht-major-factor-v1-${props.contractId}`) const BASE_INFO_KEY = 'xm-base-info-v1' const activeIndustryCode = ref('') const PRICING_CLEAR_SKIP_PREFIX = 'pricing-clear-skip:' @@ -99,8 +105,8 @@ const getDefaultMajorFactorById = (id: string): number | null => majorFactorMap. const loadFactorDefaults = async () => { const [consultMap, majorMap] = await Promise.all([ - loadConsultCategoryFactorMap(), - loadMajorFactorMap() + loadConsultCategoryFactorMap(HT_CONSULT_FACTOR_KEY.value), + loadMajorFactorMap(HT_MAJOR_FACTOR_KEY.value) ]) consultCategoryFactorMap.value = consultMap majorFactorMap.value = majorMap @@ -218,10 +224,14 @@ const buildDefaultRows = (): DetailRow[] => { benchmarkBudget: null, benchmarkBudgetBasic: null, benchmarkBudgetOptional: null, + benchmarkBudgetBasicChecked: true, + benchmarkBudgetOptionalChecked: true, basicFormula: '', optionalFormula: '', consultCategoryFactor: null, majorFactor: null, + workStageFactor: 1, + workRatio: 100, budgetFee: null, budgetFeeBasic: null, budgetFeeOptional: null, @@ -242,10 +252,14 @@ type SourceRow = Pick & | 'benchmarkBudget' | 'benchmarkBudgetBasic' | 'benchmarkBudgetOptional' + | 'benchmarkBudgetBasicChecked' + | 'benchmarkBudgetOptionalChecked' | 'basicFormula' | 'optionalFormula' | 'consultCategoryFactor' | 'majorFactor' + | 'workStageFactor' + | 'workRatio' | 'budgetFee' | 'budgetFeeBasic' | 'budgetFeeOptional' @@ -281,6 +295,10 @@ const mergeWithDictRows = ( benchmarkBudget: typeof fromDb.benchmarkBudget === 'number' ? fromDb.benchmarkBudget : null, benchmarkBudgetBasic: typeof fromDb.benchmarkBudgetBasic === 'number' ? fromDb.benchmarkBudgetBasic : null, benchmarkBudgetOptional: typeof fromDb.benchmarkBudgetOptional === 'number' ? fromDb.benchmarkBudgetOptional : null, + benchmarkBudgetBasicChecked: + typeof fromDb.benchmarkBudgetBasicChecked === 'boolean' ? fromDb.benchmarkBudgetBasicChecked : true, + benchmarkBudgetOptionalChecked: + typeof fromDb.benchmarkBudgetOptionalChecked === 'boolean' ? fromDb.benchmarkBudgetOptionalChecked : true, basicFormula: typeof fromDb.basicFormula === 'string' ? fromDb.basicFormula : '', optionalFormula: typeof fromDb.optionalFormula === 'string' ? fromDb.optionalFormula : '', consultCategoryFactor: @@ -299,6 +317,8 @@ const mergeWithDictRows = ( : hasMajorFactor ? null : getDefaultMajorFactorById(row.id), + workStageFactor: typeof fromDb.workStageFactor === 'number' ? fromDb.workStageFactor : row.workStageFactor, + workRatio: typeof fromDb.workRatio === 'number' ? fromDb.workRatio : row.workRatio, budgetFee: typeof fromDb.budgetFee === 'number' ? fromDb.budgetFee : null, budgetFeeBasic: typeof fromDb.budgetFeeBasic === 'number' ? fromDb.budgetFeeBasic : null, budgetFeeOptional: typeof fromDb.budgetFeeOptional === 'number' ? fromDb.budgetFeeOptional : null, @@ -328,10 +348,45 @@ const formatReadonlyMoney = (params: any) => { return formatThousands(roundTo(params.value, 2)) } +type BudgetCheckField = 'benchmarkBudgetBasicChecked' | 'benchmarkBudgetOptionalChecked' + +const createBudgetCellRendererWithCheck = (checkField: BudgetCheckField) => (params: any) => { + const valueText = formatReadonlyMoney(params) + const hasValue = params.value != null && params.value !== '' + if (params.node?.group || params.node?.rowPinned || !params.data || !hasValue) { + return valueText + } + + const wrapper = document.createElement('div') + wrapper.style.display = 'flex' + wrapper.style.alignItems = 'center' + wrapper.style.justifyContent = 'flex-end' + wrapper.style.gap = '6px' + wrapper.style.width = '100%' + + const checkbox = document.createElement('input') + checkbox.type = 'checkbox' + checkbox.className = 'cursor-pointer' + checkbox.checked = params.data[checkField] !== false + checkbox.addEventListener('click', event => event.stopPropagation()) + checkbox.addEventListener('change', () => { + params.data[checkField] = checkbox.checked + handleCellValueChanged() + }) + + const valueSpan = document.createElement('span') + valueSpan.textContent = valueText + wrapper.append(checkbox, valueSpan) + + return wrapper +} + const getBenchmarkBudgetSplitByLandArea = (row?: Pick) => getBenchmarkBudgetSplitByScale(row?.landArea, 'area') -const getBudgetFee = (row?: Pick) => { +const getBudgetFee = ( + row?: Pick +) => { const benchmarkBudgetSplit = getBenchmarkBudgetSplitByLandArea(row) if (!benchmarkBudgetSplit) return null @@ -339,19 +394,25 @@ const getBudgetFee = (row?: Pick) => { +const getBudgetFeeSplit = ( + row?: Pick +) => { const benchmarkBudgetSplit = getBenchmarkBudgetSplitByLandArea(row) if (!benchmarkBudgetSplit) return null return getScaleBudgetFeeSplit({ benchmarkBudgetBasic: benchmarkBudgetSplit.basic, benchmarkBudgetOptional: benchmarkBudgetSplit.optional, majorFactor: row?.majorFactor, - consultCategoryFactor: row?.consultCategoryFactor + consultCategoryFactor: row?.consultCategoryFactor, + workStageFactor: row?.workStageFactor, + workRatio: row?.workRatio }) } @@ -371,8 +432,9 @@ const columnDefs: Array | ColGroupDef> = [ headerName: '用地面积(亩)', field: 'landArea', headerClass: 'ag-right-aligned-header', - minWidth: 170, - flex: 1, + minWidth: 90, + + flex: 2, editable: params => !params.node?.group && !params.node?.rowPinned && Boolean(params.data?.hasArea), cellClass: params => @@ -389,37 +451,6 @@ const columnDefs: Array | ColGroupDef> = [ }, valueFormatter: formatEditableFlexibleNumber }, - - { - headerName: '咨询分类系数', - field: 'consultCategoryFactor', - width: 80, - minWidth: 70, - maxWidth: 90, - editable: params => !params.node?.group && !params.node?.rowPinned, - cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''), - cellClassRules: { - 'editable-cell-empty': params => - !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '') - }, - valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }), - valueFormatter: formatConsultCategoryFactor - }, - { - headerName: '专业系数', - field: 'majorFactor', - width: 80, - minWidth: 70, - maxWidth: 90, - editable: params => !params.node?.group && !params.node?.rowPinned, - cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''), - cellClassRules: { - 'editable-cell-empty': params => - !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '') - }, - valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }), - valueFormatter: formatMajorFactor - }, { headerName: '基准预算(元)', marryChildren: true, @@ -429,7 +460,7 @@ const columnDefs: Array | ColGroupDef> = [ field: 'benchmarkBudgetBasic', colId: 'benchmarkBudgetBasic', headerClass: 'ag-right-aligned-header', - minWidth: 140, + minWidth: 130, flex: 1, cellClass: 'ag-right-aligned-cell', aggFunc: decimalAggSum, @@ -437,6 +468,7 @@ const columnDefs: Array | ColGroupDef> = [ params.node?.rowPinned ? params.data?.benchmarkBudgetBasic ?? null : getBenchmarkBudgetSplitByLandArea(params.data)?.basic ?? null, + cellRenderer: createBudgetCellRendererWithCheck('benchmarkBudgetBasicChecked'), valueFormatter: formatReadonlyMoney }, { @@ -444,7 +476,7 @@ const columnDefs: Array | ColGroupDef> = [ field: 'benchmarkBudgetOptional', colId: 'benchmarkBudgetOptional', headerClass: 'ag-right-aligned-header', - minWidth: 140, + minWidth: 130, flex: 1, cellClass: 'ag-right-aligned-cell', aggFunc: decimalAggSum, @@ -452,6 +484,22 @@ const columnDefs: Array | ColGroupDef> = [ params.node?.rowPinned ? params.data?.benchmarkBudgetOptional ?? null : getBenchmarkBudgetSplitByLandArea(params.data)?.optional ?? null, + cellRenderer: createBudgetCellRendererWithCheck('benchmarkBudgetOptionalChecked'), + valueFormatter: formatReadonlyMoney + }, + { + headerName: '小计', + field: 'benchmarkBudget', + colId: 'benchmarkBudgetTotal', + headerClass: 'ag-right-aligned-header', + minWidth: 100, + flex: 1, + cellClass: 'ag-right-aligned-cell', + aggFunc: decimalAggSum, + valueGetter: params => + params.node?.rowPinned + ? params.data?.benchmarkBudget ?? null + : getBenchmarkBudgetSplitByLandArea(params.data)?.total ?? null, valueFormatter: formatReadonlyMoney } ] @@ -461,41 +509,71 @@ const columnDefs: Array | ColGroupDef> = [ marryChildren: true, children: [ { - headerName: '基本工作', - field: 'budgetFeeBasic', - colId: 'budgetFeeBasic', - headerClass: 'ag-right-aligned-header', - minWidth: 130, + headerName: '咨询分类系数', + field: 'consultCategoryFactor', + colId: 'consultCategoryFactor', + minWidth: 80, flex: 1, - cellClass: 'ag-right-aligned-cell', - aggFunc: decimalAggSum, - valueGetter: params => - params.node?.rowPinned - ? params.data?.budgetFeeBasic ?? null - : getBudgetFeeSplit(params.data)?.basic ?? null, - valueFormatter: formatReadonlyMoney + editable: params => !params.node?.group && !params.node?.rowPinned, + cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''), + cellClassRules: { + 'editable-cell-empty': params => + !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '') + }, + valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }), + valueFormatter: formatConsultCategoryFactor }, { - headerName: '可选工作', - field: 'budgetFeeOptional', - colId: 'budgetFeeOptional', - headerClass: 'ag-right-aligned-header', - minWidth: 130, + headerName: '专业系数', + field: 'majorFactor', + colId: 'majorFactor', + minWidth: 80, flex: 1, - cellClass: 'ag-right-aligned-cell', - aggFunc: decimalAggSum, - valueGetter: params => - params.node?.rowPinned - ? params.data?.budgetFeeOptional ?? null - : getBudgetFeeSplit(params.data)?.optional ?? null, - valueFormatter: formatReadonlyMoney + editable: params => !params.node?.group && !params.node?.rowPinned, + cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''), + cellClassRules: { + 'editable-cell-empty': params => + !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '') + }, + valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }), + valueFormatter: formatMajorFactor + }, + { + headerName: '工作环节系数(编审系数)', + field: 'workStageFactor', + colId: 'workStageFactor', + minWidth: 80, + flex: 1, + editable: params => !params.node?.group && !params.node?.rowPinned, + cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''), + cellClassRules: { + 'editable-cell-empty': params => + !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '') + }, + valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }), + valueFormatter: formatEditableNumber + }, + { + headerName: '工作占比(%)', + field: 'workRatio', + colId: 'workRatio', + minWidth: 80, + flex: 1, + editable: params => !params.node?.group && !params.node?.rowPinned, + cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''), + cellClassRules: { + 'editable-cell-empty': params => + !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '') + }, + valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }), + valueFormatter: formatEditableNumber }, { headerName: '合计', field: 'budgetFee', colId: 'budgetFeeTotal', headerClass: 'ag-right-aligned-header', - minWidth: 140, + minWidth: 120, flex: 1, cellClass: 'ag-right-aligned-cell', aggFunc: decimalAggSum, @@ -507,7 +585,7 @@ const columnDefs: Array | ColGroupDef> = [ { headerName: '说明', field: 'remark', - minWidth: 180, + minWidth: 100, flex: 1.2, cellEditor: 'agLargeTextCellEditor', wrapText: true, @@ -529,7 +607,7 @@ const columnDefs: Array | ColGroupDef> = [ const autoGroupColumnDef: ColDef = { headerName: '专业编码以及工程专业名称', - minWidth: 320, + minWidth: 250, pinned: 'left', flex: 2, @@ -575,10 +653,14 @@ const pinnedTopRowData = computed(() => [ benchmarkBudget: totalBenchmarkBudget.value, benchmarkBudgetBasic: totalBenchmarkBudgetBasic.value, benchmarkBudgetOptional: totalBenchmarkBudgetOptional.value, + benchmarkBudgetBasicChecked: true, + benchmarkBudgetOptionalChecked: true, basicFormula: '', optionalFormula: '', consultCategoryFactor: null, majorFactor: null, + workStageFactor: null, + workRatio: null, budgetFee: totalBudgetFee.value, budgetFeeBasic: totalBudgetFeeBasic.value, budgetFeeOptional: totalBudgetFeeOptional.value, @@ -587,30 +669,35 @@ const pinnedTopRowData = computed(() => [ } ]) -const buildPersistDetailRows = () => - detailRows.value.map(row => { +const syncComputedValuesToDetailRows = () => { + for (const row of detailRows.value) { const benchmarkBudgetSplit = getBenchmarkBudgetSplitByLandArea(row) const budgetFeeSplit = benchmarkBudgetSplit ? getScaleBudgetFeeSplit({ benchmarkBudgetBasic: benchmarkBudgetSplit.basic, benchmarkBudgetOptional: benchmarkBudgetSplit.optional, majorFactor: row.majorFactor, - consultCategoryFactor: row.consultCategoryFactor + consultCategoryFactor: row.consultCategoryFactor, + workStageFactor: row.workStageFactor, + workRatio: row.workRatio }) : null - return { - ...row, - benchmarkBudget: benchmarkBudgetSplit?.total ?? null, - benchmarkBudgetBasic: benchmarkBudgetSplit?.basic ?? null, - benchmarkBudgetOptional: benchmarkBudgetSplit?.optional ?? null, - basicFormula: benchmarkBudgetSplit?.basicFormula ?? '', - optionalFormula: benchmarkBudgetSplit?.optionalFormula ?? '', - budgetFee: budgetFeeSplit?.total ?? null, - budgetFeeBasic: budgetFeeSplit?.basic ?? null, - budgetFeeOptional: budgetFeeSplit?.optional ?? null - } - }) + row.benchmarkBudget = benchmarkBudgetSplit?.total ?? null + row.benchmarkBudgetBasic = benchmarkBudgetSplit?.basic ?? null + row.benchmarkBudgetOptional = benchmarkBudgetSplit?.optional ?? null + row.basicFormula = benchmarkBudgetSplit?.basicFormula ?? '' + row.optionalFormula = benchmarkBudgetSplit?.optionalFormula ?? '' + row.budgetFee = budgetFeeSplit?.total ?? null + row.budgetFeeBasic = budgetFeeSplit?.basic ?? null + row.budgetFeeOptional = budgetFeeSplit?.optional ?? null + } +} + +const buildPersistDetailRows = () => { + syncComputedValuesToDetailRows() + return detailRows.value.map(row => ({ ...row })) +} const saveToIndexedDB = async () => { if (shouldSkipPersist()) return @@ -641,30 +728,35 @@ const loadFromIndexedDB = async () => { typeof baseInfo?.projectIndustry === 'string' ? baseInfo.projectIndustry.trim() : '' await ensureFactorDefaultsLoaded() - if (shouldForceDefaultLoad()) { + const applyContractDefaultRows = async () => { const htData = await localforage.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value) - detailRows.value = htData?.detailRows - ? mergeWithDictRows(htData.detailRows, { includeScaleValues: false, includeFactorValues: false }) - : buildDefaultRows() + const hasContractRows = Array.isArray(htData?.detailRows) && htData.detailRows.length > 0 + detailRows.value = hasContractRows + ? mergeWithDictRows(htData!.detailRows, { includeFactorValues: true }) + : buildDefaultRows().map(row => ({ + ...row, + consultCategoryFactor: getDefaultConsultCategoryFactor(), + majorFactor: getDefaultMajorFactorById(row.id) + })) + syncComputedValuesToDetailRows() + } + if (shouldForceDefaultLoad()) { + await applyContractDefaultRows() return } const data = await localforage.getItem(DB_KEY.value) if (data) { detailRows.value = mergeWithDictRows(data.detailRows) + syncComputedValuesToDetailRows() return } - const htData = await localforage.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value) - if (htData?.detailRows) { - detailRows.value = mergeWithDictRows(htData.detailRows, { includeScaleValues: false, includeFactorValues: false }) - return - } - - detailRows.value = buildDefaultRows() + await applyContractDefaultRows() } catch (error) { console.error('loadFromIndexedDB failed:', error) detailRows.value = buildDefaultRows() + syncComputedValuesToDetailRows() } } @@ -691,6 +783,15 @@ const importContractData = async () => { } } +const clearAllData = async () => { + try { + detailRows.value = buildDefaultRows() + await saveToIndexedDB() + } catch (error) { + console.error('clearAllData failed:', error) + } +} + watch( () => pricingPaneReloadStore.getReloadVersion(props.contractId, props.serviceId), (nextVersion, prevVersion) => { @@ -706,10 +807,11 @@ let persistTimer: ReturnType | null = null let gridPersistTimer: ReturnType | null = null const handleCellValueChanged = () => { + syncComputedValuesToDetailRows() if (gridPersistTimer) clearTimeout(gridPersistTimer) gridPersistTimer = setTimeout(() => { void saveToIndexedDB() - }, 1000) + }, 300) } onMounted(async () => { @@ -752,28 +854,52 @@ const processCellFromClipboard = (params: any) => {

用地规模明细

- - - - - - - - 确认覆盖当前明细 - - 将使用合同默认数据覆盖当前用地规模明细,是否继续? - -
- - - - - - -
-
-
-
+
+ + + + + + + + 确认清空当前明细 + + 将清空当前用地规模明细,是否继续? + +
+ + + + + + +
+
+
+
+ + + + + + + + 确认覆盖当前明细 + + 将使用合同默认数据覆盖当前用地规模明细,是否继续? + +
+ + + + + + +
+
+
+
+
diff --git a/src/components/views/pricingView/WorkloadPricingPane.vue b/src/components/views/pricingView/WorkloadPricingPane.vue index d8f3bba..b693a71 100644 --- a/src/components/views/pricingView/WorkloadPricingPane.vue +++ b/src/components/views/pricingView/WorkloadPricingPane.vue @@ -43,6 +43,7 @@ const props = defineProps<{ serviceId: string | number }>() const DB_KEY = computed(() => `gzlF-${props.contractId}-${props.serviceId}`) +const HT_CONSULT_FACTOR_KEY = computed(() => `ht-consult-category-factor-v1-${props.contractId}`) const PRICING_CLEAR_SKIP_PREFIX = 'pricing-clear-skip:' const PRICING_FORCE_DEFAULT_PREFIX = 'pricing-force-default:' const pricingPaneReloadStore = usePricingPaneReloadStore() @@ -55,7 +56,7 @@ const getDefaultConsultCategoryFactor = () => const ensureFactorDefaultsLoaded = async () => { if (factorDefaultsLoaded) return - consultCategoryFactorMap.value = await loadConsultCategoryFactorMap() + consultCategoryFactorMap.value = await loadConsultCategoryFactorMap(HT_CONSULT_FACTOR_KEY.value) factorDefaultsLoaded = true } @@ -481,7 +482,7 @@ const handleCellValueChanged = () => { if (gridPersistTimer) clearTimeout(gridPersistTimer) gridPersistTimer = setTimeout(() => { void saveToIndexedDB() - }, 1000) + }, 300) } onMounted(async () => { diff --git a/src/components/views/zxFw.vue b/src/components/views/zxFw.vue index 0222914..afc0735 100644 --- a/src/components/views/zxFw.vue +++ b/src/components/views/zxFw.vue @@ -9,7 +9,7 @@ import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale' import { addNumbers } from '@/lib/decimal' import { parseNumberOrNull } from '@/lib/number' import { formatThousands, formatThousandsFlexible } from '@/lib/numberFormat' -import { getPricingMethodTotalsForServices } from '@/lib/pricingMethodTotals' +import { getPricingMethodTotalsForService, getPricingMethodTotalsForServices } from '@/lib/pricingMethodTotals' import { ZXFW_RELOAD_SERVICE_KEY } from '@/lib/zxFwPricingSync' import { Pencil, Eraser, Trash2 } from 'lucide-vue-next' import { @@ -311,19 +311,19 @@ const clearRowValues = async (row: DetailRow) => { // 若该服务编辑页已打开,先关闭,避免子页面卸载时把旧数据写回缓? tabStore.removeTab(`zxfw-edit-${props.contractId}-${row.id}`) await nextTick() await clearPricingPaneValues(row.id) - // const totals = await getPricingMethodTotalsForService({ - // contractId: props.contractId, - // serviceId: row.id - // }) + const totals = await getPricingMethodTotalsForService({ + contractId: props.contractId, + serviceId: row.id + }) const clearedRows = detailRows.value.map(item => item.id !== row.id ? item : { ...item, - investScale: null, - landScale: null, - workload: null, - hourly: null + investScale: totals.investScale, + landScale: totals.landScale, + workload: totals.workload, + hourly: totals.hourly } ) const nextInvestScale = getMethodTotalFromRows(clearedRows, 'investScale') @@ -384,7 +384,7 @@ const ActionCellRenderer = defineComponent({ ]), h('button', { class: 'zxfw-action-btn', 'data-action': 'clear', type: 'button' }, [ h(Eraser, { size: 13, 'aria-hidden': 'true' }), - h('span', '清空') + h('span', '恢复默认') ]), h('button', { class: 'zxfw-action-btn zxfw-action-btn--danger', 'data-action': 'delete', type: 'button' }, [ h(Trash2, { size: 13, 'aria-hidden': 'true' }), @@ -527,17 +527,47 @@ const detailGridOptions: GridOptions = { } } -const fillPricingTotalsForSelectedRows = async () => { - const serviceRows = detailRows.value.filter(row => !isFixedRow(row)) - if (serviceRows.length === 0) return +const applyFixedRowTotals = (rows: DetailRow[]) => { + const nextInvestScale = getMethodTotalFromRows(rows, 'investScale') + const nextLandScale = getMethodTotalFromRows(rows, 'landScale') + const nextWorkload = getMethodTotalFromRows(rows, 'workload') + const nextHourly = getMethodTotalFromRows(rows, 'hourly') + return rows.map(row => + isFixedRow(row) + ? { + ...row, + investScale: nextInvestScale, + landScale: nextLandScale, + workload: nextWorkload, + hourly: nextHourly, + subtotal: addNumbers(nextInvestScale, nextLandScale, nextWorkload, nextHourly) + } + : row + ) +} + +const fillPricingTotalsForServiceIds = async (serviceIds: string[]) => { + const targetIds = Array.from( + new Set( + serviceIds.filter(id => + detailRows.value.some(row => !isFixedRow(row) && String(row.id) === String(id)) + ) + ) + ) + + if (targetIds.length === 0) { + detailRows.value = applyFixedRowTotals(detailRows.value) + return + } const totalsByServiceId = await getPricingMethodTotalsForServices({ contractId: props.contractId, - serviceIds: serviceRows.map(row => row.id) + serviceIds: targetIds }) - detailRows.value = detailRows.value.map(row => { - if (isFixedRow(row)) return row + const targetSet = new Set(targetIds.map(id => String(id))) + const nextRows = detailRows.value.map(row => { + if (isFixedRow(row) || !targetSet.has(String(row.id))) return row const totals = totalsByServiceId.get(String(row.id)) if (!totals) return row return { @@ -548,6 +578,8 @@ const fillPricingTotalsForSelectedRows = async () => { hourly: totals.hourly } }) + + detailRows.value = applyFixedRowTotals(nextRows) } const applySelection = (codes: string[]) => { @@ -601,7 +633,11 @@ const applySelection = (codes: string[]) => { } const handleServiceSelectionChange = async (ids: string[]) => { + const prevIds = [...selectedIds.value] applySelection(ids) + const nextSelectedSet = new Set(selectedIds.value) + const addedIds = selectedIds.value.filter(id => !prevIds.includes(id) && nextSelectedSet.has(id)) + await fillPricingTotalsForServiceIds(addedIds) await saveToIndexedDB() } @@ -776,12 +812,7 @@ const loadFromIndexedDB = async () => { hourly: typeof old.hourly === 'number' ? old.hourly : null } }) - - try { - // await fillPricingTotalsForSelectedRows() - } catch (error) { - console.error('fillPricingTotalsForSelectedRows failed:', error) - } + detailRows.value = applyFixedRowTotals(detailRows.value) } catch (error) { console.error('loadFromIndexedDB failed:', error) selectedIds.value = [] @@ -802,7 +833,7 @@ const handleCellValueChanged = () => { if (gridPersistTimer) clearTimeout(gridPersistTimer) gridPersistTimer = setTimeout(() => { void saveToIndexedDB() - }, 1000) + }, 300) } onMounted(async () => { @@ -849,16 +880,16 @@ onBeforeUnmount(() => { - 确认清空服务数据 + 确认恢复默认数据 - 将清空“{{ pendingClearServiceName }}”的计价数据是否继续? + 会使用合同卡片里面最新填写的规模信息以及系数,自动计算默认数据,覆盖“{{ pendingClearServiceName }}”当前数据,是否继续?
- +
diff --git a/src/lib/diyAgGridOptions.ts b/src/lib/diyAgGridOptions.ts index 95fb6b0..6706486 100644 --- a/src/lib/diyAgGridOptions.ts +++ b/src/lib/diyAgGridOptions.ts @@ -9,6 +9,7 @@ const borderConfig = { export const myTheme = themeQuartz.withParams({ wrapperBorder: false, + wrapperBorderRadius: 0, headerBackgroundColor: '#f0f2f3', headerTextColor: '#374151', headerFontSize: 15, @@ -25,6 +26,7 @@ export const gridOptions: GridOptions = { tooltipShowMode: 'whenTruncated', suppressAggFuncInHeader: true, singleClickEdit: true, + stopEditingWhenCellsLoseFocus: true, suppressClickEdit: false, suppressContextMenu: false, groupDefaultExpanded: -1, diff --git a/src/lib/pricingMethodTotals.ts b/src/lib/pricingMethodTotals.ts index 210f425..3a82bad 100644 --- a/src/lib/pricingMethodTotals.ts +++ b/src/lib/pricingMethodTotals.ts @@ -14,6 +14,14 @@ interface StoredDetailRowsState { detailRows?: T[] } +interface StoredFactorState { + detailRows?: Array<{ + id: string + standardFactor?: number | null + budgetValue?: number | null + }> +} + type MaybeNumber = number | null | undefined interface ScaleRow { @@ -22,6 +30,8 @@ interface ScaleRow { landArea: number | null consultCategoryFactor: number | null majorFactor: number | null + workStageFactor: number | null + workRatio: number | null } interface WorkloadRow { @@ -92,25 +102,74 @@ const getDefaultMajorFactorById = (id: string) => { return toFiniteNumberOrNull(major?.defCoe) } +const resolveFactorValue = ( + row: { budgetValue?: number | null; standardFactor?: number | null } | undefined, + fallback: number | null +) => { + if (!row) return fallback + const budgetValue = toFiniteNumberOrNull(row.budgetValue) + if (budgetValue != null) return budgetValue + const standardFactor = toFiniteNumberOrNull(row.standardFactor) + if (standardFactor != null) return standardFactor + return fallback +} + +const buildConsultCategoryFactorMap = (state: StoredFactorState | null) => { + const map = new Map() + const serviceDict = getServiceDictById() as Record + for (const [id, item] of Object.entries(serviceDict)) { + map.set(String(id), toFiniteNumberOrNull(item?.defCoe)) + } + for (const row of state?.detailRows || []) { + if (!row?.id) continue + const id = String(row.id) + map.set(id, resolveFactorValue(row, map.get(id) ?? null)) + } + return map +} + +const buildMajorFactorMap = (state: StoredFactorState | null) => { + const map = new Map() + for (const [id, item] of majorById.entries()) { + map.set(String(id), toFiniteNumberOrNull(item?.defCoe)) + } + for (const row of state?.detailRows || []) { + if (!row?.id) continue + const rowId = String(row.id) + const id = map.has(rowId) ? rowId : majorIdAliasMap.get(rowId) || rowId + map.set(id, resolveFactorValue(row, map.get(id) ?? null)) + } + return map +} + const getMajorLeafIds = () => getMajorDictEntries() .filter(({ item }) => Boolean(item?.code && String(item.code).includes('-'))) .map(({ id }) => id) -const buildDefaultScaleRows = (serviceId: string | number): ScaleRow[] => { - const defaultConsultCategoryFactor = getDefaultConsultCategoryFactor(serviceId) +const buildDefaultScaleRows = ( + serviceId: string | number, + consultCategoryFactorMap?: Map, + majorFactorMap?: Map +): ScaleRow[] => { + const defaultConsultCategoryFactor = + consultCategoryFactorMap?.get(String(serviceId)) ?? getDefaultConsultCategoryFactor(serviceId) return getMajorLeafIds().map(id => ({ id, amount: null, landArea: null, consultCategoryFactor: defaultConsultCategoryFactor, - majorFactor: getDefaultMajorFactorById(id) + majorFactor: majorFactorMap?.get(id) ?? getDefaultMajorFactorById(id), + workStageFactor: 1, + workRatio: 100 })) } const mergeScaleRows = ( serviceId: string | number, - rowsFromDb: Array & Pick> | undefined + rowsFromDb: Array & Pick> | undefined, + consultCategoryFactorMap?: Map, + majorFactorMap?: Map ): ScaleRow[] => { const dbValueMap = toRowMap(rowsFromDb) for (const row of rowsFromDb || []) { @@ -121,13 +180,16 @@ const mergeScaleRows = ( } } - const defaultConsultCategoryFactor = getDefaultConsultCategoryFactor(serviceId) - return buildDefaultScaleRows(serviceId).map(row => { + const defaultConsultCategoryFactor = + consultCategoryFactorMap?.get(String(serviceId)) ?? getDefaultConsultCategoryFactor(serviceId) + return buildDefaultScaleRows(serviceId, consultCategoryFactorMap, majorFactorMap).map(row => { const fromDb = dbValueMap.get(row.id) if (!fromDb) return row const hasConsultCategoryFactor = hasOwn(fromDb, 'consultCategoryFactor') const hasMajorFactor = hasOwn(fromDb, 'majorFactor') + const hasWorkStageFactor = hasOwn(fromDb, 'workStageFactor') + const hasWorkRatio = hasOwn(fromDb, 'workRatio') return { ...row, @@ -138,7 +200,13 @@ const mergeScaleRows = ( (hasConsultCategoryFactor ? null : defaultConsultCategoryFactor), majorFactor: toFiniteNumberOrNull(fromDb.majorFactor) ?? - (hasMajorFactor ? null : getDefaultMajorFactorById(row.id)) + (hasMajorFactor ? null : (majorFactorMap?.get(row.id) ?? getDefaultMajorFactorById(row.id))), + workStageFactor: + toFiniteNumberOrNull((fromDb as Partial).workStageFactor) ?? + (hasWorkStageFactor ? null : row.workStageFactor), + workRatio: + toFiniteNumberOrNull((fromDb as Partial).workRatio) ?? + (hasWorkRatio ? null : row.workRatio) } }) } @@ -153,7 +221,9 @@ const getInvestmentBudgetFee = (row: ScaleRow) => { return getScaleBudgetFee({ benchmarkBudget: getBenchmarkBudgetByAmount(row.amount), majorFactor: row.majorFactor, - consultCategoryFactor: row.consultCategoryFactor + consultCategoryFactor: row.consultCategoryFactor, + workStageFactor: row.workStageFactor, + workRatio: row.workRatio }) } @@ -161,7 +231,9 @@ const getLandBudgetFee = (row: ScaleRow) => { return getScaleBudgetFee({ benchmarkBudget: getBenchmarkBudgetByLandArea(row.landArea), majorFactor: row.majorFactor, - consultCategoryFactor: row.consultCategoryFactor + consultCategoryFactor: row.consultCategoryFactor, + workStageFactor: row.workStageFactor, + workRatio: row.workRatio }) } @@ -170,8 +242,12 @@ const getTaskEntriesByServiceId = (serviceId: string | number) => .sort((a, b) => Number(a[0]) - Number(b[0])) .filter(([, task]) => Number(task.serviceID) === Number(serviceId)) -const buildDefaultWorkloadRows = (serviceId: string | number): WorkloadRow[] => { - const defaultConsultCategoryFactor = getDefaultConsultCategoryFactor(serviceId) +const buildDefaultWorkloadRows = ( + serviceId: string | number, + consultCategoryFactorMap?: Map +): WorkloadRow[] => { + const defaultConsultCategoryFactor = + consultCategoryFactorMap?.get(String(serviceId)) ?? getDefaultConsultCategoryFactor(serviceId) return getTaskEntriesByServiceId(serviceId).map(([taskId, task], order) => ({ id: `task-${taskId}-${order}`, conversion: toFiniteNumberOrNull(task.conversion), @@ -184,11 +260,12 @@ const buildDefaultWorkloadRows = (serviceId: string | number): WorkloadRow[] => const mergeWorkloadRows = ( serviceId: string | number, - rowsFromDb: Array & Pick> | undefined + rowsFromDb: Array & Pick> | undefined, + consultCategoryFactorMap?: Map ): WorkloadRow[] => { const dbValueMap = toRowMap(rowsFromDb) - return buildDefaultWorkloadRows(serviceId).map(row => { + return buildDefaultWorkloadRows(serviceId, consultCategoryFactorMap).map(row => { const fromDb = dbValueMap.get(row.id) if (!fromDb) return row @@ -270,15 +347,27 @@ const calcHourlyServiceBudget = (row: HourlyRow) => { const resolveScaleRows = ( serviceId: string, pricingData: StoredDetailRowsState | null, - htData: StoredDetailRowsState | null + htData: StoredDetailRowsState | null, + consultCategoryFactorMap?: Map, + majorFactorMap?: Map ) => { if (pricingData?.detailRows != null) { - return mergeScaleRows(serviceId, pricingData.detailRows as any) + return mergeScaleRows( + serviceId, + pricingData.detailRows as any, + consultCategoryFactorMap, + majorFactorMap + ) } if (htData?.detailRows != null) { - return mergeScaleRows(serviceId, htData.detailRows as any) + return mergeScaleRows( + serviceId, + htData.detailRows as any, + consultCategoryFactorMap, + majorFactorMap + ) } - return buildDefaultScaleRows(serviceId) + return buildDefaultScaleRows(serviceId, consultCategoryFactorMap, majorFactorMap) } export const getPricingMethodTotalsForService = async (params: { @@ -287,33 +376,52 @@ export const getPricingMethodTotalsForService = async (params: { }): Promise => { const serviceId = String(params.serviceId) const htDbKey = `ht-info-v3-${params.contractId}` + const consultFactorDbKey = `ht-consult-category-factor-v1-${params.contractId}` + const majorFactorDbKey = `ht-major-factor-v1-${params.contractId}` const investDbKey = `tzGMF-${params.contractId}-${serviceId}` const landDbKey = `ydGMF-${params.contractId}-${serviceId}` const workloadDbKey = `gzlF-${params.contractId}-${serviceId}` const hourlyDbKey = `hourlyPricing-${params.contractId}-${serviceId}` - const [investData, landData, workloadData, hourlyData, htData] = await Promise.all([ + const [investData, landData, workloadData, hourlyData, htData, consultFactorData, majorFactorData] = await Promise.all([ localforage.getItem(investDbKey), localforage.getItem(landDbKey), localforage.getItem(workloadDbKey), localforage.getItem(hourlyDbKey), - localforage.getItem(htDbKey) + localforage.getItem(htDbKey), + localforage.getItem(consultFactorDbKey), + localforage.getItem(majorFactorDbKey) ]) + const consultCategoryFactorMap = buildConsultCategoryFactorMap(consultFactorData) + const majorFactorMap = buildMajorFactorMap(majorFactorData) + // 优先使用对应计费页的数据;不存在时回退合同段规模信息,再回退默认字典行。 - const investRows = resolveScaleRows(serviceId, investData, htData) + const investRows = resolveScaleRows( + serviceId, + investData, + htData, + consultCategoryFactorMap, + majorFactorMap + ) const investScale = sumByNumber(investRows, row => getInvestmentBudgetFee(row)) - const landRows = resolveScaleRows(serviceId, landData, htData) + const landRows = resolveScaleRows( + serviceId, + landData, + htData, + consultCategoryFactorMap, + majorFactorMap + ) const landScale = sumByNumber(landRows, row => getLandBudgetFee(row)) - const defaultWorkloadRows = buildDefaultWorkloadRows(serviceId) + const defaultWorkloadRows = buildDefaultWorkloadRows(serviceId, consultCategoryFactorMap) const workload = defaultWorkloadRows.length === 0 ? null : sumByNumber( workloadData?.detailRows != null - ? mergeWorkloadRows(serviceId, workloadData.detailRows as any) + ? mergeWorkloadRows(serviceId, workloadData.detailRows as any, consultCategoryFactorMap) : defaultWorkloadRows, row => calcWorkloadServiceFee(row) ) diff --git a/src/lib/pricingScaleFee.ts b/src/lib/pricingScaleFee.ts index 198376e..075b90d 100644 --- a/src/lib/pricingScaleFee.ts +++ b/src/lib/pricingScaleFee.ts @@ -43,28 +43,42 @@ export const getScaleBudgetFeeSplit = (params: { benchmarkBudgetOptional: unknown majorFactor: unknown consultCategoryFactor: unknown + workStageFactor?: unknown + workRatio?: unknown }): ScaleFeeSplitResult | null => { + const hasWorkStageFactor = Object.prototype.hasOwnProperty.call(params, 'workStageFactor') + const hasWorkRatio = Object.prototype.hasOwnProperty.call(params, 'workRatio') const benchmarkBudgetBasic = toFiniteNumberOrNull(params.benchmarkBudgetBasic) const benchmarkBudgetOptional = toFiniteNumberOrNull(params.benchmarkBudgetOptional) const majorFactor = toFiniteNumberOrNull(params.majorFactor) const consultCategoryFactor = toFiniteNumberOrNull(params.consultCategoryFactor) + const workStageFactor = hasWorkStageFactor ? toFiniteNumberOrNull(params.workStageFactor) : 1 + const workRatio = hasWorkRatio ? toFiniteNumberOrNull(params.workRatio) : 100 if ( benchmarkBudgetBasic == null || benchmarkBudgetOptional == null || majorFactor == null || - consultCategoryFactor == null + consultCategoryFactor == null || + workStageFactor == null || + workRatio == null ) { return null } - const basic = roundTo(toDecimal(benchmarkBudgetBasic).mul(majorFactor).mul(consultCategoryFactor), 2) - const optional = roundTo(toDecimal(benchmarkBudgetOptional).mul(majorFactor).mul(consultCategoryFactor), 2) + const multiplier = toDecimal(consultCategoryFactor) + .mul(majorFactor) + .mul(workStageFactor) + .mul(workRatio) + .div(100) + const roundedBenchmarkBudget = roundTo(addNumbers(benchmarkBudgetBasic, benchmarkBudgetOptional), 2) + const basic = roundTo(toDecimal(benchmarkBudgetBasic).mul(multiplier), 2) + const optional = roundTo(toDecimal(benchmarkBudgetOptional).mul(multiplier), 2) return { basic, optional, - total: roundTo(addNumbers(basic, optional), 2), + total: roundTo(toDecimal(roundedBenchmarkBudget).mul(multiplier), 2), basicFormula: '', optionalFormula: '' } @@ -74,14 +88,32 @@ export const getScaleBudgetFee = (params: { benchmarkBudget: unknown majorFactor: unknown consultCategoryFactor: unknown + workStageFactor?: unknown + workRatio?: unknown }) => { + const hasWorkStageFactor = Object.prototype.hasOwnProperty.call(params, 'workStageFactor') + const hasWorkRatio = Object.prototype.hasOwnProperty.call(params, 'workRatio') const benchmarkBudget = toFiniteNumberOrNull(params.benchmarkBudget) const majorFactor = toFiniteNumberOrNull(params.majorFactor) const consultCategoryFactor = toFiniteNumberOrNull(params.consultCategoryFactor) + const workStageFactor = hasWorkStageFactor ? toFiniteNumberOrNull(params.workStageFactor) : 1 + const workRatio = hasWorkRatio ? toFiniteNumberOrNull(params.workRatio) : 100 - if (benchmarkBudget == null || majorFactor == null || consultCategoryFactor == null) { + if ( + benchmarkBudget == null || + majorFactor == null || + consultCategoryFactor == null || + workStageFactor == null || + workRatio == null + ) { return null } - return roundTo(toDecimal(benchmarkBudget).mul(majorFactor).mul(consultCategoryFactor), 2) + const roundedBenchmarkBudget = roundTo(benchmarkBudget, 2) + const multiplier = toDecimal(consultCategoryFactor) + .mul(majorFactor) + .mul(workStageFactor) + .mul(workRatio) + .div(100) + return roundTo(toDecimal(roundedBenchmarkBudget).mul(multiplier), 2) } diff --git a/src/lib/xmFactorDefaults.ts b/src/lib/xmFactorDefaults.ts index c6d0b5a..452e687 100644 --- a/src/lib/xmFactorDefaults.ts +++ b/src/lib/xmFactorDefaults.ts @@ -57,8 +57,8 @@ const loadFactorMap = async ( return map } -export const loadConsultCategoryFactorMap = async () => - loadFactorMap(CONSULT_CATEGORY_FACTOR_KEY, getServiceDictById() as FactorDict) +export const loadConsultCategoryFactorMap = async (storageKey = CONSULT_CATEGORY_FACTOR_KEY) => + loadFactorMap(storageKey, getServiceDictById() as FactorDict) -export const loadMajorFactorMap = async () => - loadFactorMap(MAJOR_FACTOR_KEY, getMajorDictById() as FactorDict, getMajorIdAliasMap()) +export const loadMajorFactorMap = async (storageKey = MAJOR_FACTOR_KEY) => + loadFactorMap(storageKey, getMajorDictById() as FactorDict, getMajorIdAliasMap()) diff --git a/tsconfig.tsbuildinfo b/tsconfig.tsbuildinfo index 7c43f72..6ad81c6 100644 --- a/tsconfig.tsbuildinfo +++ b/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/main.ts","./src/sql.ts","./src/components/ui/button/index.ts","./src/components/ui/card/index.ts","./src/components/ui/scroll-area/index.ts","./src/components/ui/tooltip/index.ts","./src/lib/decimal.ts","./src/lib/diyaggridoptions.ts","./src/lib/number.ts","./src/lib/numberformat.ts","./src/lib/pricingmethodtotals.ts","./src/lib/pricingscalefee.ts","./src/lib/utils.ts","./src/lib/xmfactordefaults.ts","./src/lib/zwarchive.ts","./src/lib/zxfwpricingsync.ts","./src/pinia/pricingpanereload.ts","./src/pinia/tab.ts","./src/app.vue","./src/components/common/commonaggrid.vue","./src/components/common/methodunavailablenotice.vue","./src/components/common/xmfactorgrid.vue","./src/components/ui/button/button.vue","./src/components/ui/card/card.vue","./src/components/ui/card/cardaction.vue","./src/components/ui/card/cardcontent.vue","./src/components/ui/card/carddescription.vue","./src/components/ui/card/cardfooter.vue","./src/components/ui/card/cardheader.vue","./src/components/ui/card/cardtitle.vue","./src/components/ui/scroll-area/scrollarea.vue","./src/components/ui/scroll-area/scrollbar.vue","./src/components/ui/tooltip/tooltipcontent.vue","./src/components/views/contractdetailview.vue","./src/components/views/ht.vue","./src/components/views/servicecheckboxselector.vue","./src/components/views/xm.vue","./src/components/views/xmconsultcategoryfactor.vue","./src/components/views/xmmajorfactor.vue","./src/components/views/zxfwview.vue","./src/components/views/htinfo.vue","./src/components/views/info.vue","./src/components/views/xminfo.vue","./src/components/views/zxfw.vue","./src/components/views/pricingview/hourlypricingpane.vue","./src/components/views/pricingview/investmentscalepricingpane.vue","./src/components/views/pricingview/landscalepricingpane.vue","./src/components/views/pricingview/workloadpricingpane.vue","./src/layout/tab.vue","./src/layout/typeline.vue"],"version":"5.9.3"} \ No newline at end of file +{"root":["./src/main.ts","./src/sql.ts","./src/components/ui/button/index.ts","./src/components/ui/card/index.ts","./src/components/ui/scroll-area/index.ts","./src/components/ui/tooltip/index.ts","./src/lib/decimal.ts","./src/lib/diyaggridoptions.ts","./src/lib/number.ts","./src/lib/numberformat.ts","./src/lib/pricingmethodtotals.ts","./src/lib/pricingscalefee.ts","./src/lib/utils.ts","./src/lib/xmfactordefaults.ts","./src/lib/zwarchive.ts","./src/lib/zxfwpricingsync.ts","./src/pinia/pricingpanereload.ts","./src/pinia/tab.ts","./src/app.vue","./src/components/common/methodunavailablenotice.vue","./src/components/common/xmfactorgrid.vue","./src/components/common/xmcommonaggrid.vue","./src/components/ui/button/button.vue","./src/components/ui/card/card.vue","./src/components/ui/card/cardaction.vue","./src/components/ui/card/cardcontent.vue","./src/components/ui/card/carddescription.vue","./src/components/ui/card/cardfooter.vue","./src/components/ui/card/cardheader.vue","./src/components/ui/card/cardtitle.vue","./src/components/ui/scroll-area/scrollarea.vue","./src/components/ui/scroll-area/scrollbar.vue","./src/components/ui/tooltip/tooltipcontent.vue","./src/components/views/ht.vue","./src/components/views/htconsultcategoryfactor.vue","./src/components/views/htmajorfactor.vue","./src/components/views/servicecheckboxselector.vue","./src/components/views/xmconsultcategoryfactor.vue","./src/components/views/xmmajorfactor.vue","./src/components/views/zxfwview.vue","./src/components/views/htcard.vue","./src/components/views/htinfo.vue","./src/components/views/info.vue","./src/components/views/xmcard.vue","./src/components/views/xminfo.vue","./src/components/views/zxfw.vue","./src/components/views/pricingview/hourlypricingpane.vue","./src/components/views/pricingview/investmentscalepricingpane.vue","./src/components/views/pricingview/landscalepricingpane.vue","./src/components/views/pricingview/workloadpricingpane.vue","./src/layout/tab.vue","./src/layout/typeline.vue"],"version":"5.9.3"} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index 3b2e37b..fe84e69 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,7 +5,7 @@ import vue from '@vitejs/plugin-vue' export default defineConfig({ - plugins: [vue(),tailwindcss()], + plugins: [vue(), tailwindcss()], // 路径别名(和 tsconfig 保持一致) resolve: { alias: { @@ -20,7 +20,10 @@ export default defineConfig({ assetsDir: 'static', // 3. 生成的静态资源文件名是否包含哈希(用于缓存控制) assetsInlineLimit: 4096, // 小于4kb的资源内联,不生成文件 - rollupOptions: { + rolldownOptions: { + checks: { + pluginTimings: false + }, // 4. 自定义入口/出口(进阶,一般无需修改) output: { // 自定义 chunk 文件名(拆分公共代码) @@ -53,7 +56,7 @@ export default defineConfig({ } } }, - chunkSizeWarningLimit: 800, + chunkSizeWarningLimit: 1800, // 5. 生产环境是否生成 sourcemap(默认 false,关闭可减小包体积) sourcemap: false, // 6. 清空输出目录(默认 true)