灏天阁

vue3.2/Vite/Ts64: 完成添加SPU业务

· Yin灏

目录结构

└─src
    └─views
        └─product
            └─spu
                ├─index.vue
                ├─skuform.vue
                └─spuform.vue

完成 SPU 添加

src/views/product/spu/spuform.vue

<template>
  <el-form label-width="100px">
    <el-form-item label="SPU名称">
      <el-input
        placeholder="请输入SPU名称"
        v-mode="SpuParams.spuName"
      ></el-input>
    </el-form-item>
    <el-form-item label="SPU品牌">
      <el-select v-model="SpuParams.tmId">
        <el-option
          v-for="(item, index) in AllTradeMark"
          :key="item.id"
          :label="item.tmName"
          :value="item.id"
        ></el-option>
      </el-select>
    </el-form-item>
    <el-form-item label="SPU描述">
      <el-input
        type="textarea"
        placeholder="请输入SPU描述"
        v-model="SpuParams.description"
      ></el-input>
    </el-form-item>
    <el-form-item label="SPU照片">
      <el-upload
        v-model:file-list="imgList"
        action="/api/admin/product/fileUpload"
        list-type="picture-card"
        :on-preview="handlePictureCardPreview"
        :on-remove="handleRemove"
        :before-upload="handleUpload"
      ></el-upload>
      <el-dialog v-model="dialogVisible">
        <img w-full :src="dialogImageUrl" style="width: 100%" alt="" />
      </el-dialog>
    </el-form-item>
    <el-form-item label="SPU销售属性">
      <!-- 展示销售属性 -->
      <el-select
        :placeholder="unSelectSaleAttr.length ? `还未选择${unSelectSaleAttr.length}个` : '无'"
        v-model="saleAttrIdAndValueName"
      >
        <el-option
          v-for="(item, index) in unSelectSaleAttr"
          :key="item.id"
          :label="item.name"
          :value="`${item.id}:${item.name}`"
        ></el-option>
      </el-select>
      <el-button
        type="primary"
        size="default"
        icon="Plus"
        :disabled="saleAttrIdAndValueName ? false : true"
        @click="addSaleAttr"
        >添加属性</el-button
      >
      <!-- 展示销售属性与属性值 -->
      <el-table border :data="saleAttr">
        <el-table-column
          label="序号"
          type="index"
          width="80px"
          align
        ></el-table-column>
        <el-table-column
          label="销售属性名字"
          width="100px"
          prop="saleAttrName"
        ></el-table-column>
        <el-table-column label="销售属性值">
          <template #="{ row, $index }">
            <el-tag
              v-for="(item, index) in row.spuSaleAttrValueList"
              :key="row.id"
              class="mx-1"
              closable
              @close="row.spuSaleAttrValueList.splice($index, 1)"
              >{{ item.saleAttrValueName }}</el-tag
            >
            <el-input
              placeholder="请你输入属性值"
              size="small"
              style="width: 100px"
              v-if="row.flag === true"
              v-model="row.saleAttrValue"
              @blur="toLook(row)"
            ></el-input>
            <el-button
              v-else
              type="primary"
              size="small"
              icon="Plus"
              @click="toEdit(row)"
            ></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>
    </el-form-item>
    <el-form-item>
      <el-button
        type="primary"
        size="default"
        @click="save"
        :disabled="saleAttr.length > 0 ? false : true"
        >保存</el-button
      >
      <el-button type="primary" size="default" @click="cancel">取消</el-button>
    </el-form-item>
  </el-form>
</template>
<script setup lang="ts">
  import { ref, computed } from "vue";
  //...
  import type {
    SpuData,
    AllTradeMark,
    SpuHasImg,
    SaleAttrResponseData,
    HasSaleAttrResponseData,
    Trademark,
    SpuImage,
    SaleAttr,
    HasSaleAttr,
    SaleAttrValue
  } from "@/api/product/spu/type";
  import {
    reqAllTradeMark,
    reqSpuImageList,
    reqSpuHasSaleAttr,
    reqAllSaleAttr,
    reqAddOrUpdateSpu
  } from "@/api/product/spu";
  //...
  let dialogVisible = ref<boolean>(false);
  let dialogImageUrl = ref<string>('');
  let AllTradeMark = ref<Trademark>([]);
  let imgList = ref<SpuImage>([]);
  let saleAttr = ref<SaleAttr>([]);
  let allSaleAttr = ref<HasSaleAttr>([]);

  let saleAttrIdAndValueName = ref<string>('');

  let SpuParams = ref<SpuData>({
    category3Id: '';
    spuName: '';
    tmId: '';
    description: '';
    spuImageList: [];
    spuSaleAttrList: [];
  });
  //...
  const initHasSpuData = async (spu: SpuData) => {
    SpuParams.value = SpuData;
    const result: AllTradeMark = await reqAllTradeMark();
    const result1: SpuHasImg = await reqSpuImageList(spu.id as number);
    const result2: SaleAttrResponseData = await reqSpuHasSaleAttr(
      spu.id as number
    );
    const result3: HasSaleAttrResponseData = await reqAllSaleAttr();
    // 数据存储
    AllTradeMark.value = result.data;
    imgList.value = result1.data.map(item => {
      return {
        name: item.imgName,
        url: item.imgUrl
      }
    });
    saleAttr.value = result2.data;
    allSaleAttr.value = result3.data;
  };

  const handlePictureCardPreview = (file: any) => {
    dialogImageUrl.value = file.url;
    dialogVisible.value = true;
  }

  const handleRemove = () => {}

  const handleUpload = (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: '只能上传图片类型'
      })
      return false
    }
  }

  let unSelectSaleAttr = computed(() => {
    let unSelectArr = allSaleAttr.value.filter(item => {
       return saleAttr.value.every(item1 => {
        return item.name !== item1.saleAttrName
       })
    });
    return unSelectArr;
  })

  let addSaleAttr = () => {
    const [baseSaleAttrId, saleAttrName] = saleAttrIdAndValueName.value.split(':');
    let newSaleAttr: SaleAttr = {
      baseSaleAttrId,
      saleAttrName,
      spuSaleAttrValueList: []
    };
    saleAttr.value.push(newSaleAttr);
    saleAttrIdAndValueName.value = '';
  }

  const toEdit = (row: SaleAttr) => {
    row.flag = true;
    row.saleAttrValue = '';
  }

  const toLook = (row: SaleAttr) => {
    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;
  }

  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;
    const result: any = await reqAddOrUpdateSpu(SpuParams.value);
    if(result.code === 200) {
      ElMessage({
        type: 'success',
        message: SpuParams.value.id ? '更新成功' : '添加成功'
      });
      $emit('changeScene', {
        flag: 0,
        params: SpuParams.value.id ? 'update' : 'add'
      })
    } else {
      ElMessage({
        type: 'error',
        message: SpuParams.value.id ? '更新失败' : '添加失败'
      })
    }
  }

  const cancel = () => {
    $emit('changeScene', {
      flag: 0,
      params: 'update'
    })
  }

  const initAddSpu = (c3Id: number|string) => {
    // clear data
    Object.assign(SpuParams.value, {
      category3Id: '',
      spuName: '',
      description: '',
      tmId: '',
      spuImageList: [],
      spuSaleAttrList: []
    });
    imgList.value = [];
    saleAttr.value = [];
    saleAttrIdAndValueName.value = '';
    // clear data
    SpuParams.value.category3Id = c3Id;
    const result: AllTradeMark = await reqAllTradeMark();
    const result1: HasSaleAttrResponseData = await reqAllSaleAttr();
    AllTradeMark.value = result.data;
    allSaleAttr.value = result1.data;
  }

  defineExpose({ initHasSpuData, initAddSpu });
</script>

父组件初始化添加场景

src/views/product/spu/index.vue

<template>
  <div>
    <Category :scene="scene" />
    <el-card>
      <div v-show="scene === 0">
        <el-button
          type="primary"
          size="default"
          icon="Plus"
          :disabled="categoryStore.c3Id ? false : true"
          @click="addSpu"
          >添加SPU</el-button
        >
        <!-- ... -->
      </div>
      <SpuForm
        ref="spu"
        v-show="scene === 1"
        @changeScene="changeScene"
      ></SpuForm>
      <!-- ... -->
    </el-card>
  </div>
</template>
<script setup lang="ts">
  //...
  import useCategoryStore from "@/store/modules/category";
  let categoryStore = useCategoryStore();
  //...
  let spu = ref<any>(null);
  //...
  const addSpu = () => {
    scene.value = 1;
    spu.value.initAddSpu(categoryStore.c3Id);
  };

  const changeScene = (obj: any) => {
    scene.value = obj.flag;
    //...
    if (obj.params === "update") {
      getHasSpu(pageNo.value);
    } else {
      getHasSpu();
    }
  };

  //...
</script>

- Book Lists -