fix
This commit is contained in:
parent
5614e315b0
commit
bbc8777b74
@ -610,13 +610,12 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => pricingPaneReloadStore.seq,
|
() => pricingPaneReloadStore.persistedSeq,
|
||||||
(nextVersion, prevVersion) => {
|
(nextVersion, prevVersion) => {
|
||||||
|
|
||||||
if (nextVersion === prevVersion || nextVersion === 0) return
|
if (nextVersion === prevVersion || nextVersion === 0) return
|
||||||
const contractId = String(props.contractId || '').trim()
|
const contractId = String(props.contractId || '').trim()
|
||||||
if (!contractId) return
|
if (!contractId) return
|
||||||
if (!matchPricingPaneReload(pricingPaneReloadStore.lastEvent, contractId, ZXFW_RELOAD_SERVICE_KEY)) return
|
if (!matchPricingPaneReload(pricingPaneReloadStore.lastPersistedEvent, contractId, ZXFW_RELOAD_SERVICE_KEY)) return
|
||||||
void loadFromIndexedDB()
|
void loadFromIndexedDB()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -117,6 +117,14 @@ const buildDefaultRows = (): FactorRow[] => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SourceRow = Pick<FactorRow, 'id'> & Partial<Pick<FactorRow, 'budgetValue' | 'remark'>>
|
type SourceRow = Pick<FactorRow, 'id'> & Partial<Pick<FactorRow, 'budgetValue' | 'remark'>>
|
||||||
|
const hasMeaningfulFactorValue = (rows: SourceRow[] | undefined) =>
|
||||||
|
Array.isArray(rows) &&
|
||||||
|
rows.some(row => {
|
||||||
|
const hasBudgetValue = typeof row?.budgetValue === 'number' && Number.isFinite(row.budgetValue)
|
||||||
|
const hasRemark = typeof row?.remark === 'string' && row.remark.trim() !== ''
|
||||||
|
return hasBudgetValue || hasRemark
|
||||||
|
})
|
||||||
|
|
||||||
const mergeWithDictRows = (rowsFromDb: SourceRow[] | undefined): FactorRow[] => {
|
const mergeWithDictRows = (rowsFromDb: SourceRow[] | undefined): FactorRow[] => {
|
||||||
const dbValueMap = new Map<string, SourceRow>()
|
const dbValueMap = new Map<string, SourceRow>()
|
||||||
for (const row of rowsFromDb || []) {
|
for (const row of rowsFromDb || []) {
|
||||||
@ -235,7 +243,7 @@ const loadGridState = async (storageKey: string): Promise<GridState | null> => {
|
|||||||
const loadFromIndexedDB = async () => {
|
const loadFromIndexedDB = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await loadGridState(props.storageKey)
|
const data = await loadGridState(props.storageKey)
|
||||||
if (data) {
|
if (data && hasMeaningfulFactorValue(data.detailRows)) {
|
||||||
detailRows.value = mergeWithDictRows(data.detailRows)
|
detailRows.value = mergeWithDictRows(data.detailRows)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -243,14 +251,20 @@ const loadFromIndexedDB = async () => {
|
|||||||
const parentStorageKey = props.parentStorageKey?.trim()
|
const parentStorageKey = props.parentStorageKey?.trim()
|
||||||
if (parentStorageKey) {
|
if (parentStorageKey) {
|
||||||
const parentData = await loadGridState(parentStorageKey)
|
const parentData = await loadGridState(parentStorageKey)
|
||||||
if (parentData) {
|
if (parentData && hasMeaningfulFactorValue(parentData.detailRows)) {
|
||||||
detailRows.value = mergeWithDictRows(parentData.detailRows)
|
detailRows.value = mergeWithDictRows(parentData.detailRows)
|
||||||
await saveToIndexedDB()
|
await saveToIndexedDB()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
detailRows.value = mergeWithDictRows(data.detailRows)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
detailRows.value = buildDefaultRows()
|
detailRows.value = buildDefaultRows()
|
||||||
|
await saveToIndexedDB()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('loadFromIndexedDB failed:', error)
|
console.error('loadFromIndexedDB failed:', error)
|
||||||
detailRows.value = buildDefaultRows()
|
detailRows.value = buildDefaultRows()
|
||||||
|
|||||||
@ -179,8 +179,26 @@ const loadFromIndexedDB = async (api: GridApi<DetailRow>) => {
|
|||||||
|
|
||||||
roughCalcEnabled.value = Boolean(contractData?.roughCalcEnabled)
|
roughCalcEnabled.value = Boolean(contractData?.roughCalcEnabled)
|
||||||
applyPinnedTotalAmount(api, contractData?.totalAmount)
|
applyPinnedTotalAmount(api, contractData?.totalAmount)
|
||||||
if (contractData?.detailRows) {
|
const contractRows = Array.isArray(contractData?.detailRows) ? contractData.detailRows : []
|
||||||
detailRows.value = mergeWithDictRows(contractData.detailRows)
|
const hasContractRows = contractRows.length > 0
|
||||||
|
const hasContractScaleValue = hasContractRows
|
||||||
|
? contractRows.some(row => {
|
||||||
|
const amount = row?.amount
|
||||||
|
const landArea = row?.landArea
|
||||||
|
return (
|
||||||
|
(typeof amount === 'number' && Number.isFinite(amount)) ||
|
||||||
|
(typeof landArea === 'number' && Number.isFinite(landArea))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
: false
|
||||||
|
const isLegacyEmptyScaleRows =
|
||||||
|
hasContractRows &&
|
||||||
|
!hasContractScaleValue &&
|
||||||
|
!roughCalcEnabled.value &&
|
||||||
|
typeof contractData?.totalAmount === 'number' &&
|
||||||
|
Number.isFinite(contractData.totalAmount)
|
||||||
|
if (hasContractRows && !isLegacyEmptyScaleRows) {
|
||||||
|
detailRows.value = mergeWithDictRows(contractRows)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,12 +208,8 @@ const loadFromIndexedDB = async (api: GridApi<DetailRow>) => {
|
|||||||
roughCalcEnabled.value = Boolean(xmData?.roughCalcEnabled)
|
roughCalcEnabled.value = Boolean(xmData?.roughCalcEnabled)
|
||||||
applyPinnedTotalAmount(api, xmData?.totalAmount)
|
applyPinnedTotalAmount(api, xmData?.totalAmount)
|
||||||
|
|
||||||
if (xmData?.detailRows) {
|
if (Array.isArray(xmData?.detailRows) && xmData.detailRows.length > 0) {
|
||||||
|
detailRows.value = mergeWithDictRows(xmData.detailRows)
|
||||||
detailRows.value = mergeWithDictRows(xmData.detailRows.map(e => ({...e,
|
|
||||||
amount: null,
|
|
||||||
landArea: null
|
|
||||||
})))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -60,5 +60,6 @@ onActivated(() => {
|
|||||||
:dict="filteredServiceDict"
|
:dict="filteredServiceDict"
|
||||||
:disable-budget-edit-when-standard-null="true"
|
:disable-budget-edit-when-standard-null="true"
|
||||||
:exclude-notshow-by-zxflxs="true"
|
:exclude-notshow-by-zxflxs="true"
|
||||||
|
:init-budget-value-from-standard="true"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -51,6 +51,7 @@ const formatAmount = (value: number | null) =>
|
|||||||
value == null ? '' : formatThousandsFlexible(value, 3)
|
value == null ? '' : formatThousandsFlexible(value, 3)
|
||||||
|
|
||||||
const loadBase = async () => {
|
const loadBase = async () => {
|
||||||
|
|
||||||
const contractId = String(props.contractId || '').trim()
|
const contractId = String(props.contractId || '').trim()
|
||||||
if (!contractId) {
|
if (!contractId) {
|
||||||
base.value = null
|
base.value = null
|
||||||
@ -112,14 +113,10 @@ const applyRateInput = () => {
|
|||||||
rateInput.value = next == null ? '' : String(next)
|
rateInput.value = next == null ? '' : String(next)
|
||||||
}
|
}
|
||||||
|
|
||||||
let saveTimer: ReturnType<typeof setTimeout> | null = null
|
|
||||||
let basePollTimer: ReturnType<typeof setInterval> | null = null
|
|
||||||
|
|
||||||
watch([rate, remark, budgetFee], () => {
|
watch([rate, remark, budgetFee], () => {
|
||||||
if (saveTimer) clearTimeout(saveTimer)
|
void saveForm()
|
||||||
saveTimer = setTimeout(() => {
|
|
||||||
void saveForm()
|
|
||||||
}, 250)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@ -130,41 +127,28 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => pricingPaneReloadStore.seq,
|
() => pricingPaneReloadStore.persistedSeq,
|
||||||
(nextVersion, prevVersion) => {
|
(nextVersion, prevVersion) => {
|
||||||
console.log(pricingPaneReloadStore.seq)
|
|
||||||
if (nextVersion === prevVersion || nextVersion === 0) return
|
if (nextVersion === prevVersion || nextVersion === 0) return
|
||||||
const contractId = String(props.contractId || '').trim()
|
const contractId = String(props.contractId || '').trim()
|
||||||
if (!contractId) return
|
if (!contractId) return
|
||||||
if (!matchPricingPaneReload(pricingPaneReloadStore.lastEvent, contractId, ZXFW_RELOAD_SERVICE_KEY)) return
|
if (!matchPricingPaneReload(pricingPaneReloadStore.lastPersistedEvent, contractId, ZXFW_RELOAD_SERVICE_KEY)) return
|
||||||
void loadBase()
|
void loadBase()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await Promise.all([loadBase(), loadForm()])
|
await Promise.all([loadBase(), loadForm()])
|
||||||
if (!basePollTimer) {
|
|
||||||
basePollTimer = setInterval(() => {
|
|
||||||
void loadBase()
|
|
||||||
}, 1000)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
onActivated(async () => {
|
onActivated(async () => {
|
||||||
await Promise.all([loadBase(), loadForm()])
|
await Promise.all([loadBase(), loadForm()])
|
||||||
if (!basePollTimer) {
|
|
||||||
basePollTimer = setInterval(() => {
|
|
||||||
void loadBase()
|
|
||||||
}, 1000)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
if (saveTimer) clearTimeout(saveTimer)
|
|
||||||
if (basePollTimer) {
|
|
||||||
clearInterval(basePollTimer)
|
|
||||||
basePollTimer = null
|
|
||||||
}
|
|
||||||
void saveForm()
|
void saveForm()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@ -175,48 +159,27 @@ onBeforeUnmount(() => {
|
|||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<label class="space-y-1.5">
|
<label class="space-y-1.5">
|
||||||
<div class="text-xs text-muted-foreground">基数(所有服务费预算合计)</div>
|
<div class="text-xs text-muted-foreground">基数(所有服务费预算合计)</div>
|
||||||
<input
|
<input type="text" :value="base" readonly disabled tabindex="-1"
|
||||||
type="text"
|
class="h-9 w-full cursor-not-allowed rounded-md border bg-muted/40 px-3 text-sm text-foreground select-none" />
|
||||||
:value="formatAmount(base)"
|
|
||||||
readonly
|
|
||||||
disabled
|
|
||||||
tabindex="-1"
|
|
||||||
class="h-9 w-full cursor-not-allowed rounded-md border bg-muted/40 px-3 text-sm text-foreground select-none"
|
|
||||||
/>
|
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="space-y-1.5">
|
<label class="space-y-1.5">
|
||||||
<div class="text-xs text-muted-foreground">费率(可编辑,三位小数)</div>
|
<div class="text-xs text-muted-foreground">费率(可编辑,三位小数)</div>
|
||||||
<input
|
<input v-model="rateInput" type="text" inputmode="decimal" placeholder="请输入费率,建议0.01 ~ 0.05"
|
||||||
v-model="rateInput"
|
|
||||||
type="text"
|
|
||||||
inputmode="decimal"
|
|
||||||
placeholder="请输入费率,建议0.01 ~ 0.05"
|
|
||||||
class="h-9 w-full rounded-md border bg-background px-3 text-sm text-foreground outline-none focus:ring-2 focus:ring-primary/30"
|
class="h-9 w-full rounded-md border bg-background px-3 text-sm text-foreground outline-none focus:ring-2 focus:ring-primary/30"
|
||||||
@blur="applyRateInput"
|
@blur="applyRateInput" />
|
||||||
/>
|
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="space-y-1.5">
|
<label class="space-y-1.5">
|
||||||
<div class="text-xs text-muted-foreground">预算费用(自动计算)</div>
|
<div class="text-xs text-muted-foreground">预算费用(自动计算)</div>
|
||||||
<input
|
<input type="text" :value="formatAmount(budgetFee)" readonly disabled tabindex="-1"
|
||||||
type="text"
|
class="h-9 w-full cursor-not-allowed rounded-md border bg-muted/40 px-3 text-sm text-foreground select-none" />
|
||||||
:value="formatAmount(budgetFee)"
|
|
||||||
readonly
|
|
||||||
disabled
|
|
||||||
tabindex="-1"
|
|
||||||
class="h-9 w-full cursor-not-allowed rounded-md border bg-muted/40 px-3 text-sm text-foreground select-none"
|
|
||||||
/>
|
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="space-y-1.5 md:col-span-2">
|
<label class="space-y-1.5 md:col-span-2">
|
||||||
<div class="text-xs text-muted-foreground">说明</div>
|
<div class="text-xs text-muted-foreground">说明</div>
|
||||||
<textarea
|
<textarea v-model="remark" rows="4" placeholder="请输入说明"
|
||||||
v-model="remark"
|
class="w-full rounded-md border bg-background px-3 py-2 text-sm text-foreground outline-none focus:ring-2 focus:ring-primary/30" />
|
||||||
rows="4"
|
|
||||||
placeholder="请输入说明"
|
|
||||||
class="w-full rounded-md border bg-background px-3 py-2 text-sm text-foreground outline-none focus:ring-2 focus:ring-primary/30"
|
|
||||||
/>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -60,5 +60,6 @@ onActivated(() => {
|
|||||||
:dict="filteredMajorDict"
|
:dict="filteredMajorDict"
|
||||||
:disable-budget-edit-when-standard-null="true"
|
:disable-budget-edit-when-standard-null="true"
|
||||||
:exclude-notshow-by-zxflxs="true"
|
:exclude-notshow-by-zxflxs="true"
|
||||||
|
:init-budget-value-from-standard="true"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -4,11 +4,19 @@ import HtFeeMethodGrid from '@/components/common/HtFeeMethodGrid.vue'
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
contractId: string
|
contractId: string
|
||||||
|
contractName?: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const STORAGE_KEY = computed(() => `htExtraFee-${props.contractId}-reserve`)
|
const STORAGE_KEY = computed(() => `htExtraFee-${props.contractId}-reserve`)
|
||||||
|
const reserveFeeNames = computed(() => ['预算费'])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<HtFeeMethodGrid title="预备费" :storageKey="STORAGE_KEY" :contract-id="props.contractId" />
|
<HtFeeMethodGrid
|
||||||
|
title="预备费"
|
||||||
|
:storageKey="STORAGE_KEY"
|
||||||
|
:contract-id="props.contractId"
|
||||||
|
:contract-name="props.contractName"
|
||||||
|
:fixed-names="reserveFeeNames"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -114,7 +114,7 @@ const reserveFeeView = markRaw(
|
|||||||
console.error('加载 HtReserveFee 组件失败:', err);
|
console.error('加载 HtReserveFee 组件失败:', err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return () => h(AsyncHtReserveFee, { contractId: props.contractId });
|
return () => h(AsyncHtReserveFee, { contractId: props.contractId, contractName: props.contractName });
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import type { ComponentPublicInstance, PropType } from 'vue'
|
|||||||
import { AgGridVue } from 'ag-grid-vue3'
|
import { AgGridVue } from 'ag-grid-vue3'
|
||||||
import type { ColDef, GridOptions, ICellRendererParams } from 'ag-grid-community'
|
import type { ColDef, GridOptions, ICellRendererParams } from 'ag-grid-community'
|
||||||
import localforage from 'localforage'
|
import localforage from 'localforage'
|
||||||
import { myTheme ,gridOptions} from '@/lib/diyAgGridOptions'
|
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
||||||
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale'
|
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale'
|
||||||
import { addNumbers } from '@/lib/decimal'
|
import { addNumbers } from '@/lib/decimal'
|
||||||
import { parseNumberOrNull } from '@/lib/number'
|
import { parseNumberOrNull } from '@/lib/number'
|
||||||
@ -32,7 +32,7 @@ import {
|
|||||||
} from 'reka-ui'
|
} from 'reka-ui'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { TooltipProvider } from '@/components/ui/tooltip'
|
import { TooltipProvider } from '@/components/ui/tooltip'
|
||||||
import { getServiceDictEntries, isIndustryEnabledByType,getIndustryTypeValue } from '@/sql'
|
import { getServiceDictEntries, isIndustryEnabledByType, getIndustryTypeValue } from '@/sql'
|
||||||
import { useTabStore } from '@/pinia/tab'
|
import { useTabStore } from '@/pinia/tab'
|
||||||
import ServiceCheckboxSelector from '@/components/views/ServiceCheckboxSelector.vue'
|
import ServiceCheckboxSelector from '@/components/views/ServiceCheckboxSelector.vue'
|
||||||
|
|
||||||
@ -121,7 +121,7 @@ const serviceDict = computed<ServiceItem[]>(() => {
|
|||||||
.map(({ id, item }) => ({ id, item: item as ServiceListItem }))
|
.map(({ id, item }) => ({ id, item: item as ServiceListItem }))
|
||||||
.filter(({ item }) => {
|
.filter(({ item }) => {
|
||||||
const itemCode = item?.code || item?.ref
|
const itemCode = item?.code || item?.ref
|
||||||
return Boolean(itemCode && item?.name) && item.defCoe !== null && isIndustryEnabledByType(item, getIndustryTypeValue(industry))
|
return Boolean(itemCode && item?.name) && item.defCoe !== null && isIndustryEnabledByType(item, getIndustryTypeValue(industry))
|
||||||
|
|
||||||
})
|
})
|
||||||
return filteredEntries.map(({ id, item }) => ({
|
return filteredEntries.map(({ id, item }) => ({
|
||||||
@ -413,12 +413,12 @@ const clearRowValues = async (row: DetailRow) => {
|
|||||||
item.id !== row.id
|
item.id !== row.id
|
||||||
? item
|
? item
|
||||||
: {
|
: {
|
||||||
...item,
|
...item,
|
||||||
investScale: sanitizedTotals.investScale,
|
investScale: sanitizedTotals.investScale,
|
||||||
landScale: sanitizedTotals.landScale,
|
landScale: sanitizedTotals.landScale,
|
||||||
workload: sanitizedTotals.workload,
|
workload: sanitizedTotals.workload,
|
||||||
hourly: sanitizedTotals.hourly
|
hourly: sanitizedTotals.hourly
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
const nextInvestScale = getMethodTotalFromRows(clearedRows, 'investScale')
|
const nextInvestScale = getMethodTotalFromRows(clearedRows, 'investScale')
|
||||||
const nextLandScale = getMethodTotalFromRows(clearedRows, 'landScale')
|
const nextLandScale = getMethodTotalFromRows(clearedRows, 'landScale')
|
||||||
@ -427,13 +427,13 @@ const clearRowValues = async (row: DetailRow) => {
|
|||||||
detailRows.value = clearedRows.map(item =>
|
detailRows.value = clearedRows.map(item =>
|
||||||
isFixedRow(item)
|
isFixedRow(item)
|
||||||
? {
|
? {
|
||||||
...item,
|
...item,
|
||||||
investScale: nextInvestScale,
|
investScale: nextInvestScale,
|
||||||
landScale: nextLandScale,
|
landScale: nextLandScale,
|
||||||
workload: nextWorkload,
|
workload: nextWorkload,
|
||||||
hourly: nextHourly,
|
hourly: nextHourly,
|
||||||
subtotal: addNumbers(nextInvestScale, nextLandScale, nextWorkload, nextHourly)
|
subtotal: addNumbers(nextInvestScale, nextLandScale, nextWorkload, nextHourly)
|
||||||
}
|
}
|
||||||
: item
|
: item
|
||||||
)
|
)
|
||||||
await saveToIndexedDB()
|
await saveToIndexedDB()
|
||||||
@ -653,13 +653,13 @@ const applyFixedRowTotals = (rows: DetailRow[]) => {
|
|||||||
return rows.map(row =>
|
return rows.map(row =>
|
||||||
isFixedRow(row)
|
isFixedRow(row)
|
||||||
? {
|
? {
|
||||||
...row,
|
...row,
|
||||||
investScale: nextInvestScale,
|
investScale: nextInvestScale,
|
||||||
landScale: nextLandScale,
|
landScale: nextLandScale,
|
||||||
workload: nextWorkload,
|
workload: nextWorkload,
|
||||||
hourly: nextHourly,
|
hourly: nextHourly,
|
||||||
subtotal: addNumbers(nextInvestScale, nextLandScale, nextWorkload, nextHourly)
|
subtotal: addNumbers(nextInvestScale, nextLandScale, nextWorkload, nextHourly)
|
||||||
}
|
}
|
||||||
: row
|
: row
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -918,24 +918,53 @@ const startDragSelect = (event: MouseEvent, code: string) => {
|
|||||||
window.addEventListener('mousemove', onDragSelectingMove)
|
window.addEventListener('mousemove', onDragSelectingMove)
|
||||||
window.addEventListener('mouseup', stopDragSelect)
|
window.addEventListener('mouseup', stopDragSelect)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDragHover = (_code: string) => {
|
const handleDragHover = (_code: string) => {
|
||||||
if (!dragSelecting.value || !dragMoved.value) return
|
if (!dragSelecting.value || !dragMoved.value) return
|
||||||
applyDragSelectionByRect()
|
applyDragSelectionByRect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const buildPersistDetailRows = () => {
|
||||||
|
const rows = detailRows.value.map(row => ({ ...row }))
|
||||||
|
const nextInvestScale = getMethodTotalFromRows(rows, 'investScale')
|
||||||
|
const nextLandScale = getMethodTotalFromRows(rows, 'landScale')
|
||||||
|
const nextWorkload = getMethodTotalFromRows(rows, 'workload')
|
||||||
|
const nextHourly = getMethodTotalFromRows(rows, 'hourly')
|
||||||
|
const fixedSubtotal = addNumbers(nextInvestScale, nextLandScale, nextWorkload, nextHourly)
|
||||||
|
|
||||||
|
return rows.map(row =>
|
||||||
|
isFixedRow(row)
|
||||||
|
? {
|
||||||
|
...row,
|
||||||
|
investScale: nextInvestScale,
|
||||||
|
landScale: nextLandScale,
|
||||||
|
workload: nextWorkload,
|
||||||
|
hourly: nextHourly,
|
||||||
|
subtotal: fixedSubtotal
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
...row,
|
||||||
|
subtotal: addNumbers(
|
||||||
|
valueOrZero(row.investScale),
|
||||||
|
valueOrZero(row.landScale),
|
||||||
|
valueOrZero(row.workload),
|
||||||
|
valueOrZero(row.hourly)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const saveToIndexedDB = async () => {
|
const saveToIndexedDB = async () => {
|
||||||
try {
|
try {
|
||||||
const payload: ZxFwState = {
|
const payload: ZxFwState = {
|
||||||
selectedIds: [...selectedIds.value],
|
selectedIds: [...selectedIds.value],
|
||||||
detailRows: JSON.parse(JSON.stringify(detailRows.value))
|
detailRows: JSON.parse(JSON.stringify(buildPersistDetailRows()))
|
||||||
}
|
}
|
||||||
await localforage.setItem(DB_KEY.value, payload)
|
await localforage.setItem(DB_KEY.value, payload)
|
||||||
|
pricingPaneReloadStore.emitPersisted(props.contractId, ZXFW_RELOAD_SERVICE_KEY)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('saveToIndexedDB failed:', error)
|
console.error('saveToIndexedDB failed:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadFromIndexedDB = async () => {
|
const loadFromIndexedDB = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await localforage.getItem<ZxFwState>(DB_KEY.value)
|
const data = await localforage.getItem<ZxFwState>(DB_KEY.value)
|
||||||
@ -1000,7 +1029,10 @@ watch(
|
|||||||
() => reloadSignal.value,
|
() => reloadSignal.value,
|
||||||
(nextVersion, prevVersion) => {
|
(nextVersion, prevVersion) => {
|
||||||
if (nextVersion === prevVersion || nextVersion === 0) return
|
if (nextVersion === prevVersion || nextVersion === 0) return
|
||||||
void loadFromIndexedDB()
|
void (async () => {
|
||||||
|
await loadFromIndexedDB()
|
||||||
|
await saveToIndexedDB()
|
||||||
|
})()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1043,69 +1075,66 @@ onBeforeUnmount(() => {
|
|||||||
<div class="h-full min-h-0 flex flex-col gap-2">
|
<div class="h-full min-h-0 flex flex-col gap-2">
|
||||||
<!-- 原“浏览框选择服务”实现已抽离并停用,改为直接复选框勾选 -->
|
<!-- 原“浏览框选择服务”实现已抽离并停用,改为直接复选框勾选 -->
|
||||||
<!-- <DialogRoot v-model:open="pickerOpen" @update:open="handlePickerOpenChange" /> -->
|
<!-- <DialogRoot v-model:open="pickerOpen" @update:open="handlePickerOpenChange" /> -->
|
||||||
<ServiceCheckboxSelector
|
<ServiceCheckboxSelector :services="serviceDict" :model-value="selectedIds"
|
||||||
:services="serviceDict"
|
@update:model-value="handleServiceSelectionChange" />
|
||||||
:model-value="selectedIds"
|
|
||||||
@update:model-value="handleServiceSelectionChange"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div
|
<div class="rounded-lg border bg-card xmMx flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||||
class="rounded-lg border bg-card xmMx flex min-h-0 flex-1 flex-col overflow-hidden"
|
<div class="flex items-center justify-between border-b px-3 py-2">
|
||||||
>
|
<h3 class="text-xs font-semibold text-foreground leading-none">
|
||||||
<div class="flex items-center justify-between border-b px-3 py-2">
|
咨询服务明细
|
||||||
<h3 class="text-xs font-semibold text-foreground leading-none">
|
</h3>
|
||||||
咨询服务明细
|
<div class="text-[11px] text-muted-foreground leading-none">按服务词典生成</div>
|
||||||
</h3>
|
</div>
|
||||||
<div class="text-[11px] text-muted-foreground leading-none">按服务词典生成</div>
|
|
||||||
|
<div class="ag-theme-quartz w-full flex-1 min-h-0">
|
||||||
|
<AgGridVue :style="{ height: '100%' }" :rowData="detailRows" :columnDefs="columnDefs"
|
||||||
|
:gridOptions="detailGridOptions" :theme="myTheme" @cell-value-changed="handleCellValueChanged"
|
||||||
|
:enableClipboard="true" :localeText="AG_GRID_LOCALE_CN" :tooltipShowDelay="500" :headerHeight="30"
|
||||||
|
:undoRedoCellEditing="true" :undoRedoCellEditingLimit="20" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ag-theme-quartz w-full flex-1 min-h-0" >
|
<AlertDialogRoot :open="clearConfirmOpen" @update:open="handleClearConfirmOpenChange">
|
||||||
<AgGridVue :style="{ height: '100%' }" :rowData="detailRows" :columnDefs="columnDefs" :gridOptions="detailGridOptions"
|
<AlertDialogPortal>
|
||||||
:theme="myTheme" @cell-value-changed="handleCellValueChanged" :enableClipboard="true"
|
<AlertDialogOverlay class="fixed inset-0 z-50 bg-black/45" />
|
||||||
:localeText="AG_GRID_LOCALE_CN" :tooltipShowDelay="500" :headerHeight="30" :undoRedoCellEditing="true"
|
<AlertDialogContent
|
||||||
:undoRedoCellEditingLimit="20" />
|
class="fixed left-1/2 top-1/2 z-[70] w-[92vw] max-w-md -translate-x-1/2 -translate-y-1/2 rounded-lg border bg-background p-5 shadow-xl">
|
||||||
</div>
|
<AlertDialogTitle class="text-base font-semibold">确认恢复默认数据</AlertDialogTitle>
|
||||||
</div>
|
<AlertDialogDescription class="mt-2 text-sm text-muted-foreground">
|
||||||
|
会使用合同卡片里面最新填写的规模信息以及系数,自动计算默认数据,覆盖“{{ pendingClearServiceName }}”当前数据,是否继续?
|
||||||
|
</AlertDialogDescription>
|
||||||
|
<div class="mt-4 flex items-center justify-end gap-2">
|
||||||
|
<AlertDialogCancel as-child>
|
||||||
|
<Button variant="outline">取消</Button>
|
||||||
|
</AlertDialogCancel>
|
||||||
|
<AlertDialogAction as-child>
|
||||||
|
<Button variant="destructive" @click="confirmClearRow">确认恢复</Button>
|
||||||
|
</AlertDialogAction>
|
||||||
|
</div>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialogPortal>
|
||||||
|
</AlertDialogRoot>
|
||||||
|
|
||||||
<AlertDialogRoot :open="clearConfirmOpen" @update:open="handleClearConfirmOpenChange">
|
<AlertDialogRoot :open="deleteConfirmOpen" @update:open="handleDeleteConfirmOpenChange">
|
||||||
<AlertDialogPortal>
|
<AlertDialogPortal>
|
||||||
<AlertDialogOverlay class="fixed inset-0 z-50 bg-black/45" />
|
<AlertDialogOverlay class="fixed inset-0 z-50 bg-black/45" />
|
||||||
<AlertDialogContent class="fixed left-1/2 top-1/2 z-[70] w-[92vw] max-w-md -translate-x-1/2 -translate-y-1/2 rounded-lg border bg-background p-5 shadow-xl">
|
<AlertDialogContent
|
||||||
<AlertDialogTitle class="text-base font-semibold">确认恢复默认数据</AlertDialogTitle>
|
class="fixed left-1/2 top-1/2 z-[70] w-[92vw] max-w-md -translate-x-1/2 -translate-y-1/2 rounded-lg border bg-background p-5 shadow-xl">
|
||||||
<AlertDialogDescription class="mt-2 text-sm text-muted-foreground">
|
<AlertDialogTitle class="text-base font-semibold">确认删除服务</AlertDialogTitle>
|
||||||
会使用合同卡片里面最新填写的规模信息以及系数,自动计算默认数据,覆盖“{{ pendingClearServiceName }}”当前数据,是否继续?
|
<AlertDialogDescription class="mt-2 text-sm text-muted-foreground">
|
||||||
</AlertDialogDescription>
|
将逻辑删除“{{ pendingDeleteServiceName }}”,已填写的数据不会清楚,重新勾选后会恢复,是否继续?
|
||||||
<div class="mt-4 flex items-center justify-end gap-2">
|
</AlertDialogDescription>
|
||||||
<AlertDialogCancel as-child>
|
<div class="mt-4 flex items-center justify-end gap-2">
|
||||||
<Button variant="outline">取消</Button>
|
<AlertDialogCancel as-child>
|
||||||
</AlertDialogCancel>
|
<Button variant="outline">取消</Button>
|
||||||
<AlertDialogAction as-child>
|
</AlertDialogCancel>
|
||||||
<Button variant="destructive" @click="confirmClearRow">确认恢复</Button>
|
<AlertDialogAction as-child>
|
||||||
</AlertDialogAction>
|
<Button variant="destructive" @click="confirmDeleteRow">确认删除</Button>
|
||||||
</div>
|
</AlertDialogAction>
|
||||||
</AlertDialogContent>
|
</div>
|
||||||
</AlertDialogPortal>
|
</AlertDialogContent>
|
||||||
</AlertDialogRoot>
|
</AlertDialogPortal>
|
||||||
|
</AlertDialogRoot>
|
||||||
<AlertDialogRoot :open="deleteConfirmOpen" @update:open="handleDeleteConfirmOpenChange">
|
|
||||||
<AlertDialogPortal>
|
|
||||||
<AlertDialogOverlay class="fixed inset-0 z-50 bg-black/45" />
|
|
||||||
<AlertDialogContent class="fixed left-1/2 top-1/2 z-[70] w-[92vw] max-w-md -translate-x-1/2 -translate-y-1/2 rounded-lg border bg-background p-5 shadow-xl">
|
|
||||||
<AlertDialogTitle class="text-base font-semibold">确认删除服务</AlertDialogTitle>
|
|
||||||
<AlertDialogDescription class="mt-2 text-sm text-muted-foreground">
|
|
||||||
将逻辑删除“{{ pendingDeleteServiceName }}”,已填写的数据不会清楚,重新勾选后会恢复,是否继续?
|
|
||||||
</AlertDialogDescription>
|
|
||||||
<div class="mt-4 flex items-center justify-end gap-2">
|
|
||||||
<AlertDialogCancel as-child>
|
|
||||||
<Button variant="outline">取消</Button>
|
|
||||||
</AlertDialogCancel>
|
|
||||||
<AlertDialogAction as-child>
|
|
||||||
<Button variant="destructive" @click="confirmDeleteRow">确认删除</Button>
|
|
||||||
</AlertDialogAction>
|
|
||||||
</div>
|
|
||||||
</AlertDialogContent>
|
|
||||||
</AlertDialogPortal>
|
|
||||||
</AlertDialogRoot>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
|||||||
@ -9,6 +9,8 @@ export interface PricingPaneReloadDetail {
|
|||||||
interface PricingPaneReloadState {
|
interface PricingPaneReloadState {
|
||||||
seq: number
|
seq: number
|
||||||
lastEvent: PricingPaneReloadDetail | null
|
lastEvent: PricingPaneReloadDetail | null
|
||||||
|
persistedSeq: number
|
||||||
|
lastPersistedEvent: PricingPaneReloadDetail | null
|
||||||
}
|
}
|
||||||
|
|
||||||
const toKey = (value: string | number) => String(value)
|
const toKey = (value: string | number) => String(value)
|
||||||
@ -16,7 +18,9 @@ const toKey = (value: string | number) => String(value)
|
|||||||
export const usePricingPaneReloadStore = defineStore('pricingPaneReload', {
|
export const usePricingPaneReloadStore = defineStore('pricingPaneReload', {
|
||||||
state: (): PricingPaneReloadState => ({
|
state: (): PricingPaneReloadState => ({
|
||||||
seq: 0,
|
seq: 0,
|
||||||
lastEvent: null
|
lastEvent: null,
|
||||||
|
persistedSeq: 0,
|
||||||
|
lastPersistedEvent: null
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
emit(contractId: string, serviceId: string | number) {
|
emit(contractId: string, serviceId: string | number) {
|
||||||
@ -26,7 +30,14 @@ export const usePricingPaneReloadStore = defineStore('pricingPaneReload', {
|
|||||||
at: Date.now()
|
at: Date.now()
|
||||||
}
|
}
|
||||||
this.seq += 1
|
this.seq += 1
|
||||||
console.log(this.seq)
|
},
|
||||||
|
emitPersisted(contractId: string, serviceId: string | number) {
|
||||||
|
this.lastPersistedEvent = {
|
||||||
|
contractId: toKey(contractId),
|
||||||
|
serviceId: toKey(serviceId),
|
||||||
|
at: Date.now()
|
||||||
|
}
|
||||||
|
this.persistedSeq += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -39,4 +50,3 @@ export const matchPricingPaneReload = (
|
|||||||
if (!detail) return false
|
if (!detail) return false
|
||||||
return detail.contractId === toKey(contractId) && detail.serviceId === toKey(serviceId)
|
return detail.contractId === toKey(contractId) && detail.serviceId === toKey(serviceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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/htfeegrid.vue","./src/components/common/htfeemethodgrid.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/htadditionalworkfee.vue","./src/components/views/htconsultcategoryfactor.vue","./src/components/views/htfeemethodtypelineview.vue","./src/components/views/htmajorfactor.vue","./src/components/views/htreservefee.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"}
|
{"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/htfeemethodreload.ts","./src/pinia/pricingpanereload.ts","./src/pinia/tab.ts","./src/app.vue","./src/components/common/hourlyfeegrid.vue","./src/components/common/htfeegrid.vue","./src/components/common/htfeemethodgrid.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/htadditionalworkfee.vue","./src/components/views/htconsultcategoryfactor.vue","./src/components/views/htfeemethodtypelineview.vue","./src/components/views/htfeeratemethodform.vue","./src/components/views/htmajorfactor.vue","./src/components/views/htreservefee.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"}
|
||||||
Loading…
x
Reference in New Issue
Block a user