完成大部分
This commit is contained in:
1 parent
e97707ac59
commit
13b03e016e
21 files changed
+1628
-275
No files matched your search
+61
-24
@@ -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"
|
||||
/>
|
||||
|
||||
Reference in new issue
Block a user