132 lines
4.1 KiB
TypeScript
132 lines
4.1 KiB
TypeScript
import type localforage from 'localforage'
|
|
|
|
export interface DataEntry {
|
|
key: string
|
|
value: any
|
|
}
|
|
|
|
export interface ForageStoreSnapshot {
|
|
storeName: string
|
|
entries: DataEntry[]
|
|
}
|
|
|
|
export interface DataPackage {
|
|
version: number
|
|
packageType?: 'project-snapshot'
|
|
exportedAt: string
|
|
projectId?: string
|
|
localStorage: DataEntry[]
|
|
sessionStorage: DataEntry[]
|
|
localforageDefault: DataEntry[]
|
|
localforageStores?: ForageStoreSnapshot[]
|
|
}
|
|
|
|
export type ForageInstance = ReturnType<typeof localforage.createInstance>
|
|
export type ForageStore = Pick<ForageInstance, 'keys' | 'getItem' | 'setItem' | 'clear'>
|
|
|
|
type XmInfoLike = {
|
|
projectName?: unknown
|
|
}
|
|
|
|
export const readWebStorage = (storageObj: Storage): DataEntry[] => {
|
|
const entries: DataEntry[] = []
|
|
for (let i = 0; i < storageObj.length; i++) {
|
|
const key = storageObj.key(i)
|
|
if (!key) continue
|
|
const raw = storageObj.getItem(key)
|
|
let value: any = raw
|
|
if (raw != null) {
|
|
try {
|
|
value = JSON.parse(raw)
|
|
} catch {
|
|
value = raw
|
|
}
|
|
}
|
|
entries.push({ key, value })
|
|
}
|
|
return entries
|
|
}
|
|
|
|
export const writeWebStorage = (storageObj: Storage, entries: DataEntry[]) => {
|
|
storageObj.clear()
|
|
for (const entry of entries || []) {
|
|
const value = typeof entry.value === 'string' ? entry.value : JSON.stringify(entry.value)
|
|
storageObj.setItem(entry.key, value)
|
|
}
|
|
}
|
|
|
|
export 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
|
|
}
|
|
}
|
|
|
|
export const readForage = async (store: ForageStore): Promise<DataEntry[]> => {
|
|
const keys = await store.keys()
|
|
const values = await Promise.all(keys.map(key => store.getItem(key)))
|
|
return keys.map((key, index) => ({
|
|
key,
|
|
value: toPersistableValue(values[index])
|
|
}))
|
|
}
|
|
|
|
export const writeForage = async (store: ForageStore, entries: DataEntry[]) => {
|
|
await store.clear()
|
|
await Promise.all((entries || []).map(entry => store.setItem(entry.key, toPersistableValue(entry.value))))
|
|
}
|
|
|
|
export const normalizeEntries = (value: unknown): DataEntry[] => {
|
|
if (!Array.isArray(value)) return []
|
|
return value
|
|
.filter(item => item && typeof item === 'object' && typeof (item as any).key === 'string')
|
|
.map(item => ({ key: String((item as any).key), value: (item as any).value }))
|
|
}
|
|
|
|
export 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)
|
|
}))
|
|
}
|
|
|
|
export const sanitizeFileNamePart = (value: string): string => {
|
|
const cleaned = value
|
|
.replace(/[\\/:*?"<>|]/g, '_')
|
|
.replace(/\s+/g, ' ')
|
|
.trim()
|
|
return cleaned || '造价项目'
|
|
}
|
|
|
|
export const getExportProjectName = (entries: DataEntry[], projectInfoDbKey: string, legacyProjectDbKey: string) => {
|
|
const target =
|
|
entries.find(item => item.key === projectInfoDbKey) ||
|
|
entries.find(item => item.key === legacyProjectDbKey)
|
|
const data = (target?.value || {}) as XmInfoLike
|
|
return typeof data.projectName === 'string' ? sanitizeFileNamePart(data.projectName) : '造价项目'
|
|
}
|
|
|
|
export const isDataPackageLike = (value: unknown): value is DataPackage => {
|
|
if (!value || typeof value !== 'object') return false
|
|
const payload = value as Partial<DataPackage>
|
|
const hasRequiredArrays =
|
|
Array.isArray(payload.localStorage) &&
|
|
Array.isArray(payload.sessionStorage) &&
|
|
Array.isArray(payload.localforageDefault)
|
|
if (!hasRequiredArrays) return false
|
|
if (typeof payload.version !== 'number' || !Number.isFinite(payload.version)) return false
|
|
if (payload.packageType != null && payload.packageType !== 'project-snapshot') return false
|
|
if (payload.projectId != null && typeof payload.projectId !== 'string') return false
|
|
return true
|
|
}
|