优化: 所有剩余UI/UX待办一批完成(持久化+AuditLog+解耦+total+原12大改+P2)

持久化(P1-c):新建 usePersistedRef composable,Tasks/AuditLog/ProjectDetail 等接入 localStorage

AuditLog(P1-d):后端 list_tool_executions 加 WHERE 筛选+返 {items,total,has_more},前端对接+长列折叠+筛选持久化

数据源解耦(P1-g):ProjectDetail projectTasks 按 project_id 独立加载 + ChatInput @项目联想独立加载(不读 store.tasks 当前页)

GitChanges(12a):后端 get_module_commits 加 git rev-list --count 返 total,前端显真实总数

原12大改:Dashboard 统计卡压底行(1)/Projects 列表卡片视图(2)/project_event 埋点排序(3)/TaskDetail 重设计(4)/IdeaDetail 重设计(5)/KnowledgeDetail 重设计(6)/界面持久化+侧栏Ctrl+B+审批数字键(7)/ProjectDetail 三栏改两栏(10)

P2打磨:文件浏览器(FileTree去重/FilePreview行号.md Diff/selectedFilePath归位)/settings反馈(假保存/端口校验)/AI会话(try-catch/scrollIntoView)/后端计数(move_queue事件/timeline total/workflow分页/import_batch分块)/杂项(TopBar/ConfirmDialog键盘/CIStatus i18n/ToolResultBody/ModuleNode/ApprovalDialog全选)
This commit is contained in:
lxy
2026-08-02 13:11:06 +08:00
parent caaabf0c15
commit f736f435bc
70 changed files with 2645 additions and 576 deletions
@@ -72,6 +72,22 @@ fn build_order_clause(order_by: Option<&str>) -> Result<String> {
}
}
/// 带「最近活跃时间」的项目记录(list_active_with_activity 专用返回结构)。
///
/// `last_active_at` = COALESCE(project_events 最新事件 created_at, projects.updated_at):
/// 反映业务活跃(任务/灵感/状态推进等事件),无事件项目回退 updated_at。供前端「最近活跃排序」
/// 展示,语义比 projects.updated_at(随元信息修改如改 description 也会刷新)更准确。
///
/// Serialize 供 IPC 层直接序列化回前端(serde 字段名 snake_case,对齐 ProjectRecord)。
#[derive(Debug, Clone, serde::Serialize)]
pub struct ProjectActivityRecord {
/// 项目完整记录(嵌套序列化:record.id / record.name ...)
#[serde(flatten)]
pub record: ProjectRecord,
/// 最近活跃时间(毫秒字符串)。前端独立消费,不混入 record 的 updated_at。
pub last_active_at: String,
}
// ============================================================
// from_row 辅助函数
// ============================================================
@@ -204,6 +220,51 @@ impl ProjectRepo {
.map_err(storage_err)?
}
/// 列出未删除项目,**按「最近活跃」排序**(LEFT JOIN project_events 取最新事件时间,
/// COALESCE 回退 updated_at)。
///
/// 业务语义:项目「最近活跃」应反映业务事件(任务/灵感/状态推进),而非 projects.updated_at
/// (后者随元信息修改如改 description/path 也会刷新,不反映真实业务活跃)。本方法通过子查询
/// 取每个项目 project_events 最新 created_at,无事件项目回退 updated_at,排序列统一可比。
///
/// 子查询而非 JOIN:project_events 每项目可能 0..N 条,JOIN 会展开需 DISTINCT,子查询
/// `(SELECT MAX(created_at) FROM project_events WHERE project_id = projects.id)` 每行一次
/// 聚合,语义清晰无笛卡尔积风险。单用户桌面应用项目数小,无性能压力。
///
/// 返回 `ProjectActivityRecord`(ProjectRecord + last_active_at 字段),供前端排序展示。
pub async fn list_active_with_activity(&self) -> Result<Vec<ProjectActivityRecord>> {
let conn = self.conn.clone();
tokio::task::spawn_blocking(move || {
let guard = conn.blocking_lock();
// COALESCE(子查询最新事件, projects.updated_at):无事件项目回退 updated_at,
// 保证所有项目有可比活跃时间。ORDER BY 该列 DESC,同时间 created_at DESC 兜底稳定。
let mut stmt = guard
.prepare(
"SELECT id, name, description, status, idea_id, path, stack, created_at, updated_at, \
COALESCE((SELECT MAX(created_at) FROM project_events WHERE project_id = projects.id), \
updated_at) AS last_active_at \
FROM projects WHERE deleted_at IS NULL \
ORDER BY last_active_at DESC, created_at DESC",
)
.map_err(storage_err)?;
let rows = stmt
.query_map([], |row| {
Ok(ProjectActivityRecord {
record: project_from_row(row)?,
last_active_at: row.get("last_active_at")?,
})
})
.map_err(storage_err)?;
let mut results = Vec::new();
for r in rows {
results.push(r.map_err(storage_err)?);
}
Ok(results)
})
.await
.map_err(storage_err)?
}
/// 按条件查询未删除项目(P2/P3:关键词搜索 + 排序 + 分页)。
///
/// 复用 `KnowledgeRepo::search` 动态 WHERE 拼接模式:按可选字段 if-let 拼 SQL 子句 +