fix: 多选同步弹窗复用站点同步表单缓存

- 抽取站点地址与 Token 的 24 小时缓存工具
- 存储页与多选同步弹窗共用 pichost.site-sync-form
- 弹窗打开时读取最新缓存,修改后写回
- 增加缓存读取与过期测试
This commit is contained in:
tianmo committed 2026-10-19 16:10:48 +08:00
1 parent 58424fe55d
commit 366b8a4272
4 files changed
+122 -31

No files matched your search

+5 -31
View File
@@ -3,6 +3,7 @@ import type {
SiteSyncDirection, SiteSyncDirection,
SiteSyncRequest SiteSyncRequest
} from '~/types/backup' } from '~/types/backup'
import { loadSiteSyncForm, saveSiteSyncForm } from '~/utils/site-sync-form'
const props = defineProps<{ const props = defineProps<{
busy: boolean busy: boolean
@@ -30,42 +31,15 @@ const conflictMode = ref<'skip' | 'overwrite'>('skip')
const direction = ref<SiteSyncDirection>('push') const direction = ref<SiteSyncDirection>('push')
const scope = ref<'all' | 'image' | 'video' | 'file'>('all') const scope = ref<'all' | 'image' | 'video' | 'file'>('all')
/** 目标站点地址与 Token 自动保存 24 小时 */
const FORM_STORAGE_KEY = 'pichost.site-sync-form'
const FORM_TTL_MS = 24 * 60 * 60 * 1000
function loadSavedForm() { function loadSavedForm() {
if (!import.meta.client) return if (!import.meta.client) return
try { const saved = loadSiteSyncForm()
const raw = localStorage.getItem(FORM_STORAGE_KEY) if (saved?.targetBaseUrl) targetBaseUrl.value = saved.targetBaseUrl
if (!raw) return if (saved?.token) token.value = saved.token
const data = JSON.parse(raw) as {
targetBaseUrl?: string
token?: string
savedAt?: number
}
if (!data.savedAt || Date.now() - data.savedAt > FORM_TTL_MS) {
localStorage.removeItem(FORM_STORAGE_KEY)
return
}
if (data.targetBaseUrl) targetBaseUrl.value = data.targetBaseUrl
if (data.token) token.value = data.token
} catch {
localStorage.removeItem(FORM_STORAGE_KEY)
}
} }
function persistForm() { function persistForm() {
if (!import.meta.client) return saveSiteSyncForm(targetBaseUrl.value, token.value)
try {
localStorage.setItem(FORM_STORAGE_KEY, JSON.stringify({
targetBaseUrl: targetBaseUrl.value,
token: token.value,
savedAt: Date.now()
}))
} catch {
// 存储不可用时忽略
}
} }
watch([targetBaseUrl, token], persistForm) watch([targetBaseUrl, token], persistForm)
@@ -1,4 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { loadSiteSyncForm, saveSiteSyncForm } from '~/utils/site-sync-form'
const props = defineProps<{ const props = defineProps<{
open: boolean open: boolean
keys: string[] keys: string[]
@@ -17,6 +19,19 @@ const token = ref('')
const conflictMode = ref<'skip' | 'overwrite'>('skip') const conflictMode = ref<'skip' | 'overwrite'>('skip')
const busy = ref(false) const busy = ref(false)
watch(() => props.open, (open) => {
if (!open) return
const saved = loadSiteSyncForm()
if (saved) {
targetBaseUrl.value = saved.targetBaseUrl
token.value = saved.token
}
}, { immediate: true })
watch([targetBaseUrl, token], () => {
saveSiteSyncForm(targetBaseUrl.value, token.value)
})
const canSubmit = computed(() => const canSubmit = computed(() =>
!busy.value !busy.value
&& props.keys.length > 0 && props.keys.length > 0
+50
View File
@@ -0,0 +1,50 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import {
loadSiteSyncForm,
saveSiteSyncForm,
SITE_SYNC_FORM_STORAGE_KEY
} from './site-sync-form'
function createStorage(): Storage {
const values = new Map<string, string>()
return {
get length() {
return values.size
},
clear: () => values.clear(),
getItem: key => values.get(key) ?? null,
key: index => [...values.keys()][index] ?? null,
removeItem: key => values.delete(key),
setItem: (key, value) => values.set(key, value)
}
}
describe('site sync form cache', () => {
afterEach(() => {
vi.useRealTimers()
vi.unstubAllGlobals()
})
it('reads values saved by another component', () => {
vi.stubGlobal('localStorage', createStorage())
saveSiteSyncForm('http://127.0.0.1:3010', 'secret-token')
expect(loadSiteSyncForm()).toEqual({
targetBaseUrl: 'http://127.0.0.1:3010',
token: 'secret-token'
})
})
it('drops the cache after 24 hours', () => {
const storage = createStorage()
vi.stubGlobal('localStorage', storage)
vi.useFakeTimers()
vi.setSystemTime(new Date('2026-10-19T00:00:00Z'))
saveSiteSyncForm('http://target', 'token')
vi.setSystemTime(new Date('2026-10-20T00:00:01Z'))
expect(loadSiteSyncForm()).toBeNull()
expect(storage.getItem(SITE_SYNC_FORM_STORAGE_KEY)).toBeNull()
})
})
+52
View File
@@ -0,0 +1,52 @@
export const SITE_SYNC_FORM_STORAGE_KEY = 'pichost.site-sync-form'
export const SITE_SYNC_FORM_TTL_MS = 24 * 60 * 60 * 1000
export interface SiteSyncFormCache {
targetBaseUrl: string
token: string
}
function getStorage(): Storage | null {
return typeof localStorage === 'undefined' ? null : localStorage
}
export function loadSiteSyncForm(): SiteSyncFormCache | null {
const storage = getStorage()
if (!storage) return null
try {
const raw = storage.getItem(SITE_SYNC_FORM_STORAGE_KEY)
if (!raw) return null
const data = JSON.parse(raw) as {
targetBaseUrl?: unknown
token?: unknown
savedAt?: unknown
}
if (typeof data.savedAt !== 'number' || Date.now() - data.savedAt > SITE_SYNC_FORM_TTL_MS) {
storage.removeItem(SITE_SYNC_FORM_STORAGE_KEY)
return null
}
return {
targetBaseUrl: typeof data.targetBaseUrl === 'string' ? data.targetBaseUrl : '',
token: typeof data.token === 'string' ? data.token : ''
}
} catch {
storage.removeItem(SITE_SYNC_FORM_STORAGE_KEY)
return null
}
}
export function saveSiteSyncForm(targetBaseUrl: string, token: string): void {
const storage = getStorage()
if (!storage) return
try {
storage.setItem(SITE_SYNC_FORM_STORAGE_KEY, JSON.stringify({
targetBaseUrl,
token,
savedAt: Date.now()
}))
} catch {
// 存储不可用时忽略
}
}