Files
art-design/docs/superpowers/plans/2026-08-06-stocking-visibility-toggle.md
T
2026-08-06 11:37:34 +08:00

273 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 备货单 / 终版列表「全部 / 我的」视图切换 实现计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 在 `stocking_list.vue` 与 `final_list.vue` 给 `userId === 73` 的特权用户增加「全部 / 我的」视图切换,切换时给列表接口传 `view_scope=all|self`。
**Architecture:** 纯前端 UI + 传参改动。新增本地状态 `viewScope`,工具栏渲染条件 `canViewAll = userStore.getUserInfo.userId === 73`;`getList` 把 `viewScope.value` 作为 `view_scope` 随请求发出;切换时重置页码并重新请求。是否返回全量数据由后端按 token 鉴权决定(后端改造见 spec 第 3.4 节,不在本计划内)。
**Tech Stack:** Vue 3 `<script setup>` + TypeScript、element-plus 2.8、Pinia(`useUserStore`)、`unplugin-auto-import`(`ref/reactive/computed/onMounted/watch` 自动导入,无需手写 import)。
## Global Constraints
- **无前端单元测试框架**:本项目 `package.json` 无 `test` 脚本。验收方式 = `lint` 通过 + 本地 `dev` 手动核对,不写自动化测试。
- **后端是前置依赖**:前端「全部」真正生效需后端 `GET /crm/stock/order/list` 支持 `view_scope` 参数(见 spec 3.4)。前端可先行独立完成 UI 与传参;完整功能联调待后端就绪。
- **安全边界在后端**:前端 `userId === 73` 仅控制按钮显隐,非授权依据。
- **代码风格**:遵循现有写法——`<script setup lang="ts">`、element-plus 组件、snake_case 请求参数。
- **commit 规范**:每任务一次 commit,信息以 `feat:` 开头。
## 文件结构
| 文件 | 责任 | 改动类型 |
|---|---|---|
| `src/views/crmpc/stocking/stocking_list.vue` | 备货单列表,加视图切换 + `view_scope` 传参 | Modify |
| `src/views/crmpc/stocking/final_list.vue` | 终版列表,同构改动 | Modify |
两文件改动完全同构,仅 `is_final`(0 vs 1)不同。不新建文件。
---
## Task 1: stocking_list.vue 增加视图切换
**Files:**
- Modify: `src/views/crmpc/stocking/stocking_list.vue`
- 模板工具栏(约 `line 9-22` 的 `toolbar-right`)
- `getList` 参数(约 `line 339-344`)
- script 状态(约 `line 280-281` `useAuth` 之后)
- script import(约 `line 264-274`)
**Interfaces:**
- Consumes: `useUserStore`(来自 `@/store/modules/user`),其 `getUserInfo: computed => Partial<{ userId: number }>`;`CrmService.getStockList(params)`(已存在)。
- Produces: 列表请求新增参数字段 `view_scope: 'all' | 'self'`。
- [ ] **Step 1: 添加 import**
在 `<script setup lang="ts">` 顶部 import 区(`import { useAuth } from '@/composables/useAuth'` 同处)增加一行:
```ts
import { useUserStore } from '@/store/modules/user'
```
- [ ] **Step 2: 添加状态与切换处理**
在 `const { hasAuth } = useAuth()`(约 line 281)之后插入:
```ts
const userStore = useUserStore()
// 仅 userId=73 的特权用户可切换「全部/我的」
const canViewAll = computed(() => userStore.getUserInfo.userId === 73)
const viewScope = ref<'all' | 'self'>('self')
const onScopeChange = () => {
pagination.currentPage = 1
getList()
}
```
说明:`computed` / `ref` 由 `unplugin-auto-import` 提供,无需 import。`getUserInfo.userId` 类型为 `number | undefined`,`undefined === 73` 为 `false`,判空安全。
- [ ] **Step 3: 修改 `getList` 请求参数**
定位 `getList` 内(约 line 339-344):
```ts
const params = {
current: currentPage,
size: pageSize,
is_final: 0, // 只获取备货单,非终版
keyword: keyword.value
}
```
改为(增加 `view_scope`):
```ts
const params = {
current: currentPage,
size: pageSize,
is_final: 0, // 只获取备货单,非终版
keyword: keyword.value,
view_scope: viewScope.value
}
```
- [ ] **Step 4: 工具栏添加切换控件**
定位模板 `toolbar-right`(约 line 9-21),在 `<el-input>` 之前插入 `el-radio-group`:
原:
```html
<div class="toolbar-right">
<el-input
v-model="keyword"
```
改为:
```html
<div class="toolbar-right">
<el-radio-group
v-if="canViewAll"
v-model="viewScope"
@change="onScopeChange"
style="margin-right: 12px"
>
<el-radio-button value="self">我的</el-radio-button>
<el-radio-button value="all">全部</el-radio-button>
</el-radio-group>
<el-input
v-model="keyword"
```
说明:用 `value`(element-plus 2.6+ 推荐)而非 `label` 作值,避免 2.8 的 label 弃用告警。文字走默认插槽。
- [ ] **Step 5: Lint 校验**
Run: `npm run fix -- src/views/crmpc/stocking/stocking_list.vue`
Expected: 无 error。若有,按提示修复(常见:prettier 格式)。
- [ ] **Step 6: 手动核对(前端独立部分)**
Run: `npm run dev`,浏览器打开备货单列表页。
核对项:
1. 用**非 73 账号**登录:工具栏**不出现**「全部/我的」控件,列表行为同改动前。
2. 打开 DevTools Network,看 `/crm/stock/order/list` 请求 Payload:无论是否 73,都应带上 `view_scope=self`(默认值)。
3. (需 73 登录态)用 73 账号登录:出现「全部/我的」,默认选中「我的」;点「全部」后 Network 请求带 `view_scope=all`,点回「我的」带 `view_scope=self`,且每次切换分页回到第 1 页。
4. 「全部」是否真返回全量数据,取决于后端是否已支持——后端未就绪时,切到「全部」数据可能不变,属预期。
- [ ] **Step 7: Commit**
```bash
git add src/views/crmpc/stocking/stocking_list.vue
git commit -m "feat(stocking): 备货单列表为特权用户增加「全部/我的」视图切换
Co-Authored-By: Claude <noreply@anthropic.com>"
```
---
## Task 2: final_list.vue 增加视图切换(同构)
**Files:**
- Modify: `src/views/crmpc/stocking/final_list.vue`
- 模板工具栏(约 `line 7-19` 的 `toolbar-right`)
- `getList` 参数(约 `line 278-283`)
- script 状态(约 `line 229` `useAuth` 之后)
- script import(约 `line 206-216`)
**Interfaces:**
- Consumes: 同 Task 1(`useUserStore`、`CrmService.getStockList`)。
- Produces: 同 Task 1(`view_scope` 参数)。
- [ ] **Step 1: 添加 import**
在 `<script setup lang="ts">` 顶部 import 区增加:
```ts
import { useUserStore } from '@/store/modules/user'
```
- [ ] **Step 2: 添加状态与切换处理**
在 `const { hasAuth } = useAuth()`(约 line 229)之后插入:
```ts
const userStore = useUserStore()
// 仅 userId=73 的特权用户可切换「全部/我的」
const canViewAll = computed(() => userStore.getUserInfo.userId === 73)
const viewScope = ref<'all' | 'self'>('self')
const onScopeChange = () => {
pagination.currentPage = 1
getList()
}
```
- [ ] **Step 3: 修改 `getList` 请求参数**
定位 `getList` 内(约 line 278-283):
```ts
const params = {
current: currentPage,
size: pageSize,
is_final: 1, // 只获取备货单,非终版
keyword: keyword.value
}
```
改为:
```ts
const params = {
current: currentPage,
size: pageSize,
is_final: 1, // 终版
keyword: keyword.value,
view_scope: viewScope.value
}
```
(注:原代码注释「非终版」是笔误,此处顺手修正为「终版」。)
- [ ] **Step 4: 工具栏添加切换控件**
定位模板 `toolbar-right`(约 line 7-19),在 `<el-input>` 之前插入:
原:
```html
<div class="toolbar-right">
<el-input
v-model="keyword"
```
改为:
```html
<div class="toolbar-right">
<el-radio-group
v-if="canViewAll"
v-model="viewScope"
@change="onScopeChange"
style="margin-right: 12px"
>
<el-radio-button value="self">我的</el-radio-button>
<el-radio-button value="all">全部</el-radio-button>
</el-radio-group>
<el-input
v-model="keyword"
```
- [ ] **Step 5: Lint 校验**
Run: `npm run fix -- src/views/crmpc/stocking/final_list.vue`
Expected: 无 error。
- [ ] **Step 6: 手动核对(前端独立部分)**
Run: `npm run dev`,浏览器打开终版列表页。核对项同 Task 1 Step 6(注意此处是 `is_final=1` 的终版页)。
- [ ] **Step 7: Commit**
```bash
git add src/views/crmpc/stocking/final_list.vue
git commit -m "feat(stocking): 终版列表为特权用户增加「全部/我的」视图切换
Co-Authored-By: Claude <noreply@anthropic.com>"
```
---
## 后端联调清单(交付后端,不在本计划执行范围)
接口 `GET /crm/stock/order/list` 增加可选参数 `view_scope`:
| 请求 | 后端行为 |
|---|---|
| 不传 / `view_scope=self` | 按现状:依 token 用户过滤 |
| `view_scope=all` 且 token 用户 id ∈ {73} | 返回全量 |
| `view_scope=all` 且 token 用户 id ∉ {73} | 降级为 `self`(返回该用户自己的) |
要点:**只信 token,忽略前端任何用户标识**,防 IDOR。