Files
guilixu-admin/.workbuddy/memory/2026-08-11.md
T
gxwebsoft 8ab3326f26 feat(shopGoods): 支持多规格价格区间及修复快递配送设置保存问题
- 商品列表价格列新增多规格价格区间显示,单规格依旧显示单值
- 后端新增非持久化最大价字段 priceMax/salePriceMax/dealerPriceMax,批量读取SKU价格区间
- 前端 API 和列表价格列渲染及导出支持价格区间显示
- 添加快递配送相关字段 canExpress 和 expressTemplateId 至后端实体并新增对应数据库字段
- 调整前端运费模板表单项位置及必填校验逻辑,修复保存不生效问题
- 修改 SKU 图片删除方法,避免误删主商品图片数组
2026-08-11 15:46:52 +08:00

88 lines
8.6 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.
# 2026-08-11 工作记录
## 运费模板配送地区改为「省+市两级多选穿梭弹窗」
### 需求背景
客户要求运费模板的配送地区选择改成仿截图的双栏穿梭弹窗(左侧候选 + 右侧已选),
选择粒度从原「省+市单选(级联)」升级为「多条规则覆盖多城市」,并保留「不选=全国」规则。
### 改动清单(纯前端,后端字段由用户确认可改)
1. **新建组件** `src/components/RegionMultiSelect/index.vue`
- 树形可展开:省级可展开看市级;省级"选择"=选该省全部市,市级可单独选。
- 左栏搜索(按省/市名过滤)、右栏已选列表(单项是"省/市"+取消)。
- 底部:全部选择 / 取消 / 确认。
- `v-model:value` 绑定已选**城市ID数组**;额外 emit `update:provinceIds`(由城市推导的省份ID数组)。
- 复用 `src/components/RegionsSelect/load-data.ts` 的 getRegionsData()public/json/regions-data.json,取省+市两级,丢弃区级)。
2. **model** `src/api/shop/shopExpressTemplateDetail/model/index.ts`
- 新增 `provinceIds?: string`(逗号分隔)、`cityIds?: string`(逗号分隔);保留旧 `provinceId/cityId` 兼容历史数据。
3. **编辑弹窗** `src/views/shop/shopExpressTemplateDetail/components/shopExpressTemplateDetailEdit.vue`
- 移除 `RegionsSelect type="provinceCity"`,改用 `RegionMultiSelect`
- 打开时从 `record.cityIds`(兼容旧 `cityId`)回填;保存时数组→逗号串提交 `cityIds/provinceIds`
4. **列表页** `src/views/shop/shopExpressTemplateDetail/index.vue`
- "配送地区"列 customRender 改为遍历 `cityIds` 多值反查 regionMap;超22字截断+tooltip;旧单值数据兼容。
- 新增 `h` 函数 import。
### 待办 / 风险
- antd 为 v4`var(--ant-color-border)` 在暗色下可能回退浅灰(边框略浅,不影响功能);如要完美暗色适配可后续改 less 变量。
- 构建验证(vite build)已通过。
## ⚠️ 重大修订(同日):前端字段名与后端不匹配,导致"没存进库"
### 根因
- 后端 `ShopExpressTemplateDetail` 实体**已有**配送区域多选机制:`regionMode`(0=全国 / 1=指定配送白名单 / 2=不发货黑名单)+ `regionIds`(逗号分隔的 6 位城市编码串)。
- 旧代码 `provinceId`/`cityId` 是单值历史字段,已被迁移脚本 `sql/shop_express_template_detail_region.sql` 迁成 `regionMode=1, regionIds=城市编码`
- 我第一版前端提交的是 `provinceIds`/`cityIds`,后端实体无此字段 → MyBatis-Plus 自动忽略未知字段 → 看起来"没存没变化"。
- ORM 是 MyBatis-Plusadd/update 用 `save()`/`updateById()` 自动生成 SQL,接收的是实体类本身(非 Param);新增字段只需在**实体加属性 + 表加列**,无需改 Mapper XML。
### 修复(对接后端已有的 regionMode/regionIds,无需后端改动)
1. model`provinceIds`/`cityIds` → 改为 `regionMode?``regionIds?`
2. `RegionMultiSelect` 组件:去掉 `update:provinceIds` emit,只 emit `update:value`(城市编码数组)。
3. 编辑弹窗:城市选择变化 → `form.regionIds = 逗号串``form.regionMode = 1`(有选)或 `0`(全国);回显从 `form.regionIds` 拆出。
4. 列表页:customRender 读 `record.regionIds` 反查城市名。
- 结论:**本需求纯前端即可闭环,后端无需改**。城市编码(`regions-data.json` 的 city value6位)与 `regionIds` 完全一致,下单校验 `DeliveryRegionChecker``regionIds` 直接生效。
## 修复:多规格SKU表格缺少库存列
- 文件:`src/views/shop/shopGoods/components/shopGoodsEdit.vue`
- 问题:`columns` 数组缺少 `stock`(库存) 和 `salePrice`(划线价) 列定义,导致多规格SKU明细表不显示库存输入框(批量设置栏有,但表格列没有)
- 修复:在 columns 数组中补齐 `salePrice``stock` 两列(bodyCell 模板已有渲染逻辑,只需加列定义)
## 修复:商品保存「快递配送时运费模板必选」前端校验缺失
- 文件:`src/views/shop/shopGoods/components/shopGoodsEdit.vue`
- 问题:后端有校验规则——`canExpress=1` 时要求 `expressTemplateId` 必填,但前端无对应校验,用户开启快递配送后直接提交被后端拦住,报错"运费模板必选"。
- 修复:在 `save()` 的 validate 通过后、请求前增加前端拦截:`canExpress=1 && !expressTemplateId``message.error('请选择运费模板')`
- UI 优化:运费模板 `<a-form-item>``:required="form.canExpress === 1"` 动态必填标记 + placeholder 提示文字。
## 修复:多规格SKU会员价(buyingPrice)保存失败
- **根因**:后端 `ShopGoodsSku` 实体类(guilixu-java + mp-java)和数据库表 `shop_goods_sku` 均无 `buying_price` 字段。前端提交的 buyingPrice 被 MyBatis-Plus 忽略 → 保存后丢失。
- **改动(三端同步)**
1. **guilixu-java** `entity/ShopGoodsSku.java`:加 `private BigDecimal buyingPrice;`
2. **mp-java** `entity/ShopGoodsSku.java`:同上(shop 模块两端同步)
3. **前端 TS 接口** `api/shop/shopGoodsSku/model/index.ts`:加 `buyingPrice?: string;`
- **需手动执行 SQL**`ALTER TABLE shop_goods_sku ADD COLUMN buying_price DECIMAL(10,2) DEFAULT NULL COMMENT '会员价' AFTER cost;`
## 修复:多规格SKU图片删不掉
- 文件:`src/views/shop/shopGoods/components/shopGoodsEdit.vue`
- 问题:`onDeleteSkuItem(index)` 误删 `images.value`(主商品图片数组),而非 `skuList.value[index].images`(对应SKU行图片)→ 点击 × 无反应
- 修复:改为清空 `skuList.value[index].images = []` + `image = ''`
## 商品列表「价格」列支持多规格价格区间(方案A)
- 需求:多规格商品各 SKU 价格不同,原列表价格列只显示主表 `price/salePrice/dealerPrice`(占位值),与 SKU 实际价格对不上。确认采用方案A:**多规格显示 ¥最低价 ~ ¥最高价,单规格保持单值**。
- 后端(**guilixu-java**,生效后端):
1. `entity/ShopGoods.java`:加 3 个**非持久化**字段 `priceMax / salePriceMax / dealerPriceMax`BigDecimal`@TableField(exist = false)`)。
2. `service/impl/ShopGoodsServiceImpl.java``pageRel`/`listRel` 查完后调 `fillSkuPriceRange(list)`——收集 `specs==1` 的商品,`shopGoodsSkuService.lambdaQuery().in(goodsId, ids)` 一次批量查 SKU,按 `price/salePrice/buyingPrice` 算 min/max 回填(主表 `price=min``priceMax=max`;市场价/会员价同理)。只填内存对象、不落库。
- 前端(**guilixu-admin**):
1. `api/shop/shopGoods/model/index.ts`ShopGoods 加 `priceMax? / salePriceMax? / dealerPriceMax?`
2. `views/shop/shopGoods/index.vue`priceGroup 列渲染改为「区间不相等时 ¥min ~ ¥max,否则单值」;导出 Excel 新增 `fmtPrice()` 同步处理。
- 验证:`guilixu-java` `mvn -o compile` 通过(本机无全局 mvn,临时用 /tmp/apache-maven-3.9.6 离线编译);`guilixu-admin` `vite build` 通过。
- 待确认:**mp-java 是否需要同步**——本部署 admin 商城走 guilixu-javamp-java 未参与;若 mp-java 侧也有管理端/列表调用 ShopGoods.listRel,则需同步 `fillSkuPriceRange` + 实体字段。
## 修复:商品「是否可以快递配送 / 运费模板」保存不成功(同根因)
- **根因**`ShopGoods` 实体(guilixu-java)与 `shop_goods` 表**都没有** `can_express` / `express_template_id` 字段。前端提交这两个值被 MyBatis-Plus 静默丢弃(与 8-11 多规格 buyingPrice 同一类问题)。
- **改动**
1. **guilixu-java** `entity/ShopGoods.java`:加 `private Integer canExpress;`(是否可以快递配送 0/1+ `private Integer expressTemplateId;`(运费模板ID,关联 shop_express_template.id)。`map-underscore-to-camel-case=true` 已开,自动映射 `can_express`/`express_template_id`
2. **迁移 SQL**(新建)`guilixu-java/sql/add_shop_goods_express_fields.sql``ALTER TABLE shop_goods ADD can_express TINYINT NOT NULL DEFAULT 1, ADD express_template_id INT NULL;`
3. **前端** `shopGoodsEdit.vue`:把"运费模板"表单项从「基本信息」页签移到「营销设置」页签内、"是否可以快递配送"开关**正下方**(用户要求)。
- **⚠️ 上线前置动作(否则不生效)**:必须在 db_guilixu 执行该 SQL 加列,且 guilixu-java 需重新编译部署(实体加了字段)。
- 此修复同时解决"运费模板"也存不进库的问题(同一根因,用户只报了 canExpress)。
- 前端 运费模板 必填校验(save 内 `canExpress===1 && !expressTemplateId`)与动态 label 已早先存在,逻辑无需改。