Skip to content

Commit 296e4d1

Browse files
committed
feat(db): data:* 通用变更事件总线与前端订阅刷新(REQ-278)
1 parent 028ed55 commit 296e4d1

19 files changed

Lines changed: 574 additions & 52 deletions

‎app/src-tauri/src/commands.rs‎

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,10 @@ pub async fn save_draft_as_note(state: State<'_, AppState>, draft: NoteDraft) ->
262262
properties: None,
263263
group_id: None,
264264
};
265-
state.db.create_note(&new).map_err(|e| e.to_string())
265+
let note = state.db.create_note(&new).map_err(|e| e.to_string())?;
266+
// REQ-278:笔记域变更广播(其它视图即时刷新)
267+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Notes);
268+
Ok(note)
266269
}
267270

268271
/// 一键流水线:音频转写 + 多图 OCR → 本地拼接 → 自动存为笔记。
@@ -298,8 +301,7 @@ pub async fn process_to_note(
298301
let title = normalize_title(title, "未命名笔记");
299302
let engines = state.engines.clone();
300303
let db = state.db.clone();
301-
tauri::async_runtime::spawn_blocking(move || {
302-
// 1) 转写(可选音频)
304+
let inner = tauri::async_runtime::spawn_blocking(move || {
303305
// H2 修复:有界等待变体——Err 携带超时/引擎诊断信息返回前端
304306
// 三维复审 #3:整文件转写用 ASR_FILE_TIMEOUT(30 分钟)——耗时与音频
305307
// 时长线性相关,短请求 60s 预算会误杀长录音(行为契约回归)
@@ -350,7 +352,12 @@ pub async fn process_to_note(
350352
.map_err(|e| e.to_string())
351353
})
352354
.await
353-
.map_err(|e| format!("任务调度失败: {}", e))?
355+
.map_err(|e| format!("任务调度失败: {}", e))?;
356+
// REQ-278:流水线落库成功 → 广播 notes 域(失败不广播——没变就不用刷)
357+
if inner.is_ok() {
358+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Notes);
359+
}
360+
inner
354361
}
355362

356363
/// 手动新建笔记(REQ-004)。
@@ -376,7 +383,10 @@ pub async fn create_note(state: State<'_, AppState>, new: NewNote) -> Result<Not
376383
// v0.11.0:手动建笔记可直接指定组(组视图内新建;无效 id 由外键拦截)
377384
group_id: new.group_id,
378385
};
379-
state.db.create_note(&new).map_err(|e| e.to_string())
386+
let note = state.db.create_note(&new).map_err(|e| e.to_string())?;
387+
// REQ-278:笔记域变更广播
388+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Notes);
389+
Ok(note)
380390
}
381391

382392
/// 列出全部笔记(REQ-004;v0.10.0 支持排序模式)。
@@ -419,7 +429,10 @@ pub async fn update_note(
419429
// v0.10.1 F2:轻量保存(自动保存/失焦/任务勾选回写)——只刷新内容不建版本
420430
// (v0.10.0 状态一致性规则:版本快照只在显式保存/AI 采纳时建立)
421431
if create_version == Some(false) {
422-
return state.db.update_note(id, &title, &content).map_err(|e| e.to_string());
432+
let ok = state.db.update_note(id, &title, &content).map_err(|e| e.to_string())?;
433+
// REQ-278:笔记域变更广播(自动保存同样触发——防抖在订阅端)
434+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Notes);
435+
return Ok(ok);
423436
}
424437
// v0.10.1 F3:内容去重——与最新版本相同则跳过 versioned_save
425438
// (防 Ctrl+S 空保存/重复保存污染版本时间线;无版本时照常建链)
@@ -439,6 +452,8 @@ pub async fn update_note(
439452
.db
440453
.update_note(id, &title, &content)
441454
.map_err(|e| e.to_string())?;
455+
// REQ-278:笔记域变更广播(versioned_save 成功路径)
456+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Notes);
442457
Ok(true)
443458
}
444459

@@ -458,6 +473,8 @@ pub async fn delete_note(state: State<'_, AppState>, id: i64) -> Result<bool, St
458473
eprintln!("[notes] 清理笔记图片目录失败({img_dir:?}): {e}");
459474
}
460475
}
476+
// REQ-278:确实删除才广播 notes 域
477+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Notes);
461478
}
462479
Ok(deleted)
463480
}
@@ -492,10 +509,13 @@ pub async fn update_note_tags(
492509
if id <= 0 {
493510
return Err("无效的笔记 id".to_string());
494511
}
495-
state
512+
let ok = state
496513
.db
497514
.update_note_tags(id, &tags)
498-
.map_err(|e| e.to_string())
515+
.map_err(|e| e.to_string())?;
516+
// REQ-278:标签即笔记元数据变更——广播 notes 域
517+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Notes);
518+
Ok(ok)
499519
}
500520

501521
/// 更新笔记固定状态(v0.10.0)。
@@ -508,10 +528,13 @@ pub async fn update_note_pin(
508528
if id <= 0 {
509529
return Err("无效的笔记 id".to_string());
510530
}
511-
state
531+
let ok = state
512532
.db
513533
.update_note_pin(id, pin)
514-
.map_err(|e| e.to_string())
534+
.map_err(|e| e.to_string())?;
535+
// REQ-278:固定状态变更——广播 notes 域
536+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Notes);
537+
Ok(ok)
515538
}
516539

517540
/// 更新笔记颜色(v0.14 B 视觉系统;color=None/空串清除 properties.color)。
@@ -526,8 +549,11 @@ pub async fn update_note_color(
526549
}
527550
// 空串等价清除(前端清除按钮传 null;防御空串)
528551
let trimmed = color.as_deref().map(str::trim).filter(|c| !c.is_empty());
529-
state
552+
let ok = state
530553
.db
531554
.update_note_color(id, trimmed)
532-
.map_err(|e| e.to_string())
555+
.map_err(|e| e.to_string())?;
556+
// REQ-278:颜色变更——广播 notes 域
557+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Notes);
558+
Ok(ok)
533559
}

‎app/src-tauri/src/commands_ai_enrich.rs‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,10 @@ pub fn ai_enrich_apply(
232232
let _ = state.db.mark_ai_task_adopted(tid);
233233
let _ = state.db.update_ai_task_cost(tid, cost);
234234
}
235-
get_note(state.inner(), note_id)
235+
let note = get_note(state.inner(), note_id)?;
236+
// REQ-278:补充采纳落库 → 广播 notes 域
237+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Notes);
238+
Ok(note)
236239
}
237240

238241
/// 撤销补充(删除无残留——内容还原补充前 base;v0.8.0 M4:= 新版本 user_edit)。
@@ -252,7 +255,10 @@ pub fn ai_enrich_revert(
252255
&crate::note_version::VersionMeta::default(),
253256
)
254257
.map_err(|e| e.to_string())?;
255-
get_note(state.inner(), note_id)
258+
let note = get_note(state.inner(), note_id)?;
259+
// REQ-278:撤销补充 = 笔记内容变更 → 广播 notes 域
260+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Notes);
261+
Ok(note)
256262
}
257263

258264
// ────────────────────────────────────────────────────────────

‎app/src-tauri/src/commands_ai_note_refine.rs‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,10 @@ pub fn ai_note_refine_apply(
188188
let _ = state.db.mark_ai_task_adopted(tid);
189189
let _ = state.db.update_ai_task_cost(tid, cost);
190190
}
191-
get_note(&state, note_id)
191+
let note = get_note(&state, note_id)?;
192+
// REQ-278:笔记级采纳落库 → 广播 notes 域
193+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Notes);
194+
Ok(note)
192195
}
193196

194197
/// 笔记存在性校验(统一错误文案——command 层提前失败给明确错误)。

‎app/src-tauri/src/commands_ai_refine.rs‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -427,11 +427,14 @@ pub fn ai_refine_apply(
427427
let _ = state.db.mark_ai_task_adopted(tid);
428428
let _ = state.db.update_ai_task_cost(tid, cost);
429429
}
430-
state
430+
let note_row = state
431431
.db
432432
.get_note(note.id)
433433
.map_err(|e| e.to_string())?
434-
.ok_or_else(|| "笔记不存在".to_string())
434+
.ok_or_else(|| "笔记不存在".to_string())?;
435+
// REQ-278:采纳落库 = 笔记内容变更 → 广播 notes 域(任务中心/会话页采纳后笔记页即时刷新)
436+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Notes);
437+
Ok(note_row)
435438
}
436439

437440
// ────────────────────────────────────────────────────────────

‎app/src-tauri/src/commands_goals.rs‎

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,10 @@ pub struct GoalProgressView {
133133
/// 新建目标(访谈确认后一步创建——status=active,无 draft 仪式)。
134134
#[tauri::command]
135135
pub fn create_goal(state: State<'_, AppState>, input: GoalCreateInput) -> Result<Goal, String> {
136-
create_goal_inner(&state.db, &input)
136+
let goal = create_goal_inner(&state.db, &input)?;
137+
// REQ-278:目标创建 → 广播 goals 域
138+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Goals);
139+
Ok(goal)
137140
}
138141

139142
/// 全部目标卡(列表;每卡现算进度——聚合皆毫秒级查询)。
@@ -163,7 +166,12 @@ pub fn update_goal(
163166
domain_tag: Option<String>,
164167
horizon: Option<String>,
165168
) -> Result<bool, String> {
166-
update_goal_inner(&state.db, id, &name, domain_tag, horizon)
169+
let ok = update_goal_inner(&state.db, id, &name, domain_tag, horizon)?;
170+
// REQ-278:目标编辑 → 广播 goals 域
171+
if ok {
172+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Goals);
173+
}
174+
Ok(ok)
167175
}
168176

169177
/// 重新访谈(答案可回溯编辑——配方重推:判据/意图整体重写)。
@@ -173,19 +181,34 @@ pub fn update_goal_interview(
173181
id: i64,
174182
input: GoalCreateInput,
175183
) -> Result<bool, String> {
176-
update_goal_interview_inner(&state.db, id, &input)
184+
let ok = update_goal_interview_inner(&state.db, id, &input)?;
185+
// REQ-278:重访谈落库 → 广播 goals 域
186+
if ok {
187+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Goals);
188+
}
189+
Ok(ok)
177190
}
178191

179192
/// 删除目标(里程碑/绑定 CASCADE;M1 无快照——毕业快照保留属 M2)。
180193
#[tauri::command]
181194
pub fn delete_goal(state: State<'_, AppState>, id: i64) -> Result<bool, String> {
182-
delete_goal_inner(&state.db, id)
195+
let ok = delete_goal_inner(&state.db, id)?;
196+
// REQ-278:目标删除 → 广播 goals 域
197+
if ok {
198+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Goals);
199+
}
200+
Ok(ok)
183201
}
184202

185203
/// 暂停/恢复(M1 只开放 active⇄paused;放弃/毕业随 M2 流程开放)。
186204
#[tauri::command]
187205
pub fn update_goal_status(state: State<'_, AppState>, id: i64, status: String) -> Result<bool, String> {
188-
update_goal_status_inner(&state.db, id, &status)
206+
let ok = update_goal_status_inner(&state.db, id, &status)?;
207+
// REQ-278:状态流转(暂停/恢复)→ 广播 goals 域
208+
if ok {
209+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Goals);
210+
}
211+
Ok(ok)
189212
}
190213

191214
/// 里程碑草案建议(宣言页预填;前端薄——单一事实源在 goal_interview.rs)。
@@ -208,7 +231,10 @@ pub fn add_goal_milestone(
208231
criteria_type: Option<String>,
209232
ref_group_id: Option<i64>,
210233
) -> Result<GoalMilestone, String> {
211-
add_goal_milestone_inner(&state.db, goal_id, &title, due_at, criteria_type, ref_group_id)
234+
let m = add_goal_milestone_inner(&state.db, goal_id, &title, due_at, criteria_type, ref_group_id)?;
235+
// REQ-278:里程碑新增 → 广播 goals 域
236+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Goals);
237+
Ok(m)
212238
}
213239

214240
#[tauri::command]
@@ -222,31 +248,56 @@ pub fn update_goal_milestone(
222248
return Err("无效的里程碑 id".to_string());
223249
}
224250
let title = normalize_title(title, "未命名里程碑");
225-
state.db.update_milestone(id, &title, due_at).map_err(|e| e.to_string())
251+
let ok = state.db.update_milestone(id, &title, due_at).map_err(|e| e.to_string())?;
252+
// REQ-278:里程碑更新 → 广播 goals 域
253+
if ok {
254+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Goals);
255+
}
256+
Ok(ok)
226257
}
227258

228259
#[tauri::command]
229260
pub fn delete_goal_milestone(state: State<'_, AppState>, id: i64) -> Result<bool, String> {
230261
if id <= 0 {
231262
return Err("无效的里程碑 id".to_string());
232263
}
233-
state.db.delete_milestone(id).map_err(|e| e.to_string())
264+
let ok = state.db.delete_milestone(id).map_err(|e| e.to_string())?;
265+
// REQ-278:里程碑删除 → 广播 goals 域
266+
if ok {
267+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Goals);
268+
}
269+
Ok(ok)
234270
}
235271

236272
#[tauri::command]
237273
pub fn set_goal_milestone_status(state: State<'_, AppState>, id: i64, status: String) -> Result<bool, String> {
238-
set_goal_milestone_status_inner(&state.db, id, &status)
274+
let ok = set_goal_milestone_status_inner(&state.db, id, &status)?;
275+
// REQ-278:里程碑状态流转 → 广播 goals 域
276+
if ok {
277+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Goals);
278+
}
279+
Ok(ok)
239280
}
240281

241282
/// 绑定/解绑组(N:M——一组可服务多目标;组仍是唯一容器)。
242283
#[tauri::command]
243284
pub fn bind_goal_group(state: State<'_, AppState>, goal_id: i64, group_id: i64) -> Result<bool, String> {
244-
bind_goal_group_inner(&state.db, goal_id, group_id)
285+
let ok = bind_goal_group_inner(&state.db, goal_id, group_id)?;
286+
// REQ-278:绑组(N:M)→ 广播 goals 域(组侧服务标随目标页刷新)
287+
if ok {
288+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Goals);
289+
}
290+
Ok(ok)
245291
}
246292

247293
#[tauri::command]
248294
pub fn unbind_goal_group(state: State<'_, AppState>, goal_id: i64, group_id: i64) -> Result<bool, String> {
249-
unbind_goal_group_inner(&state.db, goal_id, group_id)
295+
let ok = unbind_goal_group_inner(&state.db, goal_id, group_id)?;
296+
// REQ-278:解绑组 → 广播 goals 域
297+
if ok {
298+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Goals);
299+
}
300+
Ok(ok)
250301
}
251302

252303
// ─────────────────────────── inner(供测试与复用) ───────────────────────────

‎app/src-tauri/src/commands_goals_lifecycle.rs‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ pub struct GoalRetroView {
3434
/// 毕业仪式(goal_settle——信号达标 + 用户确认后执行)。
3535
#[tauri::command]
3636
pub fn goal_settle(state: State<'_, AppState>, id: i64) -> Result<GraduationReport, String> {
37-
goal_settle_inner(&state.db, id)
37+
let report = goal_settle_inner(&state.db, id)?;
38+
// REQ-278:毕业 = 目标状态机跃迁 → 广播 goals 域
39+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Goals);
40+
Ok(report)
3841
}
3942

4043
/// 回顾流(创建→里程碑→组结算→毕业;全现算零双写)。
@@ -46,7 +49,12 @@ pub fn goal_retro(state: State<'_, AppState>, id: i64) -> Result<GoalRetroView,
4649
/// 放弃(显式动作 + 可选原因——无惩罚文案;终态拒绝)。
4750
#[tauri::command]
4851
pub fn goal_abandon(state: State<'_, AppState>, id: i64, reason: Option<String>) -> Result<bool, String> {
49-
goal_abandon_inner(&state.db, id, reason.as_deref())
52+
let ok = goal_abandon_inner(&state.db, id, reason.as_deref())?;
53+
// REQ-278:放弃 = 目标状态机跃迁 → 广播 goals 域
54+
if ok {
55+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Goals);
56+
}
57+
Ok(ok)
5058
}
5159

5260
/// 毕业档案(全部报告——已删目标的报告仍在此处可读)。

‎app/src-tauri/src/commands_goals_plan.rs‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,12 @@ pub fn goal_apply_plan(
426426
other => return Err(format!("非法体系动作: {}", other)),
427427
}
428428
}
429+
// REQ-278:规划落库成功 → 广播 goals 域;本次含体系/节点/概念动作 →
430+
// 体系域同播(目标页确认后体系页即时可见,无需切页往返)
431+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Goals);
432+
if !request.systems.is_empty() {
433+
crate::notify::emit_changed(&state.app, crate::notify::DataDomain::Knowledge);
434+
}
429435
Ok(true)
430436
}
431437

0 commit comments

Comments
 (0)