目录
1-列表页面开发
1.1-列表页面需求原型分析
1.2-接口封装和数据类型定义
1.3-组件获取数据
1.4-页面组件动态渲染数据
2-添加修改SPU
2.1-原型需求分析
2.2-接口封装和数据类型定义
2.3-请求服务器获取数据
2.4-收集表单数据提交数据
3-添加SKU
3.1-原型及需求分析
3.2-请求接口和数据类型封装
3.3-组件页面渲染数据和收集数据
3.4-整理数据提交数据
4-查看sku列表
5-删除spu
6-组件销毁
列表页面结构和上一个章节 属性管理列表页面结构类似。这里稍微复杂点,操作那边多了两个功能-添加SKU和查看SKU列表;下部分的card结构,需要在添加SPU和添加SKU之间来回切换,所以需要一个变量场景值来控制显示和隐藏。我们可以将添加SPU和添加SKU封装为两个单独的组件。
我们根据接口文档,定义服务端接口封装和数据结构的定义。
文件:src\api\product\spu\index.ts中定义请求接口相关
import request from '@/utils/request'
import type {HasSpuResponseData} from './type'
enum API {
//获取已有的SPU的数据
HASSPU_URL = '/admin/product/',
}
//获取某一个三级分类下已有的SPU数据
export const reqHasSpu = (
page: number,
limit: number,
category3Id: string | number,
) =>
request.get(
API.HASSPU_URL + `${page}/${limit}?category3Id=${category3Id}`,
)
文件src\api\product\spu\type.ts定义请求响应数据相关
在组件中,我们需要向服务器发送请求获取相关数据,并且需要定义变量接受服务器返回的数据。我们知道,当我们选中了三级分类的时候,我们需求想服务器发送请求获取数据,此时我们需要监听三级分类的id,如果三级分类的id不为空,我们就发送请求获取列表数据。
文件:src\views\product\spu\index.vue中发送请求,获取数据。
当我们获取到服务器的数据之后,我们需要在页面动态渲染服务器返回的数据。
el-table-column中描述description使用了show-overflow-tooltip属性,当内容过长被隐藏时显示 tooltip。
分析一下页面结构,首先上面的三级分类,需要disabled;然后添加SPU组件整体是一个el-form表单组件。有输入框input,还有下拉的,上传图片的,里面还有一个el-table的table组件。我们需要想服务器调用四个接口:
1-获取SPU品牌列表数据用于下拉;
2-获取某一个已有的SPU下全部商品的图片地址(编辑的时候使用)
3-获取某一个已有的SPU拥有多少个销售属性(编辑的时候使用)
4-获取全部的销售属性
我们整理好新增或者编辑页面需要调用4个接口获取数据。
文件:src\api\product\spu\index.ts 定义请求的接口;
//SPU管理模块的接口
import request from '@/utils/request'
import type {
AllTradeMark,
SpuHasImg,
SaleAttrResponseData,
HasSaleAttrResponseData,
} from './type'
enum API {
//获取全部品牌的数据
ALLTRADEMARK_URL = '/admin/product/baseTrademark/getTrademarkList',
//获取某个SPU下的全部的售卖商品的图片数据
IMAGE_URL = '/admin/product/spuImageList/',
//获取某一个SPU下全部的已有的销售属性接口地址
SPUHASSALEATTR_URL = '/admin/product/spuSaleAttrList/',
//获取整个项目全部的销售属性[颜色、版本、尺码]
ALLSALEATTR_URL = '/admin/product/baseSaleAttrList',
}
//获取全部的SPU的品牌的数据
export const reqAllTradeMark = () =>
request.get(API.ALLTRADEMARK_URL)
//获取某一个已有的SPU下全部商品的图片地址
export const reqSpuImageList = (spuId: number) =>
request.get(API.IMAGE_URL + spuId)
//获取某一个已有的SPU拥有多少个销售属性
export const reqSpuHasSaleAttr = (spuId: number) =>
request.get(API.SPUHASSALEATTR_URL + spuId)
//获取全部的销售属性
export const reqAllSaleAttr = () =>
request.get(API.ALLSALEATTR_URL)
文件: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[]
}
当我们点击添加SPU或者编辑的时候,我们需要在组件加载的时候获取到服务器的数据。当我们获取到数据的时候,我们需要使用接受数据,存储数据。
spuForm组件对外暴露,否则父组件spu不能调用:
点击添加SPU按钮:
点击编辑按钮:
我们获取到相关的服务端数据,我们还需要收集用户输入其他的表单数据,进行数据整理,提交给服务器。
定义收集数据的对象:
//存储已有的SPU对象
let SpuParams = ref({
category3Id: "",//收集三级分类的ID
spuName: "",//SPU的名字
description: "",//SPU的描述
tmId: '',//品牌的ID
spuImageList: [],
spuSaleAttrList: [],
});
每个数据项收集分析:
1-spu名称:输入框,直接v-model收集数据就行;
2-spu品牌:下拉列表循环展示品牌列表信息,直接v-model收集数据品牌id,通过el-option的value属性就可收集;
3-SPU描述:输入框,直接v-model收集数据就行;
4-SPU图片:重难点-参考element-plus的照片墙demo代码(照片对象里面的key 必须叫name 和 url)
添加spu的时候:图片列表是为空的,当我们上传成功后,图片上传成功信息就收集在 属性 v-model:file-list="imgList" 里面的imgList里面;
在提交的时候,我们需要整理数据,循环遍历imgList,获取里面的元素,拼装接口需要的数据结构。
编辑SPU的时候:我们需要调用接口获取spu的图片信息,因为要展示图片,我们必须返回el-upload组件需要的数据key(name,url)
el-upload组件中还定义了on-preview 点击预览图片,on-remove 点击删除图片,before-upload 上传之前对图片做一些校验;
let dialogVisible = ref(false);//控制图片预览对话框的显示与隐藏
//照片墙点击预览按钮的时候触发的钩子
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;
}
}
5-SPU销售属性:重难点
这里先要计算当前spu没有选择的销售属性列表unSelectSaleAttr。对于添加SPU,没有选择的销售属性列表就是系统接口返回的全部数据;对于编辑来说,先需要调用获取当前spu已有的销售属性列表saleAttr,然后用全部的数据列表allSaleAttr排除掉之前已经选择平台销售属性。
//计算出当前SPU还未拥有的销售属性
let unSelectSaleAttr = computed(() => {
//全部销售属性:颜色、版本、尺码;现在已经有颜色和版本,返回结果是尺码
let unSelectArr = allSaleAttr.value.filter(item => {
return saleAttr.value.every(item1 => {
return item.name != item1.saleAttrName;
});
})
return unSelectArr;
})
这里收集的时候,将平台销售属性的id和属性值名称一起收集。
我们可以看到这个table里面销售属性值有tag,input和button(plus)三个组件,首先展示是tag和plus按钮,点击plus按钮,展示input输入框,输入属性值;当表单元素失去焦点的时候,变成tag。需要来回判断展示button还是input输入框,所以我们需要追加一个变量flag来标记。
//表单元素失却焦点的事件回调
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;//切换为查看模式
}
当我们点击添加销售属性的时候,我们需要向响应式数据saleAttr数组中push一个对象。
//添加销售属性的方法
const addSaleAttr = () => {
const [baseSaleAttrId, saleAttrName] = saleAttrIdAndValueName.value.split(':');
//准备一个新的销售属性对象:将来带给服务器即可
let newSaleAttr: SaleAttr = {
baseSaleAttrId,
saleAttrName,
spuSaleAttrValueList: []
}
//追加到数组当中
saleAttr.value.push(newSaleAttr);
//清空收集的数据
saleAttrIdAndValueName.value = '';
}
点击保存按钮,整理数据,调用服务器接口,提交给服务器。
我们看到如果没有选择销售属性,保存按钮是disabled的状态。
父组件文件src\views\product\spu\index.vue,绑定自定义事件。
点击SPU操作的添加sku按钮时,需要进行场景切换,切换到SKU组件。form表单组件,除了一些输入框之外,还需要向服务器 获取平台属性-spu的销售属性-当前spu的图片列表信息。图片的table组件里面还有一个设置默认按钮,将图片设置为当前sku的默认图片。
数据类型定义上一章节以及全部开发完成,此处就不再贴出来;
文件src\api\product\spu\index.ts定义添加sku接口,内容如下:
//SPU管理模块的接口
import request from '@/utils/request'
import type {
SkuData
} from './type'
enum API {
//追加一个新增的SKU地址
ADDSKU_URL = '/admin/product/saveSkuInfo',
}
export const reqAddSku = (data: SkuData) =>
request.post(API.ADDSKU_URL, data)
我们在SKU组件页面挂载完成后,需要向服务器 获取平台属性-spu的销售属性-当前spu的图片列表信息数据,动态渲染在组件页面上。此外我们还需要收集服务器需要的添加sku数据信息,调用添加sku接口,提交给服务器。
//获取子组件实例SkuForm
let sku = ref();
//添加SKU按钮的回调
const addSku = (row: SpuData) => {
//点击添加SKU按钮切换场景为2
scene.value = 2;
//调用子组件的方法初始化添加SKU的数据
sku.value.initSkuData(categoryStore.c1Id, categoryStore.c2Id, row);
}
组件文件:src\views\product\spu\skuForm.vue中定义接受数据和收集数据的响应式对象。
import { reqAttr } from '@/api/product/attr';//引入请求API
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([]);//平台属性
let saleArr = ref([]);//销售属性
let imgArr = ref([]);//照片的数据
let table = ref();//获取table组件实例
//收集SKU的参数
let skuParams = reactive({
//父组件传递过来的数据
category3Id: "",//三级分类的ID
spuId: "",//已有的SPU的ID
tmId: "",//SPU品牌的ID
//v-model收集
skuName: "",//sku名字
price: "",//sku价格
weight: "",//sku重量
skuDesc: "",//sku的描述
skuAttrValueList: [//平台属性的收集
],
skuSaleAttrValueList: [//销售属性
],
skuDefaultImg: "",//sku图片地址
})
定义父组件需要调用子组件sku的方法initSkuData,并且需要对外暴露,这样父组件就可以调用。
//对外暴露方法
defineExpose({
initSkuData
});
收集数据:
1-SKU名称,价格(元),重量(g),SKU描述都是input输入框类型,直接v-model收集数据;
2-平台属性:下拉框类型,在el-select标签中使用v-model收集的是el-option中的value属性的数据。
3-销售属性:同理销售销售的数据收集逻辑和平台属性类似。
4-图片:
收集到组件页面的数据后,我们需要对数据进行组装和提交接口需要的数据。首先,我们把设置为默认图片handler方法逻辑先处理一下。
点击保存按钮逻辑处理:
//保存按钮的方法
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']);
点击页面的查看sku列表,需要展示当前spu下面的所有sku类别信息。业务逻辑比较简单直接上代码。
//存储全部的SKU数据
let skuArr = ref([]);
let show = ref(false);
//查看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的按钮的时候,需要弹框提示用户,点击确定 调用后台接口删除spu信息。因为业务逻辑比较简单,直接上代码。
//删除已有的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: '删除失败'})
}
}
当组件路由销毁的时候,我们需要清除仓库三级分类相关的数据。文件:src\views\product\spu\index.vue中需要添加onBeforeUnmount方法的逻辑。
import { ref, watch, onBeforeUnmount } from 'vue';
//路由组件销毁前,情况仓库关于分类的数据
onBeforeUnmount(() => {
categoryStore.$reset();
})