import { defineStore } from 'pinia' import { ref } from 'vue' import { addNumbers } from '@/lib/decimal' import { toFiniteNumberOrNull } from '@/lib/number' import { waitForHydration } from '@/pinia/Plugin/indexdb' import { parseHtFeeMainStorageKey, parseHtFeeMethodStorageKey, useZxFwPricingHtFeeStore } from '@/pinia/zxFwPricingHtFee' import { useZxFwPricingKeysStore } from '@/pinia/zxFwPricingKeys' export type ZxFwPricingField = 'investScale' | 'landScale' | 'workload' | 'hourly' export type ServicePricingMethod = ZxFwPricingField export interface ZxFwDetailRow { id: string code?: string name?: string process?: number | null investScale: number | null landScale: number | null workload: number | null hourly: number | null subtotal?: number | null finalFee?: number | null actions?: unknown } export interface ZxFwState { selectedIds?: string[] selectedCodes?: string[] detailRows: ZxFwDetailRow[] } export interface ServicePricingMethodState { detailRows: TRow[] projectCount?: number | null } export interface ServicePricingState { investScale?: ServicePricingMethodState landScale?: ServicePricingMethodState workload?: ServicePricingMethodState hourly?: ServicePricingMethodState } export type HtFeeMethodType = 'rate-fee' | 'hourly-fee' | 'quantity-unit-price-fee' export interface HtFeeMainState { detailRows: TRow[] } export type HtFeeMethodPayload = unknown const FIXED_ROW_ID = 'fixed-budget-c' const METHOD_STORAGE_PREFIX_MAP: Record = { investScale: 'tzGMF', landScale: 'ydGMF', workload: 'gzlF', hourly: 'hourlyPricing' } const STORAGE_PREFIX_METHOD_MAP = new Map( Object.entries(METHOD_STORAGE_PREFIX_MAP).map(([method, prefix]) => [prefix, method as ServicePricingMethod]) ) const toKey = (contractId: string | number) => String(contractId || '').trim() const toServiceKey = (serviceId: string | number) => String(serviceId || '').trim() const serviceMethodDbKeyOf = (contractId: string, serviceId: string, method: ServicePricingMethod) => `${METHOD_STORAGE_PREFIX_MAP[method]}-${contractId}-${serviceId}` const round3 = (value: number) => Number(value.toFixed(3)) const isFiniteNumberValue = (value: unknown): value is number => typeof value === 'number' && Number.isFinite(value) const sumNullableNumbers = (values: Array): number | null => { const validValues = values.filter(isFiniteNumberValue) if (validValues.length === 0) return null return addNumbers(...validValues) } const round3Nullable = (value: number | null | undefined) => { const numeric = toFiniteNumberOrNull(value) return numeric == null ? null : round3(numeric) } const normalizeProcessValue = (value: unknown, rowId: string) => { if (rowId === FIXED_ROW_ID) return null return Number(value) === 1 ? 1 : 0 } const toKeySnapshot = (value: unknown) => JSON.stringify(value ?? null) const cloneAny = (value: T): T => { if (value == null) return value return JSON.parse(JSON.stringify(value)) as T } const normalizeRows = (rows: unknown): ZxFwDetailRow[] => (Array.isArray(rows) ? rows : []).map(item => { const row = item as Partial const rowId = String(row.id || '') return { id: rowId, code: typeof row.code === 'string' ? row.code : '', name: typeof row.name === 'string' ? row.name : '', process: normalizeProcessValue(row.process, rowId), investScale: toFiniteNumberOrNull(row.investScale), landScale: toFiniteNumberOrNull(row.landScale), workload: toFiniteNumberOrNull(row.workload), hourly: toFiniteNumberOrNull(row.hourly), subtotal: toFiniteNumberOrNull(row.subtotal), finalFee: toFiniteNumberOrNull(row.finalFee), actions: row.actions } }).filter(row => row.id) const applyRowSubtotals = (rows: ZxFwDetailRow[]): ZxFwDetailRow[] => { const normalized = rows.map(row => ({ ...row })) const nonFixedRows = normalized.filter(row => row.id !== FIXED_ROW_ID) const totalInvestScale = sumNullableNumbers(nonFixedRows.map(row => row.investScale)) const totalLandScale = sumNullableNumbers(nonFixedRows.map(row => row.landScale)) const totalWorkload = sumNullableNumbers(nonFixedRows.map(row => row.workload)) const totalHourly = sumNullableNumbers(nonFixedRows.map(row => row.hourly)) const fixedSubtotal = sumNullableNumbers([totalInvestScale, totalLandScale, totalWorkload, totalHourly]) return normalized.map(row => { if (row.id === FIXED_ROW_ID) { return { ...row, investScale: round3Nullable(totalInvestScale), landScale: round3Nullable(totalLandScale), workload: round3Nullable(totalWorkload), hourly: round3Nullable(totalHourly), subtotal: round3Nullable(fixedSubtotal), finalFee: row.finalFee } } const subtotal = sumNullableNumbers([ row.investScale, row.landScale, row.workload, row.hourly ]) return { ...row, subtotal: round3Nullable(subtotal), finalFee: row.finalFee != null ? round3Nullable(row.finalFee) : round3Nullable(subtotal) } }) } const normalizeState = (state: ZxFwState | null | undefined): ZxFwState => ({ selectedIds: Array.isArray(state?.selectedIds) ? state.selectedIds.map(id => String(id || '')).filter(Boolean) : [], selectedCodes: Array.isArray(state?.selectedCodes) ? state.selectedCodes.map(code => String(code || '')).filter(Boolean) : [], detailRows: applyRowSubtotals(normalizeRows(state?.detailRows)) }) const cloneState = (state: ZxFwState): ZxFwState => ({ selectedIds: [...(state.selectedIds || [])], selectedCodes: [...(state.selectedCodes || [])], detailRows: state.detailRows.map(row => ({ ...row })) }) const isSameStringArray = (a: string[] | undefined, b: string[] | undefined) => { const left = Array.isArray(a) ? a : [] const right = Array.isArray(b) ? b : [] if (left.length !== right.length) return false for (let i = 0; i < left.length; i += 1) { if (left[i] !== right[i]) return false } return true } const isSameNullableNumber = (a: number | null | undefined, b: number | null | undefined) => { const left = toFiniteNumberOrNull(a) const right = toFiniteNumberOrNull(b) return left === right } const isSameRows = (a: ZxFwDetailRow[] | undefined, b: ZxFwDetailRow[] | undefined) => { const left = Array.isArray(a) ? a : [] const right = Array.isArray(b) ? b : [] if (left.length !== right.length) return false for (let i = 0; i < left.length; i += 1) { const l = left[i] const r = right[i] if (!l || !r) return false if (l.id !== r.id) return false if ((l.code || '') !== (r.code || '')) return false if ((l.name || '') !== (r.name || '')) return false if (normalizeProcessValue(l.process, l.id) !== normalizeProcessValue(r.process, r.id)) return false if (!isSameNullableNumber(l.investScale, r.investScale)) return false if (!isSameNullableNumber(l.landScale, r.landScale)) return false if (!isSameNullableNumber(l.workload, r.workload)) return false if (!isSameNullableNumber(l.hourly, r.hourly)) return false if (!isSameNullableNumber(l.subtotal, r.subtotal)) return false if (!isSameNullableNumber(l.finalFee, r.finalFee)) return false } return true } const isSameState = (a: ZxFwState | null | undefined, b: ZxFwState | null | undefined) => { if (!a || !b) return false if (!isSameStringArray(a.selectedIds, b.selectedIds)) return false if (!isSameStringArray(a.selectedCodes, b.selectedCodes)) return false return isSameRows(a.detailRows, b.detailRows) } const normalizeProjectCount = (value: unknown) => { const numeric = Number(value) if (!Number.isFinite(numeric)) return null return Math.max(1, Math.floor(numeric)) } const normalizeServiceMethodState = ( payload: Partial | null | undefined ): ServicePricingMethodState => ({ detailRows: Array.isArray(payload?.detailRows) ? cloneAny(payload.detailRows) : [], projectCount: normalizeProjectCount(payload?.projectCount) }) const parseServiceMethodStorageKey = (keyRaw: string | number) => { const key = toKey(keyRaw) if (!key) return null const firstDash = key.indexOf('-') if (firstDash <= 0 || firstDash >= key.length - 1) return null const prefix = key.slice(0, firstDash) const method = STORAGE_PREFIX_METHOD_MAP.get(prefix) if (!method) return null const rest = key.slice(firstDash + 1) const splitIndex = rest.lastIndexOf('-') if (splitIndex <= 0 || splitIndex >= rest.length - 1) return null const contractId = rest.slice(0, splitIndex).trim() const serviceId = rest.slice(splitIndex + 1).trim() if (!contractId || !serviceId) return null return { key, method, contractId, serviceId } } const loadTasks = new Map>() export const useZxFwPricingStore = defineStore('zxFwPricing', () => { let hydrationReady = false let hydrationTask: Promise | null = null const contracts = ref>({}) const contractLoaded = ref>({}) const servicePricingStates = ref>>({}) const keysStore = useZxFwPricingKeysStore() const htFeeStore = useZxFwPricingHtFeeStore() const htFeeMainStates = htFeeStore.htFeeMainStates const htFeeMethodStates = htFeeStore.htFeeMethodStates const keyedStates = keysStore.keyedStates const ensureHydrated = async () => { if (hydrationReady) return if (!hydrationTask) { hydrationTask = waitForHydration('zxFwPricing') .catch(() => undefined) .finally(() => { hydrationReady = true hydrationTask = null }) } await hydrationTask } const ensureServicePricingState = (contractIdRaw: string | number, serviceIdRaw: string | number) => { const contractId = toKey(contractIdRaw) const serviceId = toServiceKey(serviceIdRaw) if (!contractId || !serviceId) return null if (!servicePricingStates.value[contractId]) { servicePricingStates.value[contractId] = {} } if (!servicePricingStates.value[contractId][serviceId]) { servicePricingStates.value[contractId][serviceId] = {} } return servicePricingStates.value[contractId][serviceId] } const setServiceMethodStateInMemory = ( contractIdRaw: string | number, serviceIdRaw: string | number, method: ServicePricingMethod, payload: Partial | null | undefined ) => { const state = ensureServicePricingState(contractIdRaw, serviceIdRaw) if (!state) return null if (!payload) { delete state[method] return null } state[method] = normalizeServiceMethodState(payload) return state[method] || null } const getServicePricingMethodState = ( contractIdRaw: string | number, serviceIdRaw: string | number, method: ServicePricingMethod ) => { const contractId = toKey(contractIdRaw) const serviceId = toServiceKey(serviceIdRaw) if (!contractId || !serviceId) return null return (servicePricingStates.value[contractId]?.[serviceId]?.[method] as ServicePricingMethodState | undefined) || null } // 写入某合同某服务某种计费方式的明细状态。 // 默认会同步更新 keysStore,从而触发持久化;syncKeyState=false 时只回填内存态。 const setServicePricingMethodState = ( contractIdRaw: string | number, serviceIdRaw: string | number, method: ServicePricingMethod, payload: Partial> | null | undefined, options?: { force?: boolean syncKeyState?: boolean } ) => { const contractId = toKey(contractIdRaw) const serviceId = toServiceKey(serviceIdRaw) if (!contractId || !serviceId) return false const storageKey = serviceMethodDbKeyOf(contractId, serviceId, method) const force = options?.force === true const syncKeyState = options?.syncKeyState !== false const normalizedPayload = payload == null ? null : normalizeServiceMethodState(payload) const prevSnapshot = toKeySnapshot(getServicePricingMethodState(contractId, serviceId, method)) const nextSnapshot = toKeySnapshot(normalizedPayload) if (!force && prevSnapshot === nextSnapshot) return false setServiceMethodStateInMemory(contractId, serviceId, method, normalizedPayload) if (syncKeyState) { if (normalizedPayload == null) { keysStore.removeKeyState(storageKey) } else { keysStore.setKeyState(storageKey, cloneAny(normalizedPayload), { force: true }) } } return true } // 按需加载某个服务的计费方式明细。 // 若内存中已有且未强制刷新,直接返回;否则从 keysStore/KV 读取后回填到内存。 const loadServicePricingMethodState = async ( contractIdRaw: string | number, serviceIdRaw: string | number, method: ServicePricingMethod, force = false ): Promise | null> => { const contractId = toKey(contractIdRaw) const serviceId = toServiceKey(serviceIdRaw) if (!contractId || !serviceId) return null if (!force) { const existing = getServicePricingMethodState(contractId, serviceId, method) if (existing) return existing } const storageKey = serviceMethodDbKeyOf(contractId, serviceId, method) const payload = await loadKeyState>(storageKey, force) if (!payload) { setServiceMethodStateInMemory(contractId, serviceId, method, null) return null } setServicePricingMethodState(contractId, serviceId, method, payload, { force: true, syncKeyState: false }) return getServicePricingMethodState(contractId, serviceId, method) } // 删除单个服务某种计费方式的状态,并同步清理对应持久化 key。 const removeServicePricingMethodState = ( contractIdRaw: string | number, serviceIdRaw: string | number, method: ServicePricingMethod ) => { const contractId = toKey(contractIdRaw) const serviceId = toServiceKey(serviceIdRaw) if (!contractId || !serviceId) return false const storageKey = serviceMethodDbKeyOf(contractId, serviceId, method) const had = getServicePricingMethodState(contractId, serviceId, method) != null setServiceMethodStateInMemory(contractId, serviceId, method, null) keysStore.removeKeyState(storageKey) return had } // 暴露给外部使用的单个计费方式存储键,便于兼容旧逻辑直接读写底层数据。 const getServicePricingStorageKey = ( contractIdRaw: string | number, serviceIdRaw: string | number, method: ServicePricingMethod ) => { const contractId = toKey(contractIdRaw) const serviceId = toServiceKey(serviceIdRaw) if (!contractId || !serviceId) return '' return serviceMethodDbKeyOf(contractId, serviceId, method) } // 返回某个服务全部计费方式对应的存储键集合,常用于批量清理或导出。 const getServicePricingStorageKeys = (contractIdRaw: string | number, serviceIdRaw: string | number) => { const contractId = toKey(contractIdRaw) const serviceId = toServiceKey(serviceIdRaw) if (!contractId || !serviceId) return [] as string[] return (Object.keys(METHOD_STORAGE_PREFIX_MAP) as ServicePricingMethod[]).map(method => serviceMethodDbKeyOf(contractId, serviceId, method) ) } const removeAllServicePricingMethodStates = (contractIdRaw: string | number, serviceIdRaw: string | number) => { let changed = false for (const method of Object.keys(METHOD_STORAGE_PREFIX_MAP) as ServicePricingMethod[]) { changed = removeServicePricingMethodState(contractIdRaw, serviceIdRaw, method) || changed } return changed } const getHtFeeMainState = htFeeStore.getHtFeeMainState const setHtFeeMainState = htFeeStore.setHtFeeMainState const loadHtFeeMainState = htFeeStore.loadHtFeeMainState const removeHtFeeMainState = htFeeStore.removeHtFeeMainState const getHtFeeMethodStorageKey = htFeeStore.getHtFeeMethodStorageKey const getHtFeeMethodState = htFeeStore.getHtFeeMethodState const setHtFeeMethodState = htFeeStore.setHtFeeMethodState const loadHtFeeMethodState = htFeeStore.loadHtFeeMethodState const removeHtFeeMethodState = htFeeStore.removeHtFeeMethodState const getKeyState = (keyRaw: string | number): T | null => { const key = toKey(keyRaw) if (!key) return null const serviceMeta = parseServiceMethodStorageKey(key) if (serviceMeta) { const methodState = getServicePricingMethodState( serviceMeta.contractId, serviceMeta.serviceId, serviceMeta.method ) if (methodState != null) return cloneAny(methodState as T) } const htMethodMeta = parseHtFeeMethodStorageKey(key) if (htMethodMeta) { const methodState = getHtFeeMethodState( htMethodMeta.mainStorageKey, htMethodMeta.rowId, htMethodMeta.method ) if (methodState != null) return cloneAny(methodState as T) } const htMainMeta = parseHtFeeMainStorageKey(key) if (htMainMeta) { const mainState = getHtFeeMainState(htMainMeta.mainStorageKey) if (mainState != null) return cloneAny(mainState as T) } return keysStore.getKeyState(key) } const loadKeyState = async (keyRaw: string | number, force = false): Promise => { const key = toKey(keyRaw) if (!key) return null const raw = await keysStore.loadKeyState(key, force) const serviceMeta = parseServiceMethodStorageKey(key) if (serviceMeta) { setServicePricingMethodState( serviceMeta.contractId, serviceMeta.serviceId, serviceMeta.method, raw as Partial, { force: true, syncKeyState: false } ) } const htMethodMeta = parseHtFeeMethodStorageKey(key) if (htMethodMeta) { setHtFeeMethodState( htMethodMeta.mainStorageKey, htMethodMeta.rowId, htMethodMeta.method, raw, { force: true, syncKeyState: false } ) } const htMainMeta = parseHtFeeMainStorageKey(key) if (htMainMeta) { setHtFeeMainState(htMainMeta.mainStorageKey, raw as Partial, { force: true, syncKeyState: false }) } return getKeyState(key) } const setKeyState = ( keyRaw: string | number, value: T, options?: { force?: boolean } ) => { const key = toKey(keyRaw) if (!key) return false const serviceMeta = parseServiceMethodStorageKey(key) if (serviceMeta) { setServicePricingMethodState( serviceMeta.contractId, serviceMeta.serviceId, serviceMeta.method, value as Partial, { force: true, syncKeyState: false } ) } const htMethodMeta = parseHtFeeMethodStorageKey(key) if (htMethodMeta) { setHtFeeMethodState( htMethodMeta.mainStorageKey, htMethodMeta.rowId, htMethodMeta.method, value, { force: true, syncKeyState: false } ) } const htMainMeta = parseHtFeeMainStorageKey(key) if (htMainMeta) { setHtFeeMainState(htMainMeta.mainStorageKey, value as Partial, { force: true, syncKeyState: false }) } return keysStore.setKeyState(key, value, options) } const removeKeyState = (keyRaw: string | number) => { const key = toKey(keyRaw) if (!key) return false const serviceMeta = parseServiceMethodStorageKey(key) if (serviceMeta) { setServiceMethodStateInMemory(serviceMeta.contractId, serviceMeta.serviceId, serviceMeta.method, null) } const htMethodMeta = parseHtFeeMethodStorageKey(key) if (htMethodMeta) { setHtFeeMethodState(htMethodMeta.mainStorageKey, htMethodMeta.rowId, htMethodMeta.method, null, { force: true, syncKeyState: false }) } const htMainMeta = parseHtFeeMainStorageKey(key) if (htMainMeta) { setHtFeeMainState(htMainMeta.mainStorageKey, null, { force: true, syncKeyState: false }) } return keysStore.removeKeyState(key) } // 对外返回合同咨询服务状态的深拷贝,避免组件直接改写 store 内部引用。 const getContractState = (contractIdRaw: string | number) => { const contractId = toKey(contractIdRaw) if (!contractId) return null const data = contracts.value[contractId] return data ? cloneState(data) : null } // 加载合同维度的咨询服务汇总状态。 // 同一合同在并发场景下会复用同一个加载任务,避免重复读取 KV。 const loadContract = async (contractIdRaw: string | number, force = false) => { const contractId = toKey(contractIdRaw) if (!contractId) return null await ensureHydrated() if (!force && contractLoaded.value[contractId]) return getContractState(contractId) if (!force && contracts.value[contractId]) return getContractState(contractId) if (!force && loadTasks.has(contractId)) return loadTasks.get(contractId) as Promise const task = (async () => { const current = contracts.value[contractId] if (!current) { contracts.value[contractId] = normalizeState(null) } contractLoaded.value[contractId] = true return getContractState(contractId) })() loadTasks.set(contractId, task) try { return await task } finally { loadTasks.delete(contractId) } } // 整体替换某个合同的咨询服务状态。 // 入口会先标准化数据并做快照比较,只有真实变化时才递增版本号。 const setContractState = async (contractIdRaw: string | number, state: ZxFwState) => { const contractId = toKey(contractIdRaw) if (!contractId) return false const normalized = normalizeState(state) const current = contracts.value[contractId] if (current && isSameState(current, normalized)) return false contracts.value[contractId] = normalized contractLoaded.value[contractId] = true return true } // 只更新某个服务行上的单个汇总字段,适合计费页回写 investScale/landScale/workload/hourly。 // 为保证“计价法金额变化 -> 确认金额跟随小计”,这里会同步重算 finalFee: // - 普通行:finalFee = 当前四种计价法小计 // - 固定小计行:finalFee = 普通行 finalFee 合计 const updatePricingField = async (params: { contractId: string serviceId: string | number field: ZxFwPricingField value: number | null | undefined }) => { const contractId = toKey(params.contractId) if (!contractId) return false const current = contracts.value[contractId] if (!current?.detailRows?.length) return false const targetServiceId = String(params.serviceId || '').trim() if (!targetServiceId) return false const nextValue = toFiniteNumberOrNull(params.value) let changed = false const updatedRows = current.detailRows.map(row => { if (String(row.id || '') !== targetServiceId) return row const oldValue = toFiniteNumberOrNull(row[params.field]) if (oldValue === nextValue) return row changed = true return { ...row, [params.field]: nextValue } }) if (!changed) return false const rowsWithSyncedFinalFee = updatedRows.map(row => { const rowId = String(row.id || '') if (rowId === FIXED_ROW_ID) return row if (rowId !== targetServiceId) return row const rowSubtotal = sumNullableNumbers([ toFiniteNumberOrNull(row.investScale), toFiniteNumberOrNull(row.landScale), toFiniteNumberOrNull(row.workload), toFiniteNumberOrNull(row.hourly) ]) return { ...row, finalFee: round3Nullable(rowSubtotal) } }) const fixedFinalFee = round3Nullable( sumNullableNumbers( rowsWithSyncedFinalFee .filter(row => String(row.id || '') !== FIXED_ROW_ID) .map(row => toFiniteNumberOrNull(row.finalFee)) ) ) const nextRows = rowsWithSyncedFinalFee.map(row => String(row.id || '') === FIXED_ROW_ID ? { ...row, finalFee: fixedFinalFee } : row ) const nextState = normalizeState({ ...current, detailRows: nextRows }) if (isSameState(current, nextState)) return false contracts.value[contractId] = nextState contractLoaded.value[contractId] = true const targetRow = nextState.detailRows.find(row => String(row.id || '') === targetServiceId) return true } const getBaseSubtotal = (contractIdRaw: string | number): number | null => { const contractId = toKey(contractIdRaw) if (!contractId) return null const state = contracts.value[contractId] if (!state?.detailRows?.length) return null const fixedRow = state.detailRows.find(row => String(row.id || '') === FIXED_ROW_ID) const fixedFinalFee = toFiniteNumberOrNull(fixedRow?.finalFee) if (fixedFinalFee != null) return round3(fixedFinalFee) let hasValid = false const sum = state.detailRows.reduce((acc, row) => { if (String(row.id || '') === FIXED_ROW_ID) return acc const fee = toFiniteNumberOrNull(row.finalFee) ?? toFiniteNumberOrNull(row.subtotal) if (fee != null) hasValid = true return fee == null ? acc : acc + fee }, 0) return hasValid ? round3(sum) : null } // 清理合同维度的内存态、版本号以及该合同下所有相关持久化键。 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 } loadTasks.delete(contractId) changed = htFeeStore.removeContractHtFeeData(contractId) || changed const htMainPrefix = `htExtraFee-${contractId}-` changed = keysStore.removeKeysByPrefix(htMainPrefix) || changed for (const prefix of Object.values(METHOD_STORAGE_PREFIX_MAP)) { changed = keysStore.removeKeysByPrefix(`${prefix}-${contractId}-`) || changed } return changed } return { contracts, contractLoaded, servicePricingStates, htFeeMainStates, htFeeMethodStates, keyedStates, getContractState, loadContract, setContractState, updatePricingField, getBaseSubtotal, removeContractData, getKeyState, loadKeyState, setKeyState, removeKeyState, getServicePricingMethodState, setServicePricingMethodState, loadServicePricingMethodState, removeServicePricingMethodState, getServicePricingStorageKey, getServicePricingStorageKeys, removeAllServicePricingMethodStates, getHtFeeMainState, setHtFeeMainState, loadHtFeeMainState, removeHtFeeMainState, getHtFeeMethodStorageKey, getHtFeeMethodState, setHtFeeMethodState, loadHtFeeMethodState, removeHtFeeMethodState } }, { persist: true })