1. 页面概览

1.1 是什么

「项目协作」页面(页面内标题为 Gotham 项目协作,TAD-11)是 LightGotham 的团队分析项目管理页:创建分析项目(如「反洗钱专项」)、管理项目成员角色,以及保存/恢复共享视图(分析上下文 State JSON)。它把「一群人围绕一个分析主题协作」的最小闭环——建项目、拉成员、存视图——集中在一个页面完成。

1.2 核心价值

维度说明
项目列表「我参与的项目」按成员关系过滤,仅展示当前用户参与的项目
成员角色owner/editor/viewer 三级角色;创建者自动成为 owner,写操作需 owner/editor
成员管理添加成员时选角色、移除成员,owner 专属操作
共享视图保存分析上下文(view_type + State JSON),成员可一键「恢复」
归档active 项目可归档(archived),归档后从列表操作列移除归档入口

1.3 一句话总结

建项目、拉成员、存共享视图——Gotham 的协作以项目为边界,以成员角色控制读写,以共享视图传递分析上下文。

2. 访问入口

2.1 路由与菜单

2.2 认证与权限

2.3 端口与 API 前缀

3. 界面布局

页面为「主列表 + 右侧抽屉」布局:

┌──────────────────────────────────────────────────────────────┐
│ Gotham 项目协作 [刷新]                                         │
│ [alert 操作结果提示条(可关闭)]                                │
│ ① 新建项目:项目名称(如 反洗钱专项)/ 项目描述 [创建项目]        │
│ ② 我参与的项目(N)                                            │
│    ID/名称/描述/状态/创建者/操作(详情/成员/共享视图|归档|删除)│
└──────────────────────────────────────────────────────────────┘
  ┌ 抽屉:项目 #id:name [关闭] ─────────────────────┐
  │ 成员管理:用户 ID(注册用户)/ 角色 [添加成员]      │
  │   [搜索已加入成员(渲染层过滤)]                   │
  │   用户 ID/角色/加入时间/移除                      │
  │ 共享视图(恢复分析上下文):视图名称/类型 [保存视图] │
  │   State JSON(可选)                              │
  │   名称/类型/创建人/State/恢复(跳转)/删除          │
  └───────────────────────────────────────────────────┘

4. 交互元素

控件位置含义与作用
「刷新」按钮页头右上重载项目列表
项目名称/描述输入框 + 「创建项目」新建项目卡片名称必填;成功后提示「项目创建成功(id=...,你已自动成为 owner)」并刷新列表
行内「详情 / 成员 / 共享视图」项目列表操作列打开详情抽屉,并行加载成员与共享视图
行内「归档」项目列表操作列仅 active 项目显示;confirm 确认「确认归档项目「name」?」后调用 POST /collab/projects/:id/archive
行内「删除」项目列表操作列所有项目均显示;confirm 中明确说明「软删除(等同归档)、不级联删除任何关联数据、项目仍留在列表」,确认后调用 DELETE /collab/projects/:id
「搜索待添加用户」输入框抽屉成员管理节输入登录名/展示名(300ms 防抖)后调 GET /collab/projects/:id/member-candidates?q=&limit=20(仅本项目 owner/editor 可调);候选条目显示 @username、展示名、user_id,已是成员的置灰并标注「已是成员」;空关键词不请求;无命中显示「无匹配用户(换个关键词,或确认对方已注册)」;403 显示「无权搜索候选用户:仅本项目的 owner/editor 可枚举用户」
候选条目(点击)候选列表把候选 id(user_id)填入下方输入框(后端添加成员按 user_id 精确匹配),展示名仅作辅助信息
待添加用户 user_id 输入框抽屉成员管理节由候选自动填入,也可手输精确匹配;有值时显示「清除」
成员表「搜索」输入框抽屉成员管理节渲染层过滤已加载成员(按 user_id 子串,大小写不敏感)——只对当前项目成员列表做展示层切片,不属后端契约缺口
「添加成员」抽屉成员管理节选角色(editor/viewer/owner)后添加;user_id 必填(未选候选/未输入时按钮禁用)
「移除」成员表操作列confirm 确认「确认移除成员 {user_id}?」后移除
「保存视图」抽屉共享视图节视图名称必填;选择类型(cross_filter/graph/map/timeline/report),State JSON 可空(非法 JSON 自动包成 {raw: 原文})
「恢复」共享视图表操作列解析 State 并提示,同时按 view_type 自动跳转对应分析路由(graph→/gotham、map→/gotham/map、timeline→/gotham/timeline、cross_filter→/gotham/views、report→/gotham/reports),携带 view_id/view_type 查询参数;未知类型仅展示 State 不跳转
「删除」共享视图表操作列confirm 确认「确认删除共享视图「name」?」后删除

5. 后端关联

5.1 API 客户端

5.2 端点表

方法路径说明
GET/collab/projects列出当前用户参与的项目
POST/collab/projects创建项目(创建者自动成为 owner 成员)
GET/collab/projects/:id项目详情
PUT/collab/projects/:id更新项目信息
DELETE/collab/projects/:id删除项目(软删除:状态置 archived,对齐 TAD-11 §4.1 DELETE 语义)
POST/collab/projects/:id/archive归档项目(状态置 archived)
GET/collab/projects/:id/members列出项目成员
POST/collab/projects/:id/members添加成员(body {user_id, role},仅 owner)
DELETE/collab/projects/:id/members/:user_id移除成员(仅 owner)
GET/collab/projects/:id/member-candidates?q=&limit=成员候选用户搜索(D4-a):仅本项目 owner/editor;返回 [{id, username, display_name}] 最小字段集(不含邮箱等 PII);q 对登录名与展示名做子串匹配,limit 默认 20 / 上限 50
GET/collab/projects/:id/shared-views列出项目共享视图
POST/collab/projects/:id/shared-views创建共享视图(body {name, view_type, state})
GET/collab/shared-views/:id获取共享视图(恢复分析上下文)
DELETE/collab/shared-views/:id删除共享视图

5.3 响应结构

统一响应体 {code: 0, data: ...}。项目与共享视图示例:

{ "code": 0, "data": [ { "id": 1, "name": "反洗钱专项", "description": "资金链路分析",
  "status": "active", "owner_id": "alice", "created_at": "2026-08-30T..." } ] }
{ "code": 0, "data": { "id": 1, "project_id": 1, "name": "华北情报视图",
  "view_type": "cross_filter", "state": {"filters":[{"field":"region","op":"eq","value":"北京"}]},
  "created_by": "alice" } }

5.4 关键机制

6. 权限与安全

7. 常见问题与排错

问题一:项目列表为空,但项目确实存在

现象:列表页看不到已有项目。原因:列表接口按成员关系过滤,只返回当前用户参与的项目。处理:确认当前登录用户是否是该项目的成员;查看 GET /collab/projects 返回;创建者需先在项目里把自己设为成员(创建时已自动成为 owner)。

问题二:添加成员报「仅项目 owner 可执行该操作」

现象:添加/移除成员被拒绝。原因:当前用户角色为 editor/viewer,成员管理仅限 owner。处理:让 owner 成员执行添加/移除;或在项目中把自己提升为 owner 角色。

问题三:创建项目成功但提示框里没有 id

现象:成功提示未显示项目 id。原因:接口返回体解析异常或网络抖动。处理:刷新项目列表确认是否已出现新项目;用 curl POST /api/v1/collab/projects 复现并检查响应体 {code:0, data:{id}}。

问题四:点「恢复」只弹提示,没有跳转到分析页面

现象:点「恢复」后跳转到了分析页面,但上下文未自动还原。原因:「恢复」按 view_type 跳转到对应路由并携带 view_id/view_type 查询参数,目标页面仍以自身查询逻辑为准(State 内容仅完整展示在提示条中,不做跨页状态注入)。处理:查看提示条中的 State JSON,在目标页面按其筛选条件复原上下文;若 view_type 为未知值则不会跳转,仅展示 State。

问题五:保存视图时 State 输入了非法 JSON 但保存成功

现象:非法 JSON 也能保存。原因:前端把非法输入自动包装为 {raw: 原文} 保存,属设计行为。处理:按规范填写 JSON(如 {"filters":[{"field":"region","op":"eq","value":"北京"}]}),非法内容以 raw 形式原样保存,恢复时以原文展示。

8. 已知缺陷与边界

边界说明
项目删除为软删除DELETE /collab/projects/:id 与归档同义(共用 ArchiveProject,collab/service.go:458):仅置 status=archived,无回收站、无级联删除(成员/任务/评论/共享视图全部保留),项目仍出现在列表中(列表不按状态过滤)。页面已补「删除」入口,确认弹窗与列表说明如实标注该范围
共享视图恢复「恢复」已按 view_type 自动跳转(graph/map/timeline/cross_filter/report → 对应路由,携带 view_id/view_type);未知 view_type 仅展示 State 不跳转
成员即用户(已收口)~~后端无用户/成员枚举接口,无法提供候选搜索下拉~~ 已于 2026-09-17 收口(D4-a):新增 GET /collab/projects/:id/member-candidates?q=&limit=(项目级受限,仅该项目 owner/editor;返回 id/username/display_name 最小字段集,不含邮箱等 PII,limit 上限 50)。添加仍按 user_id 精确匹配(候选选中后自动填入);成员表搜索仍为渲染层切片(仅过滤已加载成员,不涉后端缺口)
候选搜索的非成员视角viewer 与项目外用户调用候选端点一律 403(无写权限即无枚举权);本页对 403 给出明确中文提示而非静默失败
候选列表不过滤已有成员后端不做「排除已是成员」过滤,由前端按已加载成员列表置灰并标注「已是成员」(避免成员列表未刷新时后端与前端判定不一致)
视图类型view_type 选项固定 cross_filter/graph/map/timeline/report,跳转映射以 router/index.js Gotham 子路由为准,不校验 State 内容与页面参数一致
角色变更owner 成员可在成员表行内角色下拉直接改他人角色(PUT 即改);不能修改自己的角色(owner 本人行显示只读徽标)
WebSocket本页不主动建立 WS 连接,实时通知依赖其他页面/协同动态页启动连接
描述截断项目描述列 max-width 220px 超出省略号截断,完整内容需打开详情确认