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(); // 关闭对话框
}
......
}