fix(vocab): 兼容 Swift dictionary.json 老 schema (notes/hitCount)
老 Swift 版 DictionaryEntry 写到 ~/Library/Application Support/OpenLess/dictionary.json 的 字段名是 `notes`(复数)和 `hitCount`,Rust 这边期待 `note`/`hits` → serde decode 失败 → list_vocab 命令永远返回 Err → 前端 spinner 永久卡死。 修法: - types.rs DictionaryEntry 加 #[serde(alias = "notes")] / #[serde(alias = "hitCount")] + default,兼容老文件 + 新写入。 - Vocab.tsx refresh() 加 try/catch + finally,即使后端报错也清掉加载状态,显示错误提示。
This commit is contained in:
1 parent
4475a6dc31
commit
7326aa2d7e
2 files changed
+29
-5
No files matched your search
@@ -57,12 +57,22 @@ pub struct DictationSession {
|
||||
pub struct DictionaryEntry {
|
||||
pub id: String,
|
||||
pub phrase: String,
|
||||
/// Swift `DictionaryEntry.swift` 用的是 `notes`(复数);Rust 用 `note`(单数)。
|
||||
/// alias 接受老文件 + 自身字段名。
|
||||
#[serde(default, alias = "notes")]
|
||||
pub note: Option<String>,
|
||||
#[serde(default = "default_true")]
|
||||
pub enabled: bool,
|
||||
/// Swift 用 `hitCount`,Rust 用 `hits`。alias + default 让老文件不缺字段。
|
||||
#[serde(default, alias = "hitCount")]
|
||||
pub hits: u64,
|
||||
/// Swift 写 ISO8601;Rust 也用 String,直接通过。
|
||||
#[serde(default)]
|
||||
pub created_at: String,
|
||||
}
|
||||
|
||||
fn default_true() -> bool { true }
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UserPreferences {
|
||||
|
||||
@@ -11,10 +11,19 @@ export function Vocab() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const refresh = async () => {
|
||||
const data = await listVocab();
|
||||
setEntries(data);
|
||||
setLoading(false);
|
||||
try {
|
||||
setError(null);
|
||||
const data = await listVocab();
|
||||
setEntries(data);
|
||||
} catch (e) {
|
||||
// 之前没 try/catch,后端 decode 失败时 spinner 永久卡死。
|
||||
setError(e instanceof Error ? e.message : String(e));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -82,12 +91,17 @@ export function Vocab() {
|
||||
</div>
|
||||
<div style={{ padding: 18, display: 'flex', flexWrap: 'wrap', gap: 8, minHeight: 80 }}>
|
||||
{loading && <div style={{ fontSize: 12, color: 'var(--ol-ink-4)' }}>加载中…</div>}
|
||||
{!loading && entries.length === 0 && (
|
||||
{!loading && error && (
|
||||
<div style={{ fontSize: 12, color: 'var(--ol-err)', lineHeight: 1.6 }}>
|
||||
加载失败:{error}
|
||||
</div>
|
||||
)}
|
||||
{!loading && !error && entries.length === 0 && (
|
||||
<div style={{ fontSize: 12, color: 'var(--ol-ink-4)' }}>
|
||||
还没有词条。在上面输入一个生词或专业术语,让模型在听写时优先匹配。
|
||||
</div>
|
||||
)}
|
||||
{entries.map(e => (
|
||||
{!error && entries.map(e => (
|
||||
<VocabChip key={e.id} entry={e} onRemove={() => onRemove(e.id)} onToggle={() => onToggle(e)} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user