fix
This commit is contained in:
1 parent
ad4e9cdee0
commit
043e1fc879
8 files changed
+529
-112
No files matched your search
@@ -3,7 +3,7 @@ import { computed, onActivated, onBeforeUnmount, onMounted, ref, watch } from 'v
|
||||
import { AgGridVue } from 'ag-grid-vue3'
|
||||
import type { ColDef, ColGroupDef } from 'ag-grid-community'
|
||||
import localforage from 'localforage'
|
||||
import { getMajorDictEntries, isMajorIdInIndustryScope } from '@/sql'
|
||||
import { getMajorDictEntries, getServiceDictItemById, isMajorIdInIndustryScope } from '@/sql'
|
||||
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
||||
import { decimalAggSum, roundTo, sumByNumber } from '@/lib/decimal'
|
||||
import { formatThousands } from '@/lib/numberFormat'
|
||||
@@ -78,6 +78,10 @@ interface XmBaseInfoState {
|
||||
projectIndustry?: string
|
||||
}
|
||||
|
||||
interface ServiceLite {
|
||||
onlyCostScale?: boolean | null
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
contractId: string,
|
||||
serviceId: string | number
|
||||
@@ -95,11 +99,16 @@ const consultCategoryFactorMap = ref<Map<string, number | null>>(new Map())
|
||||
const majorFactorMap = ref<Map<string, number | null>>(new Map())
|
||||
let factorDefaultsLoaded = false
|
||||
const paneInstanceCreatedAt = Date.now()
|
||||
const ONLY_COST_SCALE_ROW_ID = '__only-cost-scale-total__'
|
||||
|
||||
const getDefaultConsultCategoryFactor = () =>
|
||||
consultCategoryFactorMap.value.get(String(props.serviceId)) ?? null
|
||||
|
||||
const getDefaultMajorFactorById = (id: string): number | null => majorFactorMap.value.get(id) ?? null
|
||||
const isOnlyCostScaleService = computed(() => {
|
||||
const service = getServiceDictItemById(props.serviceId) as ServiceLite | undefined
|
||||
return service?.onlyCostScale === true
|
||||
})
|
||||
|
||||
const loadFactorDefaults = async () => {
|
||||
const [consultMap, majorMap] = await Promise.all([
|
||||
@@ -184,12 +193,17 @@ const detailDict: DictGroup[] = (() => {
|
||||
})
|
||||
}
|
||||
|
||||
const hasCost = item.hasCost !== false
|
||||
const hasArea = item.hasArea !== false
|
||||
// 特殊规则:投资规模法中,hasCost && hasArea 的专业不参与明细行
|
||||
if (hasCost && hasArea) continue
|
||||
|
||||
groupMap.get(parentCode)!.children.push({
|
||||
id: key,
|
||||
code,
|
||||
name: item.name,
|
||||
hasCost: item.hasCost !== false,
|
||||
hasArea: item.hasArea !== false
|
||||
hasCost,
|
||||
hasArea
|
||||
})
|
||||
}
|
||||
|
||||
@@ -241,6 +255,48 @@ const buildDefaultRows = (): DetailRow[] => {
|
||||
return rows
|
||||
}
|
||||
|
||||
const calcOnlyCostScaleAmountFromRows = (rows?: Array<{ amount?: unknown }>) =>
|
||||
sumByNumber(rows || [], row => (typeof row?.amount === 'number' ? row.amount : null))
|
||||
|
||||
const buildOnlyCostScaleRow = (
|
||||
amount: number | null,
|
||||
fromDb?: Partial<Pick<DetailRow, 'consultCategoryFactor' | 'majorFactor' | 'workStageFactor' | 'workRatio' | 'remark'>>
|
||||
): DetailRow => ({
|
||||
id: ONLY_COST_SCALE_ROW_ID,
|
||||
groupCode: 'TOTAL',
|
||||
groupName: '总投资',
|
||||
majorCode: 'TOTAL',
|
||||
majorName: '总投资',
|
||||
hasCost: true,
|
||||
hasArea: false,
|
||||
amount,
|
||||
benchmarkBudget: null,
|
||||
benchmarkBudgetBasic: null,
|
||||
benchmarkBudgetOptional: null,
|
||||
benchmarkBudgetBasicChecked: true,
|
||||
benchmarkBudgetOptionalChecked: true,
|
||||
basicFormula: '',
|
||||
optionalFormula: '',
|
||||
consultCategoryFactor:
|
||||
typeof fromDb?.consultCategoryFactor === 'number' ? fromDb.consultCategoryFactor : getDefaultConsultCategoryFactor(),
|
||||
majorFactor: typeof fromDb?.majorFactor === 'number' ? fromDb.majorFactor : 1,
|
||||
workStageFactor: typeof fromDb?.workStageFactor === 'number' ? fromDb.workStageFactor : 1,
|
||||
workRatio: typeof fromDb?.workRatio === 'number' ? fromDb.workRatio : 100,
|
||||
budgetFee: null,
|
||||
budgetFeeBasic: null,
|
||||
budgetFeeOptional: null,
|
||||
remark: typeof fromDb?.remark === 'string' ? fromDb.remark : '',
|
||||
path: [ONLY_COST_SCALE_ROW_ID]
|
||||
})
|
||||
|
||||
const buildOnlyCostScaleRows = (
|
||||
rowsFromDb?: Array<Partial<DetailRow> & Pick<DetailRow, 'id'>>
|
||||
): DetailRow[] => {
|
||||
const totalAmount = calcOnlyCostScaleAmountFromRows(rowsFromDb)
|
||||
const onlyRow = rowsFromDb?.find(row => String(row.id) === ONLY_COST_SCALE_ROW_ID)
|
||||
return [buildOnlyCostScaleRow(totalAmount, onlyRow)]
|
||||
}
|
||||
|
||||
type SourceRow = Pick<DetailRow, 'id'> &
|
||||
Partial<
|
||||
Pick<
|
||||
@@ -340,6 +396,11 @@ const formatMajorFactor = (params: any) => {
|
||||
}
|
||||
|
||||
const formatEditableMoney = (params: any) => {
|
||||
if (isOnlyCostScaleService.value) {
|
||||
if (!params.node?.rowPinned) return ''
|
||||
if (params.value == null || params.value === '') return '点击输入'
|
||||
return formatThousands(params.value)
|
||||
}
|
||||
if (!params.node?.group && !params.node?.rowPinned && !params.data?.hasCost) {
|
||||
return ''
|
||||
}
|
||||
@@ -432,14 +493,21 @@ const columnDefs: Array<ColDef<DetailRow> | ColGroupDef<DetailRow>> = [
|
||||
minWidth: 90,
|
||||
flex: 2,
|
||||
|
||||
editable: params => !params.node?.group && !params.node?.rowPinned && Boolean(params.data?.hasCost),
|
||||
editable: params => {
|
||||
if (isOnlyCostScaleService.value) return Boolean(params.node?.rowPinned)
|
||||
return !params.node?.group && !params.node?.rowPinned && Boolean(params.data?.hasCost)
|
||||
},
|
||||
cellClass: params =>
|
||||
!params.node?.group && !params.node?.rowPinned && params.data?.hasCost
|
||||
isOnlyCostScaleService.value && params.node?.rowPinned
|
||||
? 'ag-right-aligned-cell editable-cell-line'
|
||||
: !params.node?.group && !params.node?.rowPinned && params.data?.hasCost
|
||||
? 'ag-right-aligned-cell editable-cell-line'
|
||||
: 'ag-right-aligned-cell',
|
||||
cellClassRules: {
|
||||
'editable-cell-empty': params =>
|
||||
!params.node?.group && !params.node?.rowPinned && Boolean(params.data?.hasCost) && (params.value == null || params.value === '')
|
||||
isOnlyCostScaleService.value && params.node?.rowPinned
|
||||
? params.value == null || params.value === ''
|
||||
: !params.node?.group && !params.node?.rowPinned && Boolean(params.data?.hasCost) && (params.value == null || params.value === '')
|
||||
},
|
||||
aggFunc: decimalAggSum,
|
||||
valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }),
|
||||
@@ -613,13 +681,13 @@ const autoGroupColumnDef: ColDef = {
|
||||
},
|
||||
valueFormatter: params => {
|
||||
if (params.node?.rowPinned) {
|
||||
return '总合计'
|
||||
return isOnlyCostScaleService.value ? '总投资' : '总合计'
|
||||
}
|
||||
const nodeId = String(params.value || '')
|
||||
return idLabelMap.get(nodeId) || nodeId
|
||||
},
|
||||
tooltipValueGetter: params => {
|
||||
if (params.node?.rowPinned) return '总合计'
|
||||
if (params.node?.rowPinned) return isOnlyCostScaleService.value ? '总投资' : '总合计'
|
||||
const nodeId = String(params.value || '')
|
||||
return idLabelMap.get(nodeId) || nodeId
|
||||
}
|
||||
@@ -627,6 +695,7 @@ const autoGroupColumnDef: ColDef = {
|
||||
|
||||
|
||||
const totalAmount = computed(() => sumByNumber(detailRows.value, row => row.amount))
|
||||
const visibleDetailRows = computed(() => (isOnlyCostScaleService.value ? [] : detailRows.value))
|
||||
|
||||
const totalBenchmarkBudgetBasic = computed(() => sumByNumber(detailRows.value, row => getBenchmarkBudgetSplitByAmount(row)?.basic))
|
||||
const totalBenchmarkBudgetOptional = computed(() => sumByNumber(detailRows.value, row => getBenchmarkBudgetSplitByAmount(row)?.optional))
|
||||
@@ -727,13 +796,17 @@ const loadFromIndexedDB = async () => {
|
||||
const applyContractDefaultRows = async () => {
|
||||
const htData = await localforage.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value)
|
||||
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)
|
||||
}))
|
||||
if (isOnlyCostScaleService.value) {
|
||||
detailRows.value = hasContractRows ? buildOnlyCostScaleRows(htData!.detailRows as any) : buildOnlyCostScaleRows()
|
||||
} else {
|
||||
detailRows.value = hasContractRows
|
||||
? mergeWithDictRows(htData!.detailRows, { includeFactorValues: true })
|
||||
: buildDefaultRows().map(row => ({
|
||||
...row,
|
||||
consultCategoryFactor: getDefaultConsultCategoryFactor(),
|
||||
majorFactor: getDefaultMajorFactorById(row.id)
|
||||
}))
|
||||
}
|
||||
syncComputedValuesToDetailRows()
|
||||
}
|
||||
if (shouldForceDefaultLoad()) {
|
||||
@@ -743,7 +816,9 @@ const loadFromIndexedDB = async () => {
|
||||
|
||||
const data = await localforage.getItem<XmInfoState>(DB_KEY.value)
|
||||
if (data) {
|
||||
detailRows.value = mergeWithDictRows(data.detailRows)
|
||||
detailRows.value = isOnlyCostScaleService.value
|
||||
? buildOnlyCostScaleRows(data.detailRows as any)
|
||||
: mergeWithDictRows(data.detailRows)
|
||||
syncComputedValuesToDetailRows()
|
||||
return
|
||||
}
|
||||
@@ -751,7 +826,7 @@ const loadFromIndexedDB = async () => {
|
||||
await applyContractDefaultRows()
|
||||
} catch (error) {
|
||||
console.error('loadFromIndexedDB failed:', error)
|
||||
detailRows.value = buildDefaultRows()
|
||||
detailRows.value = isOnlyCostScaleService.value ? buildOnlyCostScaleRows() : buildDefaultRows()
|
||||
syncComputedValuesToDetailRows()
|
||||
}
|
||||
}
|
||||
@@ -766,13 +841,17 @@ const importContractData = async () => {
|
||||
await loadFactorDefaults()
|
||||
const htData = await localforage.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value)
|
||||
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)
|
||||
}))
|
||||
if (isOnlyCostScaleService.value) {
|
||||
detailRows.value = hasContractRows ? buildOnlyCostScaleRows(htData!.detailRows as any) : buildOnlyCostScaleRows()
|
||||
} else {
|
||||
detailRows.value = hasContractRows
|
||||
? mergeWithDictRows(htData!.detailRows, { includeFactorValues: true })
|
||||
: buildDefaultRows().map(row => ({
|
||||
...row,
|
||||
consultCategoryFactor: getDefaultConsultCategoryFactor(),
|
||||
majorFactor: getDefaultMajorFactorById(row.id)
|
||||
}))
|
||||
}
|
||||
await saveToIndexedDB()
|
||||
} catch (error) {
|
||||
console.error('importContractData failed:', error)
|
||||
@@ -781,7 +860,7 @@ const importContractData = async () => {
|
||||
|
||||
const clearAllData = async () => {
|
||||
try {
|
||||
detailRows.value = buildDefaultRows()
|
||||
detailRows.value = isOnlyCostScaleService.value ? buildOnlyCostScaleRows() : buildDefaultRows()
|
||||
await saveToIndexedDB()
|
||||
} catch (error) {
|
||||
console.error('clearAllData failed:', error)
|
||||
@@ -802,7 +881,20 @@ let persistTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
|
||||
let gridPersistTimer: ReturnType<typeof setTimeout> | null = null
|
||||
const handleCellValueChanged = () => {
|
||||
const applyOnlyCostScalePinnedAmount = (rawValue: unknown) => {
|
||||
const amount = parseNumberOrNull(rawValue, { precision: 2 })
|
||||
const current = detailRows.value[0]
|
||||
if (!current) {
|
||||
detailRows.value = [buildOnlyCostScaleRow(amount)]
|
||||
return
|
||||
}
|
||||
detailRows.value = [{ ...current, amount }]
|
||||
}
|
||||
|
||||
const handleCellValueChanged = (event?: any) => {
|
||||
if (isOnlyCostScaleService.value && event?.node?.rowPinned && event.colDef?.field === 'amount') {
|
||||
applyOnlyCostScalePinnedAmount(event.newValue)
|
||||
}
|
||||
syncComputedValuesToDetailRows()
|
||||
if (gridPersistTimer) clearTimeout(gridPersistTimer)
|
||||
gridPersistTimer = setTimeout(() => {
|
||||
@@ -899,7 +991,7 @@ const processCellFromClipboard = (params: any) => {
|
||||
</div>
|
||||
|
||||
<div class="ag-theme-quartz h-full min-h-0 w-full flex-1">
|
||||
<AgGridVue :style="{ height: '100%' }" :rowData="detailRows" :pinnedTopRowData="pinnedTopRowData"
|
||||
<AgGridVue :style="{ height: '100%' }" :rowData="visibleDetailRows" :pinnedTopRowData="pinnedTopRowData"
|
||||
:columnDefs="columnDefs" :autoGroupColumnDef="autoGroupColumnDef" :gridOptions="gridOptions" :theme="myTheme"
|
||||
@cell-value-changed="handleCellValueChanged" :suppressColumnVirtualisation="true"
|
||||
:suppressRowVirtualisation="true" :cellSelection="{ handle: { mode: 'range' } }" :enableClipboard="true"
|
||||
|
||||
Reference in new issue
Block a user