feat(web): 技术信息折叠、原因码人话化、待处理「指定作品」(DEAI-2)
- 新增 TechInfo 折叠组件(默认收起,标题「技术信息」);设置新增「界面」分区, 「默认展开技术信息」开关存本机浏览器 - 原因码表 @umt/contracts reasons.ts(identity_unknown、blocked:target_exists、below_min_size… → 一句人话 + 下一步),服务端整理记录 message 与冲突事项标题/摘要改用它 - 整理记录列表/详情、下载详情与时间线:原码、ID、hash、用时、变更序号进技术信息; 下载完成但入库失败时单独提示;时间线只列有记录的阶段,标题改「经过」 - 待处理:没识别出作品类事项主按钮「指定作品」(搜媒体库 + TMDB,走已有 resolve; 整理类事项打开重新整理),忽略为次要;去掉正常态徽章与「实时」指示;原始证据改「详细信息」进技术信息 - 整理成功后同时关闭该下载的「没识别出作品」事项 Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
4480fc346d
commit
f0d4935fa7
21 files changed
+688
-78
No files matched your search
@@ -0,0 +1,72 @@
|
||||
import { useSyncExternalStore } from "react";
|
||||
|
||||
// 本机界面偏好(设置 → 界面):只影响当前浏览器,存 localStorage;读写失败(隐私模式、禁用存储)时用默认值。
|
||||
|
||||
export type UiPrefs = {
|
||||
/** 技术信息(ID、hash、token、耗时、原始字段、内部状态码)默认展开 */
|
||||
techInfoExpanded: boolean;
|
||||
};
|
||||
|
||||
const KEY = "umt.ui-prefs";
|
||||
const DEFAULTS: UiPrefs = { techInfoExpanded: false };
|
||||
const listeners = new Set<() => void>();
|
||||
let cache: UiPrefs | null = null;
|
||||
|
||||
function read(): UiPrefs {
|
||||
if (cache) return cache;
|
||||
let v: UiPrefs = DEFAULTS;
|
||||
try {
|
||||
const raw = typeof localStorage === "undefined" ? null : localStorage.getItem(KEY);
|
||||
if (raw) v = { ...DEFAULTS, ...(JSON.parse(raw) as Partial<UiPrefs>) };
|
||||
} catch {
|
||||
v = DEFAULTS;
|
||||
}
|
||||
cache = v;
|
||||
return v;
|
||||
}
|
||||
|
||||
export function setUiPref<K extends keyof UiPrefs>(key: K, value: UiPrefs[K]) {
|
||||
cache = { ...read(), [key]: value };
|
||||
try {
|
||||
localStorage.setItem(KEY, JSON.stringify(cache));
|
||||
} catch {
|
||||
// 存不下就只在本次会话生效
|
||||
}
|
||||
for (const l of listeners) l();
|
||||
}
|
||||
|
||||
function subscribe(l: () => void) {
|
||||
listeners.add(l);
|
||||
const onStorage = (e: StorageEvent) => {
|
||||
if (e.key !== KEY) return;
|
||||
cache = null;
|
||||
l();
|
||||
};
|
||||
if (typeof window !== "undefined") window.addEventListener("storage", onStorage);
|
||||
return () => {
|
||||
listeners.delete(l);
|
||||
if (typeof window !== "undefined") window.removeEventListener("storage", onStorage);
|
||||
};
|
||||
}
|
||||
|
||||
export function useUiPrefs(): UiPrefs {
|
||||
return useSyncExternalStore(subscribe, read, () => DEFAULTS);
|
||||
}
|
||||
|
||||
/** 本地记住的折叠状态(计划页分组等):key → 是否展开;读写失败时返回 null */
|
||||
export function readCollapseState(key: string): Record<string, boolean> | null {
|
||||
try {
|
||||
const raw = localStorage.getItem(key);
|
||||
return raw ? (JSON.parse(raw) as Record<string, boolean>) : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function writeCollapseState(key: string, v: Record<string, boolean>) {
|
||||
try {
|
||||
localStorage.setItem(key, JSON.stringify(v));
|
||||
} catch {
|
||||
// 忽略
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user