# 公共站内通知前端联调与验收契约

## 交付边界

本文是后台管理端、代理端和 C 端的框架无关契约。当前仓库不包含前端源码，因此页面组件、状态管理和浏览器联调必须在对应前端仓库实施；本文不把契约完成表述为前端实现或人工验收完成。

第一版固定使用 HTTP 轮询，不使用 WebSocket/SSE，也不维护 Redis 未读计数。

## 未读轮询与徽标

后台布局和 C 端消息入口挂载后立即请求各自的 `unread-count`，之后每 30 秒刷新：

1. 页面变为不可见时暂停计时器。
2. 页面恢复可见时立即刷新一次，再恢复 30 秒周期。
3. 请求失败时保留上一次成功值，不改写为 0；后续周期静默重试。
4. 组件卸载时必须清理计时器，避免重复轮询。

徽标固定宽度，验收矩阵如下：

| `count` | `display_count` | 展示 |
|---:|---|---|
| 0 | `0` | 隐藏徽标 |
| 1 | `1` | 显示 1 |
| 99 | `99` | 显示 99 |
| 100 | `99+` | 显示 99+ |

前端直接使用后端 `display_count`，不自行重复计算上限。

## 后台铃铛、抽屉和通知中心

铃铛点击后使用 `GET /api/admin/notifications?page=1&page_size=10` 加载最近 10 条。抽屉提供“全部、审批、临期、同步、系统”入口，对应 `category` 为空或 `approval/expiry/sync/system`。

完整通知中心使用服务端参数：

- `category`：`approval/expiry/sync/system`
- `type`：稳定通知类型
- `severity`：`info/warning/error/critical`
- `is_read`：已读状态
- `page/page_size`：服务端分页，默认 20、最大 50

“全部已读”调用 `PUT /api/admin/notifications/read-all`。当前分类为空时提交空对象；在分类视图中提交对应 `category`，成功后使用 `updated_count` 更新提示并重新拉取列表、汇总和未读数。

加载时保留已有内容并展示局部加载状态；首次空结果展示空态；请求失败展示重试入口，不把上一页数据伪装成新筛选结果。

## 点击、已读和受控目标

点击顺序固定为：

1. 立即进入已读视觉状态。
2. 调用 `PUT /api/admin/notifications/:id/read`。
3. 调用 `GET /api/admin/notifications/:id/target`。
4. 只有 `available=true` 且 `target_type` 在前端白名单内时，使用结构化 `target_id/target_key` 构造站内路由。

已读请求失败时以下一次服务端刷新为准；目标解析失败或不可用不恢复未读。响应不包含 URL，前端禁止把 `target_id`、`target_key` 当作路径或完整地址直接跳转。

目标白名单：

| `target_type` | 结构化标识 | 页面语义 |
|---|---|---|
| `refund_detail` | `target_id` | 退款详情 |
| `agent_recharge_detail` | `target_id` | 代理充值详情 |
| `wecom_approval_detail` | `target_id` | 企微审批详情 |
| `iot_card_detail` | `target_id` | 物联网卡详情 |
| `device_detail` | `target_id` | 设备详情 |
| `expiring_asset_list` | `target_id` | 指定店铺的临期资产列表 |
| `shop_fund_summary` | `target_id` | 店铺资金概况 |
| `integration_log` | `target_key` | 外部集成记录 |
| `system_config` | `target_key` | 受控系统配置 |

未知类型、空 `target_type` 或 `available=false` 只展示正文，不跳转、不回退到自由 URL。

示例：

```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "target_type": "shop_fund_summary",
    "target_id": 42,
    "available": true
  },
  "timestamp": "2026-07-24T11:00:00+08:00"
}
```

## C 端简化通知中心

C 端只调用：

- `GET /api/c/v1/notifications/unread-count`
- `GET /api/c/v1/notifications?page=&page_size=`
- `PUT /api/c/v1/notifications/read-all`
- `PUT /api/c/v1/notifications/:id/read`

C 端不展示后台筛选、分类汇总、同步或系统运维消息。列表只呈现当前个人客户可见的已开放审批、套餐、订单和资产业务通知。无通知时展示空态；网络失败保留上一成功结果并允许重试。

## 联调验收矩阵

- 后台账号只能读取和修改自己的通知，构造其他通知 ID 不泄露事实。
- 个人客户之间完全隔离，C 端不能通过 ID 已读其他客户通知。
- 过期通知不进入列表、未读数、分类汇总或受控目标解析。
- 静态 `/read-all`、`/unread-count`、`/unread-summary` 不被 `/:id` 路由吞掉。
- 筛选、翻页、全部已读后，列表、汇总和徽标最终一致。
- 权限变化、目标删除和未知引用均显示正文但不跳转。
- 0、1、99、100 四个徽标边界无布局抖动。
- 页面隐藏时无轮询，恢复后立即刷新；失败期间未读数不闪回零。
