Skip to content

完成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 拿到子组件实例,再调用子组件暴露的方法。

课堂画面 ​

课堂画面 1

课堂画面 2

课堂画面 3

课堂画面 4

课堂画面 5

代码示例 ​

常见错误 ​

  1. 点"添加SPU"保存成功,但列表里没有新增的那条

    • 现象:提示添加成功,场景切回 0,列表数量不变。
    • 原因:SPUParams 里没带 category3Id,后端不知道往哪个三级分类下挂;父组件明明有这个值却没往下传。
    • 修法:点击"添加"时把 category3Id 通过 props 传给子组件,保存前 spuParams.category3Id = category3Id,再在 Network 里核对请求体第一个字段。
  2. 先点"更新"再点"添加",表单里残留上一条的数据/ID,提示变成"更新成功"

    • 现象:新增页里带着旧 SPU 的名称、描述、图片、销售属性;提交后走的是更新逻辑。
    • 原因:切换场景时只切了 scene,没有清空 spuParams、imageArr、spuSaleAttr,尤其 spuParams.id 没置空。
    • 修法:在子组件暴露的初始化方法开头统一清空:图片数组置 []、销售属性置 []、spuParams 重置为初始对象(含 id: ''),清空要在收集之前执行。
  3. 不管添加还是更新,保存后都跳回第一页

    • 现象:在第 3 页点更新保存,回来还在第 1 页。
    • 原因:子组件通知父组件时只传了场景值 val,父组件无法区分"添加"还是"更新"。
    • 修法:通知时多传一个标记(有 spuParams.id 就是 update,没有就是 add);父组件判断:add → 页码置 1 重新拉取,update → 保持当前页重新拉取。
  4. 父组件点"添加SKU"没反应,三个请求一个都没发

    • 现象:$refs 拿到的是 undefined,或调用方法报 "not a function"。
    • 原因:<script setup> 下子组件默认关闭对外暴露,方法没暴露;或 ref 变量名和模板上的 ref="" 不一致。
    • 修法:子组件 defineExpose({ initAddSku });父组件 const skuRef = ref(),模板 ref="skuRef",点击时 skuRef.value.initAddSku(...)。此处不需要 nextTick,因为数据是点击后才请求的。
  5. SKU 平台属性/销售属性提交后后端收不到或对不上

    • 现象:请求发出去了,但属性没保存。
    • 原因:把整个平台属性对象塞进数组,或只收集了 attrId 漏了 valueId。
    • 修法:按接口结构逐条收集 { attrId, valueId } 和 { saleAttrId, saleAttrValueId };提交前 console.log 打印数组长度,与页面勾选数量比对。
  6. 照片墙里点了"设置默认"但提交后没有默认图

    • 现象:skuDefaultImg 为空字符串。
    • 原因:只做了 UI 高亮,没有把选中图片的地址写回参数对象。
    • 修法:点"设置默认"时把该行的图片地址赋给 skuParams.skuDefaultImg,提交前确认该字段非空。

练习 ​

  1. 给"添加SPU"补上 category3Id,提交后在 Network 面板确认请求体包含该字段,并验证列表新增成功。
  2. 实现"添加回第一页、更新留在当前页":改造子组件向父组件通知的传参,父组件据此决定页码。
  3. 在打开"添加SPU"时清空表单:要求清空后 spuParams.id 为空、图片墙和销售属性数组为空,并验证先更新再添加不会串数据。
  4. 完成添加 SKU 的参数收集:把平台属性、销售属性、默认图片分别收集到 skuParams 对应字段,console.log 检查字段总数与勾选数量是否一致。
  5. 用 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

小结 ​

  1. 添加类业务的第一优先级不是写页面,而是先把请求参数对齐接口文档,参数缺一个字段,界面做得再对也白搭。
  2. 组件通信的两个方向要分清:父传子用 props(如 category3Id、已有 SPU 对象),子调父用事件(切场景、通知添加/更新)。
  3. 父组件要调子组件的方法,必须走 ref + defineExpose,<script setup> 不会自动对外暴露。
  4. 场景切换类弹窗/页面,打开时清空、关闭时可不清,清空要放在收集之前,否则等于白清。
  5. 更新和添加共用一套表单时,靠 id 是否存在来区分行为,这个标记要一路传到父组件用于分页决策。
  6. 已有 SPU 对象本身就是"参数包",里面同时含 category3Id 和 spuId,整对象传给子组件比拆开传更省事。
  7. 调试这类模块只有两招:看 Network 的请求参数、看控制台里收集的数据结构,其它都是猜。
  8. 数据量大的模块不要被字段数量吓住,套路和前面的小模块完全一致——静态 → 拉数据 → 渲染 → 收集 → 提交。