前言
在管理端会遇到多分类时,要求有层次展示出来,并且每个分类有额外的操作。例如:添加分类、编辑分类、删除、拖到分类等。
下面将会记录这样的一个需求实习过程。
了解需求
- 分类展示按层级展示
- 分类根据特定的参数展示可以操作的按钮,分类的操作有增、删、改
- 分类还支持拖拽功能,并不是所有的分类都支持拖拽
- 点分类去执行别的操作。例如:刷新数据(不实现)
- 增加分类之后刷新分类数据,当前选择的分类为增加的分类
- 删除分类后回到上一级分类
- 右击分类和点击操作按钮均可以弹出操作弹窗
- 点击分类前的箭头可展开和折叠分类
效果图
- 分类展示
- 分类操作的弹窗
组件库
采用ElementUI 中的 Tree树形控件、Dropdown下拉菜单
- Tree树形控件:Element - The world's most popular Vue UI framework
- Dropdown下拉菜单dropdown:Element - The world's most popular Vue UI framework
开始编码
搭建tree 组件
- html 部分:
<el-tree :data="classifyData" node-key="id" draggable ref="tree" :accordion="false"
auto-expand-parent :default-expanded-keys="[checkedId]" :props="defaultProps"
:allow-drop="allowDrop" :allow-drag="allowDrag"
@node-drag-start="handleDragStart" @node-drop="handleDrop"
@node-click="nodeClick" @node-contextmenu="rightClick"
:show-checkbox="false" :check-strictly="true" >
<div class="custom-tree-node" slot-scope="{ node, data }">
<span>{{ data.name }}</span>
<span>
<el-dropdown type="primary" trigger="click" :ref="'messageDrop'+data.id" @visible-change="controlCheckedKeys">
<span class="el-dropdown-link" @click.stop="setSeletKey(data.id)">
<img src="~@/../more-active.png" v-if="checkedKeys == data.id" class="myicon-opt" />
<img src="~@/../more.png" v-else class="myicon-opt" />
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-if="data.is_add_classify">
<div @click="openClassify(data.id,'新增子分类')">
<img src="~@/../add.png" class="myicon-opt"/>
新增子分类
</div>
</el-dropdown-item>
<el-dropdown-item v-if="data.is_edit_sort">
<div @click="editClassify(data)">
<img src="~@/../edit.png" class="myicon-opt" />
修改
</div>
</el-dropdown-item>
<el-dropdown-item v-if="data.is_edit_sort">
<div @click="delBefore(data.id,data.parent_id)">
<img src="~@/../del.png" class="myicon-opt" />
删除
</div>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</span>
</div>
</el-tree>
- css
<style lang="stylus" scoped>
.active{
background: #F2F6F9;
color: #409EFF;
}
.classify{
padding : 0 16px;
height: 40px;
font-family: PingFangSC-Medium;
font-weight: 500;
font-size: 15px;
line-height:40px;
}
.el-tree ::v-deep {
.el-tree-node__content{
@extend .classify;
&:hover{
@extend .active;
}
.el-tree-node__expand-icon.is-leaf{
// display:none
margin-left:-12px
}
}
.is-checked > .el-tree-node__content{
@extend .active;
}
}
.custom-tree-node{
display: flex;
justify-content: space-between;
width: 100%;
}
.myicon-opt{
vertical-align: middle;
width: 16px;
height: 16px;
}
</style>
- js
<script>
export default {
props:{
activeId:{
type:[String,Number],
default:''
},
classifyData:{
type:Array,
default:[]
}
},
watch:{
activeId: {
handler(v,o){
// v 值为0时, 0 == '' 值为true
if (typeof v == 'number') {
this.checkedId = v
this.$nextTick(()=>{
this.$refs.tree.setCheckedKeys([v])
})
}
},
immediate:true,
deep:true
},
},
data() {
return {
checkedId:'',
checkedKeys:'',
defaultProps: {
children: 'child',
label: 'name'
},
classifyCofig:{
flag:false,
Id: '',
title:'',
value:''
},
}
},
methods: {
// 点击分类名称
nodeClick(data,node){
this.checkedId = data.id
this.$refs.tree.setCheckedKeys([data.id])
node.expanded = true
this.$emit('selectId',data.id)
// console.log('node',data.id,node.parent)
let addId = [ data.id]
if(node.parent.parent != null) this.selectNode(addId,node.parent)
// console.log('addId',addId)
this.$emit('selectaddId', addId)
},
// 获取多层级的父类id加入到数组下标为0的位置
selectNode(id,node){
id.unshift(node.data.id)
if(node.parent.parent != null){
this.selectNode(id,node.parent)
}
},
// 右击分类
rightClick(event,data, Node, element){
setTimeout(()=>{
this.checkedKeys = data.id
this.$refs['messageDrop'+data.id].show()
})
},
// 点击操作按钮
setSeletKey(k){
setTimeout(()=>{
this.checkedKeys = k
})
},
// 下拉菜单的异步监听,打开(true)还是隐藏(flase)
controlCheckedKeys(flag){
if(!flag){
this.checkedKeys = ''
}
},
// 节点开始拖拽时触发的事件
handleDragStart(node) {
if(!node.data.is_edit_sort){
return false
}
},
// 拖拽成功完成时触发的事件
handleDrop(draggingNode, dropNode, dropType) {
if(dropType == 'none') return
// 准备排序参数可自行更改
let params = {
pk1: draggingNode.data.id,
pk2: dropNode.data.id,
direction:dropType == 'before' ? -1 : 1
}
this.orderClassify(params)
},
/**
* 拖拽时判定目标节点能否被放置。
* @param {*} draggingNode
* @param {*} dropNode
* @param {*} type 参数有三种情况:'prev'、'inner' 和 'next',分别表示放置在目标节点前、插入至目标节点和放置在目标节点后
*/
allowDrop(draggingNode, dropNode, type) {
if (draggingNode.level === dropNode.level) {
if (draggingNode.data.parent_id === dropNode.data.parent_id && dropNode.data.is_edit_sort) {
// 向上拖拽 || 向下拖拽
return type === "prev" || type === "next"
}
} else {
// 不同级进行处理
return false
}
},
//判断节点能否被拖拽
allowDrag(draggingNode) {
if(!draggingNode.data.is_edit_sort){
return false
}
return true
},
async orderClassify(params){
// 发送排序请求
},
setClassCofig(flag,id,title,value){
this.classifyCofig['flag'] = flag
this.classifyCofig['Id'] = id
this.classifyCofig['title'] = title
this.classifyCofig['value'] = value
},
openClassify(pid,txt){
this.setClassCofig(true,pid, txt ? txt : '新增分类','')
},
editClassify(row){
this.setClassCofig(true,row.id, '修改分类', row.name)
},
closeAdd(){
this.setClassCofig(false,'', '', '')
},
// 新增/修改分类
async sureClassify(params){
let {value,Id} = this.classifyCofig
// 通过value的值判断当前是新增还是修改
// 刷新分类,cid 新分类的id
let refresh = { }
if(value){
refresh.flag = false
}else{
refresh.flag = true
}
// 准备参数,发送请求
// 请求成功后执行
this.setClassCofig(false,'', '', '')
refresh.cid = value? this.checkedId : res.data.data.id
this.$emit('refreshClass',refresh)
},
//判断分类是否可以删除
async delBefore(id,pid){
//1.自定义判断是否可以删除,
//2.可以删去执行删除操作,
this.sureDelete(id,pid)
},
//删除分类,删除后回到上一级
async sureDelete(id,pid){
//1.准备删除的接口使用数据
//2.发起请求,请求成功后执行下面代码
this.setClassCofig(false,'', '', '')
let refresh = {
flag: true,
cid: pid
}
this.$emit('refreshClass',refresh)
},
}
};
</script>
使用tree组件
- html
<PersonalTree :activeId="currentClassfiyId" :classifyData="classifyData"
@selectId="changeSelectId" @selectaddId="setAddId" @refreshClass="refreshClass"/>
- js
<script>
// 在此处引入tree组件命名为customTree
export default{
components:{customTree},
data(){
return{
currentClassfiyId:'',
addClassifyId:[],
classifyData:[],
}
},
mounted(){
this.getClassList(true)
},
methods:{
async getClassList(flagScene,cid){
// console.log(flagScene,cid)
// 发送请求,获取全部分类
this.classifyData = res.data.data.classify
this.currentClassfiyId = cid || this.classifyData?.[0].id
if(flagScene){
// 可以去获取内容
}
}
},
refreshClass({flag,cid}){
// 去刷新分类列表
this.getClassList(flag,cid)
},
setAddId(val){
this.addClassifyId = val
},
changeSelectId(id){
this.currentClassfiyId = id
// 可以去获取内容
},
}
}
</script>
classifyData的数据:
[{
"id": 1033,
"name": "一级分类",
"parent_id": 0,
"level": 1,
"child": [
{
"id": 1036,
"name": "aaaaaaaaa",
"parent_id": 1033,
"level": 2,
"child": [],
"is_edit_sort": true,
"is_add_classify": true,
"is_add_scene": true
},
{
"id": 1035,
"name": "aaaaa",
"parent_id": 1033,
"level": 2,
"child": [
{
"id": 1037,
"name": "a-1",
"parent_id": 1035,
"level": 3,
"child": [
{
"id": 1040,
"name": "a-1-3",
"parent_id": 1037,
"level": 4,
"child": [],
"is_edit_sort": true,
"is_add_classify": false,
"is_add_scene": true
},
{
"id": 1038,
"name": "a-1-1",
"parent_id": 1037,
"level": 4,
"child": [],
"is_edit_sort": true,
"is_add_classify": false,
"is_add_scene": true
}
],
"is_edit_sort": true,
"is_add_classify": true,
"is_add_scene": true
}
],
"is_edit_sort": true,
"is_add_classify": true,
"is_add_scene": true
}
],
"is_edit_sort": true,
"is_add_classify": true,
"is_add_scene": true
},{
"id": 1032,
"name": "测试分类b",
"parent_id": 0,
"level": 1,
"child": [],
"is_edit_sort": true,
"is_add_classify": true,
"is_add_scene": true
},{
"id": 1015,
"name": "无操作区",
"parent_id": 0,
"level": 1,
"child": [],
"is_edit_sort": false,
"is_add_classify": false,
"is_add_scene": false
}]
如有帮到您,请收藏+关注哦!!!