cf7d065379
- 用户反馈编辑弹窗的Markdown编辑器体验差,采用website-admin的富文本编辑器替代 - 新增依赖@wangeditor/editor、@wangeditor/editor-for-vue、markdown-it及类型声明 - 新建RichTextEditor组件,去除Nuxt专用包装,适配现有文件上传接口 - shopGoodsEdit.vue移除MdEditor及相关冗余代码,改用RichTextEditor显示内容 - 实现编辑时Markdown内容转换为HTML,保持数据库存储格式新旧兼容 - 保存逻辑保持不变,提交内容为HTML格式 - 确认小程序端可兼容新旧内容格式,潜在HTML图片链接误解析风险 - build验证通过,文件产出正常,图片上传小程序域名白名单需上线后确认
11 KiB
11 KiB
2026-08-11 工作记录
运费模板配送地区改为「省+市两级多选穿梭弹窗」
需求背景
客户要求运费模板的配送地区选择改成仿截图的双栏穿梭弹窗(左侧候选 + 右侧已选), 选择粒度从原「省+市单选(级联)」升级为「多条规则覆盖多城市」,并保留「不选=全国」规则。
改动清单(纯前端,后端字段由用户确认可改)
- 新建组件
src/components/RegionMultiSelect/index.vue- 树形可展开:省级可展开看市级;省级"选择"=选该省全部市,市级可单独选。
- 左栏搜索(按省/市名过滤)、右栏已选列表(单项是"省/市"+取消)。
- 底部:全部选择 / 取消 / 确认。
v-model:value绑定已选城市ID数组;额外 emitupdate:provinceIds(由城市推导的省份ID数组)。- 复用
src/components/RegionsSelect/load-data.ts的 getRegionsData()(public/json/regions-data.json,取省+市两级,丢弃区级)。
- model
src/api/shop/shopExpressTemplateDetail/model/index.ts- 新增
provinceIds?: string(逗号分隔)、cityIds?: string(逗号分隔);保留旧provinceId/cityId兼容历史数据。
- 新增
- 编辑弹窗
src/views/shop/shopExpressTemplateDetail/components/shopExpressTemplateDetailEdit.vue- 移除
RegionsSelect type="provinceCity",改用RegionMultiSelect。 - 打开时从
record.cityIds(兼容旧cityId)回填;保存时数组→逗号串提交cityIds/provinceIds。
- 移除
- 列表页
src/views/shop/shopExpressTemplateDetail/index.vue- "配送地区"列 customRender 改为遍历
cityIds多值反查 regionMap;超22字截断+tooltip;旧单值数据兼容。 - 新增
h函数 import。
- "配送地区"列 customRender 改为遍历
待办 / 风险
- 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-Plus,add/update 用
save()/updateById()自动生成 SQL,接收的是实体类本身(非 Param);新增字段只需在实体加属性 + 表加列,无需改 Mapper XML。
修复(对接后端已有的 regionMode/regionIds,无需后端改动)
- model:
provinceIds/cityIds→ 改为regionMode?、regionIds?。 RegionMultiSelect组件:去掉update:provinceIdsemit,只 emitupdate:value(城市编码数组)。- 编辑弹窗:城市选择变化 →
form.regionIds = 逗号串、form.regionMode = 1(有选)或0(全国);回显从form.regionIds拆出。 - 列表页:customRender 读
record.regionIds反查城市名。
- 结论:本需求纯前端即可闭环,后端无需改。城市编码(
regions-data.json的 city value,6位)与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 忽略 → 保存后丢失。 - 改动(三端同步):
- guilixu-java
entity/ShopGoodsSku.java:加private BigDecimal buyingPrice; - mp-java
entity/ShopGoodsSku.java:同上(shop 模块两端同步) - 前端 TS 接口
api/shop/shopGoodsSku/model/index.ts:加buyingPrice?: string;
- guilixu-java
- 需手动执行 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,生效后端):
entity/ShopGoods.java:加 3 个非持久化字段priceMax / salePriceMax / dealerPriceMax(BigDecimal,@TableField(exist = false))。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):
api/shop/shopGoods/model/index.ts:ShopGoods 加priceMax? / salePriceMax? / dealerPriceMax?。views/shop/shopGoods/index.vue:priceGroup 列渲染改为「区间不相等时 ¥min ~ ¥max,否则单值」;导出 Excel 新增fmtPrice()同步处理。
- 验证:
guilixu-javamvn -o compile通过(本机无全局 mvn,临时用 /tmp/apache-maven-3.9.6 离线编译);guilixu-adminvite build通过。 - 待确认:mp-java 是否需要同步——本部署 admin 商城走 guilixu-java,mp-java 未参与;若 mp-java 侧也有管理端/列表调用 ShopGoods.listRel,则需同步
fillSkuPriceRange+ 实体字段。
修复:商品「是否可以快递配送 / 运费模板」保存不成功(同根因)
- 根因:
ShopGoods实体(guilixu-java)与shop_goods表都没有can_express/express_template_id字段。前端提交这两个值被 MyBatis-Plus 静默丢弃(与 8-11 多规格 buyingPrice 同一类问题)。 - 改动:
- 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。 - 迁移 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; - 前端
shopGoodsEdit.vue:把"运费模板"表单项从「基本信息」页签移到「营销设置」页签内、"是否可以快递配送"开关正下方(用户要求)。
- guilixu-java
- ⚠️ 上线前置动作(否则不生效):必须在 db_guilixu 执行该 SQL 加列,且 guilixu-java 需重新编译部署(实体加了字段)。
- 此修复同时解决"运费模板"也存不进库的问题(同一根因,用户只报了 canExpress)。
- 前端 运费模板 必填校验(save 内
canExpress===1 && !expressTemplateId)与动态 label 已早先存在,逻辑无需改。
商品管理编辑弹窗:Markdown 编辑器换成 WangEditor 富文本
需求
用户反馈商品「编辑弹窗」的 Markdown 编辑器不好用,要求换成 website-admin 的 app/pages/articles.vue 那套编辑器。
方案(经评估后采用)
- articles.vue 用的是
app/components/RichTextEditor.vue(WangEditor@wangeditor/editor-for-vue,输出 HTML),并非 Markdown。 - 采用「编辑时按需转换」策略:老商品 content 是 Markdown,编辑回显时用 markdown-it 转成 HTML 再进富文本编辑器;已是 HTML 则原样返回。零 DB 风险,存储格式新旧混存。
改动清单
- 新增依赖(guilixu-admin,pnpm):
@wangeditor/editor@^5、@wangeditor/editor-for-vue@^5、markdown-it@^14、@types/markdown-it@^14(已写 package.json + node_modules)。 - 新建组件
src/components/RichTextEditor.vue:从 website-admin 的同名组件复制,删除 Nuxt 专属<ClientOnly>包装(guilixu-admin 是 Vite SPA 无此组件);图片上传用@/api/system/file的uploadFile(返回res.url || res.path,admin 已有)。 - 改造
src/views/shop/shopGoods/components/shopGoodsEdit.vue:- 移除
MdEditor(md-editor-v3)+ 相关 import /markdownToolbars/onMarkdownUploadImg/editorVisiblewatch /disabledref /editorRef/ 文件库SelectData弹窗(无打开入口,死代码)/:deep(.md-editor-*)样式。 - 商品详情 tab 内改用
<RichTextEditor v-model="content" :height="520" v-if="active==='content'"/>(仅在 tab 激活时挂载,规避隐藏容器初始化宽度 0 问题)。 - 新增
md = markdownit()与toEditorContent(raw),编辑回显content.value = toEditorContent(props.data.content)。 - 补回被误删的
FileRecordimport(chooseImage 等函数仍用)。
- 移除
- 保存逻辑不变:
save()已读content.value,直接提交 HTML。
验证
pnpm build(vite build)通过,exit 0,dist 正常产出。
风险 / 待确认
- 消费端(小程序
guilixu-taro/src/pages/shop/product-detail.tsx)已兼容两种格式:parseContent把转<img>,其余 HTML 直接RichText nodes渲染 → 新旧 content 都能展示,不崩。 - 潜在坑:若 HTML content 里含裸
.jpg链接(非<img>包裹,如<a href="https://x/x.jpg">),parseContent的兜底会把该 URL 误转成<img>破坏链接。WangEditor 产出的图片都是<img>,正常商品详情极少触发,低概率。 uploadFile返回的url是否为小程序域名白名单内完整 URL 需上线后在真机确认(影响商品详情页图片显示)。- 老商品仅在「重新编辑」时转 HTML;从未编辑过的老 Markdown 商品保持 Markdown,仍由小程序正确渲染。