Files
guilixu-admin/.workbuddy/memory/2026-08-11.md
T
gxwebsoft 07add26fe6 refactor(cmsArticle): 统一文章编辑器为单一 WangEditor 富文本模式
- 删除原有 TinyMCE 富文本和 MdEditor Markdown 双模式切换及相关逻辑
- 替换文章编辑弹窗内容详情为 RichTextEditor 组件
- 新增图片库和视频库选择按钮,支持插入图片和视频 HTML 标签
- 实现一键排版和首行缩进按钮,直接操作内容 HTML 字符串
- 回显时 Markdown 内容转换为 HTML,保存时固定 editor 类型为 1
- 移除无用的配置和样式,简化代码逻辑,提升编辑体验
- 留下文章编辑器类型设置字段待后续清理,展示端保持一致 HTML 渲染方式
2026-08-11 21:58:09 +08:00

131 lines
13 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 已早先存在,逻辑无需改。
## 商品管理编辑弹窗: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 风险,存储格式新旧混存。
### 改动清单
1. **新增依赖**guilixu-adminpnpm):`@wangeditor/editor@^5``@wangeditor/editor-for-vue@^5``markdown-it@^14``@types/markdown-it@^14`(已写 package.json + node_modules)。
2. **新建组件** `src/components/RichTextEditor.vue`:从 website-admin 的同名组件复制,**删除 Nuxt 专属 `<ClientOnly>` 包装**guilixu-admin 是 Vite SPA 无此组件);图片上传用 `@/api/system/file``uploadFile`(返回 `res.url || res.path`admin 已有)。
3. **改造** `src/views/shop/shopGoods/components/shopGoodsEdit.vue`
- 移除 `MdEditor``md-editor-v3`+ 相关 import / `markdownToolbars` / `onMarkdownUploadImg` / `editorVisible` watch / `disabled` ref / `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)`
- 补回被误删的 `FileRecord` importchooseImage 等函数仍用)。
4. **保存逻辑不变**`save()` 已读 `content.value`,直接提交 HTML。
### 验证
- `pnpm build`vite build)通过,exit 0dist 正常产出。
### 风险 / 待确认
- **消费端(小程序 `guilixu-taro/src/pages/shop/product-detail.tsx`)已兼容两种格式**`parseContent``![alt](url)``<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,仍由小程序正确渲染。
## 文章管理编辑弹窗:统一换成 WangEditor 富文本(双模式→单模式)
- 用户要求文章模块编辑器"也换了",与商品模块一致。经确认采用「统一 WangEditor 单模式」,**删除原有的 TinyMCE 富文本 + MdEditor Markdown 双模式切换**。
- 改动文件:`src/views/cms/cmsArticle/components/articleEdit.vue`(复用已建的 `src/components/RichTextEditor.vue`)。
- 删除:双模式 `v-if="editor==1/2"` 渲染分支、`editor` ref、`onEditorTypeChange/performEditorSwitch/convertContentFormat/onEditor` 切换逻辑、`markdownToolbars``onMarkdownUploadImg``openMarkdownImageSelector/openMarkdownVideoSelector``insertMarkdownText`、TinyMCE `config`(含 custom_image_selector/quick_upload/auto_format/toggle_indent 等自定义按钮 setup)、`.mce-content-body` 样式、`uploadOss` import(仅在删块中使用)。
- 新增/改写:
- 内容详情改用 `<RichTextEditor ref="editorRef" v-model="content" :height="500">`
- 编辑器下方按钮区:`📷从图片库选择`/`🎬从视频库选择`(调 `editorRef.value.insertHtml` 插入 img/video,复用 SelectData 弹窗)、`🎨一键排版`/`📐首行缩进`(直接操作 `content.value` HTML 字符串,底层 smartFormatContent/addIndent/removeIndent 纯函数保留)。
- `md = markdownit()` + `toEditorContent(raw)`:回显时 Markdown→HTML,已是 HTML 原样返回。
- `save()` 固定 `editor: 1`(统一 HTML)。
- 验证:`pnpm build` 通过(exit 0)。
- 风险/待办:
- `src/views/cms/cmsWebsiteSettingEdit.vue` 仍有"编辑器类型 1 md-editor-v3, 2 tinymce-editor"设置字段,文章编辑已不再读取,**该设置现在对文章编辑无效**,建议后续清理(改动涉及系统设置暂未动)。
- 展示端 `guilixu-taro/src/pages/index/article-detail.tsx` 用 Taro `RichText` 渲染 HTML(目前是占位页),统一 HTML 安全。
- `uploadFile` 的 url 域名白名单同商品模块,需真机确认。