3、SPU管理
3.1 spu和sku的概述
SPU:电商术语,代表的是一个标准化产品单元
华为公司:品牌名称 华为->产品单元 (类)
SPU组成:
SPU:产品品牌名字
SPU:描述
SPU:公司旗下产品图片介绍
SPU:销售属性[整个项目销售属性一共三个:颜色、版本、尺码]
SKU:库存量最小单位 (实例)
点击“添加SPU” 弹窗如下:
点击列表右侧 “SPU 操作” 中的 加号按钮显示如下:
点击列表右侧 “SPU 操作” 中的 预览按钮显示如下:
3.2 spu 列表模块搭建和头部三级分类组件的引入
- 定义类型 project\src\api\product\spu\type.ts
//服务器全部接口返回的数据类型
export interface ResponseData {
code: number,
message: string,
ok: boolean
}
//SPU数据的ts类型:需要修改
export interface SpuData {
category3Id: string | number,
id?: number,
spuName: string,
tmId: number | string,
description: string,
spuImageList: null | SpuImg[]
spuSaleAttrList: null | SaleAttr[],
}
//数组:元素都是已有SPU数据类型
export type Records = SpuData[];
//定义获取已有的SPU接口返回的数据ts类型
export interface HasSpuResponseData extends ResponseData {
data: {
records: Records,
total: number,
size: number,
current: number,
searchCount: boolean,
pages: number
}
}
//品牌数据的TS类型
export interface Trademark {
id: number,
tmName: string,
logoUrl: string
}
//品牌接口返回的数据ts类型
export interface AllTradeMark extends ResponseData {
data: Trademark[]
}
//商品图片的ts类型
export interface SpuImg {
id?: number,
imgName?: string,
imgUrl?: string
createTime?: string,
updateTime?: string,
spuId?: number,
name?: string,
url?: string
}
//已有的SPU的照片墙数据的类型
export interface SpuHasImg extends ResponseData {
data: SpuImg[]
}
//已有的销售属性值对象ts类型
export interface SaleAttrValue {
"id"?: number,
"createTime"?: null,
"updateTime"?: null,
"spuId"?: number,
"baseSaleAttrId": number | string,
"saleAttrValueName": string,
"saleAttrName"?: string,
"isChecked"?: null
}
//存储已有的销售属性值数组类型
export type SpuSaleAttrValueList = SaleAttrValue[];
//销售属性对象ts类型
export interface SaleAttr {
"id"?: number,
"createTime"?: null,
"updateTime"?: null,
"spuId"?: number,
"baseSaleAttrId": number | string,
"saleAttrName": string,
"spuSaleAttrValueList": SpuSaleAttrValueList
flag?: boolean,
saleAttrValue?: string,
}
//SPU已有的销售属性接口返回数据ts类型
export interface SaleAttrResponseData extends ResponseData {
data: SaleAttr[]
}
//已有的全部SPU的返回数据ts类型
export interface HasSaleAttr {
id: number,
name: string
}
export interface HasSaleAttrResponseData extends ResponseData {
data: HasSaleAttr[]
}
export interface Attr {
"attrId": number | string,//平台属性的ID
"valueId": number | string,//属性值的ID
}
export interface saleArr {
"saleAttrId": number | string,//属性ID
"saleAttrValueId": number | string,//属性值的ID
}
export interface SkuData {
"category3Id": string | number,//三级分类的ID
"spuId": string | number,//已有的SPU的ID
"tmId": string | number,//SPU品牌的ID
"skuName": string,//sku名字
"price": string | number,//sku价格
"weight": string | number,//sku重量
"skuDesc": string,//sku的描述
"skuAttrValueList"?: Attr[],
"skuSaleAttrValueList"?: saleArr[]
"skuDefaultImg": string,//sku图片地址
}
//获取SKU数据接口的ts类型
export interface SkuInfoData extends ResponseData {
data: SkuData[]
}
- 编写接口 project\src\api\product\spu\index.ts
//SPU管理模块的接口
import request from "@/utils/request";
import type { SkuInfoData, SkuData, SpuData, HasSpuResponseData, AllTradeMark, SpuHasImg, SaleAttrResponseData, HasSaleAttrResponseData } from './type';
enum API {
//获取已有的SPU的数据
HASSPU_URL = '/admin/product/',
//获取全部品牌的数据
ALLTRADEMARK_URL = '/admin/product/baseTrademark/getTrademarkList',
//获取某个SPU下的全部的售卖商品的图片数据
IMAGE_URL = '/admin/product/spuImageList/',
//获取某一个SPU下全部的已有的销售属性接口地址
SPUHASSALEATTR_URL = '/admin/product/spuSaleAttrList/',
//获取整个项目全部的销售属性[颜色、版本、尺码]
ALLSALEATTR_URL = '/admin/product/baseSaleAttrList',
//追加一个新的SPU
ADDSPU_URL = '/admin/product/saveSpuInfo',
//更新已有的SPU
UPDATESPU_URL = '/admin/product/updateSpuInfo',
//追加一个新增的SKU地址
ADDSKU_URL = '/admin/product/saveSkuInfo',
//查看某一个已有的SPU下全部售卖的商品
SKUINFO_URL = '/admin/product/findBySpuId/',
//删除已有的SPU
REMOVESPU_URL = '/admin/product/deleteSpu/'
}
//获取某一个三级分类下已有的SPU数据
export const reqHasSpu = (page: number, limit: number, category3Id: string | number) => request.get<any, HasSpuResponseData>(API.HASSPU_URL + `${page}/${limit}?category3Id=${category3Id}`);
//获取全部的SPU的品牌的数据
export const reqAllTradeMark = () => request.get<any, AllTradeMark>(API.ALLTRADEMARK_URL);
//获取某一个已有的SPU下全部商品的图片地址
export const reqSpuImageList = (spuId: number) => request.get<any, SpuHasImg>(API.IMAGE_URL + spuId)
//获取某一个已有的SPU拥有多少个销售属性
export const reqSpuHasSaleAttr = (spuId: number) => request.get<any, SaleAttrResponseData>(API.SPUHASSALEATTR_URL + spuId)
//获取全部的销售属性
export const reqAllSaleAttr = () => request.get<any, HasSaleAttrResponseData>(API.ALLSALEATTR_URL);
//添加一个新的SPU的
//更新已有的SPU接口
//data:即为新增的SPU|或者已有的SPU对象
export const reqAddOrUpdateSpu = (data: SpuData) => {
//如果SPU对象拥有ID,更新已有的SPU
if (data.id) {
return request.post<any, any>(API.UPDATESPU_URL, data)
} else {
return request.post<any, any>(API.ADDSPU_URL, data);
}
}
//添加SKU的请求方法
export const reqAddSku = (data: SkuData) => request.post<any, any>(API.ADDSKU_URL, data);
//获取SKU数据
export const reqSkuList = (spuId: number | string) => request.get<any, SkuInfoData>(API.SKUINFO_URL + spuId);
//删除已有的SPU
export const reqRemoveSpu = (spuId: number | string) => request.delete<any, any>(API.REMOVESPU_URL + spuId);
-
在 project\src\views\product\spu\index.vue 页面实现
<template> <div> <!-- 三级分类 --> <Category :scene="scene"></Category> <el-card style="margin:10px 0px"> <!-- v-if|v-show:都可以实现显示与隐藏 --> <div v-show="scene == 0"> <el-button @click="addSpu" type="primary" size="default" icon="Plus" :disabled="categoryStore.c3Id ? false : true">添加SPU</el-button> <!-- 展示已有SPU数据 --> <el-table style="margin: 10px 0px;" border :data="records"> <el-table-column label="序号" type="index" align="center" width="80px"></el-table-column> <el-table-column label="SPU名称" prop="spuName"></el-table-column> <el-table-column label="SPU描述" prop="description" show-overflow-tooltip></el-table-column> <el-table-column label="SPU操作"> <!-- row:即为已有的SPU对象 --> <template #="{ row, $index }"> <el-button type="primary" size="small" icon="Plus" title="添加SKU" @click="addSku(row)"></el-button> <el-button type="primary" size="small" icon="Edit" title="修改SPU" @click="updateSpu(row)"></el-button> <el-button type="primary" size="small" icon="View" title="查看SKU列表" @click="findSku(row)"></el-button> <el-popconfirm :title="`你确定删除${row.spuName}?`" width="200px" @confirm="deleteSpu(row)"> <template #reference> <el-button type="primary" size="small" icon="Delete" title="删除SPU"></el-button> </template> </el-popconfirm> </template> </el-table-column> </el-table> <!-- 分页器 --> <el-pagination v-model:current-page="pageNo" v-model:page-size="pageSize" :page-sizes="[3, 5, 7, 9]" :background="true" layout="prev, pager, next, jumper,->,sizes,total" :total="total" @current-change="getHasSpu" @size-change="changeSize" /> </div> </el-card> </div> </template> <script setup lang="ts"> import type { HasSpuResponseData, Records, SkuInfoData, SkuData } from '@/api/product/spu/type' import { ref, watch, onBeforeUnmount } from 'vue'; import { reqHasSpu, reqSkuList, reqRemoveSpu } from '@/api/product/spu'; //引入分类的仓库 import useCategoryStore from '@/store/modules/category'; import type { SpuData } from '@/api/product/spu/type' import { ElMessage } from 'element-plus'; let categoryStore = useCategoryStore(); //场景的数据 let scene = ref<number>(0); //0:显示已有SPU 1:添加或者修改已有SPU 2:添加SKU的结构 //分页器默认页码 let pageNo = ref<number>(1); //每一页展示几条数据 let pageSize = ref<number>(3); //存储已有的SPU的数据 let records = ref<Records>([]); //存储已有SPU总个数 let total = ref<number>(0); //监听三级分类ID变化 watch(() => categoryStore.c3Id, () => { //当三级分类发生变化的时候清空对应的数据 records.value = []; //务必保证有三级分类ID if (!categoryStore.c3Id) return; getHasSpu(); }); //此方法执行:可以获取某一个三级分类下全部的已有的SPU const getHasSpu = async (pager = 1) => { //修改当前页码 pageNo.value = pager; let result: HasSpuResponseData = await reqHasSpu(pageNo.value, pageSize.value, categoryStore.c3Id); if (result.code == 200) { records.value = result.data.records; total.value = result.data.total; } } //分页器下拉菜单发生变化的时候触发 const changeSize = () => { getHasSpu(); } //添加新的SPU按钮的回调 const addSpu = () => { //切换为场景1:添加与修改已有SPU结构->SpuForm scene.value = 1; } //添加SKU按钮的回调 const addSku = (row: SpuData) => { //点击添加SKU按钮切换场景为2 scene.value = 2; } //修改已有的SPU的按钮的回调 const updateSpu = (row: SpuData) => { //切换为场景1:添加与修改已有SPU结构->SpuForm scene.value = 1; } //查看SKU列表的数据 const findSku = async (row: SpuData) => { let result: SkuInfoData = await reqSkuList((row.id as number)); if (result.code == 200) { skuArr.value = result.data; //对话框显示出来 show.value = true; } } //删除已有的SPU按钮的回调 const deleteSpu = async (row: SpuData) => { let result: any = await reqRemoveSpu((row.id as number)); if (result.code == 200) { ElMessage({ type: 'success', message: '删除成功' }); //获取剩余SPU数据 getHasSpu(records.value.length > 1 ? pageNo.value : pageNo.value - 1) } else { ElMessage({ type: 'error', message: '删除失败' }) } } </script> <style lang="scss" scoped> </style>
3.3 新增和修改spu,sku 组件的封装,并完成整个spu模块的搭建
-
project\src\views\product\spu\spuForm.vue
<template> <el-form label-width="100px"> <el-form-item label="SPU名称"> <el-input placeholder="请你输入SPU名称" v-model="SpuParams.spuName"></el-input> </el-form-item> <el-form-item label="SPU品牌"> <el-select v-model="SpuParams.tmId"> <el-option v-for="(item, index) in AllTradeMark" :key="item.id" :label="item.tmName" :value="item.id"></el-option> </el-select> </el-form-item> <el-form-item label="SPU描述"> <el-input type="textarea" placeholder="请你输入SPU描述" v-model="SpuParams.description"></el-input> </el-form-item> <el-form-item label="SPU图片"> <!-- v-model:fileList->展示默认图片 action:上传图片的接口地址 list-type:文件列表的类型 --> <el-upload v-model:file-list="imgList" action="/api/admin/product/fileUpload" list-type="picture-card" :on-preview="handlePictureCardPreview" :on-remove="handleRemove" :before-upload="handlerUpload"> <el-icon> <Plus /> </el-icon> </el-upload> <el-dialog v-model="dialogVisible"> <img w-full :src="dialogImageUrl" alt="Preview Image" style="width:100%;height: 100%;" /> </el-dialog> </el-form-item> <el-form-item label="SPU销售属性"> <!-- 展示销售属性的下拉菜单 --> <el-select v-model="saleAttrIdAndValueName" :placeholder="unSelectSaleAttr.length ? `还未选择${unSelectSaleAttr.length}个` : '无'"> <el-option :value="`${item.id}:${item.name}`" v-for="(item, index) in unSelectSaleAttr" :key="item.id" :label="item.name"></el-option> </el-select> <el-button @click="addSaleAttr" :disabled="saleAttrIdAndValueName ? false : true" style="margin-left:10px" type="primary" size="default" icon="Plus">添加属性</el-button> <!-- table展示销售属性与属性值的地方 --> <el-table border style="margin:10px 0px" :data="saleAttr"> <el-table-column label="序号" type="index" align="center" width="80px"></el-table-column> <el-table-column label="销售属性名字" width="120px" prop="saleAttrName"></el-table-column> <el-table-column label="销售属性值"> <!-- row:即为当前SPU已有的销售属性对象 --> <template #="{ row, $index }"> <el-tag style="margin:0px 5px" @close="row.spuSaleAttrValueList.splice(index, 1)" v-for="(item, index) in row.spuSaleAttrValueList" :key="row.id" class="mx-1" closable> {{ item.saleAttrValueName }} </el-tag> <el-input @blur="toLook(row)" v-model="row.saleAttrValue" v-if="row.flag == true" placeholder="请你输入属性值" size="small" style="width:100px"></el-input> <el-button @click="toEdit(row)" v-else type="primary" size="small" icon="Plus"></el-button> </template> </el-table-column> <el-table-column label="操作" width="120px"> <template #="{ row, $index }"> <el-button type="primary" size="small" icon="Delete" @click="saleAttr.splice($index, 1)"></el-button> </template> </el-table-column> </el-table> </el-form-item> <el-form-item> <el-button :disabled="saleAttr.length > 0 ? false : true" type="primary" size="default" @click="save">保存</el-button> <el-button type="primary" size="default" @click="cancel">取消</el-button> </el-form-item> </el-form> </template> <script setup lang="ts"> import type { SpuData } from '@/api/product/spu/type' import { ref, computed } from 'vue'; import { reqAllTradeMark, reqSpuImageList, reqSpuHasSaleAttr, reqAllSaleAttr, reqAddOrUpdateSpu } from '@/api/product/spu' import type { SaleAttrValue, HasSaleAttr, SaleAttr, SpuImg, Trademark, AllTradeMark, SpuHasImg, SaleAttrResponseData, HasSaleAttrResponseData } from '@/api/product/spu/type'; import { ElMessage } from 'element-plus'; let $emit = defineEmits(['changeScene']); //点击取消按钮:通知父组件切换场景为1,展示有的SPU的数据 const cancel = () => { $emit('changeScene',{flag:0,params:'update'}); } //存储已有的SPU这些数据 let AllTradeMark = ref<Trademark[]>([]); //商品图片 let imgList = ref<SpuImg[]>([]); //已有的SPU销售属性 let saleAttr = ref<SaleAttr[]>([]); //全部销售属性 let allSaleAttr = ref<HasSaleAttr[]>([]); //控制对话框的显示与隐藏 let dialogVisible = ref<boolean>(false); //存储预览图片地址 let dialogImageUrl = ref<string>('') //存储已有的SPU对象 let SpuParams = ref<SpuData>({ category3Id: "",//收集三级分类的ID spuName: "",//SPU的名字 description: "",//SPU的描述 tmId: '',//品牌的ID spuImageList: [], spuSaleAttrList: [], }); //将来收集还未选择的销售属性的ID与属性值的名字 let saleAttrIdAndValueName = ref<string>('') //子组件书写一个方法 const initHasSpuData = async (spu: SpuData) => { //存储已有的SPU对象,将来在模板中展示 SpuParams.value = spu; //spu:即为父组件传递过来的已有的SPU对象[不完整] //获取全部品牌的数据 let result: AllTradeMark = await reqAllTradeMark(); //获取某一个品牌旗下全部售卖商品的图片 let result1: SpuHasImg = await reqSpuImageList((spu.id as number)); //获取已有的SPU销售属性的数据 let result2: SaleAttrResponseData = await reqSpuHasSaleAttr((spu.id as number)); //获取整个项目全部SPU的销售属性 let result3: HasSaleAttrResponseData = await reqAllSaleAttr(); //存储全部品牌的数据 AllTradeMark.value = result.data; //SPU对应商品图片,将获取到的每一张图片属性进行映射成组件规定的,组件要求的图片的name就是name,我们的是 imageName imgList.value = result1.data.map(item => { return { name: item.imgName, url: item.imgUrl } }) //存储已有的SPU的销售属性 saleAttr.value = result2.data; //存储全部的销售属性 allSaleAttr.value = result3.data; } //照片墙点击预览按钮的时候触发的钩子 const handlePictureCardPreview = (file: any) => { dialogImageUrl.value = file.url; //对话框弹出来 dialogVisible.value = true; } //照片墙删除文件钩子 const handleRemove = () => { console.log(123); } //照片钱上传成功之前的钩子约束文件的大小与类型 const handlerUpload = (file: any) => { if (file.type == 'image/png' || file.type == 'image/jpeg' || file.type == 'image/gif') { if (file.size / 1024 / 1024 < 3) { return true; } else { ElMessage({ type: 'error', message: '上传文件务必小于3M' }) return false; } } else { ElMessage({ type: 'error', message: '上传文件务必PNG|JPG|GIF' }) return false; } } //计算出当前SPU还未拥有的销售属性 let unSelectSaleAttr = computed(() => { //全部销售属性:颜色、版本、尺码 //已有的销售属性:颜色、版本 let unSelectArr = allSaleAttr.value.filter(item => { return saleAttr.value.every(item1 => { return item.name != item1.saleAttrName; }); }) return unSelectArr; }) //添加销售属性的方法 const addSaleAttr = () => { /* "baseSaleAttrId": number, "saleAttrName": string, "spuSaleAttrValueList": SpuSaleAttrValueList */ const [baseSaleAttrId, saleAttrName] = saleAttrIdAndValueName.value.split(':'); //准备一个新的销售属性对象:将来带给服务器即可 let newSaleAttr: SaleAttr = { baseSaleAttrId, saleAttrName, spuSaleAttrValueList: [] } //追加到数组当中 saleAttr.value.push(newSaleAttr); //清空收集的数据 saleAttrIdAndValueName.value = ''; } //属性值按钮的点击事件 const toEdit = (row: SaleAttr) => { //点击按钮的时候,input组件不就不出来->编辑模式 row.flag = true; row.saleAttrValue = '' } //表单元素失却焦点的事件回调 const toLook = (row: SaleAttr) => { //整理收集的属性的ID与属性值的名字 const { baseSaleAttrId, saleAttrValue } = row; //整理成服务器需要的属性值形式 let newSaleAttrValue: SaleAttrValue = { baseSaleAttrId, saleAttrValueName: (saleAttrValue as string) } //非法情况判断 if ((saleAttrValue as string).trim() == '') { ElMessage({ type: 'error', message: '属性值不能为空的' }) return; } //判断属性值是否在数组当中存在 let repeat = row.spuSaleAttrValueList.find(item => { return item.saleAttrValueName == saleAttrValue; }) if (repeat) { ElMessage({ type: 'error', message: '属性值重复' }) return; } //追加新的属性值对象 row.spuSaleAttrValueList.push(newSaleAttrValue); //切换为查看模式 row.flag = false; } //保存按钮的回调 const save = async () => { //整理参数 //发请求:添加SPU|更新已有的SPU //成功 //失败 //1:照片墙的数据 SpuParams.value.spuImageList = imgList.value.map((item: any) => { return { imgName: item.name,//图片的名字 imgUrl: (item.response && item.response.data) || item.url } }); //2:整理销售属性的数据 SpuParams.value.spuSaleAttrList = saleAttr.value; let result = await reqAddOrUpdateSpu(SpuParams.value); if (result.code == 200) { ElMessage({ type: 'success', message: SpuParams.value.id ? '更新成功' : '添加成功' }) //通知父组件切换场景为0 $emit('changeScene',{flag:0,params:SpuParams.value.id?'update':'add'}); } else { ElMessage({ type: 'success', message: SpuParams.value.id ? '更新成功' : '添加成功' }) } } //添加一个新的SPU初始化请求方法 const initAddSpu = async (c3Id: number | string) => { //清空数据 Object.assign(SpuParams.value, { category3Id: "",//收集三级分类的ID spuName: "",//SPU的名字 description: "",//SPU的描述 tmId: '',//品牌的ID spuImageList: [], spuSaleAttrList: [], }); //清空照片 imgList.value = []; //清空销售属性 saleAttr.value = []; saleAttrIdAndValueName.value = ''; //存储三级分类的ID SpuParams.value.category3Id = c3Id; //获取全部品牌的数据 let result: AllTradeMark = await reqAllTradeMark(); let result1: HasSaleAttrResponseData = await reqAllSaleAttr(); //存储数据 AllTradeMark.value = result.data; allSaleAttr.value = result1.data; } //对外暴露 便于父组件调用进而初始化子组件需要的数据 defineExpose({ initHasSpuData, initAddSpu }) </script> <style scoped></style> -
project\src\views\product\spu\skuForm.vue
<template> <el-form label-width="100px"> <el-form-item label="SKU名称"> <el-input placeholder="SKU名称" v-model="skuParams.skuName"></el-input> </el-form-item> <el-form-item label="价格(元)"> <el-input placeholder="价格(元)" type="number" v-model="skuParams.price"></el-input> </el-form-item> <el-form-item label="重量(g)"> <el-input placeholder="重量(g)" type="number" v-model="skuParams.weight"></el-input> </el-form-item> <el-form-item label="SKU描述"> <el-input placeholder="SKU描述" type="textarea" v-model="skuParams.skuDesc"></el-input> </el-form-item> <el-form-item label="平台属性"> <el-form :inline="true"> <el-form-item v-for="(item, index) in attrArr" :key="item.id" :label="item.attrName"> <el-select v-model="item.attrIdAndValueId"> <el-option :value="`${item.id}:${attrValue.id}`" v-for="(attrValue, index) in item.attrValueList" :key="attrValue.id" :label="attrValue.valueName"></el-option> </el-select> </el-form-item> </el-form> </el-form-item> <el-form-item label="销售属性"> <el-form :inline="true"> <el-form-item :label="item.saleAttrName" v-for="(item, index) in saleArr" :key="item.id"> <el-select v-model="item.saleIdAndValueId"> <el-option :value="`${item.id}:${saleAttrValue.id}`" v-for="(saleAttrValue, index) in item.spuSaleAttrValueList" :key="saleAttrValue.id" :label="saleAttrValue.saleAttrValueName"></el-option> </el-select> </el-form-item> </el-form> </el-form-item> <el-form-item label="图片名称"> <el-table border :data="imgArr" ref="table"> <el-table-column type="selection" width="80px" align="center"></el-table-column> <el-table-column label="图片"> <template #="{ row, $index }"> <img :src="row.imgUrl" alt="" style="width:100px;height: 100px;"> </template> </el-table-column> <el-table-column label="名称" prop="imgName"></el-table-column> <el-table-column label="操作"> <template #="{ row, $index }"> <el-button type="primary" size="small" @click="handler(row)">设置默认</el-button> </template> </el-table-column> </el-table> </el-form-item> <el-form-item> <el-button type="primary" size="default" @click="save">保存</el-button> <el-button type="primary" size="default" @click="cancel">取消</el-button> </el-form-item> </el-form> </template> <script setup lang="ts"> //引入请求API import { reqAttr } from '@/api/product/attr'; import { reqSpuImageList, reqSpuHasSaleAttr, reqAddSku } from '@/api/product/spu'; import type { SkuData } from '@/api/product/spu/type' import { ElMessage } from 'element-plus'; import { ref, reactive } from 'vue'; //平台属性 let attrArr = ref<any>([]); //销售属性 let saleArr = ref<any>([]); //照片的数据 let imgArr = ref<any>([]); //获取table组件实例 let table = ref<any>(); //收集SKU的参数 let skuParams = reactive<SkuData>({ //父组件传递过来的数据 "category3Id": "",//三级分类的ID "spuId": "",//已有的SPU的ID "tmId": "",//SPU品牌的ID //v-model收集 "skuName": "",//sku名字 "price": "",//sku价格 "weight": "",//sku重量 "skuDesc": "",//sku的描述 "skuAttrValueList": [//平台属性的收集 ], "skuSaleAttrValueList": [//销售属性 ], "skuDefaultImg": "",//sku图片地址 }) //当前子组件的方法对外暴露 const initSkuData = async (c1Id: number | string, c2Id: number | string, spu: any) => { //收集数据 skuParams.category3Id = spu.category3Id; skuParams.spuId = spu.id; skuParams.tmId = spu.tmId; //获取平台属性 let result: any = await reqAttr(c1Id, c2Id, spu.category3Id); //获取对应的销售属性 let result1: any = await reqSpuHasSaleAttr(spu.id); //获取照片墙的数据 let result2: any = await reqSpuImageList(spu.id); //平台属性 attrArr.value = result.data; //销售属性 saleArr.value = result1.data; //图片 imgArr.value = result2.data; } //取消按钮的回调 const cancel = () => { $emit('changeScene', { flag: 0, params: '' }); } //设置默认图片的方法回调 const handler = (row: any) => { //点击的时候,全部图片的的复选框不勾选 imgArr.value.forEach((item: any) => { table.value.toggleRowSelection(item, false); }); //选中的图片才勾选 table.value.toggleRowSelection(row, true); //收集图片地址 skuParams.skuDefaultImg = row.imgUrl; } //对外暴露方法 defineExpose({ initSkuData }); //保存按钮的方法 const save = async () => { //整理参数 //平台属性 skuParams.skuAttrValueList = attrArr.value.reduce((prev: any, next: any) => { if (next.attrIdAndValueId) { let [attrId, valueId] = next.attrIdAndValueId.split(':'); prev.push({ attrId, valueId }) } return prev; }, []); //销售属性 skuParams.skuSaleAttrValueList = saleArr.value.reduce((prev: any, next: any) => { if (next.saleIdAndValueId) { let [saleAttrId, saleAttrValueId] = next.saleIdAndValueId.split(':'); prev.push({ saleAttrId, saleAttrValueId }) } return prev; }, []); //添加SKU的请求 let result: any = await reqAddSku(skuParams); if (result.code == 200) { ElMessage({ type: 'success', message: '添加SKU成功' }); //通知父组件切换场景为零 $emit('changeScene',{flag:0,params:''}) } else { ElMessage({ type: 'error', message: '添加SKU失败' }) } } //自定义事件的方法 let $emit = defineEmits(['changeScene']); </script> <style scoped></style> -
project\src\views\product\spu\index.vue
<template>
<div>
<!-- 三级分类 -->
<Category :scene="scene"></Category>
<el-card style="margin:10px 0px">
<!-- v-if|v-show:都可以实现显示与隐藏 -->
<div v-show="scene == 0">
<el-button @click="addSpu" type="primary" size="default" icon="Plus"
:disabled="categoryStore.c3Id ? false : true">添加SPU</el-button>
<!-- 展示已有SPU数据 -->
<el-table style="margin: 10px 0px;" border :data="records">
<el-table-column label="序号" type="index" align="center" width="80px"></el-table-column>
<el-table-column label="SPU名称" prop="spuName"></el-table-column>
<el-table-column label="SPU描述" prop="description" show-overflow-tooltip></el-table-column>
<el-table-column label="SPU操作">
<!-- row:即为已有的SPU对象 -->
<template #="{ row, $index }">
<el-button type="primary" size="small" icon="Plus" title="添加SKU"
@click="addSku(row)"></el-button>
<el-button type="primary" size="small" icon="Edit" title="修改SPU"
@click="updateSpu(row)"></el-button>
<el-button type="primary" size="small" icon="View" title="查看SKU列表"
@click="findSku(row)"></el-button>
<el-popconfirm :title="`你确定删除${row.spuName}?`" width="200px" @confirm="deleteSpu(row)">
<template #reference>
<el-button type="primary" size="small" icon="Delete" title="删除SPU"></el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<!-- 分页器 -->
<el-pagination v-model:current-page="pageNo" v-model:page-size="pageSize" :page-sizes="[3, 5, 7, 9]"
:background="true" layout="prev, pager, next, jumper,->,sizes,total" :total="total"
@current-change="getHasSpu" @size-change="changeSize" />
</div>
<!-- 添加SPU|修改SPU子组件 -->
<SpuForm ref="spu" v-show="scene == 1" @changeScene="changeScene"></SpuForm>
<!-- 添加SKU的子组件 -->
<SkuForm ref="sku" v-show="scene == 2" @changeScene="changeScene"></SkuForm>
<!-- dialog对话框:展示已有的SKU数据 -->
<el-dialog v-model="show" title="SKU列表">
<el-table border :data="skuArr">
<el-table-column label="SKU名字" prop="skuName"></el-table-column>
<el-table-column label="SKU价格" prop="price"></el-table-column>
<el-table-column label="SKU重量" prop="weight"></el-table-column>
<el-table-column label="SKU图片">
<template #="{ row, $index }">
<img :src="row.skuDefaultImg" style="width: 100px;height: 100px;">
</template>
</el-table-column>
</el-table>
</el-dialog>
</el-card>
</div>
</template>
<script setup lang="ts">
import type { HasSpuResponseData, Records, SkuInfoData, SkuData } from '@/api/product/spu/type'
import { ref, watch, onBeforeUnmount } from 'vue';
import { reqHasSpu, reqSkuList, reqRemoveSpu } from '@/api/product/spu';
//引入分类的仓库
import useCategoryStore from '@/store/modules/category';
import type { SpuData } from '@/api/product/spu/type'
import SpuForm from './spuForm.vue';
import SkuForm from './skuForm.vue';
import { ElMessage } from 'element-plus';
let categoryStore = useCategoryStore();
//场景的数据
let scene = ref<number>(0); //0:显示已有SPU 1:添加或者修改已有SPU 2:添加SKU的结构
//分页器默认页码
let pageNo = ref<number>(1);
//每一页展示几条数据
let pageSize = ref<number>(3);
//存储已有的SPU的数据
let records = ref<Records>([]);
//存储已有SPU总个数
let total = ref<number>(0);
//获取子组件实例SpuForm
let spu = ref<any>();
//获取子组件实例SkuForm
let sku = ref<any>();
//存储全部的SKU数据
let skuArr = ref<SkuData[]>([]);
let show = ref<boolean>(false);
//监听三级分类ID变化
watch(() => categoryStore.c3Id, () => {
//当三级分类发生变化的时候清空对应的数据
records.value = [];
//务必保证有三级分类ID
if (!categoryStore.c3Id) return;
getHasSpu();
});
//此方法执行:可以获取某一个三级分类下全部的已有的SPU
const getHasSpu = async (pager = 1) => {
//修改当前页码
pageNo.value = pager;
let result: HasSpuResponseData = await reqHasSpu(pageNo.value, pageSize.value, categoryStore.c3Id);
if (result.code == 200) {
records.value = result.data.records;
total.value = result.data.total;
}
}
//分页器下拉菜单发生变化的时候触发
const changeSize = () => {
getHasSpu();
}
//添加新的SPU按钮的回调
const addSpu = () => {
//切换为场景1:添加与修改已有SPU结构->SpuForm
scene.value = 1;
//点击添加SPU按钮,调用子组件的方法初始化数据
//spu 就是子组件SpuForm的实列,然后调用子组件的方法,注意子组件的显示与隐藏要用 v-show , 不能用v-if 否则获取不到子组件的实列
spu.value.initAddSpu(categoryStore.c3Id);
}
//修改已有的SPU的按钮的回调
const updateSpu = (row: SpuData) => {
//切换为场景1:添加与修改已有SPU结构->SpuForm
scene.value = 1;
//调用子组件实例方法获取完整已有的SPU的数据
//spu 就是子组件SpuForm的实列,然后调用子组件的方法,注意子组件的显示与隐藏要用 v-show , 不能用v-if 否则获取不到子组件的实列
spu.value.initHasSpuData(row);
}
//子组件SpuForm绑定自定义事件:目前是让子组件通知父组件切换场景为0
const changeScene = (obj: any) => {
//子组件Spuform点击取消变为场景0:展示已有的SPU
scene.value = obj.flag;
if (obj.params == 'update') {
//更新留在当前页
getHasSpu(pageNo.value);
} else {
//添加留在第一页
getHasSpu();
}
}
//添加SKU按钮的回调
const addSku = (row: SpuData) => {
//点击添加SKU按钮切换场景为2
scene.value = 2;
//调用子组件的方法初始化添加SKU的数据
//sku 就是子组件SkuForm的实列,然后调用子组件的方法,注意子组件的显示与隐藏要用 v-show , 不能用v-if 否则获取不到子组件的实列
sku.value.initSkuData(categoryStore.c1Id, categoryStore.c2Id, row);
}
//查看SKU列表的数据
const findSku = async (row: SpuData) => {
let result: SkuInfoData = await reqSkuList((row.id as number));
if (result.code == 200) {
skuArr.value = result.data;
//对话框显示出来
show.value = true;
}
}
//删除已有的SPU按钮的回调
const deleteSpu = async (row: SpuData) => {
let result: any = await reqRemoveSpu((row.id as number));
if (result.code == 200) {
ElMessage({
type: 'success',
message: '删除成功'
});
//获取剩余SPU数据
getHasSpu(records.value.length > 1 ? pageNo.value : pageNo.value - 1)
} else {
ElMessage({
type: 'error',
message: '删除失败'
})
}
}
//路由组件销毁前,情况仓库关于分类的数据
onBeforeUnmount(() => {
categoryStore.$reset();
})
</script>
<style scoped></style>
4、SKU管理
4.1 功能展示
4.2 sku 模块的搭建
-
新建sku所有模块所用到的数据类型 project\src\api\product\sku\type.ts
export interface ResponseData { code: number, message: string, ok: boolean } //定义SKU对象的ts类型 export interface Attr { id?:number "attrId": number | string,//平台属性的ID "valueId": number | string,//属性值的ID } export interface saleArr { id?:number, "saleAttrId": number | string,//属性ID "saleAttrValueId": number | string,//属性值的ID } export interface SkuData { "category3Id"?: string | number,//三级分类的ID "spuId"?: string | number,//已有的SPU的ID "tmId"?: string | number,//SPU品牌的ID "skuName"?: string,//sku名字 "price"?: string | number,//sku价格 "weight"?: string | number,//sku重量 "skuDesc"?: string,//sku的描述 "skuAttrValueList"?: Attr[], "skuSaleAttrValueList"?: saleArr[] "skuDefaultImg"?: string,//sku图片地址 isSale?: number,//控制商品的上架与下架 id?: number } //获取SKU接口返回的数据ts类型 export interface SkuResponseData extends ResponseData { data: { records: SkuData[], "total": number, "size": number, "current": number, "orders": [], "optimizeCountSql": boolean, "hitCount": boolean, "countId": null, "maxLimit": null, "searchCount": boolean, "pages": number } } //获取SKU商品详情接口的ts类型 export interface SkuInfoData extends ResponseData { data: SkuData }
-
新增sku模块所需要的接口 project\src\api\product\sku\index.ts
//SKU模块接口管理 import request from "@/utils/request"; import type { SkuResponseData, SkuInfoData } from './type' //枚举地址 enum API { //获取已有的商品的数据-SKU SKU_URL = '/admin/product/list/', //上架 SALE_URL = '/admin/product/onSale/', //下架的接口 CANCELSALE_URL = '/admin/product/cancelSale/', //获取商品详情的接口 SKUINFO_URL = '/admin/product/getSkuInfo/', //删除已有的商品 DELETESKU_URL = '/admin/product/deleteSku/' } //获取商品SKU的接口 export const reqSkuList = (page: number, limit: number) => request.get<any, SkuResponseData>(API.SKU_URL + `${page}/${limit}`) //已有商品上架的请求 export const reqSaleSku = (skuId: number) => request.get<any, any>(API.SALE_URL + skuId); //下架的请求 export const reqCancelSale = (skuId: number) => request.get<any, any>(API.CANCELSALE_URL + skuId); //获取商品详情的接口 export const reqSkuInfo = (skuId: number) => request.get<any, SkuInfoData>(API.SKUINFO_URL + skuId); //删除某一个已有的商品 export const reqRemoveSku = (skuId: number) => request.delete<any, any>(API.DELETESKU_URL + skuId)-
页面实现 project\src\views\product\sku\index.vue
<template> <el-card> <el-table border style="margin: 10px 0px;" :data="skuArr"> <el-table-column label="序号" type="index" align="center" width="80px"></el-table-column> <el-table-column label="名称" show-overflow-tooltip width="150px" prop="skuName"></el-table-column> <el-table-column label="描述" show-overflow-tooltip width="150px" prop="skuDesc"></el-table-column> <el-table-column label="图片" width="150px"> <template #="{ row, $index }"> <img :src="row.skuDefaultImg" alt="" style="width: 100px;height: 100px;"> </template> </el-table-column> <el-table-column label="重量" width="150px" prop="weight"></el-table-column> <el-table-column label="价格" width="150px" prop="price"></el-table-column> <el-table-column label="操作" width="250px" fixed="right"> <template #="{ row, $index }"> <el-button type="primary" size="small" :icon="row.isSale == 1 ? 'Bottom' : 'Top'" @click="updateSale(row)"></el-button> <el-button type="primary" size="small" icon="Edit" @click="updateSku"></el-button> <el-button type="primary" size="small" icon="InfoFilled" @click="findSku(row)"></el-button> <el-popconfirm :title="`你确定要删除${row.skuName}?`" width="200px" @confirm="removeSku(row.id)"> <template #reference> <el-button type="primary" size="small" icon="Delete"></el-button> </template> </el-popconfirm> </template> </el-table-column> </el-table> <el-pagination v-model:current-page="pageNo" v-model:page-size="pageSize" :page-sizes="[10, 20, 30, 40]" :background="true" layout="prev, pager, next, jumper,->,sizes,total" :total="total" @current-change="getHasSku" @size-change="handler" /> <!-- 抽屉组件:展示商品详情 --> <el-drawer v-model="drawer"> <!-- 标题部分 --> <template #header> <h4>查看商品的详情</h4> </template> <template #default> <el-row style="margin:10px 0px;"> <el-col :span="6">名称</el-col> <el-col :span="18">{{ skuInfo.skuName }}</el-col> </el-row> <el-row style="margin:10px 0px;"> <el-col :span="6">描述</el-col> <el-col :span="18">{{ skuInfo.skuDesc }}</el-col> </el-row> <el-row style="margin:10px 0px;"> <el-col :span="6">价格</el-col> <el-col :span="18">{{ skuInfo.price }}</el-col> </el-row> <el-row style="margin:10px 0px;"> <el-col :span="6">平台属性</el-col> <el-col :span="18"> <el-tag style="margin:5px;" v-for="item in skuInfo.skuAttrValueList" :key="item.id">{{ item.valueName }}</el-tag> </el-col> </el-row> <el-row style="margin:10px 0px;"> <el-col :span="6">销售属性</el-col> <el-col :span="18"> <el-tag style="margin:5px;" v-for="item in skuInfo.skuSaleAttrValueList" :key="item.id">{{ item.saleAttrValueName }}</el-tag> </el-col> </el-row> <el-row style="margin:10px 0px;"> <el-col :span="6">商品图片</el-col> <el-col :span="18"> <el-carousel :interval="4000" type="card" height="200px"> <el-carousel-item v-for="item in skuInfo.skuImageList" :key="item.id"> <img :src="item.imgUrl" alt="" style="width:100%;height: 100%;"> </el-carousel-item> </el-carousel> </el-col> </el-row> </template> </el-drawer> </el-card> </template> <script setup lang="ts"> import { ref, onMounted } from 'vue'; //引入请求 import { reqSkuList, reqSaleSku, reqCancelSale, reqSkuInfo, reqRemoveSku } from '@/api/product/sku' //引入ts类型 import type { SkuResponseData, SkuData, SkuInfoData } from '@/api/product/sku/type'; import { ElMessage } from 'element-plus'; //分页器当前页码 let pageNo = ref<number>(1); //每一页展示几条数据 let pageSize = ref<number>(10); let total = ref<number>(0); let skuArr = ref<SkuData[]>([]); //控制抽屉显示与隐藏的字段 let drawer = ref<boolean>(false); let skuInfo = ref<any>({}); //组件挂载完毕 onMounted(() => { getHasSku(); }); const getHasSku = async (pager = 1) => { //当前分页器的页码 pageNo.value = pager; let result: SkuResponseData = await reqSkuList(pageNo.value, pageSize.value); if (result.code == 200) { total.value = result.data.total; skuArr.value = result.data.records; } } //分页器下拉菜单发生变化触发 const handler = (pageSizes: number) => { getHasSku(); } //商品的上架与下架的操作 const updateSale = async (row: SkuData) => { //如果当前商品的isSale==1,说明当前商品是上架的额状态->更新为下架 //否则else情况与上面情况相反 if (row.isSale == 1) { //下架操作 await reqCancelSale((row.id as number)); //提示信息 ElMessage({ type: 'success', message: '下架成功' }); //发请求获取当前更新完毕的全部已有的SKU getHasSku(pageNo.value); } else { //下架操作 await reqSaleSku((row.id as number)); //提示信息 ElMessage({ type: 'success', message: '上架成功' }); //发请求获取当前更新完毕的全部已有的SKU getHasSku(pageNo.value); } } //更新已有的SKU const updateSku = () => { ElMessage({ type: 'success', message: '程序员在努力的更新中....' }) } //查看商品详情按钮的回调 const findSku = async (row: SkuData) => { //抽屉展示出来 drawer.value = true; //获取已有商品详情数据 let result: SkuInfoData = await reqSkuInfo((row.id as number)); //存储已有的SKU skuInfo.value = result.data; } //删除某一个已有的商品 const removeSku = async (id: number) => { //删除某一个已有商品的情况 let result: any = await reqRemoveSku(id); if (result.code == 200) { //提示信息 ElMessage({ type: 'success', message: '删除成功' }); //获取已有全部商品 getHasSku(skuArr.value.length > 1 ? pageNo.value : pageNo.value - 1); } else { //删除失败 ElMessage({ type: 'error', message: '系统数据不能删除' }); } } </script> <style scoped> .el-carousel__item h3 { color: #475669; opacity: 0.75; line-height: 200px; margin: 0; text-align: center; } .el-carousel__item:nth-child(2n) { background-color: #99a9bf; } .el-carousel__item:nth-child(2n + 1) { background-color: #d3dce6; } </style>用户管理模块
-