This commit is contained in:
wintsa 2026-03-10 17:16:45 +08:00
parent 5614e315b0
commit bbc8777b74
11 changed files with 197 additions and 158 deletions

View File

@ -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()
} }
) )

View File

@ -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()

View File

@ -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
} }
} }

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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 });
} }
}) })
); );

View File

@ -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>

View File

@ -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)
} }

View File

@ -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"}