fix,去掉大部分indexdb的逻辑
This commit is contained in:
parent
9a045cfe86
commit
3d26b0b259
@ -172,10 +172,10 @@ const hydrateRowsFromMethodStores = async (rows: FeeMethodRow[]): Promise<FeeMet
|
|||||||
const storedRateFee = toFiniteUnknown(rateData?.budgetFee)
|
const storedRateFee = toFiniteUnknown(rateData?.budgetFee)
|
||||||
const rateValue = toFiniteUnknown(rateData?.rate)
|
const rateValue = toFiniteUnknown(rateData?.rate)
|
||||||
const rateFee =
|
const rateFee =
|
||||||
storedRateFee != null
|
contractBase != null && rateValue != null
|
||||||
? round3(storedRateFee)
|
? round3(contractBase * rateValue)
|
||||||
: contractBase != null && rateValue != null
|
: storedRateFee != null
|
||||||
? round3(contractBase * rateValue)
|
? round3(storedRateFee)
|
||||||
: null
|
: null
|
||||||
const hourlyFee = sumHourlyMethodFee(hourlyData)
|
const hourlyFee = sumHourlyMethodFee(hourlyData)
|
||||||
const quantityUnitPriceFee = sumQuantityMethodFee(quantityData)
|
const quantityUnitPriceFee = sumQuantityMethodFee(quantityData)
|
||||||
|
|||||||
@ -2,10 +2,10 @@
|
|||||||
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||||
import { AgGridVue } from 'ag-grid-vue3'
|
import { AgGridVue } from 'ag-grid-vue3'
|
||||||
import type { ColDef, FirstDataRenderedEvent, GridApi, GridReadyEvent, GridSizeChangedEvent } from 'ag-grid-community'
|
import type { ColDef, FirstDataRenderedEvent, GridApi, GridReadyEvent, GridSizeChangedEvent } from 'ag-grid-community'
|
||||||
import localforage from 'localforage'
|
|
||||||
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
||||||
import { parseNumberOrNull } from '@/lib/number'
|
import { parseNumberOrNull } from '@/lib/number'
|
||||||
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale'
|
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale'
|
||||||
|
import { useKvStore } from '@/pinia/kv'
|
||||||
|
|
||||||
interface DictItem {
|
interface DictItem {
|
||||||
code: string
|
code: string
|
||||||
@ -42,6 +42,7 @@ const props = defineProps<{
|
|||||||
initBudgetValueFromStandard?: boolean
|
initBudgetValueFromStandard?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const kvStore = useKvStore()
|
||||||
const detailRows = ref<FactorRow[]>([])
|
const detailRows = ref<FactorRow[]>([])
|
||||||
const gridApi = ref<GridApi<FactorRow> | null>(null)
|
const gridApi = ref<GridApi<FactorRow> | null>(null)
|
||||||
|
|
||||||
@ -227,7 +228,7 @@ const saveToIndexedDB = async () => {
|
|||||||
const payload: GridState = {
|
const payload: GridState = {
|
||||||
detailRows: JSON.parse(JSON.stringify(detailRows.value))
|
detailRows: JSON.parse(JSON.stringify(detailRows.value))
|
||||||
}
|
}
|
||||||
await localforage.setItem(props.storageKey, payload)
|
await kvStore.setItem(props.storageKey, payload)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('saveToIndexedDB failed:', error)
|
console.error('saveToIndexedDB failed:', error)
|
||||||
}
|
}
|
||||||
@ -235,7 +236,7 @@ const saveToIndexedDB = async () => {
|
|||||||
|
|
||||||
const loadGridState = async (storageKey: string): Promise<GridState | null> => {
|
const loadGridState = async (storageKey: string): Promise<GridState | null> => {
|
||||||
if (!storageKey) return null
|
if (!storageKey) return null
|
||||||
const data = await localforage.getItem<GridState>(storageKey)
|
const data = await kvStore.getItem<GridState>(storageKey)
|
||||||
if (!data?.detailRows || !Array.isArray(data.detailRows)) return null
|
if (!data?.detailRows || !Array.isArray(data.detailRows)) return null
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,11 +4,11 @@ import { AgGridVue } from 'ag-grid-vue3'
|
|||||||
import type { CellValueChangedEvent, ColDef, GridApi, GridReadyEvent } from 'ag-grid-community'
|
import type { CellValueChangedEvent, ColDef, GridApi, GridReadyEvent } from 'ag-grid-community'
|
||||||
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale'
|
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale'
|
||||||
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
||||||
import localforage from 'localforage'
|
|
||||||
import { decimalAggSum, roundTo, sumByNumber } from '@/lib/decimal'
|
import { decimalAggSum, roundTo, sumByNumber } from '@/lib/decimal'
|
||||||
import { formatThousandsFlexible } from '@/lib/numberFormat'
|
import { formatThousandsFlexible } from '@/lib/numberFormat'
|
||||||
import { industryTypeList, getMajorDictEntries, isMajorIdInIndustryScope } from '@/sql'
|
import { industryTypeList, getMajorDictEntries, isMajorIdInIndustryScope } from '@/sql'
|
||||||
import { SwitchRoot, SwitchThumb } from 'reka-ui'
|
import { SwitchRoot, SwitchThumb } from 'reka-ui'
|
||||||
|
import { useKvStore } from '@/pinia/kv'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -42,6 +42,7 @@ interface XmBaseInfoState {
|
|||||||
|
|
||||||
const BASE_INFO_KEY = 'xm-base-info-v1'
|
const BASE_INFO_KEY = 'xm-base-info-v1'
|
||||||
type MajorLite = { code: string; name: string; hasCost?: boolean; hasArea?: boolean }
|
type MajorLite = { code: string; name: string; hasCost?: boolean; hasArea?: boolean }
|
||||||
|
const kvStore = useKvStore()
|
||||||
|
|
||||||
const detailRows = ref<DetailRow[]>([])
|
const detailRows = ref<DetailRow[]>([])
|
||||||
const detailDict = ref<DictGroup[]>([])
|
const detailDict = ref<DictGroup[]>([])
|
||||||
@ -157,8 +158,8 @@ const applyPinnedTotalAmount = (
|
|||||||
const loadFromIndexedDB = async (api: GridApi<DetailRow>) => {
|
const loadFromIndexedDB = async (api: GridApi<DetailRow>) => {
|
||||||
try {
|
try {
|
||||||
const [baseInfo, contractData] = await Promise.all([
|
const [baseInfo, contractData] = await Promise.all([
|
||||||
localforage.getItem<XmBaseInfoState>(BASE_INFO_KEY),
|
kvStore.getItem<XmBaseInfoState>(BASE_INFO_KEY),
|
||||||
localforage.getItem<XmScaleState>(props.dbKey)
|
kvStore.getItem<XmScaleState>(props.dbKey)
|
||||||
])
|
])
|
||||||
|
|
||||||
activeIndustryId.value =
|
activeIndustryId.value =
|
||||||
@ -204,7 +205,7 @@ const loadFromIndexedDB = async (api: GridApi<DetailRow>) => {
|
|||||||
|
|
||||||
if (props.xmInfoKey) {
|
if (props.xmInfoKey) {
|
||||||
// 首次创建合同段时,默认继承项目规模信息(同一套专业字典,按 id 对齐)
|
// 首次创建合同段时,默认继承项目规模信息(同一套专业字典,按 id 对齐)
|
||||||
const xmData = await localforage.getItem<XmScaleState>(props.xmInfoKey)
|
const xmData = await kvStore.getItem<XmScaleState>(props.xmInfoKey)
|
||||||
roughCalcEnabled.value = Boolean(xmData?.roughCalcEnabled)
|
roughCalcEnabled.value = Boolean(xmData?.roughCalcEnabled)
|
||||||
applyPinnedTotalAmount(api, xmData?.totalAmount)
|
applyPinnedTotalAmount(api, xmData?.totalAmount)
|
||||||
|
|
||||||
@ -405,7 +406,7 @@ const saveToIndexedDB = async () => {
|
|||||||
}
|
}
|
||||||
payload.roughCalcEnabled = roughCalcEnabled.value
|
payload.roughCalcEnabled = roughCalcEnabled.value
|
||||||
payload.totalAmount = pinnedTopRowData.value[0].amount
|
payload.totalAmount = pinnedTopRowData.value[0].amount
|
||||||
await localforage.setItem(props.dbKey, payload)
|
await kvStore.setItem(props.dbKey, payload)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('saveToIndexedDB failed:', error)
|
console.error('saveToIndexedDB failed:', error)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, nextTick, onActivated, onBeforeUnmount, onMounted, ref } from 'vue'
|
import { computed, nextTick, onActivated, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
import localforage from 'localforage'
|
|
||||||
import { Card, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||||
import { TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from '@/components/ui/tooltip'
|
import { TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from '@/components/ui/tooltip'
|
||||||
import { useTabStore } from '@/pinia/tab'
|
import { useTabStore } from '@/pinia/tab'
|
||||||
import { useZxFwPricingStore } from '@/pinia/zxFwPricing'
|
import { useZxFwPricingStore } from '@/pinia/zxFwPricing'
|
||||||
|
import { useKvStore } from '@/pinia/kv'
|
||||||
import { ArrowUp, Edit3, GripVertical, MoreHorizontal, Plus, Trash2, X } from 'lucide-vue-next'
|
import { ArrowUp, Edit3, GripVertical, MoreHorizontal, Plus, Trash2, X } from 'lucide-vue-next'
|
||||||
import { decodeZwArchive, encodeZwArchive } from '@/lib/zwArchive'
|
import { decodeZwArchive, encodeZwArchive } from '@/lib/zwArchive'
|
||||||
import { industryTypeList } from '@/sql'
|
import { industryTypeList } from '@/sql'
|
||||||
@ -73,6 +73,7 @@ const SERVICE_PRICING_METHODS = ['investScale', 'landScale', 'workload', 'hourly
|
|||||||
|
|
||||||
const tabStore = useTabStore()
|
const tabStore = useTabStore()
|
||||||
const zxFwPricingStore = useZxFwPricingStore()
|
const zxFwPricingStore = useZxFwPricingStore()
|
||||||
|
const kvStore = useKvStore()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -186,7 +187,7 @@ const closeContractDataMenu = () => {
|
|||||||
|
|
||||||
const loadProjectBaseState = async () => {
|
const loadProjectBaseState = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await localforage.getItem<XmBaseInfoState>(PROJECT_INFO_KEY)
|
const data = await kvStore.getItem<XmBaseInfoState>(PROJECT_INFO_KEY)
|
||||||
canManageContracts.value = Boolean(typeof data?.projectIndustry === 'string' && data.projectIndustry.trim())
|
canManageContracts.value = Boolean(typeof data?.projectIndustry === 'string' && data.projectIndustry.trim())
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('load project base state failed:', error)
|
console.error('load project base state failed:', error)
|
||||||
@ -195,7 +196,7 @@ const loadProjectBaseState = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getCurrentProjectIndustry = async (): Promise<string> => {
|
const getCurrentProjectIndustry = async (): Promise<string> => {
|
||||||
const data = await localforage.getItem<XmBaseInfoState>(PROJECT_INFO_KEY)
|
const data = await kvStore.getItem<XmBaseInfoState>(PROJECT_INFO_KEY)
|
||||||
return typeof data?.projectIndustry === 'string' ? data.projectIndustry.trim() : ''
|
return typeof data?.projectIndustry === 'string' ? data.projectIndustry.trim() : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,7 +355,7 @@ const getContractCardStyle = (index: number) => {
|
|||||||
const saveContracts = async () => {
|
const saveContracts = async () => {
|
||||||
try {
|
try {
|
||||||
contracts.value = normalizeOrder(contracts.value)
|
contracts.value = normalizeOrder(contracts.value)
|
||||||
await localforage.setItem(STORAGE_KEY, JSON.parse(JSON.stringify(contracts.value)))
|
await kvStore.setItem(STORAGE_KEY, JSON.parse(JSON.stringify(contracts.value)))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('save contracts failed:', error)
|
console.error('save contracts failed:', error)
|
||||||
}
|
}
|
||||||
@ -509,7 +510,7 @@ const isContractRelatedForageKey = (key: string, contractId: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const readContractRelatedForageEntries = async (contractIds: string[]) => {
|
const readContractRelatedForageEntries = async (contractIds: string[]) => {
|
||||||
const keys = await localforage.keys()
|
const keys = await kvStore.keys()
|
||||||
const idSet = new Set(contractIds)
|
const idSet = new Set(contractIds)
|
||||||
const targetKeys = keys.filter(key => {
|
const targetKeys = keys.filter(key => {
|
||||||
for (const id of idSet) {
|
for (const id of idSet) {
|
||||||
@ -520,7 +521,7 @@ const readContractRelatedForageEntries = async (contractIds: string[]) => {
|
|||||||
return Promise.all(
|
return Promise.all(
|
||||||
targetKeys.map(async key => ({
|
targetKeys.map(async key => ({
|
||||||
key,
|
key,
|
||||||
value: await localforage.getItem(key)
|
value: await kvStore.getItem(key)
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -669,7 +670,7 @@ const importContractSegments = async (event: Event) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
await Promise.all(rewrittenEntries.map(entry => localforage.setItem(entry.key, entry.value)))
|
await Promise.all(rewrittenEntries.map(entry => kvStore.setItem(entry.key, entry.value)))
|
||||||
await applyImportedContractPiniaPayload(payload.piniaState, oldToNewIdMap)
|
await applyImportedContractPiniaPayload(payload.piniaState, oldToNewIdMap)
|
||||||
|
|
||||||
contracts.value = [...contracts.value, ...nextContracts]
|
contracts.value = [...contracts.value, ...nextContracts]
|
||||||
@ -697,7 +698,7 @@ const importContractSegments = async (event: Event) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeForageKeysByContractId = async (store: typeof localforage, contractId: string) => {
|
const removeForageKeysByContractId = async (store: typeof kvStore, contractId: string) => {
|
||||||
try {
|
try {
|
||||||
const keys = await store.keys()
|
const keys = await store.keys()
|
||||||
const targetKeys = keys.filter(key => key.includes(contractId))
|
const targetKeys = keys.filter(key => key.includes(contractId))
|
||||||
@ -725,14 +726,16 @@ const removeRelatedTabsByContractId = (contractId: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cleanupContractRelatedData = async (contractId: string) => {
|
const cleanupContractRelatedData = async (contractId: string) => {
|
||||||
|
zxFwPricingStore.removeContractData(contractId)
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
removeForageKeysByContractId(localforage, contractId),
|
removeForageKeysByContractId(kvStore, contractId),
|
||||||
])
|
])
|
||||||
|
await zxFwPricingStore.$persistNow?.()
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadContracts = async () => {
|
const loadContracts = async () => {
|
||||||
try {
|
try {
|
||||||
const saved = await localforage.getItem<ContractItem[]>(STORAGE_KEY)
|
const saved = await kvStore.getItem<ContractItem[]>(STORAGE_KEY)
|
||||||
if (!saved || saved.length === 0) {
|
if (!saved || saved.length === 0) {
|
||||||
contracts.value = buildDefaultContracts()
|
contracts.value = buildDefaultContracts()
|
||||||
await saveContracts()
|
await saveContracts()
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onActivated, onMounted, ref } from 'vue'
|
import { computed, onActivated, onMounted, ref } from 'vue'
|
||||||
import localforage from 'localforage'
|
|
||||||
import { getServiceDictEntries, isIndustryEnabledByType, getIndustryTypeValue } from '@/sql'
|
import { getServiceDictEntries, isIndustryEnabledByType, getIndustryTypeValue } from '@/sql'
|
||||||
import XmFactorGrid from '@/components/common/XmFactorGrid.vue'
|
import XmFactorGrid from '@/components/common/XmFactorGrid.vue'
|
||||||
|
import { useKvStore } from '@/pinia/kv'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
contractId: string
|
contractId: string
|
||||||
@ -22,10 +22,11 @@ type ServiceItem = {
|
|||||||
|
|
||||||
const PROJECT_INFO_KEY = 'xm-base-info-v1'
|
const PROJECT_INFO_KEY = 'xm-base-info-v1'
|
||||||
const projectIndustry = ref('')
|
const projectIndustry = ref('')
|
||||||
|
const kvStore = useKvStore()
|
||||||
|
|
||||||
const loadProjectIndustry = async () => {
|
const loadProjectIndustry = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await localforage.getItem<XmBaseInfoState>(PROJECT_INFO_KEY)
|
const data = await kvStore.getItem<XmBaseInfoState>(PROJECT_INFO_KEY)
|
||||||
projectIndustry.value =
|
projectIndustry.value =
|
||||||
typeof data?.projectIndustry === 'string' ? data.projectIndustry.trim() : ''
|
typeof data?.projectIndustry === 'string' ? data.projectIndustry.trim() : ''
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onActivated, onMounted, ref } from 'vue'
|
import { computed, onActivated, onMounted, ref } from 'vue'
|
||||||
import localforage from 'localforage'
|
|
||||||
import { getMajorDictEntries, isMajorIdInIndustryScope } from '@/sql'
|
import { getMajorDictEntries, isMajorIdInIndustryScope } from '@/sql'
|
||||||
import XmFactorGrid from '@/components/common/XmFactorGrid.vue'
|
import XmFactorGrid from '@/components/common/XmFactorGrid.vue'
|
||||||
|
import { useKvStore } from '@/pinia/kv'
|
||||||
|
|
||||||
interface XmBaseInfoState {
|
interface XmBaseInfoState {
|
||||||
projectIndustry?: string
|
projectIndustry?: string
|
||||||
@ -22,10 +22,11 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const PROJECT_INFO_KEY = 'xm-base-info-v1'
|
const PROJECT_INFO_KEY = 'xm-base-info-v1'
|
||||||
const projectIndustry = ref('')
|
const projectIndustry = ref('')
|
||||||
|
const kvStore = useKvStore()
|
||||||
|
|
||||||
const loadProjectIndustry = async () => {
|
const loadProjectIndustry = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await localforage.getItem<XmBaseInfoState>(PROJECT_INFO_KEY)
|
const data = await kvStore.getItem<XmBaseInfoState>(PROJECT_INFO_KEY)
|
||||||
projectIndustry.value =
|
projectIndustry.value =
|
||||||
typeof data?.projectIndustry === 'string' ? data.projectIndustry.trim() : ''
|
typeof data?.projectIndustry === 'string' ? data.projectIndustry.trim() : ''
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onActivated, onMounted, ref } from 'vue'
|
import { computed, onActivated, onMounted, ref } from 'vue'
|
||||||
import localforage from 'localforage'
|
|
||||||
import { getServiceDictEntries, isIndustryEnabledByType, getIndustryTypeValue } from '@/sql'
|
import { getServiceDictEntries, isIndustryEnabledByType, getIndustryTypeValue } from '@/sql'
|
||||||
import XmFactorGrid from '@/components/common/XmFactorGrid.vue'
|
import XmFactorGrid from '@/components/common/XmFactorGrid.vue'
|
||||||
|
import { useKvStore } from '@/pinia/kv'
|
||||||
|
|
||||||
interface XmBaseInfoState {
|
interface XmBaseInfoState {
|
||||||
projectIndustry?: string
|
projectIndustry?: string
|
||||||
@ -18,10 +18,11 @@ type ServiceItem = {
|
|||||||
|
|
||||||
const PROJECT_INFO_KEY = 'xm-base-info-v1'
|
const PROJECT_INFO_KEY = 'xm-base-info-v1'
|
||||||
const projectIndustry = ref('')
|
const projectIndustry = ref('')
|
||||||
|
const kvStore = useKvStore()
|
||||||
|
|
||||||
const loadProjectIndustry = async () => {
|
const loadProjectIndustry = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await localforage.getItem<XmBaseInfoState>(PROJECT_INFO_KEY)
|
const data = await kvStore.getItem<XmBaseInfoState>(PROJECT_INFO_KEY)
|
||||||
projectIndustry.value =
|
projectIndustry.value =
|
||||||
typeof data?.projectIndustry === 'string' ? data.projectIndustry.trim() : ''
|
typeof data?.projectIndustry === 'string' ? data.projectIndustry.trim() : ''
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onActivated, onMounted, ref } from 'vue'
|
import { computed, onActivated, onMounted, ref } from 'vue'
|
||||||
import localforage from 'localforage'
|
|
||||||
import { getMajorDictEntries, isMajorIdInIndustryScope } from '@/sql'
|
import { getMajorDictEntries, isMajorIdInIndustryScope } from '@/sql'
|
||||||
import XmFactorGrid from '@/components/common/XmFactorGrid.vue'
|
import XmFactorGrid from '@/components/common/XmFactorGrid.vue'
|
||||||
import MethodUnavailableNotice from '@/components/common/MethodUnavailableNotice.vue'
|
import MethodUnavailableNotice from '@/components/common/MethodUnavailableNotice.vue'
|
||||||
|
import { useKvStore } from '@/pinia/kv'
|
||||||
|
|
||||||
interface XmBaseInfoState {
|
interface XmBaseInfoState {
|
||||||
projectIndustry?: string
|
projectIndustry?: string
|
||||||
@ -20,10 +20,11 @@ type MajorItem = {
|
|||||||
const PROJECT_INFO_KEY = 'xm-base-info-v1'
|
const PROJECT_INFO_KEY = 'xm-base-info-v1'
|
||||||
const projectIndustry = ref('')
|
const projectIndustry = ref('')
|
||||||
const hasProjectBaseInfo = ref(false)
|
const hasProjectBaseInfo = ref(false)
|
||||||
|
const kvStore = useKvStore()
|
||||||
|
|
||||||
const loadProjectIndustry = async () => {
|
const loadProjectIndustry = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await localforage.getItem<XmBaseInfoState>(PROJECT_INFO_KEY)
|
const data = await kvStore.getItem<XmBaseInfoState>(PROJECT_INFO_KEY)
|
||||||
hasProjectBaseInfo.value = Boolean(data)
|
hasProjectBaseInfo.value = Boolean(data)
|
||||||
projectIndustry.value =
|
projectIndustry.value =
|
||||||
typeof data?.projectIndustry === 'string' ? data.projectIndustry.trim() : ''
|
typeof data?.projectIndustry === 'string' ? data.projectIndustry.trim() : ''
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onActivated, onMounted, ref } from 'vue'
|
import { computed } from 'vue'
|
||||||
import localforage from 'localforage'
|
|
||||||
import { getMajorDictEntries, isMajorIdInIndustryScope } from '@/sql'
|
|
||||||
import CommonAgGrid from '@/components/common/xmCommonAgGrid.vue'
|
import CommonAgGrid from '@/components/common/xmCommonAgGrid.vue'
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { parseDate } from '@internationalized/date'
|
import { parseDate } from '@internationalized/date'
|
||||||
import { onMounted, ref, watch } from 'vue'
|
import { onMounted, ref, watch } from 'vue'
|
||||||
import localforage from 'localforage'
|
|
||||||
import { industryTypeList } from '@/sql'
|
import { industryTypeList } from '@/sql'
|
||||||
|
import { useKvStore } from '@/pinia/kv'
|
||||||
import { Calendar as CalendarIcon, CircleHelp } from 'lucide-vue-next'
|
import { Calendar as CalendarIcon, CircleHelp } from 'lucide-vue-next'
|
||||||
import { TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from '@/components/ui/tooltip'
|
import { TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from '@/components/ui/tooltip'
|
||||||
import {
|
import {
|
||||||
@ -99,6 +99,7 @@ const majorParentNodes: MajorParentNode[] = industryTypeList.map(item => ({
|
|||||||
}))
|
}))
|
||||||
const majorParentCodeSet = new Set(majorParentNodes.map(item => item.id))
|
const majorParentCodeSet = new Set(majorParentNodes.map(item => item.id))
|
||||||
const DEFAULT_PROJECT_INDUSTRY = majorParentNodes[0]?.id || ''
|
const DEFAULT_PROJECT_INDUSTRY = majorParentNodes[0]?.id || ''
|
||||||
|
const kvStore = useKvStore()
|
||||||
|
|
||||||
const saveToIndexedDB = async () => {
|
const saveToIndexedDB = async () => {
|
||||||
try {
|
try {
|
||||||
@ -110,7 +111,7 @@ const saveToIndexedDB = async () => {
|
|||||||
preparedCompany: preparedCompany.value,
|
preparedCompany: preparedCompany.value,
|
||||||
preparedDate: preparedDate.value
|
preparedDate: preparedDate.value
|
||||||
}
|
}
|
||||||
await localforage.setItem(DB_KEY, payload)
|
await kvStore.setItem(DB_KEY, payload)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('saveToIndexedDB failed:', error)
|
console.error('saveToIndexedDB failed:', error)
|
||||||
}
|
}
|
||||||
@ -118,7 +119,7 @@ const saveToIndexedDB = async () => {
|
|||||||
|
|
||||||
const loadFromIndexedDB = async () => {
|
const loadFromIndexedDB = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await localforage.getItem<XmInfoState>(DB_KEY)
|
const data = await kvStore.getItem<XmInfoState>(DB_KEY)
|
||||||
if (data) {
|
if (data) {
|
||||||
isProjectInitialized.value = true
|
isProjectInitialized.value = true
|
||||||
projectIndustry.value =
|
projectIndustry.value =
|
||||||
|
|||||||
@ -2,13 +2,13 @@
|
|||||||
import { computed, onActivated, onBeforeUnmount, onMounted, ref } from 'vue'
|
import { computed, onActivated, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||||
import { AgGridVue } from 'ag-grid-vue3'
|
import { AgGridVue } from 'ag-grid-vue3'
|
||||||
import type { ColDef, ColGroupDef } from 'ag-grid-community'
|
import type { ColDef, ColGroupDef } from 'ag-grid-community'
|
||||||
import localforage from 'localforage'
|
|
||||||
import { getMajorDictEntries, getServiceDictItemById, industryTypeList, isMajorIdInIndustryScope } from '@/sql'
|
import { getMajorDictEntries, getServiceDictItemById, industryTypeList, isMajorIdInIndustryScope } from '@/sql'
|
||||||
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
||||||
import { addNumbers, decimalAggSum, roundTo, sumByNumber } from '@/lib/decimal'
|
import { addNumbers, decimalAggSum, roundTo, sumByNumber } from '@/lib/decimal'
|
||||||
import { formatThousandsFlexible } from '@/lib/numberFormat'
|
import { formatThousandsFlexible } from '@/lib/numberFormat'
|
||||||
import { syncPricingTotalToZxFw } from '@/lib/zxFwPricingSync'
|
import { syncPricingTotalToZxFw } from '@/lib/zxFwPricingSync'
|
||||||
import { useZxFwPricingStore } from '@/pinia/zxFwPricing'
|
import { useZxFwPricingStore } from '@/pinia/zxFwPricing'
|
||||||
|
import { useKvStore } from '@/pinia/kv'
|
||||||
import { loadConsultCategoryFactorMap, loadMajorFactorMap } from '@/lib/xmFactorDefaults'
|
import { loadConsultCategoryFactorMap, loadMajorFactorMap } from '@/lib/xmFactorDefaults'
|
||||||
import { parseNumberOrNull } from '@/lib/number'
|
import { parseNumberOrNull } from '@/lib/number'
|
||||||
import { getBenchmarkBudgetSplitByScale, getScaleBudgetFeeSplit } from '@/lib/pricingScaleFee'
|
import { getBenchmarkBudgetSplitByScale, getScaleBudgetFeeSplit } from '@/lib/pricingScaleFee'
|
||||||
@ -94,6 +94,7 @@ const props = defineProps<{
|
|||||||
serviceId: string | number
|
serviceId: string | number
|
||||||
}>()
|
}>()
|
||||||
const zxFwPricingStore = useZxFwPricingStore()
|
const zxFwPricingStore = useZxFwPricingStore()
|
||||||
|
const kvStore = useKvStore()
|
||||||
const DB_KEY = computed(() => `tzGMF-${props.contractId}-${props.serviceId}`)
|
const DB_KEY = computed(() => `tzGMF-${props.contractId}-${props.serviceId}`)
|
||||||
const HT_DB_KEY = computed(() => `ht-info-v3-${props.contractId}`)
|
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_CONSULT_FACTOR_KEY = computed(() => `ht-consult-category-factor-v1-${props.contractId}`)
|
||||||
@ -1058,7 +1059,7 @@ const buildRowsFromImportDefaultSource = async (
|
|||||||
): Promise<DetailRow[]> => {
|
): Promise<DetailRow[]> => {
|
||||||
// 与“使用默认数据”同源:先强制刷新系数,再按合同卡片默认带出。
|
// 与“使用默认数据”同源:先强制刷新系数,再按合同卡片默认带出。
|
||||||
await loadFactorDefaults()
|
await loadFactorDefaults()
|
||||||
const htData = await localforage.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value)
|
const htData = await kvStore.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value)
|
||||||
const hasContractRows = Array.isArray(htData?.detailRows) && htData.detailRows.length > 0
|
const hasContractRows = Array.isArray(htData?.detailRows) && htData.detailRows.length > 0
|
||||||
if (isOnlyCostScaleService.value) {
|
if (isOnlyCostScaleService.value) {
|
||||||
return hasContractRows
|
return hasContractRows
|
||||||
@ -1129,14 +1130,14 @@ const applyProjectCountChange = async (nextValue: unknown) => {
|
|||||||
|
|
||||||
const loadFromIndexedDB = async () => {
|
const loadFromIndexedDB = async () => {
|
||||||
try {
|
try {
|
||||||
const baseInfo = await localforage.getItem<XmBaseInfoState>(BASE_INFO_KEY)
|
const baseInfo = await kvStore.getItem<XmBaseInfoState>(BASE_INFO_KEY)
|
||||||
activeIndustryCode.value =
|
activeIndustryCode.value =
|
||||||
typeof baseInfo?.projectIndustry === 'string' ? baseInfo.projectIndustry.trim() : ''
|
typeof baseInfo?.projectIndustry === 'string' ? baseInfo.projectIndustry.trim() : ''
|
||||||
projectCount.value = 1
|
projectCount.value = 1
|
||||||
|
|
||||||
await ensureFactorDefaultsLoaded()
|
await ensureFactorDefaultsLoaded()
|
||||||
const applyContractDefaultRows = async () => {
|
const applyContractDefaultRows = async () => {
|
||||||
const htData = await localforage.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value)
|
const htData = await kvStore.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value)
|
||||||
const hasContractRows = Array.isArray(htData?.detailRows) && htData.detailRows.length > 0
|
const hasContractRows = Array.isArray(htData?.detailRows) && htData.detailRows.length > 0
|
||||||
const targetProjectCount = getTargetProjectCount()
|
const targetProjectCount = getTargetProjectCount()
|
||||||
if (isOnlyCostScaleService.value) {
|
if (isOnlyCostScaleService.value) {
|
||||||
@ -1194,13 +1195,13 @@ const loadFromIndexedDB = async () => {
|
|||||||
|
|
||||||
const importContractData = async () => {
|
const importContractData = async () => {
|
||||||
try {
|
try {
|
||||||
const baseInfo = await localforage.getItem<XmBaseInfoState>(BASE_INFO_KEY)
|
const baseInfo = await kvStore.getItem<XmBaseInfoState>(BASE_INFO_KEY)
|
||||||
activeIndustryCode.value =
|
activeIndustryCode.value =
|
||||||
typeof baseInfo?.projectIndustry === 'string' ? baseInfo.projectIndustry.trim() : ''
|
typeof baseInfo?.projectIndustry === 'string' ? baseInfo.projectIndustry.trim() : ''
|
||||||
|
|
||||||
// 使用默认数据时,强制读取最新的项目系数(预算取值优先,空值回退标准系数)
|
// 使用默认数据时,强制读取最新的项目系数(预算取值优先,空值回退标准系数)
|
||||||
await loadFactorDefaults()
|
await loadFactorDefaults()
|
||||||
const htData = await localforage.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value)
|
const htData = await kvStore.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value)
|
||||||
const hasContractRows = Array.isArray(htData?.detailRows) && htData.detailRows.length > 0
|
const hasContractRows = Array.isArray(htData?.detailRows) && htData.detailRows.length > 0
|
||||||
const targetProjectCount = getTargetProjectCount()
|
const targetProjectCount = getTargetProjectCount()
|
||||||
if (isOnlyCostScaleService.value) {
|
if (isOnlyCostScaleService.value) {
|
||||||
|
|||||||
@ -2,13 +2,13 @@
|
|||||||
import { computed, onActivated, onBeforeUnmount, onMounted, ref } from 'vue'
|
import { computed, onActivated, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||||
import { AgGridVue } from 'ag-grid-vue3'
|
import { AgGridVue } from 'ag-grid-vue3'
|
||||||
import type { ColDef, ColGroupDef } from 'ag-grid-community'
|
import type { ColDef, ColGroupDef } from 'ag-grid-community'
|
||||||
import localforage from 'localforage'
|
|
||||||
import { getMajorDictEntries, getServiceDictItemById, industryTypeList, isMajorIdInIndustryScope } from '@/sql'
|
import { getMajorDictEntries, getServiceDictItemById, industryTypeList, isMajorIdInIndustryScope } from '@/sql'
|
||||||
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
||||||
import { addNumbers, decimalAggSum, roundTo, sumByNumber } from '@/lib/decimal'
|
import { addNumbers, decimalAggSum, roundTo, sumByNumber } from '@/lib/decimal'
|
||||||
import { formatThousandsFlexible } from '@/lib/numberFormat'
|
import { formatThousandsFlexible } from '@/lib/numberFormat'
|
||||||
import { syncPricingTotalToZxFw } from '@/lib/zxFwPricingSync'
|
import { syncPricingTotalToZxFw } from '@/lib/zxFwPricingSync'
|
||||||
import { useZxFwPricingStore } from '@/pinia/zxFwPricing'
|
import { useZxFwPricingStore } from '@/pinia/zxFwPricing'
|
||||||
|
import { useKvStore } from '@/pinia/kv'
|
||||||
import { loadConsultCategoryFactorMap, loadMajorFactorMap } from '@/lib/xmFactorDefaults'
|
import { loadConsultCategoryFactorMap, loadMajorFactorMap } from '@/lib/xmFactorDefaults'
|
||||||
import { parseNumberOrNull } from '@/lib/number'
|
import { parseNumberOrNull } from '@/lib/number'
|
||||||
import { getBenchmarkBudgetSplitByScale, getScaleBudgetFeeSplit } from '@/lib/pricingScaleFee'
|
import { getBenchmarkBudgetSplitByScale, getScaleBudgetFeeSplit } from '@/lib/pricingScaleFee'
|
||||||
@ -94,6 +94,7 @@ const props = defineProps<{
|
|||||||
serviceId: string | number
|
serviceId: string | number
|
||||||
}>()
|
}>()
|
||||||
const zxFwPricingStore = useZxFwPricingStore()
|
const zxFwPricingStore = useZxFwPricingStore()
|
||||||
|
const kvStore = useKvStore()
|
||||||
const DB_KEY = computed(() => `ydGMF-${props.contractId}-${props.serviceId}`)
|
const DB_KEY = computed(() => `ydGMF-${props.contractId}-${props.serviceId}`)
|
||||||
const HT_DB_KEY = computed(() => `ht-info-v3-${props.contractId}`)
|
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_CONSULT_FACTOR_KEY = computed(() => `ht-consult-category-factor-v1-${props.contractId}`)
|
||||||
@ -911,7 +912,7 @@ const buildRowsFromImportDefaultSource = async (
|
|||||||
): Promise<DetailRow[]> => {
|
): Promise<DetailRow[]> => {
|
||||||
// 与“使用默认数据”同源:先强制刷新系数,再按合同卡片默认带出。
|
// 与“使用默认数据”同源:先强制刷新系数,再按合同卡片默认带出。
|
||||||
await loadFactorDefaults()
|
await loadFactorDefaults()
|
||||||
const htData = await localforage.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value)
|
const htData = await kvStore.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value)
|
||||||
const hasContractRows = Array.isArray(htData?.detailRows) && htData.detailRows.length > 0
|
const hasContractRows = Array.isArray(htData?.detailRows) && htData.detailRows.length > 0
|
||||||
return hasContractRows
|
return hasContractRows
|
||||||
? mergeWithDictRows(htData!.detailRows, {
|
? mergeWithDictRows(htData!.detailRows, {
|
||||||
@ -975,14 +976,14 @@ const applyProjectCountChange = async (nextValue: unknown) => {
|
|||||||
|
|
||||||
const loadFromIndexedDB = async () => {
|
const loadFromIndexedDB = async () => {
|
||||||
try {
|
try {
|
||||||
const baseInfo = await localforage.getItem<XmBaseInfoState>(BASE_INFO_KEY)
|
const baseInfo = await kvStore.getItem<XmBaseInfoState>(BASE_INFO_KEY)
|
||||||
activeIndustryCode.value =
|
activeIndustryCode.value =
|
||||||
typeof baseInfo?.projectIndustry === 'string' ? baseInfo.projectIndustry.trim() : ''
|
typeof baseInfo?.projectIndustry === 'string' ? baseInfo.projectIndustry.trim() : ''
|
||||||
projectCount.value = 1
|
projectCount.value = 1
|
||||||
|
|
||||||
await ensureFactorDefaultsLoaded()
|
await ensureFactorDefaultsLoaded()
|
||||||
const applyContractDefaultRows = async () => {
|
const applyContractDefaultRows = async () => {
|
||||||
const htData = await localforage.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value)
|
const htData = await kvStore.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value)
|
||||||
const hasContractRows = Array.isArray(htData?.detailRows) && htData.detailRows.length > 0
|
const hasContractRows = Array.isArray(htData?.detailRows) && htData.detailRows.length > 0
|
||||||
const targetProjectCount = getTargetProjectCount()
|
const targetProjectCount = getTargetProjectCount()
|
||||||
detailRows.value = hasContractRows
|
detailRows.value = hasContractRows
|
||||||
@ -1027,13 +1028,13 @@ const loadFromIndexedDB = async () => {
|
|||||||
|
|
||||||
const importContractData = async () => {
|
const importContractData = async () => {
|
||||||
try {
|
try {
|
||||||
const baseInfo = await localforage.getItem<XmBaseInfoState>(BASE_INFO_KEY)
|
const baseInfo = await kvStore.getItem<XmBaseInfoState>(BASE_INFO_KEY)
|
||||||
activeIndustryCode.value =
|
activeIndustryCode.value =
|
||||||
typeof baseInfo?.projectIndustry === 'string' ? baseInfo.projectIndustry.trim() : ''
|
typeof baseInfo?.projectIndustry === 'string' ? baseInfo.projectIndustry.trim() : ''
|
||||||
|
|
||||||
// 使用默认数据时,强制读取最新的项目系数(预算取值优先,空值回退标准系数)
|
// 使用默认数据时,强制读取最新的项目系数(预算取值优先,空值回退标准系数)
|
||||||
await loadFactorDefaults()
|
await loadFactorDefaults()
|
||||||
const htData = await localforage.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value)
|
const htData = await kvStore.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value)
|
||||||
const hasContractRows = Array.isArray(htData?.detailRows) && htData.detailRows.length > 0
|
const hasContractRows = Array.isArray(htData?.detailRows) && htData.detailRows.length > 0
|
||||||
const targetProjectCount = getTargetProjectCount()
|
const targetProjectCount = getTargetProjectCount()
|
||||||
detailRows.value = hasContractRows
|
detailRows.value = hasContractRows
|
||||||
|
|||||||
@ -10,8 +10,8 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, defineAsyncComponent, markRaw, onBeforeUnmount, onMounted, ref } from 'vue'
|
import { computed, defineAsyncComponent, markRaw, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||||
import localforage from 'localforage'
|
|
||||||
import TypeLine from '@/layout/typeLine.vue'
|
import TypeLine from '@/layout/typeLine.vue'
|
||||||
|
import { useKvStore } from '@/pinia/kv'
|
||||||
|
|
||||||
const infoView = markRaw(defineAsyncComponent(() => import('@/components/views/info.vue')))
|
const infoView = markRaw(defineAsyncComponent(() => import('@/components/views/info.vue')))
|
||||||
const scaleInfoView = markRaw(defineAsyncComponent(() => import('@/components/views/xmInfo.vue')))
|
const scaleInfoView = markRaw(defineAsyncComponent(() => import('@/components/views/xmInfo.vue')))
|
||||||
@ -27,6 +27,7 @@ const PROJECT_INFO_KEY = 'xm-base-info-v1'
|
|||||||
const PROJECT_INIT_CHANGED_EVENT = 'xm-project-init-changed'
|
const PROJECT_INIT_CHANGED_EVENT = 'xm-project-init-changed'
|
||||||
|
|
||||||
const hasProjectBaseInfo = ref(false)
|
const hasProjectBaseInfo = ref(false)
|
||||||
|
const kvStore = useKvStore()
|
||||||
|
|
||||||
const fullXmCategories = [
|
const fullXmCategories = [
|
||||||
{ key: 'info', label: '基础信息', component: infoView },
|
{ key: 'info', label: '基础信息', component: infoView },
|
||||||
@ -42,7 +43,7 @@ const xmCategories = computed(() =>
|
|||||||
|
|
||||||
const refreshProjectBaseInfoState = async () => {
|
const refreshProjectBaseInfoState = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await localforage.getItem(PROJECT_INFO_KEY)
|
const data = await kvStore.getItem(PROJECT_INFO_KEY)
|
||||||
hasProjectBaseInfo.value = Boolean(data)
|
hasProjectBaseInfo.value = Boolean(data)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('read project base info failed:', error)
|
console.error('read project base info failed:', error)
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import { computed, defineComponent, h, nextTick, onActivated, onBeforeUnmount, o
|
|||||||
import type { ComponentPublicInstance, PropType } from 'vue'
|
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 { 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'
|
||||||
@ -32,6 +31,7 @@ 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 { useZxFwPricingStore } from '@/pinia/zxFwPricing'
|
import { useZxFwPricingStore } from '@/pinia/zxFwPricing'
|
||||||
|
import { useKvStore } from '@/pinia/kv'
|
||||||
import ServiceCheckboxSelector from '@/components/views/ServiceCheckboxSelector.vue'
|
import ServiceCheckboxSelector from '@/components/views/ServiceCheckboxSelector.vue'
|
||||||
|
|
||||||
interface ServiceItem {
|
interface ServiceItem {
|
||||||
@ -76,6 +76,7 @@ const props = defineProps<{
|
|||||||
}>()
|
}>()
|
||||||
const tabStore = useTabStore()
|
const tabStore = useTabStore()
|
||||||
const zxFwPricingStore = useZxFwPricingStore()
|
const zxFwPricingStore = useZxFwPricingStore()
|
||||||
|
const kvStore = useKvStore()
|
||||||
const PROJECT_INFO_KEY = 'xm-base-info-v1'
|
const PROJECT_INFO_KEY = 'xm-base-info-v1'
|
||||||
const PRICING_CLEAR_SKIP_PREFIX = 'pricing-clear-skip:'
|
const PRICING_CLEAR_SKIP_PREFIX = 'pricing-clear-skip:'
|
||||||
const PRICING_FORCE_DEFAULT_PREFIX = 'pricing-force-default:'
|
const PRICING_FORCE_DEFAULT_PREFIX = 'pricing-force-default:'
|
||||||
@ -423,7 +424,7 @@ const clearPricingPaneValues = async (serviceId: string) => {
|
|||||||
}
|
}
|
||||||
zxFwPricingStore.removeAllServicePricingMethodStates(props.contractId, serviceId)
|
zxFwPricingStore.removeAllServicePricingMethodStates(props.contractId, serviceId)
|
||||||
// Reset后会立刻有逻辑读取IndexedDB计算默认值,这里强制同步删除持久层,避免读到旧数据。
|
// Reset后会立刻有逻辑读取IndexedDB计算默认值,这里强制同步删除持久层,避免读到旧数据。
|
||||||
await Promise.all(keys.map(key => localforage.removeItem(key)))
|
await Promise.all(keys.map(key => kvStore.removeItem(key)))
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearRowValues = async (row: DetailRow) => {
|
const clearRowValues = async (row: DetailRow) => {
|
||||||
@ -995,7 +996,7 @@ const initializeContractState = async () => {
|
|||||||
|
|
||||||
const loadProjectIndustry = async () => {
|
const loadProjectIndustry = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await localforage.getItem<XmBaseInfoState>(PROJECT_INFO_KEY)
|
const data = await kvStore.getItem<XmBaseInfoState>(PROJECT_INFO_KEY)
|
||||||
projectIndustry.value =
|
projectIndustry.value =
|
||||||
typeof data?.projectIndustry === 'string' ? data.projectIndustry.trim() : ''
|
typeof data?.projectIndustry === 'string' ? data.projectIndustry.trim() : ''
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, defineAsyncComponent, markRaw, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
import { computed, defineAsyncComponent, markRaw, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue'
|
||||||
import type { ComponentPublicInstance } from 'vue'
|
import type { ComponentPublicInstance } from 'vue'
|
||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
import { useTabStore } from '@/pinia/tab'
|
import { useTabStore } from '@/pinia/tab'
|
||||||
import { useZxFwPricingStore } from '@/pinia/zxFwPricing'
|
import { useZxFwPricingStore } from '@/pinia/zxFwPricing'
|
||||||
|
import { useKvStore } from '@/pinia/kv'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||||
import { TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from '@/components/ui/tooltip'
|
import { TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from '@/components/ui/tooltip'
|
||||||
import { ChevronDown, CircleHelp, RotateCcw, X } from 'lucide-vue-next'
|
import { ChevronDown, CircleHelp, RotateCcw, X } from 'lucide-vue-next'
|
||||||
import { createPinia } from 'pinia';
|
|
||||||
|
|
||||||
import localforage from 'localforage'
|
import localforage from 'localforage'
|
||||||
import {
|
import {
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
@ -30,12 +29,18 @@ interface DataEntry {
|
|||||||
value: any
|
value: any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ForageStoreSnapshot {
|
||||||
|
storeName: string
|
||||||
|
entries: DataEntry[]
|
||||||
|
}
|
||||||
|
|
||||||
interface DataPackage {
|
interface DataPackage {
|
||||||
version: number
|
version: number
|
||||||
exportedAt: string
|
exportedAt: string
|
||||||
localStorage: DataEntry[]
|
localStorage: DataEntry[]
|
||||||
sessionStorage: DataEntry[]
|
sessionStorage: DataEntry[]
|
||||||
localforageDefault: DataEntry[]
|
localforageDefault: DataEntry[]
|
||||||
|
localforageStores?: ForageStoreSnapshot[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserGuideStep {
|
interface UserGuideStep {
|
||||||
@ -268,6 +273,9 @@ const PROJECT_INFO_DB_KEY = 'xm-base-info-v1'
|
|||||||
const LEGACY_PROJECT_DB_KEY = 'xm-info-v3'
|
const LEGACY_PROJECT_DB_KEY = 'xm-info-v3'
|
||||||
const CONSULT_CATEGORY_FACTOR_DB_KEY = 'xm-consult-category-factor-v1'
|
const CONSULT_CATEGORY_FACTOR_DB_KEY = 'xm-consult-category-factor-v1'
|
||||||
const MAJOR_FACTOR_DB_KEY = 'xm-major-factor-v1'
|
const MAJOR_FACTOR_DB_KEY = 'xm-major-factor-v1'
|
||||||
|
const PINIA_PERSIST_DB_NAME = 'DB'
|
||||||
|
const PINIA_PERSIST_BASE_STORE_NAME = 'pinia'
|
||||||
|
const PINIA_PERSIST_STORE_IDS = ['tabs', 'zxFwPricing', 'kv'] as const
|
||||||
const userGuideSteps: UserGuideStep[] = [
|
const userGuideSteps: UserGuideStep[] = [
|
||||||
{
|
{
|
||||||
title: '欢迎使用',
|
title: '欢迎使用',
|
||||||
@ -352,6 +360,7 @@ const componentMap: Record<string, any> = {
|
|||||||
|
|
||||||
const tabStore = useTabStore()
|
const tabStore = useTabStore()
|
||||||
const zxFwPricingStore = useZxFwPricingStore()
|
const zxFwPricingStore = useZxFwPricingStore()
|
||||||
|
const kvStore = useKvStore()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -365,7 +374,7 @@ const dataMenuOpen = ref(false)
|
|||||||
const dataMenuRef = ref<HTMLElement | null>(null)
|
const dataMenuRef = ref<HTMLElement | null>(null)
|
||||||
const importFileRef = ref<HTMLInputElement | null>(null)
|
const importFileRef = ref<HTMLInputElement | null>(null)
|
||||||
const importConfirmOpen = ref(false)
|
const importConfirmOpen = ref(false)
|
||||||
const pendingImportPayload = ref<DataPackage | null>(null)
|
const pendingImportPayload = shallowRef<DataPackage | null>(null)
|
||||||
const pendingImportFileName = ref('')
|
const pendingImportFileName = ref('')
|
||||||
const userGuideOpen = ref(false)
|
const userGuideOpen = ref(false)
|
||||||
const userGuideStepIndex = ref(0)
|
const userGuideStepIndex = ref(0)
|
||||||
@ -450,10 +459,10 @@ const shouldAutoOpenGuide = async () => {
|
|||||||
if (hasGuideCompleted()) return false
|
if (hasGuideCompleted()) return false
|
||||||
if (hasNonDefaultTabState()) return false
|
if (hasNonDefaultTabState()) return false
|
||||||
try {
|
try {
|
||||||
const keys = await localforage.keys()
|
const keys = await kvStore.keys()
|
||||||
return keys.length === 0
|
return keys.length === 0
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('read localforage keys failed:', error)
|
console.error('read kv keys failed:', error)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -734,20 +743,49 @@ const writeWebStorage = (storageObj: Storage, entries: DataEntry[]) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const readForage = async (store: typeof localforage): Promise<DataEntry[]> => {
|
type ForageInstance = ReturnType<typeof localforage.createInstance>
|
||||||
|
type ForageStore = Pick<ForageInstance, 'keys' | 'getItem' | 'setItem' | 'clear'>
|
||||||
|
|
||||||
|
const createForageStore = (storeName: string): ForageInstance =>
|
||||||
|
localforage.createInstance({
|
||||||
|
name: PINIA_PERSIST_DB_NAME,
|
||||||
|
storeName
|
||||||
|
})
|
||||||
|
|
||||||
|
const getPiniaPersistStoreName = (storeId: string) => `${PINIA_PERSIST_BASE_STORE_NAME}-${storeId}`
|
||||||
|
|
||||||
|
const getPiniaPersistStores = () =>
|
||||||
|
PINIA_PERSIST_STORE_IDS.map(storeId => {
|
||||||
|
const storeName = getPiniaPersistStoreName(storeId)
|
||||||
|
return {
|
||||||
|
storeName,
|
||||||
|
store: createForageStore(storeName)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const readForage = async (store: ForageStore): Promise<DataEntry[]> => {
|
||||||
const keys = await store.keys()
|
const keys = await store.keys()
|
||||||
const entries: DataEntry[] = []
|
const entries: DataEntry[] = []
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
const value = await store.getItem(key)
|
const value = await store.getItem(key)
|
||||||
entries.push({ key, value })
|
entries.push({ key, value: toPersistableValue(value) })
|
||||||
}
|
}
|
||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
const writeForage = async (store: typeof localforage, entries: DataEntry[]) => {
|
const toPersistableValue = (value: unknown) => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(JSON.stringify(value))
|
||||||
|
} catch (error) {
|
||||||
|
console.error('normalize persist value failed, fallback to null:', error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const writeForage = async (store: ForageStore, entries: DataEntry[]) => {
|
||||||
await store.clear()
|
await store.clear()
|
||||||
for (const entry of entries || []) {
|
for (const entry of entries || []) {
|
||||||
await store.setItem(entry.key, entry.value)
|
await store.setItem(entry.key, toPersistableValue(entry.value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -758,6 +796,21 @@ const normalizeEntries = (value: unknown): DataEntry[] => {
|
|||||||
.map(item => ({ key: String((item as any).key), value: (item as any).value }))
|
.map(item => ({ key: String((item as any).key), value: (item as any).value }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const normalizeForageStoreSnapshots = (value: unknown): ForageStoreSnapshot[] => {
|
||||||
|
if (!Array.isArray(value)) return []
|
||||||
|
return value
|
||||||
|
.filter(item =>
|
||||||
|
item
|
||||||
|
&& typeof item === 'object'
|
||||||
|
&& typeof (item as any).storeName === 'string'
|
||||||
|
&& Array.isArray((item as any).entries)
|
||||||
|
)
|
||||||
|
.map(item => ({
|
||||||
|
storeName: String((item as any).storeName),
|
||||||
|
entries: normalizeEntries((item as any).entries)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
const sanitizeFileNamePart = (value: string): string => {
|
const sanitizeFileNamePart = (value: string): string => {
|
||||||
const cleaned = value
|
const cleaned = value
|
||||||
.replace(/[\\/:*?"<>|]/g, '_')
|
.replace(/[\\/:*?"<>|]/g, '_')
|
||||||
@ -1058,11 +1111,11 @@ const buildServiceFee = (
|
|||||||
|
|
||||||
const buildExportReportPayload = async (): Promise<ExportReportPayload> => {
|
const buildExportReportPayload = async (): Promise<ExportReportPayload> => {
|
||||||
const [projectInfoRaw, projectScaleRaw, consultCategoryFactorRaw, majorFactorRaw, contractCardsRaw] = await Promise.all([
|
const [projectInfoRaw, projectScaleRaw, consultCategoryFactorRaw, majorFactorRaw, contractCardsRaw] = await Promise.all([
|
||||||
localforage.getItem<XmInfoLike>(PROJECT_INFO_DB_KEY),
|
kvStore.getItem<XmInfoLike>(PROJECT_INFO_DB_KEY),
|
||||||
localforage.getItem<XmInfoStorageLike>(LEGACY_PROJECT_DB_KEY),
|
kvStore.getItem<XmInfoStorageLike>(LEGACY_PROJECT_DB_KEY),
|
||||||
localforage.getItem<DetailRowsStorageLike<FactorRowLike>>(CONSULT_CATEGORY_FACTOR_DB_KEY),
|
kvStore.getItem<DetailRowsStorageLike<FactorRowLike>>(CONSULT_CATEGORY_FACTOR_DB_KEY),
|
||||||
localforage.getItem<DetailRowsStorageLike<FactorRowLike>>(MAJOR_FACTOR_DB_KEY),
|
kvStore.getItem<DetailRowsStorageLike<FactorRowLike>>(MAJOR_FACTOR_DB_KEY),
|
||||||
localforage.getItem<ContractCardItem[]>('ht-card-v1')
|
kvStore.getItem<ContractCardItem[]>('ht-card-v1')
|
||||||
])
|
])
|
||||||
|
|
||||||
const projectInfo = projectInfoRaw || {}
|
const projectInfo = projectInfoRaw || {}
|
||||||
@ -1087,8 +1140,8 @@ const buildExportReportPayload = async (): Promise<ExportReportPayload> => {
|
|||||||
const contract = contractCards[index]
|
const contract = contractCards[index]
|
||||||
const contractId = contract.id
|
const contractId = contract.id
|
||||||
const [htInfoRaw, zxFwRaw] = await Promise.all([
|
const [htInfoRaw, zxFwRaw] = await Promise.all([
|
||||||
localforage.getItem<DetailRowsStorageLike<ScaleRowLike>>(`ht-info-v3-${contractId}`),
|
kvStore.getItem<DetailRowsStorageLike<ScaleRowLike>>(`ht-info-v3-${contractId}`),
|
||||||
localforage.getItem<ZxFwStorageLike>(`zxFW-${contractId}`)
|
kvStore.getItem<ZxFwStorageLike>(`zxFW-${contractId}`)
|
||||||
])
|
])
|
||||||
|
|
||||||
const zxRows = Array.isArray(zxFwRaw?.detailRows) ? zxFwRaw.detailRows : []
|
const zxRows = Array.isArray(zxFwRaw?.detailRows) ? zxFwRaw.detailRows : []
|
||||||
@ -1179,12 +1232,19 @@ const buildExportReportPayload = async (): Promise<ExportReportPayload> => {
|
|||||||
const exportData = async () => {
|
const exportData = async () => {
|
||||||
try {
|
try {
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
|
const piniaForageStores = await Promise.all(
|
||||||
|
getPiniaPersistStores().map(async ({ storeName, store }) => ({
|
||||||
|
storeName,
|
||||||
|
entries: await readForage(store)
|
||||||
|
}))
|
||||||
|
)
|
||||||
const payload: DataPackage = {
|
const payload: DataPackage = {
|
||||||
version: 1,
|
version: 2,
|
||||||
exportedAt: now.toISOString(),
|
exportedAt: now.toISOString(),
|
||||||
localStorage: readWebStorage(localStorage),
|
localStorage: readWebStorage(localStorage),
|
||||||
sessionStorage: readWebStorage(sessionStorage),
|
sessionStorage: readWebStorage(sessionStorage),
|
||||||
localforageDefault: await readForage(localforage),
|
localforageDefault: await readForage(localforage),
|
||||||
|
localforageStores: piniaForageStores
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = await encodeZwArchive(payload)
|
const content = await encodeZwArchive(payload)
|
||||||
@ -1261,9 +1321,44 @@ const confirmImportOverride = async () => {
|
|||||||
writeWebStorage(localStorage, normalizeEntries(payload.localStorage))
|
writeWebStorage(localStorage, normalizeEntries(payload.localStorage))
|
||||||
writeWebStorage(sessionStorage, normalizeEntries(payload.sessionStorage))
|
writeWebStorage(sessionStorage, normalizeEntries(payload.sessionStorage))
|
||||||
await writeForage(localforage, normalizeEntries(payload.localforageDefault))
|
await writeForage(localforage, normalizeEntries(payload.localforageDefault))
|
||||||
|
const piniaSnapshots = normalizeForageStoreSnapshots(payload.localforageStores)
|
||||||
|
const snapshotMap = new Map(piniaSnapshots.map(item => [item.storeName, item.entries]))
|
||||||
|
await Promise.all(
|
||||||
|
getPiniaPersistStores().map(async ({ storeName, store }) => {
|
||||||
|
const entries = snapshotMap.get(storeName) || []
|
||||||
|
await writeForage(store, entries)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
tabStore.resetTabs()
|
const readPersistedState = (storeId: string) => {
|
||||||
await tabStore.$persistNow?.()
|
const storeName = getPiniaPersistStoreName(storeId)
|
||||||
|
const entries = snapshotMap.get(storeName) || []
|
||||||
|
const hit = entries.find(entry => entry.key === storeName)
|
||||||
|
return hit && hit.value && typeof hit.value === 'object' ? hit.value : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabsState = readPersistedState('tabs')
|
||||||
|
if (tabsState) {
|
||||||
|
tabStore.$patch(tabsState as any)
|
||||||
|
} else {
|
||||||
|
tabStore.resetTabs()
|
||||||
|
}
|
||||||
|
|
||||||
|
const zxFwPricingState = readPersistedState('zxFwPricing')
|
||||||
|
if (zxFwPricingState) {
|
||||||
|
zxFwPricingStore.$patch(zxFwPricingState as any)
|
||||||
|
}
|
||||||
|
|
||||||
|
const kvState = readPersistedState('kv')
|
||||||
|
if (kvState) {
|
||||||
|
kvStore.$patch(kvState as any)
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
tabStore.$persistNow?.(),
|
||||||
|
zxFwPricingStore.$persistNow?.(),
|
||||||
|
kvStore.$persistNow?.()
|
||||||
|
])
|
||||||
dataMenuOpen.value = false
|
dataMenuOpen.value = false
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -1279,6 +1374,11 @@ const handleReset = async () => {
|
|||||||
localStorage.clear()
|
localStorage.clear()
|
||||||
sessionStorage.clear()
|
sessionStorage.clear()
|
||||||
await localforage.clear()
|
await localforage.clear()
|
||||||
|
await Promise.all(
|
||||||
|
getPiniaPersistStores().map(async ({ store }) => {
|
||||||
|
await store.clear()
|
||||||
|
})
|
||||||
|
)
|
||||||
localStorage.setItem(USER_GUIDE_COMPLETED_KEY, '1')
|
localStorage.setItem(USER_GUIDE_COMPLETED_KEY, '1')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('reset failed:', error)
|
console.error('reset failed:', error)
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import localforage from 'localforage'
|
|
||||||
import {
|
import {
|
||||||
expertList,
|
expertList,
|
||||||
getMajorDictEntries,
|
getMajorDictEntries,
|
||||||
@ -10,6 +9,7 @@ import { roundTo, sumByNumber, toDecimal } from '@/lib/decimal'
|
|||||||
import { toFiniteNumberOrNull } from '@/lib/number'
|
import { toFiniteNumberOrNull } from '@/lib/number'
|
||||||
import { getBenchmarkBudgetByScale, getScaleBudgetFee } from '@/lib/pricingScaleFee'
|
import { getBenchmarkBudgetByScale, getScaleBudgetFee } from '@/lib/pricingScaleFee'
|
||||||
import { useZxFwPricingStore, type ServicePricingMethod } from '@/pinia/zxFwPricing'
|
import { useZxFwPricingStore, type ServicePricingMethod } from '@/pinia/zxFwPricing'
|
||||||
|
import { useKvStore } from '@/pinia/kv'
|
||||||
|
|
||||||
interface StoredDetailRowsState<T = any> {
|
interface StoredDetailRowsState<T = any> {
|
||||||
detailRows?: T[]
|
detailRows?: T[]
|
||||||
@ -123,6 +123,26 @@ const getZxFwStoreSafely = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getKvStoreSafely = () => {
|
||||||
|
try {
|
||||||
|
return useKvStore()
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const kvGetItem = async <T = unknown>(key: string): Promise<T | null> => {
|
||||||
|
const store = getKvStoreSafely()
|
||||||
|
if (!store) return null
|
||||||
|
return store.getItem<T>(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
const kvSetItem = async <T = unknown>(key: string, value: T): Promise<void> => {
|
||||||
|
const store = getKvStoreSafely()
|
||||||
|
if (!store) return
|
||||||
|
await store.setItem(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
const toStoredDetailRowsState = <TRow = unknown>(state: { detailRows?: TRow[] } | null | undefined): StoredDetailRowsState<TRow> | null => {
|
const toStoredDetailRowsState = <TRow = unknown>(state: { detailRows?: TRow[] } | null | undefined): StoredDetailRowsState<TRow> | null => {
|
||||||
if (!state || !Array.isArray(state.detailRows)) return null
|
if (!state || !Array.isArray(state.detailRows)) return null
|
||||||
return {
|
return {
|
||||||
@ -594,10 +614,10 @@ const loadPricingMethodDefaultBuildContext = async (
|
|||||||
const baseInfoDbKey = 'xm-base-info-v1'
|
const baseInfoDbKey = 'xm-base-info-v1'
|
||||||
|
|
||||||
const [htData, consultFactorData, majorFactorData, baseInfo] = await Promise.all([
|
const [htData, consultFactorData, majorFactorData, baseInfo] = await Promise.all([
|
||||||
localforage.getItem<StoredDetailRowsState>(htDbKey),
|
kvGetItem<StoredDetailRowsState>(htDbKey),
|
||||||
localforage.getItem<StoredFactorState>(consultFactorDbKey),
|
kvGetItem<StoredFactorState>(consultFactorDbKey),
|
||||||
localforage.getItem<StoredFactorState>(majorFactorDbKey),
|
kvGetItem<StoredFactorState>(majorFactorDbKey),
|
||||||
localforage.getItem<XmBaseInfoState>(baseInfoDbKey)
|
kvGetItem<XmBaseInfoState>(baseInfoDbKey)
|
||||||
])
|
])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -668,10 +688,10 @@ export const persistDefaultPricingMethodDetailRowsForServices = async (params: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
localforage.setItem(dbKeys.investScale, { detailRows: defaultRows.investScale }),
|
kvSetItem(dbKeys.investScale, { detailRows: defaultRows.investScale }),
|
||||||
localforage.setItem(dbKeys.landScale, { detailRows: defaultRows.landScale }),
|
kvSetItem(dbKeys.landScale, { detailRows: defaultRows.landScale }),
|
||||||
localforage.setItem(dbKeys.workload, { detailRows: defaultRows.workload }),
|
kvSetItem(dbKeys.workload, { detailRows: defaultRows.workload }),
|
||||||
localforage.setItem(dbKeys.hourly, { detailRows: defaultRows.hourly })
|
kvSetItem(dbKeys.hourly, { detailRows: defaultRows.hourly })
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@ -695,17 +715,17 @@ export const getPricingMethodTotalsForService = async (params: {
|
|||||||
store?.loadServicePricingMethodState<Record<string, unknown>>(params.contractId, serviceId, 'landScale') || Promise.resolve(null),
|
store?.loadServicePricingMethodState<Record<string, unknown>>(params.contractId, serviceId, 'landScale') || Promise.resolve(null),
|
||||||
store?.loadServicePricingMethodState<Record<string, unknown>>(params.contractId, serviceId, 'workload') || Promise.resolve(null),
|
store?.loadServicePricingMethodState<Record<string, unknown>>(params.contractId, serviceId, 'workload') || Promise.resolve(null),
|
||||||
store?.loadServicePricingMethodState<Record<string, unknown>>(params.contractId, serviceId, 'hourly') || Promise.resolve(null),
|
store?.loadServicePricingMethodState<Record<string, unknown>>(params.contractId, serviceId, 'hourly') || Promise.resolve(null),
|
||||||
localforage.getItem<StoredDetailRowsState>(htDbKey),
|
kvGetItem<StoredDetailRowsState>(htDbKey),
|
||||||
localforage.getItem<StoredFactorState>(consultFactorDbKey),
|
kvGetItem<StoredFactorState>(consultFactorDbKey),
|
||||||
localforage.getItem<StoredFactorState>(majorFactorDbKey),
|
kvGetItem<StoredFactorState>(majorFactorDbKey),
|
||||||
localforage.getItem<XmBaseInfoState>(baseInfoDbKey)
|
kvGetItem<XmBaseInfoState>(baseInfoDbKey)
|
||||||
])
|
])
|
||||||
|
|
||||||
const [investDataFallback, landDataFallback, workloadDataFallback, hourlyDataFallback] = await Promise.all([
|
const [investDataFallback, landDataFallback, workloadDataFallback, hourlyDataFallback] = await Promise.all([
|
||||||
storeInvestData ? Promise.resolve(null) : localforage.getItem<StoredDetailRowsState>(dbKeys.investScale),
|
storeInvestData ? Promise.resolve(null) : kvGetItem<StoredDetailRowsState>(dbKeys.investScale),
|
||||||
storeLandData ? Promise.resolve(null) : localforage.getItem<StoredDetailRowsState>(dbKeys.landScale),
|
storeLandData ? Promise.resolve(null) : kvGetItem<StoredDetailRowsState>(dbKeys.landScale),
|
||||||
storeWorkloadData ? Promise.resolve(null) : localforage.getItem<StoredDetailRowsState>(dbKeys.workload),
|
storeWorkloadData ? Promise.resolve(null) : kvGetItem<StoredDetailRowsState>(dbKeys.workload),
|
||||||
storeHourlyData ? Promise.resolve(null) : localforage.getItem<StoredDetailRowsState>(dbKeys.hourly)
|
storeHourlyData ? Promise.resolve(null) : kvGetItem<StoredDetailRowsState>(dbKeys.hourly)
|
||||||
])
|
])
|
||||||
|
|
||||||
const investData = toStoredDetailRowsState(storeInvestData) || investDataFallback
|
const investData = toStoredDetailRowsState(storeInvestData) || investDataFallback
|
||||||
@ -799,10 +819,10 @@ export const ensurePricingMethodDetailRowsForServices = async (params: {
|
|||||||
store?.loadServicePricingMethodState<Record<string, unknown>>(params.contractId, serviceId, 'hourly') || Promise.resolve(null)
|
store?.loadServicePricingMethodState<Record<string, unknown>>(params.contractId, serviceId, 'hourly') || Promise.resolve(null)
|
||||||
])
|
])
|
||||||
const [investDataFallback, landDataFallback, workloadDataFallback, hourlyDataFallback] = await Promise.all([
|
const [investDataFallback, landDataFallback, workloadDataFallback, hourlyDataFallback] = await Promise.all([
|
||||||
storeInvestData ? Promise.resolve(null) : localforage.getItem<StoredDetailRowsState>(dbKeys.investScale),
|
storeInvestData ? Promise.resolve(null) : kvGetItem<StoredDetailRowsState>(dbKeys.investScale),
|
||||||
storeLandData ? Promise.resolve(null) : localforage.getItem<StoredDetailRowsState>(dbKeys.landScale),
|
storeLandData ? Promise.resolve(null) : kvGetItem<StoredDetailRowsState>(dbKeys.landScale),
|
||||||
storeWorkloadData ? Promise.resolve(null) : localforage.getItem<StoredDetailRowsState>(dbKeys.workload),
|
storeWorkloadData ? Promise.resolve(null) : kvGetItem<StoredDetailRowsState>(dbKeys.workload),
|
||||||
storeHourlyData ? Promise.resolve(null) : localforage.getItem<StoredDetailRowsState>(dbKeys.hourly)
|
storeHourlyData ? Promise.resolve(null) : kvGetItem<StoredDetailRowsState>(dbKeys.hourly)
|
||||||
])
|
])
|
||||||
const investData = toStoredDetailRowsState(storeInvestData) || investDataFallback
|
const investData = toStoredDetailRowsState(storeInvestData) || investDataFallback
|
||||||
const landData = toStoredDetailRowsState(storeLandData) || landDataFallback
|
const landData = toStoredDetailRowsState(storeLandData) || landDataFallback
|
||||||
@ -829,7 +849,7 @@ export const ensurePricingMethodDetailRowsForServices = async (params: {
|
|||||||
detailRows: getDefaultRows().investScale
|
detailRows: getDefaultRows().investScale
|
||||||
}, { force: true })
|
}, { force: true })
|
||||||
}
|
}
|
||||||
writeTasks.push(localforage.setItem(dbKeys.investScale, { detailRows: getDefaultRows().investScale }))
|
writeTasks.push(kvSetItem(dbKeys.investScale, { detailRows: getDefaultRows().investScale }))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldInitLand) {
|
if (shouldInitLand) {
|
||||||
@ -838,7 +858,7 @@ export const ensurePricingMethodDetailRowsForServices = async (params: {
|
|||||||
detailRows: getDefaultRows().landScale
|
detailRows: getDefaultRows().landScale
|
||||||
}, { force: true })
|
}, { force: true })
|
||||||
}
|
}
|
||||||
writeTasks.push(localforage.setItem(dbKeys.landScale, { detailRows: getDefaultRows().landScale }))
|
writeTasks.push(kvSetItem(dbKeys.landScale, { detailRows: getDefaultRows().landScale }))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldInitWorkload) {
|
if (shouldInitWorkload) {
|
||||||
@ -847,7 +867,7 @@ export const ensurePricingMethodDetailRowsForServices = async (params: {
|
|||||||
detailRows: getDefaultRows().workload
|
detailRows: getDefaultRows().workload
|
||||||
}, { force: true })
|
}, { force: true })
|
||||||
}
|
}
|
||||||
writeTasks.push(localforage.setItem(dbKeys.workload, { detailRows: getDefaultRows().workload }))
|
writeTasks.push(kvSetItem(dbKeys.workload, { detailRows: getDefaultRows().workload }))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldInitHourly) {
|
if (shouldInitHourly) {
|
||||||
@ -856,7 +876,7 @@ export const ensurePricingMethodDetailRowsForServices = async (params: {
|
|||||||
detailRows: getDefaultRows().hourly
|
detailRows: getDefaultRows().hourly
|
||||||
}, { force: true })
|
}, { force: true })
|
||||||
}
|
}
|
||||||
writeTasks.push(localforage.setItem(dbKeys.hourly, { detailRows: getDefaultRows().hourly }))
|
writeTasks.push(kvSetItem(dbKeys.hourly, { detailRows: getDefaultRows().hourly }))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (writeTasks.length > 0) {
|
if (writeTasks.length > 0) {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import localforage from 'localforage'
|
|
||||||
import { getMajorDictById, getMajorIdAliasMap, getServiceDictById } from '@/sql'
|
import { getMajorDictById, getMajorIdAliasMap, getServiceDictById } from '@/sql'
|
||||||
import { toFiniteNumberOrNull } from '@/lib/number'
|
import { toFiniteNumberOrNull } from '@/lib/number'
|
||||||
|
import { useKvStore } from '@/pinia/kv'
|
||||||
|
|
||||||
const CONSULT_CATEGORY_FACTOR_KEY = 'xm-consult-category-factor-v1'
|
const CONSULT_CATEGORY_FACTOR_KEY = 'xm-consult-category-factor-v1'
|
||||||
const MAJOR_FACTOR_KEY = 'xm-major-factor-v1'
|
const MAJOR_FACTOR_KEY = 'xm-major-factor-v1'
|
||||||
@ -41,12 +41,21 @@ const resolveFactorValue = (
|
|||||||
return fallbackStandard
|
return fallbackStandard
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getKvStoreSafely = () => {
|
||||||
|
try {
|
||||||
|
return useKvStore()
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const loadFactorMap = async (
|
const loadFactorMap = async (
|
||||||
storageKey: string,
|
storageKey: string,
|
||||||
dict: FactorDict,
|
dict: FactorDict,
|
||||||
aliases?: Map<string, string>
|
aliases?: Map<string, string>
|
||||||
): Promise<Map<string, number | null>> => {
|
): Promise<Map<string, number | null>> => {
|
||||||
const data = await localforage.getItem<XmFactorState>(storageKey)
|
const kvStore = getKvStoreSafely()
|
||||||
|
const data = kvStore ? await kvStore.getItem<XmFactorState>(storageKey) : null
|
||||||
const map = buildStandardFactorMap(dict)
|
const map = buildStandardFactorMap(dict)
|
||||||
for (const row of data?.detailRows || []) {
|
for (const row of data?.detailRows || []) {
|
||||||
if (!row?.id) continue
|
if (!row?.id) continue
|
||||||
|
|||||||
90
src/pinia/kv.ts
Normal file
90
src/pinia/kv.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const cloneJson = <T>(value: T): T => JSON.parse(JSON.stringify(value)) as T
|
||||||
|
|
||||||
|
export const useKvStore = defineStore('kv', () => {
|
||||||
|
const entries = ref<Record<string, unknown>>({})
|
||||||
|
const ready = ref(false)
|
||||||
|
const loading = ref<Promise<void> | null>(null)
|
||||||
|
|
||||||
|
const ensureReady = async () => {
|
||||||
|
if (ready.value) return
|
||||||
|
if (loading.value) {
|
||||||
|
await loading.value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loading.value = (async () => {
|
||||||
|
ready.value = true
|
||||||
|
})()
|
||||||
|
await loading.value
|
||||||
|
loading.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const getItem = async <T = unknown>(keyRaw: string | number): Promise<T | null> => {
|
||||||
|
await ensureReady()
|
||||||
|
const key = String(keyRaw || '').trim()
|
||||||
|
if (!key) return null
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(entries.value, key)) return null
|
||||||
|
return cloneJson(entries.value[key] as T)
|
||||||
|
}
|
||||||
|
|
||||||
|
const setItem = async <T = unknown>(keyRaw: string | number, value: T): Promise<void> => {
|
||||||
|
await ensureReady()
|
||||||
|
const key = String(keyRaw || '').trim()
|
||||||
|
if (!key) return
|
||||||
|
entries.value[key] = cloneJson(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeItem = async (keyRaw: string | number): Promise<void> => {
|
||||||
|
await ensureReady()
|
||||||
|
const key = String(keyRaw || '').trim()
|
||||||
|
if (!key) return
|
||||||
|
delete entries.value[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
const keys = async (): Promise<string[]> => {
|
||||||
|
await ensureReady()
|
||||||
|
return Object.keys(entries.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const clear = async (): Promise<void> => {
|
||||||
|
await ensureReady()
|
||||||
|
entries.value = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportEntries = async () => {
|
||||||
|
await ensureReady()
|
||||||
|
return Object.entries(entries.value).map(([key, value]) => ({ key, value: cloneJson(value) }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const importEntries = async (
|
||||||
|
incoming: Array<{ key: string; value: unknown }>,
|
||||||
|
options?: { replace?: boolean }
|
||||||
|
) => {
|
||||||
|
await ensureReady()
|
||||||
|
if (options?.replace !== false) {
|
||||||
|
entries.value = {}
|
||||||
|
}
|
||||||
|
for (const item of incoming || []) {
|
||||||
|
const key = String(item?.key || '').trim()
|
||||||
|
if (!key) continue
|
||||||
|
entries.value[key] = cloneJson(item.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
entries,
|
||||||
|
ready,
|
||||||
|
ensureReady,
|
||||||
|
getItem,
|
||||||
|
setItem,
|
||||||
|
removeItem,
|
||||||
|
keys,
|
||||||
|
clear,
|
||||||
|
exportEntries,
|
||||||
|
importEntries
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
persist: true
|
||||||
|
})
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import localforage from 'localforage'
|
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { addNumbers } from '@/lib/decimal'
|
import { addNumbers } from '@/lib/decimal'
|
||||||
import { toFiniteNumberOrNull } from '@/lib/number'
|
import { toFiniteNumberOrNull } from '@/lib/number'
|
||||||
|
import { useKvStore } from '@/pinia/kv'
|
||||||
|
|
||||||
export type ZxFwPricingField = 'investScale' | 'landScale' | 'workload' | 'hourly'
|
export type ZxFwPricingField = 'investScale' | 'landScale' | 'workload' | 'hourly'
|
||||||
export type ServicePricingMethod = ZxFwPricingField
|
export type ServicePricingMethod = ZxFwPricingField
|
||||||
@ -276,6 +276,13 @@ export const useZxFwPricingStore = defineStore('zxFwPricing', () => {
|
|||||||
const touchKeyVersion = (key: string) => {
|
const touchKeyVersion = (key: string) => {
|
||||||
keyVersions.value[key] = (keyVersions.value[key] || 0) + 1
|
keyVersions.value[key] = (keyVersions.value[key] || 0) + 1
|
||||||
}
|
}
|
||||||
|
const getKvStoreSafely = () => {
|
||||||
|
try {
|
||||||
|
return useKvStore()
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const ensureServicePricingState = (contractIdRaw: string | number, serviceIdRaw: string | number) => {
|
const ensureServicePricingState = (contractIdRaw: string | number, serviceIdRaw: string | number) => {
|
||||||
const contractId = toKey(contractIdRaw)
|
const contractId = toKey(contractIdRaw)
|
||||||
@ -658,7 +665,8 @@ export const useZxFwPricingStore = defineStore('zxFwPricing', () => {
|
|||||||
if (!force && keyLoadTasks.has(key)) return keyLoadTasks.get(key) as Promise<T | null>
|
if (!force && keyLoadTasks.has(key)) return keyLoadTasks.get(key) as Promise<T | null>
|
||||||
|
|
||||||
const task = (async () => {
|
const task = (async () => {
|
||||||
const raw = await localforage.getItem<T>(key)
|
const kvStore = getKvStoreSafely()
|
||||||
|
const raw = kvStore ? await kvStore.getItem<T>(key) : null
|
||||||
const nextSnapshot = toKeySnapshot(raw)
|
const nextSnapshot = toKeySnapshot(raw)
|
||||||
const prevSnapshot = keySnapshots.value[key]
|
const prevSnapshot = keySnapshots.value[key]
|
||||||
keyedLoaded.value[key] = true
|
keyedLoaded.value[key] = true
|
||||||
@ -793,7 +801,10 @@ export const useZxFwPricingStore = defineStore('zxFwPricing', () => {
|
|||||||
if (!force && loadTasks.has(contractId)) return loadTasks.get(contractId) as Promise<ZxFwState | null>
|
if (!force && loadTasks.has(contractId)) return loadTasks.get(contractId) as Promise<ZxFwState | null>
|
||||||
|
|
||||||
const task = (async () => {
|
const task = (async () => {
|
||||||
const raw = await localforage.getItem<ZxFwState>(dbKeyOf(contractId))
|
const kvStore = getKvStoreSafely()
|
||||||
|
const raw = kvStore
|
||||||
|
? await kvStore.getItem<ZxFwState>(dbKeyOf(contractId))
|
||||||
|
: null
|
||||||
const current = contracts.value[contractId]
|
const current = contracts.value[contractId]
|
||||||
if (raw) {
|
if (raw) {
|
||||||
const normalized = normalizeState(raw)
|
const normalized = normalizeState(raw)
|
||||||
@ -892,6 +903,76 @@ export const useZxFwPricingStore = defineStore('zxFwPricing', () => {
|
|||||||
return round3(sum)
|
return round3(sum)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const removeContractData = (contractIdRaw: string | number) => {
|
||||||
|
const contractId = toKey(contractIdRaw)
|
||||||
|
if (!contractId) return false
|
||||||
|
let changed = false
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(contracts.value, contractId)) {
|
||||||
|
delete contracts.value[contractId]
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(servicePricingStates.value, contractId)) {
|
||||||
|
delete servicePricingStates.value[contractId]
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(contractLoaded.value, contractId)) {
|
||||||
|
delete contractLoaded.value[contractId]
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(contractVersions.value, contractId)) {
|
||||||
|
delete contractVersions.value[contractId]
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
loadTasks.delete(contractId)
|
||||||
|
|
||||||
|
const htMainPrefix = `htExtraFee-${contractId}-`
|
||||||
|
for (const key of Object.keys(htFeeMainStates.value)) {
|
||||||
|
if (!key.startsWith(htMainPrefix)) continue
|
||||||
|
delete htFeeMainStates.value[key]
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
for (const key of Object.keys(htFeeMethodStates.value)) {
|
||||||
|
if (!key.startsWith(htMainPrefix)) continue
|
||||||
|
delete htFeeMethodStates.value[key]
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const methodPrefixes = Object.values(METHOD_STORAGE_PREFIX_MAP)
|
||||||
|
const isContractRelatedKey = (key: string) => {
|
||||||
|
if (key === dbKeyOf(contractId)) return true
|
||||||
|
if (key.startsWith(htMainPrefix)) return true
|
||||||
|
if (methodPrefixes.some(prefix => key.startsWith(`${prefix}-${contractId}-`))) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const keySet = new Set<string>([
|
||||||
|
...Object.keys(keyedStates.value),
|
||||||
|
...Object.keys(keyedLoaded.value),
|
||||||
|
...Object.keys(keyVersions.value),
|
||||||
|
...Object.keys(keySnapshots.value)
|
||||||
|
])
|
||||||
|
for (const key of keySet) {
|
||||||
|
if (!isContractRelatedKey(key)) continue
|
||||||
|
if (Object.prototype.hasOwnProperty.call(keyedStates.value, key)) {
|
||||||
|
delete keyedStates.value[key]
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(keyedLoaded.value, key)) {
|
||||||
|
delete keyedLoaded.value[key]
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(keyVersions.value, key)) {
|
||||||
|
delete keyVersions.value[key]
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(keySnapshots.value, key)) {
|
||||||
|
delete keySnapshots.value[key]
|
||||||
|
}
|
||||||
|
keyLoadTasks.delete(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
contracts,
|
contracts,
|
||||||
contractVersions,
|
contractVersions,
|
||||||
@ -906,6 +987,7 @@ export const useZxFwPricingStore = defineStore('zxFwPricing', () => {
|
|||||||
setContractState,
|
setContractState,
|
||||||
updatePricingField,
|
updatePricingField,
|
||||||
getBaseSubtotal,
|
getBaseSubtotal,
|
||||||
|
removeContractData,
|
||||||
getKeyState,
|
getKeyState,
|
||||||
loadKeyState,
|
loadKeyState,
|
||||||
setKeyState,
|
setKeyState,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user