SPU管理

  • 先搞定静态组件(类似Attr管理的页面结构)
### product.Spu.index.vue
<template>
  <div>
    <!--三级联动结构(全局组件)-->
    <el-card style="margin: 20px 0px;">
      <!--传值show过去-->
      <CategorySelect @getCategoryId="getCategoryId" :show="!show"></CategorySelect>
    </el-card>

    <!--下半部分结构(有三部分要进行切换)-->
    <el-card>
      <!--第一部分SPU结构-->
      <div>
        <el-button type="primary" icon="el-icon-plus">添加SPU</el-button>
        <el-table style="width: 100%" border>

          <el-table-column type="index" label="序号" width="80px" align="center">
          </el-table-column>

          <el-table-column prop="prop" label="SPU名称" width="width">
          </el-table-column>

          <el-table-column prop="prop" label="SPU描述" width="width">
          </el-table-column>

          <el-table-column prop="prop" label="操作" width="width">
            <template slot-scope="{ row, $index }">
              <el-button type="success" icon="el-icon-plus" size="mini"></el-button>
              <el-button type="warning" icon="el-icon-edit" size="mini"></el-button>
              <el-button type="info" icon="el-icon-info" size="mini"></el-button>
              <el-button type="danger" icon="el-icon-delete" size="mini"></el-button>
            </template>
          </el-table-column>
        </el-table>

        <!--分页器
          @current-change="getPageList" @size-change="handleSizeChange"
        -->
        <el-pagination style="textAlign:center" :current-page="6" :page-size="3"
          :page-sizes="[3,5,10]" :total="23" layout="prev,pager,next,jumper,->,sizes,total">
        </el-pagination>

      </div>
    </el-card>
  </div>
</template>

<script>
  export default {
    name:'Spu',
    data(){
      return {
        show:true, // 初始化数据
        category1Id:'',
        category2Id:'',
        category3Id:'',
      }
    },
    methods:{
      getCategoryId({categoryId,level}){ // 收集categoryId
        if(level == 1){
          this.category1Id = categoryId
          this.category2Id = '',
          this.category3Id = ''
        }else if(level == 2){
          this.category2Id = categoryId
          this.category3Id = ''
        }else{
          this.category3Id = categoryId
          this.getSpuList()
        }
      },
      getSpuList(){ // 占位

      }
    }
  }
</script>
  • 发请求获取SPU列表数据
### api.product.spu.js
import request from '@/utils/request'
// 接口从外表看,只需要两个数据,category3Id通过params参数传给后端
export const reqSpuList = (page,limit,category3Id)=>request({url:`/admin/product/${page}/${limit}`,method:'get',params:{category3Id}})

### Spu.index.vue
......
<script>
  export default {
    name:'Spu',
    data(){
      return {
       ......
        page:1, // 分页器数据相关
        limit:3,
        total:0,

        records:[] // SPU列表数据
      }
    },
    methods:{
      getCategoryId({categoryId,level}){
        ......
      },
      async getSpuList(){ //发请求获取SPU数据和分页相关数据
        const {page,limit,category3Id} = this;
        let res = await this.$API.spu.reqSpuList(page,limit,category3Id)
		if(res.code == 200){
			this.total = res.data.total
			this.records = res.data.records
		}
      }
    }
  }
</script>

  • 按钮打包成全局组件并运用
### components.HintButton.index.vue
<template>
  <!--这里若使用div,则每个按钮都独立一行,不符合需求-->
  <!--使用title来展示"当用户把鼠标移动到图标而显示的提示信息"-->
  <a :title="title" style="margin: 10px;">
    <!--$attrs获取组件传过来的所有原生属性-->
    <!--$listeners用来监听,这里暂时没有用到,是个空对象-->
    <el-button v-bind="$attrs" v-on="$listeners"></el-button>
  </a>
</template>

<script>

  export default {
    name:"HintButton",
    props:['title'], // 接收传过来的title
  }
</script>

### main.js
......
import HintButton from '@/components/HintButton'
Vue.component(HintButton.name,HintButton)

### Spu.index.vue
......
<el-table-column prop="prop" label="操作" width="width">
	<template slot-scope="{ row, $index }">
	  <!--type,icon,size都被 $attrs接收,title被 props 接收-->
	  <hint-button type="success" icon="el-icon-plus" size="mini" title="添加sku"></hint-button>
	  <hint-button type="warning" icon="el-icon-edit" size="mini" title="修改spu"></hint-button>
	  <hint-button type="info" icon="el-icon-info" size="mini" title="查看当前spu的全部sku列表"></hint-button>
	  <hint-button type="danger" icon="el-icon-delete" size="mini" title="删除spu"></hint-button>
	  <!--以下默认写法当然可以,上面的写法vue也支持-->
	  <!-- <HintButton type="danger" icon="el-icon-delete" size="mini" title="我的测试"></HintButton> -->
	</template>
</el-table-column>
  • 分页器参数搞成动态的
### Spu.index.vue
......
<!--动态参数并绑定事件-->
<el-pagination style="textAlign:center" :current-page="page" :page-size="limit"
  :page-sizes="[3,5,10]" :total="total" layout="prev,pager,next,jumper,->,sizes,total"
  @current-change="getSpuList" @size-change="handleSizeChange">
  <!-- @current-change="handleCurrentChange"> -->
</el-pagination>
......
<script>
  export default {
    name:'Spu',
    ......
    methods:{
      getCategoryId({categoryId,level}){
        ......
      },
      <!--默认页码数设置为1-->
      async getSpuList(pages=1){
        this.page = pages;
        const {page,limit,category3Id} = this;
        let res = await this.$API.spu.reqSpuList(page,limit,category3Id)
   
        if(res.code == 200){
          this.total = res.data.total
          this.records = res.data.records
        }
      },
      <!--当用户改变"每页显示的页码数"时,触发-->
      handleSizeChange(limit){
        this.limit = limit;
        this.getSpuList();
      }
      // 把以下逻辑合并到了getSpuList
      // handleCurrentChange(page){
      //   this.page = page;
      //   this.getSpuList()
      // }
    }
  }
</script>

SPU内容的切换

  • SPU列表(三级联动共存),使用数字0来表示

  • 添加 && 修改 SPU(三级联动共存),使用数字1来表示

  • 添加SKU(三级联动消失,独占右边部分),使用数字2来表示

  • 由于添加 && 修改 SPU添加SKU结构内容有点多,所以把它们打包成组件来处理

### Sku.SkuForm.index.vue
<template>
  <div>
    添加SKU
  </div>
</template>

<script>
  export default {
    name:''
  }
</script>

<style>
</style>

### Spu.SpuForm.index.vue
<template>
  <div>
    添加SPU && 修改SPU
  </div>
</template>

<script>
  export default {
    name:''
  }
</script>

<style>
</style>

### Spu.index.vue
<template>
  <div>
    <!--三级联动结构-->
    <el-card style="margin: 20px 0px;">
      ......
    </el-card>

    <!--下半部分结构(有三部分要进行切换)-->
    <el-card>
      <!--第一部分SPU列表结构-->
      <!--由scene值来决定是否展示-->
      <div v-show="scene == 0">
        ......
      </div>
	  
      <!--由scene值来决定是否展示-->
      <!--添加SPU && 修改SPU-->
      <SpuForm v-show="scene == 1"></SpuForm>
      <!--添加SKU-->
      <SkuForm v-show="scene == 2"></SkuForm>
      
    </el-card>
  </div>
</template>

<script>
  import SkuForm from './SkuForm'
  import SpuForm from './SpuForm'

  export default {
    name:'Spu',
    data(){
      return {
        ......
        scene:0 // 初始化值
      }
    },
    ....
    components:{ // 注册一下
      SkuForm,
      SpuForm
    }
  }
</script>

<style>
</style>

搞定 添加 && 修改 SPU 静态组件

### SpuForm.index.vue
<template>
  <div>
    <el-form ref="form" label-width="80px">

      <el-form-item label="SPU名称">
        <el-input placeholder="SPU名称"></el-input>
      </el-form-item>

      <el-form-item label="品牌">
        <el-select placeholder="请选择品牌" value="">
          <el-option label="label" value="value"></el-option>
        </el-select>
      </el-form-item>

      <el-form-item label="SPU描述">
        <!--element没有textarea组件,但可以利用input改造即可-->
        <el-input placeholder="描述" type="textarea" rows="4"></el-input>
      </el-form-item>

      <el-form-item label="SPU图片">
        <!--使用'照片墙'组件,可以上传多张图片(之前上传头像只能传1张)-->
        <el-upload action="https://jsonplaceholder.typicode.com/posts/" list-type="picture-card"
          :on-preview="handlePictureCardPreview" :on-remove="handleRemove">
          <i class="el-icon-plus"></i>
        </el-upload>
        <el-dialog :visible.sync="dialogVisible">
          <img width="100%" :src="dialogImageUrl" alt="">
        </el-dialog>
      </el-form-item>

      <el-form-item label="销售属性">

        <el-select placeholder="还有3未选择" value="">
          <el-option label="label" value="value"></el-option>
        </el-select>

        <el-button type="primary" icon="el-icon-plus">添加销售属性</el-button>
        <el-table style="width: 100%" border>

          <el-table-column type="index" label="序号" width="80px" align="center">
          </el-table-column>

          <el-table-column prop="prop" label="属性名" width="width">
          </el-table-column>

          <el-table-column prop="prop" label="属性值名称列表" width="width">
          </el-table-column>

          <el-table-column prop="prop" label="操作" width="width">
          </el-table-column>
        </el-table>

      </el-form-item>

      <el-form-item>
        <el-button type="primary">保存</el-button>
        <el-button>取消</el-button>
      </el-form-item>

    </el-form>
  </div>
</template>

<script>
  export default {
    name: '',
    data() {
      return {
        dialogImageUrl: '', // 上传组件相关参数
        dialogVisible: false
      }
    },
    methods: {
      handleRemove(file, fileList) { // 上传组件相关逻辑
        console.log(file, fileList);
      },
      handlePictureCardPreview(file) {
        this.dialogImageUrl = file.url;
        this.dialogVisible = true;
      }
    }
  }
</script>

<style>
</style>

SPU 业务分析

  • 本次逻辑,先从修改功能搞起(之前的逻辑都是先搞新增)
  • 当用户点击修改的时候,总共发了 4个 请求
- 品牌的数据的获取,需要发请求
- 销售属性的获取及,需要发请求
- 获取某一个SPU信息
- 获取SPU图片
  • 什么时候发送这个4个请求呢?之前的做法是,写在mounted
### SpuForm.index.vue
......
mounted(){
	console.log('我要发4个请求了')
}

- 由于是使用 v-show 进行结构的切换,当 Spu页面加载完成的时候,就会触发子组件的mounted

- 现在想实现这种效果: 当用户点击'添加SPU'按钮时,才发这4个请求
  • 先解决一个小问题,当用户点击添加SPU按钮的时候,再点击取消按钮,应该返回SPU列表展示
- 此时应该由 子组件 SpuForm 传scene值 给父组件,然后展示父组件'SPU列表展示'
  子传父 这样的逻辑,使用'自定义事件'来实现
### SpuForm.index.vue
......
<el-form-item>
    <el-button type="primary">保存</el-button>
    <!--触发自定义事件-->
    <el-button @click="$emit('changeScene',0)">取消</el-button>
</el-form-item>

### Spu.index.vue
......
<!--接收-->
<SpuForm v-show="scene == 1" @changeScene="changeScene"></SpuForm>
......
changeScene(scene){ // 接收子组件的scene值并赋值
	this.scene = scene
}
  • 实现效果: 当用户点击'添加SPU'按钮时,才发这4个请求,思路如下
- 在父组件中,给子组件添加身份标识ref
- 利用ref,在父组件书写逻辑,调用子组件的方法,并把 spu对象传给 子组件
### Spu.index.vue
......
<!--spu对象 row 已经有了,传给子组件-->
<hint-button ...... @click="updateSpu(row)">
<!--添加ref标识-->
<SpuForm v-show="scene == 1" @changeScene="changeScene" ref="spu"></SpuForm>
......
updateSpu(row){
    this.scene = 1
    <!--把row传给子组件-->
    this.$refs.spu.initSpuData(row)
},

### SpuForm.index.vue
......
initSpuData(spu){
	console.log('发请求了',spu) // 成功接收spu对象
}

配置发送四个请求,并存储数据

### api.product.spu.js
import request from '@/utils/request'
......
//获取SPU信息
///admin/product/getSpuById/{spuId}   get   
export const reqSpu = (spuId) => request({ url: `/admin/product/getSpuById/${spuId}`, method: 'get' });

//获取品牌的信息
///admin/product/baseTrademark/getTrademarkList  get
export const reqTradeMarkList = () => request({ url: `/admin/product/baseTrademark/getTrademarkList`, method: 'get' });

//获取SPU图标的接口
///admin/product/spuImageList/{spuId}  get
export const reqSpuImageList = (spuId) => request({ url: `/admin/product/spuImageList/${spuId}`, method: 'get' });

//获取平台全部销售属性----整个平台销售属性一共三个
//GET /admin/product/baseSaleAttrList  

export const reqBaseSaleAttrList = () => request({ url: '/admin/product/baseSaleAttrList', method: 'get' });


### SpuForm.index.vue
......
<script>
  export default {
    name: '',
    data() {
      return {
       ......
        // spu:{}, // SPU数据
        spu: { // 初始化数据
          //三级分类的id
          category3Id: 0,
          //描述
          description: "",
          //spu名称
          spuName: "",
          //平台的id
          tmId: 0,
          //收集SPU图片的信息
          spuImageList: [
            {
              id: 0,
              imgName: "string",
              imgUrl: "string",
              spuId: 0,
            },
          ],
          //平台属性与属性值收集
          spuSaleAttrList: [
            {
              baseSaleAttrId: 0,
              id: 0,
              saleAttrName: "string",
              spuId: 0,
              spuSaleAttrValueList: [
                {
                  baseSaleAttrId: 0,
                  id: 0,
                  isChecked: "string",
                  saleAttrName: "string",
                  saleAttrValueName: "string",
                  spuId: 0,
                },
              ],
            },
          ],
        },
        tradeMarkList:[], // 品牌数据
        spuImageList:[], // SPU图片
        saleAttrList:[] // 销售属性数据
      }
    },
    methods: {
     ......
      async initSpuData(spu){
        // 获取SPU 数据
        let resSpu = await this.$API.spu.reqSpu(spu.id)
        if(resSpu.code == 200){
          this.spu = resSpu.data
        };
        // 品牌数据
        let resTradeMarkList = await this.$API.spu.reqTradeMarkList()
        if(resTradeMarkList.code == 200){
          this.tradeMarkList = resTradeMarkList.data
        };
        // SPU图片
        let resSpuImageList = await this.$API.spu.reqSpuImageList(spu.id)
        if(resSpuImageList.code == 200){
          this.spuImageList = resSpuImageList.data
        };
        // 销售属性
        let resSaleAttrList = await this.$API.spu.reqBaseSaleAttrList()
        if(resSaleAttrList.code == 200){
          this.saleAttrList = resSaleAttrList.data
        };
      }
    },

  }
</script>
  • 渲染数据
<template>
  <div>
	<!--:model收集表单数据-->  
    <el-form ref="form" label-width="80px" :model="spu">

      <el-form-item label="SPU名称">
		<!--渲染-->  
        <el-input placeholder="SPU名称" v-model="spu.spuName"></el-input>
      </el-form-item>

      <el-form-item label="品牌">
        <el-select placeholder="请选择品牌" v-model="spu.tmId">
	      <!--渲染-->		
          <el-option v-for="(tm,index) in tradeMarkList" :key="tm.id" :label="tm.tmName" :value="tm.id"></el-option>
        </el-select>
      </el-form-item>

      <el-form-item label="SPU描述">
		<!--渲染-->  
        <el-input placeholder="描述" type="textarea" rows="4" v-model="spu.description"></el-input>
      </el-form-item>

      <el-form-item label="SPU图片">
		<!--加上':file-list',照片墙组件才会展示所有的图片-->  
        <el-upload action="/dev-api/admin/product/fileUpload" list-type="picture-card"
          :on-preview="handlePictureCardPreview" :on-remove="handleRemove" :file-list="spuImageList">
          <i class="el-icon-plus"></i>
        </el-upload>
        <el-dialog :visible.sync="dialogVisible">
          <img width="100%" :src="dialogImageUrl" alt="">
        </el-dialog>
      </el-form-item>
	  ......
    </el-form>
  </div>
</template>

<script>
  export default {
    name: '',
    data() {
      return {
        dialogImageUrl: '', // 上传组件相关参数
        dialogVisible: false,

        // spu:{}, // SPU数据
        spu: {
          //三级分类的id
          category3Id: 0,
          //描述
          description: "",
          //spu名称
          spuName: "",
          //平台的id
          tmId: 0,
          //收集SPU图片的信息
          spuImageList: [
            {
              id: 0,
              imgName: "string",
              imgUrl: "string",
              spuId: 0,
            },
          ],
          //平台属性与属性值收集
          spuSaleAttrList: [
            {
              baseSaleAttrId: 0,
              id: 0,
              saleAttrName: "string",
              spuId: 0,
              spuSaleAttrValueList: [
                {
                  baseSaleAttrId: 0,
                  id: 0,
                  isChecked: "string",
                  saleAttrName: "string",
                  saleAttrValueName: "string",
                  spuId: 0,
                },
              ],
            },
          ],
        },
        tradeMarkList:[], // 品牌数据
        spuImageList:[], // SPU图片
        saleAttrList:[] // 销售属性数据
      }
    },
    methods: {
      ......
      async initSpuData(spu){
        // 获取SPU 数据
        let resSpu = await this.$API.spu.reqSpu(spu.id)
        if(resSpu.code == 200){
          this.spu = resSpu.data
        };
        // 品牌数据
        let resTradeMarkList = await this.$API.spu.reqTradeMarkList()
        if(resTradeMarkList.code == 200){
          this.tradeMarkList = resTradeMarkList.data
        };
        // SPU图片
        let resSpuImageList = await this.$API.spu.reqSpuImageList(spu.id)
        if(resSpuImageList.code == 200){
          // this.spuImageList = resSpuImageList.data
		  // 修改服务器返回的数据名称,以便适应照片墙组件的命名要求
          let listArr = resSpuImageList.data;
          listArr.forEach(item=>{
            item.name = item.imgName;
            item.url = item.imgUrl
          })
          this.spuImageList = listArr
        };
        // 销售属性
        let resSaleAttrList = await this.$API.spu.reqBaseSaleAttrList()
        if(resSaleAttrList.code == 200){
          this.saleAttrList = resSaleAttrList.data
        };
      }
    },

  }
</script>

销售属性数据的渲染

### spuForm.index.vue
......
<!--数据源:spu.spuSaleAttrList -->
<el-table style="width: 100%" border :data="spu.spuSaleAttrList">

  <el-table-column type="index" label="序号" width="80px" align="center">
  </el-table-column>
  
  <!--渲染数据-->
  <el-table-column prop="saleAttrName" label="属性名" width="width">
  </el-table-column>

  <el-table-column prop="prop" label="属性值名称列表" width="width">
	<template slot-scope="{ row, $index }">
	  <!--涉及tag(本质就是span),input,button之间,三元素的切换-->
	  <!--@close="handleClose(tag)"-->
	  <!--渲染属性值-->
	  <el-tag :key="tag.id" v-for="tag in row.spuSaleAttrValueList" closable :disable-transitions="false">
		{{tag.saleAttrValueName}}
	  </el-tag>
	  <!--@keyup.enter.native="handleInputConfirm" @blur="handleInputConfirm"-->
	  <!--这里先假定row里面有以下值(没有指定值,那么默认就是false)-->
	  <el-input class="input-new-tag" v-if="row.inputVisible" v-model="row.inputValue" ref="saveTagInput" size="small">
	  </el-input>
	  <!--@click="showInput"-->
	  <el-button v-else class="button-new-tag" size="small" >添加</el-button>
	</template>
  </el-table-column>

  <el-table-column prop="prop" label="操作" width="width">
	<template slot-scope="{ row, $index }">
	  <!--插入按钮结构-->
	  <el-button type="danger" icon="el-icon-delete" size="mini" >删除</el-button>
	</template>
  </el-table-column>
</el-table>
......
<script>
  export default {
    name: '',
    data() {
      return {
        ......
        dynamicTags: ['标签一', '标签二', '标签三'], // 销售属性结构相关
        inputVisible: false,
        inputValue: '',

        attrId:'' // 收集未选择的销售属性id
      }
    },
    methods: {
      ......
      // 销售属性相关结构
      handleClose(tag) {
        this.dynamicTags.splice(this.dynamicTags.indexOf(tag), 1);
      },

      showInput() {
        this.inputVisible = true;
        this.$nextTick(_ => {
          this.$refs.saveTagInput.$refs.input.focus();
        });
      },

      handleInputConfirm() {
        let inputValue = this.inputValue;
        if (inputValue) {
          this.dynamicTags.push(inputValue);
        }
        this.inputVisible = false;
        this.inputValue = '';
      }
    },
    computed:{
    // 从两个数组中,分别获取每一项,然后作对比,返回符合新条件的数组
    // 这里筛选未被选择的元素
      unSelectSaleAttr(){
        var res = this.saleAttrList.filter(item=>{
          return this.spu.spuSaleAttrList.every(item1=>{
            return item.name != item1.saleAttrName
          })
        });
        return res
      }
    },

  }
</script>

收集图片数据

......
<el-form-item label="SPU图片">
    <!--增加on-success事件-->
	<el-upload action="/dev-api/admin/product/fileUpload" list-type="picture-card"
	  :on-preview="handlePictureCardPreview" :on-remove="handleRemove" :on-success="handlerSuccess" :file-list="spuImageList">
	  <i class="el-icon-plus"></i>
	</el-upload>
	<el-dialog :visible.sync="dialogVisible">
	  <img width="100%" :src="dialogImageUrl" alt="">
	</el-dialog>
</el-form-item>
......
handleRemove(file, fileList) { // 上传组件相关逻辑
	// file是被删除的文件对象
	// fileList是剩余的文件对象
	this.spuImageList = fileList
},
handlePictureCardPreview(file) {
	......
},
handlerSuccess(response,file,fileList){
	this.spuImageList = fileList // 上传成功以后的回调
},

收集销售属性数据并展示

<el-form-item label="销售属性">
	<!--v-model用来保存value传过来的值-->
	<el-select :placeholder=......" v-model="attrIdAndAttrName">
	  <!--value用来收集id和name-->	
	  <el-option ...... :value="`${unselect.id}:${unselect.name}`"></el-option>
	</el-select>
	<!--绑定点击事件,把上面收集到的数据添加到spu数据项-->
	<el-button type="primary" icon="el-icon-plus" :disabled="!attrIdAndAttrName" @click="addSaleAttr">添加销售属性</el-button>
	<el-table ......>
	  ......
	</el-table>
</el-form-item>

......
<script>
  export default {
    name: '',
    data() {
      return {
        ......
        // attrId:'' // 收集未选择的销售属性id
        attrIdAndAttrName:'' // 收集未选择的销售属性id
      }
    },
    methods: {
      ......
      addSaleAttr(){
        // 解构数据,构造新对象,然后添加到原数据项,最后清空数据,恢复原貌
        const [baseSaleAttrId,saleAttrName] = this.attrIdAndAttrName.split(':')
        var newSaleAttr = {baseSaleAttrId,saleAttrName,spuSaleAttrValueList:[]}
        this.spu.spuSaleAttrList.push(newSaleAttr)
        this.attrIdAndAttrName = ''
      }
    },
    ......

  }
</script>

添加属性值

 <el-table-column prop="prop" label="属性值名称列表" width="width">
	<template slot-scope="{ row, $index }">
	  ......
	  <!--绑定blur事件-->
	  <el-input class="input-new-tag" v-if="row.inputVisible" v-model="row.inputValue" ref="saveTagInput" size="small" @blur="handlerInputConfirm(row)">
	  </el-input>
	  <!--绑定点击事件-->
	  <el-button v-else class="button-new-tag" size="small" @click="addSaleAttrValue(row)">添加</el-button>
	</template>
</el-table-column>

.......
addSaleAttrValue(row){
	// 增加响应式属性inputVisible(负责结构的切换)和inputValue(负责收集用户输入的值)
	this.$set(row,'inputVisible',true)
	this.$set(row,'inputValue','')
},

handlerInputConfirm(row){
	
	const {baseSaleAttrId,inputValue} = row
    // 判读是否为空值
	if(inputValue.trim() == ''){
	  this.$message('属性值不能为空');
	  return;
	};
	// 判读属性值是否重复
   var res = row.spuSaleAttrValueList.some(item=>item.saleAttrValueName == inputValue)
   if(res){
	 this.$message('属性值不能重复')
	 return
   }

	// 构造数据对象,插入原有的数据源,最后切换结构
	var newSaleAttrValue = {baseSaleAttrId,saleAttrValueName:inputValue}
	row.spuSaleAttrValueList.push(newSaleAttrValue)
	row.inputVisible = false
}

删除 功能实现

......
 <el-table-column prop="prop" label="属性值名称列表" width="width">
	<template slot-scope="{ row, $index }">
	  <!--删除属性值(本质就是list元素的删除)-->
	  <el-tag :key="tag.id" v-for="(tag,index) in row.spuSaleAttrValueList" closable :disable-transitions="false" @close="row.spuSaleAttrValueList.splice(index,1)">
		{{tag.saleAttrValueName}}
	  </el-tag>
......
<el-table-column prop="prop" label="操作" width="width">
	<template slot-scope="{ row, $index }">
	  <!--删除属性-->
	  <el-button type="danger" icon="el-icon-delete" size="mini" @click="spu.spuSaleAttrList.splice($index,1)">删除</el-button>
	</template>
  </el-table-column>

小功能 实现

  • 当用户点击增加或者编辑SPU的时候,三级联动结构应该变成disabled状态
- 之前使用 v-show = scene 来控制 SPU结构 和 添加SPU&&修改SPU 和 添加SKU 的展示
- 同样的,可以利用scene值来决定"三级联动结构"是否'disabled'
### Spu.index.vue
......
<el-card style="margin: 20px 0px;">
  <!--
  	- 若scene=0,则:show=false,传过去的值就是false,三级组件可用
  	- 其他情况均为true,三级组件不可用
  -->
  <CategorySelect ...... :show="scene!=0"></CategorySelect>
</el-card>

### CategorySelect.vue
......
<!--接收show-->
:disabled="show" 

功能实现 更新后提交保存

  • 发请求
### api.product.spu.js
......
export const reqAddOrUpdateSpu = (spuInfo)=>{
  if(spuInfo.id){
    // 带id就是更新
    return request({ url: '/admin/product/updateSpuInfo', method: 'post', data: spuInfo });
  }else{
    // 不带id就是新增
    return request({ url: '/admin/product/saveSpuInfo', method: 'post', data: spuInfo });
  }
}

### spu.spuForm.index.vue
......
<el-form-item>
     <!--绑定点击事件-->
	<el-button type="primary" @click="addOrUpdateSpu">保存</el-button>
	<el-button @click="$emit('changeScene',0)">取消</el-button>
</el-form-item>
......

<!--
	- 把spu对象的数据收集完整
	- 经过检查,只有 spu.spuImageList 的数据需要作调整
		- 现有的"图片对象"数据没有问题,此时若新增'图片对象',是没有'imageName'和'imageUrl'
		- 新增'图片对象'的数据藏在response项中
		- 所以,需要把"spu.spuImageList"数据格式进行统一的处理后提交给后端
-->


async addOrUpdateSpu(){
	this.spu.spuImageList = this.spuImageList.map(item=>{
	  return {
		imageName:item.name,
		// 优先获取 新增项的数据,若没有,再获取原先的数据
		imageUrl: (item.response && item.response.data) || item.url
	  }
	});
	let res = await this.$API.spu.reqAddOrUpdateSpu(this.spu)
	if(res.code == 200){
	  this.$message({type:'success',message:'更新保存成功'})
	  // 切换回 SPU列表结构
	  this.$emit('changeScene',0)
	}
}

### Spu.index.vue
......
changeScene(scene){
	this.scene = scene;
	this.getSpuList(this.page) // 更新哪一页的数据,就跳回到哪一页
}

点击"添加SPU"后,"保存功能"的实现

  • 当用户点击该按钮的时候,触发子组件的逻辑,发两个请求
    • 获取所有品牌数据
    • 获取所有销售属性
### Spu.index.vue
......
addSpu(){
	this.scene = 1;
	// 触发子组件的逻辑
	this.$refs.spu.addSpuData(this.category3Id);
},

### SpuForm.index.vue
......
async addSpuData(category3Id){

	this.spu.category3Id = category3Id

	// 品牌数据
	let resTradeMarkList = await this.$API.spu.reqTradeMarkList()
	if (resTradeMarkList.code == 200) {
	  this.tradeMarkList = resTradeMarkList.data
	};

	// 销售属性
	let resSaleAttrList = await this.$API.spu.reqBaseSaleAttrList()
	if (resSaleAttrList.code == 200) {
	  this.saleAttrList = resSaleAttrList.data
	};
},
  • 当用户在子组件中点击保存的时候
    • 如果是更新,就跳转到数据的那一页
    • 如果是保存,就跳转回第一页
### SpuForm.index.vue
......
async addOrUpdateSpu(){
	......
	if(res.code == 200){
	  this.$message({type:'success',message:'保存成功'})
	  <!--由flag来决定跳到'首页'还是'更新页'-->
	  this.$emit('changeScene',{scene:0,flag:this.spu.id ? '修改' : '添加'})
	}
	// 清空data中的数据
	Object.assign(this._data,this.$options.data())
},

### Spu.index.vue
......
changeScene({scene,flag}){
	this.scene = scene;
	if(flag == '修改'){
	  this.getSpuList(this.page) // 跳转到更新页
	}else{
	  this.getSpuList() // 跳转到首页
	}
}
  • 当用户点击取消的时候,切换场景并清空数据
#### SpuForm.index.vue
......
<el-form-item>
	......
	<!--绑定事件-->
	<el-button @click="cancel">取消</el-button>
  </el-form-item>
......
cancel(){
	// 切换结构,至于到哪一页,不关心
	this.$emit('changeScene',{scene:0,flag:''})
	// 清空数据
	Object.assign(this._data,this.$options.data())
}

删除SPU 功能实现

  • 配置请求
### api.product.spu.js
......
export const reqDeleteSpu = (spuId)=>request({url:`/admin/product/deleteSpu/${spuId}`,method:'delete'});

  • 删除相关逻辑
### Spu.index.vue
......
<el-table-column prop="prop" label="操作" width="width">
	<template slot-scope="{ row, $index }">
	  ......
	  <!-- <hint-button type="danger" icon="el-icon-delete" size="mini" title="删除spu" @click="deleteSpu(row)"></hint-button> -->
	  <!--改造结构,由于hint-button继承自el-button,所以以下属性值都支持-->
	  <!--绑定事件-->
	  <el-popconfirm title="确定删除吗?" @onConfirm="deleteSpu(row)">
			<hint-button slot="reference" type="danger" icon="el-icon-delete" size="mini" title="删除SPU">删除</hint-button>
	  </el-popconfirm>
	 
	</template>
</el-table-column>

SKU静态组件展示

### SkuForm.index.vue

<template>
  <div>
    <el-form ref="form" label-width="80px">

      <el-form-item label="SPU名称">
        XXX
      </el-form-item>

      <el-form-item label="SKU名称">
        <el-input placeholder="SKU名称"></el-input>
      </el-form-item>

      <el-form-item label="价格(元)">
        <el-input placeholder="SKU价格"></el-input>
      </el-form-item>

      <el-form-item label="重量(千克)">
        <el-input placeholder="SKU重量"></el-input>
      </el-form-item>

     <el-form-item label="规格描述">
        <el-input placeholder="SKU规格描述" type="textarea" rows="4"></el-input>
      </el-form-item>

      <el-form-item label="平台属性">
        <el-form :inline="true" label-width="80px">
          <el-form-item label="屏幕尺寸">
            <el-select  placeholder="请选择" value=''>
              <el-option label="label" value="value"></el-option>
            </el-select>
          </el-form-item>
        </el-form>
      </el-form-item>

      <el-form-item label="销售属性">
        <el-form :inline="true" label-width="80px">
          <el-form-item label="颜色">
            <el-select  placeholder="请选择" value=''>
              <el-option label="label" value="value"></el-option>
            </el-select>
          </el-form-item>
        </el-form>
      </el-form-item>

      <el-table style="width: 100%" border >

        <el-table-column type="selection"  width="80px" prop="prop" label="label">
        </el-table-column>

        <el-table-column prop="prop" label="图片" width="width">
        </el-table-column>

        <el-table-column prop="prop" label="名称" width="width">
        </el-table-column>

        <el-table-column prop="prop" label="操作" width="width">
        </el-table-column>

      </el-table>

      <el-form-item>
        <el-button type="primary">保存</el-button>
        <el-button>取消</el-button>
      </el-form-item>

    </el-form>
  </div>
</template>

  • 发请求并存储数据: 规定必须发3个请求
### api.product.sku.js

import request from '@/utils/request'

// SPU图片
export const reqSpuImageList = (spuId)=>request({url:`/admin/product/spuImageList/${spuId}`,method:'get'})

// SPU销售属性
export const reqSpuSaleAttrList = (spuId)=>request({url:`/admin/product/spuSaleAttrList/${spuId}`,method:'get'})

// 属性
export const reqAttrInfoList = (category1Id,category2Id,category3Id)=>request({
  url:`admin/product/attrInfoList/${category1Id}/${category2Id}/${category3Id}`,
  method:'get'
})

### Spu.index.vue
......
addSku(row){
	this.scene = 2;
	// 父组件点击按钮,触发子组件逻辑
	this.$refs.sku.getData(this.category1Id,this.category2Id,row);
}

### SkuForm.index.vue
......

<script>
  export default {
    name:'',
    data(){
      return {
        spuImageList:[], // SPU图片
        spuSaleAttrList:[], // SPU销售属性数据
        attrInfoList:[] // 属性
      }
    },
    methods:{
      // 发三个请求并存储后端返回的数据
      async getData(category1Id,category2Id,spu){
        let resSpuImageList = await this.$API.sku.reqSpuImageList(spu.id)
        if(resSpuImageList.code == 200){
          this.spuImageList = resSpuImageList.data
        };

        let resSpuSaleAttrList = await this.$API.sku.reqSpuSaleAttrList(spu.id)
        if(resSpuSaleAttrList.code == 200){
          this.spuSaleAttrList = resSpuSaleAttrList.data
        };

        let resAttrInfoList = await this.$API.sku.reqAttrInfoList(category1Id,category2Id,spu.category3Id)
        if(resAttrInfoList.code == 200){
          this.attrInfoList = resAttrInfoList.data
        };
      }
    }
  }
</script>

初始化数据并渲染

### skuForm.index.vue

<template>
  <div>
    <el-form ref="form" label-width="80px">
							<!--渲染数据-->
      <el-form-item label="SPU名称">{{spu.spuName}}</el-form-item>

      <el-form-item label="SKU名称">
      	<!--收集数据-->
        <el-input placeholder="SKU名称" v-model="skuInfo.skuName"></el-input>
      </el-form-item>
	    <!--收集数据-->	
      <el-form-item label="价格(元)">
        <el-input placeholder="SKU价格" type="number" v-model="skuInfo.price"></el-input>
      </el-form-item>
	   <!--收集数据-->	
      <el-form-item label="重量(千克)">
        <el-input placeholder="SKU重量" v-model="skuInfo.weight"></el-input>
      </el-form-item>
       <!--收集数据-->
     <el-form-item label="规格描述">
        <el-input placeholder="SKU规格描述" type="textarea" rows="4" v-model="skuInfo.skuDesc"></el-input>
      </el-form-item>

      <el-form-item label="平台属性">
        <el-form :inline="true" label-width="80px">
          <!--渲染数据-->
          <el-form-item :label="attr.attrName" v-for="(attr,index) in attrInfoList" :key="attr.id">
            <!--通过value和v-model搭配,收集两个Id-->
            <el-select  placeholder="请选择" v-model="attr.attrIdAndValueId">
              <el-option :label="attrValue.valueName" :value="`${attr.id}:${attrValue.id}`" v-for="(attrValue,index) in attr.attrValueList" :key="attrValue.id"></el-option>
            </el-select>
          </el-form-item>
        </el-form>
      </el-form-item>

      <el-form-item label="销售属性">
        <el-form :inline="true" label-width="80px">
           <!--渲染数据-->
          <el-form-item :label="saleAttr.saleAttrName" v-for="(saleAttr,index) in spuSaleAttrList" :key="saleAttr.id">
            <el-select  placeholder="请选择" v-model="saleAttr.attrIdAndValueId">
              <!--通过value和v-model搭配,收集两个Id-->
              <el-option :label="saleAttrValue.saleAttrValueName" value="`${saleAttr.id}:${saleAttrValue.id}`" v-for="(saleAttrValue,index) in saleAttr.spuSaleAttrValueList"></el-option>
            </el-select>
          </el-form-item>
        </el-form>
      </el-form-item>


      <!--@selection-change当用户勾选图片的时候,会被触发-->
      <el-table style="width: 100%" border :data="spuImageList" @selection-change="handleSelectionChange">

        <el-table-column type="selection"  width="80px" prop="prop">
        </el-table-column>

        <el-table-column prop="prop" label="图片" width="width">
          <template slot-scope="{row,$index}">
            <!--渲染图片并调整图片样式-->
            <img :src="row.imgUrl" style="width: 100px;height: 100px;">
          </template>
        </el-table-column>

        <el-table-column prop="prop" label="名称" width="width">
        </el-table-column>

        <el-table-column prop="prop" label="操作" width="width">
          <template slot-scope="{row,$index}">
            <!--v-if="row.isDefault==0"-->
            <el-button type="primary"  @click="changeDefault(row)">设置默认</el-button>
            <!--v-else-->
            <el-button >默认</el-button>
          </template>
        </el-table-column>

      </el-table>

    ......

    </el-form>
  </div>
</template>

......
<script>
  export default {
    name:'',
    data(){
      return {
       ......

        //收集sku数据的字段
        skuInfo: {
          //第一类收集的数据:父组件给的数据
          category3Id: 0,
          spuId: 0,
          tmId: 0,
          //第二类:需要通过数据双向绑定v-model收集
          skuName: "",
          price: 0,
          weight: "",
          skuDesc: "",
          //第三类:需要自己书写代码
          //默认图片
          skuDefaultImg: "",
          //收集图片的字段
          skuImageList: [
            // {
            //   id: 0,
            //   imgName: "string",
            //   imgUrl: "string",
            //   isDefault: "string",
            //   skuId: 0,
            //   spuImgId: 0,
            // },
          ],
          //平台属性
          skuAttrValueList: [
            // {
            //   attrId: 0,
            //   valueId: 0,
            // },
          ],
          //销售属性
          skuSaleAttrValueList: [
            // {
            //   id: 0,
            //   saleAttrId: 0,
            //   saleAttrName: "string",
            //   saleAttrValueId: 0,
            //   saleAttrValueName: "string",
            //   skuId: 0,
            //   spuId: 0,
            // },
          ],


        },
        spu:{}, // 存储父组件传过来的spu对象
        //收集被勾选的图片数据:目前缺少isDefault字段
        imageList:[]
      }
    },
    methods:{
      async getData(category1Id,category2Id,spu){
        // 收集父组件给的数据
        this.skuInfo.category3Id = spu.category3Id
        this.skuInfo.spuId = spu.id
        this.skuInfo.tmId = spu.tmId
        this.spu = spu
        ......
      },
      handleSelectionChange(params){
        // 收集被勾选的图片对象
        this.imageList = params;
      },
      changeDefault(row){
        // 排它操作 
        this.spuImageList.forEach(item=>{
          item.isDefault = 0
        });
        row.isDefault = 1
        // 赋值
        this.skuInfo.skuDefaultImg = row.imgUrl
      }
    }
  }
</script>

  • 发请求
### api.product.sku.js
......
// 添加SKU
export const reqAddSku = (spuInfo)=>request({url:'/admin/product/saveSkuInfo',method:'post',data:spuInfo})
  • 先完成小功能取消按钮
### SkuForm.index.vue	
......
<el-form-item>
    ......
    <!--绑定事件-->
	<el-button @click="cancel">取消</el-button>
</el-form-item>
......
cancel(){
	this.$emit('changeScenes',0); // 触发父组件并传值
}

### Spu.index.vue
......
<!--接收子组件传过来的值-->
<SkuForm v-show="scene == 2" ref="sku" @changeScenes="changeScenes"></SkuForm>
......
changeScenes(scene){
    this.scene = scene // 显示父组件
    // 清空数据(若不清空,当用户再次点击'添加SKU'按钮时,就有表单就有数据存在了)
    Object.assign(this._data,this.$$options.data()) 
}
  • 添加SKU保存功能实现: 整理数据,发请求
### SkuForm.index.vue	
......
<el-form-item>
    <!--绑定事件-->
	<el-button type="primary" @click="save">保存</el-button>
	......
</el-form-item>
......
async save(){
	// 整理销售属性数据,添加到 skuInfo(写法一)
	// const {attrInfoList,skuInfo} = this;
	// let arr = [];
	// attrInfoList.forEach(item=>{
	//   if(item.attrIdAndValueId){
	//     const [attrId,valueId] = item.attrIdAndValueId.split(':');
	//     let obj = {valueId,attrId};
	//     arr.push(obj)
	//   }
	// });
	// skuInfo.skuAttrValueList = arr;

	// 整理销售属性数据,添加到 skuInfo(写法二)
	const {attrInfoList,skuInfo,spuSaleAttrList,imageList} = this;
	skuInfo.skuAttrValueList = attrInfoList.reduce((prev,item)=>{
	  if(item.attrIdAndValueId){
		const [attrId,valueId] = item.attrIdAndValueId.split(':');
		prev.push({attrId,valueId})
	  }
	  return prev
	},[]);

	// 整理销售属性
	skuInfo.skuSaleAttrValueList = spuSaleAttrList.reduce((prev,item)=>{
	  if(item.attrIdAndValueId){
		const [saleAttrId,saleAttrValueId] = item.attrIdAndValueId.split(':');
		prev.push({saleAttrId,saleAttrValueId})
	  }
	  return prev
	},[]);

	//整理图片数据
	skuInfo.skuImageList = imageList.map(item=>{
	  return {
		imgName:item.imgName,
		imgUrl:item.imgUrl,
		isDefault:item.isDefault,
		spuImgId:item.id
	  }
	});
	
	// 发请求并切换回父组件结构
	let res = await this.$API.sku.reqAddSku(skuInfo);
	if(res.code == 200){
	  this.$message({type:'success',message:'添加SKU成功'})
	  this.$emit('changeScenes',0)
	}
}

查看SKU列表 功能实现

  • 发请求
### api.product.spu.js
......
//获取SKU列表数据的接口
export const reqSkuList = (spuId)=>request({url:`/admin/product/findBySpuId/${spuId}`,method:'get'});
  • 结构的完成以及数据的渲染
    • 当用户点击查看全部SKU列表按钮的时候,弹出对话框
### Spu.index.vue
......
<!--绑定点击事件-->
<hint-button type="info" icon="el-icon-info" size="mini" title="查看当前spu的全部sku列表" @click="handler(row)"></hint-button>

......
</el-card>
    <!--SKU列表数据对话框-->
    <!--渲染数据-->
    <el-dialog :title="`${spu.spuName}的sku列表`" :visible.sync="dialogTableVisible">
      <el-table :data="skuList">
        <el-table-column property="skuName" label="名称" width="width"></el-table-column>
        <el-table-column property="price" label="价格" width="width"></el-table-column>
        <el-table-column property="weight" label="重量" width="width"></el-table-column>
        <el-table-column label="默认图片" width="width">
          <template slot-scope="{ row, $index }">
            <img :src="row.skuDefaultImg" style="width: 100px;height: 100px;">
          </template>
        </el-table-column>
      </el-table>
    </el-dialog>
......
<script>
  ......

  export default {
    name:'Spu',
    data(){
      return {
        ......
        dialogTableVisible: false ,// SKU列表数据结构展示 && 隐藏
        spu:{} ,// 存储单个spu对象(以后渲染用)
        skuList:[] // 存储sku列表数据(以后渲染用)
      }
    },
    methods:{
     ......
      async handler(spu){
        // 展示对话框,发请求并存储数据
        this.dialogTableVisible = true;
        this.spu = spu;
        let res = await this.$API.spu.reqSkuList(spu.id);
        console.log(res)
        if(res.code == 200){
          this.skuList = res.data
        }
      }
    },
    ......
  }
</script>
  • 加载效果 功能实现
- 两句即可: <el-table :data="skuList" v-loading="loading"> # 注意放对位置
- loading:true // 加载效果
......
<!--SKU列表数据对话框-->
<!--'before-close',对话框关闭之前的回调-->
<el-dialog :title="`${spu.spuName}的sku列表`" :visible.sync="dialogTableVisible" :before-close="close">
  <el-table :data="skuList" v-loading="loading">
	......
  </el-table>
</el-dialog>
......
methods:{
     ......
      async handler(spu){
      ......
        if(res.code == 200){
          this.skuList = res.data
          this.loading = false // 请求完成以后,自动关闭加载效果
        }
      },
       close(done){
        this.loading = true; // 重新激活loading效果
        this.skuList = []; // 清空数据,避免残留
        done(); // 关闭对话框
      }
    ......
  }