平台属性管理
三级联动
1、封装为全局组件
2、使用element-ui设置静态样式
3、接口
//平台属性管理模块请求文件
import request from '@/utils/request';
//获取一级分类数据接口
///admin/product/getCategory1 get
export const reqCategory1List = ()=>request({url:'/admin/product/getCategory1',method:'get'});
//获取二级分类数据接口
//admin/product/getCategory2/{category1Id} get
export const reqCategory2List = (category1Id)=>request({url:`/admin/product/getCategory2/${category1Id}`,method:'get'});
//获取三级分类数据接口
///admin/product/getCategory3/{category2Id} get
export const reqCategory3List = (category2Id)=>request({url:`/admin/product/getCategory3/${category2Id}`,method:'get'});
4、组件挂载完毕发送请求,获取数据
//获取一级分类数据的方法
async getCategory1List() {
//获取一级分类的请求:不需要携带参数
let result = await this.$API.attr.reqCategory1List();
if (result.code == 200) {
this.list1 = result.data;
}
},
//一级分类的select事件回调(当一级分类的option发生变化的时候获取相应二级分类的数据)
async handler1() {
//清除数据
this.list2 = [];
this.list3 = [];
this.cForm.category2Id = "";
this.cForm.category3Id = "";
//解构出一级分类的id
const { category1Id } = this.cForm;
this.$emit("getCategoryId", { categoryId: category1Id, level: 1 });
//通过一级分类的id,获取二级分类的数据
let result = await this.$API.attr.reqCategory2List(category1Id);
if (result.code == 200) {
this.list2 = result.data;
}
},
//二级分类的select事件回调(当二级分类的option发生变化的时候获取相应三级分类的数据)
async handler2() {
//清除数据
this.list3 = [];
this.cForm.category3Id = "";
//结构出数据
const { category2Id } = this.cForm;
this.$emit("getCategoryId", { categoryId: category2Id, level: 2 });
let result = await this.$API.attr.reqCategory3List(category2Id);
if (result.code == 200) {
this.list3 = result.data;
}
},
//三级分类的事件回调
handler3() {
//获取三级分类的id
const { category3Id } = this.cForm;
this.$emit("getCategoryId", { categoryId: category3Id, level: 3 });
},
5、数据展示
<!-- inline:代表的是行内表单,代表一行可以放置多个表单元素 -->
<el-form :inline="true" class="demo-form-inline" :model="cForm">
<el-form-item label="一级分类">
<el-select
placeholder="请选择"
v-model="cForm.category1Id"
@change="handler1"
:disabled="show"
>
<el-option
:label="c1.name"
:value="c1.id"
v-for="(c1, index) in list1"
:key="c1.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="二级分类">
<el-select
placeholder="请选择"
v-model="cForm.category2Id"
@change="handler2"
:disabled="show"
>
<el-option
:label="c2.name"
:value="c2.id"
v-for="(c2, index) in list2"
:key="c2.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="三级分类">
<el-select
placeholder="请选择"
v-model="cForm.category3Id"
@change="handler3"
:disabled="show"
>
<el-option
:label="c3.name"
:value="c3.id"
v-for="(c3, index) in list3"
:key="c3.id"
></el-option>
</el-select>
</el-form-item>
</el-form>
逻辑1:
当获得二级分类的数据时,需要向服务器传递一级分类的id,当获得三级分类时,需要向服务器传递二级分类的id,所以此时需要使用v-model收集二级和一级id。
逻辑2:
当一级列表的选中的内容即option发生变化时,二级分类的相应数据就应该被获取,三级分类和二级分类一样。
逻辑3:
当三个级别的列表都已经选择完毕之后,再次修改一级列表内容时,二三级列表内容及id也应该被清空,当二级列表内容修改之后,三级列表内容及id也会清空。
平台属性展示
子组件通过自定义事件向父组件传递数据
this.$emit("getCategoryId", { categoryId: category1Id, level: 1 });
//自定义事件的回调
getCategoryId({ categoryId, level }) {
//区分三级分类相应的id,以及父组件进行存储
if (level == 1) {
this.category1Id = categoryId;
this.category2Id = "";
this.category3Id = "";
} else if (level == 2) {
this.category2Id = categoryId;
this.category3Id = "";
} else {
//代表三级分类的id有了
this.category3Id = categoryId;
//发请求获取平台的属性数据
this.getAttrList();
}
},
},
逻辑1:
当子组件触发生成id传递给父组件时,父组件会同时获得三个组件的id,但是父组件无法分辨是几级分类的id,所以此时子组件应该向父组件传递的是一个对象,对象中包含id加level,一二三级分类level值都不一样,此时父组件可以判断得到对象的level来判断是几级id,然后将id存储在相应的位置。
逻辑2:
当三个级别的列表都已经选择完毕之后,再次修改一级列表内容时,二三级列表id也应该被修改,当二级列表内容修改之后,三级列表id也应该清空。
根据收集的id获取数据并展示
1、接口
//获取平台属性接口
///admin/product/attrInfoList/{category1Id}/{category2Id}/{category3Id} get
export const reqAttrList = (category1Id,category2Id,category3Id)=>request({url:`/admin/product/attrInfoList/${category1Id}/${category2Id}/${category3Id}`,method:'get'});
2、发请求获取数据
//获取平台属性的数据
//当用户确定三级分类的数据时候,可以向服务器发请求获取平台属性进行展示
async getAttrList() {
//获取分类的ID
const { category1Id, category2Id, category3Id } = this;
//获取属性列表的数据
let result = await this.$API.attr.reqAttrList(
category1Id,
category2Id,
category3Id
);
if (result.code == 200) {
this.attrList = result.data;
}
3、静态
4、数据展示
<el-card style="margin: 20px 0px">
<CategorySelect @getCategoryId="getCategoryId" :show="!isShowTable"></CategorySelect>
</el-card>
<el-card>
<div v-show="isShowTable">
<el-button
type="primary"
icon="el-icon-plus"
:disabled="!category3Id"
@click="addAttr"
>添加属性</el-button
>
<!-- 表格:展示平台属性 -->
<el-table style="width: 100%" border :data="attrList">
<el-table-column type="index" label="序号" width="80" align="center">
</el-table-column>
<el-table-column prop="attrName" label="属性名称" width="150">
</el-table-column>
<el-table-column prop="prop" label="属性值列表" width="width">
<template slot-scope="{ row, $index }">
<el-tag
type="success"
v-for="(attrValue, index) in row.attrValueList"
:key="attrValue.id"
style="margin: 0px 20px"
>{{ attrValue.valueName }}</el-tag
>
</template>
</el-table-column>
<el-table-column prop="prop" label="操作" width="150">
<template slot-scope="{ row, $index }">
<el-button
type="warning"
icon="el-icon-edit"
size="mini"
@click="updateAttr(row)"
></el-button>
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
></el-button>
</template>
</el-table-column>
</el-table>
添加属性
1、让添加属性和原本展示的模块进行动态展示与隐藏
将添加属性和原本展示分成两个模块,给原本展示的模块添加一个属性,使用v-show进行展示。当点击添加按钮和编辑属性按钮隐藏。
2、添加属性静态页面
</div>
<!-- 添加属性|修改属性的结构 -->
<div v-show="!isShowTable">
<el-form :inline="true" ref="form" label-width="80px" :model="attrInfo">
<el-form-item label="属性名">
<el-input
placeholder="请输入属性名"
v-model="attrInfo.attrName"
></el-input>
</el-form-item>
</el-form>
<el-button
type="primary"
icon="el-icon-plus"
@click="addAttrValue"
:disabled="!attrInfo.attrName"
>添加属性值</el-button
>
<el-button @click="isShowTable = true">取消</el-button>
<el-table
style="width: 100%; margin: 20px 0px"
border
:data="attrInfo.attrValueList"
>
<el-table-column align="center" type="index" label="序号" width="80">
</el-table-column>
<el-table-column width="width" prop="prop" label="属性值名称">
<template slot-scope="{ row, $index }">
<!-- 这里结构需要用到span与input进行来回的切换 -->
<el-input
v-model="row.valueName"
placeholder="请输入属性值名称"
size="mini"
v-if="row.flag"
@blur="toLook(row)"
@keyup.native.enter="toLook(row)"
:ref="$index"
></el-input>
<span
v-else
@click="toEdit(row, $index)"
style="display: block"
>{{ row.valueName }}</span
>
</template>
</el-table-column>
<el-table-column width="width" prop="prop" label="操作">
<template slot-scope="{ row, $index }">
</el-table>
<el-button type="primary" @click="addOrUpdateAttr" :disabled="attrInfo.attrValueList.length<1">保存</el-button>
<el-button @click="isShowTable = true">取消</el-button>
</div>
3、收集属性名与属性值
(1)接口
//添加属性与属性值接口
///admin/product/saveAttrInfo post
export const reqAddOrUpdateAttr = (data)=>request({url:'/admin/product/saveAttrInfo',method:'post',data});
{
"attrName": "", //属性名
"attrValueList": [ //属性名中属性值,因为属性值可以是多个,因此需要的是数组
{
"attrId": 0, //属性的id
"valueName": "string" //属性值
}
],
"categoryId": 0, //category3Id
"categoryLevel":3,
}
(2)属性名使用v-model进行收集
(3)属性值收集,书写添加属性值的回调
逻辑1:
因为对象的是无序的,收集属性值时不要在data中收集,所以此时书写一个添加属性值的回调,利用数组的方式收集
逻辑2:
当点击修改属性值时,添加的属性值应该带有自己属性的id,而当点击新增属性时,新增属性是没有id的
(4)当点击取消按钮
当点击取消按钮则代表写入的数据没有用了,所以当下一次点击添加属性值的按钮时,输入框内的值应该被清空。所以此时选择在每次点击添加属性值按钮时都清空一次输入框内的数据
(5)修改属性,此时需要用到深拷贝,按需引入lodash当中的深拷贝
//按需引入lodash当中的深拷贝
import cloneDeep from "lodash/cloneDeep";
//由于数据结构当中存在对象里面套数组,数组里面有套对象,因此需要使用深拷贝解决这类问题
this.attrInfo = cloneDeep(row);
4、查看模式和修改模式切换
查看模式:显示span
编辑模式:显示input
注意:通过flag标记进行切换查看模式与编辑模式,但是需要注意的时候,一个属性flag没有办法控制全部的属性值的切换
使用信号量flag来判断为查看模式还是修改模式,但是此flag不能添加到data中,应该添加在每个属性值对象里面,这样才能修改一个属性值的模式而不对其他属性值造成影响,然后再用v-if和v-else进行展示,并且添击事件
5、输入的属性值校验,即在切换为查看模式之前先校验用户输入的数据是否正确
6、修改属性的查看模式和编辑模式
修改属性时的属性值里没有flag,所以需要给要修改的属性值添加一个flag,但是不能使用
item.flag = false
这种方式,因为这种方式添加的属性flag并不是响应式属性,此时需要用到$set。
$set:向响应式对象中添加一个property,并确保这个新的property同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新的property,因为Vue无法探测普通的新增property
this.$set(item, "flag", false);
7、表单元素自动聚焦
逻辑:
给span添加点击事件,点击之后flag变成true,即生成input,再使用ref获取input节点,当生成多个input时,然后使用index来区分是哪一个生成的input。
注意:
点击span的时候,切换为input变为编辑模式,但是需要注意,对于浏览器而言,页面重绘与重拍耗时间的,点击span的时候,我们不可能一点击span立马获取到input。
$nextTick:当节点渲染完毕了,会执行一次
8、删除属性值,不需要发请求
数据展示的是服务器返回的数据,而服务器获得的数据也是页面所收集的数据,所以此时并不需要修改。
9、当添加属性或修改属性点击保存按钮时向服务器发请求
整理参数:
1、如果用户添加很多属性值,且属性值为空的不应该提交给服务器
2、提交给服务器数据当中不应该出现flag字段
注意:当保存完数据之后,应该再次调用新的数据然后进行展示,并且isShowTable变成true。
三级联动以及保存按钮的可操作性
三级联动的可操作和不可操作由isShowTable决定,使用props将父组件的isShowTable传递给子组件
保存按钮的可操作和不可操作由属性值数组的长度,当长度小于1时不展示。
代码
平台属性管理
<template>
<div>
<el-card style="margin: 20px 0px">
<CategorySelect @getCategoryId="getCategoryId" :show="!isShowTable"></CategorySelect>
</el-card>
<el-card>
<div v-show="isShowTable">
<el-button
type="primary"
icon="el-icon-plus"
:disabled="!category3Id"
@click="addAttr"
>添加属性</el-button
>
<!-- 表格:展示平台属性 -->
<el-table style="width: 100%" border :data="attrList">
<el-table-column type="index" label="序号" width="80" align="center">
</el-table-column>
<el-table-column prop="attrName" label="属性名称" width="150">
</el-table-column>
<el-table-column prop="prop" label="属性值列表" width="width">
<template slot-scope="{ row, $index }">
<el-tag
type="success"
v-for="(attrValue, index) in row.attrValueList"
:key="attrValue.id"
style="margin: 0px 20px"
>{{ attrValue.valueName }}</el-tag
>
</template>
</el-table-column>
<el-table-column prop="prop" label="操作" width="150">
<template slot-scope="{ row, $index }">
<el-button
type="warning"
icon="el-icon-edit"
size="mini"
@click="updateAttr(row)"
></el-button>
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
></el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 添加属性|修改属性的结构 -->
<div v-show="!isShowTable">
<el-form :inline="true" ref="form" label-width="80px" :model="attrInfo">
<el-form-item label="属性名">
<el-input
placeholder="请输入属性名"
v-model="attrInfo.attrName"
></el-input>
</el-form-item>
</el-form>
<el-button
type="primary"
icon="el-icon-plus"
@click="addAttrValue"
:disabled="!attrInfo.attrName"
>添加属性值</el-button
>
<el-button @click="isShowTable = true">取消</el-button>
<el-table
style="width: 100%; margin: 20px 0px"
border
:data="attrInfo.attrValueList"
>
<el-table-column align="center" type="index" label="序号" width="80">
</el-table-column>
<el-table-column width="width" prop="prop" label="属性值名称">
<template slot-scope="{ row, $index }">
<!-- 这里结构需要用到span与input进行来回的切换 -->
<el-input
v-model="row.valueName"
placeholder="请输入属性值名称"
size="mini"
v-if="row.flag"
@blur="toLook(row)"
@keyup.native.enter="toLook(row)"
:ref="$index"
></el-input>
<span
v-else
@click="toEdit(row, $index)"
style="display: block"
>{{ row.valueName }}</span
>
</template>
</el-table-column>
<el-table-column width="width" prop="prop" label="操作">
<template slot-scope="{ row, $index }">
<!-- 气泡确认框 -->
<el-popconfirm :title="`确定删除${row.valueName}?`" @onConfirm="deleteAttrValue($index)">
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
slot="reference"
></el-button>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<el-button type="primary" @click="addOrUpdateAttr" :disabled="attrInfo.attrValueList.length<1">保存</el-button>
<el-button
@click="isShowTable = true">取消</el-button>
</div>
</el-card>
</div>
</template>
<script>
//按需引入lodash当中的深拷贝
import cloneDeep from "lodash/cloneDeep";
export default {
name: "Attr",
data() {
return {
category1Id: "",
category2Id: "",
category3Id: "",
//接受平台属性的数据
attrList: [],
//这个属性控制table表格显示与隐藏的
isShowTable: true,
//收集新增属性|修改属性使用的
attrInfo: {
attrName: "", //属性名
attrValueList: [
//属性值,因为属性值可以有多个因此用数组,每一个属性值都是一个对象需要attrId,valueName
],
categoryId: 0, //三级分类的id
categoryLevel: 3, //因为服务器也需要区分几级id
},
};
},
methods: {
//自定义事件的回调
getCategoryId({ categoryId, level }) {
//区分三级分类相应的id,以及父组件进行存储
if (level == 1) {
this.category1Id = categoryId;
this.category2Id = "";
this.category3Id = "";
} else if (level == 2) {
this.category2Id = categoryId;
this.category3Id = "";
} else {
//代表三级分类的id有了
this.category3Id = categoryId;
//发请求获取平台的属性数据
this.getAttrList();
}
},
//获取平台属性的数据
//当用户确定三级分类的数据时候,可以向服务器发请求获取平台属性进行展示
async getAttrList() {
//获取分类的ID
const { category1Id, category2Id, category3Id } = this;
//获取属性列表的数据
let result = await this.$API.attr.reqAttrList(
category1Id,
category2Id,
category3Id
);
if (result.code == 200) {
this.attrList = result.data;
}
},
//添加属性值回调
addAttrValue() {
//向属性值的数组里面添加元素
//attrId:是你相应的属性的id,目前而言我们是添加属性的操作,还没有相应的属性的id,目前而言带给服务器的id为undefined
//valueName:相应的属性值的名称
this.attrInfo.attrValueList.push({
attrId: this.attrInfo.id, //对于修改某一个属性的时候,可以在已有的属性值基础之上新增新的属性值(新增属性值的时候,需要把已有的属性的id带上)
valueName: "",
flag: true,
});
//flag属性:给每一个属性值添加一个标记flag,用户切换查看模式与编辑模式,好处,每一个属性值可以控制自己的模式切换
//当前flag属性,响应式数据(数据变化视图跟着变化)
this.$nextTick(() => {
this.$refs[this.attrInfo.attrValueList.length - 1].focus();
});
},
//添加属性按钮的回调
addAttr() {
//切换table显示与隐藏
this.isShowTable = false;
//清除数据
//收集三级分类的id
this.attrInfo = {
attrName: "", //属性名
attrValueList: [
//属性值,因为属性值可以有多个因此用数组,每一个属性值都是一个对象需要attrId,valueName
],
categoryId: this.category3Id, //三级分类的id
categoryLevel: 3, //因为服务器也需要区分几级id
};
},
//修改某一个属性
updateAttr(row) {
//isShowTable变为false
this.isShowTable = false;
//将选中的属性赋值给attrInfo
//由于数据结构当中存在对象里面套数组,数组里面有套对象,因此需要使用深拷贝解决这类问题
//深拷贝,浅拷贝在面试的时候出现频率很高,切记达到手写深拷贝与浅拷贝
this.attrInfo = cloneDeep(row);
//在修改某一个属性的时候,将相应的属性值元素添加上flag这个标记
this.attrInfo.attrValueList.forEach((item) => {
//这样书写也可以给属性值添加flag自动,但是会发现视图不会跟着变化(因为flag不是响应式数据)
//因为Vue无法探测普通的新增 property,这样书写的属性并非响应式属性(数据变化视图跟这边)
//第一个参数:对象 第二个参数:添加新的响应式属性 第三参数:新的属性的属性值
this.$set(item, "flag", false);
});
},
//失却焦点的事件---切换为查看模式,展示span
toLook(row) {
// 如果属性值为空不能作为新的属性值,需要给用户提示,让他输入一个其他的属性值
if(row.valueName.trim()==''){
this.$message('请你输入一个正常的属性值');
return;
}
//新增的属性值不能与已有的属性值重复
let isRepat = this.attrInfo.attrValueList.some(item=>{
//需要将row从数组里面判断的时候去除
//row最新新增的属性值【数组的最后一项元素】
//判断的时候,需要把已有的数组当中新增的这个属性值去除
if(row!==item){
return row.valueName==item.valueName;
}
});
if(isRepat) return;
// row:形参是当前用户添加的最新的属性值
// 当前编辑模式变为查看模式【让input消失,显示span】
row.flag = false;
},
//点击span的回调,变为编辑模式
toEdit(row, index) {
row.flag = true;
//获取input节点,实现自动聚焦
//需要注意:点击span的时候,切换为input变为编辑模式,但是需要注意,对于浏览器而言,页面重绘与重拍耗时间的
//点击span的时候,重绘重拍一个input它是需要耗费事件,因此我们不可能一点击span立马获取到input
//$nextTick,当节点渲染完毕了,会执行一次
this.$nextTick(() => {
//获取相应的input表单元素实现聚焦
this.$refs[index].focus();
});
},
//气泡确认框确定按钮的回调
//最新版本的ElementUI----2.15.6,目前模板中的版本号2.13.x
deleteAttrValue(index){
//当前删除属性值的操作是不需要发请求的
this.attrInfo.attrValueList.splice(index,1);
},
//保存按钮:进行添加属性或者修改属性的操作
async addOrUpdateAttr(){
//整理参数:1,如果用户添加很多属性值,且属性值为空的不应该提交给服务器
//提交给服务器数据当中不应该出现flag字段
this.attrInfo.attrValueList = this.attrInfo.attrValueList.filter(item=>{
//过滤掉属性值不是空的
if(item.valueName!=''){
//删除掉flag属性
delete item.flag;
return true;
}
})
try {
//发请求
await this.$API.attr.reqAddOrUpdateAttr(this.attrInfo);
//展示平台属性的信号量进行切换
this.isShowTable = true;
//提示消失
this.$message({type:'success',message:'保存成功'});
//保存成功以后需要再次获取平台属性进行展示
this.getAttrList();
} catch (error) {
// this.$message('保存失败')
}
}
},
};
</script>
三级联动组件
<template>
<div>
<!-- inline:代表的是行内表单,代表一行可以放置多个表单元素 -->
<el-form :inline="true" class="demo-form-inline" :model="cForm">
<el-form-item label="一级分类">
<el-select
placeholder="请选择"
v-model="cForm.category1Id"
@change="handler1"
:disabled="show"
>
<el-option
:label="c1.name"
:value="c1.id"
v-for="(c1, index) in list1"
:key="c1.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="二级分类">
<el-select
placeholder="请选择"
v-model="cForm.category2Id"
@change="handler2"
:disabled="show"
>
<el-option
:label="c2.name"
:value="c2.id"
v-for="(c2, index) in list2"
:key="c2.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="三级分类">
<el-select
placeholder="请选择"
v-model="cForm.category3Id"
@change="handler3"
:disabled="show"
>
<el-option
:label="c3.name"
:value="c3.id"
v-for="(c3, index) in list3"
:key="c3.id"
></el-option>
</el-select>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
name: "CategorySelect",
props: ["show"],
data() {
return {
//一级分类的数据
list1: [],
//二级分类的数据
list2: [],
//三级分类的数据
list3: [],
//收集相应的一级二级三级分类的id
cForm: {
category1Id: "",
category2Id: "",
category3Id: "",
},
};
},
//组件挂载完毕:向服务器发请求,获取相应的一级分类的数据
mounted() {
//获取一级分类的数据的方法
this.getCategory1List();
},
methods: {
//获取一级分类数据的方法
async getCategory1List() {
//获取一级分类的请求:不需要携带参数
let result = await this.$API.attr.reqCategory1List();
if (result.code == 200) {
this.list1 = result.data;
}
},
//一级分类的select事件回调(当一级分类的option发生变化的时候获取相应二级分类的数据)
async handler1() {
//清除数据
this.list2 = [];
this.list3 = [];
this.cForm.category2Id = "";
this.cForm.category3Id = "";
//解构出一级分类的id
const { category1Id } = this.cForm;
this.$emit("getCategoryId", { categoryId: category1Id, level: 1 });
//通过一级分类的id,获取二级分类的数据
let result = await this.$API.attr.reqCategory2List(category1Id);
if (result.code == 200) {
this.list2 = result.data;
}
},
//二级分类的select事件回调(当二级分类的option发生变化的时候获取相应三级分类的数据)
async handler2() {
//清除数据
this.list3 = [];
this.cForm.category3Id = "";
//结构出数据
const { category2Id } = this.cForm;
this.$emit("getCategoryId", { categoryId: category2Id, level: 2 });
let result = await this.$API.attr.reqCategory3List(category2Id);
if (result.code == 200) {
this.list3 = result.data;
}
},
//三级分类的事件回调
handler3() {
//获取三级分类的id
const { category3Id } = this.cForm;
this.$emit("getCategoryId", { categoryId: category3Id, level: 3 });
},
},
};
</script>