vue 之组件递归;
在开发一个 PC 端的项目时,需要开发一个树状结构,直接上效果图如下:点击 "+" 号的时候则展开下一级,点击 "-" 号的时候则收起;
之所以写这篇博客,因为在实现过程中用到了组件递归,觉得之后再遇到此种功能时能借鉴一下,treeViewItem.vue 中通过 name: "treeViewItem" 实现组件内自己调用自己,实现组件递归,从而实现对树状结构数据的渲染;
数据结构如下:
代码如下:
menusModule.js (store/module/menusModule.js)
let menus = []; let levelNum = 1; let currentComCode = ''; let startExpand = []; // 保存刷新后当前要展开的菜单项 if(localStorage.getItem('comInfoParam')){ currentComCode = JSON.parse(localStorage.getItem('comInfoParam')).key; } function setExpand(source, url) { let sourceItem = ''; for (let i = 0; i < source.length; i++) { sourceItem = JSON.stringify(source[i]); // 把菜单项转为字符串 if (sourceItem.indexOf(url) > -1) { // 查找当前 URL 所对应的子菜单属于哪一个祖先菜单 if (source[i].type === 'button') { // 导航菜单为按钮 source[i].isSelected = true; // 设置选中高亮 source[i].isExpanded = true; // 设置为展开 startExpand.push(source[i]); // 递归下一级菜单,以此类推 setExpand(source[i].subMenu, url); } break; } } } const state = { menus, levelNum, currentComCode }; const mutations = { findParents(state, payload) { if (payload.menu.ifExits === 'true') { payload.menu.isExpanded = !payload.menu.isExpanded; } else if (payload.menu.ifExits === 'false') { if (startExpand.length > 0) { for (let i = 0; i < startExpand.length; i++) { startExpand[i].isSelected = false; } } startExpand = []; // 清空展开菜单记录项 setExpand(state.menus, payload.menu.url); }; }, firstInit(state, payload) { setExpand(state.menus, payload.url); }, getList(state, payload) { state.menus = payload; }, getCurrentComCode(state,payload) { state.currentComCode = payload; } } export default { state, mutations };
index.js (store/index.js)
import Vue from 'vue' import Vuex from 'vuex' import menusModule from './module/menusModule' Vue.use(Vuex); const store = new Vuex.Store({ modules: { menusModule } }) export default store
categoryManage.vue
<template> <main class='categoryManage'> <h2>类目管理</h2> <div class="categoryContent"> <div class="operateCategory"> <button @click="addNewCategory">添加类目</button> </div> <div class="categoryTab"> <div class="categoryTabHeader"> <div class="categoryCode">类目编码</div> <div class="col-1 categoryName">类目名称</div> <div class="col-2 isDisplay">是否展示</div> <div class="col-3 categoryGrade">类目等级</div> <div class="col-4 categoryOperate">操作</div> </div> <div class="categoryTabBody"> <tree-view></tree-view> </div> <!-- 查询数据为空时 --> <div class="emptyTab" v-if='categoryList.length === 0'> <span>啊噢~~ 查询不到数据,您可以点击右上角按钮手动添加类目.</span> </div> </div> </div> <hd-diolag :is-show="isShowDiolag" @getdata="isShowDiolag=false"> <p>{{contentText}}</p> </hd-diolag> </main> </template> <script> import treeView from './treeView.vue'; import { getCategoryList } from '../../../api/commodityDefine/categoryManage.js'; export default { data() { return { categoryList:[], isShowDiolag:false, contentText:'' }; }, mounted() { this.getCategoryListFn(); }, methods: { getCategoryListFn() { let _this = this; getCategoryList().then(res => { if(res.code === 200){ _this.categoryList = res.data; this.$store.commit("getList", _this.categoryList); }else{ this.isShowDiolag = true; this.contentText = res.msg; } }) }, addNewCategory() { let _this = this; _this.$router.push({ name: 'newCategory', params: {editInfo:null,higherCategory:'',isAddNewFlag:true,categoryList:_this.categoryList} }); }, }, components: { treeView } }; </script> <style scoped> /* 操作区域 */ .categoryManage > h2 { height: 60px; line-height: 60px; } .operateCategory { display: flex; justify-content: flex-end; } .operateCategory > button { background: rgb(45, 107, 145); border: 1px solid #ddd; width: 110px; border-radius: 5px; color: #fff; font-size: 16px; display: flex; align-items: center; justify-content: center; cursor: pointer; } .operateCategory > button:hover { background: #39f; } .operateCategory > button:before { content: '+'; font-size: 30px; } /* 内容展示区域 */ .categoryTab { margin-top: 20px; } .categoryTab, .categoryTab .tabRow { font-size: 0; } .categoryTab .tabRow > div, .categoryTab .categoryTabHeader > div { display: inline-block; } .categoryTab .categoryTabHeader > div, .categoryTab .tabRow > div { display: inline-block; border-right: 1px solid #ccc; border-top: 1px solid #ccc; text-align: center; box-sizing: border-box; } .categoryTab > div.categoryTabHeader > div { font-size: 16px; } .categoryTab .tabRow > div { font-size: 14px; } .categoryTab .categoryCode{ width:17.13%; } .categoryTab .col-1 { width: 31.47%; border-left: 1px solid #ccc; position: relative; } .categoryTab > div .col-1 > span > span { cursor: pointer; position: absolute; left: 44%; font-size: 20px; top: -2px; font-weight: bold; } .categoryTab > div .col-1 > span > span:hover { color: #39f; } .categoryTab > div .col-2, .categoryTab > div .col-3, .categoryTab > div .col-4 { width: 17.13%; } .categoryTab .tabRow:last-child { border-bottom: 1px solid #ccc; } .categoryTab .categoryTabHeader { height: 32px; line-height: 32px; color: #fff; border-top-left-radius: 5px; } .categoryTab .categoryTabHeader > div { color: #fff; background: #444; } .categoryTab .tabRow > div { height: 30px; line-height: 30px; } .categoryOperate > span { cursor: pointer; } .categoryOperate > span:hover { color: #39f; } .categoryTab > div .col-1 > span > span.unfoldBtn { z-index: 10; background: #fff; text-align: center; width: 1rem; top: 0px; } /* 无数据时css start */ .emptyTab { font-size: 16px; height: 500px; display: flex; align-items: center; color: #999; justify-content: center; border: 1px solid #ccc; border-top:none; } </style>
treeView.vue
<template> <div class="tree-view-menu"> <tree-view-item :menus='menus'></tree-view-item> </div> </template> <script> import treeViewItem from "./treeViewItem"; const menusData = []; export default { components: { treeViewItem }, name: "treeViewMenu", data() { return { menus: [] }; }, mounted(){ let _this = this; setTimeout(()=>{ _this.menus = _this.$store.state.menusModule.menus; for(var i=0;i<_this.menus.length;i++){ _this.$set(_this.menus[i],'isExpanded',false); _this.$set(_this.menus[i],'isSelected',false); let childs = _this.menus[i].childs; if(childs.length){ for(var j=0;j<childs.length;j++){ _this.$set(childs[j],'isExpanded',false); _this.$set(childs[j],'isSelected',false); } } } },100) } }; </script> <style scoped> .tree-view-menu { height: 100%; overflow-y: auto; overflow-x: hidden; } .tree-view-menu::-webkit-scrollbar { height: 6px; width: 6px; } .tree-view-menu::-webkit-scrollbar-trac { -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); } .tree-view-menu::-webkit-scrollbar-thumb { background-color: #6e6e6e; outline: 1px solid #333; } .tree-view-menu::-webkit-scrollbar { height: 4px; width: 4px; } .tree-view-menu::-webkit-scrollbar-track { -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); } .tree-view-menu::-webkit-scrollbar-thumb { background-color: #6e6e6e; outline: 1px solid #708090; } </style>
treeViewItem.vue
<template> <div class="tree-view-item"> <div class="level" :class="'level-'+ menu.gradeNo" v-for="(menu,index) in menus" :key="index"> <div> <div class='itemCode'> {{menu.id}} </div> <div class="button heading name col-1" :class="{selected: menu.isSelected,expand:menu.isExpanded}" @click="toggle(menu)"> <!-- 折叠展开样式的控制 --> <span v-if='menu.childs.length && !menu.isExpanded' :class="{'unfold':menu.childs.length && !menu.isExpanded}">+</span> <span v-if='!menu.childs.length || menu.isExpanded' :class="{'fold':!menu.childs.length || menu.isExpanded}">-</span> <span>{{menu.name}}</span> </div> <div class="statusShow col-2">{{statusShowList[menu.ifShow]}}</div> <div class="gradeShow col-3">{{categoryList[menu.gradeNo]}}</div> <div class="operate col-4"> <span @click='editCategory(menu)'>编辑</span> <!-- 此版本暂不做删除功能 --> <!-- <span>|</span> <span @click='deleteCategory(menu.gradeNo,index)'>删除</span> --> </div> <transition name="fade"> <div class="heading-children" v-show="menu.isExpanded" v-if="menu.childs"> <tree-view-item :menus='menu.childs' :name='menu.name'></tree-view-item> </div> </transition> </div> </div> </div> </template> <script> export default { name: "treeViewItem", props: ["menus","name"], data() { return { categoryList:['','一级类目','二级类目','三级类目','四级类目'], statusShowList:['否','是'] } }, created() { this.$store.commit("firstInit", { url: this.$route.path }); }, methods: { toggle(menu) { this.$store.commit("findParents", { menu }); }, editCategory(editItem){ var _this = this; this.$router.push({ name:'newCategory', params:{editInfo:editItem,higherCategory:_this.name} }) }, deleteCategory(level,index){ this.menus.splice(index,1); } } }; </script> <style scoped> a { text-decoration: none; color: #333; } .button { position: relative; } .level-3:hover, .link:hover, .button:hover { color: #1976d2; background-color: #eee; cursor: pointer; } .icon { position: absolute; right: 0; display: inline-block; height: 24px; width: 24px; fill: currentColor; transition: -webkit-transform 0.15s; transition: transform 0.15s; transition: transform 0.15s, -webkit-transform 0.15s; transition-timing-function: ease-in-out; } .heading-children { padding-left: 14px; overflow: hidden; } .expand { display: block; } .collapsed { display: none; } .expand .icon { -webkit-transform: rotate(90deg); transform: rotate(90deg); } .selected { color: #1976d2; } .fade-enter-active { transition: all 0.5s ease 0s; } .fade-enter { opacity: 0; } .fade-enter-to { opacity: 1; } .fade-leave-to { height: 0; } /* 表格css start */ .tree-view-item .name>span:nth-child(1){ display: inline-block; font-size:20px; margin-top:-4px; } .tree-view-item .name>span{ vertical-align: middle; } .tree-view-item .name>span.unfold:hover, .tree-view-item .name>span.fold:hover{ color:#39f; } .tree-view-item .level{ width:100%; line-height: 24px; } .tree-view-item .level>div{ font-size:0; } .tree-view-item .heading-children{ padding-left:0; } .tree-view-item .level>div .statusShow, .tree-view-item .level>div .gradeShow, .tree-view-item .level>div .operate, .tree-view-item .level>div .name{ display: inline-block; font-size:14px; text-align: center; border-width:0 1px 1px 0; border-color:#ccc; border-style:solid; height:32px; line-height: 32px; box-sizing:border-box; } .tree-view-item .level>div .itemCode{ font-size: 14px; display: inline-block; text-align: center; color: #000; width:17.13%; box-sizing: border-box; height: 32px; line-height: 32px; border-left: 1px solid #ccc; border-bottom: 1px solid #ccc; } .tree-view-item .level>div .name{ width:31.47%; border-left:1px solid #ccc; text-align: left; } .tree-view-item .level>div .statusShow, .tree-view-item .level>div .gradeShow, .tree-view-item .level>div .operate{ width:17.13%; } .tree-view-item .level-1 .name{ padding-left:14.5%; } .tree-view-item .level-2 .name{ padding-left:15.5%; } .tree-view-item .level-3 .name{ padding-left:16.5%; } .tree-view-item .level-4 .name{ padding-left:17.5%; } .tree-view-item .operate >span{ cursor: pointer; } .tree-view-item .operate >span:hover{ color:#39f; } </style>
vue 之组件递归;的更多相关文章
- 使用Python3.7+Django2.0.4配合vue.js2.0的组件递归来实现无限级分类(递归层级结构)
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_133 所谓的无限极分类是啥?其实简单点说就是一个人类可以繁衍出多个后代,然后一个后代又可以分另外多个后代这样无限繁衍下去(可以想象 ...
- vue+element UI以组件递归方式实现多级导航菜单
介绍 这是一个是基于element-UI的导航菜单组件基础上,进行了二次封装的菜单组件,该组件以组件递归的方式,实现了可根据从后端接收到的json菜单数据,动态渲染多级菜单的功能. 使用方法 由于该组 ...
- Vue中组件的递归
先来说下需求,就是一个表单,会有树形结构一样,会有子表单,表单显示什么内容是后台通过接口数据来确定的:这个时候就和树形结构一样,肯定会有子组件的递归:这次是自己第一次写递归,遇到了三个问题记录下: 1 ...
- vue组件递归的一些理解
自己做个小项目练手,需要用到组件递归,网上查了一些资料,每个代码片段都认识,但是连起来,就一团浆糊. 既然人傻就多思考吧.不明白的点有以下: 1.组件怎么自己调用自己,函数的递归是就是在functio ...
- [转] Vue + Webpack 组件式开发(练习环境)
前言 研究了下别人的 vue 多页面框架, 都是直接复制 package.json 文件,然后在本地 npm install 一下即可, 或者使用官网 vue-cli 工具生成一个项目, 觉得这样虽然 ...
- vue.js组件(component)
简介: 组件(Component)是 Vue.js 最强大的功能之一. 组件可以扩展 HTML 元素,封装可重用的代码. 组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面 ...
- vue 自定义侧边栏 递归无限子级菜单
有很多网站会涉及到导航栏,我自己在开发中用的是element导航组件,并且自定义事件,给大家分享一下. 1.使用递归方法,无限循环子级菜单. 2.使用组件封装,维护方便. 3.使用index作为路由跳 ...
- vue+element UI递归方式实现多级导航菜单
介绍 这是一个是基于element-UI的导航菜单组件基础上,进行了二次封装的菜单组件,该组件以组件递归的方式,实现了可根据从后端接收到的json菜单数据,动态渲染多级菜单的功能. 使用方法 由于该组 ...
- 【Vue】组件的基础与组件间通信
转载:https://segmentfault.com/a/1190000016409329 Vue.js 最核心的功能就是组件(Component),从组件的构建.注册到组件间通信,Vue .x 提 ...
随机推荐
- XML格式化工具
做接口开发的时候,往往接受参数或返回值是一个XML的字符串.如下图,不方便辨识 两种方法, 1.将它保存为xxx.xml,然后用浏览器打开.这种方法稍微有些麻烦. 2.使用 UltraEdit 工具
- CAS单点登陆,URL多出个参数jsessionid导致登陆失败问题
目录: 1.定位问题 2.问题产生的原因 3.解决问题 一 定位问题 首先,如下图所示:输入到地址栏的地址被302重定向到单点登录地址,地址由Response Headers中的参数Location所 ...
- tomcat启动时非常慢,启动时 一直卡在Root WebApplicationContext: initialization completed(转)
转载地址:https://www.cnblogs.com/youxin/p/8793554.html 使用方式: JAVA_OPTS="-Djava.awt.headless=true -D ...
- centos7修改系统时间、时区
直接用下面命令直接更换时区 cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
- NaviSoft31.源码开发完成
NaviSoft是作者一人开发完成,从之前的1.0版本,到现在的3.1版本.历经2年时间,开发完成 下面是NaviSoft的源码结构 加QQ群了解更多信息
- class ObjectOutputStream也是过滤流,使节点流直接获得输出对象。
class ObjectOutputStream也是过滤流,使节点流直接获得输出对象. 最有用的方法:WriteObject(Object b) 用流传输对象称为对象的序列化,但并不使所有的对象都可以 ...
- android studio设置imageview显示图片
拖动imageview 选择图片 .png 代码引用: private Imageview pay; pay = (ImageView)findViewById(R.id.imageView2); p ...
- mysql可以远程连接的配置
由于配置好几次了,老是会忘记命令,所以记录下来 1.修改配置文件 我的配置文件是/etc/mysql/mysql.conf.d/mysqld.cnf 找到 bind-address = 127.0.0 ...
- VS F5不编译 F5总是重新编译
遇到奇怪的现象,F5不编译了 右键解决方案-配置管理器-确保项目的生成被勾选 另外一个情况,即使不修改任何代码,每次点击“生成”或者F5,都会重新编译(Debug模式没问题,Release有这个问题, ...
- A new session could not be created. (Original error: Requested a new session but one was in progress) (WARNING: The server did not provide any stacktrace information)
出现这个问题,是因为关毕了appium,或者是关毕了appium再次打开,那么session就不是一样的了所以报错. 一般是因为测试的时候报错了,appium自动关毕了,再次打出现的报错 解决方法:重 ...