# 10-产品动态时间线 前端 API 文档 ## 0. 文档定位 本文档是给前端产品首页“产品动态展示区域”单独使用的接口文档。 目标: - 明确前端当前应该调用哪个接口 - 明确左侧筛选项如何映射到后端参数 - 明确接口返回字段、时间格式、边界规则 - 避免继续混用设置页最近动态和首页正式时间线 说明: - 设置页原最近动态接口 `GET /project/product/{id}/activities` 继续保留 - 产品首页正式动态时间线请统一使用本文档中的新接口 - 当前首页动态时间线不包含需求池变动,需求池由独立区域承载 --- ## 1. 接口概览 ### 1.1 接口信息 - 接口名称:获取产品动态时间线分页 - 请求方法:`GET` - 请求路径:`/project/product/{id}/activities/page` - 权限码:`project:product:query` - 适用页面:产品首页动态时间线区域 ### 1.2 接口用途 该接口用于返回产品首页动态展示区域的正式时间线数据,支持: - 默认最近 30 天 - 左侧类型筛选 - 动作多选筛选 - 分页查询 - 创建初始化噪音去除 ### 1.3 当前纳入首页时间线的事件范围 当前只包含以下 5 类: - 产品创建 - 产品状态变更 - 产品经理变更 - 成员加入 - 成员移出 当前明确不包含: - 需求池变动 - `update_member` - 普通产品主数据编辑 `update` - 删除产品动态 --- ## 2. 请求定义 ### 2.1 路径参数 | 参数名 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `id` | `integer(int64)` | 是 | 产品 ID | ### 2.2 查询参数 | 参数名 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `pageNo` | `integer` | 是 | 页码,从 `1` 开始 | | `pageSize` | `integer` | 是 | 每页条数 | | `activityType` | `string` | 否 | 分类,可选 `status` / `product` / `member` | | `actionTypes` | `array` | 否 | 动作编码数组,支持多选 | | `startTime` | `string` | 否 | 开始时间,格式 `yyyy-MM-dd HH:mm:ss` | | `endTime` | `string` | 否 | 结束时间,格式 `yyyy-MM-dd HH:mm:ss` | ### 2.3 参数规则 #### 2.3.1 时间参数规则 - `startTime` 和 `endTime` 必须同时传,或者同时不传 - 都不传时,后端默认查询最近 `30` 天 - 只传一个时,后端返回参数错误 - `startTime > endTime` 时,后端返回参数错误 #### 2.3.2 筛选参数规则 - `activityType` 是分类筛选 - `actionTypes` 是动作细筛选 - 两者同时传时,按交集处理 - 如果前端未来需要做跨类型多选,可以不传 `activityType`,只传 `actionTypes` #### 2.3.3 `actionTypes` 传参方式 GET 场景请按重复参数方式传递,例如: ```text /project/product/1024/activities/page?pageNo=1&pageSize=10&activityType=status&actionTypes=pause&actionTypes=resume&actionTypes=archive&actionTypes=abandon ``` --- ## 3. 左侧筛选映射 首页左侧当前 5 个筛选项,前端请按下表映射到请求参数: | 前端筛选项 | `activityType` | `actionTypes` | | --- | --- | --- | | 产品创建 | `product` | `create` | | 产品状态变更 | `status` | `pause` / `resume` / `archive` / `abandon` | | 产品经理变更 | `product` | `change_manager` | | 成员加入 | `member` | `add_member` | | 成员移出 | `member` | `remove_member` | 补充说明: - 首页时间线当前不展示需求池变动 - 需求池的展示由独立模块负责,不要通过本接口混查 --- ## 4. 响应定义 ### 4.1 响应包装 接口统一返回 `CommonResult>`。 成功响应结构: ```json { "code": 0, "msg": "", "data": { "total": 0, "list": [] } } ``` ### 4.2 `data` 结构 | 字段 | 类型 | 说明 | | --- | --- | --- | | `total` | `integer(int64)` | 总条数 | | `list` | `array` | 当前页数据 | ### 4.3 单条动态结构 | 字段 | 类型 | 说明 | | --- | --- | --- | | `id` | `string` | 动态唯一标识,格式 `type:sourceId`,例如 `status:11` | | `type` | `string` | 动态类型,取值 `status` / `product` / `member` | | `actionType` | `string` | 动作编码 | | `actionName` | `string` | 动作中文名称 | | `operatorUserId` | `integer(int64)` | 操作人用户 ID,可为 `null` | | `operatorName` | `string` | 操作人名称,可为空字符串 | | `occurredAt` | `integer(int64)` | 动态发生时间,毫秒时间戳 | | `summary` | `string` | 可直接展示的摘要文案 | | `reason` | `string` | 原因说明,可为 `null` | | `fromStatus` | `string` | 原状态编码,可为 `null` | | `toStatus` | `string` | 目标状态编码,可为 `null` | | `details` | `string` | 补充明细,当前为 JSON 字符串 | ### 4.4 时间格式说明 这个接口当前有两个时间口径: - 请求里的 `startTime`、`endTime`:字符串,格式 `yyyy-MM-dd HH:mm:ss` - 响应里的 `occurredAt`:毫秒时间戳 `number` 前端需要按这个真实口径处理,不要把 `occurredAt` 当成格式化字符串读取。 --- ## 5. 字段语义说明 ### 5.1 `type` 取值说明 | 取值 | 说明 | 数据来源 | | --- | --- | --- | | `status` | 产品状态变更 | `rdms_product_status_log` | | `product` | 产品对象动态 | `rdms_biz_audit_log` 中 `bizType=product` | | `member` | 产品团队动态 | `rdms_biz_audit_log` 中 `bizType=rdms_user_object_role` | ### 5.2 `actionType` 取值范围 当前首页时间线只会出现以下动作: | `type` | `actionType` | 说明 | | --- | --- | --- | | `product` | `create` | 产品创建 | | `product` | `change_manager` | 产品经理变更 | | `status` | `pause` | 暂停 | | `status` | `resume` | 恢复 | | `status` | `archive` | 归档 | | `status` | `abandon` | 废弃 | | `member` | `add_member` | 成员加入 | | `member` | `remove_member` | 成员移出 | ### 5.3 `details` 当前口径 `details` 当前不做统一结构化建模,按来源原样返回字符串: - `type=status` - 返回状态日志补充信息 - 当前包含 `productCodeSnapshot`、`productNameSnapshot` - `type=product` - 返回产品审计 `fieldChanges` - `type=member` - 返回成员审计 `fieldChanges` 前端建议: - 首版先不依赖 `details` 做复杂渲染 - 先以 `actionName + summary + operatorName + occurredAt` 跑通展示 --- ## 6. 后端聚合规则 为了让前端看到的是“可直接展示的正式时间线”,后端已固定以下规则: ### 6.1 创建去噪 产品创建时通常会伴随初始化动作: - 初始化 `change_manager` - 初始化 `add_member` 这两类初始化动作不会单独出现在首页时间线里,最终只保留一条 `create`。 ### 6.2 状态日志优先 如果同一状态动作同时存在: - 产品审计日志 - 状态日志 则首页时间线只取状态日志,不重复展示产品审计里的同类状态动作。 ### 6.3 成员调整排除 `update_member` 当前不进入首页正式时间线。 原因: - 首页当前只需要展示“加入”和“移出” - 角色调整、备注调整等细节先不进入首页主时间线 --- ## 7. 请求示例 ### 7.1 默认查询首页动态 ```http GET /project/product/1024/activities/page?pageNo=1&pageSize=6 ``` ### 7.2 查询“产品状态变更” ```http GET /project/product/1024/activities/page?pageNo=1&pageSize=10&activityType=status&actionTypes=pause&actionTypes=resume&actionTypes=archive&actionTypes=abandon ``` ### 7.3 查询“成员移出”并限制时间范围 ```http GET /project/product/1024/activities/page?pageNo=1&pageSize=10&activityType=member&actionTypes=remove_member&startTime=2026-03-24 00:00:00&endTime=2026-04-23 23:59:59 ``` --- ## 8. 响应示例 ```json { "code": 0, "msg": "", "data": { "total": 2, "list": [ { "id": "product:22", "type": "product", "actionType": "change_manager", "actionName": "切换产品经理", "operatorUserId": 10002, "operatorName": "李四", "occurredAt": 1776812345000, "summary": "李四执行了【切换产品经理】", "reason": null, "fromStatus": null, "toStatus": null, "details": "{\"managerUserId\":{\"before\":10001,\"after\":10002}}" }, { "id": "status:11", "type": "status", "actionType": "resume", "actionName": "恢复", "operatorUserId": 10001, "operatorName": "张三", "occurredAt": 1776812984000, "summary": "张三执行了【恢复】:可以继续开展", "reason": "可以继续开展", "fromStatus": "paused", "toStatus": "active", "details": "{\"productCodeSnapshot\":\"CNPD2026001\",\"productNameSnapshot\":\"统一交付平台\"}" } ] } } ``` --- ## 9. 错误码 | `code` | 说明 | | --- | --- | | `0` | 成功 | | `400` | 请求参数错误,例如只传了一侧时间,或开始时间晚于结束时间 | | `401` | 未登录 | | `403` | 没有该产品查询权限 | | `1008001000` | 产品不存在 | 参数错误示例: ```json { "code": 400, "msg": "开始时间和结束时间必须同时传入", "data": null } ``` --- ## 10. 前端接入建议 首页动态区域首版建议直接消费以下字段: - `actionName` - `summary` - `operatorName` - `occurredAt` 左侧筛选建议直接使用: - `activityType` - `actionTypes` 当前不建议首版依赖: - `details` 的深度结构化解析 - 需求池事件混入本接口 - 自行从 `actionType` 反推新的派生事件类型 一句话结论: - 设置页最近动态继续调 `/activities` - 产品首页正式动态时间线统一调 `/activities/page`