From 2f6acb7fc60d33ad625664d5809a31d752e9ab2f Mon Sep 17 00:00:00 2001 From: Aisland Date: Tue, 1 Sep 2026 13:31:02 +0800 Subject: [PATCH] =?UTF-8?q?fix(console):=20=E7=BA=AF=E8=89=B2=E6=81=A2?= =?UTF-8?q?=E5=A4=8D=E5=88=9D=E5=A7=8B=E5=9B=9E=E5=88=B0=E5=8E=9F=E8=83=8C?= =?UTF-8?q?=E6=99=AF=E8=89=B2(#0a0d13,=20hsl=20-140/31/6)=EF=BC=9B?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=8A=E4=BC=A0=E5=90=8E=E7=BC=A9=E7=95=A5?= =?UTF-8?q?=E5=9B=BE=E6=8C=82=E5=9B=BE=EF=BC=88=E4=B8=8D=E5=86=8D=E6=92=A4?= =?UTF-8?q?=E9=94=80=E5=85=B1=E4=BA=AB=E5=AF=B9=E8=B1=A1URL=EF=BC=89?= =?UTF-8?q?=EF=BC=9BphotoStore=20=E8=BF=81=E7=A7=BB=E6=94=B9=E6=83=B0?= =?UTF-8?q?=E6=80=A7=E3=80=81=E5=8D=87=E7=BA=A7=E4=BA=8B=E5=8A=A1=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E5=88=A0=E6=97=A7store?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 01_content/lib/client.js | 120 +++++++++++++--------------- 01_content/src/client/index.tsx | 2 +- 01_content/src/client/photoStore.ts | 84 ++++++++++--------- 01_content/src/client/split.tsx | 11 +-- 4 files changed, 107 insertions(+), 110 deletions(-) diff --git a/01_content/lib/client.js b/01_content/lib/client.js index 623e419..1d88f50 100644 --- a/01_content/lib/client.js +++ b/01_content/lib/client.js @@ -17368,7 +17368,7 @@ function ConsolePane() { const photoUrlRef = (0, import_react.useRef)(""); const [openMenu, setOpenMenu] = (0, import_react.useState)(null); const [bgEdit, setBgEdit] = (0, import_react.useState)(null); - const [plainHsl, setPlainHslState] = (0, import_react.useState)(() => splitEnv?.console?.getPlainHsl?.() ?? { h: 220, s: 31, l: 6 }); + const [plainHsl, setPlainHslState] = (0, import_react.useState)(() => splitEnv?.console?.getPlainHsl?.() ?? { h: -140, s: 31, l: 6 }); const [glowHsl, setGlowHslState] = (0, import_react.useState)(() => splitEnv?.console?.getGlowHsl?.() ?? { h: 0, s: 100, l: 100 }); const [photoHsl, setPhotoHslState] = (0, import_react.useState)({ h: 0, s: 100, l: 100 }); const gridRef = (0, import_react.useRef)(null); @@ -17405,19 +17405,13 @@ function ConsolePane() { const onFilePhoto = async (file) => { let id = ""; let url = ""; - const rec = await splitEnv?.console?.addPhoto?.(file).catch(() => null); + const rec = await (splitEnv?.console?.addPhoto?.(file) ?? Promise.resolve(null)).catch(() => null); if (rec) { id = rec.id; url = rec.url; } else { url = URL.createObjectURL(file); } - if (photoUrlRef.current) { - try { - URL.revokeObjectURL(photoUrlRef.current); - } catch { - } - } photoUrlRef.current = url; setPhotoIdLocal(id); if (id) setPhotoList((prev) => [{ id, url }, ...prev]); @@ -17428,12 +17422,6 @@ function ConsolePane() { const selectPhoto = (p) => { setPhotoIdLocal(p.id); splitEnv?.console?.setPhotoId?.(p.id); - if (photoUrlRef.current) { - try { - URL.revokeObjectURL(photoUrlRef.current); - } catch { - } - } photoUrlRef.current = p.url; setBgPhoto(p.url); setBgState("photo"); @@ -17464,8 +17452,8 @@ function ConsolePane() { }; const resetHsl = (kind) => { if (kind === "plain") { - setPlainHslState({ h: 0, s: 31, l: 6 }); - splitEnv?.console?.setPlainHsl?.({ h: 0, s: 31, l: 6 }); + setPlainHslState({ h: -140, s: 31, l: 6 }); + splitEnv?.console?.setPlainHsl?.({ h: -140, s: 31, l: 6 }); } else if (kind === "glow") { setGlowHslState({ h: 0, s: 100, l: 100 }); splitEnv?.console?.setGlowHsl?.({ h: 0, s: 100, l: 100 }); @@ -17549,12 +17537,6 @@ function ConsolePane() { const active = lib2.list.find((p) => p.id === lib2.activeId) ?? lib2.list[0] ?? null; if (active) { setPhotoIdLocal(active.id); - if (photoUrlRef.current) { - try { - URL.revokeObjectURL(photoUrlRef.current); - } catch { - } - } photoUrlRef.current = active.url; setBgPhoto(active.url); } @@ -18818,59 +18800,65 @@ function openDb() { req.onupgradeneeded = () => { const db = req.result; if (!db.objectStoreNames.contains(STORE)) db.createObjectStore(STORE); - if (db.objectStoreNames.contains(LEGACY_STORE)) { - try { - const tx = req.transaction; - const getReq = tx.objectStore(LEGACY_STORE).get(LEGACY_KEY); - getReq.onsuccess = () => { - try { - const blob = getReq.result; - if (blob instanceof Blob) { - const rec = { id: "legacy", createdAt: Date.now(), blob }; - tx.objectStore(STORE).put(rec, rec.id); - } - tx.objectStore(LEGACY_STORE).delete(LEGACY_KEY); - } catch { - } - }; - } catch { - } - try { - db.deleteObjectStore(LEGACY_STORE); - } catch { - } - } }; req.onsuccess = () => resolve(req.result); req.onerror = () => reject(req.error); }); } +function getAll(db) { + return new Promise((resolve, reject) => { + const tx = db.transaction(STORE, "readonly"); + const req = tx.objectStore(STORE).getAll(); + req.onsuccess = () => { + const arr = []; + for (const v of req.result) { + const r = v; + if (r && typeof r === "object" && typeof r.id === "string" && r.blob instanceof Blob) { + arr.push(r); + } + } + arr.sort((a, b) => b.createdAt - a.createdAt); + db.close(); + resolve(arr); + }; + req.onerror = () => { + db.close(); + reject(req.error); + }; + }); +} var photoStore = { - /** 全部照片记录(最新在前) */ + /** 全部照片记录(最新在前);无记录时惰性迁移 v1 单图 */ async list() { const db = await openDb(); - return new Promise((resolve, reject) => { - const tx = db.transaction(STORE, "readonly"); - const req = tx.objectStore(STORE).getAll(); - req.onsuccess = () => { - const arr = []; - for (const v of req.result) { - const r = v; - if (r && typeof r === "object" && typeof r.id === "string" && r.blob instanceof Blob) { - arr.push(r); - } + const main = await getAll(db); + if (main.length > 0) return main; + try { + if (db.objectStoreNames.contains(LEGACY_STORE)) { + const blob = await new Promise((resolve, reject) => { + const tx = db.transaction(LEGACY_STORE, "readonly"); + const req = tx.objectStore(LEGACY_STORE).get(LEGACY_KEY); + req.onsuccess = () => resolve(req.result instanceof Blob ? req.result : null); + req.onerror = () => reject(req.error); + }); + if (blob) { + const rec = { id: "legacy", createdAt: Date.now(), blob }; + await new Promise((resolve, reject) => { + const tx = db.transaction(STORE, "readwrite"); + tx.objectStore(STORE).put(rec, rec.id); + tx.oncomplete = () => resolve(); + tx.onerror = () => reject(tx.error); + }); + db.close(); + return [rec]; } - arr.sort((a, b) => b.createdAt - a.createdAt); - db.close(); - resolve(arr); - }; - req.onerror = () => { - db.close(); - reject(req.error); - }; - }); + } + } catch { + } + db.close(); + return []; }, - /** 新增一张原始照片,返回其 id(自动置为最新) */ + /** 新增一张原始照片,返回其 id(自动成为最新) */ async add(blob) { const db = await openDb(); const id = Date.now().toString(36) + Math.random().toString(36).slice(2, 7); @@ -20150,7 +20138,7 @@ function WorktableSection(props) { return { id, url: URL.createObjectURL(blob) }; }, setPhotoId: (id) => persistView({ consoleBgPhotoId: id }), - getPlainHsl: () => viewRef.current.consoleBgPlainHsl ?? { h: 0, s: 31, l: 6 }, + getPlainHsl: () => viewRef.current.consoleBgPlainHsl ?? { h: -140, s: 31, l: 6 }, setPlainHsl: (v) => persistView({ consoleBgPlainHsl: { ...v } }), getGlowHsl: () => viewRef.current.consoleBgGlowHsl ?? { h: 0, s: 100, l: 100 }, setGlowHsl: (v) => persistView({ consoleBgGlowHsl: { ...v } }), diff --git a/01_content/src/client/index.tsx b/01_content/src/client/index.tsx index 8d5cb91..be99426 100644 --- a/01_content/src/client/index.tsx +++ b/01_content/src/client/index.tsx @@ -1394,7 +1394,7 @@ function WorktableSection(props: any) { return { id, url: URL.createObjectURL(blob) } }, setPhotoId: (id: string) => persistView({ consoleBgPhotoId: id }), - getPlainHsl: () => viewRef.current.consoleBgPlainHsl ?? { h: 0, s: 31, l: 6 }, + getPlainHsl: () => viewRef.current.consoleBgPlainHsl ?? { h: -140, s: 31, l: 6 }, setPlainHsl: (v: { h: number; s: number; l: number }) => persistView({ consoleBgPlainHsl: { ...v } }), getGlowHsl: () => viewRef.current.consoleBgGlowHsl ?? { h: 0, s: 100, l: 100 }, setGlowHsl: (v: { h: number; s: number; l: number }) => persistView({ consoleBgGlowHsl: { ...v } }), diff --git a/01_content/src/client/photoStore.ts b/01_content/src/client/photoStore.ts index 98988b6..a38806a 100644 --- a/01_content/src/client/photoStore.ts +++ b/01_content/src/client/photoStore.ts @@ -1,5 +1,6 @@ /** dsh-worktable 控制室背景照片库:IndexedDB 存原始图片(Blob 记录,零压缩)。 - * v2:多张照片记录(id/createdAt/blob),v1 单图自动迁移为一条 'legacy' 记录。 */ + * v2:多张照片记录(id/createdAt/blob);v1 单图在首次 list() 时惰性迁移为 'legacy' 记录。 + * (不在 onupgradeneeded 里动旧 store —— 升级事务内异步读写易出竞态,迁移放到读路径最稳。) */ const DB_NAME = 'dsh-worktable' const STORE = 'photoRecords' const LEGACY_STORE = 'consoleBgPhoto' @@ -13,53 +14,64 @@ function openDb(): Promise { req.onupgradeneeded = () => { const db = req.result if (!db.objectStoreNames.contains(STORE)) db.createObjectStore(STORE) - // v1→v2 迁移:旧单图(consoleBgPhoto/original)转为一条 'legacy' 记录 - if (db.objectStoreNames.contains(LEGACY_STORE)) { - try { - const tx = req.transaction as IDBTransaction - const getReq = tx.objectStore(LEGACY_STORE).get(LEGACY_KEY) - getReq.onsuccess = () => { - try { - const blob = getReq.result - if (blob instanceof Blob) { - const rec: PhotoRecord = { id: 'legacy', createdAt: Date.now(), blob } - tx.objectStore(STORE).put(rec, rec.id) - } - tx.objectStore(LEGACY_STORE).delete(LEGACY_KEY) - } catch {} - } - } catch {} - try { db.deleteObjectStore(LEGACY_STORE) } catch {} - } } req.onsuccess = () => resolve(req.result) req.onerror = () => reject(req.error) }) } +function getAll(db: IDBDatabase): Promise { + return new Promise((resolve, reject) => { + const tx = db.transaction(STORE, 'readonly') + const req = tx.objectStore(STORE).getAll() + req.onsuccess = () => { + const arr: PhotoRecord[] = [] + for (const v of req.result as unknown[]) { + const r = v as PhotoRecord | null + if (r && typeof r === 'object' && typeof (r as any).id === 'string' && (r as any).blob instanceof Blob) { + arr.push(r) + } + } + arr.sort((a, b) => b.createdAt - a.createdAt) + db.close() + resolve(arr) + } + req.onerror = () => { db.close(); reject(req.error) } + }) +} + export const photoStore = { - /** 全部照片记录(最新在前) */ + /** 全部照片记录(最新在前);无记录时惰性迁移 v1 单图 */ async list(): Promise { const db = await openDb() - return new Promise((resolve, reject) => { - const tx = db.transaction(STORE, 'readonly') - const req = tx.objectStore(STORE).getAll() - req.onsuccess = () => { - const arr: PhotoRecord[] = [] - for (const v of req.result as unknown[]) { - const r = v as PhotoRecord | null - if (r && typeof r === 'object' && typeof (r as any).id === 'string' && (r as any).blob instanceof Blob) { - arr.push(r) - } + const main = await getAll(db) + if (main.length > 0) return main + // 迁移旧单图(若有):读取 → 转为 'legacy' 记录 + try { + if (db.objectStoreNames.contains(LEGACY_STORE)) { + const blob = await new Promise((resolve, reject) => { + const tx = db.transaction(LEGACY_STORE, 'readonly') + const req = tx.objectStore(LEGACY_STORE).get(LEGACY_KEY) + req.onsuccess = () => resolve(req.result instanceof Blob ? req.result : null) + req.onerror = () => reject(req.error) + }) + if (blob) { + const rec: PhotoRecord = { id: 'legacy', createdAt: Date.now(), blob } + await new Promise((resolve, reject) => { + const tx = db.transaction(STORE, 'readwrite') + tx.objectStore(STORE).put(rec, rec.id) + tx.oncomplete = () => resolve() + tx.onerror = () => reject(tx.error) + }) + db.close() + return [rec] } - arr.sort((a, b) => b.createdAt - a.createdAt) - db.close() - resolve(arr) } - req.onerror = () => { db.close(); reject(req.error) } - }) + } catch {} + db.close() + return [] }, - /** 新增一张原始照片,返回其 id(自动置为最新) */ + /** 新增一张原始照片,返回其 id(自动成为最新) */ async add(blob: Blob): Promise { const db = await openDb() const id = Date.now().toString(36) + Math.random().toString(36).slice(2, 7) diff --git a/01_content/src/client/split.tsx b/01_content/src/client/split.tsx index 1427a3e..f794f31 100644 --- a/01_content/src/client/split.tsx +++ b/01_content/src/client/split.tsx @@ -1090,7 +1090,7 @@ function ConsolePane() { const photoUrlRef = useRef('') const [openMenu, setOpenMenu] = useState(null) const [bgEdit, setBgEdit] = useState<'plain' | 'glow' | 'photo' | null>(null) - const [plainHsl, setPlainHslState] = useState<{ h: number; s: number; l: number }>(() => splitEnv?.console?.getPlainHsl?.() ?? { h: 220, s: 31, l: 6 }) + const [plainHsl, setPlainHslState] = useState<{ h: number; s: number; l: number }>(() => splitEnv?.console?.getPlainHsl?.() ?? { h: -140, s: 31, l: 6 }) const [glowHsl, setGlowHslState] = useState<{ h: number; s: number; l: number }>(() => splitEnv?.console?.getGlowHsl?.() ?? { h: 0, s: 100, l: 100 }) const [photoHsl, setPhotoHslState] = useState<{ h: number; s: number; l: number }>({ h: 0, s: 100, l: 100 }) const gridRef = useRef(null) @@ -1129,11 +1129,10 @@ function ConsolePane() { const onFilePhoto = async (file: Blob) => { let id = '' let url = '' - const rec = await splitEnv?.console?.addPhoto?.(file).catch(() => null) + const rec = await (splitEnv?.console?.addPhoto?.(file) ?? Promise.resolve(null)).catch(() => null) if (rec) { id = rec.id; url = rec.url } else { url = URL.createObjectURL(file) } - if (photoUrlRef.current) { try { URL.revokeObjectURL(photoUrlRef.current) } catch {} } photoUrlRef.current = url setPhotoIdLocal(id) if (id) setPhotoList((prev) => [{ id, url }, ...prev]) @@ -1145,7 +1144,6 @@ function ConsolePane() { const selectPhoto = (p: { id: string; url: string }) => { setPhotoIdLocal(p.id) splitEnv?.console?.setPhotoId?.(p.id) - if (photoUrlRef.current) { try { URL.revokeObjectURL(photoUrlRef.current) } catch {} } photoUrlRef.current = p.url setBgPhoto(p.url) setBgState('photo') @@ -1177,8 +1175,8 @@ function ConsolePane() { } const resetHsl = (kind: 'plain' | 'glow' | 'photo') => { if (kind === 'plain') { - setPlainHslState({ h: 0, s: 31, l: 6 }) - splitEnv?.console?.setPlainHsl?.({ h: 0, s: 31, l: 6 }) + setPlainHslState({ h: -140, s: 31, l: 6 }) + splitEnv?.console?.setPlainHsl?.({ h: -140, s: 31, l: 6 }) } else if (kind === 'glow') { setGlowHslState({ h: 0, s: 100, l: 100 }) splitEnv?.console?.setGlowHsl?.({ h: 0, s: 100, l: 100 }) @@ -1239,7 +1237,6 @@ function ConsolePane() { const active = lib.list.find((p) => p.id === lib.activeId) ?? lib.list[0] ?? null if (active) { setPhotoIdLocal(active.id) - if (photoUrlRef.current) { try { URL.revokeObjectURL(photoUrlRef.current) } catch {} } photoUrlRef.current = active.url setBgPhoto(active.url) }