Vue3+TypeScript项目(SPU管理模块)

SPU(Standard Product Unit):标准化产品单元。是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。通俗点讲,属性值、特性相同的商品就可以称为一个SPU。

一、Spu模块的静态页面 

src\views\product\spu\index.vue

<template>
  <div>
    <!--三级分类卡片 -->
    <Category :scene="scene"></Category>
    <!-- 展示数据卡片 -->
    <el-card>
      <el-button type="primary" icon="Plus">添加SPU</el-button>
      <el-table border style="margin: 10px 0">
        <el-table-column type="index" align="center" label="序号" width="80px"></el-table-column>
        <el-table-column label="SPU名称" ></el-table-column>
        <el-table-column label="SPU描述"></el-table-column>
        <el-table-column label="操作"></el-table-column>
      </el-table>
      <!-- 分页器 -->
      <el-pagination
        @size-change="sizeChange"
        @current-change="getHasTrademark"
        v-model:current-page="pageNo"
        v-model:page-size="limit"
        :page-sizes="[3, 5, 7, 9]"
        :background="true"
        layout=" prev, pager, next, jumper,->,total, sizes,"
        :total="total"
      />
    </el-card>
  </div>
</template>

<script setup lang="ts">
import {ref} from "vue"
//
let scene = ref<number>(0);
//当前页码
let pageNo = ref<number>(1);
//每一页展示的数据
let limit = ref<number>(3);
//存储已有品牌数据总数
let total = ref<number>(0);

</script>

<style scoped lang="scss"></style>

 二、Spu模块展示已有数据

2.1 API

src\api\product\spu\index.ts

//SPU管理模块的接口
import request from '@/utils/request'
import type { HasSpuResponseData } from './type'
enum API {
  //获取已有的SPU的数据
  HASSPU_URL = 'http://114.115.179.162:8022/prod-api/admin/product/',
}


//获取某一个三级分类下已有的SPU数据
export const reqHasSpu = (
  page: number,
  limit: number,
  category3Id: string | number,
) => {
  return request.get<any, HasSpuResponseData>(
    API.HASSPU_URL + `${page}/${limit}?category3Id=${category3Id}`,
  )
}

2.2 type

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
    spuSaleAttrList: null
  }
  //数组:元素都是已有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
    }
  }

2.3 展示数据

src\views\product\spu\index.vue

<template>
  <div>
    <!--三级分类卡片 -->
    <Category :scene="scene"></Category>
    <!-- 展示数据卡片 -->
    <el-card>
      <el-button 
        type="primary" 
        icon="Plus"
        :disabled="categoryStore.c3Id ? false : true"   
      >添加SPU</el-button>
      <el-table border style="margin: 10px 10px" :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"
                ></el-button>
              <el-button
                type="primary"
                size="small"
                icon="Edit"
                title="修改SPU"
                ></el-button>
              <el-button
                type="primary"
                size="small"
                icon="View"
                title="查看SKU列表"
                ></el-button>
              <el-popconfirm :title="`你确定删除${row.spuName}?`" width="200px">
                <template #reference>
                  <el-button
                    type="danger"
                    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"
    />
    </el-card>
  </div>
</template>

<script setup lang="ts">
import {ref,watch,onBeforeUnmount } from "vue"
import {reqHasSpu} from '@/api/product/spu/index'
import type { HasSpuResponseData,records } from '@/api/product/spu/type'
//引入分类相关的仓库
import useCategoryStore from '@/store/modules/category'
let categoryStore = useCategoryStore()
//定义card组件内容切换变量
let scene = ref<number>(0);
//当前页码
let pageNo = ref<number>(1);
//每一页展示的数据
let pageSize = ref<number>(3);
//存储已有品牌数据总数
let total = ref<number>(0);
//存储已有的SPU数据
let records = ref<Records>([]);
//此方法执行:可以获取某一个三级分类下全部的已有的SPU
const getHasSpu = async (pager = 1) => {
  //修改当前页码
  pageNo.value = pager
  let result: HasSpuResponseData = await reqHasSpu(
    pageNo.value,
    pageSize.value,
    categoryStore.c3Id,
  )
  console.log(result)
  if (result.code == 200) {
    records.value = result.data.records
    total.value = result.data.total
  }
}
//分页器的 下拉菜单发生变化的时候触发
const changeSize = () => {
  getHasSpu()
}
//监听三级分类ID变化
watch(
  () => categoryStore.c3Id,
  () => {
    //当三级分类发生变化的时候清空对应的数据
    records.value = []
    //务必保证有三级分类ID
    if (!categoryStore.c3Id) return
    getHasSpu()
  },
)
</script>

<style scoped lang="scss"></style>

效果图:

三、SPU场景一的静态&&场景切换 

3.1 子组件搭建

由于SPU模块需要在三个场景进行切换,全都放在一个组件里面的话会显得很臃肿。因此我们将它放到三个组件当中。

使用v-show来展示页面:v-if是销毁组件,v-show是隐藏组件。在初加载的时候v-if比较快,但是在频繁切换的时候v-if任务重。

3.2 SPU场景一子组件静态 

 src\views\product\spu\spuFrom.vue

<template>
  <el-form label-width="auto">
    <el-form-item label="SPU名称">
      <el-input placeholder="请输入SPU名称"></el-input>
    </el-form-item>
    <el-form-item label="SPU品牌">
      <el-select placeholder="请选择">
        <el-option label="北京"></el-option>
        <el-option label="上海"></el-option>
        <el-option label="广州"></el-option>
        <el-option label="深圳"></el-option>
      </el-select>
    </el-form-item>
    <el-form-item label="SPU描述">
      <el-input type="textarea" placeholder="请输入SPU描述"></el-input>
    </el-form-item>
    <el-form-item label="SPU图标">
      <el-upload
        v-model:file-list="fileList"
        action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
        list-type="picture-card"
        :on-preview="handlePictureCardPreview"
        :on-remove="handleRemove"
      >
        <el-icon><Plus /></el-icon>
      </el-upload>

      <el-dialog v-model="dialogVisible">
        <img w-full :src="dialogImageUrl" alt="Preview Image" />
      </el-dialog>
    </el-form-item>
    <el-form-item label="SPU销售属性">
      <el-select style="width: 250px; margin-right: 10px" placeholder="请选择">
        <el-option label="北京"></el-option>
        <el-option label="上海"></el-option>
        <el-option label="广州"></el-option>
        <el-option label="深圳"></el-option>
      </el-select>
      <el-button type="primary" icon="Plus">添加属性</el-button>
      <el-table border style="margin: 10px 0">
        <el-table-column label="序号" width="80px" align="center"></el-table-column>
        <el-table-column label="销售属性名称" width="120px"></el-table-column>
        <el-table-column label="销售属性值"></el-table-column>
        <el-table-column label="操作" width="120px"></el-table-column>
      </el-table>
      <el-button type="primary">保存 </el-button>
      <el-button type="default" @click="cancel">取消</el-button>
    </el-form-item>
  </el-form>
</template>

<script setup lang="ts">
let $emit = defineEmits(['changeScene']);

const cancel= ()=>{
    $emit('changeScene',0);
}


</script>

<style scoped lang="scss"></style>

3.3 父组件中添加SPU按钮&&修改按钮 

这两个按钮都是跳转到场景一.下面是对应的回调

//添加新的SPU按钮的回调
const addSpu = () => {
  //切换为场景1:添加与修改已有SPU结构->SpuForm
  scene.value = 1
}

//修改已有的SPU的按钮的回调
const updateSpu = () => {
  //切换为场景1:添加与修改已有SPU结构->SpuForm
  scene.value = 1
}

 3.4 子组件中取消按钮的回调

需要改变的是父组件中的scene,因此涉及到父子组件通信。这里使用自定义事件。

 子组件

四、SPU模块API&&TS类型(修改&&添加)

修改和添加的页面是差不多的。页面1的四个地方都需要发请求拿数据,我们在这一部分分别编写4个部分的API以及ts类型

 

4.1SPU品牌

  1. API:src\api\product\spu\index.ts
    //获取全部品牌的数据
      ALLTRADEMARK_URL = '/admin/product/baseTrademark/getTrademarkList',
    //获取全部的SPU的品牌的数据
    export const reqAllTradeMark = () => {
      return request.get<any, AllTradeMark>(API.ALLTRADEMARK_URL)
    }

    ts类型: src\api\product\spu\type.ts

    //品牌数据的TS类型
    export interface Trademark {
      id: number
      tmName: string
      logoUrl: string
    }
    //品牌接口返回的数据ts类型
    export interface AllTradeMark extends ResponseData {
      data: Trademark[]
    }

    4.2 SPU图片

API 

//获取某个SPU下的全部的售卖商品的图片数据
  IMAGE_URL = '/admin/product/spuImageList/',
//获取某一个已有的SPU下全部商品的图片地址
export const reqSpuImageList = (spuId: number) => {
  return request.get<any, SpuHasImg>(API.IMAGE_URL + spuId)
}

ts类型

//商品图片的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[]
}

 4.3 全部销售属性

API

//获取整个项目全部的销售属性[颜色、版本、尺码]
  ALLSALEATTR_URL = '/admin/product/baseSaleAttrList',
//获取全部的销售属性
export const reqAllSaleAttr = () => {
  return request.get<any, HasSaleAttrResponseData>(API.ALLSALEATTR_URL)
}

ts类型

//已有的全部SPU的返回数据ts类型
export interface HasSaleAttr {
  id: number
  name: string
}
export interface HasSaleAttrResponseData extends ResponseData {
  data: HasSaleAttr[]
}

4.4 已有的销售属性

API

//获取某一个SPU下全部的已有的销售属性接口地址
  SPUHASSALEATTR_URL = '/admin/product/spuSaleAttrList/',
//获取某一个已有的SPU拥有多少个销售属性
export const reqSpuHasSaleAttr = (spuId: number) => {
  return request.get<any, SaleAttrResponseData>(API.SPUHASSALEATTR_URL + spuId)
}

ts类型

//销售属性对象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的数据

首先:SPU的数据应该分为5部分:第一部分:是父组件里的展示的数据,也是我们点击修改按钮时的那个数据。其余4个部分的数据需要我们发请求得到。

问题1:子组件需要用到父组件中的数据,应该怎么办?答:要传递的数据是指定的,也就是我们点击修改时的数据。通过ref的方式,拿到子组件时的实例,再调用子组件暴露的方法将数据做为参数传递过去。(有点类似于反向的自定义事件)

问题2:其余4个部分的数据什么时候获取。答:同样的在点击修改按钮时获取,问题一中通过调用子组件的函数传递数据,我们同时也在这个函数中发请求得到数据

第一部分数据的传递
1.父组件拿到子组件实例

 

2.子组件暴露对外函数

3.修改按钮点击函数中调用子组件函数,并传递第一部分数据

 

//修改已有的SPU的按钮的回调
const updateSpu = (row: SpuData) => {
  //切换为场景1:添加与修改已有SPU结构->SpuForm
  scene.value = 1
  //调用子组件实例方法获取完整已有的SPU的数据
  spu.value.initHasSpuData(row)
}
其余数据

子组件中直接发起请求,并且将服务器返回的四个数据存储,加上参数传递的第一部分数据,这样子组件拿到了全部的数据。

src\views\product\spu\spuFrom.vue

<script setup lang="ts">
import { ref } from "vue";
import {
  reqAllTradeMark,
  reqSpuImageList,
  reqSpuHasSaleAttr,
  reqAllSaleAttr,
} from "@/api/product/spu/index";
import type {
  AllTradeMark,
  SpuHasImg,
  SaleAttrResponseData,
  HasSaleAttrResponseData,
  SpuData,
  Trademark,
  SpuImg,
  SaleAttr,
  HasSaleAttr
} from "@/api/product/spu/type";
let $emit = defineEmits(["changeScene"]);

const cancel = () => {
  $emit("changeScene", 0);
};
//存储已有的spu品牌数据
let MyAllTradeMark = ref<Trademark[]>([]);
//存储商品图片
let imgList  =ref<SpuImg[]>([]);
//存储已有的销售属性
let saleAttr = ref<SaleAttr[]>([]);
//存储全部销售属性
let allSaleAttr = ref<HasSaleAttr[]>([]);
//子组件书写一个方法
const initHasSpuData = async (spu: SpuData) => {
  //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();
  //存储全部品牌的数据
  MyAllTradeMark.value = result.data;
  //SPU对应商品图片
  imgList.value = result1.data.map((item) => {
    return {
      name: item.imgName,
      url: item.imgUrl,
    };
  });
  //存储已有的SPU的销售属性
  saleAttr.value = result2.data;
  //存储全部的销售属性
  allSaleAttr.value = result3.data;
};

//对外暴露
defineExpose({ initHasSpuData });
</script>

五、修改与添加的接口&&TS 

5.1 API src\api\product\spu\index.ts

//追加一个新的SPU
  ADDSPU_URL = '/admin/product/saveSpuInfo',
  //更新已有的SPU
  UPDATESPU_URL = '/admin/product/updateSpuInfo',
//添加一个新的SPU的
//更新已有的SPU接口
//data:即为新增的SPU|或者已有的SPU对象
export const reqAddOrUpdateSpu = (data: any) => {
  //如果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)
  }
}

 5.2 TS类型

//SPU数据的ts类型:需要修改
export interface SpuData {
  category3Id: string | number
  id?: number
  spuName: string
  tmId: number | string
  description: string
  spuImageList: null | SpuImg[]
  spuSaleAttrList: null | SaleAttr[]
}

5.3 展示与收集已有的数据

5.3.1 存储父组件传递过来的数据

//存储已有的spu品牌数据
let MyAllTradeMark = ref<Trademark[]>([]);
//存储商品图片
let imgList = ref<SpuImg[]>([]);
//存储已有的销售属性
let saleAttr = ref<SaleAttr[]>([]);
//存储全部销售属性
let allSaleAttr = ref<HasSaleAttr[]>([]);
//存储已有的SPU对象
let SpuParams = ref<SpuData>({
  category3Id: "", //收集三级分类的ID
  spuName: "", //SPU的名字
  description: "", //SPU的描述
  tmId: "", //品牌的ID
  spuImageList: [],
  spuSaleAttrList: [],
});
let saleAttrIdAndValueName = ref<string>('')
//控制对话框的显示与隐藏
let dialogVisible = ref<boolean>(false)
//存储预览图片地址
let dialogImageUrl = ref<string>('')
//子组件书写一个方法
//存储已有的SPU对象
let SpuParams = ref<SpuData>({
  category3Id: '', //收集三级分类的ID
  spuName: '', //SPU的名字
  description: '', //SPU的描述
  tmId: '', //品牌的ID
  spuImageList: [],
  spuSaleAttrList: [],
})
//子组件书写一个方法
const initHasSpuData = async (spu: SpuData) => {
  //存储已有的SPU对象,将来在模板中展示
  SpuParams.value = spu
  。。。。。。
}

5.3.2 展示SPU名称

5.3.3 展示SPU品牌 

注意:下方的红框展示的是所有品牌,上方的绑定的是一个数字也就是下方的第几个

5.3.4 SPU描述

5.3.5 照片墙PART 

照片墙部分我们使用了element-plus的el-upload组件。下面详细介绍组件的功能及作用

1.整体结构

 

 上面el-upload是上传照片的照片墙,下面是查看照片的对话框

2.v-model:file-list

 

//商品图片
let imgList = ref<SpuImg[]>([])
//子组件书写一个方法
const initHasSpuData = async (spu: SpuData) => {
  。。。。。。
  //获取某一个品牌旗下全部售卖商品的图片
  let result1: SpuHasImg = await reqSpuImageList(spu.id as number)
  ......
  //SPU对应商品图片
  imgList.value = result1.data.map((item) => {
    return {
      name: item.imgName,
      url: item.imgUrl,
    }
  })
 ......
}

 这部分是一个双向绑定的数据,我们从服务器得到数据会展示到照片墙上。得到数据的过程我们使用了数组的map方法,这是因为组件对于数据的格式有要求。

3.action

action是指图片上传的地址。组件还会将返回的数据放到对应的img的数据中

4.list-type:照片墙的形式

5.:on-preview

预览的钩子,预览照片时会触发。会注入对应图片的数据

//控制对话框的显示与隐藏
let dialogVisible = ref<boolean>(false)
//存储预览图片地址
let dialogImageUrl = ref<string>('')
//照片墙点击预览按钮的时候触发的钩子
const handlePictureCardPreview = (file: any) => {
  dialogImageUrl.value = file.url
  //对话框弹出来
  dialogVisible.value = true
}

 6.:on-remove

移除图片前的钩子

7.:before-upload

上传前的钩子,我们用来对数据做预处理

//照片钱上传成功之前的钩子约束文件的大小与类型
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.4 展示已有的销售属性与属性值

展示销售属性与属性值 

其实就是4列,对应好每一列以及对应的数据就好

src\views\product\spu\spuFrom.vue

<!-- 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
              class="mx-1"
              closable
              style="margin: 0px 5px"
              @close="row.spuSaleAttrValueList.splice(index, 1)"
              v-for="(item, index) in row.spuSaleAttrValueList"
              :key="row.id"
            >
              {{ item.saleAttrValueName }}
            </el-tag>
            <el-button 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>

5.5 删除操作 

<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>

5.5 完成收集新增销售属性业务 

计算出还未拥有的销售属性

//计算出当前SPU还未拥有的销售属性
let unSelectSaleAttr = computed(() => {
  //全部销售属性:颜色、版本、尺码
  //已有的销售属性:颜色、版本
  let unSelectArr = allSaleAttr.value.filter((item) => {
    return saleAttr.value.every((item1) => {
      return item.name != item1.saleAttrName
    })
  })
  return unSelectArr
})

 收集你选择的属性的id以及name

添加属性按钮的回调

 

//添加销售属性的方法
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 = ''
}

5.6 销售属性值的添加删除业务

其实销售属性值和之前的添加属性业务差不多。最重要的是熟悉数据的结构。步骤分为:组件收集数据->回调中将数据整理后push到对应的数组中。

5.6.1 添加按钮与input框的切换

通过flag属性。一上来是没有的,点击按钮添加。输入框输入完毕blur时再将flag变为false

 

//属性值按钮的点击事件
const toEdit = (row: SaleAttr) => {
  //点击按钮的时候,input组件不就不出来->编辑模式
  row.flag = true;
  row.saleAttrValue = "";
};

5.6.2 收集&&添加属性值

收集的数据有俩个

saleAttrValue:点击添加按钮时初始化为空,收集输入的信息

baseSaleAttrId:所在的数据的id。由row给出

其余做的事就是:非法数据的过滤

//表单元素失却焦点的事件回调
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;
  }

 5.6.3 删除属性值

5.6.4 保存

整理数据+发送请求+通知父组件更新页面

//保存按钮的回调
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: "error",
      message: SpuParams.value.id ? "更新失败" : "添加失败",
    });
  }
};

5.7 添加spu业务&&收尾工作

5.7.1 添加spu业务

添加spu业务我们要做什么?收集数据(发请求得到的、自己添加的)放到对应的数据(存储数据用的容器)中,发起请求(保存按钮已经做完了),更新页面

1.父组件添加按钮回调

添加和修改按钮不同的地方在于对于数据的来源不同,修改按钮是一部分(spuParams)来源于父组件传递的数据,将他们与组件绑定,在数据上展示。添加按钮父组件只需要传递category3Id就行,其他的自己收集。

 src\views\product\spu\index.vue

//添加新的SPU按钮的回调
const addSpu = () => {
  //切换为场景1:添加与修改已有SPU结构->SpuForm
  scene.value = 1
  //点击添加SPU按钮,调用子组件的方法初始化数据
  spu.value.initAddSpu(categoryStore.c3Id)
}

 2.子组件收集数据

注意要对外暴露,让父组件可以使用

//添加一个新的SPU初始化请求方法
const initAddSpu = async (c3Id: number | string) => {
  //存储三级分类的ID
  SpuParams.value.category3Id = c3Id
  //获取全部品牌的数据
  let result: AllTradeMark = await reqAllTradeMark()
  let result1: HasSaleAttrResponseData = await reqAllSaleAttr()
  //存储数据
  MyAllTradeMark.value = result.data
  allSaleAttr.value = result1.data
}
//对外暴露
defineExpose({ initHasSpuData, initAddSpu })

3.整理数据与发送请求

这部分通过保存按钮的回调已经做完了。

5.8 清空数据

我们应该在每次添加spu前清空上次的数据

//添加一个新的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 = ''
  、、、、、、
}

5.9 跳转页面

在添加和修改spu属性后,跳转的页面不一样。修改应该跳转到当前页面,添加应该跳转到第一页。如何区分?SpuParams.value.id属性修改按钮的SpuParams是自带这个属性的,而添加按钮没有这个属性。因此在保存的时候通过这个属性告知父组件。

子组件:src\views\product\spu\spuFrom.vue

//保存按钮的回调
const save = async () => {
  。。。。。。。
    //通知父组件切换场景为0
    $emit('changeScene', {
      flag: 0,
      params: SpuParams.value.id ? 'update' : 'add',
    })
 。。。。。。
}

父组件: src\views\product\spu\index.vue

//子组件SpuForm绑定自定义事件:目前是让子组件通知父组件切换场景为0
const changeScene = (obj: any) => {
  //子组件Spuform点击取消变为场景0:展示已有的SPU
  scene.value = obj.flag
  if (obj.params == 'update') {
    //更新留在当前页
    getHasSpu(pageNo.value)
  } else {
    //添加留在第一页
    getHasSpu()
  }
}

六、添加SKU的静态

6.1 绑定回调 

src\views\product\spu\index.vue

//添加SKU按钮的回调
const addSku = (row: SpuData) => {
  //点击添加SKU按钮切换场景为2
  scene.value = 2
}

 

6.2 子组件静态页面

src\views\product\spu\skuFrom.vue

<template>
  <el-form label-width="auto">
    <el-form-item label="SKU名称">
      <el-input placeholder="请输入SKU名称" v-model="skuParams.skuName"></el-input>
    </el-form-item>
    <el-form-item label="价格(元)">
      <el-input
        type="number"
        placeholder="请输入价格"
        v-model="skuParams.price"
      ></el-input>
    </el-form-item>
    <el-form-item label="重量(g)">
      <el-input
        type="number"
        placeholder="请输入重量"
        v-model="skuParams.weight"
      ></el-input>
    </el-form-item>
    <el-form-item label="SKU描述">
      <el-input
        type="textarea"
        placeholder="请输入SKU描述"
        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 placeholder="请选择" 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}`"
              :label="saleAttrValue.saleAttrValueName"
              v-for="(saleAttrValue, index) in item.spuSaleAttrValueList"
              :key="saleAttrValue.id"
            ></el-option>
          </el-select>
        </el-form-item>
      </el-form>
    </el-form-item>
    <el-form-item label="图片名称" size="normal">
      <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-button type="primary" @click="save">保存</el-button>
    <el-button type="primary" @click="cancel">取消</el-button>
  </el-form>
</template>

<script setup lang="ts">
import { reqAttr } from "@/api/product/attr";
import {
  reqSpuHasSaleAttr,
  reqSpuImageList,
  reqAddSku,
} from "@/api/product/spu/index.ts";
import { ref, reactive } from "vue";
import { ElMessage } from "element-plus"
let $emit = defineEmits(["changeScene"]);
//平台属性
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 cancel = () => {
  $emit("changeScene", { flag: 0, params: "" });
};

//当前子组件的方法对外暴露
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);
  console.log(result);
  //获取对应的销售属性
  let result1: any = await reqSpuHasSaleAttr(spu.id);
  console.log(result1);
  //获取照片墙的数据
  let result2: any = await reqSpuImageList(spu.id);
  console.log(result2);
  //平台属性
  attrArr.value = result.data;
  //销售属性
  saleArr.value = result1.data;
  //图片
  imgArr.value = result2.data;
};

//设置默认图片的方法回调
const handler = (row: any) => {
  //点击的时候,全部图片的的复选框不勾选
  imgArr.value.forEach((item: any) => {
    table.value.toggleRowSelection(item, false);
  });
  //选中的图片才勾选
  table.value.toggleRowSelection(row, true);
  //收集图片地址
  skuParams.skuDefaultImg = row.imgUrl;
};

//保存按钮的方法
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;
  }, []);
  let res = await reqAddSku(skuParams);
  if (res.code === 200) {
    ElMessage({
      type: "success",
      message: "添加SKU成功",
    });
    $emit("changeScene", { flag: 0, params: "" });
  } else {
    ElMessage({
      type: "error",
      message: "添加SKU失败",
    });
  }
};

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

<style scoped lang="scss">
.el-select {
  width: 180px;
  height: 30px;
}
</style>

6.2.1 取消按钮

//自定义事件的方法
let $emit = defineEmits(['changeScene'])
//取消按钮的回调
const cancel = () => {
  $emit('changeScene', { flag: 0, params: '' })
}

6.3 获取添加SKU数据并展示

6.3.1 父组件添加按钮回调->调用子组件函数收集数据

父组件 src\views\product\spu\index.vue

//添加SKU按钮回调
const addSku =(row:SpuData)=>{
  //切换场景二
  scene.value = 2
  //调用子组件的方法初始化添加SKU的数据
  sku.value.initSkuData(categoryStore.c1Id, categoryStore.c2Id,row)
}

子组件暴露

src\views\product\spu\skuFrom.vue

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

6.3.2 子组件函数收集数据(平台属性、销售属性、图片名称)

//平台属性
let attrArr = ref<any>([]);
//销售属性
let saleArr = ref<any>([]);
//图片
let imgArr = ref<any>([]);


。。。。。。


//当前子组件的方法对外暴露
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;
};

6.3.3 模板展示 

src\views\product\spu\skuFrom.vue

<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 placeholder="请选择" 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}`"
              :label="saleAttrValue.saleAttrValueName"
              v-for="(saleAttrValue, index) in item.spuSaleAttrValueList"
              :key="saleAttrValue.id"
            ></el-option>
          </el-select>
        </el-form-item>
      </el-form>
    </el-form-item>
    <el-form-item label="图片名称" size="normal">
      <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>

6.4 sku收集总数据

使用skuParams将sku模块的所有数据全都存储下来

6.4.1 API&&Ts

src\api\product\spu\index.ts

//追加一个新增的SKU地址
  ADDSKU_URL = '/admin/product/saveSkuInfo',
}
//添加SKU的请求方法
export const reqAddSku = (data: SkuData) => {
  return request.post<any, any>(API.ADDSKU_URL, data)
}

ts:src\api\product\spu\type.ts

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图片地址
}

6.4.2 收集父组件传递过来的数据

这部分数据包括三级id,spuid还有品牌id。由于是父组件传递过来的,我们可以直接在添加按钮调用的那个函数中收集

src\views\product\spu\skuFrom.vue

//当前子组件的方法对外暴露
const initSkuData = async (c1Id: number | string, c2Id: number | string, spu: any) => {
  //收集数据
  skuParams.category3Id = spu.category3Id;
  skuParams.spuId = spu.id;
  skuParams.tmId = spu.tmId;



。。。。。。。。。。。。



}
//收集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图片地址
})

6.4.3 input框收集数据

sku名称、价格、重量、sku描述都是收集的用户输入的数据。我们直接使用v-model

6.4.4 收集平台属性以及销售属性 

 我们在数据绑定的时候将这俩个属性所选择的数据绑定到自身。之后整合数据的时候通过遍历得到

6.4.5 img 数据&&设置默认图片

//设置默认图片的方法回调
const handler = (row: any) => {
  //点击的时候,全部图片的的复选框不勾选
  imgArr.value.forEach((item: any) => {
    table.value.toggleRowSelection(item, false)
  })
  //选中的图片才勾选
  table.value.toggleRowSelection(row, true)
  //收集图片地址
  skuParams.skuDefaultImg = row.imgUrl
}

 6.4.6 完成添加sku

整合数据&&发请求

回调

//保存按钮的方法
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;
  }, []);
  let res = await reqAddSku(skuParams);
  if (res.code === 200) {
    ElMessage({
      type: "success",
      message: "添加SKU成功",
    });
    $emit("changeScene", { flag: 0, params: "" });
  } else {
    ElMessage({
      type: "error",
      message: "添加SKU失败",
    });
  }
};

6.4.7 bug

bug1:在发送请求的时候返回时undefined:注意;这种情况一般是由于API的请求函数没有写返回值(格式化之后)

bug2:平台属性和销售属性收集不到。可能时element-plus自带的table校验。前面数据填的格式不对(比如重量和价格input确定是数字但是可以输入字母e,这时候会导致错误)或者没有填写会导致后面的数据出问题。

七、SKU展示

7.1 API&&type 

API:src\api\product\spu\index.ts

//查看某一个已有的SPU下全部售卖的商品
  SKUINFO_URL = '/admin/product/findBySpuId/',
//获取SKU数据
export const reqSkuList = (spuId: number | string) => {
  return request.get<any, SkuInfoData>(API.SKUINFO_URL + spuId)
}

 TS:src\api\product\spu\type.ts

//获取SKU数据接口的ts类型
export interface SkuInfoData extends ResponseData {
  data: SkuData[]
}

7.2 绑定点击函数&&回调

src\views\product\spu\index.vue

//存储全部的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
  }
}

 7.3 模板展示

其实就是弹出一个对话框dialog,然后里面是一个form

<!-- 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>

八、删除spu业务

8.1 API

type为any,因此没有写专门的type

src\api\product\spu\index.ts

//删除已有的SPU
REMOVESPU_URL = '/admin/product/deleteSpu/',
//删除已有的SPU
export const reqRemoveSpu = (spuId: number | string) => {
  return request.delete<any, any>(API.REMOVESPU_URL + spuId)
}

 8.2 绑定点击函数

8.3 回调函数

 src\views\product\spu\index.vue

//删除已有的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: '删除失败',
    })
  }
}

九、spu业务完成

//路由组件销毁前,清空仓库关于分类的数据
onBeforeUnmount(() => {
  categoryStore.$reset()
})

  • 20
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值