目录
静态页面
api接口
二次封装ui button组件
代码
?v-$attrs的用法详解与原理?
$listeners
?spuForm
spuForm静态页面
spuForm逻辑分析
完整代码
? ?spu完整代码
静态页面
<template>
<div>
<el-card>
<!-- 三级联动已经为全局组件了 -->
<componentSelect
@getShopId="getShopId"
:isShow="!isShow"
></componentSelect>
</el-card>
<el-card>
<!-- 底部将来三部分进行切换-->
<el-button type="primary" icon="el-icon-plus">添加SPU</el-button>
<el-table border :data="records">
<el-table-column
type="index"
label="序号"
width="80"
align="center"
></el-table-column>
<el-table-column prop="spuName" label="SPU名称"></el-table-column>
<el-table-column prop="description" label="SPU描述"></el-table-column>
<el-table-column label="操作">
<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>
<el-pagination
:total="total"
:current-page="page"
:page-size="limit"
:page-sizes="[3, 5, 10]"
layout="prev,pager,next,jumper,sizes,total"
@current-change="reqSpuList"
>
</el-pagination>
</el-card>
</div>
</template>
api接口
import request from '@/utils/request';
//获取SPU列表数据的接口
///admin/product/{page}/{limit} get page limit category3Id
export const reqSpuList = (page, limit, category3Id) => {
return request.get(`/admin/product/${page}/${limit}`, {
params: { category3Id }
})
}
//获取spu信息
// /admin/product/getSpuById/{spuId} get
export const getSpuById = (spuId) => {
return request.get(`/admin/product/getSpuById/${spuId}`)
}
//获取品牌的信息
// /admin/product/baseTrademark/getTrademarkList get
export const getTrademarkList = () => {
return request.get(`/admin/product/baseTrademark/getTrademarkList`)
}
//获取SPU图标的接口
// /admin/product/spuImageList/{spuId} get
export const spuImageList = (spuId) => {
return request.get(`/admin/product/spuImageList/${spuId}`)
}
//获取平台全部销售属性----整个平台销售属性一共三个
//GET /admin/product/baseSaleAttrList
export const baseSaleAttrList = () => {
return request.get(`/admin/product/baseSaleAttrList`)
}
//修改SPU||添加SPU:对于修改或者添加,携带给服务器参数大致一样的,唯一的区别就是携带的参数是否带id
// /admin/product/updateSpuInfo 修改 /admin/product/saveSpuInfo 新增
export const updateSpuInfo = (spuInfo) => {
//修改
if (spuInfo.id) {
return request.post('/admin/product/updateSpuInfo', spuInfo)
} else {
return request.post('/admin/product/saveSpuInfo', spuInfo)
}
}
//删除SPU
///admin/product/deleteSpu/{spuId}
export const deleteSpu = (spuId) => {
return request.delete(`/admin/product/deleteSpu/${spuId}`)
}
代码
<template>
<a :title="title">
<el-button v-bind="$attrs" v-on="$listeners"></el-button>
</a>
</template>
<script>
export default {
name: "HintButton",
props: ["title"],
};
</script>
<style>
a {
margin: 5px;
}
</style>
?v-$attrs的用法详解与原理?
官方解释
包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs"传入内部组件——在创建高级别的组件时非常有用。
理解
attrs 就是属性的意思即attribute , js的setAttribute ,?getAttribute 听过把,jq 的$().attr 有用过吧,他们是用来设置啥的?不就是设置类似于title, data-x, src这类的属性么,由此延伸,大概可知道vue实例里的this.$attrs是啥了。
栗子 再来,如有一个父组件是这样的:
<father :age="age" :sex="sex" title="ohNo" data-height="100"></father> 1
如上,很明显age, sex在子组件中可通过props 来接受这些值,这就完成可父组件向子组件传值,可注意,这时候props 可拿不到像title 与data-height 的值,这时候在子组件打印this.$attrs,你会发现是这样子的:
?
?这样子就可以拿到这些属性值啦,值得注意的是,class跟style不属于属性值。
-$attrs深传递
如果有这样一种情况呢,子组件可以通过this.$attrs 的拿到父组件的属性值,然后孙组件呢,如果在子组件上面没有定义属性,在孙组件里打印this.$attrs 其实是个控制,为啥?因为子组件没定义属性啊,相要在孙组件乃至更深层的后代里拿到父组件的属性值,可以在相应子组件里通过v-bind="$attrs" 即可传递父组件的属性值至下一代子组件,若要继续往下传递,相应字组件也要添加v-bind="$attrs" 如: 父组件:
<father :age="age" :sex="sex" title="ohNo" data-height="100"></father>
子组件:
<child v-bind="$attrs"></child>
这里this.$attrs是{data-height: "45", title: "ohNo"}
孙组件
<boy v-bind="attrs"></boy>
这里this.$attrs 是{data-height: "45", title: "ohNo"} ,如果子组件没有加v-bind="$attrs", 这里打印this.$attrs 为空对象
$listeners
包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on=”$listeners” 传入内部组件——在创建更高层次的组件时非常有用。
子组件需要调用父组件中的方法,可以通过?$listeners.方法名?形式调用。方法名必须在父组件中被定义,因为子组件是在父组件的模块中来使用的,通过$listeners 可以很好的用在多次传递方法
<div id="app">
A{{msg}}
<my-button :msg="msg" @todo="handleClick"></my-button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.7/vue.common.dev.js"></script>
<script>
let vm = new Vue({
el: '#app',
data: {
msg: '100'
},
methods: {
handleClick () {
console.log('点击事件')
}
},
components: {
'MyButton': {
template: `<div @click="$listeners.todo">B</div>`,
created() {
console.log(this.$listeners) // 包含父级所有绑定的方法
}
},
}
})
</script>
- input ? type=textarea 文本域 rows行高
- el-select??v-model收集选择的值,对应option的value
- ?所有el-form-item 都会继承 el-form 的label-width
?上传图片el-upload
- ? ? ? ? action:图片上传的地址 ?
- ? ? ? ? list-type 文件列表的类型
- ? ? ? ? ?on-preview:图片预览时触发
- ? ? ? ? ?on-remove:删除图片时触发
- ? ? ? ? ?file-list:照片墙展示列表
SPUFORM子组件发请求地方分析:
不能书写在mounted里面:每一次显示SpuForm子组件的时候,都会发四个请求,而我们为什么不能放在子组件的mounted里面,因为v-show只是控制SpuForm子组件显示与隐藏,这个子组件并没有卸载(只是显示或者隐藏),导致mounted只能执行一次。
我们应该父组件点击修改时使用$ref获取子组件实例方法发送请求并传入修改的id
修改和新增数据
?//存储spu信息属性
//spu属性初始化的时候,在修改spu的时候会向服务器发请求。返回spu信息(对象)在修改的时候可以利用这个对象收集新的数据提交给服务器
?//添加spu。如果是添加spu的时候并没有向服务器发请求,
计算可选销售属性
//整个平台的的销售属性为3个 颜色 尺寸 版本---baseSaleAttrList
? //当前拥有的自己的销售属性spu.spuSaleAttrList
? ?//数组过滤方法,可以从已有的数据当中过滤出用户需要的元素,并返回一个新的数组
/ ever 返回一个布尔值【真 假】 只有一个不符合就为假
computed: {
//计算出还未选择的销售属性
uneSaleAttr() {
//整个平台的的销售属性为3个 颜色 尺寸 版本---baseSaleAttrList
//当前拥有的自己的销售属性spu.spuSaleAttrList
//数组过滤方法,可以从已有的数据当中过滤出用户需要的元素,并返回一个新的数组
let result = this.baseSaleAttrList.filter((item) => {
// ever 返回一个布尔值【真 假】 只有一个不符合就为假
return this.spu.spuSaleAttrList.every((ele) => {
return item.name != ele.saleAttrName;
});
});
return result;
},
},
完成SpuForm照片墙图片的收集
- ? ? ?---预览照片墙的时候,显示大的图片的时候,需要收集数据吗? ---不需要收集的【数据已经有了】? ?
- ?---照片墙在删除图片的时候,需要收集数据的。
- ? ??---照片墙在添加图片的时候,需要收集数据的。
完成添加属性的操作
收集哪些数据? baseSaleAttrId(id)? ?saleAttrName(属性名)? ?spuSaleAttrValueList(属性值)
-----在什么时候收集数据
-----收集到哪里呀?? 把数据收集到SPU对象
element ui 的下拉框options的value使用模板字符串可以收集两个 在使用split拆分为数组
完成销售属性值展示与收集
新增的销售属性值需要收集的字段:
baseSaleAttrId
saleAttrValueName
清除数据 ? Object.assign(this._data, this.$options.data());
- ? ? ? // Object.assign es6新增的方法,可以合并对象
- ? ? ? // 组件实例 this._data 可以操作data中的响应式数据
- ? ? ? // his.$options 可以获取配置对象。配置对象的data函数执行,返回响应式数据为空的
完整代码
<template>
<div>
<!-- 所有el-form-item 都会继承 el-form 的label-width-->
<el-form label-width="80px">
<el-form-item label="spu的名称">
<el-input placeholder="spu的名称" v-model="spu.spuName"></el-input>
</el-form-item>
<el-form-item label="品牌">
<!-- v-model收集选择的值,对应option的value label展示的文字 -->
<el-select placeholder="请选择品牌" v-model="spu.tmId">
<el-option
:label="item.tmName"
v-for="item in TrademarkList"
:key="item.id"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
<!--input type=textarea 文本域 rows行高-->
<el-form-item label="spu描述">
<el-input
placeholder="spu描述"
type="textarea"
rows="4"
v-model="spu.description"
></el-input>
</el-form-item>
<!-- 上传图片
action:图片上传的地址
list-type 文件列表的类型
on-preview:图片预览时触发
on-remove:删除图片时触发
file-list:照片墙展示列表(数组 :必须有name与url字段)
-->
<el-form-item label="spu图片">
<el-upload
action="/dev-api/admin/product/fileUpload"
list-type="picture-card"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
:file-list="spuImageList"
:on-success="successChange"
>
<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="`还有未${uneSaleAttr.length}选择`"
v-model="attrid"
>
<el-option
v-for="item in uneSaleAttr"
:key="item.id"
:label="item.name"
:value="`${item.id}:${item.name}`"
></el-option>
</el-select>
<el-button
type="primary"
icon="el-icon-plus"
:disabled="!attrid"
@click="addSaleAttr"
>添加销售属性</el-button
>
<!-- 展示spu属性自己的销售属性 -->
<el-table border :data="spu.spuSaleAttrList">
<el-table-column
label="序号"
type="index"
width="80px"
align="center"
></el-table-column>
<el-table-column prop="saleAttrName" label="属性名"></el-table-column>
<el-table-column label="属性值名称列表">
<template slot-scope="{ row }">
<!-- el-tag 用于展示已有属性值的数据 -->
<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-input
class="input-new-tag"
v-if="row.inputVisible"
v-model="row.inputValue"
ref="saveTagInput"
size="small"
@keyup.enter.native="handleInputConfirm"
@blur="handleInputConfirm(row)"
>
</el-input>
<el-button
v-else
class="button-new-tag"
size="small"
@click="showInput(row)"
>添加</el-button
>
</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="{ $index }">
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
@click="spu.spuSaleAttrList.splice($index, 1)"
></el-button>
</template>
</el-table-column>
</el-table>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="updateSpuInfo">保存</el-button>
<el-button @click="cancle">取消</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
name: "spuForm",
data() {
return {
dialogImageUrl: "",
dialogVisible: false,
attrid: "", //收集未选择的属性id,
//存储spu信息属性
//spu属性初始化的时候,在修改spu的时候会向服务器发请求。返回spu信息(对象)在修改的时候可以利用这个对象收集新的数据提交给服务器
//添加spu。如果是添加spu的时候并没有向服务器发请求,
spu: {
//三级分类的id
category3Id: 0,
//描述
description: "",
//spu的名称,
spuName: "",
//品牌的id
tmId: "",
//收集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图片
baseSaleAttrList: [], //存储全部销售属性,
};
},
methods: {
//照片墙删除的时候触发
handleRemove(file, fileList) {
//file 删除的那张图片,fileList剩余的图片墙
console.log(file, fileList);
//收集照片请数据(照片墙中显示的图片有name与url字段,对于服务器而言不需要 ,最后需要处理)
this.spuImageList = fileList;
},
//照片墙预览
handlePictureCardPreview(file) {
//将图片地址赋值给这个属性
this.dialogImageUrl = file.url;
//对话框显示
this.dialogVisible = true;
},
//照片墙上传成功后
successChange(response, file, flieList) {
console.log(flieList);
//response 服务器返回的信息 file上传成功的图片 flieList所有图片的信息
//收集图片
this.spuImageList = flieList;
},
//初始化数据
initData(id) {
this.getSpuById(id);
this.getTrademark();
this.spuImage(id);
this.baseSaleAttr();
},
// 点击添加端的时候
addData(id) {
//添加spu的时候收集三级分类的id
this.spu.category3Id = id;
this.getTrademark();
this.baseSaleAttr();
},
// 获取spu信息;
async getSpuById(id) {
let { code, data } = await this.$api.spu.getSpuById(id);
if (code != 200) return;
//在修改spu的时候,需要向服务器发请求,把服务器返回的数据(对象)赋值给spu
this.spu = data;
},
//获取品牌的信息
async getTrademark() {
let { code, data } = await this.$api.spu.getTrademarkList();
if (code != 200) return;
this.TrademarkList = data;
},
//获取SPU图标的接口
async spuImage(id) {
let result = await this.$api.spu.spuImageList(id);
if (result.code == 200) {
// 由于照片墙显示的而图片数据需要数组,数组里面的元素需要name与url字段,需要把服务器返回的数据进行修改
let listArr = result.data;
listArr.forEach((item) => {
item.name = item.imgNane;
item.url = item.imgUrl;
});
this.spuImageList = listArr;
}
},
//获取平台全部销售属性----整个平台销售属性一共三个
async baseSaleAttr() {
let { code, data } = await this.$api.spu.baseSaleAttrList();
if (code != 200) return;
this.baseSaleAttrList = data;
},
//添加新的销售属性
addSaleAttr() {
//已经收集到了销售属性的信息
//把收集到的销售属性的数据进行分割 split返回数组
const [baseSaleAttrId, saleAttrName] = this.attrid.split(":");
//向spu spuSaleAttrList 添加新的销售属性
let newSaleAttr = {
baseSaleAttrId,
saleAttrName,
spuSaleAttrValueList: [],
};
// 添加新的销售属性
this.spu.spuSaleAttrList.push(newSaleAttr);
//清空数据
this.attrid = "";
},
//添加按钮的回调
showInput(row) {
//点击销售属性值中的添加按钮时 需要有button变为input 通过当前销售属性的 inputVisible 控制
// 挂载在销售属性身上的响应式数据 控制button 和input 切换
this.$set(row, "inputVisible", true);
this.$set(row, "inputValue", "");
},
// input 失去焦点的事件
handleInputConfirm(row) {
// 结构出 销售属性中收集数据
const { inputValue, baseSaleAttrId } = row;
// 新增销售属性值的名称不能为空
if (inputValue.trim() == "") {
this.$message("属性值不能为空");
return;
}
let result = row.spuSaleAttrValueList.every((item) => {
return item.saleAttrValueName != inputValue;
});
if (!result) {
this.$message("属性值不能重复");
return;
}
// 新增的销售属性值
let newSaleAttrValue = {
saleAttrValueName: inputValue,
baseSaleAttrId,
};
// 新增
row.spuSaleAttrValueList.push(newSaleAttrValue);
//修改inputVisibl 为false 展示 button
row.inputVisible = false;
},
//保存按钮的回调
async updateSpuInfo() {
// 整理数据 照片墙的数据
// 携带参数对于图片,需要携带 imageName与imageUrl 字段
this.spu.spuImageList = this.spuImageList.map((item) => {
return {
imageName: item.name,
// 且运算返回true 取后面值 或运算第一个不成立看第二个
imageUrl: (item.response && item.response.data) || item.url,
};
});
let { code } = await this.$api.spu.updateSpuInfo(this.spu);
if (code != 200) return;
this.$message({ type: "success", message: "保存成功" });
this.$emit("changeScene", {
scene: 0,
flag: this.spu.id ? "修改" : "添加",
});
//清除数据
Object.assign(this._data, this.$options.data());
},
// 取消按钮
cancle() {
//通知父亲切换常景为0
this.$emit("changeScene", {
scene: 0,
flag: "",
});
//清除数据
// Object.assign es6新增的方法,可以合并对象
// 组件实例 this._data 可以操作data中的响应式数据
// his.$options 可以获取配置对象。配置对象的data函数执行,返回响应式数据为空的
Object.assign(this._data, this.$options.data());
},
},
computed: {
//计算出还未选择的销售属性
uneSaleAttr() {
//整个平台的的销售属性为3个 颜色 尺寸 版本---baseSaleAttrList
//当前拥有的自己的销售属性spu.spuSaleAttrList
//数组过滤方法,可以从已有的数据当中过滤出用户需要的元素,并返回一个新的数组
let result = this.baseSaleAttrList.filter((item) => {
// ever 返回一个布尔值【真 假】 只有一个不符合就为假
return this.spu.spuSaleAttrList.every((ele) => {
return item.name != ele.saleAttrName;
});
});
return result;
},
},
};
</script>
<style>
.el-tag + .el-tag {
margin-left: 10px;
}
.button-new-tag {
margin-left: 10px;
height: 32px;
line-height: 30px;
padding-top: 0;
padding-bottom: 0;
}
.input-new-tag {
width: 90px;
margin-left: 10px;
vertical-align: bottom;
}
</style>
? ?spu完整代码
<template>
<div>
<el-card>
<!-- 三级联动已经为全局组件了 -->
<componentSelect
@getShopId="getShopId"
:isShow="scene != 0"
></componentSelect>
</el-card>
<el-card>
<!-- 底部将来三部分进行切换 展示spu-->
<div v-show="scene == 0">
<el-button
type="primary"
icon="el-icon-plus"
:disabled="!listId"
@click="addspu"
>添加SPU</el-button
>
<el-table border :data="records">
<el-table-column
type="index"
label="序号"
width="80"
align="center"
></el-table-column>
<el-table-column prop="spuName" label="SPU名称"></el-table-column>
<el-table-column prop="description" label="SPU描述"></el-table-column>
<el-table-column label="操作">
<template slot-scope="{ row }">
<HintButton
type="success"
icon="el-icon-plus"
size="mini"
title="添加spu"
></HintButton>
<HintButton
type="warning"
icon="el-icon-edit"
size="mini"
title="修改spu"
@click="unDateSpu(row)"
>
</HintButton>
<HintButton
type="info"
icon="el-icon-info"
size="mini"
title="查看当前spu全部的sku列表"
></HintButton>
<el-popconfirm
title="这是一段内容确定删除吗"
@onConfirm="despu(row)"
>
<HintButton
type="danger"
icon="el-icon-delete"
size="mini"
title="删除spu"
slot="reference"
></HintButton>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<el-pagination
:total="total"
:current-page="page"
:page-size="limit"
:page-sizes="[3, 5, 10]"
layout="prev,pager,next,jumper,sizes,total"
@current-change="reqSpuList"
@size-change="sizeChange"
>
</el-pagination>
</div>
<spuForm
v-show="scene == 1"
@changeScene="changeScene"
ref="spu"
></spuForm>
<skuForm v-show="scene == 2"></skuForm>
</el-card>
</div>
</template>
<script>
import api from "@/api";
import skuForm from "./skuForm.vue";
import spuForm from "./spuForm.vue";
export default {
name: "Spu",
components: {
skuForm,
spuForm,
},
data() {
return {
// 三级联动的id
listId: null,
//控制三级分类的可操作性
isShow: true,
page: 1, //分页器第几页
limit: 3, //每一页需要展示多少条数据
records: [], //spu列表数据,
total: 0, //分页器一共需要展示数据的条数
scene: 0, //0展示spu 1添加修改spu 2展示添加sku
};
},
methods: {
// 自定义事件的获取id
getShopId(listId) {
this.listId = listId;
this.reqSpuList();
},
//获取spu列表数据
async reqSpuList(page = 1) {
this.page = page;
if (this.listId) {
//携带三个参数 page第几页 limit 每一页需要展示多少条数据 三级分类id
let { getCategory3ID } = this.listId;
let { page, limit } = this;
let { data, code } = await this.$api.spu.reqSpuList(
page,
limit,
getCategory3ID
);
if (code != 200) return;
this.total = data.total;
this.records = data.records;
}
},
// 当分页器某一个展示数据条数发生变化的回调
sizeChange(limit) {
this.limit = limit;
this.reqSpuList();
},
//添加spu按钮的回调
addspu() {
this.scene = 1;
//通知子组件发请求
this.$refs.spu.addData(this.listId.getCategory3ID);
},
//修改spu
unDateSpu(row) {
this.scene = 1;
// 获取子组件使用$ref
this.$refs.spu.initData(row.id);
},
//自定义事件切换长景
changeScene({ scene, flag }) {
// flag 为了区分添加还是修改
//切换场景
this.scene = scene;
// 子组件通知父组件切换常景,需要重新拉取数据
if (flag == "修改") {
this.reqSpuList(this.page);
} else {
this.reqSpuList();
}
},
//删除spu按钮的回调
async despu(row) {
console.log(111);
let { code } = await this.$api.spu.deleteSpu(row.id);
if (code != 200) return;
this.$message("删除成功");
// spu 列表的个数大于1删除的时候停留在当前页,如果spu的个数小于1返回上一页
if (this.records.length > 1) {
this.reqSpuList(this.page);
} else {
this.reqSpuList(this.page - 1);
}
},
},
};
</script>
<style>
.el-card {
margin: 20px 0;
}
.el-pagination {
text-align: center;
}
</style>
|