后台管理项目笔记

准备

建立项目

npm install -g @vue/cli
vue create managesys--创建vue2

配置其他

router

npm install vue-router@3.5.0

router/index.js
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
const routers=[]
const router=new Router({	routes:routers})
export default router

store

npm install vuex@3.1.2 --save

store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store=new Vuex.Store({
	state:{},
	mutation:{},

})
export default store

axios

npm install axios@0.18.0 --save

api/config.js
import axios from "axios";
export function request(config) {
  const instance = axios.create({
    //https://www.fastmock.site/mock/b7da26133fab0e187542cfe12815754f/system
    baseURL:
      "https://www.fastmock.site/mock/b7da26133fab0e187542cfe12815754f/system",
    timeout: 50000,
  });
  //再次基础上可以添加请求拦截器和响应拦截器
  
  return instance(config);
}

路径别名

//src/vue.config.js  这里的webpack配置会和公共的webpack.config.js进行合并
const path = require('path');

function resolve(dir) {
    return path.join(__dirname, dir)
}

module.exports = {
    lintOnSave: false,//是否再保存的时候使用'eslint-loader进行检查  默认为true  最好修改为false
    chainWebpack: config => {
        config.resolve.alias
            .set('@', resolve('src'))
            .set('assets', resolve('src/assets'))
            .set('components', resolve('src/components'))
            .set('api', resolve('src/api'))
            .set('views', resolve('src/views'))
    }
}

element UI

npm i element-ui -S

//main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import http from '@/api/config'//axios
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.config.productionTip = false
Vue.prototype.$http=http
Vue.use(ElementUI);
new Vue({
	router,
	store,
  render: h => h(App),
}).$mount('#app')

组件

框架

elementUI-container容器实现
在这里插入图片描述

Main.vue
<template>
  <el-container style="height: 100%">
    <!-- 左侧栏 -->
    <el-aside width="auto">
      <!-- 左侧栏控件 -->
      <common-aside></common-aside>
    </el-aside>
    <!-- 右侧栏 -->
    <el-container>
      <!-- header部分 -->
      <el-header> 23 </el-header>
      <!-- 内容区域 -->
      <el-main> er </el-main>
    </el-container>
  </el-container>
</template>

<script>
import commonAside from "components/CommonAside";
export default {
  name: "mainIndex",
  components: { commonAside },
};

侧边导航栏

Menu组件
循环遍历路由,或者data数据,设置menu-item
通过name属性进行路由的跳转是最便利的

// 命名的路由,并加上参数,让路由建立 url
router.push({ name: 'user', params: { username: 'eduardo' } })

在这里插入图片描述
左侧栏和header部分对于整个后台部分都是不变的,因此里面显示的vue组件,都是Main.vue的子组件

path:'/',
		component:()=>import('views/Mainindex'),
		children:[
			{
				path:'/',
				name:'home',
				component:()=>import('views/Home/Home')
			},
			{
				path:'/mall',
				name:'mall',
				component:()=>import('views/MallManage/MallManage')
			},
				{
					path:'/other',
					component:()=>import('views/Other/index'),
					children:[
						{
							path:'/page1',
							name: "page1",
							component:()=>import('views/Other/PageOne')
						},
						{
							path:'/page2',
							name: "page2",
							component:()=>import('views/Other/PageTwo')
						}
					]
			}
		]
	},

头部栏

侧边导航栏的显示与折叠 以及个人中心
将iscollapse这个变量存储在store中

  <div style="color: aliceblue;height:60px;line-height:60px;">
	<div class="l-content" style="    float: left">
		<el-button icon="el-icon-menu" @click="collapseMenu"></el-button>
        首页
	</div>
 <div class="r-content" style="    float: right;">
	<el-dropdown trigger="click">
      <span class="el-dropdown-link">
		用户
      </span>
      <el-dropdown-menu slot="dropdown">
        <el-dropdown-item>个人中心</el-dropdown-item>
        <el-dropdown-item>退出</el-dropdown-item>
      </el-dropdown-menu>
    </el-dropdown>
 </div>
  </div>
</template>

<script>
export default {
  name: "commonHeader",
  methods:{
	collapseMenu(){
		this.$store.commit('collapseMenu')
	}
  }
};
const store=new Vuex.Store({
	state:{
		iscollapse:false//false表示侧边导航栏展开
	},
	mutations:{
		collapseMenu(state){
			state.iscollapse=!state.iscollapse
		}
	},

})

首页

结合栅格row和col进行划分
再分区进行内容填充
在这里插入图片描述

<template>
  <el-row :gutter="20" v-if="this.tableData.length">
    <el-col :span="8">
      <el-card class="box-card" style="margin-top: 10px">
        <div slot="header" class="clearfix">
          <div class="user">
            <img src="" alt="" />
            <div class="userinfo">
              <p class="name">Admin</p>
              <p class="acess">超级管理员</p>
            </div>
          </div>
        </div>
        <div class="login-info">
          <p>上次登录的时间:</p>
          <p>上次登录的地点:</p>
        </div>
      </el-card>
      <el-card class="box-card" style="margin-top: 10px; ">
        <el-table :data="tableData" style="width: 100%">
          <el-table-column prop="name" label="课程"> </el-table-column>
          <el-table-column prop="todayBuy" label="今日购买"> </el-table-column>
          <el-table-column prop="monthBuy" label="本月购买"> </el-table-column>
          <el-table-column prop="totalBuy" label="总购买"> </el-table-column>
        </el-table>
      </el-card>
    </el-col>
    <el-col :span="16">
      <div class="num">
        <el-card
          v-for="(item, index) in countData"
          :key="item.name"
          class="num-item"
          :body-style="{ display: 'flex', padding: 0 }"
        >
          <div class="item-icon" :style="'backgroundColor:' + item.color">
            <i :class="'el-icon-' + item.icon"></i>
          </div>
          <div class="item-text">
            <p style="font-size:25px">{{ item.value }}</p>
            <p>{{ item.name }}</p>
          </div>
        </el-card>
      </div>
      <el-card> </el-card>
      <el-row>
        <el-col :span="12">
          <el-card></el-card>
        </el-col>
        <el-col :span="12">
          <el-card></el-card>
        </el-col>
      </el-row>
    </el-col>
  </el-row>
</template>

<script>
import { request } from "api/config";
export default {
  name: "homeView",
  data() {
    return {
      tableData: [],
      countData: [
        {
          name: "今日支付订单",
          value: 1234,
          icon: "success",
          color: "#2ec7c9",
        },
        {
          name: "今日收藏订单",
          value: 210,
          icon: "star-on",
          color: "#ffb980",
        },
        {
          name: "今日未支付订单",
          value: 1234,
          icon: "s-goods",
          color: "#5ab1ef",
        },
        {
          name: "本月支付订单",
          value: 1234,
          icon: "success",
          color: "#2ec7c9",
        },
        {
          name: "本月收藏订单",
          value: 210,
          icon: "star-on",
          color: "#ffb980",
        },
        {
          name: "本月未支付订单",
          value: 1234,
          icon: "s-goods",
          color: "#5ab1ef",
        },
      ],
    };
  },
  mounted() {
    this.getTableData();
  },
  methods: {
    getTableData() {
      request({
        url: "/home/getData",
        methods: "get",
      }).then((res) => {
        console.log(res.data.data.tableData);
        this.tableData = res.data.data.tableData;
      });
    },
  },
};
</script>

<style scoped>
.num {
  display: flex;
  flex-wrap: wrap;
  align-content: center;
  justify-content: space-around;
}
.num-item {
  width: 290px;
  margin-top: 20px;
}
.item-icon {
  width: 40%;
  line-height: 116.9px;
}
.item-text{
	text-align: center;
    flex: 1;
}

面包屑+tab切换功能

利用Element-UI中的面包屑和tag标签

面包屑

面包屑导航的难度在于如何将路由与面包屑导航进行映射
首先面包屑首页一定要存在,接下来,侧边组建点击某个菜单,把这个数据存在vuex中,然后头部组件来获取vuex中这个数据并展示
都是利用$route的match属性来实现面包屑功能,此时需要给路由映射表中,添加meta属性,以及name属性
在这里插入图片描述
首先是能够实现渲染,二是点击能够跳转到相应的路由

router/index.js
  path: "/mall",
        name: "mall",
        meta: {
          title: "商品管理",
        },
        component: () => import("views/MallManage/MallManage"),
      },
      {
        path: "/other",
        component: () => import("views/Other/index"),
        children: [
          {
            path: "/page1",
            name: "page1",
            meta: {
              title: "页面1",
            },
CommonAside
  mounted() {
  //初始路由,存储首页
    let matched = this.$route.matched.filter((item) => item.name);
    this.$store.commit("selectMenu", matched[0]);
  },
  methods: {
    // 点击后实现路由的跳转
    clickMenu(item) {
      this.$router.push({ name: item.name });
    },
  },
  watch: {
    $route: {
		//监听路由的修改改变面包屑
      handler() {
        let matched = this.$route.matched.filter((item) => item.name);
        //以及修改面包屑
        this.$store.commit("selectMenu", matched[0]);
      },
    },
  },

再去修改vuex中的变量,结合最终显示的效果进行数组的变化

	state:{
		userInfo:{},
		iscollapse:false,//false表示侧边导航栏展开
		currentMenu:[{path:'/',title:'首页'}],
		tabsList:[{path:'/',name:'home',label:'首页',icon:'home'}]
	},
	mutations:{
		// 侧边栏是否展开
		collapseMenu(state){
			state.iscollapse=!state.iscollapse
		},
		//获取用户信息
		getuserInfo(state,payload){
			state.userInfo=payload
		},
		//选择标签,选择面包屑
		selectMenu(state,payload){
			let obj={path:payload.path,title:payload.meta.title}
			// if(payload.name!='home'&& findItem(state.currentMenu,'title',obj.title)==-1){
			// 	state.currentMenu.push(obj)
			// }
			if(payload.name!='home'){
				if(state.currentMenu.length==2){
					state.currentMenu.pop()
				}
				state.currentMenu.push(obj)
			}
			if(payload.name=='home'&&state.currentMenu.length==2){
				state.currentMenu.pop()
			}
		},
		
	},

渲染到表头

CommonHeader
      <!-- 面包屑 -->
      <el-breadcrumb separator-class="el-icon-arrow-right" style="margin-left:20px;">
        <el-breadcrumb-item v-for="(item,index) in $store.state.currentMenu" :to="item.path" :key=index>{{ item.title }}</el-breadcrumb-item>
      </el-breadcrumb>
  computed:{
	current(){
		return this.$store.state.currentMenu
	}

$route.matched

比如目前的路由是/a/aa/aaa,那么此时this.$route.matched匹配到的会是一个数组,包含’/‘,‘/a’,’/aa’,'/aaa’这四个路由信息,从而可以直接利用路由信息渲染面包屑导航

判断一个数组对象中是否含有某个对象

	let arr=[
			{num:1},
			{num:2},
			{num:3}
		]
		let obj={num:2}
		let obj2=arr[1]
		console.log(arr.includes(obj),arr.includes(obj2));//false true

因为里面的引用类型是判断引用地址,obj重新开辟了地址,因此判断为false
???目的是,存在一个obj,要判断出arr是含有obj内容的

1、“JSON.stringify”+‘indexOf’/‘include’

把原来的数组和开辟新地址的对象,转变为字符串,之后通过indexOf判断下标是否为-1

console.log(JSON.stringify(arr).includes(JSON.stringify(obj)))//true

缺点:开销大
以及如果数组或者对象里面含有函数的话,就不能使用

2、通过对象的唯一键值对进行判断
function findItem(arr,key,val) {
    for(let i = 0; i < arr.length; i++) {
        if(arr[i][key] == val) {
            return i
        }
    }
    return -1
}

			// if(payload.name!='home'&& findItem(state.currentMenu,'title',obj.title)==-1){
			// 	state.currentMenu.push(obj)
			// }
3、使用findIndex
 tabsList: [
            {
                path: '/',
                name: 'home',
                label: '首页',
                icon: 'home'
            }
        ]
 selectMenu(state, val) {
            if (val.name === 'home') {
                state.currentMenu = null
            } else {
                state.currentMenu = val
                //如果等于-1说明tabsList不存在那么插入,否则什么都不做
                let result = state.tabsList.findIndex(item => item.name === val.name)
                result === -1 ? state.tabsList.push(val) : ''

            }

tab切换

在这里插入图片描述
1、实现显示,首页tag一开始就会存在,而且是不能进行删除的
2、点击:切换到相应的路由,以及改变背景颜色,当点击左侧栏时,如果tag没有该菜单名称则新增,如果已经有了那么当前tag背景颜色加深
3、可以删除,删除当前tag,如果是最后一个,那么路由调整到他前面那个标签,并且背景变蓝,如果不是最后一个那么路由调整到他后面那个标签对应的路由
主要就是elemntUI中el-tag标签的使用

  <div style="margin-left: 10px; text-align: left; margin-top: 20px">
    <el-tag
      v-for="(tag) in $store.state.tabsList"
      :key="tag.title"
      :closable="tag.title!=='首页'"//标签可关闭属性
      @close="handleClose(tag)"
      style="margin-left: 10px"
      :effect="$route.meta.title==tag.title?'dark':'plain'"//选中当前路由,tag变色
      @click="chosetag(tag)"//点击tag进行路由跳转
    >
 {{ tag.title }}
    </el-tag>
  </div>
export default {
  data() {
    return {
      currentindex: 0,
      ischose: [false, false, false, false],
    };
  },
  computed: {
    tags() {
      return this.$store.tabsList;
    },
  },
  methods: {
    chosetag(tag) {
// 点击tag时候,切换路由
console.log(tag);
this.$router.push({path:tag.path})
    },
    handleClose(tag) {//不完整,没有考虑删除tag标签与当前路由一致的情况
      this.$store.commit("closetag", tag);
    },
  },
};

完整:

    chosetag(tag) {
      // 点击tag时候,切换路由
      this.$router.push({ path: tag.path });
    },
    handleClose(tag, index) {
      //删除当前tag,如果是最后一个,那么路由调整到他前面那个标签,并且背景变蓝,如果不是最后一个那么路由调整到他后面那个标签对应的路由
      //如果删掉这个标签与路由有关
      if (tag.path == this.$route.path) {
        //如果删掉的是最后一个那么就让他前移
        if (index == this.$store.state.tabsList.length - 1) {
          this.$router.push({
            path: this.$store.state.tabsList[index - 1].path,
          });
        } else {
          //如果删掉的是中间的tag。那么让路由后移
          this.$router.push({
            path: this.$store.state.tabsList[index + 1].path,
          });
        }
      }
      //如果删掉的这个标签与当前路由无关,那么就直接删除
      this.$store.commit("closetag", tag);
    },
  },
		closetag(state,payload){
			state.tabsList.splice(state.tabsList.indexOf(payload),1)
		}
		//选择标签,选择面包屑
		selectMenu(state,payload){
			let obj={path:payload.path,title:payload.meta.title}
			if(payload.name!='home'&& findItem(state.tabsList,'title',obj.title)==-1){//判断之前的数组对象中,是否含有某个对象
				state.tabsList.push(obj)
			}
		currentMenu:[{path:'/',title:'首页'}],
		tabsList:[{path:'/',title:'首页'}]
	},
Vue.use(Vuex)
function findItem(arr,key,val){
	for(let i=0;i<arr.length;i++){
		if(arr[i][key]==val){
			return i
		}
	}
	return -1
}

封装ECharts组件-折线图、直方图、饼图

场景:订单数量表,商品销量表,会员数量表等,以折线图、柱状图、饼状图等方式呈现
数据传入组件
数据形式:
title:标题;tooltip:提示框组件;legend:图例组件;xAxis:直角坐标轴中的x轴;yAxis:直角坐标轴中的y轴;seried:系列列表,每个系列通过type决定自己的图标类型
在这里插入图片描述
在这里插入图片描述
对数据进行处理成series需要的格式

      echartData: {
        order: {
          xData: [],
          series: [],
        },
      },
	//数据处理
	//1、取出series需要的name部分-键名
	let keyArray=Object.keys(order.data[0])//['苹果','vivo','oppo','。。。']
	//2、循环添加数据
	keyArray.forEach(key=>{
		this.echartData.order.series.push({
			name:key==='wechat'?'小程序':key,
			data:order.data.map(item=>item[key]),
			type:'line'
		})
	})
      });

在这里插入图片描述

echarts.vue
<template>
  <div style="height: 100%" ref="echart">rcharts</div>
</template>

<script>
import * as echarts from "echarts";
export default {
  props: {
    //接受父组件数据:1、chartData(series数据+x轴坐标系数据)
    //2、isAxisChart(是否有x坐标系,如果为false,则xData就为空)
    chartData: {
      type: Object,
      default() {
        return {
          xData: [],
          series: [],
        };
      },
    },
    isAxisChart: {
      type: Boolean,
      default: true,
    },
  },
  computed:{
options(){
	return this.isAxisChart?this.axisOption:this.normalOption
}
  },
  data() {
    return {
      echart: null,
      axisOption: {
        legend: { textStyle: { color: "#333" } },
        grid: { left: "20%" },
        tooltip: { trigger: "axis" },
        xAxis: {
          type: "category",
          data: [], //横坐标
          axisLine: { lineStyle: { color: "#17b3a3" } },
          axisLabel: { color: "#333" },
          boundaryGap: false,
        },
        yAxis: [
          {
            type: "value",
            axisLine: { lineStyle: { color: "#17b3a3", type: "dashed" } },
            axisLabel: { color: "#333" },
          },
        ],
        color: [
          "#2ec7c9",
          "#b6a2de",
          "#5ab1ef",
          "#ffb980",
          "#d87a80",
          "#8d98b3",
          "#e5cf0d",
          "#97b552",
          "#95706d",
          "#dc69aa",
          "#07a2a4",
          "#9a7fd1",
          "#588dd5",
          "#f5994e",
          "#c05050",
          "#59678c",
          "#c9ab00",
          "#7eb00a",
          "#6f5553",
          "#c14089",
        ],
        series: [], //要展示的数据
      },
      normalOption: {
        tooltip: { trigger: "item" },
        color: [
          "#0f78f4",
          "#dd536b",
          "#9462e5",
          "#a6a6a6",
          "#e1bb22",
          "#39c362",
          "#3ed1cf",
        ],
        series: [],
      },
    };
  },
  mounted() {
    this.initChart();
	//resize改变图标尺寸,在容器大小发生改变时需要手动调用,(因为侧边栏是可以收缩的,所以图标根据是否收缩来改变尺寸)
	window.addEventListener('resize',this.resizeChart)
  },
  watch: {
	//实际中数据是在不断变化的
    chartData: {
      handler: function () {
        this.initChart();
      },
      deep: true,
    },
  },
  methods: {
    initChart() {
      //获取处理好的数据
      this.initChartData();
      if (this.echart) {
        this.echart.setOption(this.options);
      } else {
        this.echart = echarts.init(this.$refs.echart);
        this.echart.setOption(this.options);
      }
    },
    //处理数据
    initChartData() {
      if (this.isAxisChart) {
        this.axisOption.xAxis.data = this.chartData.xData;
        this.axisOption.series = this.chartData.series;
      } else {
        //饼状图不需要横坐标
        this.normalOption.series = this.chartData.series;
      }
    },
	resizeChart(){
		this.echart?this.echart.resize():""
	}
  },
  destoryed(){
	window.removeEventListener('resize',this.resizeChart)
  }
};
</script>

<style></style>

在使用这个组件的时候,父组件只要传入必要的数据即可

     <el-card style="margin-top: 20px"
        ><Echarts
          :chartData="echartData.order"
          style="height: 280px"
          v-if="echartData.order.xData.length"
        ></Echarts>
      </el-card>
      <el-row>
        <el-col :span="12" style="margin-top: 20px">
          <el-card>
            <Echarts
              :chartData="echartData.user"
              style="height: 240px"
              v-if="echartData.user.xData.length"
            ></Echarts>
          </el-card>
        </el-col>
        <el-col :span="12" style="margin-top: 20px">
          <el-card>
            <Echarts
              :chartData="echartData.video"
              style="height: 240px"
              v-if="echartData.video.series.length"
              :isAxisChart="false"
            ></Echarts>
          </el-card>
        </el-col>
      </el-row>
    </el-col>
  </el-row>
   echartData: {
        order: {
          //折线图
          xData: [],
          series: [],
        },
        user: {
          //直方图
          xData: [],
          series: [],
        },
        //饼状图,不需要横坐标
        video: {
          series: [],
        },
      },
    };
  getTableData() {
      request({
        url: "/home/getData",
        methods: "get",
      }).then((res) => {
        this.tableData = res.data.data.tableData; //获取表格数据
        //订单折线图
        const order = res.data.data.orderData;
        this.echartData.order.xData = order.date;
        //数据处理
        //1、取出series需要的name部分-键名
        let keyArray = Object.keys(order.data[0]); //['苹果','vivo','oppo','。。。']
        //2、循环添加数据
        keyArray.forEach((key) => {
          this.echartData.order.series.push({
            name: key === "wechat" ? "小程序" : key,
            data: order.data.map((item) => item[key]),
            type: "line",
          });
        });
        //用户柱状图
        const user = res.data.data.userData;
        this.echartData.user.xData = user.map((item) => item.date);
        this.echartData.user.series.push({
          name: "新增客户",
          data: user.map((item) => item.new),
          type: "bar",
        });
        this.echartData.user.series.push({
          name: "活跃客户",
          data: user.map((item) => item.active),
          type: "bar",
          barGap: 0,
        });
        //饼状图
        const video = res.data.data.videoData;
        this.echartData.video.series.push({
          data: video,
          type: "pie",
        });
      });
    },

组件封装思路????

伪造数据,使用mock.js更加方便

npm install mockjs
main.js
import router from './router'
import store from './store'
import './mock'

用户管理

table表格组件的使用
form表单实现对表格数据的增删改查

将从后端得到的数据渲染到表格中

      tableLabel: [//表格的label标签,表头
        { prop: "name", label: "姓名" },
        { prop: "age", label: "年龄" },
        {
          prop: "sex",
          label: "性别",
        },
        { prop: "birth", label: "出生日期" },
        { prop: "addr", label: "地址" },
      ],
      tableData: [],//表格数据,从后端请求回来的数据

    <el-table :data="tableData" stripe style="width: 100%">
      <el-table-column label="序号" width="180">
        <template slot-scope="scope">
        //序号部分是写死的数据,结合element-ui文档
          <span style="margin-left: 10px">{{
            (config.page - 1) * 20 + scope.$index + 1
          }}</span>
        </template>
      </el-table-column>
      <el-table-column
        :prop="item.prop"
        :label="item.label"
        width="180"
        v-for="(item, index) in tableLabel"
        :key="item.label"
      >
      </el-table-column>
      <el-table-column label="操作">
        <template slot-scope="scope">
          <el-button size="mini" @click="handleEdit(scope.$index, scope.row)"
            >编辑</el-button
          >
          <el-button
            size="mini"
            type="danger"
            @click="handleDelete(scope.$index, scope.row)"
            >删除</el-button
          >
        </template>
      </el-table-column>
    </el-table>

同时在使用表格的时候,一般都需要加分页器

      config: { page: 1, total: 30, load: false },
    <!-- 分页 -->
    <div class="block">
      <el-pagination
        layout="prev, pager, next"
        :total="config.total"
        :page-size="20"
        @current-change="changePage"
      >
      </el-pagination>
    </div>

获取数据:表格数据、分页数据(后端实现分页)

methods: {
    getdata(name = "") {//name用来实现对表格数据的筛选
      this.config.load = true;
      //搜索时,页码需要设置为1,才能正确返回数据格式,因为数据是从第一页开始返回的
      name ? (this.config.page = 1) : "";
      request1({
        url: "/user/getUser",
        method: "get",
        params: {
          page: this.config.page,
          name,
        },
      }).then((res) => {
        this.config.total = res.data.count;
        const response = res.data;
        this.tableData = response.list;
        this.tableData = this.tableData.filter((item) => {
          //对返回的数据进行修改,不用render函数
          item.sex = item.sex == 1 ? "男" : "女";
          return item;
        });
      });
    },

实现删除数据

    //删除元素
    handleDelete(index, row) {
      //不是删除获取来的数据,要返回给后端进行删除
      this.$confirm("此操作将永久删除该文件, 是否继续?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      })
        .then(() => {
          let id = row.id; //根据唯一的id号去后端进行删除
          request1({
            url: "/user/del",
            method: "get",
            params: { id },
          }).then(() => {
            this.$message({
              type: "success",
              message: "删除成功!",
            });
            this.getdata();
          });
        })
        .catch((error) => {
          console.log(error);
          this.$message({
            type: "info",
            message: "已取消删除",
          });
        });
    },

###实现修改操作–结合form表单

    <!-- 更新用户、新增用户 form表单-->
    <el-dialog
      :title="oprateType == 'edit' ? '更新用户' : '新增用户'"
      :visible.sync="isshow"
      width="30%"
    >
      <span slot="footer" class="dialog-footer"
        ><el-form
          label-position="right"
          label-width="80px"
          :model="formLabelAlign"
          ref="form"
        >
          <el-form-item
            :label="item.label"
            v-for="(item, index) in formLable"
            :key="item.label"
          >
            <el-input
              v-model="formLabelAlign[item.model]"
              v-if="item.type == 'input'"
              :placeholder="'请输入' + item.label"
            ></el-input>

            <el-select
              v-if="item.type == 'select'"
              v-model="formLabelAlign[item.model]"
            >
              <el-option
                v-for="item in item.opts"
                :key="item.value"
                :label="item.label"
                :value="item.value"
              ></el-option>
            </el-select>
            <el-date-picker
              v-if="item.type == 'date'"
              placeholder="'选择日期'"
              v-model="formLabelAlign[item.model]"
              type="date"
              value-format="yyy-MM-dd"
            ></el-date-picker>
          </el-form-item>
        </el-form>
        <el-button @click="isshow = false">取 消</el-button>
        <el-button type="primary" @click="confirm">确 定</el-button>
      </span>
    </el-dialog>

结合表单实现添加操作–使用form

使用form表单实现筛选

  <!-- 对表格数据的操作 -->
    <div class="manage-header">
      <el-button type="primary" @click="addUser" size="small">+新增</el-button>
      <el-form :inline="true" :model="searchform" class="demo-form-inline">
        <el-form-item>
          <el-input
            v-model="searchform.keyword"
            placeholder="输入关键字"
          ></el-input>
        </el-form-item>
        <el-form-item>
          <el-button
            type="primary"
            @click="getdata(searchform.keyword)"
            size="small"
            >搜索</el-button
          >
        </el-form-item>
      </el-form>
    </div>

前端+后端。自己mock数据的使用,增删改查都是在数组上进行的,因此mock函数里面,使用数组方法:filter、map等
:进一步:对table表格以及form表单进行封装

 <div class="common-table">
        <!--stripe	是否为斑马纹  v-loading在请求数据未返回的时间有个加载的图案,提高用户体验-->
        <el-table :data="tableData" height="90%" stripe v-loading="config.loading">
            <!--第一行为序号 默认写死-->
            <el-table-column label="序号" width="85">
                <!--slot-scope="scope" 这里取到当前单元格,scope.$index就是索引 默认从0开始这里从1开始-->
                <template slot-scope="scope">
                    <span style="margin-left: 10px">{{ (config.page - 1) * 20 + scope.$index + 1 }}</span>
                </template>
            </el-table-column>
            <!--show-overflow-tooltip 当内容过长被隐藏时显示 tooltip-->
            <el-table-column show-overflow-tooltip v-for="item in tableLabel" :key="item.prop" :label="item.label" :width="item.width ? item.width : 125">
                <!--其实可以在上面:prop="item.prop"就可以显示表单数据 这里设置插槽的方式话更加灵活 我们可以写样式-->
                <template slot-scope="scope">
                    <span style="margin-left: 10px">{{ scope.row[item.prop] }}</span>
                </template>
            </el-table-column>
            <!--操作-->
            <el-table-column label="操作" min-width="180">
                <template slot-scope="scope">
                    <el-button size="min" @click="handleEdit(scope.row)">编辑</el-button>
                    <el-button size="min" type="danger" @click="handleDelete(scope.row)">删除</el-button>
                </template>
            </el-table-column>
        </el-table>
        <!--分页-->
        <el-pagination class="pager" layout="prev, pager, next" :total="config.total" :current-page.sync="config.page" @current-change="changePage" :page-size="20"></el-pagination>
    </div>
</template>

<script>
    // config分页数据,这里面至少包括当前页码 总数量
    export default {
        props: {
            tableData: Array,
            tableLabel: Array,
            config: Object
        },
        methods: {
            //更新
            handleEdit(row) {
                this.$emit('edit', row)
            },
            //删除
            handleDelete(row) {
                this.$emit('del', row)
            },
            //分页
            changePage(page) {
                this.$emit('changePage', page)
            }
        }
    }
   <!--是否行内表单-->
    <el-form :inline="inline" :model="form" ref="form" label-width="100px">
        <!--标签显示名称-->
        <el-form-item v-for="item in formLabel" :key="item.model" :label="item.label">
            <!--根据type来显示是什么标签-->
            <el-input v-model="form[item.model]" :placeholder="'请输入' + item.label" v-if="item.type==='input'"></el-input>
            <el-select v-model="form[item.model]" placeholder="请选择" v-if="item.type === 'select'">
                 <!--如果是select或者checkbox 、Radio就还需要选项信息-->
                <el-option v-for="item in item.opts" :key="item.value" :label="item.label" :value="item.value"></el-option>
            </el-select>
            <el-switch v-model="form[item.model]" v-if="item.type === 'switch'"></el-switch>
            <el-date-picker v-model="form[item.model]" type="date" placeholder="选择日期" v-if="item.type === 'date'" value-format="yyyy-MM-dd"> </el-date-picker>
        </el-form-item>
        <el-form-item><slot></slot></el-form-item>
    </el-form>
</template>

<script>
    export default {
        //inline 属性可以让表单域变为行内的表单域
        //form 表单数据 formLabel 是标签数据
        props: {
            inline: Boolean,
            form: Object,
            formLabel: Array
        }
    }
  <div class="manage">
      <el-dialog :title="operateType === 'add' ? '新增用户' : '更新用户'" :visible.sync="isShow">
          <common-form :formLabel="operateFormLabel" :form="operateForm" ref="form"></common-form>
          <div slot="footer" class="dialog-footer">

			
              <el-button @click="isShow = false">取 消</el-button>
              <el-button type="primary" @click="confirm">确 定</el-button>
          </div>
      </el-dialog>
      <div class="manage-header">
          <el-button type="primary" @click="addUser">+ 新增</el-button>
          <common-form inline :formLabel="formLabel" :form="searchFrom">
              <el-button type="primary" @click="getList(searchFrom.keyword)">搜索</el-button>
          </common-form>
      </div>
      <!--依次是: 表格数据 表格标签数据 分页数据  列表方法 更新方法 删除方法-->
      <common-table :tableData="tableData" :tableLabel="tableLabel" :config="config" @changePage="getList()" @edit="editUser" @del="delUser"></common-table>
  </div>

权限管理+登录验证+动态路由

场景:
1、登录权限验证:部分页面没有登录不允许访问
2、角色验证:在登录权限的基础上加上角色验证,比如同一个页面两种角色权限看到展示的内容模块不同

1、不同的用户会根据权限不同,在后台管理系统中渲染出不同的菜单栏
2、用户登录之后,会获取路由菜单和一个token,之后跳转的页面都需要带着token
3、用户退出登录,清除动态路由,清除token,跳转到login页面
4、如果当前没有token,那么跳转到任何页面都应该重定向到login页面

参考:
github

mock数据

模拟后端返回的数据源
一般的后台数据库中,就是分为一个user用户表,一个role权限路由表

const dynamicUser = [
  {
    name: "管理员",
    avatar:
      "https://sf3-ttcdn-tos.pstatp.com/img/user-avatar/ccb565eca95535ab2caac9f6129b8b7a~300x300.image",
    desc: "管理员 - admin",
    username: "admin",
    password: "654321",
    token: "rtVrM4PhiFK8PNopqWuSjsc1n02oKc3f",
    routes: [
      {
        path: "/",
        name: "home",
        meta: {
          title: "首页",
        },
        component: () => import("views/Home/Home"),
      },
      {
        path: "/mall",
        name: "mall",
        meta: {
          title: "商品管理",
        },
        component: () => import("views/MallManage/MallManage"),
      },
      {
        path: "/user",
        name: "user",
        meta: {
          title: "用户管理",
        },
        component: () => import("views/UserManage/UserManage"),
      },
      {
        path: "/other",
        component: () => import("views/Other/index"),
        children: [
          {
            path: "/page1",
            name: "page1",
            meta: {
              title: "页面1",
            },
            component: () => import("views/Other/PageOne"),
          },
          {
            path: "/page2",
            name: "page2",
            meta: {
              title: "页面2",
            },
            component: () => import("views/Other/PageTwo"),
          },
        ],
      },
    ],
  },
  {
    name: "普通用户",
    avatar:
      "https://sf1-ttcdn-tos.pstatp.com/img/user-avatar/6364348965908f03e6a2dd188816e927~300x300.image",
    desc: "普通用户 - people",
    username: "people",
    password: "123456",
    token: "4es8eyDwznXrCX3b3439EmTFnIkrBYWh",
    routes: [
      {
        path: "/",
        name: "home",
        meta: {
          title: "首页",
        },
        component: () => import("views/Home/Home"),
      },
      {
        path: "/mall",
        name: "mall",
        meta: {
          title: "商品管理",
        },
        component: () => import("views/MallManage/MallManage"),
      },
    ],
  },
];

export default {
  getdynamicUser: (config) => {
    return {
      code: 200,
      message: "登录成功",
    };
  },
};

可以看出,一般登录之后,返回的数据里面,包含了一个用户的姓名、头像以及token。

处理方法:所有的路由权限等,都交给后端,后端根据前端的账号密码,去获取角色权限,处理路由,返回的就是已经匹配对应角色的路由

const dynamicUser = [
  {
    name: "管理员",
    avatar:
      "https://sf3-ttcdn-tos.pstatp.com/img/user-avatar/ccb565eca95535ab2caac9f6129b8b7a~300x300.image",
    desc: "管理员 - admin",
    username: "admin",
    // password: "654321",
    token: "rtVrM4PhiFK8PNopqWuSjsc1n02oKc3f",
    routes: [
      {
        path: "/",
        name: "home",
        meta: {
          title: "首页",
        },
        component: () => import("views/Home/Home"),
      },
      {
        path: "/mall",
        name: "mall",
        meta: {
          title: "商品管理",
        },
        component: () => import("views/MallManage/MallManage"),
      },
      {
        path: "/user",
        name: "user",
        meta: {
          title: "用户管理",
        },
        component: () => import("views/UserManage/UserManage"),
      },
      {
        path: "/other",
        component: () => import("views/Other/index"),
        children: [
          {
            path: "/page1",
            name: "page1",
            meta: {
              title: "页面1",
            },
            component: () => import("views/Other/PageOne"),
          },
          {
            path: "/page2",
            name: "page2",
            meta: {
              title: "页面2",
            },
            component: () => import("views/Other/PageTwo"),
          },
        ],
      },
    ],
  },
  {
    name: "普通用户",
    avatar:
      "https://sf1-ttcdn-tos.pstatp.com/img/user-avatar/6364348965908f03e6a2dd188816e927~300x300.image",
    desc: "普通用户 - people",
    username: "people",
    // password: "123456",
    token: "4es8eyDwznXrCX3b3439EmTFnIkrBYWh",
    routes: [
      {
        path: "/",
        name: "home",
        meta: {
          title: "首页",
        },
        component: () => import("views/Home/Home"),
      },
      {
        path: "/mall",
        name: "mall",
        meta: {
          title: "商品管理",
        },
        component: () => import("views/MallManage/MallManage"),
      },
    ],
  },
];

export default {
  getdynamicUser: (config) => {
    const { username, password } = JSON.parse(config.body);
    console.log(JSON.parse(config.body));
    //先判断用户是否存在
    if (username === "admin" || username === "people") {
      //判断账号和密码是否对象
      if (username === "admin" && password === "654321") {
        return {
          code: 200,
          message: "登录成功",
          data: dynamicUser.filter((item) => {
            item.username === username;
          }),
        };
      } else if (username === "peopel" && password === "123456") {
        return {
          code: 200,
          message: "登录成功",
          data: dynamicUser.filter((item) => {
            item.username === username;
          }),
        };
      }
    } else {
      return { code: 404, data: { message: "用户不存在" } };
    }
  },
};

password一般是后端不可能带出来的,routes就是管理员和普通用户的差异化动态路由
在写差异化动态路控制不同用户访问不同的页面,还可以用另外一种思路实现:把完整的静态路由都写在前端router中,然后根据router的meta属性,协商对于user的role,登录的时候,在根据后端返回的权限,去过滤对比权限,把该用户级角色所对应的路由处理好,缺点:想要修改就需要重新打包处理,而且不能经过后台动态新增删除

//代码位置:router/index.js
 {
    path: '',
    component: layout, //整体页面的布局(包含左侧菜单跟主内容区域)
    children: [{
      path: 'main',
      component: main,
      meta: {
        title: '首页', //菜单名称
        roles: ['user', 'admin'], //当前菜单哪些角色可以看到
      }
    }]
  }

登录页

表单处理

<template>
  <el-form
    :model="dynamicValidateForm"
    ref="dynamicValidateForm"
    label-width="100px"
    class="demo-dynamic"
  >
    <h3>用户登录</h3>
    <el-form-item
      prop="username"
      label="用户名"
      :rules="[
        { required: true, message: '请输入用户名', trigger: 'blur' },
        { min: 3, message: '用户名长度不能小于3', trigger: ['blur'] },
      ]"
    >
      <el-input v-model="dynamicValidateForm.username"></el-input>
    </el-form-item>
    <el-form-item
      prop="password"
      label="密码"
      :rules="[{ required: true, message: '请输入密码', trigger: 'blur' }]"
    >
      <el-input v-model="dynamicValidateForm.password"></el-input>
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="submitForm('dynamicValidateForm')"
        >登录</el-button
      >
    </el-form-item>
  </el-form>
</template>

<script>
import { request1 } from "api/config";
export default {
  name: "loginView",
  data() {
    return {
      dynamicValidateForm: {
        password: "",
        username: "",
      },
    };
  },
  methods: {
    submitForm(formName) {
      this.$refs[formName].validate((valid) => {
        console.log(this.dynamicValidateForm);
        if (valid) {
          request1({
            url: "/permission/getUser",
            method: "post",
            data: this.dynamicValidateForm,
          }).then((res) => {
            if (res.data.code == 404) {
              this.$message(res.data.data.message);
            }
            if (res.data.code == 200) {
				this.$router.replace({path:'/main',componnet:()=>require('views/Mainindex')})
            }
          });
        } else {
          console.log("error submit!!");
          return false;
        }
      });
    },
  },

验证表单后的路由跳转问题

动态路由addRoute()

vue2中可以通过路由的addRoutes()和addRoute()两种方法实现路由动态渲染
但是在Vue3中,付日期了addRoutes()方法,只保留了addRoute()单个添加路由配置的方法
router.addRoute()接受的是一个路由规则,也就是一个对象,或者接受一个字符串和一个对象

使用场景:

假设登录的使用是普通用户,我们展示默认页面,普通用户不可以去访问管理员的页面,管理员可以访问所有页面

当从后端根据不同用户返回可以访问的路由时,需要将他们添加到动态路由中

    routes: [
      {
        path: "/",
        name: "home",
		icon: "s-home",
        meta: {
          title: "首页",
        },
        component: () => import("views/Home/Home"),
      },
      {
    addMenu(state, router) {
      //添加动态路由 主路由为Main.vue
      let currentMenu = {
        path: "/",
        component: () => import(`@/views/Mainindex`),
        children: null,
      };
      //将根据登录用户返回的路由添加到main的子路由中
    //   currentMenu.children.push(...state.currentMenu);//...state.currentMenu 会造成里面import导入的component属性丢失
	currentMenu.children=state.currentMenu
      console.log(currentMenu.children[0].component);
      router.addRoute(currentMenu);
    },

但是发现问题:import导入的component属性,在复制之后,children里面并没有该属性,因此需要修改

        children: [
          {
            path: "/page1",
            name: "page1",
            icon: "setting",
            meta: {
              title: "页面1",
            },
            url: "/Other/PageOne",
          },
          {
            path: "/page2",
            name: "page2",
            icon: "setting",
            meta: {
              title: "页面2",
            },
            url: "/Other/PageTwo",
          },

先通过url保存基本的路径,在添加动态路由的时候,根据实际文件位置来进行导入

      //将根据登录用户返回的路由添加到main的子路由中
      //   currentMenu.children.push(...state.currentMenu);//...state.currentMenu 会造成里面import导入的component属性丢失
	  //如果是以及菜单,那么菜单名称肯定有路由 如果是二级菜单,一级没有,二级有路由
      state.currentMenu.forEach((item) => {
        if (item.children) {
          item.children = item.children.map((item) => {
            item.component = () => import(`@/views${item.url}`);
            return item;
          });
          currentMenu.children.push(...item.children);
        } else {
          item.component = () => import(`@/views${item.url}`);
          currentMenu.children.push(item);
        }
      });
      console.log(currentMenu.children[0].component);
      router.addRoute(currentMenu);

在这里插入图片描述

import和require

模块化中使用

require是AMD规范引入方式;是运行时调用,苏搜易require理论上可以运用在代码的任何地方;是负值过程,require的结果就是对象、数字、字符串、函数等,再把require的结果赋值给某个变量
import是ES6的一个语法标准;是编译时调用,必须放在文件开头;是解构过程

路由跳转用name还是path

尽量用name:前期配置的路由路径后期可能会修改,如果路由跳转使用path,有时候path为‘/’,不利于维护,直接用唯一的name进行跳转
现在已经可以实现:动态路由+登录验证+权限管理
但是还没有将token考虑进去
这一部分的登录验证是mock最简单的时候,当有多个用户时,反正都是后端完成,是否验证成功,我们需要通过form表单将账号和密码传递过去

  getdynamicUser: (config) => {
    const { username, password } = JSON.parse(config.body);
    console.log(JSON.parse(config.body));
    //先判断用户是否存在
    if (username === "admin" || username === "people") {
      //判断账号和密码是否对象
      if (username === "admin" && password === "654321") {
        return {
          code: 200,
          message: "登录成功",
          data: dynamicUser.filter((item) => {
            return item.username === username;
          }),
        };
      } else if (username === "people" && password === "123456") {
        return {
          code: 200,
          message: "登录成功",
          data: dynamicUser.filter((item) => {
            return item.username === username;
          }),
        };
      } else {
        return { code: 404, data: { message: "密码错误" } };
      }

token相关

登录所做的事情:
1、获取当前用户对应的菜单栏的菜单,并存储到vuex和cookies中
2、获取当前用户的Token,存储到vuex和cookies中
3、获取当前的菜单生成动态路由

退出登录所做的
1、清除vuex和cookie中的菜单
2、清除vuex和cookie中的token

路由守卫所做的:
因为是后台管理系统,所以在每切换一个路由都需要判断当前token是否存在

sessionStorage,localStorage,cookie

首先,HTTP是无状态协议,不能保存每一次请求的状态,所以需要给客户端增加Cookie来保存客户端状态

Cookie主要用于用户识别和状态管理(比如网页常见的记住密码)

HTML5提供了两种在客户端存储数据的新方法:localStorage和sessionStorage

在这里插入图片描述

token和cookie的关系

cookie是客户端存储信息的方式,token是信息本身
HTTP进行数据交换的时候是无状态的,因此每次都需要重新验证身份
Cookie可以作为一个状态保存的状态机,用来保存用户的相关登录状态,当第一次验证通过后,服务器通过set-cookie令客户端将自己的cookie保存起来,当下一次再次发送请求的时候,直接带上cookie即可。而服务器检测到客户端发送的cookie与其保存的cookie值保持一致时,则直接信任该连接,不再进行验证操作

Token:类似cookie的一种验证信息,客户端通过登录验证后,服务器会返回给客户端一个加密的token,当客户端再次向服务器发起连接时,带上token,服务器直接对token进行校验即可完成权限校验

Cookie作为HTTP规范,存在跨域限制。
Token需要自己存储,自己进行发送,不存在跨域限制

安装

npm install js-cookie -s

使用

一般将token存放在cookie以及vuex中

   token: "",
  },
  mutations: {
    // 存放token
    setToken(state, val) {
      state.token = val;
          //Cookies.set(key,data) 是 `js-cookie`提供的存放 Cookie 的方法 
      Cookie.set("token", val);
    },
    // 获取token
    getToken(state) {
      state.token = Cookie.get("token");
    },
    // 清除token
    clearToken(state) {
      state.token = "";
      Cookie.remove("token");
    },

当登录验证成功的时候,通过后端返回的数据保存在token中

         data: this.dynamicValidateForm,
          }).then((res) => {
            if (res.data.code == 404) {
              this.$message(res.data.data.message);
            }
            if (res.data.code == 200) {
				//后端返回对象中包含token,将token取出放入token
				this.$store.commit('setToken',res.data.data[0].token)
				//需要把返回的路由,添加到动态路由中去

设置全局路由守卫,进行token验证

Vue.config.productionTip = false
//路由全局守卫
router.beforeEach((to,from,next)=>{
	let token=store.state.token
	// 过滤登录页,因为去登录页不需要token(防止死循环)
	if(!token && to.name!=='login'){
		next({name:'login'})
	}
	else{
		next()
	}
})

在这里插入图片描述
退出登录,所做的:清除动态路由,清除token,跳转到login页面

	logOut(){
			//登出的时候,需要清除token以及
		this.$store.commit('clearToken')
		location.reload()//强制页面刷新
	}

token实现免密登录

在这里插入图片描述

    </el-form-item>
    <el-form-item>
      <el-checkbox label="记住账号" v-model="isRemenber"></el-checkbox>
    </el-form-item>

    <el-form-item>
      <el-button type="primary" @click="submitForm('dynamicValidateForm')"
        >登录</el-button
      >
    </el-form-item>

      isRemenber: false,

github项目地址

动态菜单

const dynamicUser = [
  {
    name: "管理员",
    avatar:
      "https://sf3-ttcdn-tos.pstatp.com/img/user-avatar/ccb565eca95535ab2caac9f6129b8b7a~300x300.image",
    desc: "管理员 - admin",
    username: "admin",
    // password: "654321",
    token: "rtVrM4PhiFK8PNopqWuSjsc1n02oKc3f",
    routes: [
      {
        path: "/",
        name: "home",
        icon: "s-home",
        meta: {
          title: "首页",
        },
        url: "/Home/Home",
      },
      {
        path: "/mall",
        name: "mall",
        icon: "video-play",
        meta: {
          title: "商品管理",
        },
        url: "/MallManage/MallManage",
      },
      {
        path: "/user",
        name: "user",
        icon: "user",
        meta: {
          title: "用户管理",
        },
        url: "/UserManage/UserManage",
      },
      {
        path: "/other",
        icon: "location",
        meta: {
          title: "其他",
        },
        url: "/Other/index",
        children: [
          {
            path: "/page1",
            name: "page1",
            icon: "setting",
            meta: {
              title: "页面1",
            },
            url: "/Other/PageOne",
          },
          {
            path: "/page2",
            name: "page2",
            icon: "setting",
            meta: {
              title: "页面2",
            },
            url: "/Other/PageTwo",
          },
        ],
      },
    ],
  },
  {
    name: "普通用户",
    avatar:
      "https://sf1-ttcdn-tos.pstatp.com/img/user-avatar/6364348965908f03e6a2dd188816e927~300x300.image",
    desc: "普通用户 - people",
    username: "people",
    // password: "123456",
    token: "4es8eyDwznXrCX3b3439EmTFnIkrBYWh",
    routes: [
      {
        path: "/",
        name: "home",
        icon: "s-home",
        meta: {
          title: "首页",
        },
        url: "/Home/Home",
      },
      {
        path: "/mall",
        name: "mall",
        icon: "video-play",
        meta: {
          title: "商品管理",
        },
        url: "/MallManage/MallManage",
      },
    ],
  },
];

export default {
  getdynamicUser: (config) => {
    const { username, password } = JSON.parse(config.body);
    //先判断用户是否存在
    if (username === "admin" || username === "people") {
      //判断账号和密码是否对象
      if (username === "admin" && password === "654321") {
        return {
          code: 200,
          message: "登录成功",
          data: dynamicUser.filter((item) => {
            return item.username === username;
          }),
        };
      } else if (username === "people" && password === "123456") {
        return {
          code: 200,
          message: "登录成功",
          data: dynamicUser.filter((item) => {
            return item.username === username;
          }),
        };
      } else {
        return { code: 404, data: { message: "密码错误" } };
      }
    } else {
      return { code: 404, data: { message: "用户不存在" } };
    }
  },
};

在后台管理项目中,牵涉到权限的东西多数是后端传递过来的数据,前端去展示,就导航菜单而言,不能写死,需要在用户登录之后,发请求获取用户的对应菜单数据,根据对应的数据去展示对应的菜单
在这里插入图片描述

</span></el-menu-item
        >
      </el-menu-item-group>
    </el-submenu>
  
  </el-menu>
</template>

el-menu菜单的核心数据:
1、菜单的名字name
点击菜单进行路由跳转的路径path
菜单上小图标icon
菜单不是最内层的菜单,即children是否是空数组,当children为空的时候,就说明到菜单嘴里层了(最里层的菜单children为空数组的时候,点击做路由跳转)

el-menu代码分为两类:有子集和没有子集
有自己:用的是el-submenu标签包含template标签指定名字跟很多个el-menu-item标签

	<el-submenu
      v-for="(item, index) in hasChildren"
      :key="index"
      :index="item.meta.title"
    >
      <template slot="title">
        <i :class="'el-icon-' + item.icon"></i>
        <span slot="title">{{ item.meta.title}}</span>
      </template>
      <el-menu-item-group>
        <el-menu-item
          :index="subItem.path"
          v-for="(subItem, subIndex) in item.children"
          :key="subIndex"
          @click="clickMenu(subItem)"
        >
          <i :class="'el-icon-' + subItem.icon"></i>
          <span slot="title">{{ subItem.meta.title }}</span></el-menu-item
        >
      </el-menu-item-group>
    </el-submenu>

没有子集:直接用很多个el-menu-item标签

    <!-- 分为有子路由和无子路由的导航 -->
    <el-menu-item
      v-for="item in noChildren"
      :key="item.name"
      :index="item.path"
      @click="clickMenu(item)"
    >
      <i :class="'el-icon-' + item.icon"></i>
      <span slot="title">{{ item.meta.title }}</span>
    </el-menu-item>

版权声明:本文为qq_45387575原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。