摘要
很多 CRMEB Pro 二开需求看起来很小:给商品加一个业务字段,比如商品卖点、渠道标识、适用场景、内部备注、采购属性、展示标签。真正动手时才会发现,商品字段不是只改一张表那么简单。
商品保存会经过后台表单、Controller 参数收集、Services 数据整理、Dao/Model 入库、规格库存同步、列表搜索、移动端展示和缓存清理。字段如果只加在前端,保存时会丢;只加在后端,列表不显示;只改主表,不考虑 SKU 和缓存,移动端可能还是旧数据。
这篇结合 CRMEB Pro 当前商品模块实现,整理一套商品字段二开的检查链路。
一、先看商品保存的真实入口
后台商品模块的核心路由在 crmeb_pro/route/admin.php 的 product 分组中,和商品新增、编辑、列表、规格、库存有关的入口包括:
text
GET product/product 商品列表
POST product/product/:id 新建或修改商品
GET product/product/:id 商品编辑详情
GET product/product/type_header 商品列表头部数据
GET product/product/attrs/:id 获取商品规格
PUT product/product/saveStocks/:id 快速批量修改库存
POST generate_attr/:id/:type 生成商品规格列表
后台前端对应接口封装在 crmeb_pro_admin/src/api/product.js:
text
getGoods 商品列表
productInfoApi 商品编辑详情
productAddApi 商品提交
getGoodHeade 商品头部统计
productAttrsApi 商品规格
productSaveStocksApi 提交规格库存
所以商品字段二开不要只盯一个页面,要先确认这个字段属于:
text
商品主表字段
商品 SKU 字段
商品分类/品牌/标签关系
商品详情 description
列表筛选字段
移动端展示字段
字段归属不同,改动位置完全不同。
二、Controller 里要先接住字段
后台商品保存入口是 StoreProduct::save()。它并不直接读取所有请求参数,而是通过 getProductSaveFields() 定义允许接收的字段。
当前商品保存字段里已经包含很多业务项,例如:
text
product_type 商品类型
cate_id 商品分类
store_name 商品名称
store_info 商品简介
keyword 关键字
slider_image 商品轮播图
spec_type 单规格/多规格
items SKU 明细
attrs 多规格规则
label_id 用户标签
store_label_id 商品标签
brand_id 品牌
delivery_type 配送方式
temp_id 运费模板
is_vip_product 付费会员商品
product_clear 商品适用群体
custom_form 自定义表单
system_form_id 系统表单
如果新增字段没有放进这个参数白名单,前端传了也会被过滤,后续 Services 根本拿不到。
二开建议:
text
1. 新增商品主字段:先补 getProductSaveFields()
2. 新增列表筛选字段:再补 index() 和 type_header() 的 getMore()
3. 新增快速编辑字段:检查 set_product 或批量操作入口
4. 新增移动端字段:再检查 API 输出字段
Controller 只做参数接收和调用,不要在这里写查询或业务判断。
三、Services 才是商品字段落库前的核心整理层
商品保存真正的业务编排在 StoreProductServices::saveData(),它会先调用 prepareProductSaveData() 整理数据,然后进入事务保存。
这个链路里会做很多事情:
text
1. 校验商品基础数据
2. 处理运费和配送方式
3. 区分普通商品、供应商商品、门店商品
4. 整理单规格/多规格数据
5. 处理分类、标签、轮播图、主图
6. 保存商品详情 description
7. 校验并保存 SKU
8. 汇总商品库存、会员价、售罄状态
9. 触发商品创建事件和库存流水事件
10. 清理商品和规格缓存
这也是为什么"加一个字段"会牵动这么多地方。比如你加的是主表字段,至少要确认:
text
prepareProductSaveData() 中不会被 unset 掉
normalizeProductBaseData() 或相关整理方法是否需要处理格式
fillProductPriceData() 是否会影响价格字段
saveData() 事务里是否需要和 SKU 一起更新
event('product.create') 后续监听是否需要这个字段
如果字段和 SKU 没关系,不要放到 items 或 attrs 里;如果字段是 SKU 维度,比如规格独立编码、规格成本、规格外部库存,就应该放到 SKU 保存链路,而不是商品主表。
四、SKU 和库存不能顺手乱改
商品保存时,StoreProductServices::saveData() 会调用:
text
StoreProductAttrServices::validateProductAttr()
StoreProductAttrServices::saveProductAttr()
规格保存完成后,还会根据所有 SKU 的库存汇总更新商品主表:
text
商品 stock = 所有 SKU stock 求和
商品 is_sold = SKU 最小库存判断
商品 vip_price = SKU 会员价最小值
这意味着,如果你的字段和库存、价格、会员价、佣金、成本价有关,就不能只改商品主表。
例如新增"规格外部编码"时,应该考虑:
text
store_product_attr_value 是否新增字段
StoreProductAttrServices::validateProductAttr() 是否允许该字段
StoreProductAttrServices::saveProductAttr() 是否保存该字段
StoreProductAttrValueServices::updateAttrs() 是否快速编辑同步
前端规格表格是否显示和提交该字段
导入/导出是否需要支持该字段
否则后台新增商品时字段能保存,列表快速改库存时又可能把它漏掉。
五、列表展示和搜索也要一起补
后台商品列表在 StoreProduct::index() 接收筛选条件,再调用 StoreProductServices::getList(),最后走 StoreProductDao::getList() 和 Model 搜索器。
当前列表筛选已经支持很多条件:
text
商品名称/ID/关键字/条码
商品类型
分类、品牌、配送方式
单规格/多规格
商品标签
供应商
销量区间、价格区间、库存区间
创建时间区间
付费会员价
等级会员价格
是否参与返佣
活动类型
适用群体
如果新增字段需要搜索,不要在 Services 拼 SQL。更稳的方式是:
text
Controller 增加筛选参数
Dao search() 传入 where
Model 增加搜索器
前端 tableForm/artFrom 增加字段
列表导出字段同步更新
例如适用群体 product_clear 最终对应 Model 搜索器,会转换成 is_general_product、is_channel_product、is_vip_product 等字段判断。类似字段最好也按搜索器封装,避免列表页和导出页各写一份查询逻辑。
六、缓存和草稿也容易被忽略
商品新增编辑页有临时保存能力:
text
GET product/cache 获取退出未保存的数据
POST product/cache 保存还未提交数据
DELETE product/cache 删除退出未保存数据
Controller 中 saveCacheData() 会把商品编辑中的大量字段写到缓存里。如果新增字段只加正式保存,不加草稿保存,运营编辑一半离开页面后再回来,这个字段可能丢失。
商品保存成功后,系统会清理:
text
商品缓存 tag
规格缓存 tag
涉及移动端商品详情、商品列表、SKU 展示的字段,发布后还要确认缓存是否清掉,否则会出现后台已保存、前端仍展示旧值。
七、前端需要同步三类位置
后台前端至少要看这些位置:
text
crmeb_pro_admin/src/api/product.js
crmeb_pro_admin/src/pages/product/productAdd/index.vue
crmeb_pro_admin/src/pages/product/productAdd/components/productBaseSet.vue
crmeb_pro_admin/src/pages/product/productAdd/components/vipPriceSet.vue
crmeb_pro_admin/src/pages/product/productList/index.vue
一般字段改造分三类:
text
编辑页字段:表单展示、校验、提交
列表字段:表格展示、搜索、导出
SKU 字段:规格表格列、单规格默认值、多规格批量填充
移动端如果要展示,还要看:
text
crmeb_pro_uniapp/api/store.js
product/detail/:id
products
product/hot
v2/get_attr/:id/:type
不要只改后台保存,忘了移动端读取。
八、推荐二开步骤
text
1. 先判断字段归属:主表、SKU、详情、关系表还是筛选字段
2. 涉及数据库时,同步维护完整安装 SQL 和升级 SQL
3. Controller 参数白名单接住字段
4. Services 中整理格式、校验边界、避免被 unset
5. Dao/Model 搜索器补查询,不在业务层写 SQL
6. 前端编辑页、列表页、导出和移动端展示一起同步
7. 保存后确认缓存 tag 清理
8. 测试新增、编辑、复制商品、批量操作、导入导出、移动端详情
如果字段影响价格、库存、佣金、会员价,一定要额外测试下单、退款、库存扣减和分销结算,不要只看商品页保存成功。
九、关键目录说明
text
crmeb_pro/route/admin.php
后台商品路由入口,能看到商品列表、保存、规格、库存等接口。
crmeb_pro/app/controller/admin/v1/product/StoreProduct.php
商品后台控制器,负责接收参数、调用 Services、返回统一响应。
crmeb_pro/app/services/product/product/StoreProductServices.php
商品主业务服务,负责保存编排、字段整理、库存汇总、缓存清理和事件触发。
crmeb_pro/app/services/product/sku/StoreProductAttrServices.php
商品规格服务,负责规格校验、SKU 生成和 SKU 保存。
crmeb_pro/app/services/product/sku/StoreProductAttrValueServices.php
规格值服务,负责 SKU 库存、价格、库存流水和快速库存修改。
crmeb_pro/app/dao/product/product/StoreProductDao.php
商品查询 Dao,列表、搜索、分页、统计都应优先从这里扩展。
crmeb_pro/app/model/product/product/StoreProduct.php
商品模型,包含关联和搜索器。
crmeb_pro_admin/src/api/product.js
后台前端商品接口封装。
crmeb_pro_admin/src/pages/product/productAdd
商品新增编辑页面和组件。
crmeb_pro_admin/src/pages/product/productList/index.vue
商品列表、筛选、批量操作和库存入口。
十、注意事项
- 不要在 Controller 或 Services 中直接写临时 SQL。
- 新增查询条件优先放到 Dao 和 Model 搜索器。
- 新增字段涉及数据库时,安装 SQL 和升级 SQL 都要维护。
- SKU 字段不要混到商品主表字段里。
- 价格、库存、会员价、佣金字段必须额外测试下单和退款链路。
- 商品编辑草稿、复制商品、导入商品、导出商品都要检查字段是否丢失。
- 发布后要确认商品缓存和规格缓存被清理。
十一、标签建议
text
CRMEB
CRMEB Pro
二次开发
商品模块
SKU
ThinkPHP
商城系统
源码解析