07add26fe6
- 删除原有 TinyMCE 富文本和 MdEditor Markdown 双模式切换及相关逻辑 - 替换文章编辑弹窗内容详情为 RichTextEditor 组件 - 新增图片库和视频库选择按钮,支持插入图片和视频 HTML 标签 - 实现一键排版和首行缩进按钮,直接操作内容 HTML 字符串 - 回显时 Markdown 内容转换为 HTML,保存时固定 editor 类型为 1 - 移除无用的配置和样式,简化代码逻辑,提升编辑体验 - 留下文章编辑器类型设置字段待后续清理,展示端保持一致 HTML 渲染方式
131 lines
13 KiB
Markdown
131 lines
13 KiB
Markdown
# 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-Plus,add/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 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 忽略 → 保存后丢失。
|
||
- **改动(三端同步)**:
|
||
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-java,mp-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-admin,pnpm):`@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` import(chooseImage 等函数仍用)。
|
||
4. **保存逻辑不变**:`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,仍由小程序正确渲染。
|
||
|
||
## 文章管理编辑弹窗:统一换成 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 域名白名单同商品模块,需真机确认。
|