From 76baee2fe5d2ecb0def51ae29b55b21b5d2ec3bd Mon Sep 17 00:00:00 2001 From: mzl Date: Thu, 6 Aug 2026 11:34:33 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E5=A4=87=E8=B4=A7=E5=8D=95/=E7=BB=88?= =?UTF-8?q?=E7=89=88=E5=88=97=E8=A1=A8=E3=80=8C=E5=85=A8=E9=83=A8/?= =?UTF-8?q?=E6=88=91=E7=9A=84=E3=80=8D=E8=A7=86=E5=9B=BE=E5=88=87=E6=8D=A2?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为 user_id=73 在 stocking_list 与 final_list 增加「全部/我的」切换, view_scope 语义化传参,后端按 token+73 鉴权,默认「我的」。 Co-Authored-By: Claude --- ...08-06-stocking-visibility-toggle-design.md | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-06-stocking-visibility-toggle-design.md diff --git a/docs/superpowers/specs/2026-08-06-stocking-visibility-toggle-design.md b/docs/superpowers/specs/2026-08-06-stocking-visibility-toggle-design.md new file mode 100644 index 0000000..a893225 --- /dev/null +++ b/docs/superpowers/specs/2026-08-06-stocking-visibility-toggle-design.md @@ -0,0 +1,99 @@ +# 备货单 / 终版列表「全部 / 我的」视图切换 设计 + +- 日期:2026-08-06 +- 分支:feature/crm_pc +- 涉及页面:`src/views/crmpc/stocking/stocking_list.vue`、`src/views/crmpc/stocking/final_list.vue` +- 涉及接口:`GET /crm/stock/order/list` + +## 1. 背景 + +备货单列表(`stocking_list`,`is_final=0`)与终版列表(`final_list`,`is_final=1`)当前共用同一个列表接口 `GET /crm/stock/order/list`。后端依据请求 token 中的用户 id 过滤数据,即每个登录用户只能看到自己作为业务员的备货单 / 终版。前端请求参数只有 `current / size / is_final / keyword`,无任何范围控制。 + +业务上存在一个固定特权用户(user_id = 73),需要在「看自己」之外,额外能查看「全公司所有备货单 / 终版」。 + +## 2. 需求 + +- 给 user_id = 73 这一个用户,在两个列表页增加「全部 / 我的」视图切换。 +- 选「全部」时,该用户看到全公司数据;选「我的」时,行为同现状(只看自己)。 +- 其他用户(非 73)完全不受影响:不显示切换控件,行为同现状。 +- 默认视图为「我的」。 + +## 3. 设计 + +### 3.1 UI + +- 在两个列表页的工具栏增加一组互斥切换控件「全部 / 我的」(`el-radio-group` button 样式,二选一)。 +- 控件位置:工具栏右侧、搜索框左边,两个页面位置保持一致(`final_list` 现有 `toolbar-left` 被注释,不在那里放,统一放右侧,改动更小)。 +- 渲染条件:`useUserStore().getUserInfo.userId === 73`。`getUserInfo` 是 `userStore.info` 的 computed,类型 `Partial`,`userId` 可能为 `undefined`,`undefined === 73` 为 `false`,判断安全,无需额外判空。 + +### 3.2 前端逻辑 + +- 新增本地状态 `viewScope = ref<'all' | 'self'>('self')`,初值 `'self'`(即默认「我的」)。 +- 切换控件绑定 `viewScope`;`@change` 时: + 1. `pagination.currentPage = 1`(切回第一页,避免越界空页); + 2. 调用 `getList()`。 +- `getList()` 构造参数时,当 `userId === 73` 把 `viewScope.value` 作为 `view_scope` 一并传入: + - 「全部」→ `view_scope: 'all'` + - 「我的」→ `view_scope: 'self'` +- 非 73 用户:不渲染控件,`viewScope` 恒为 `'self'`,传 `view_scope: 'self'`(或不传,见 3.3)。为简单起见统一传,后端对非特权用户会忽略该值。 + +### 3.3 请求参数 + +列表请求参数由 + +```jsonc +{ "current": 1, "size": 10, "is_final": 0, "keyword": "" } +``` + +扩展为(73 用户) + +```jsonc +{ "current": 1, "size": 10, "is_final": 0, "keyword": "", "view_scope": "all" } +``` + +- 参数名 `view_scope` 沿用现有 snake_case 风格(与 `is_final` / `keyword` 一致)。 +- 取值为语义化的 `'all' | 'self'`,**不传用户 id**。 + +### 3.4 后端配合 + +`GET /crm/stock/order/list` 增加可选参数 `view_scope`,处理逻辑: + +| 请求情况 | 后端行为 | +|---|---| +| 不传 / `view_scope=self` | 现状逻辑:按 token 用户过滤,返回自己的数据 | +| `view_scope=all` 且 token 用户 id ∈ {73} | 返回全公司全量数据 | +| `view_scope=all` 且 token 用户 id ∉ {73} | 降级:当作 `self` 处理,返回该用户自己的数据(忽略 `all`) | + +后端「可看全部」白名单暂写死为 `{73}`,与前端判断一致。 + +### 3.5 默认值 + +73 用户打开页面,`viewScope` 初值 `'self'`,即默认「我的」。需看全部时手动切换。 + +## 4. 安全考量 + +- **后端只认 token**:是否返回全量数据,由后端依据 token 中的用户 id 判断,绝不信任前端传入的任何用户标识。前端 `userId` 存于 localStorage,可被篡改。 +- 前端写死 `userId === 73` 仅用于**控件显隐**的便捷实现,不是安全边界。即便有人改前端强制显示按钮并传 `view_scope=all`,后端鉴权不通过仍只返回其自己的数据。 +- 结论:语义化参数 + 后端 token 鉴权,可兜住水平越权(IDOR)风险。 + +## 5. 改动清单 + +| 端 | 文件 / 接口 | 改动 | +|---|---|---| +| 前端 | `stocking_list.vue` | 工具栏加「全部/我的」`el-radio-group`(仅 73 显示);`getList` 传 `view_scope`;切换重置页码并重新请求 | +| 前端 | `final_list.vue` | 同上(`is_final=1`) | +| 后端 | `GET /crm/stock/order/list` | 接收 `view_scope`;token 用户 ∈ {73} 且 `=all` 时返回全量,否则按现状过滤 | + +## 6. 验收标准 + +1. 以非 73 账号登录两个列表页:不出现「全部/我的」控件,数据范围与改动前一致。 +2. 以 73 账号登录: + - 默认显示「我的」选中态,列表为 73 自己的数据。 + - 切到「全部」,列表变为全公司数据;切回「我的」恢复。 + - 切换时分页重置到第 1 页。 +3. 用任意账号在浏览器手动把请求改成 `view_scope=all`:后端只返回该账号自己的数据(非 73 不越权)。 + +## 7. 未做项 / 后续 TODO + +- 前端 `userId === 73` 为硬编码,加人需改前端发版。后续建议:`getUserInfo` 下发权限标记(如 `stock@view_all` / `final_stock@view_all`),前端用现有 `hasAuth(...)` 控制显隐;后端白名单同步改为权限点判断。本次保留此扩展点,先不动。 +- `view_scope` 选择不持久化(每次进页面回到默认「我的」)。