Vue--"Vue3 Complete Guide to Creating an Extensible Project Management System Background (7)

Today I started to use vue3 + ts to build a project management background, because the article will explain the writing of code in every part of the project, so this project will be divided into several articles to explain, and I will explain it in the last article The project code is open-sourced on my GitHub , and you can download and run it yourself. I hope that friends who are helpful to this article can pay more attention to this column and learn more front-end Vue knowledge. Then, at the beginning of the article, I will briefly introduce the ones used in this project. What are the aspects of the technology stack ( read the technology that can be learned in this article ):

vite : A fast, lightweight and feature-rich front-end building tool that helps developers build modern web applications more efficiently.

pnpm : A high-performance, lightweight npm replacement that helps developers handle application dependencies more efficiently.

Vue3 : The latest version of Vue.js, a progressive JavaScript framework for building user interfaces.

TypeScript : A superset of JavaScript that provides static type checking to make the code more robust.

Animate : A JavaScript-based animation framework that enables developers to easily create various cool animation effects.

vue-router : The routing manager officially provided by Vue.js is tightly coupled with Vue.js, which is very convenient to use with Vue.js.

Pinia : A Vuex alternative built with Vue3 that is responsive and provides a very simple API for state management.

element-plus : A Vue.js 3.0-based UI component library for building high-quality responsive web applications.

axios : Promise-based HTTP client that can be used in the browser and node.js.

three : JavaScript-based WebGL library, developers can write high-performance, high-quality 3D scene rendering effects.

echarts : JavaScript-based visual charting library that supports multiple types of charts and can be installed as needed.

Of course, there are many other third-party libraries that need to be installed, so I won’t introduce them one by one here. The places used in the project will be explained by themselves. You can learn by yourself. Now let’s walk into vue3+ts. Actual project.

Table of contents

SPU module construction and display data

SPU module scene switching

Display and collect existing SPU data

Collect images and SPU sales attribute data

Modify the complete implementation of the business

Add business implementation

Add SKU business realization

View SKU and delete SKU functions


SPU module construction and display data

Next, start to build the content of the SPU module. It is still the old routine to build the content first. Here I introduced the three-level classification module explained in the previous article, and then introduced the table and pager content provided by element-plus. The basic content Build as follows:

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

The specific presentation style is as follows:

Next, start writing the interface function for obtaining SPU data, as follows:

// 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}`)

In many previous articles, I have written the ts type in one stroke. Today I will present the specific json data. You can compare how to write the ts type with complex json data:

{
  "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
}

According to the json data provided above, we first get the outermost data, then get the innermost data, and finally nest the innermost data with the outermost data, and implement it through inheritance. The specific operation As follows, you can see if there is a regularity:

// 服务器全部接口返回的数据类型
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
  }
}

Next, set the listener watch. If the listener detects the change of the three-level category ID, it will call to obtain all the SPU data under the current three-level category ID. The specific code operation is as follows:

// 存储已有的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
  }
}

The data obtained by the interface is presented in the table through data, and in the table-column through prop, as follows:

Of course, there is a small detail here. We can only click the Add SPU button when we get the three-level classification, otherwise the button is always disabled, and the results are as follows:

Next, operate the pager. We call the current-change event when switching the page number value, which will automatically inject the current page number value. That is to say, when you click the pager to switch data, the getHasSpu function will automatically inject a The parameter dynamically changes the data of pageNo.

When we dynamically change the number of data displayed on each page, because we have no parameters to inject the getHasSpu function, that is to say, every time we change the value of pageSize, we will use the data of the default parameter pager = 1 of getHasSpu, which is also Let us change the pageSize every time, the data display will automatically switch to the page with pageNo=1, which is more in line with the user experience.

The result is as follows:

SPU module scene switching

The SPU module has three scenarios: SPU data display scene, SPU addition and modification scene, and SKU addition scene. We need to switch back and forth between the scenes through the relevant buttons. Here, for the convenience of component maintenance, the SPU addition and modification scene and SKU addition scene are separated. Extract a component to switch scenes through buttons:

Because scene switching is frequent, v-show is used here to display and hide components, as follows:

Here we first add and modify the construction of the SPU scene. The basic static page construction is as follows:

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

Its static page style is as follows:

Here, through the custom event, when we click the cancel button, it will tell the scene value of the parent component to change, as follows:

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

Of course, we also need to switch the scene when we click the Add SPU and Modify SPU buttons, as follows:

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

Display and collect existing SPU data

Next, we realize that when we click the Modify SPU button, the obtained data will be displayed on the modification page. First, we still write the relevant interface for obtaining data:

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

When we click the Modify SPU button, the child component calls the relevant interface, and then passes the method of calling the child component through the parent component to the data of the SPU to be modified when the child component currently clicks the modify button, as follows:

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

The child component exposes the method to the outside, so that the parent component can get the relevant method, and then the child component passes the attribute object passed by the parent component, and stores the obtained data in the responsive ref by calling the relevant interface, and then through the v-model Two-way data binding presents the data as follows:

// 存储已有的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 })

The result looks like this:

Collect images and SPU sales attribute data

Next, collect data related to pictures and sales attributes. If you collect picture data, you still use the component of uploading pictures in element-plus, as follows:

Click the preview button of the picture to realize the pop-up of the dialog box and assign the relevant address to the picture path:

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

Here you can also restrict the format and size of uploaded images:

// 照片墙上传成功之前的钩子约束文件的类型和大小
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
  }
}

The drop-down menu that displays the sales attributes, the default data is three, when we make a selection, the drop-down menu will become less until all three options are selected and there will be no data. Here, it is as follows:

Here, the property is calculated to determine how much data is still available for selection in the current drop-down box:

// 计算当前的SPU还未拥有的销售属性
let unSelectSaleAttr = computed(() => {
  // 全部销售属性中过滤去未有的销售属性
  let unSelectArr = allSaleAttr.value.filter((item) => {
    return saleAttr.value.every((item1) => {
      return item.name != item1.saleAttrName
    })
  })
  return unSelectArr
})

Here is a corresponding display of sales attributes through a table:

By processing the data in the drop-down box, adding it to a data object conforming to the sales attribute object for processing, and then adding it to the array through the push attribute:

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

The final result is as follows:

After collecting the sales attributes, next we implement an alternate presentation of an input box and a button after clicking the Add button:

When we click the button, we enter the input box mode, and then through v-model two-way data binding, a new attribute is added to the row to store the data we input, as follows:

And when the input box loses focus, execute the out-of-focus function, as follows:

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

Modify the complete implementation of the business

In the above, we will display all the data that can be obtained, and then implement the official modification of the SPU business. We set the click event on the save button:

Here is a detail, the save button can only be clicked when the sales attribute data is greater than 0, otherwise the buttons are disabled, that is to say, the sales attribute must be greater than or equal to 1 to realize the modification business.

In the save function, we collect all the data and sort out the specifications that meet the requirements of the server to return the data format. The specific operations are as follows:

// 保存按钮的回调
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 ? '更新失败' : '添加失败',
    })
  }
}

The final rendering effect is as follows:

Add business implementation

We first declare the function of initializing data in the spuForm component, as follows:

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

Then expose the function to the outside, so that the parent component can call the method of the child component

We call the method of the child component in the parent component, and pass the ID of the currently selected three-level classification, as follows:

There is a point to note here. When we switch scenes, in addition to switching scene values, we also need to judge whether we are currently in the state of adding SPU or modifying SPU. If we add, the page number value will automatically jump to the first page. If we modify The page number value remains unchanged at the current page number value, as follows:

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

The final result is as follows:

Add SKU business realization

When we click on the SKU, we still need to switch a scene, and the specific static construction is placed in the skuForm component:

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

Here is still when the SKU is clicked, the parent component calls the method of the child component, and then passes the corresponding value to the child component, as follows:

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

The subcomponent gets the data value to store a data, as follows:

// 初始化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 })

Then the subcomponent calls the method to obtain the interface for adding SKU data, as follows:

Realize data storage and interaction by clicking the save button:

// 保存按钮的方法
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: '' })
  }
}

Of course, when we initialize the data, don't forget to clear the data:

The final result presented is as follows:

View SKU and delete SKU functions

Regardless of whether it is to view or delete functions, you need to call the relevant API interface functions, as follows:

When the View SPU button is clicked, the interface function for obtaining SKU data is called, as follows:

// 查看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 the display of dialog box data through a switch of show data

The delete module is displayed in the form of a bubble dialog box, as follows:

Then set the deleted confirm function:

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

Finally, clear the warehouse data when switching routes:

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

The construction of the SPU management page function of this project is explained here. The next article will continue to explain the main content of other modules. Follow bloggers to learn more front-end Vue knowledge. Your support is the biggest motivation for bloggers to create! 

Guess you like

Origin blog.csdn.net/qq_53123067/article/details/131339824