xsh_1997 vor 2 Wochen
Ursprung
Commit
ab671df7df

+ 15 - 2
doc/前端设计/前端设计.md

@@ -87,7 +87,20 @@
87 87
 
88 88
 **禁止**:同一区间用两个 `type="date"` 的 `el-date-picker` 分别选开始、结束(旧页面若仍存在,新功能与改版时须改为 `daterange`)。
89 89
 
90
-#### 5.「ruoyi-ui」默认布局设置 **一律**使用 Element UI:
90
+#### 5. 商家端店铺上下文(统一规范)
91
+
92
+凡 **店铺经营管理端**(`ruoyi-ui` 下 `views/agri/seller/**`)涉及 **当前店铺 / 切换店铺** 的:
93
+
94
+| 项 | 说明 |
95
+|----|------|
96
+| **唯一入口** | 顶部导航 **`Navbar.vue`** 展示「当前店铺」及 `el-select` 切换 |
97
+| **业务页面禁止** | **不得** 在列表卡片头、弹窗、表单中再展示店铺名称、店铺下拉、切换按钮 |
98
+| **页面仍需做的事** | `created` 时调用 `getSellerContext` → `setSellerShopContext`;API 通过 `sellerShopHeaders()` 携带 **`X-Shop-Id`** |
99
+| **切换后刷新** | Navbar 切换店铺后 **`location.reload()`**;业务页 **不要** 再实现 `handleShopSwitch` |
100
+
101
+**已实现:** `layout/components/Navbar.vue`、`api/agri/seller/context.js`、`utils/sellerShop.js`
102
+
103
+#### 6.「ruoyi-ui」默认布局设置 **一律**使用 Element UI:
91 104
 当出现 增删改查 时 默认
92 105
 ```html
93 106
 <el-card>
@@ -98,7 +111,7 @@
98 111
   表格
99 112
 </el-card> 
100 113
 
101
-#### 6.「ruoyi-ui」Cursor 输出规则
114
+#### 7.「ruoyi-ui」Cursor 输出规则
102 115
 
103 116
 1. **先看「ruoyi-ui」老代码 → 再生成代码**
104 117
 2. 给我代码前,必须先说明:

+ 5 - 8
doc/店铺后台/商品管理/店铺商品分类/店铺商品分类前端技术方案.md

@@ -14,7 +14,7 @@
14 14
 | 框架 | Vue 2 + Element UI(与 RuoYi-Vue 一致) |
15 15
 | 请求 | `@/utils/request`;商家端接口携带 **`X-Shop-Id`** |
16 16
 | 参考页面 | `ruoyi-ui/src/views/agri/goods/category/index.vue`(两级分类树交互) |
17
-| 商家端参考 | `ruoyi-ui/src/views/agri/seller/role/index.vue`(店铺上下文、切换店铺) |
17
+| 商家端参考 | `ruoyi-ui/src/views/agri/seller/role/index.vue`(`loadShopContext` 写 X-Shop-Id) |
18 18
 | 布局 | 检索区 `el-card` + `<br/>` + 列表区 `el-card` + 表格 `border` |
19 19
 
20 20
 ---
@@ -71,7 +71,6 @@
71 71
 ├── 检索区(el-card)
72 72
 │   └── 分类名称 keyword(模糊,选填)
73 73
 ├── 列表区(el-card + border 表格)
74
-│   ├── 卡片头:当前店铺名称;经营账号可多店时下拉切换
75 74
 │   ├── 提示:本店分类与平台分类数据隔离
76 75
 │   ├── 工具栏:添加一级分类、批量删除
77 76
 │   ├── 列:分类名称(层级标识)、上级分类、分类图片、是否显示、是否热门、排序
@@ -96,11 +95,9 @@
96 95
 |------|------|
97 96
 | 页面 `created` | `GET /agri/seller/context` → `setSellerShopContext(shopId, shopName)` |
98 97
 | 每次 API | `sellerShopHeaders()` 注入 `X-Shop-Id` |
99
-| 页头展示 | 当前店铺名称(只读) |
100
-| 多店切换 | 卡片头 `el-select` 调 `PUT /agri/seller/context/shop`,成功后刷新列表与一级下拉 |
101
-| 导航栏 | `Navbar.vue` 对 `merchant` 角色同样提供店铺切换(切换后 `location.reload()`) |
98
+| 切换店铺 | **仅 Navbar**(`merchant` 角色)→ `PUT /agri/seller/context/shop` → `location.reload()` |
102 99
 
103
-未选店铺或无效店铺时,后端返回业务错误;前端依赖全局 `request` 拦截器提示
100
+**业务页禁止** 展示店铺名称或选择器(见 `doc/前端设计/前端设计.md` §5)。
104 101
 
105 102
 ---
106 103
 
@@ -173,8 +170,8 @@
173 170
 ## 9. 联调检查清单
174 171
 
175 172
 - [ ] 后端菜单配置组件路径 `agri/seller/shopCategory/index` 及按钮权限
176
-- [ ] 商户经营账号登录,Navbar / 页头显示当前店铺
177
-- [ ] 多店账号切换店铺后列表仅展示该店分类
173
+- [ ] 商户经营账号登录,Navbar 显示当前店铺并可切换
174
+- [ ] 多店账号 Navbar 切换店铺后列表仅展示该店分类
178 175
 - [ ] 添加一级、添加二级、编辑、单删、批删
179 176
 - [ ] 分类下有商品时删除失败,错误提示可读
180 177
 - [ ] 子管理员无权限时按钮隐藏、接口 403

+ 5 - 5
doc/店铺后台/商家端店铺上下文接口说明.md

@@ -221,7 +221,7 @@ buildContext(account, shopId)             → 返回新上下文(结构同 GET
221 221
     → 后续 /agri/seller/** 请求头附加 X-Shop-Id: {shopId}
222 222
 经营账号切换店铺
223 223
     → PUT /agri/seller/context/shop { shopId }
224
-    → 更新缓存 → 刷新当前页列表
224
+    → 更新缓存 → Navbar 刷新页面(location.reload)
225 225
 ```
226 226
 
227 227
 ### 5.2 已实现前端
@@ -230,14 +230,14 @@ buildContext(account, shopId)             → 返回新上下文(结构同 GET
230 230
 |------|------|
231 231
 | `api/agri/seller/context.js` | `getSellerContext`、`switchSellerShop` |
232 232
 | `utils/sellerShop.js` | `setSellerShopContext`、`sellerShopHeaders()` |
233
-| `views/agri/seller/role/index.vue` | 卡片头展示店铺、下拉切换 |
234
-| `views/agri/seller/employee/index.vue` | 同上 |
233
+| `layout/components/Navbar.vue` | **唯一** 展示「当前店铺」及下拉切换(`merchant` 角色) |
234
+| `views/agri/seller/**/index.vue` | 仅 `loadShopContext` 写缓存;**不** 展示店铺 UI |
235 235
 
236 236
 ### 5.3 注意事项
237 237
 
238 238
 - `GET /agri/seller/context` **不要** 带 `X-Shop-Id`(也不需要)。
239
-- 切换店铺后,角色/员工等 **按店铺隔离** 的列表须重新请求。
240
-- `switchable === false` 时不展示店铺切换控件
239
+- Navbar 切换店铺后整页刷新,各业务列表自动按新店 `X-Shop-Id` 重新请求。
240
+- **业务页面禁止** 重复实现店铺选择器(见 `doc/前端设计/前端设计.md` §5)
241 241
 
242 242
 ---
243 243
 

+ 4 - 4
doc/店铺后台/账号管理/员工管理/员工管理前端技术方案.md

@@ -58,7 +58,7 @@
58 58
 ├── 检索区(el-card)
59 59
 │   └── 员工名称 employeeName(模糊,仅此一项)
60 60
 ├── 列表区(el-card + border 表格)
61
-│   ├── 卡片头:当前店铺、商户名称、员工配额 usedCount/maxCount、店铺切换
61
+│   ├── 卡片头:商户名称、员工配额 usedCount/maxCount
62 62
 │   ├── 配额已满 warning
63 63
 │   ├── 工具栏:创建员工(配额满 disabled)
64 64
 │   ├── 列:员工账号、员工名称、手机号(脱敏)、邮箱、状态
@@ -113,7 +113,7 @@
113 113
 
114 114
 | 字段 | 展示 |
115 115
 |------|------|
116
-| shopName / merchantName | 卡片头只读 |
116
+| shopName / merchantName | quota 接口返回;页头 **仅展示商户名称**(店铺切换见 Navbar) |
117 117
 | usedCount / maxCount | 「员工 3/5」;**商户级**计数 |
118 118
 | quotaFull | `usedCount >= maxCount` 时禁用创建、展示 warning |
119 119
 
@@ -152,7 +152,7 @@
152 152
 
153 153
 | 场景 | 前端行为 |
154 154
 |------|----------|
155
-| 店铺上下文 | 与角色管理相同:context → X-Shop-Id → 切换店铺刷新 |
155
+| 店铺上下文 | `loadShopContext` 写 X-Shop-Id;切换店铺 **仅 Navbar** reload |
156 156
 | 创建 | 配额满拦截;角色至少 1 个 |
157 157
 | 创建成功 | 提示须改密;可选立即打开改密弹窗 |
158 158
 | 启用员工 | 编辑 status=0;超配额由后端 msg |
@@ -194,7 +194,7 @@ v-hasPermi="['agri:seller:employee:remove']"
194 194
 - [ ] 编辑 / 修改账号 / 改密 / 删除各接口正常  
195 195
 - [ ] 超配额创建/启用后端 msg  
196 196
 - [ ] 手机号重复后端 msg  
197
-- [ ] 切换店铺后列表变化  
197
+- [ ] 切换店铺后列表变化(Navbar reload)  
198 198
 
199 199
 ---
200 200
 

+ 4 - 5
doc/店铺后台/账号管理/角色管理/角色管理前端技术方案.md

@@ -58,7 +58,6 @@
58 58
 ├── 检索区(el-card)
59 59
 │   └── 角色名称 roleName(模糊,仅此一项)
60 60
 ├── 列表区(el-card + border 表格)
61
-│   ├── 卡片头:当前店铺名称(只读);经营账号可多店时下拉切换
62 61
 │   ├── 工具栏:创建角色
63 62
 │   ├── 列:角色名称、角色描述、状态、绑定员工数、创建时间
64 63
 │   └── 操作:修改角色、删除
@@ -79,9 +78,9 @@
79 78
 |------|------|
80 79
 | 页面 `created` | `GET /agri/seller/context` → 缓存 `shopId` / `shopName` |
81 80
 | 后续请求 | `sellerShop.js` 为 `/agri/seller/role/*` 附加 `X-Shop-Id` |
82
-| 切换店铺 | 经营账号且 `switchable=true` 时展示下拉 → `PUT /agri/seller/context/shop` → 刷新列表 |
81
+| 切换店铺 | **仅 Navbar**(`layout/components/Navbar.vue`)→ `PUT /agri/seller/context/shop` → `location.reload()` |
83 82
 
84
-未携带有效 `X-Shop-Id` 时后端可能返回「请先选择当前店铺」
83
+**业务页禁止** 展示店铺名称或选择器(见 `doc/前端设计/前端设计.md` §5)
85 84
 
86 85
 ---
87 86
 
@@ -188,13 +187,13 @@ v-hasPermi="['agri:seller:role:remove']"
188 187
 
189 188
 - [ ] 菜单组件路径 `agri/seller/role/index` 可打开  
190 189
 - [ ] 登录后 context 写入 `X-Shop-Id`;列表仅当前店铺角色  
191
-- [ ] 卡片头展示当前店铺名称  
190
+- [ ] Navbar 展示当前店铺(经营账号可切换)  
192 191
 - [ ] roleName 模糊检索  
193 192
 - [ ] 创建:名称+至少一项权限;店铺内重名后端 msg  
194 193
 - [ ] 权限树仅商家端菜单  
195 194
 - [ ] 编辑回显 menuIds;修改成功  
196 195
 - [ ] 删除已绑定角色后端阻断  
197
-- [ ] 切换店铺后列表范围变化  
196
+- [ ] 切换店铺后列表范围变化(Navbar reload)  
198 197
 - [ ] 经营账号外访问后端 msg「仅商户经营账号可管理角色」  
199 198
 
200 199
 ---

+ 1 - 36
ruoyi-ui/src/views/agri/seller/employee/index.vue

@@ -19,24 +19,8 @@
19 19
     <el-card shadow="never" class="table-card">
20 20
       <div slot="header" class="card-header">
21 21
         <span>员工列表</span>
22
-        <span class="header-tip">当前店铺:{{ quota.shopName || currentShopName || '—' }}</span>
23 22
         <span class="header-tip" v-if="quota.merchantName">商户:{{ quota.merchantName }}</span>
24 23
         <span class="quota-tip">员工 {{ quota.usedCount != null ? quota.usedCount : '—' }}/{{ quota.maxCount != null ? quota.maxCount : '—' }}</span>
25
-        <el-select
26
-          v-if="shopSwitchable && shopOptions.length > 1"
27
-          v-model="currentShopId"
28
-          size="small"
29
-          placeholder="切换店铺"
30
-          style="width: 200px; margin-left: 12px"
31
-          @change="handleShopSwitch"
32
-        >
33
-          <el-option
34
-            v-for="item in shopOptions"
35
-            :key="item.shopId"
36
-            :label="item.shopName"
37
-            :value="item.shopId"
38
-          />
39
-        </el-select>
40 24
       </div>
41 25
 
42 26
       <el-row :gutter="10" class="mb8">
@@ -179,7 +163,7 @@ import {
179 163
   resetSellerEmployeePassword,
180 164
   delSellerEmployee
181 165
 } from "@/api/agri/seller/employee"
182
-import { getSellerContext, switchSellerShop } from "@/api/agri/seller/context"
166
+import { getSellerContext } from "@/api/agri/seller/context"
183 167
 import { setSellerShopContext } from "@/utils/sellerShop"
184 168
 import passwordRule from "@/utils/passwordRule"
185 169
 
@@ -215,10 +199,6 @@ export default {
215 199
       open: false,
216 200
       profileOpen: false,
217 201
       pwdOpen: false,
218
-      currentShopName: "",
219
-      currentShopId: undefined,
220
-      shopSwitchable: false,
221
-      shopOptions: [],
222 202
       quota: {},
223 203
       roleOptions: [],
224 204
       queryParams: {
@@ -298,24 +278,9 @@ export default {
298 278
         const data = response.data || {}
299 279
         if (data.shopId != null) {
300 280
           setSellerShopContext(data.shopId, data.shopName)
301
-          this.currentShopId = data.shopId
302
-          this.currentShopName = data.shopName || ""
303
-          this.shopSwitchable = data.switchable === true
304
-          this.shopOptions = data.shops || []
305 281
         }
306 282
       })
307 283
     },
308
-    /** 切换店铺 */
309
-    handleShopSwitch(shopId) {
310
-      switchSellerShop({ shopId: shopId }).then(response => {
311
-        const data = response.data || {}
312
-        setSellerShopContext(data.shopId, data.shopName)
313
-        this.currentShopId = data.shopId
314
-        this.currentShopName = data.shopName || ""
315
-        this.queryParams.pageNum = 1
316
-        this.refreshPage()
317
-      })
318
-    },
319 284
     /** 刷新列表与配额 */
320 285
     refreshPage() {
321 286
       this.loadQuota()

+ 1 - 55
ruoyi-ui/src/views/agri/seller/role/index.vue

@@ -17,26 +17,6 @@
17 17
 
18 18
     <!-- 列表区 -->
19 19
     <el-card shadow="never" class="table-card">
20
-      <div slot="header" class="card-header">
21
-        <span>角色列表</span>
22
-        <span class="header-tip">当前店铺:{{ currentShopName || '—' }}</span>
23
-        <el-select
24
-          v-if="shopSwitchable && shopOptions.length > 1"
25
-          v-model="currentShopId"
26
-          size="small"
27
-          placeholder="切换店铺"
28
-          style="width: 200px; margin-left: 12px"
29
-          @change="handleShopSwitch"
30
-        >
31
-          <el-option
32
-            v-for="item in shopOptions"
33
-            :key="item.shopId"
34
-            :label="item.shopName"
35
-            :value="item.shopId"
36
-          />
37
-        </el-select>
38
-      </div>
39
-
40 20
       <el-row :gutter="10" class="mb8">
41 21
         <el-col :span="1.5">
42 22
           <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['agri:seller:role:add']">创建角色</el-button>
@@ -128,7 +108,7 @@ import {
128 108
   updateSellerRole,
129 109
   delSellerRole
130 110
 } from "@/api/agri/seller/role"
131
-import { getSellerContext, switchSellerShop } from "@/api/agri/seller/context"
111
+import { getSellerContext } from "@/api/agri/seller/context"
132 112
 import { setSellerShopContext } from "@/utils/sellerShop"
133 113
 
134 114
 export default {
@@ -148,14 +128,6 @@ export default {
148 128
       title: "",
149 129
       // 是否显示弹窗
150 130
       open: false,
151
-      // 当前店铺名称
152
-      currentShopName: "",
153
-      // 当前店铺 ID
154
-      currentShopId: undefined,
155
-      // 是否可切换店铺
156
-      shopSwitchable: false,
157
-      // 可切换店铺列表
158
-      shopOptions: [],
159 131
       // 菜单树数据
160 132
       menuOptions: [],
161 133
       menuExpand: false,
@@ -201,24 +173,9 @@ export default {
201 173
         const data = response.data || {}
202 174
         if (data.shopId != null) {
203 175
           setSellerShopContext(data.shopId, data.shopName)
204
-          this.currentShopId = data.shopId
205
-          this.currentShopName = data.shopName || ""
206
-          this.shopSwitchable = data.switchable === true
207
-          this.shopOptions = data.shops || []
208 176
         }
209 177
       })
210 178
     },
211
-    /** 经营账号切换店铺 */
212
-    handleShopSwitch(shopId) {
213
-      switchSellerShop({ shopId: shopId }).then(response => {
214
-        const data = response.data || {}
215
-        setSellerShopContext(data.shopId, data.shopName)
216
-        this.currentShopId = data.shopId
217
-        this.currentShopName = data.shopName || ""
218
-        this.queryParams.pageNum = 1
219
-        this.getList()
220
-      })
221
-    },
222 179
     /** 查询角色列表 */
223 180
     getList() {
224 181
       this.loading = true
@@ -374,17 +331,6 @@ export default {
374 331
 .search-card {
375 332
   margin-bottom: 0;
376 333
 }
377
-.card-header {
378
-  display: flex;
379
-  align-items: center;
380
-  flex-wrap: wrap;
381
-  gap: 8px;
382
-}
383
-.header-tip {
384
-  color: #909399;
385
-  font-size: 13px;
386
-  font-weight: normal;
387
-}
388 334
 .tree-border {
389 335
   margin-top: 8px;
390 336
   border: 1px solid #e5e6e7;

+ 1 - 52
ruoyi-ui/src/views/agri/seller/shopCategory/index.vue

@@ -17,26 +17,6 @@
17 17
 
18 18
     <!-- 列表区 -->
19 19
     <el-card shadow="never" class="table-card">
20
-      <div slot="header" class="card-header">
21
-        <span>店铺商品分类</span>
22
-        <span class="header-tip">当前店铺:{{ currentShopName || '—' }}</span>
23
-        <el-select
24
-          v-if="shopSwitchable && shopOptions.length > 1"
25
-          v-model="currentShopId"
26
-          size="small"
27
-          placeholder="切换店铺"
28
-          style="width: 200px; margin-left: 12px"
29
-          @change="handleShopSwitch"
30
-        >
31
-          <el-option
32
-            v-for="item in shopOptions"
33
-            :key="item.shopId"
34
-            :label="item.shopName"
35
-            :value="item.shopId"
36
-          />
37
-        </el-select>
38
-      </div>
39
-
40 20
       <el-alert type="info" :closable="false" class="mb12" title="当前店铺商品分类(shop_id 非空),与平台全部分类数据隔离;最多两级" />
41 21
 
42 22
       <el-row :gutter="10" class="mb8">
@@ -145,7 +125,7 @@ import {
145 125
   updateShopCategory,
146 126
   delShopCategory
147 127
 } from "@/api/agri/seller/shopCategory"
148
-import { getSellerContext, switchSellerShop } from "@/api/agri/seller/context"
128
+import { getSellerContext } from "@/api/agri/seller/context"
149 129
 import { setSellerShopContext } from "@/utils/sellerShop"
150 130
 
151 131
 export default {
@@ -161,10 +141,6 @@ export default {
161 141
       level1List: [],
162 142
       title: "",
163 143
       open: false,
164
-      currentShopName: "",
165
-      currentShopId: undefined,
166
-      shopSwitchable: false,
167
-      shopOptions: [],
168 144
       queryParams: {
169 145
         pageNum: 1,
170 146
         pageSize: 10,
@@ -201,25 +177,9 @@ export default {
201 177
         const data = response.data || {}
202 178
         if (data.shopId != null) {
203 179
           setSellerShopContext(data.shopId, data.shopName)
204
-          this.currentShopId = data.shopId
205
-          this.currentShopName = data.shopName || ""
206
-          this.shopSwitchable = data.switchable === true
207
-          this.shopOptions = data.shops || []
208 180
         }
209 181
       })
210 182
     },
211
-    /** 经营账号切换店铺 */
212
-    handleShopSwitch(shopId) {
213
-      switchSellerShop({ shopId: shopId }).then(response => {
214
-        const data = response.data || {}
215
-        setSellerShopContext(data.shopId, data.shopName)
216
-        this.currentShopId = data.shopId
217
-        this.currentShopName = data.shopName || ""
218
-        this.queryParams.pageNum = 1
219
-        this.getList()
220
-        this.loadLevel1Options()
221
-      })
222
-    },
223 183
     /** 查询分类列表 */
224 184
     getList() {
225 185
       this.loading = true
@@ -343,17 +303,6 @@ export default {
343 303
 .search-card {
344 304
   margin-bottom: 0;
345 305
 }
346
-.card-header {
347
-  display: flex;
348
-  align-items: center;
349
-  flex-wrap: wrap;
350
-  gap: 8px;
351
-}
352
-.header-tip {
353
-  color: #909399;
354
-  font-size: 13px;
355
-  font-weight: normal;
356
-}
357 306
 .mb12 {
358 307
   margin-bottom: 12px;
359 308
 }