diff --git a/src/api/mall/product/spu.ts b/src/api/mall/product/spu.ts index 60248dd1..528d6acc 100644 --- a/src/api/mall/product/spu.ts +++ b/src/api/mall/product/spu.ts @@ -66,6 +66,7 @@ export interface Spu { deviceId?: number // 设备号 purchaseLimitType?: number // 限购类型:0-不限购,1-每天限购,2-每周限购,3-终身限购 purchaseLimitCount?: number // 限购数量 + hotRecommend?: boolean // 是否热门推荐(分页筛选:不传则不筛;传 true/false 精确匹配) } // 获得 Spu 列表 diff --git a/src/api/mall/promotion/discount/discountActivity.ts b/src/api/mall/promotion/discount/discountActivity.ts index e755c1bd..4934bd9f 100644 --- a/src/api/mall/promotion/discount/discountActivity.ts +++ b/src/api/mall/promotion/discount/discountActivity.ts @@ -5,6 +5,7 @@ export interface DiscountActivityVO { id?: number spuId?: number name?: string + picUrl?: string status?: number remark?: string startTime?: Date diff --git a/src/api/mall/promotion/ranking/index.ts b/src/api/mall/promotion/ranking/index.ts new file mode 100644 index 00000000..1fb0c5f7 --- /dev/null +++ b/src/api/mall/promotion/ranking/index.ts @@ -0,0 +1,64 @@ +import request from '@/config/axios' + +/** 榜单内商品(响应) */ +export interface RankingSpuResp { + id: number // 关联表主键 + spuId: number + sort: number +} + +/** 榜单详情/列表行 */ +export interface RankingRespVO { + id: number + title: string + subtitle?: string + iconUrl?: string + sort: number + status: number // 0 开启 1 关闭 + remark?: string + createTime?: string + spus: RankingSpuResp[] +} + +/** 提交时榜单商品项 */ +export interface RankingSpuItem { + spuId: number + sort: number +} + +export interface RankingCreateBody { + title: string + subtitle?: string + iconUrl?: string + sort: number + status: number + remark?: string + spus: RankingSpuItem[] +} + +export type RankingUpdateBody = RankingCreateBody & { id: number } + +/** 分页查询 */ +export const getRankingPage = async (params: any) => { + return await request.get({ url: '/promotion/ranking/page', params }) +} + +/** 详情 */ +export const getRanking = async (id: number) => { + return await request.get({ url: '/promotion/ranking/get', params: { id } }) +} + +/** 创建 */ +export const createRanking = async (data: RankingCreateBody) => { + return await request.post({ url: '/promotion/ranking/create', data }) +} + +/** 更新 */ +export const updateRanking = async (data: RankingUpdateBody) => { + return await request.put({ url: '/promotion/ranking/update', data }) +} + +/** 删除 */ +export const deleteRanking = async (id: number) => { + return await request.delete({ url: '/promotion/ranking/delete', params: { id } }) +} diff --git a/src/views/mall/product/spu/components/SkuList.vue b/src/views/mall/product/spu/components/SkuList.vue index b44ae47d..99783b91 100644 --- a/src/views/mall/product/spu/components/SkuList.vue +++ b/src/views/mall/product/spu/components/SkuList.vue @@ -25,16 +25,6 @@ v-for="(item, index) in tableHeaders" :key="index" :label="item.label" align="ce - - - - - - - - - - - - {{ row.cabinetChannel }} - - - - - {{ row.cabinetType }} - - {{ row.price }} @@ -221,16 +201,6 @@ v-for="(item, index) in tableHeaders" :key="index" :label="item.label" align="ce {{ row.barCode }} - - - {{ row.cabinetChannel }} - - - - - {{ row.cabinetType }} - - {{ formatToFraction(row.price) }} diff --git a/src/views/mall/product/spu/form/OtherForm.vue b/src/views/mall/product/spu/form/OtherForm.vue index e7e63580..ca65179f 100644 --- a/src/views/mall/product/spu/form/OtherForm.vue +++ b/src/views/mall/product/spu/form/OtherForm.vue @@ -25,6 +25,16 @@ class="w-80!" /> + + + diff --git a/src/views/mall/promotion/ranking/index.vue b/src/views/mall/promotion/ranking/index.vue new file mode 100644 index 00000000..cde15055 --- /dev/null +++ b/src/views/mall/promotion/ranking/index.vue @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + 搜索 + + + + 重置 + + + + 新增 + + + + + + + 排序数值越大越靠前(榜单与榜单内商品规则一致) + + + + + + + + - + + + + + + + + + + + {{ row.spus?.length ?? 0 }} + + + + + + + 编辑 + + + 删除 + + + + + + + + + + + diff --git a/src/views/system/user/user-device-binding.md b/src/views/system/user/user-device-binding.md new file mode 100644 index 00000000..8424a998 --- /dev/null +++ b/src/views/system/user/user-device-binding.md @@ -0,0 +1,679 @@ +# 用户设备绑定功能 API 文档 + +## 概述 + +本功能实现了系统用户与设备的多对多绑定关系,一个用户账号可以绑定多个设备进行管理。设备下拉列表会根据当前登录用户绑定的设备自动过滤。 + +## 数据库表结构 + +### system_user_device(用户设备关联表) + +| 字段名 | 类型 | 说明 | 备注 | +|--------|------|------|------| +| id | bigint | 自增主键 | PRIMARY KEY | +| user_id | bigint | 用户ID | 关联 system_users.id | +| device_id | bigint | 设备ID | 关联 product_device.id | +| creator | varchar(64) | 创建者 | | +| create_time | datetime | 创建时间 | | +| updater | varchar(64) | 更新者 | | +| update_time | datetime | 更新时间 | | +| deleted | bit(1) | 是否删除 | 默认 0 | +| tenant_id | bigint | 租户编号 | 默认 0 | + +**唯一索引**:`uk_user_device` (user_id, device_id, tenant_id, deleted) - 防止重复绑定 + +--- + +## API 接口列表 + +### 1. 绑定用户设备 + +**接口地址**:`POST /system/user/bind-devices` + +**接口说明**:为指定用户绑定一个或多个设备。如果设备已绑定,会自动跳过(去重)。 + +**权限要求**:`system:user:update` + +**请求参数**: + +```json +{ + "userId": 1, + "deviceIds": [1, 2, 3] +} +``` + +**参数说明**: + +| 参数名 | 类型 | 必填 | 说明 | 示例 | +|--------|------|------|------|------| +| userId | Long | 是 | 用户ID | 1 | +| deviceIds | List | 是 | 设备ID列表 | [1, 2, 3] | + +**请求示例**: + +```bash +curl -X POST "http://localhost:8080/system/user/bind-devices" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer {token}" \ + -d '{ + "userId": 1, + "deviceIds": [1, 2, 3] + }' +``` + +**响应示例**: + +```json +{ + "code": 0, + "data": true, + "msg": "操作成功" +} +``` + +**错误响应**: + +```json +{ + "code": 1_003_000_001, + "data": null, + "msg": "用户不存在" +} +``` + +--- + +### 2. 解绑用户设备 + +**接口地址**:`DELETE /system/user/unbind-devices` + +**接口说明**:为指定用户解绑一个或多个设备。 + +**权限要求**:`system:user:update` + +**请求参数**: + +```json +{ + "userId": 1, + "deviceIds": [2, 3] +} +``` + +**参数说明**: + +| 参数名 | 类型 | 必填 | 说明 | 示例 | +|--------|------|------|------|------| +| userId | Long | 是 | 用户ID | 1 | +| deviceIds | List | 是 | 要解绑的设备ID列表 | [2, 3] | + +**请求示例**: + +```bash +curl -X DELETE "http://localhost:8080/system/user/unbind-devices" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer {token}" \ + -d '{ + "userId": 1, + "deviceIds": [2, 3] + }' +``` + +**响应示例**: + +```json +{ + "code": 0, + "data": true, + "msg": "操作成功" +} +``` + +--- + +### 3. 获取用户绑定的设备ID列表 + +**接口地址**:`GET /system/user/get-device-ids` + +**接口说明**:查询指定用户已绑定的所有设备ID列表。 + +**权限要求**:`system:user:query` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | 示例 | +|--------|------|------|------|------| +| userId | Long | 是 | 用户ID | 1 | + +**请求示例**: + +```bash +curl -X GET "http://localhost:8080/system/user/get-device-ids?userId=1" \ + -H "Authorization: Bearer {token}" +``` + +**响应示例**: + +```json +{ + "code": 0, + "data": { + "userId": 1, + "deviceIds": [1, 2, 3] + }, + "msg": "操作成功" +} +``` + +**响应字段说明**: + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| userId | Long | 用户ID | +| deviceIds | List | 绑定的设备ID列表 | + +--- + +### 4. 获取设备下拉列表(精简) + +**接口地址**:`GET /product/device/list-all-simple` + +**接口说明**:获取设备下拉列表,**自动根据当前登录用户绑定的设备进行过滤**。如果用户未绑定任何设备,返回空列表。 + +**权限要求**:`product:device:query` + +**请求参数**:无 + +**请求示例**: + +```bash +curl -X GET "http://localhost:8080/product/device/list-all-simple" \ + -H "Authorization: Bearer {token}" +``` + +**响应示例**: + +```json +{ + "code": 0, + "data": [ + { + "id": 1, + "name": "贩卖机001", + "deviceId": "123456789012345" + }, + { + "id": 2, + "name": "贩卖机002", + "deviceId": "123456789012346" + } + ], + "msg": "操作成功" +} +``` + +**响应字段说明**: + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| id | Long | 设备编号 | +| name | String | 设备名称 | +| deviceId | String | 设备IMEI | + +**重要说明**: +- 此接口会根据当前登录用户自动过滤设备 +- 只返回当前用户已绑定的设备 +- 如果用户未绑定任何设备,返回空数组 `[]` +- 兼容旧逻辑:如果无法获取用户ID,则根据部门ID查询 + +--- + +## 前端实现建议 + +### 1. 用户管理页面 - 设备绑定功能 + +在用户详情或编辑页面,添加设备绑定功能: + +```vue + + + + + + + + + + 可绑定多个设备,设备下拉列表会根据当前用户绑定的设备自动过滤 + + + + + +``` + +### 2. 设备下拉选择组件 + +在需要使用设备下拉的地方(如商品管理、订单管理等),使用设备下拉接口: + +```vue + + + + + + + +``` + +### 3. 用户设备管理页面(可选) + +可以创建一个专门的页面来管理用户设备绑定关系: + +```vue + + + + + 用户设备绑定管理 + + 绑定设备 + + + + + + + + + + + + + + + + + 解绑 + + + + + + + + + + + + + 取消 + 确定 + + + + + + +``` + +--- + +## 业务逻辑说明 + +### 1. 设备绑定逻辑 + +- **去重处理**:绑定设备时会自动检查已绑定的设备,只绑定新设备,避免重复 +- **事务支持**:绑定操作在事务中执行,保证数据一致性 +- **用户校验**:绑定前会校验用户是否存在 + +### 2. 设备下拉过滤逻辑 + +- **优先级**:优先根据用户绑定的设备过滤 +- **兼容性**:如果无法获取用户ID,则回退到根据部门ID查询(兼容旧逻辑) +- **空列表处理**:如果用户未绑定任何设备,返回空列表 + +### 3. 数据清理 + +- **删除用户时**:自动删除该用户的所有设备绑定关系 +- **删除设备时**:建议同时删除相关的用户设备绑定关系(需要在删除设备接口中实现) + +--- + +## 错误码说明 + +| 错误码 | 说明 | +|--------|------| +| 1_003_000_001 | 用户不存在 | +| 1_008_009_000 | 设备不存在 | + +--- + +## 接口调用流程示例 + +### 场景:为用户绑定设备 + +1. **获取用户信息** + ``` + GET /system/user/get?id=1 + ``` + +2. **获取所有可用设备列表**(用于选择) + ``` + GET /product/device/page?pageNo=1&pageSize=100 + ``` + +3. **获取用户已绑定的设备**(用于回显) + ``` + GET /system/user/get-device-ids?userId=1 + ``` + +4. **绑定设备** + ``` + POST /system/user/bind-devices + { + "userId": 1, + "deviceIds": [1, 2, 3] + } + ``` + +### 场景:使用设备下拉(自动过滤) + +1. **获取设备下拉列表**(自动根据当前用户过滤) + ``` + GET /product/device/list-all-simple + ``` + 返回结果只包含当前登录用户已绑定的设备 + +--- + +## 注意事项 + +1. **权限控制**: + - 绑定/解绑设备需要 `system:user:update` 权限 + - 查询设备列表需要 `product:device:query` 权限 + +2. **数据一致性**: + - 绑定操作支持事务,失败会自动回滚 + - 建议在删除设备时,同步删除用户设备关联 + +3. **性能优化**: + - 设备下拉接口已优化,只返回用户绑定的设备,减少数据量 + - 如果用户未绑定设备,直接返回空列表,避免不必要的查询 + +4. **兼容性**: + - 设备下拉接口兼容旧逻辑,如果无法获取用户ID,会回退到部门ID查询 + - 新老逻辑可以平滑过渡 + +--- + +## 前端开发检查清单 + +- [ ] 用户管理页面添加设备绑定功能 +- [ ] 设备下拉组件使用新的接口(自动过滤) +- [ ] 处理用户未绑定设备的情况(提示信息) +- [ ] 实现设备绑定的增删改查功能 +- [ ] 添加权限校验 +- [ ] 添加错误处理和提示 +- [ ] 添加加载状态显示 +- [ ] 实现设备列表的搜索和筛选(如需要) + +--- + +## 测试用例 + +### 测试场景 1:绑定设备 + +1. 选择用户ID = 1 +2. 选择设备ID = [1, 2, 3] +3. 调用绑定接口 +4. 验证返回成功 +5. 查询用户设备列表,验证设备已绑定 + +### 测试场景 2:重复绑定 + +1. 用户已绑定设备 [1, 2] +2. 再次绑定设备 [2, 3] +3. 验证设备 [2] 不会重复绑定 +4. 验证最终绑定设备为 [1, 2, 3] + +### 测试场景 3:设备下拉过滤 + +1. 用户A绑定设备 [1, 2] +2. 用户B绑定设备 [2, 3] +3. 用户A登录,调用设备下拉接口 +4. 验证只返回设备 [1, 2] +5. 用户B登录,调用设备下拉接口 +6. 验证只返回设备 [2, 3] + +### 测试场景 4:解绑设备 + +1. 用户绑定设备 [1, 2, 3] +2. 解绑设备 [2] +3. 验证用户剩余绑定设备为 [1, 3] + +--- + +## 更新日志 + +- 2025-01-XX:初始版本,实现用户设备多对多绑定功能 +