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

文章目录

  • 前言
    • 1. Hamburger
      • 1. 看看源码hamburger
      • 2. 看看Hamburger.vue
      • 3. 看看toggleSideBar()
      • 4. 看看 app/toggleSideBar
      • 5. 看看:is-active="sidebar.opened"
    • 2. 看看Breadcrumb
      • 1. 看看源码
      • 2. 看看Breadcrumb.vue
    • 3. 看看这个搜索框
    • 4. 看看ruo-yi-git
      • 看看Ruo-yi-git.vue
    • 5. 看看ruoyi-doc
      • 看看Ruo-yi-doc.vue
    • 6.看看全屏
      • 看看Screenfull.vue
    • 7. 看看布局大小
      • 看看入口文件 main.js
      • 看看SizeSelect.vue
    • 持续更新ing

前言

上节我们把首页分为三大部分,这节讲b部分的主体Navbar部分
涛涛的若依学习笔记——首页是怎样实现的?——b部分Navbar_第1张图片

模块 对应的代码
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中的对应值。

打开图
涛涛的若依学习笔记——首页是怎样实现的?——b部分Navbar_第2张图片
关闭图
涛涛的若依学习笔记——首页是怎样实现的?——b部分Navbar_第3张图片

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

:is-active 是绑定的动态属性,通过prop父子传值,对子组件样式进行改变
根据传来值的不同
涛涛的若依学习笔记——首页是怎样实现的?——b部分Navbar_第4张图片
isActive的值会对应变化
涛涛的若依学习笔记——首页是怎样实现的?——b部分Navbar_第5张图片
则此处显示的打开和关闭的图标是不一样的
在这里插入图片描述
注意: 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
上文提到的
涛涛的若依学习笔记——首页是怎样实现的?——b部分Navbar_第6张图片
综上所述,点击 小图标 ,会把store里存的sidebar值进行取反,并修改cookie里存的sidebarState的值,并且子组件小图标会 动态变换。

2. 看看Breadcrumb

1. 看看源码

是针对这个标签的显示
涛涛的若依学习笔记——首页是怎样实现的?——b部分Navbar_第7张图片

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

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

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

涛涛的若依学习笔记——首页是怎样实现的?——b部分Navbar_第8张图片

2. 看看Breadcrumb.vue

在此之前,先看看人家官网用法
涛涛的若依学习笔记——首页是怎样实现的?——b部分Navbar_第9张图片
涛涛的若依学习笔记——首页是怎样实现的?——b部分Navbar_第10张图片
还是蛮复杂的

<template>
  <el-breadcrumb class="app-breadcrumb" separator="/">
    <transition-group name="breadcrumb">
      
      <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"
          >
          
          {{ item.meta.title }}
          span
        >
        
        
        <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">
    
    <svg-icon
      class-name="search-icon"
      icon-class="search"
      @click.stop="click"
    />
    
    <el-select
      ref="headerSearchSelect"
      v-model="search"
      :remote-method="querySearch"
      filterable
      default-first-option
      remote
      placeholder="Search"
      class="header-search-select"
      @change="change"
    >
      
      <el-option
        v-for="option in options"
        :key="option.item.path"
        :value="option.item"
        :label="option.item.title.join(' > ')"
      />
      
    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	在每个 

的内容之后插入内容。 ::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>
       

看看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"
    />
    
  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">
    
    <div>
      <svg-icon class-name="size-icon" icon-class="size" />
    div>
    
    <el-dropdown-menu slot="dropdown">
      
      <el-dropdown-item
        v-for="item of sizeOptions"
        :key="item.value"
        :disabled="size === item.value"
        :command="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

你可能感兴趣的:(若依,前端,vue.js,前端,javascript)