涛涛的若依学习笔记——首页是怎样实现的?——b部分Navbar

前言

上节我们把首页分为三大部分,这节讲b部分的主体Navbar部分
在这里插入图片描述

模块对应的代码
b的主体src\layout\components\Navbar.vue
b的点击头像的布局设置src\layout\components\Settings\index.vue
b的页标签src\layout\components\TagsView\index.vue

先看一下这里

import { mapGetters } from 'vuex'
import Breadcrumb from '@/components/Breadcrumb'
import TopNav from '@/components/TopNav'
import Hamburger from '@/components/Hamburger'
import Screenfull from '@/components/Screenfull'
import SizeSelect from '@/components/SizeSelect'
import Search from '@/components/HeaderSearch'
import RuoYiGit from '@/components/RuoYi/Git'
import RuoYiDoc from '@/components/RuoYi/Doc'

引入了一堆组件
具体每个是什么作用呢,一个个看看

1. Hamburger

在这里插入图片描述
这是左上角的小图标

1. 看看源码hamburger

    <hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />

显然,这是一个组件,在下面有引入

import Hamburger from '@/components/Hamburger'

2. 看看Hamburger.vue

<template>
  <div style="padding: 0 15px;" @click="toggleClick">
    <svg
      :class="{'is-active':isActive}"
      class="hamburger"
      viewBox="0 0 1024 1024"
      xmlns="http://www.w3.org/2000/svg"
      width="64"
      height="64"
    >
      <path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
    </svg>
  </div>
</template>

<script>
export default {
  name: 'Hamburger',
  props: {
    isActive: {  //显示不同的图标
      type: Boolean,
      default: false
    }
  },
  methods: {
    toggleClick() {
      this.$emit('toggleClick')
    }
  }
}
</script>

显然这里根据 isActive的值变化不同的样式
这个组件被点击时,会触发toggleClick方法
子组件使用 this.$emit(‘toggleClick’)

  vue中 关于$emit的用法
    1、父组件可以使用 props 把数据传给子组件。
    2、子组件可以使用 $emit 触发父组件的自定义事件。

所以这里调用的父组件的toggleClick事件
即 触发了 @toggleClick=“toggleSideBar”

3. 看看toggleSideBar()

    toggleSideBar() {
      this.$store.dispatch('app/toggleSideBar')
      /**
       * this.$store.dispatch() 与 this.$store.commit()方法的区别总的来说他们只是存取方式的不同,两个方法都是传值给vuex的mutation改变state
this.$store.dispatch() :含有异步操作,例如向后台提交数据,写法:this.$store.dispatch(‘action方法名’,值)
this.$store.commit():同步操作,,写法:this.$store.commit(‘mutations方法名’,值)
commit: 同步操作
存储 this.$store.commit('changeValue',name)
取值 this.$store.state.changeValue
dispatch: 异步操作
存储 this.$store.dispatch('getlists',name)
取值 this.$store.getters.getlists
*/
    }

显然这里做了异步提交,调用了 app/toggleSideBar

4. 看看 app/toggleSideBar

仅保留相关代码

import Cookies from 'js-cookie'

const state = {
  sidebar: {
    opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
    withoutAnimation: false
  }
}
const mutations = {
  TOGGLE_SIDEBAR: state => {
    // 取反 侧边栏开关
    state.sidebar.opened = !state.sidebar.opened
    state.sidebar.withoutAnimation = false
    if (state.sidebar.opened) {
      // 侧边栏打开,则sidebarStatus值为1
      Cookies.set('sidebarStatus', 1)
    } else {
      // 否则0
      Cookies.set('sidebarStatus', 0)
    }
  }
}

const actions = {
  toggleSideBar({ commit }) {
    //调用了 TOGGLE_SIDEBAR
    commit('TOGGLE_SIDEBAR')
  }
}

export default {
  namespaced: true,
  state,
  mutations,
  actions
}

这个方法实现了对 sidebar 对象的opened属性值取反,并修改Cookie中的对应值。

打开图
在这里插入图片描述
关闭图
在这里插入图片描述

5. 看看:is-active=“sidebar.opened”

:is-active 是绑定的动态属性,通过prop父子传值,对子组件样式进行改变
根据传来值的不同
在这里插入图片描述
isActive的值会对应变化
在这里插入图片描述
则此处显示的打开和关闭的图标是不一样的
在这里插入图片描述
注意: prop是单向数据传递,父会改变子,子不会改变父
参考: https://cn.vuejs.org/v2/guide/components-props.html

OK再看看 sidebar.opened

这是哪来的呢???

来自这里

    computed: {
    ...mapGetters([
      'sidebar',  // 使用对象展开运算符将 getter 混入 computed 对象中
      'avatar',
      'device'
    ])
 }

以下写法相同

  computed: {
    ...mapGetters([
     // 'sidebar',  // 使用对象展开运算符将 getter 混入 computed 对象中
      'avatar',
      'device'
    ]),
    sidebar:function(){
      return this.$store.state.app.sidebar
    }
 }

那 …mapGetters 又是啥???

来自这里

import { mapGetters } from 'vuex'

看看这个src\store\getters.js

const getters = {
  sidebar: state => state.app.sidebar,  //用于对侧边栏的控制
  size: state => state.app.size,
  device: state => state.app.device,
  visitedViews: state => state.tagsView.visitedViews,
  cachedViews: state => state.tagsView.cachedViews,
  token: state => state.user.token,
  avatar: state => state.user.avatar,
  name: state => state.user.name,
  introduction: state => state.user.introduction,
  roles: state => state.user.roles,
  permissions: state => state.user.permissions,
  permission_routes: state => state.permission.routes,
  topbarRouters:state => state.permission.topbarRouters,
  defaultRoutes:state => state.permission.defaultRoutes,
  sidebarRouters:state => state.permission.sidebarRouters,
}
export default getters

这里导出了一堆变量,就来自这里
再往里找找 state.app.sidebar
就是 src\store\modules\app.js
上文提到的
在这里插入图片描述
综上所述,点击 小图标 ,会把store里存的sidebar值进行取反,并修改cookie里存的sidebarState的值,并且子组件小图标会 动态变换。

2. 看看Breadcrumb

1. 看看源码

是针对这个标签的显示
在这里插入图片描述
在这里插入图片描述

<breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!topNav"/>

显然,只有topNav=false时显示,即不开启TopNav时显示

computed: {
  topNav: {
      get() {
        return this.$store.state.settings.topNav
      }
    }
  }

在这里插入图片描述

2. 看看Breadcrumb.vue

在此之前,先看看人家官网用法
在这里插入图片描述
在这里插入图片描述
还是蛮复杂的

<template>
  <el-breadcrumb class="app-breadcrumb" separator="/">
    <transition-group name="breadcrumb">
      <!--
            transition-group用于列表的过渡效果。
            默认为span, 可以使用tag 转换为其他元素。
            子元素通常使用v-for进行循环。
            子元素必须要有唯一的key属性,且key不能为索引值。
            css过渡是应用在子元素中,而不是这个容器本身。
            参考  https://blog.csdn.net/u012574931/article/details/103563183
       -->
      <el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
        <span
          v-if="item.redirect === 'noRedirect' || index == levelList.length - 1"
          class="no-redirect"
          >
          <!-- {{ index }},{{ levelList.length - 1 }},{{ item.meta.title }} -->
          {{ item.meta.title }}
          </span
        >
        <!-- 首页    /     1,2,系统管理    /    2,2,岗位管理
          levelList 长度为3
          对象1  首页
          对象2 系统管理
          对象3 岗位管理
          而 index 是从 0 开始计算的
          0 为首页
          1 为系统管理
          2 为岗位管理
          所以当 是二级菜单时,也不可选中
           -->
        <!-- no-redirect 效果是  首页/系统管理/用户管理  只有首页可以是超链接,其余两个禁止点击
        所有的一级菜单,比如 系统管理,是符合 item.redirect === 'noRedirect'
        所有的二级菜单,比如 用户管理,是符合 index == levelList.length - 1
        -->
        <a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
        <!-- 所以这句话专门是给 首页  两个字用的,就它可以点击 -->
      </el-breadcrumb-item>
    </transition-group>
  </el-breadcrumb>
</template>

<script>
export default {
  data() {
    return {
      levelList: null //存储的是目录层级
    };
  },
  watch: {
    $route(route) {
      // if you go to the redirect page, do not update the breadcrumbs
      if (route.path.startsWith("/redirect/")) { //如何去的是重定向页面,不改变breadcrumbs。比如去的是Nacos控制台,若依官网等。
        return;
      }
      this.getBreadcrumb();
    }
  },
  created() {
    this.getBreadcrumb();
  },
  methods: {
    getBreadcrumb() {
      // only show routes with meta.title
      let matched = this.$route.matched.filter(
        item => item.meta && item.meta.title
      );
      // console.log("*************************");
      // console.log(
      //   "this.$route.matched.filter(item => item.meta && item.meta.title):"
      // );
      // this.$route.matched.filter(item => {
      /**
         *系统管理
          岗位管理
         **/
      // console.log(item.meta && item.meta.title);
      // console.log(item);
      // console.log(item.meta); //一级标题 系统管理
      // console.log(item.meta.title); //二级标题 用户管理/字典管理/。。。等等
      // });
      // console.log("*************************");
      // console.log("***********matched**************");
      // console.log(matched);
      const first = matched[0]; //两个路由对象

      if (!this.isDashboard(first)) {
        //如果不是首页,就添加一个首页对象在前面
        matched = [{ path: "/index", meta: { title: "首页" } }].concat(matched); // 首页  系统管理  岗位管理
        //如果是这样,首页也不可以选中
        // matched = [
        //   { path: "/index", meta: { title: "首页" }, redirect: "noRedirect" }
        //   // 0,2,首页/1,2,系统管理/2,2,岗位管理
        // ].concat(matched); // 首页  系统管理  岗位管理
      }
      // 否则的话  只是  首页
      // console.log("***********matched2**************");
      // console.log(matched);
      // this.levelList = matched.filter(item => {
      //   // console.log("item.meta && item.meta.title && item.meta.breadcrumb:");
      //   // console.log(item.meta);
      //   // console.log(item.meta.title);
      //   // console.log(item);
      //   // console.log(item.meta.breadcrumb);
      //   // 匹配,如果不是false,添加到 this.levelList
      //   item.meta && item.meta.title && item.meta.breadcrumb !== false;
      //   // 不理解   item.meta.breadcrumb是undefined呀   整个条件不就永真了吗
      // });
      this.levelList = matched; //和上面的过滤后效果一模一样
      //所以我再试试三个&&&
      // console.log("1"&&"2"&&"3"); //  打印为 3
      // console.log("1"&&"2"&&undefined); //  打印为 undefined
      // console.log("1"&&"2"&&null); //  打印为 null
      // console.log("1"&&"2"&&""); //  打印为 ""
      // 所以那个过滤效果为 item.meta.breadcrumb !== false
      // 因为item.meta.breadcrumb始终是undefined
      // console.log("undefined!==false:" + undefined !== false); //  打印为 true
      //所以????过滤了啥
      // console.log("this.levelList:" + this.levelList);
      // for (const key in this.levelList) {
      //   if (Object.hasOwnProperty.call(this.levelList, key)) {
      //     const element = this.levelList[key];
      //     console.log(element);
      //   }
      // }
    },
    isDashboard(route) {
      // console.log("route:" + route && route.name); // System
      // console.log("1"&&"2"); //  打印为 2
      // console.log("1"&&undefined); //  打印为 undefined
      // console.log("1"&&null); //  打印为 null
      // console.log("1"&&""); //  打印为 ""
      const name = route && route.name;
      if (!name) {
        // 如果  !没有名字
        return false;
      }
      return name.trim() === "Index"; //去掉两边的空格后返回是不是首页
    },
    handleLink(item) {
      //获取当前的对象
      // console.log(item)  // 首页路由对象  想想也是,只有首页支持跳转
      const { redirect, path } = item;
      // console.log(redirect)  // undefined
      // console.log(path)  // index
      if (redirect) {
        this.$router.push(redirect);
        return;
      }
      this.$router.push(path);
    }
  }
};
</script>

3. 看看这个搜索框

 <search id="header-search" class="right-menu-item" />

短短一行代码,解读好麻烦

<template>
  <div :class="{ show: show }" class="header-search">
    <!-- :class="{'show':show}"  绑定动态样式 -->
    <svg-icon
      class-name="search-icon"
      icon-class="search"
      @click.stop="click"
    />
    <!-- 事件冒泡 :当一个元素接收到事件的时候 会把他接收到的事件传给自己的父级,一直到window 。
    (注意这里传递的仅仅是事件 并不传递所绑定的事件函数。所以如果父级没有绑定事件函数,就算传递了事件 也不会有什么表现 但事件确实传递了。)
    @click.stop: 阻止事件冒泡;
     -->
    <el-select
      ref="headerSearchSelect"
      v-model="search"
      :remote-method="querySearch"
      filterable
      default-first-option
      remote
      placeholder="Search"
      class="header-search-select"
      @change="change"
    >
      <!--
    为了启用远程搜索,需要将filterable和remote设置为true,同时传入一个remote-method。
    remote-method为一个Function,它会在输入值发生变化时调用,参数为当前输入值。
    需要注意的是,如果el-option是通过v-for指令渲染出来的,此时需要为el-option添加key属性,且其值需具有唯一性,比如此例中的item.value。
    ref="headerSearchSelect"  参考 https://www.jianshu.com/p/623c8b009a85
    方便下面调用取值
    v-model="search"  双向绑定一个search值
    :remote-method="querySearch"   调用querySearch方法
    filterable	是否可搜索	boolean
    default-first-option	在输入框按下回车,选择第一个匹配项。需配合 filterable 或 remote 使用
    remote	是否为远程搜索
    placeholder="Search"  没有输入时的默认值
    @change当输入框失焦的时候触发而且在elementUI中使用change时是这样的@visible-change
    @input是输入框发生变化时触发,也就是说输入框一动就出触发了
     -->
      <el-option
        v-for="option in options"
        :key="option.item.path"
        :value="option.item"
        :label="option.item.title.join(' > ')"
      />
      <!--
        选择框
        v-for="option in options" :key="option.item.path" :value="option.item"
        遍历可选项, key为它们的路径
        :label 分组的组名之间添加 >
       -->
    </el-select>
  </div>
</template>

<script>
// fuse is a lightweight fuzzy-search module
// make search results more in line with expectations
import Fuse from "fuse.js/dist/fuse.min.js"; //https://fusejs.io/api/methods.html  fuse.js模糊搜索
import path from "path";

export default {
  name: "HeaderSearch",
  data() {
    return {
      search: "", //搜索框的内容
      options: [], //可选项
      searchPool: [], // 可被搜索的数据池
      show: false, //搜索输入框的开关
      fuse: undefined // fuse对象,用于查找
    };
  },
  computed: {
    routes() {
      return this.$store.getters.permission_routes; //返回当前权限路径
    }
  },
  watch: {
    //监听
    routes() {
      this.searchPool = this.generateRoutes(this.routes); //用于搜索框的搜索
    },
    searchPool(list) {
      this.initFuse(list); // 初始化fuse数据池
    },
    show(value) {
      if (value) {
        // 动态监听事件 该对象添加this.close事件
        document.body.addEventListener("click", this.close);
      } else {
        // 动态监听事件 该对象删除this.close事件
        document.body.removeEventListener("click", this.close);
      }
    }
  },
  mounted() {
    this.searchPool = this.generateRoutes(this.routes); //更新fuse数据池
  },
  methods: {
    click() {
      this.show = !this.show; //搜索框的开和关
      if (this.show) {
        // 如果是打开的,则调用 this.$refs.headerSearchSelect.focus()
        this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.focus();
      }
    },
    close() {
      // 当前框失去焦点时 触发
      this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.blur();
      this.options = []; //可选项置空
      this.show = false; //展示为不可显示
    },
    change(val) {
      const path = val.path;
      if (this.ishttp(val.path)) {
        // http(s):// 路径新窗口打开
        const pindex = path.indexOf("http"); //如果是http地址
        window.open(path.substr(pindex, path.length), "_blank"); //在新窗口中打开
      } else {
        this.$router.push(val.path); //否则路由跳转到那个页面
      }
      this.search = ""; //搜索框置空
      this.options = []; //选择框置空
      this.$nextTick(() => {
        this.show = false; //搜索框关闭
      });
    },
    initFuse(list) {
      //初始化fuse,把可能被搜索的数据放入新的fuse
      this.fuse = new Fuse(list, {
        shouldSort: true, //按分数对结果列表进行排序
        threshold: 0.4, //匹配算法在什么时候放弃。阈值0.0需要完美匹配(字母和位置),阈值1.0可以匹配任何内容。
        location: 0, //大致确定文本中预期要找到的模式的位置
        distance: 100, //默认: 100
        //确定匹配必须与模糊位置(由 指定location)的接近程度。distance与模糊位置相距字符的精确字母匹配将计分为完全不匹配。A distanceof0要求匹配在location指定的精确位置。
        //距离 of1000需要完美匹配才能在使用of找到的800字符内。locationthreshold0.8
        maxPatternLength: 32, //最大匹配长度
        minMatchCharLength: 1, //仅返回长度超过此值的匹配项。
        keys: [
          //将被搜索的键列表。这支持嵌套路径、加权搜索、在字符串和对象数组中搜索
          {
            name: "title",
            weight: 0.7
          },
          {
            name: "path",
            weight: 0.3
          }
        ]
      });
    },
    // Filter out the routes that can be displayed in the sidebar
    // And generate the internationalized title
    /**
     * //筛选出可以在侧边栏中显示的路线
     * //并生成国际化的标题
     * */
    generateRoutes(routes, basePath = "/", prefixTitle = []) {
      let res = [];

      for (const router of routes) {
        // skip hidden router  跳过隐藏路由
        if (router.hidden) {
          continue;
        }

        const data = {
          path: !this.ishttp(router.path)
            ? path.resolve(basePath, router.path)
            : router.path,
          title: [...prefixTitle] // title 是 复制 prefixTitle 数组
          //如果是http或者https开头//给定的路径的序列是从右往左被处理的,后面每个 path 被依次解析,直到构造完成一个绝对路径。
          //  例如,给定的路径片段的序列为:/foo、/bar、baz,则调用 path.resolve('/foo', '/bar', 'baz') 会返回 /bar/baz。
        };
        // console.log("router.path:"+router.path)
        // console.log("basePath.path:"+basePath.path)
        // console.log("path.resolve(basePath, router.path):"+path.resolve(basePath, router.path))

        //router.path:/system
        //path.resolve(basePath, router.path):/system
        //basePath.path:undefined

        //router.path:http://localhost:8080/swagger-ui/index.html
        //path.resolve(basePath, router.path):/tool/gen
        //basePath.path:undefined
        // console.log("*******");
        // console.log("data.path:" + data.path);
        // console.log("data.title:" + data.title);
        // console.log("router.meta:" + router.meta);
        // console.log(" router.meta.title:" + router.meta.title);
        // console.log(
        //   "router.meta && router.meta.title:" + router.meta && router.meta.title
        // );
        //               for (const key in router.meta) {
        //           if (Object.hasOwnProperty.call(router.meta, key)) {
        //           const element = router.meta[key];
        //           console.log(key+"------"+element);
        //           /*
        // *******
        // index.vue?6ced:183 title------定时任务
        // index.vue?6ced:183 icon------job
        // index.vue?6ced:183 noCache------false
        // index.vue?6ced:183 link------null
        // index.vue?6ced:206 *******
        // index.vue?6ced:172 *******
        // index.vue?6ced:183 title------Sentinel控制台
        // index.vue?6ced:183 icon------sentinel
        // index.vue?6ced:183 noCache------false
        // index.vue?6ced:183 link------http://localhost:8718
        // *******
        // index.vue?6ced:183 Admin控制台
        // index.vue?6ced:183 server
        // index.vue?6ced:183 false
        // index.vue?6ced:183 http://localhost:9100/login
        // index.vue?6ced:186 *******
        //           */
        //         }
        //       }
        //         console.log("*******");
        /**
 * *******
 *
 *
 *
index.vue?6ced:173 data.path:/system
index.vue?6ced:174 data.title:
index.vue?6ced:175 router.meta:[object Object]
index.vue?6ced:180 *******
index.vue?6ced:172 *******
index.vue?6ced:173 data.path:/system/user
index.vue?6ced:174 data.title:系统管理
index.vue?6ced:175 router.meta:[object Object]
index.vue?6ced:180 *******
 * */
        if (router.meta && router.meta.title) {
          //router.meta.title
          data.title = [...data.title, router.meta.title];
          // console.log("data.title:"+data.title)
          /*
          *******
index.vue?6ced:223 data.title:首页
index.vue?6ced:172 *******
index.vue?6ced:223 data.title:系统管理
index.vue?6ced:172 *******
index.vue?6ced:223 data.title:系统管理,用户管理
index.vue?6ced:172 *******
index.vue?6ced:223 data.title:系统管理,角色管理
index.vue?6ced:172 *******
index.vue?6ced:223 data.title:系统管理,菜单管理
index.vue?6ced:172 *******
index.vue?6ced:223 data.title:系统管理,部门管理
index.vue?6ced:172 *******
index.vue?6ced:223 data.title:系统管理,岗位管理
index.vue?6ced:172 *******
index.vue?6ced:223 data.title:系统管理,字典管理
index.vue?6ced:172 *******
index.vue?6ced:223 data.title:系统管理,参数设置
index.vue?6ced:172 *******
index.vue?6ced:223 data.title:系统管理,通知公告
index.vue?6ced:172 *******
index.vue?6ced:223 data.title:系统管理,操作日志
index.vue?6ced:172 *******
index.vue?6ced:223 data.title:系统管理,登录日志
index.vue?6ced:223 data.title:系统监控
index.vue?6ced:172 *******
index.vue?6ced:223 data.title:系统监控,在线用户
index.vue?6ced:172 *******
index.vue?6ced:223 data.title:系统监控,定时任务
index.vue?6ced:172 *******
index.vue?6ced:223 data.title:系统监控,Sentinel控制台
index.vue?6ced:172 *******
index.vue?6ced:223 data.title:系统监控,Nacos控制台
index.vue?6ced:172 *******
index.vue?6ced:223 data.title:系统监控,Admin控制台
index.vue?6ced:172 *******
index.vue?6ced:223 data.title:系统工具
index.vue?6ced:172 *******
index.vue?6ced:223 data.title:系统工具,表单构建
index.vue?6ced:172 *******
index.vue?6ced:223 data.title:系统工具,代码生成
index.vue?6ced:172 *******
index.vue?6ced:223 data.title:系统工具,系统接口
index.vue?6ced:172 *******
index.vue?6ced:223 data.title:若依官网
。。。。
          */
          if (router.redirect !== "noRedirect") {
            // 如果  ! 不需要重定向  即重定向
            // only push the routes with title
            // special case: need to exclude parent router without redirect
            res.push(data);
          }
        }
        // console.log("++++++++++++")
        // console.log("res:"+res)
        // for (const key in res) {
        //   if (Object.hasOwnProperty.call(res, key)) {
        //     const element = res[key];
        //   }
        // }
        //     console.log("++++++++++++")
        // recursive child routes
        if (router.children) {
          //如果有子路由
          //获取子路由
          const tempRoutes = this.generateRoutes(
            router.children,
            data.path,
            data.title
          );
          if (tempRoutes.length >= 1) {
            res = [...res, ...tempRoutes];
            //  ... 是复制数组
            // 即可以抽象为 res+=tempRoutes
          }
        }
      }
      // console.log(res)
      // console.log("++++++++++++");
      // for (let i = 0; i < res.length; i++) {
      //   if (res[i].title.length == 1) {
      //     console.log(res[i].title[0]+"----"+res[i].path);
      //   } else {
      //     console.log(res[i].title[0] + "----" + res[i].title[1]+"----"+res[i].path);
      //   }
      // }
      // console.log("++++++++++++");
      /*
++++++++++++
index.vue?6ced:308 首页----/index
index.vue?6ced:310 系统管理----用户管理----/system/user
index.vue?6ced:310 系统管理----角色管理----/system/role
index.vue?6ced:310 系统管理----菜单管理----/system/menu
index.vue?6ced:310 系统管理----部门管理----/system/dept
index.vue?6ced:310 系统管理----岗位管理----/system/post
index.vue?6ced:310 系统管理----字典管理----/system/dict
index.vue?6ced:310 系统管理----参数设置----/system/config
index.vue?6ced:310 系统管理----通知公告----/system/notice
index.vue?6ced:310 系统管理----操作日志----/system/log/operlog
index.vue?6ced:310 系统管理----登录日志----/system/log/logininfor
index.vue?6ced:310 系统监控----在线用户----/monitor/online
index.vue?6ced:310 系统监控----定时任务----/monitor/job
index.vue?6ced:310 系统监控----Sentinel控制台----http://localhost:8718
index.vue?6ced:310 系统监控----Nacos控制台----http://localhost:8848/nacos
index.vue?6ced:310 系统监控----Admin控制台----http://localhost:9100/login
index.vue?6ced:310 系统工具----表单构建----/tool/build
index.vue?6ced:310 系统工具----代码生成----/tool/gen
index.vue?6ced:310 系统工具----系统接口----http://localhost:8080/swagger-ui/index.html
index.vue?6ced:308 若依官网----http://ruoyi.vip
index.vue?6ced:313 ++++++++++++
*/
      return res;
    },
    querySearch(query) {
      if (query !== "") {
        this.options = this.fuse.search(query);
      } else {
        this.options = [];
      }
    },
    ishttp(url) {
      //判断是不是http:// 或者 https://
      return url.indexOf("http://") !== -1 || url.indexOf("https://") !== -1;
    }
  }
};
</script>

<style lang="scss" scoped>
// 为了样式模块化,给其加上scoped属性
/**
给HTML的DOM节点加一个不重复data属性(形如:data-v-2311c06a)来表示他的唯一性
在每句css选择器的末尾(编译后的生成的css语句)加一个当前组件的data属性选择器(如[data-v-2311c06a])来私有化样式
如果组件内部包含有其他组件,只会给其他组件的最外层标签加上当前组件的data属性
*/
.header-search {
  font-size: 0 !important;

  .search-icon {
    cursor: pointer;
    font-size: 18px;
    vertical-align: middle;
  }

  .header-search-select {
    font-size: 18px;
    transition: width 0.2s;
    width: 0;
    overflow: hidden;
    background: transparent;
    border-radius: 0;
    display: inline-block;
    vertical-align: middle;
    // p::after	在每个 <p> 的内容之后插入内容。
    ::v-deep .el-input__inner {
      border-radius: 0;
      border: 0;
      padding-left: 0;
      padding-right: 0;
      box-shadow: none !important;
      border-bottom: 1px solid #d9d9d9;
      vertical-align: middle;
    }
  }
  // 父选择器的标识符 & 在嵌套 CSS 规则时,有时也需要直接使用嵌套外层的父选择器,
  // 例如,当给某个元素设定 hover 样式时,或者当 body 元素有某个 classname 时,可以用 & 代表嵌套规则外层的父选择器。
  &.show {
    //父级class样式是 show 的组件
    .header-search-select {
      width: 210px; //搜索框的宽度
      margin-left: 10px; //搜索框和搜索图标之间的距离
    }
  }
}
</style>

这里面使用了Fuse.js,是一个轻量级的模糊搜索
整体流程就是

  1. 页面创建时,获取目录路径放入Fuse的被搜索数据池中
  2. 点击搜索图标,根据动态绑定样式,会露出搜索框
  3. 输入要搜索的内容
  4. Fuse根据搜索的内容,进行模糊搜索,并把结果放在可选项里
  5. 直接回车默认跳第一个选项,或者点其他选项
  6. 如果是http地址,打开一个新窗口,显示;如果不是,跳到对应的页面
  7. 搜索框收回,可选项清空

4. 看看ruo-yi-git

盲猜一下,这个好解读

<el-tooltip content="源码地址" effect="dark" placement="bottom">
          <ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />
</el-tooltip>
       <!--
        effect	默认提供的主题	String	dark/light	dark
        content	显示的内容,也可以通过 slot#content 传入 DOM	String

        placement	Tooltip 的出现位置
        top/top-start/top-end/bottom/bottom-start/bottom-end/left/left-start/left-end/right/right-start/right-end
        默认值 	bottom
         -->

看看Ruo-yi-git.vue

<template>
  <div>
    <svg-icon icon-class="github" @click="goto" />
  </div>
</template>
<script>
export default {
  name: 'RuoYiGit',
  data() {
    return {
      url: 'https://gitee.com/y_project/RuoYi-Cloud'
    }
  },
  methods: {
    goto() {
      window.open(this.url)
    }
  }
}
</script>

5. 看看ruoyi-doc

<el-tooltip content="源码地址" effect="dark" placement="bottom">
          <ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />
</el-tooltip>

看看Ruo-yi-doc.vue

<template>
  <div>
    <svg-icon icon-class="question" @click="goto" />
  </div>
</template>
<script>
<script>
export default {
  name: 'RuoYiDoc',
  data() {
    return {
      url: 'http://doc.ruoyi.vip/ruoyi-cloud'
    }
  },
  methods: {
    goto() {
      window.open(this.url) //打开一个新的窗口
    }
  }
}
</script>

6.看看全屏

<screenfull id="screenfull" class="right-menu-item hover-effect" />

看看Screenfull.vue

<template>
  <div>
    <svg-icon
      :icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'"
      @click="click"
    />
    <!--
      :icon-class="isFullscreen?'exit-fullscreen':'fullscreen'" 动态样式绑定 是全屏?退出全屏:全屏
     -->
  </div>
</template>
<script>
// 全屏插件
import screenfull from "screenfull";

export default {
  name: "Screenfull",
  data() {
    return {
      isFullscreen: false
    };
  },
  // https://www.jianshu.com/p/672e967e201c
  // vue 生命周期详解
  /*
  beforeCreate( 创建前 )
  在实例初始化之后,数据观测和事件配置之前被调用,
  此时组件的选项对象还未创建,el 和 data 并未初始化,
  因此无法访问methods, data, computed等上的方法和数据。

  created ( 创建后 )
  实例已经创建完成之后被调用,在这一步,
  实例已完成以下配置:数据观测、属性和方法的运算,watch/event事件回调,
  完成了data 数据的初始化,el没有。
   然而,挂在阶段还没有开始, $el属性目前不可见,这是一个常用的生命周期,
  因为你可以调用methods中的方法,改变data中的数据,
   并且修改可以通过vue的响应式绑定体现在页面上,
   获取computed中的计算属性等等,通常我们可以在这里对实例进行预处理,

   也有一些童鞋喜欢在这里发ajax请求,值得注意的是,

   这个周期中是没有什么方法来对实例化过程进行拦截的,
   因此假如有某些数据必须获取才允许进入页面的话,并不适合在这个方法发请求,
   建议在组件路由钩子beforeRouteEnter中完成

  beforeMount
  挂载开始之前被调用,相关的render函数首次被调用(虚拟DOM),
  实例已完成以下的配置: 编译模板,把data里面的数据和模板生成html,完成了el和data 初始化,
  注意此时还没有挂在html到页面上。

  mounted
  挂载完成,也就是模板中的HTML渲染到HTML页面中,此时一般可以做一些ajax操作

  mounted只会执行一次。


  beforeUpdate
  在数据更新之前被调用,发生在虚拟DOM重新渲染和打补丁之前,可以在该钩子中进一步地更改状态,
  不会触发附加地重渲染过程

  updated(更新后)
  在由于数据更改导致地虚拟DOM重新渲染和打补丁只会调用,调用时,
  组件DOM已经更新,所以可以执行依赖于DOM的操作,
  然后在大多是情况下,应该避免在此期间更改状态,
  因为这可能会导致更新无限循环,
  该钩子在服务器端渲染期间不被调用

  beforeDestroy(销毁前)
  在实例销毁之前调用,实例仍然完全可用,

  这一步还可以用this来获取实例,
  一般在这一步做一些重置的操作,比如清除掉组件中的定时器 和 监听的dom事件

  destroyed(销毁后)
  在实例销毁之后调用,调用后,所以的事件监听器会被移出,所有的子实例也会被销毁,
  该钩子在服务器端渲染期间不被调用

  */
  mounted() { //  挂载完成,也就是模板中的HTML渲染到HTML页面中,此时一般可以做一些ajax操作;mounted只会执行一次。
    this.init();
  },
  beforeDestroy() {//  在实例销毁之前调用,实例仍然完全可用,  这一步还可以用this来获取实例,
  // 一般在这一步做一些重置的操作,比如清除掉组件中的定时器 和 监听的dom事件
    this.destroy();
  },
  methods: {
    click() {
      // screenfull.isEnabled 判断浏览器能不能全屏
      if (!screenfull.isEnabled) {
        this.$message({ message: "你的浏览器不支持全屏", type: "warning" });
        return false;
      }
      // 开启全屏
      screenfull.toggle();
    },
    change() {
      // 全屏状态 的 参数更新,这个是控制图标显示的
      this.isFullscreen = screenfull.isFullscreen;
    },
    init() {
      if (screenfull.isEnabled) {
        // 开启全屏
        screenfull.on("change", this.change);
      }
    },
    destroy() {
      if (screenfull.isEnabled) {
        // 关闭全屏
        screenfull.off("change", this.change);
      }
    }
  }
};
</script>

<style scoped>
.screenfull-svg {
  display: inline-block;
  cursor: pointer;
  fill: #5a5e66;
  width: 20px;
  height: 20px;
  vertical-align: 10px;
}
</style>

7. 看看布局大小

<!-- 调节布局大小 -->
<el-tooltip content="布局大小" effect="dark" placement="bottom">
     <size-select id="size-select" class="right-menu-item hover-effect" />
 </el-tooltip>

看看入口文件 main.js

Vue.use(Element, {// 设置默认和刷新浏览器设置为你指定的大小
  size: Cookies.get('size') || 'medium' // set element-ui default size
})

看看SizeSelect.vue

<template>
  <el-dropdown trigger="click" @command="handleSetSize">
    <!-- 下拉框
trigger="click"  点击事件
trigger	触发下拉的行为	string	 可选值 :hover, click	  默认 hover
@command="handleSetSize"     command	点击菜单项触发的事件回调	dropdown-item 的指令
 -->
    <div>
      <svg-icon class-name="size-icon" icon-class="size" />
    </div>
    <!-- 下拉框元素 -->
    <el-dropdown-menu slot="dropdown">
      <!--
显然,这里也是父级  子集在el-dropdown-menu中
Dropdown Slots https://element.eleme.cn/#/zh-CN/component/drawer
传入dropdown 给el-dropdown-menu
该组件内容嵌入到el-dropdown-menu中去

对于这样的情况,<slot> 元素有一个特殊的 attribute:name。这个 attribute 可以用来定义额外的插槽:

子集对应接收

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>
一个不带 name 的 <slot> 出口会带有隐含的名字“default”。

在向具名插槽提供内容的时候,我们可以在一个 <template> 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称:

父级,把需要的内容传进去

<base-layout>
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>

  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <template v-slot:footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>
现在 <template> 元素中的所有内容都将会被传入相应的插槽。任何没有被包裹在带有 v-slot 的 <template> 中的内容都会被视为默认插槽的内容。
       -->
      <el-dropdown-item
        v-for="item of sizeOptions"
        :key="item.value"
        :disabled="size === item.value"
        :command="item.value"
      >
        <!-- item.value 传给 handleSetSize
      @command="handleSetSize"
      :command="item.value"

      disabled 属性规定应该禁用 input 元素。
被禁用的 input 元素既不可用,也不可点击。可以设置 disabled 属性,
直到满足某些其他的条件为止(比如选择了一个复选框等等)。
然后,就需要通过 JavaScript 来删除 disabled 值,将 input 元素的值切换为可用。
就是当前的值 如果是size === item.value
则禁止点击这个选项。
      -->
        {{ item.label }}
      </el-dropdown-item>
    </el-dropdown-menu>
  </el-dropdown>
</template>
<script>
export default {
  data() {
    return {
      // 尺寸元素
      sizeOptions: [
        { label: "Default", value: "default" },
        { label: "Medium", value: "medium" },
        { label: "Small", value: "small" },
        { label: "Mini", value: "mini" }
      ]
    };
  },
  computed: {
    size() {
      return this.$store.getters.size; //返回当前尺寸大小
    }
  },
  methods: {
    //参考 https://segmentfault.com/q/1010000021461812
    handleSetSize(size) {
      this.$ELEMENT.size = size; //这是 Element-UI 向 Vue 暴露的实例属性
      this.$store.dispatch("app/setSize", size); //把当前大小更新到 store
      this.refreshView(); //主要为了及时当前页面生效,做了一个 replace
      this.$message({
        message: "Switch Size Success",
        type: "success"
      });
    },
    refreshView() {
      //刷新视图
      // In order to make the cached page re-rendered
      this.$store.dispatch("tagsView/delAllCachedViews", this.$route); //删除当前路径所有缓存视图
      const { fullPath } = this.$route; //获取全路径
      // 页面刷新  https://blog.csdn.net/liubangbo/article/details/103333959
      /**
     * 在vue项目中,经常会遇到需要刷新当前页面的需求。
      因为vue-router判断如果路由没有变化,是不会刷新页面获取数据的。

      方式1:go(0)和reload()
      通过location.reload()或是this.$router.go(0)两种强制刷新方式,相当于按F5,会出现瞬间白屏,体验差,不推荐。

      方式2:定义一个空白路由页面,路由跳转到该空白页后立马跳回当前页,实现路由刷新。
      在router路由表中定义一个空白路由,

      // 强制刷新当前页所用的中间跳转页
  这种方式,基本上能够应付绝大多数情况,推荐使用。
但是,有时候,有一些极端情况下,这种刷新不起作用,而又不想用第一种那种毛子般的简单粗暴的方式的话,下面的方式可以选择使用。

方式3:provede/inject 方式
vue官方文档说了,这个依赖注入方式是给插件开发使用的,普通应用中不推荐使用。
但是,效果却很好。
原理就是通过依赖注入的方式,在顶部app通过v-if的显示隐藏来强制切换显示,以此来让vue重新渲染整个页面,app中通过provide方式定义的reload方法,在它的后代组件中,无论嵌套多深,都能够触发调用这个方法。具体说明查看官方文档。
这种方式刷新,虽然官方说不推荐,但是反正效果挺好,有些方式2解决不了的刷新问题,这个方式能解决。慎用。

本文采用的就是第三种
     */
      this.$nextTick(() => {
        //this.$nextTick()是在数据完成更新后立即获取数据
        this.$router.replace({
          //当遇到你需要刷新页面的情况,你就手动重定向页面到redirect页面,
          // 它会将页面重新redirect重定向回来,由于页面的 key 发生了变化,从而间接实现了刷新页面组件的效果。
          path: "/redirect" + fullPath
        });
      });
    }
  }
};
</script>
总体就是通过添加下拉框选中需要选的尺寸大小,传给 Element UI 暴露出来的尺寸修改
参数进行修改。

持续更新ing

  • 6
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
要隐藏若依框架的侧边栏和顶部导航栏,可以按照以下步骤进行操作。 1. 参考[1]中的解决方案,可以使用CSS样式来隐藏侧边栏。在HTML文件中,为侧边栏容器元素添加一个类名,例如`sidebar-container`,然后通过JavaScript代码为该元素设置`display: none`样式来隐藏侧边栏。 2. 同时,你还可以通过在HTML文件中添加一个专门用于响应单击和双击事件的元素,例如`datashow-page`,并在Vue的`methods`中定义相应的单击和双击事件处理函数。在单击事件处理函数中,通过获取侧边栏容器元素,并设置其样式为`display: none`来隐藏侧边栏;在双击事件处理函数中,通过获取侧边栏容器元素,并设置其样式为`display: block`来显示侧边栏。 3. 参考中的引用,可以通过Vuex状态管理工具中的一个异步操作来控制导航栏是否显示。具体来说,可以查找Vuex中控制导航栏显示的异步操作的名称,然后在需要隐藏导航栏的页面中调用该异步操作,并将其参数设置为`true`。 综上所述,通过组合使用CSS样式、JavaScript事件处理和Vuex状态管理,你可以实现若依框架中导航栏的隐藏效果。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Vue单击双击事件,若依框架隐藏侧边栏](https://blog.csdn.net/someday____/article/details/128092646)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [若依框架隐藏侧边导航栏](https://blog.csdn.net/Yly147/article/details/126724205)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值