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 提 ...
随机推荐
- java中random()函数用法介绍
Random() 创建一个新的随机数生成器. 代码如下 复制代码 Random(long seed) 使用单个 long 种子创建一个新的随机数生成器. 我们可以在构造Random对象的时候指定种子 ...
- caffe可重入单例机制分析
一个函数可重入是指该函数可以被多个线程同时调用.大多数函数都不是可重如的,因为很多函数会修改静态数据结构里的内容,如果多个线程同时调用,势必破坏共享的静态结构.可以在不改变公共接口的情况下,将一个非重 ...
- atitit 各分公司ceo cao行政经理职责.docx
1.1. 人员招募--分公司高层人员招募(每月招募四五人吧,每周一人平均) 1 1.2. 组织架构优化 1 1.3. 制度建设 健全并完善分公司内部管理机构设置,优化分公司业务管理流程: 1 1.4 ...
- CentOS 7 安装GitLab
CentOS 安装GitLab CentOS 安装GitLab GitLab是一个利用Ruby on Rails开发的开源应用程序,实现一个自托管的Git项目仓库,可通过Web界面进行访问公开的或者私 ...
- 看雪CTF第十四题
from z3 import * dest=[] s = Solver() data = [, , , , , , , , , , , , , , , , , , , , , , , , , , , ...
- [原创]Cadence Allegro小技巧之解决Out of date shapes问题
Allegro报错“Dynamic shapes are out of date; please update them. Check for out of date shapes in Setup ...
- ambari 安装HDP3.0.1后,启动服务的问题记录
HDP的ambari集成安装工具真的是比ClouderaManager差上那么一点儿,不说安装的时候就麻烦,即使软件安装包已成功安装,也不意味着可以正常使用了,启动HDP集群过程中还会有不少的错误! ...
- iOS开发之--Masonry多个平均布局
使用Masonry平均布局,代码如下: 1.创建 // 图片组数 NSArray *imgAry = @[@"home_icon01",@"home_icon02&quo ...
- How to write threats to validity?
Paper reference Threats to construct validity are concerned with the relationship between theory and ...
- Microsoft office 2019 正式版镜像下载
http://www.xitongtiandi.net/soft_yy/4373.htmlMicrosoft office 2019 正式版镜像下载 http://www.xitongtiandi.n ...