Skip to content

Commit 7326aa2

Browse files
author
baiqing
committed
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,即使后端报错也清掉加载状态,显示错误提示。
1 parent 4475a6d commit 7326aa2

2 files changed

Lines changed: 29 additions & 5 deletions

File tree

openless -all/app/src-tauri/src/types.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,22 @@ pub struct DictationSession {
5757
pub struct DictionaryEntry {
5858
pub id: String,
5959
pub phrase: String,
60+
/// Swift `DictionaryEntry.swift` 用的是 `notes`(复数);Rust 用 `note`(单数)。
61+
/// alias 接受老文件 + 自身字段名。
62+
#[serde(default, alias = "notes")]
6063
pub note: Option<String>,
64+
#[serde(default = "default_true")]
6165
pub enabled: bool,
66+
/// Swift 用 `hitCount`,Rust 用 `hits`。alias + default 让老文件不缺字段。
67+
#[serde(default, alias = "hitCount")]
6268
pub hits: u64,
69+
/// Swift 写 ISO8601;Rust 也用 String,直接通过。
70+
#[serde(default)]
6371
pub created_at: String,
6472
}
6573

74+
fn default_true() -> bool { true }
75+
6676
#[derive(Debug, Clone, Serialize, Deserialize)]
6777
#[serde(rename_all = "camelCase")]
6878
pub struct UserPreferences {

openless -all/app/src/pages/Vocab.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,19 @@ export function Vocab() {
1111
const [loading, setLoading] = useState(true);
1212
const inputRef = useRef<HTMLInputElement>(null);
1313

14+
const [error, setError] = useState<string | null>(null);
15+
1416
const refresh = async () => {
15-
const data = await listVocab();
16-
setEntries(data);
17-
setLoading(false);
17+
try {
18+
setError(null);
19+
const data = await listVocab();
20+
setEntries(data);
21+
} catch (e) {
22+
// 之前没 try/catch,后端 decode 失败时 spinner 永久卡死。
23+
setError(e instanceof Error ? e.message : String(e));
24+
} finally {
25+
setLoading(false);
26+
}
1827
};
1928

2029
useEffect(() => {
@@ -82,12 +91,17 @@ export function Vocab() {
8291
</div>
8392
<div style={{ padding: 18, display: 'flex', flexWrap: 'wrap', gap: 8, minHeight: 80 }}>
8493
{loading && <div style={{ fontSize: 12, color: 'var(--ol-ink-4)' }}>加载中…</div>}
85-
{!loading && entries.length === 0 && (
94+
{!loading && error && (
95+
<div style={{ fontSize: 12, color: 'var(--ol-err)', lineHeight: 1.6 }}>
96+
加载失败:{error}
97+
</div>
98+
)}
99+
{!loading && !error && entries.length === 0 && (
86100
<div style={{ fontSize: 12, color: 'var(--ol-ink-4)' }}>
87101
还没有词条。在上面输入一个生词或专业术语,让模型在听写时优先匹配。
88102
</div>
89103
)}
90-
{entries.map(e => (
104+
{!error && entries.map(e => (
91105
<VocabChip key={e.id} entry={e} onRemove={() => onRemove(e.id)} onToggle={() => onToggle(e)} />
92106
))}
93107
</div>

0 commit comments

Comments
 (0)