主题
完成SPU管理模块与完成添加SKU的业务(Day7 1/2)
本页对应 day7,共 5 段视频,约 1 小时 41 分。
学习目标
- 能够独立完成添加 SPU 功能:点击添加后切换场景一,通过 ref 调用子组件暴露方法,发起获取全部品牌与全部销售属性的请求,并将返回数据用于页面渲染。
- 能够根据新增 SPU 接口要求补齐并收集参数,包括 SPU 名称、品牌 ID、描述、照片墙、销售属性及三级分类 ID,完成保存后验证列表更新,并实现表单清空、ID 清空等细节处理。
- 能够区分添加与更新后的分页行为:向父组件传递场景切换值和添加/更新标记,实现添加后回到第一页、更新后留在当前页。
- 能够完成场景二添加 SKU 的静态搭建,包括 SKU 名称、价格、重量、描述、平台属性、销售属性、图片表格及保存/取消按钮,并实现场景切换。
- 能够通过 ref 调用子组件方法,在添加 SKU 时获取并渲染平台属性、当前 SPU 已有销售属性与照片墙数据。
- 能够理解并收集新增 SKU 接口参数,包括 SPU ID、三级分类 ID、品牌 ID、SKU 名称、价格、重量、描述、平台属性 ID/值 ID、销售属性 ID/值 ID、默认图片地址,并调用 POST 接口完成新增 SKU 保存。
知识点
先明确“添加SPU”的入口与场景切换
为什么需要:添加SPU不是新开一个页面,而是在SPU管理里把场景从列表切到表单。入口不清楚,后面的请求、保存、返回都会乱。
怎么想:点击列表里的“添加SPU”,先切换到添加场景,再让表单子组件初始化。转写稿里反复说“点击添加SPU是不是切换为场景一”,说明场景值由父组件控制。
怎么做:父组件拿到子组件实例,切换场景后调用子组件暴露的初始化方法。
js
// 父组件:点击“添加SPU”
const addSpu = () => {
scene.value = 1
spuRef.value.initAddSpu()
}易错点:不要只在父组件切场景却不通知子组件;也不要子组件自己猜何时初始化。转写稿强调“点击这个按钮的时候,获取到子组件,让它暴露一个方法”。
进入添加页要发哪两个请求
为什么需要:添加SPU时用户要选品牌、选销售属性,所以页面必须拿到“全部品牌”和“全部销售属性”。已有SPU数据不需要重新获取。
怎么想:转写稿说得很直白:“添加的时候是不是要发俩?获取所有的品牌、获取所有的销售属性。”
怎么做:在子组件暴露的 initAddSpu 里发两个请求并存储。
js
const initAddSpu = async () => {
const result = await reqTradeMarkList() // 获取全部品牌
trademarkList.value = result
const result2 = await reqSaleAttrList() // 获取全部销售属性
saleAttrList.value = result2
}易错点:不要把“更新SPU”的四个请求照搬到添加;添加只需要品牌和销售属性这两类基础数据。
保存时为什么少了三级分类ID
为什么需要:新增SPU必须知道它挂在哪个三级分类下。转写稿调试时新增vivo2,提示成功但列表没有,打开Network看参数,发现少了三级分类ID。
怎么想:能点“添加SPU”时,父组件一定已有三级分类ID;父组件把它传给子组件,子组件保存时补进参数。
怎么做:父传 c3Id,保存前赋值。
js
// 子组件保存前
spuParams.category3Id = c3Id易错点:只收集名称、品牌、描述、照片墙、销售属性还不够;少了 category3Id 就会出现“保存成功但查不到”的假成功。
添加前为什么要清空表单
为什么需要:更新和添加共用同一个表单。更新后再点添加,如果ID、照片墙、销售属性没清,页面会残留旧数据,甚至把“添加”做成“更新”。
怎么想:转写稿说“点添加按钮,每次都会调用清空方法”,并且清空要在收集之前。
怎么做:清照片墙、销售属性、SPU参数对象和ID。
js
const clearData = () => {
spuParams.images = []
spuParams.spuSaleAttrList = []
spuParams.spuParams = {}
spuParams.id = undefined
}易错点:清空放在收集之后就没意义;ID不清,保存时会走更新逻辑。转写稿里“取消后再添加却显示更新”就是ID残留。
保存成功后如何决定回第一页还是留在当前页
为什么需要:添加成功应回第一页;更新成功应留在当前页。转写稿指出,原来保存后只通知父组件切回场景0,没有区分添加还是更新,所以两者都回第一页。
怎么想:保存时除了传场景值,再传一个标记:有ID是更新,没有ID是添加。
怎么做:
js
emit('changeScene', {
scene: 0,
flag: spuParams.id ? 'UPDATE' : 'ADD'
})父组件根据 flag 决定页码:ADD 回第一页,UPDATE 留当前页。
易错点:只传场景值不够;必须让父组件知道本次是添加还是更新。
调试方法:看网络请求和开发者工具
为什么需要:后台管理系统最常见的问题不是“不会写”,而是参数不对、格式不对。转写稿说写这类项目最重要两件事:看网络请求参数、看开发者工具收集的数据。
怎么想:先看Network里请求有没有发、参数全不全;再看表单收集对象格式是否和接口一致。
怎么做:新增失败时先查 category3Id,再查ID是否残留,最后查保存后页码逻辑。
易错点:不要只看“保存成功”提示;提示成功不等于列表正确,必须回到列表验证。
本节目标:把“添加SKU”串成完整业务
为什么需要:SPU 是“类”,SKU 是这个类下真正售卖的具体商品。给已有 SPU 追加 SKU,必须把用户填写的数据按接口格式提交,否则后台拿不到参数。
怎么想:先搭静态表单,再进入添加场景拉取可选数据,最后收集参数发请求。
怎么做:点击“添加SKU”切到场景 2,父组件通过 ref 调子组件方法,子组件发请求并渲染。
易错点:不要只把页面搭出来就提交;先确认接口要什么字段,再决定收集什么。
2.1 静态 + 数据的渲染:先搭表单,再填数据
为什么需要:平台属性、销售属性、照片墙都不是写死的,必须请求后渲染,用户才能选择。
怎么想:静态部分包括 SKU 名称、价格、重量、描述、平台属性、销售属性、图片表格、保存/取消。进入添加场景后,至少发三类请求:当前三级分类下的平台属性;当前 SPU 已有的销售属性与属性值;当前 SPU 的照片墙数据。
怎么做:父组件点击“添加SKU”时切场景为 2,并调用子组件暴露的方法;子组件用 ref 保存平台属性、销售属性、图片数组,再在模板中 v-for 渲染。平台属性下拉的每个选项要带属性 ID 和属性值 ID;销售属性同理;照片墙表格展示图片地址和名称。
易错点:平台属性请求通常要一、二、三级分类 ID;销售属性和照片墙请求要当前 spuId。不要把这些 ID 写死,应从当前 SPU 和分类上下文传入。
2.2 收集新增SKU的数据发请求:按接口格式收集
为什么需要:添加 SKU 的请求参数不是随便拼的,必须和接口一致。笔记给出的参数结构如下:
json
{
"category3Id":"",//三级分类的ID
"spuId":"",//SPU的ID
"tmId":"",//SPU归属于哪一个品牌
"skuName": "",//SKU名字
"price":"",//SKU价格
"weight": "",//SKU重量
"skuDesc": "",//SKU商品的描述
"skuDefaultImg": "", //默认图片的地址
"skuAttrValueList": [ //收集平台属性的数据
{
"attrId":"",//平台属性的ID
"valueId":"",//平台属性值的ID
}
],
"skuSaleAttrValueList": [//收集销售属性
{
"saleAttrId":"",//销售属性的ID
"saleAttrValueId":"",//属性值的ID
}
],
}怎么想:平台属性和销售属性都是“已有数据”,用户只是勾选,所以只需要收集“哪个属性 ID 对应哪个属性值 ID”。照片墙要收集用户选中的默认图片地址,填入 skuDefaultImg。
怎么做:在子组件中准备一个 skuParam 对象,保存时把表单字段、分类 ID、SPU ID、品牌 ID、平台属性数组、销售属性数组、默认图片地址组装进去,再调用添加 SKU 接口。接口一般是 POST 请求,返回值只表示成功或失败。
易错点:category3Id、spuId、tmId 最容易漏;平台属性数组不要收集整条属性对象,只留 attrId 和 valueId;销售属性只留 saleAttrId 和 saleAttrValueId;默认图片地址不要漏。
调试过程:看网络请求,也看开发者工具
为什么需要:添加后页面没变化,不一定是页面错,可能是参数错。
怎么想:先看请求有没有发、请求参数是否完整,再看收集对象格式是否正确。
怎么做:打开开发者工具,检查 skuParam 中分类 ID、SPU ID、品牌 ID、属性数组和图片地址;对照接口参数逐项核对。保存成功或取消后,让父组件切回场景 0 即可;追加的是当前 SPU 下的 SKU 实例,不必误以为会新增一个 SPU 类。
易错点:只切场景不刷新数据时要确认展示逻辑;表单初始化或再次进入时要复位旧数据,避免上次选择残留。组件通信要牢记:父组件通过 ref 拿到子组件实例,再调用子组件暴露的方法。
课堂画面





代码示例
常见错误
点"添加SPU"保存成功,但列表里没有新增的那条
- 现象:提示添加成功,场景切回 0,列表数量不变。
- 原因:
SPUParams里没带category3Id,后端不知道往哪个三级分类下挂;父组件明明有这个值却没往下传。 - 修法:点击"添加"时把
category3Id通过 props 传给子组件,保存前spuParams.category3Id = category3Id,再在 Network 里核对请求体第一个字段。
先点"更新"再点"添加",表单里残留上一条的数据/ID,提示变成"更新成功"
- 现象:新增页里带着旧 SPU 的名称、描述、图片、销售属性;提交后走的是更新逻辑。
- 原因:切换场景时只切了
scene,没有清空spuParams、imageArr、spuSaleAttr,尤其spuParams.id没置空。 - 修法:在子组件暴露的初始化方法开头统一清空:图片数组置
[]、销售属性置[]、spuParams重置为初始对象(含id: ''),清空要在收集之前执行。
不管添加还是更新,保存后都跳回第一页
- 现象:在第 3 页点更新保存,回来还在第 1 页。
- 原因:子组件通知父组件时只传了场景值
val,父组件无法区分"添加"还是"更新"。 - 修法:通知时多传一个标记(有
spuParams.id就是 update,没有就是 add);父组件判断:add→ 页码置 1 重新拉取,update→ 保持当前页重新拉取。
父组件点"添加SKU"没反应,三个请求一个都没发
- 现象:
$refs拿到的是undefined,或调用方法报 "not a function"。 - 原因:
<script setup>下子组件默认关闭对外暴露,方法没暴露;或 ref 变量名和模板上的ref=""不一致。 - 修法:子组件
defineExpose({ initAddSku });父组件const skuRef = ref(),模板ref="skuRef",点击时skuRef.value.initAddSku(...)。此处不需要nextTick,因为数据是点击后才请求的。
- 现象:
SKU 平台属性/销售属性提交后后端收不到或对不上
- 现象:请求发出去了,但属性没保存。
- 原因:把整个平台属性对象塞进数组,或只收集了
attrId漏了valueId。 - 修法:按接口结构逐条收集
{ attrId, valueId }和{ saleAttrId, saleAttrValueId };提交前console.log打印数组长度,与页面勾选数量比对。
照片墙里点了"设置默认"但提交后没有默认图
- 现象:
skuDefaultImg为空字符串。 - 原因:只做了 UI 高亮,没有把选中图片的地址写回参数对象。
- 修法:点"设置默认"时把该行的图片地址赋给
skuParams.skuDefaultImg,提交前确认该字段非空。
- 现象:
练习
- 给"添加SPU"补上
category3Id,提交后在 Network 面板确认请求体包含该字段,并验证列表新增成功。 - 实现"添加回第一页、更新留在当前页":改造子组件向父组件通知的传参,父组件据此决定页码。
- 在打开"添加SPU"时清空表单:要求清空后
spuParams.id为空、图片墙和销售属性数组为空,并验证先更新再添加不会串数据。 - 完成添加 SKU 的参数收集:把平台属性、销售属性、默认图片分别收集到
skuParams对应字段,console.log检查字段总数与勾选数量是否一致。 - 用
defineExpose+ref实现父组件调用子组件的initAddSku,验证点击"添加SKU"时平台属性、销售属性、照片墙三个请求同时发出且参数正确。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 完成添加SPU的业务 | 25:46 |
| 2 | 完成添加SPU的业务 | 08:53 |
| 3 | 完成场景2添加SKU的静态搭建 | 18:27 |
| 4 | 完成添加SKU数据的获取与渲染 | 32:28 |
| 5 | 添加SKU接口的与参数理解 | 15:35 |
小结
- 添加类业务的第一优先级不是写页面,而是先把请求参数对齐接口文档,参数缺一个字段,界面做得再对也白搭。
- 组件通信的两个方向要分清:父传子用 props(如
category3Id、已有 SPU 对象),子调父用事件(切场景、通知添加/更新)。 - 父组件要调子组件的方法,必须走
ref+defineExpose,<script setup>不会自动对外暴露。 - 场景切换类弹窗/页面,打开时清空、关闭时可不清,清空要放在收集之前,否则等于白清。
- 更新和添加共用一套表单时,靠
id是否存在来区分行为,这个标记要一路传到父组件用于分页决策。 - 已有 SPU 对象本身就是"参数包",里面同时含
category3Id和spuId,整对象传给子组件比拆开传更省事。 - 调试这类模块只有两招:看 Network 的请求参数、看控制台里收集的数据结构,其它都是猜。
- 数据量大的模块不要被字段数量吓住,套路和前面的小模块完全一致——静态 → 拉数据 → 渲染 → 收集 → 提交。