完成大部分

This commit is contained in:
zwgczx committed 2026-02-28 16:34:36 +08:00
1 parent e97707ac59
commit 13b03e016e
21 files changed
+1628 -275

No files matched your search

+61 -24
View File
@@ -18,6 +18,7 @@ import {
AlertDialogTitle,
AlertDialogTrigger,
} from 'reka-ui'
import { decodeZwArchive, encodeZwArchive, ZW_FILE_EXTENSION } from '@/lib/zwArchive'
interface DataEntry {
key: string
@@ -30,7 +31,10 @@ interface DataPackage {
localStorage: DataEntry[]
sessionStorage: DataEntry[]
localforageDefault: DataEntry[]
localforageFormState: DataEntry[]
}
type XmInfoLike = {
projectName?: unknown
}
const componentMap: Record<string, any> = {
@@ -41,10 +45,7 @@ const componentMap: Record<string, any> = {
const tabStore = useTabStore()
const formStore = localforage.createInstance({
name: 'jgjs-pricing-db',
storeName: 'form_state'
})
const tabContextOpen = ref(false)
const tabContextX = ref(0)
@@ -242,23 +243,57 @@ const writeForage = async (store: typeof localforage, entries: DataEntry[]) => {
}
}
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 }))
}
const sanitizeFileNamePart = (value: string): string => {
const cleaned = value
.replace(/[\\/:*?"<>|]/g, '_')
.replace(/\s+/g, ' ')
.trim()
return cleaned || '造价项目'
}
const formatExportTimestamp = (date: Date): string => {
const yyyy = date.getFullYear()
const mm = String(date.getMonth() + 1).padStart(2, '0')
const dd = String(date.getDate()).padStart(2, '0')
const hh = String(date.getHours()).padStart(2, '0')
const mi = String(date.getMinutes()).padStart(2, '0')
return `${yyyy}${mm}${dd}-${hh}${mi}`
}
const getExportProjectName = (entries: DataEntry[]): string => {
const target = entries.find(item => item.key === 'xm-info-v3')
const data = (target?.value || {}) as XmInfoLike
return typeof data.projectName === 'string' ? sanitizeFileNamePart(data.projectName) : '造价项目'
}
const exportData = async () => {
try {
const now = new Date()
const payload: DataPackage = {
version: 1,
exportedAt: new Date().toISOString(),
exportedAt: now.toISOString(),
localStorage: readWebStorage(localStorage),
sessionStorage: readWebStorage(sessionStorage),
localforageDefault: await readForage(localforage),
localforageFormState: await readForage(formStore as any)
}
const content = JSON.stringify(payload, null, 2)
const blob = new Blob([content], { type: 'application/json;charset=utf-8' })
const content = await encodeZwArchive(payload)
const binary = new Uint8Array(content.length)
binary.set(content)
const blob = new Blob([binary], { type: 'application/octet-stream' })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `造价项目-${new Date().toISOString().slice(0, 19).replace(/[:T]/g, '-')}.json`
const projectName = getExportProjectName(payload.localforageDefault)
const timestamp = formatExportTimestamp(now)
link.download = `${projectName}-${timestamp}${ZW_FILE_EXTENSION}`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
@@ -281,20 +316,22 @@ const importData = async (event: Event) => {
if (!file) return
try {
const text = await file.text()
const payload = JSON.parse(text) as DataPackage
if (!file.name.toLowerCase().endsWith(ZW_FILE_EXTENSION)) {
throw new Error('INVALID_FILE_EXT')
}
const buffer = await file.arrayBuffer()
const payload = await decodeZwArchive<DataPackage>(buffer)
writeWebStorage(localStorage, payload.localStorage || [])
writeWebStorage(sessionStorage, payload.sessionStorage || [])
await writeForage(localforage, payload.localforageDefault || [])
await writeForage(formStore as any, payload.localforageFormState || [])
writeWebStorage(localStorage, normalizeEntries(payload.localStorage))
writeWebStorage(sessionStorage, normalizeEntries(payload.sessionStorage))
await writeForage(localforage, normalizeEntries(payload.localforageDefault))
tabStore.resetTabs()
dataMenuOpen.value = false
window.location.reload()
} catch (error) {
console.error('import failed:', error)
window.alert('导入失败,文件格式不正确。')
window.alert('导入失败:文件无效、已损坏或被修改。')
} finally {
input.value = ''
}
@@ -397,31 +434,31 @@ watch(
</ScrollArea>
<div ref="dataMenuRef" class="relative mb-2 shrink-0">
<Button variant="outline" size="sm" @click="dataMenuOpen = !dataMenuOpen">
<Button variant="outline" size="sm" class="cursor-pointer" @click="dataMenuOpen = !dataMenuOpen">
<ChevronDown class="h-4 w-4 mr-1" />
导入/导出
</Button>
<div
v-if="dataMenuOpen"
class="absolute right-0 top-full mt-1 z-50 min-w-[140px] rounded-md border bg-background p-1 shadow-md"
class="absolute right-0 top-full mt-1 z-50 min-w-[108px] rounded-md border bg-background p-1 shadow-md"
>
<button
class="w-full rounded px-3 py-1.5 text-left text-sm hover:bg-muted"
class="w-full cursor-pointer rounded px-3 py-1.5 text-left text-sm hover:bg-muted"
@click="exportData"
>
导出数据
导出
</button>
<button
class="w-full rounded px-3 py-1.5 text-left text-sm hover:bg-muted"
class="w-full cursor-pointer rounded px-3 py-1.5 text-left text-sm hover:bg-muted"
@click="triggerImport"
>
导入数据
导入
</button>
</div>
<input
ref="importFileRef"
type="file"
accept="application/json,.json"
accept=".zw"
class="hidden"
@change="importData"
/>