76baee2fe5
为 user_id=73 在 stocking_list 与 final_list 增加「全部/我的」切换, view_scope 语义化传参,后端按 token+73 鉴权,默认「我的」。 Co-Authored-By: Claude <noreply@anthropic.com>
5.2 KiB
5.2 KiB
备货单 / 终版列表「全部 / 我的」视图切换 设计
- 日期: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-groupbutton 样式,二选一)。 - 控件位置:工具栏右侧、搜索框左边,两个页面位置保持一致(
final_list现有toolbar-left被注释,不在那里放,统一放右侧,改动更小)。 - 渲染条件:
useUserStore().getUserInfo.userId === 73。getUserInfo是userStore.info的 computed,类型Partial<UserInfo>,userId可能为undefined,undefined === 73为false,判断安全,无需额外判空。
3.2 前端逻辑
- 新增本地状态
viewScope = ref<'all' | 'self'>('self'),初值'self'(即默认「我的」)。 - 切换控件绑定
viewScope;@change时:pagination.currentPage = 1(切回第一页,避免越界空页);- 调用
getList()。
getList()构造参数时,当userId === 73把viewScope.value作为view_scope一并传入:- 「全部」→
view_scope: 'all' - 「我的」→
view_scope: 'self'
- 「全部」→
- 非 73 用户:不渲染控件,
viewScope恒为'self',传view_scope: 'self'(或不传,见 3.3)。为简单起见统一传,后端对非特权用户会忽略该值。
3.3 请求参数
列表请求参数由
{ "current": 1, "size": 10, "is_final": 0, "keyword": "" }
扩展为(73 用户)
{ "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. 验收标准
- 以非 73 账号登录两个列表页:不出现「全部/我的」控件,数据范围与改动前一致。
- 以 73 账号登录:
- 默认显示「我的」选中态,列表为 73 自己的数据。
- 切到「全部」,列表变为全公司数据;切回「我的」恢复。
- 切换时分页重置到第 1 页。
- 用任意账号在浏览器手动把请求改成
view_scope=all:后端只返回该账号自己的数据(非 73 不越权)。
7. 未做项 / 后续 TODO
- 前端
userId === 73为硬编码,加人需改前端发版。后续建议:getUserInfo下发权限标记(如stock@view_all/final_stock@view_all),前端用现有hasAuth(...)控制显隐;后端白名单同步改为权限点判断。本次保留此扩展点,先不动。 view_scope选择不持久化(每次进页面回到默认「我的」)。