Vue--》Vue3打造可扩展的项目管理系统后台的完整指南(七)

今天开始使用 vue3 + ts 搭建一个项目管理的后台,因为文章会将项目的每一个地方代码的书写都会讲解到,所以本项目会分成好几篇文章进行讲解,我会在最后一篇文章中会将项目代码开源到我的GithHub上,大家可以自行去进行下载运行,希望本文章对有帮助的朋友们能多多关注本专栏,学习更多前端vue知识,然后开篇先简单介绍一下本项目用到的技术栈都有哪几个方面(阅读本文章能够学习到的技术):

vite:快速轻量且功能丰富的前端构建工具,帮助开发人员更高效构建现代Web应用程序。

pnpm:高性能、轻量级npm替代品,帮助开发人员更加高效地处理应用程序的依赖关系。

Vue3:Vue.js最新版本的用于构建用户界面的渐进式JavaScript框架。

TypeScript:JavaScript的超集,提供了静态类型检查,使得代码更加健壮。

Animate:基于JavaScript的动画框架,它使开发者可以轻松创建各种炫酷的动画效果。

vue-router:Vue.js官方提供的路由管理器与Vue.js紧密耦合,非常方便与Vue.js一同使用。

Pinia:Vue3构建的Vuex替代品,具有响应式能力,提供非常简单的 API,进行状态管理。

element-plus:基于Vue.js 3.0的UI组件库,用于构建高品质的响应式Web应用程序。

axios:基于Promise的HTTP客户端,可以在浏览器和node.js中使用。

three:基于JavaScript的WebGL库,开发者可以编写高性能、高质量的3D场景呈现效果。

echarts:基于JavaScript的可视化图表库,支持多种类型的图表,可根据需要自行安装。

当然还有许多其他的需要安装的第三方库,这里就不再一一介绍了,在项目中用到的地方自行会进行讲解,大家自行学习即可,现在就让我们走进vue3+ts的实战项目吧。

目录

SPU模块搭建与展示数据

SPU模块场景切换

展示和收集已有的SPU数据

收集图片和SPU销售属性数据

修改业务完整实现

添加业务的实现

添加SKU业务实现

查看SKU及删除SKU功能实现


SPU模块搭建与展示数据

接下来开始搭建SPU模块的内容,还是老套路先进行内容的搭建,这里我引入了上篇文章讲解到的三级分类模块,然后引入了element-plus提供的表格和分页器内容,基本的内容搭建如下:

<template>
  <div>
    <!-- 三级分类结构 -->
    <Category :scene="scene"></Category>
    <el-card style="margin: 10px 0px">
      <el-button type="primary" size="default" icon="Plus">添加SPU</el-button>
      <el-table style="margin: 10px 0px" border>
        <el-table-column label="序号" type="index" align="center" 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="SPU操作"></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="400"
      />
    </el-card>
  </div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
// 场景的数据
let scene = ref<number>(0)
// 分页器默认页码
let pageNo = ref<number>(1)
// 每一页展示几条数据
let pageSize = ref<number>(3)
</script>

具体呈现的样式如下:

接下来开始编写获取SPU数据的接口函数,如下:

// SPU管理模块的接口
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}`)

在之前的许多文章中我都一笔带过书写ts类型,今天我将具体的json数据呈现出来,大家可以比较一下复杂的json数据如何编写ts类型:

{
  "code": 200,
  "message": "成功",
  "data": {
    "records": [
      {
        "id": 5,
        "spuName": "VIVO S10",
        "description": "产品名称\nS10\n品牌\nvivo\n首销日期\n以官网信息为准\n入网型号\nV2121A\n上市年份\n2021年\n上市月份\n7月",
        "category3Id": 61,
        "tmId": 6,
        "spuSaleAttrList": null,
        "spuImageList": null
      },
      {
        "id": 4,
        "spuName": "HUAWEI P40",
        "description": "HUAWEI P40",
        "category3Id": 61,
        "tmId": 3,
        "spuSaleAttrList": null,
        "spuImageList": null
      },
      {
        "id": 3,
        "spuName": "Apple iPhone 12",
        "description": "Apple iPhone 12",
        "category3Id": 61,
        "tmId": 2,
        "spuSaleAttrList": null,
        "spuImageList": null
      }
    ],
    "total": 8,
    "size": 3,
    "current": 2,
    "searchCount": true,
    "pages": 3
  },
  "ok": true
}

根据上文提供的json数据,我们先弄最外层的数据,然后再弄最里层的数据,最后将最里层的数据嵌套最外层的数据,通过继承的方式实现,具体的操作如下,大家可以看看是否有规律性:

// 服务器全部接口返回的数据类型
export interface ResponseData {
  code: number
  message: string
  ok: boolean
}
// SPU数据的ts类型:需要修改
export interface SpuData {
  id?: number
  spuName: string
  description: string
  category3Id: string | number
  tmId: number
  spuSaleAttrList: null
  spuImageList: 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
  }
}

接下来设置监听器watch,如果监听器监听到了三级分类ID的变化时就调用获取到当前三级分类ID下的所有SPU数据,具体代码操作如下:

// 存储已有的SPU的数据
let records = ref<Records>([])
// 存储已有的SPU总个数
let total = ref<number>(0)

// 监听三级分类ID的变化
watch(
  () => categoryStore.c3Id,
  () => {
    // 务必保证有三级分类的ID
    if (!categoryStore.c3Id) return
    getHasSpu()
  },
)
// 获取某个三级分类下的所有SPU数据
const getHasSpu = async () => {
  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
  }
}

将接口获取到的数据,通过data在table中呈现,通过prop在table-column中呈现,如下:

当然这里还有一个小细节,当我们获取三级分类的时候才能点击添加SPU按钮,否则该按钮一直处于禁用状态,结果如下:

接下来对分页器进行操作,我们在对切换页码值时调用current-change事件,其会自动注入当前的页码值,也就是说随着你点击分页器切换数据的时候,getHasSpu函数会自动注入一个参数动态的改变pageNo的数据。

当我们去动态的改变每一页展示几条数据,因为我们没有参数对getHasSpu函数进行注入,也就是说每次我们改变pageSize数值的时候,我们都会使用getHasSpu默认参数pager = 1的数据,这也就让我们每次改变pageSize时,数据展示会自动切换到pageNo=1的页面,比较符合用户体验。

结果如下:

SPU模块场景切换

SPU模块有SPU数据展示场景、SPU添加和修改场景、SKU添加场景这三个场景,我们要通过相关按钮进行场景的来回切换,这里为了方便组件的维护,将SPU添加修改场景和SKU添加场景单独抽离出一个组件通过按钮来进行场景的切换:

因为场景切换是频繁的,所以这里采用v-show进行组件的展示与隐藏,如下:

我们这里先进行添加和修改SPU场景的搭建,其基本静态页面搭建如下:

<template>
  <el-form label-width="120px">
    <el-form-item label="SPU的名称">
      <el-input placeholder="请你输入SPU名称"></el-input>
    </el-form-item>
    <el-form-item label="SPU的品牌">
      <el-select>
        <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>
        <el-option label="华为"></el-option>
        <el-option label="华为"></el-option>
        <el-option label="华为"></el-option>
      </el-select>
      <el-button type="primary" size="default" icon="Plus" style="margin-left: 10px">添加属性值</el-button>
      <!-- table展示销售属性与属性值的地方 -->
      <el-table border style="margin: 10px 0px">
        <el-table-column label="序号" type="index" align="center" width="80px"></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-form-item>
    <el-form-item>
      <el-button type="primary" size="default">保存</el-button>
      <el-button type="primary" size="default" @click="cancel">取消</el-button>
    </el-form-item>
  </el-form>
</template>

<script setup lang="ts">
let $emit = defineEmits(['changeScene'])
// 点击取消按钮进行场景切换,通知父组件切换场景为0,展示已有的SPU数据
const cancel = () => {
  $emit('changeScene', 0)
}
</script>

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

其静态页面样式如下:

这里通过自定义事件,当我们点击取消按钮时,将告诉父组件的scene场景值要发生改变,如下:

// 子组件SpuForm绑定自定义事件,让子组件通知父组件切换场景为0
const changeScene = (num: number) => {
  // 子组件SpuForm点击取消变为场景0,展示已有的SPU数据
  scene.value = num
}

当然我们在点击添加SPU和修改SPU按钮时也需要对场景的切换,如下:

// 添加SPU按钮的回调
const addSpu = () => {
  // 切换为场景1
  scene.value = 1
}
// 修改已有的SPU按钮的回调
const updateSPU = () => {
  // 切换为场景1
  scene.value = 1
}

展示和收集已有的SPU数据

接下来我们实现,当我们点击修改SPU按钮的时候,将获取到数据展示到修改页面上,首先仍然编写相关获取数据的接口:

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/saveSpuInfo',
}
// 获取某一个三级分类下已有的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
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)
  }
}

当我们点击修改SPU按钮的时候,子组件调用相关接口,然后通过父组件调用子组件的方法传递给子组件当前点击修改按钮时相关要修改的SPU的数据,如下:

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

子组件向外暴露方法,让父组件能够拿到相关方法,然后子组件通过父组件传递过来的属性对象,通过调用相关的接口将获取到的数据存储在响应式ref中,然后通过v-model双向数据绑定对数据进行一个呈现,如下:

// 存储已有的SPU的数据
let allTradeMark = ref<Trademark[]>([])
// 商品图片
let imgList = ref<SpuImg[]>([])
// 已有的SPU销售属性
let saleAttr = ref<SaleAttr[]>([])
// 全部的销售属性
let allSaleAttr = ref<HasSaleAttr[]>([])
// 存储已有的SPU对象
let SpuParams = ref<SpuData>({
  category3Id: '', // 收集三级分类的ID
  spuName: '', // SPU的名字
  description: '', // SPU的描述
  tmId: '', // 品牌的ID
  spuImageList: [],
  spuSaleAttrList: [],
})

let $emit = defineEmits(['changeScene'])
// 点击取消按钮进行场景切换,通知父组件切换场景为0,展示已有的SPU数据
const cancel = () => {
  $emit('changeScene', 0)
}
// 子组件书写的方法,spu:即为父组件传递过来的已有的SPU对象[不完整]
const initHasSpuData = async (spu: SpuData) => {
  // 存储已有的SPU对象,将来在模板中展示
  SpuParams.value = 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对应的商品图片
  imgList.value = result1.data
  // 存储已有的SPU的销售属性
  saleAttr.value = result2.data
  // 存储全部的销售属性
  allSaleAttr.value = result3.data
}
// 对外暴露该方法
defineExpose({ initHasSpuData })

结果如下所示:

收集图片和SPU销售属性数据

接下来实现收集图片和销售属性的相关数据,收集图片数据的话仍然采用element-plus中上传图片的组件,如下:

通过点击图片的预览按钮实现对话框的弹出然后给图片路径赋予相关地址:

// 照片墙点击预览按钮的时候触发的钩子
const handlePictureCardPreview = (file: any) => {
  // 对话框弹出
  dialogVisible.value = true
  dialogImageUrl.value = file.url
}

这里也可以对上传图片的格式和大小进行相关限制:

// 照片墙上传成功之前的钩子约束文件的类型和大小
const handlerUpload = (file: any) => {
  if (file.type == 'image/png' || file.type == 'image/jpeg' || file.type == 'image/jpg' || file.type == 'image/gif') {
    if (file.size / 1024 / 1024 < 3) {
      return true
    } else {
      ElMessage({
        type: 'error',
        message: '上传文件务必小于3MB!',
      })
      return false
    }
  } else {
    ElMessage({
      type: 'error',
      message: '上传文件格式务必是 png|jpeg|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
})

这里对销售属性通过一个表格进行相应的展示:

通过将下拉框的数据进行一个处理,添加到一个符合销售属性对象的数据对象中进行处理,然后再通过push属性追加到数组当中:

// 添加销售属性的方法
const addSaleAttr = () => {
  const [baseSaleAttrId, saleAttrName] = saleAttrIdAndValueName.value.split(':')
  // 准备一个新的销售对象,将来带给服务器即可
  let newSaleAttr: SaleAttr = {
    baseSaleAttrId,
    saleAttrName,
    spuSaleAttrValueList: [],
  }
  // 追加到数组当中
  saleAttr.value.push(newSaleAttr)
  // 清空收集到的数据
  saleAttrIdAndValueName.value = ''
}

最终呈现的结果如下:

收集到销售属性之后,接下来我们实现点击添加按钮后进行一个输入框和按钮的相互交替呈现:

当我们点击按钮时进入输入框模式,然后通过v-model双向数据绑定,给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
  }
  // 追加新的属性值对象
  row.spuSaleAttrValueList.push(newSaleAttrValue)
  // 切换为查看模式
  row.flag = false
}

修改业务完整实现

在上文我们将能够获取到的数据都进行一个展示,接下来实现正式的修改SPU业务的实现,我们在保存按钮出设置点击事件:

这里有个细节,只有当销售属性数据大于0时才能点击保存按钮,否则按钮都处于禁用状态,也就是说销售属性必须大于等于1个才能进行修改业务的实现。

在save保存函数中,我们将所以的数据进行一个收集,整理出符合服务器要求我们返回数据格式的规范,具体操作如下:

// 保存按钮的回调
const save = async () => {
  // 整理数据
  // 照片墙数据的整理,让其符合服务器要求我们返回过去的数据
  SpuParams.value.spuImageList = imgList.value.map((item: any) => {
    return {
      imgName: item.name, // 图片的名字
      imgUrl: (item.response && item.response.data) || item.url,
    }
  })
  // 整理销售属性的数据
  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', 0)
  } else {
    ElMessage({
      type: 'error',
      message: SpuParams.value.id ? '更新失败' : '添加失败',
    })
  }
}

最终呈现的效果如下所示:

添加业务的实现

我们先在spuForm组件中声明初始化数据的函数,如下:

// 添加一个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
}

然后向外暴露该函数,方便父组件调用子组件的方法

我们在父组件中调用子组件的方法,并将当前选择的三级分类的ID进行一个传递,如下:

这里有个注意点,当我们在切换场景的时候,除了要切换场景值外,还需要进行判断当前是处于添加SPU还是修改SPU的状态,添加的话页码值自动跳转到第一页,修改的话页码值保留在当前页码值不改变,如下:

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

最终呈现的结果如下:

添加SKU业务实现

当我们点击SKU的时候,仍然要进行一个场景的切换,具体的静态搭建放置在skuForm组件当中:

<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 in attrArr" :key="item.id" :label="item.attrName">
          <el-select v-model="item.attrIdAndValueId">
            <el-option
              :value="`${item.id}:${attrValue.id}`"
              v-for="attrValue 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 v-for="item in saleArr" :key="item.id" :label="item.saleAttrName">
          <el-select v-model="item.saleIdAndValueId">
            <el-option
              :value="`${item.id}:${saleAttrValue.id}`"
              v-for="saleAttrValue 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 #default="{ row }">
            <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 #default="{ row }">
            <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>

这里仍然是当点击SKU的时候,父组件调用子组件的方法,然后给子组件传递相应的数值,如下:

// 添加SKU按钮的回调
const addSku = (row: SpuData) => {
  // 点击添加SKU按钮切换场景为2
  scene.value = 2
  // 调用子组件的实例方法
  sku.value.initSkuData(categoryStore.c1Id, categoryStore.c2Id, row)
}

子组件拿到数据值进行一个数据的存储,如下:

// 初始化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
}
// 子组件的方法对外保留
defineExpose({ initSkuData })

然后子组件调用获取增加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
  }, [])
  // 添加SKU的请求
  let result: any = await reqAddSku(skuParams)
  if (result.code == 200) {
    ElMessage({
      type: 'success',
      message: '添加SKU成功',
    })
    // 通知父组件切换场景为0
    $emit('changeScene', { flag: 0, params: '' })
  } else {
    ElMessage({
      type: 'error',
      message: '添加SKU失败',
    })
    // 通知父组件切换场景为0
    $emit('changeScene', { flag: 0, params: '' })
  }
}

当然我们在初始化数据的时候,也别忘记了给数据进行一个清除:

最后呈现的结果如下:

查看SKU及删除SKU功能实现

不管是查看还是删除功能都需要调用相关的API接口函数,如下:

在点击查看SPU按钮的时候,调用获取SKU数据的接口函数,如下:

// 查看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
  }
}

通过一个show数据的切换来展示对话框数据的展示

删除模块通过一个气泡对话框的形式展示,如下:

然后给删除的confirm函数进行设置:

// 删除已有的SPI回调
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()
})

本项目的SPU管理页面功能的搭建就讲解到这,下一篇文章将继续讲解其它模块的主体内容,关注博主学习更多前端vue知识,您的支持就是博主创作的最大动力! 

猜你喜欢

转载自blog.csdn.net/qq_53123067/article/details/131339824