vue3-实战-08-管理后台-SPU模块开发

目录

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-组件销毁


1-列表页面开发

1.1-列表页面需求原型分析

       列表页面结构和上一个章节 属性管理列表页面结构类似。这里稍微复杂点,操作那边多了两个功能-添加SKU和查看SKU列表;下部分的card结构,需要在添加SPU和添加SKU之间来回切换,所以需要一个变量场景值来控制显示和隐藏。我们可以将添加SPU和添加SKU封装为两个单独的组件。

1.2-接口封装和数据类型定义

我们根据接口文档,定义服务端接口封装和数据结构的定义。
文件: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<any, HasSpuResponseData>(
    API.HASSPU_URL + `${page}/${limit}?category3Id=${category3Id}`,
  )

文件src\api\product\spu\type.ts定义请求响应数据相关

1.3-组件获取数据

       在组件中,我们需要向服务器发送请求获取相关数据,并且需要定义变量接受服务器返回的数据。我们知道,当我们选中了三级分类的时候,我们需求想服务器发送请求获取数据,此时我们需要监听三级分类的id,如果三级分类的id不为空,我们就发送请求获取列表数据。

文件:src\views\product\spu\index.vue中发送请求,获取数据。

 

 

1.4-页面组件动态渲染数据

当我们获取到服务器的数据之后,我们需要在页面动态渲染服务器返回的数据。 

 el-table-column中描述description使用了show-overflow-tooltip属性,当内容过长被隐藏时显示 tooltip

2-添加修改SPU

2.1-原型需求分析

分析一下页面结构,首先上面的三级分类,需要disabled;然后添加SPU组件整体是一个el-form表单组件。有输入框input,还有下拉的,上传图片的,里面还有一个el-table的table组件。我们需要想服务器调用四个接口:
1-获取SPU品牌列表数据用于下拉;
2-获取某一个已有的SPU下全部商品的图片地址(编辑的时候使用)
3-获取某一个已有的SPU拥有多少个销售属性(编辑的时候使用)
4-获取全部的销售属性

2.2-接口封装和数据类型定义

我们整理好新增或者编辑页面需要调用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<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)

文件: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[]
}

2.3-请求服务器获取数据

       当我们点击添加SPU或者编辑的时候,我们需要在组件加载的时候获取到服务器的数据。当我们获取到数据的时候,我们需要使用接受数据,存储数据。

 

spuForm组件对外暴露,否则父组件spu不能调用

 

点击添加SPU按钮

 

 点击编辑按钮

 

 

2.4-收集表单数据提交数据

       我们获取到相关的服务端数据,我们还需要收集用户输入其他的表单数据,进行数据整理,提交给服务器。

定义收集数据的对象

//存储已有的SPU对象
let SpuParams = ref<SpuData>({
    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<boolean>(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,绑定自定义事件。

3-添加SKU

3.1-原型及需求分析

        点击SPU操作的添加sku按钮时,需要进行场景切换,切换到SKU组件。form表单组件,除了一些输入框之外,还需要向服务器 获取平台属性-spu的销售属性-当前spu的图片列表信息。图片的table组件里面还有一个设置默认按钮,将图片设置为当前sku的默认图片。

3.2-请求接口和数据类型封装

数据类型定义上一章节以及全部开发完成,此处就不再贴出来;
文件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<any, any>(API.ADDSKU_URL, data)

3.3-组件页面渲染数据和收集数据

       我们在SKU组件页面挂载完成后,需要向服务器 获取平台属性-spu的销售属性-当前spu的图片列表信息数据,动态渲染在组件页面上。此外我们还需要收集服务器需要的添加sku数据信息,调用添加sku接口,提交给服务器。

 

//获取子组件实例SkuForm
let sku = ref<any>();
//添加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<any>([]);//平台属性
let saleArr = ref<any>([]);//销售属性
let imgArr = ref<any>([]);//照片的数据
let table = ref<any>();//获取table组件实例
//收集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图片地址
})

定义父组件需要调用子组件sku的方法initSkuData,并且需要对外暴露,这样父组件就可以调用。

//对外暴露方法
defineExpose({
    initSkuData
});

收集数据:
1-SKU名称,价格(元),重量(g),SKU描述都是input输入框类型,直接v-model收集数据;
2-平台属性:下拉框类型,在el-select标签中使用v-model收集的是el-option中的value属性的数据。
3-销售属性:同理销售销售的数据收集逻辑和平台属性类似。
4-图片:


 

 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']);

4-查看sku列表

       点击页面的查看sku列表,需要展示当前spu下面的所有sku类别信息。业务逻辑比较简单直接上代码。

//存储全部的SKU数据
let skuArr = ref<SkuData[]>([]);
let show = ref<boolean>(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;
    }
}

5-删除spu

当点击删除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: '删除失败'})
    }
}

6-组件销毁

当组件路由销毁的时候,我们需要清除仓库三级分类相关的数据。文件:src\views\product\spu\index.vue中需要添加onBeforeUnmount方法的逻辑。

import { ref, watch, onBeforeUnmount } from 'vue';
//路由组件销毁前,情况仓库关于分类的数据
onBeforeUnmount(() => {
    categoryStore.$reset();
})

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值