1. 后台图表多样化 API 的 /admin/overview 新增了按 AI 提供商聚合的用量(providerSeries)和云端数据构成(dataDist)。Admin.vue 概览页改用 ECharts:日活折线图、Token 堆叠柱状图(叠加调用次数折线)、AI 提供商用量饼图、云端数据构成饼图。

2. 端口监控与刷新间隔 「存为应用」不再跳转启动台,而是在本页弹出复用的 LaunchAppFormModal 表单,保存成功后弹确认框询问「留在本页 / 前往启动台」。启动台和端口监控页头都加了 RefreshIntervalPicker(仅手动 / 5 / 10 / 30 / 60 秒,默认 30 秒),选择记忆在 localStorage。

3. 团队所有者与用户详情 团队列表的所有者、用户列表的用户名都改为「头像 + 昵称 + ID」的可点击单元格,点击弹出用户详情:头像、昵称、头衔、待办/工单/笔记/团队数量、注册时间、最近活跃、拥有团队数。API 新增 GET /admin/users/:id,团队列表关联查询出 ownerNickname/ownerAvatar。

4. 项目专属排除规则 SQLite exclusion_rules 加了 project_id 列(唯一约束改为 (project_id, pattern),旧库自动重建迁移)。项目详情页「代码」标签下新增专属规则面板,可增删;扫描时全局规则 + 项目规则叠加生效。规则随项目身份上云,其它机器认领项目后自动带回。

5. 一句话 AI 生成待办/工单 待办页和工单页工具栏各嵌入一个 AI 生成输入条:一句话回车后由后端 AIGenerateTasks 调用 AI 拆解成多条草稿,弹预览框逐条勾选、可改标题/类型/优先级/日期,选归属项目后批量保存。

6. 项目云同步与认领

全局挂载了 CloudClaimModal:登录同步后发现云端有本机未落地的项目时自动弹出(同一批只打扰一次),每个项目可「选目录认领」或「忽略」;工作台横幅和个人主页可随时再打开。
统计数据按机器码推送到 stats:<机器码> 文档,只推不拉,不同电脑的扫描历史互不覆盖。
个人主页 · 云同步新增「项目云同步」区块:自动/手动模式切换(手动模式下常规同步跳过项目文档,仅点「立即同步项目」时推拉)、待认领入口、已忽略项目的恢复列表。
途中补齐了约 60 个中英双语 i18n 键,并修掉了 PortMonitor 引用但缺失的 pmSavedToast 键。
This commit is contained in:
李琦
2026-08-17 09:04:53 +08:00
parent c65cedf9fa
commit c430a0f6da
25 changed files with 1666 additions and 240 deletions

View File

@@ -111,7 +111,75 @@ func (a *App) rebuildCloudPending(entries []projectDocEntry) {
_ = a.store.SetMeta("cloud_projects_pending", string(b))
}
// ListCloudPendingProjects 返回云端存在但本机尚未绑定路径的项目
// cloudIgnoredSet 已被用户忽略的云端项目名集合(认领弹窗里点了「忽略」)
func (a *App) cloudIgnoredSet() map[string]bool {
out := map[string]bool{}
raw := a.store.Meta("cloud_projects_ignored")
if raw == "" {
return out
}
var names []string
if json.Unmarshal([]byte(raw), &names) != nil {
return out
}
for _, n := range names {
if n = strings.TrimSpace(n); n != "" {
out[n] = true
}
}
return out
}
func (a *App) saveCloudIgnored(set map[string]bool) {
names := make([]string, 0, len(set))
for n, on := range set {
if on {
names = append(names, n)
}
}
b, _ := json.Marshal(names)
_ = a.store.SetMeta("cloud_projects_ignored", string(b))
}
// IgnoreCloudProject 忽略某个云端待认领项目(仅本机记忆,可随时恢复)。
func (a *App) IgnoreCloudProject(name string) error {
if e := a.ready(); e != nil {
return e
}
name = strings.TrimSpace(name)
if name == "" {
return errors.New("NAME_REQUIRED")
}
set := a.cloudIgnoredSet()
set[name] = true
a.saveCloudIgnored(set)
return nil
}
// UnignoreCloudProject 恢复被忽略的云端项目,使其重新出现在待认领清单。
func (a *App) UnignoreCloudProject(name string) error {
if e := a.ready(); e != nil {
return e
}
set := a.cloudIgnoredSet()
delete(set, strings.TrimSpace(name))
a.saveCloudIgnored(set)
return nil
}
// ListCloudIgnoredProjects 返回被忽略的云端项目名(供认领弹窗「已忽略」区展示)。
func (a *App) ListCloudIgnoredProjects() ([]string, error) {
if e := a.ready(); e != nil {
return nil, e
}
out := []string{}
for n := range a.cloudIgnoredSet() {
out = append(out, n)
}
return out, nil
}
// ListCloudPendingProjects 返回云端存在但本机尚未绑定路径、且未被忽略的项目。
func (a *App) ListCloudPendingProjects() ([]projectDocEntry, error) {
if e := a.ready(); e != nil {
return nil, e
@@ -125,7 +193,11 @@ func (a *App) ListCloudPendingProjects() ([]projectDocEntry, error) {
if json.Unmarshal([]byte(raw), &list) != nil {
return out, nil
}
ignored := a.cloudIgnoredSet()
for _, it := range list {
if ignored[strings.TrimSpace(it.Name)] {
continue
}
// 本地可能在同步间隙手动建了同名项目,再过滤一遍
var pid int64
if a.store.db.QueryRow(`SELECT id FROM projects WHERE name=? LIMIT 1`, it.Name).Scan(&pid) == sql.ErrNoRows {
@@ -189,6 +261,17 @@ func (a *App) BindCloudProject(name, path string) (Project, error) {
if entry.Favorite {
_, _ = a.store.db.Exec(`INSERT OR IGNORE INTO favorites(project_id,created_at) VALUES(?,?)`, pid, now)
}
// 云端身份携带的项目级排除规则一并落地
if len(entry.Rules) > 0 {
if e := a.applyProjectRules(pid, entry.Rules); e != nil {
return Project{}, e
}
}
// 认领后如仍在忽略清单里,顺手移除
if set := a.cloudIgnoredSet(); set[name] {
delete(set, name)
a.saveCloudIgnored(set)
}
a.store.Log("info", "同步", "云端项目已绑定本机目录", name+" → "+path)
if a.syncUserID() > 0 {
go a.syncOnce(true)