上节我们把首页分为三大部分,这节讲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'
引入了一堆组件
具体每个是什么作用呢,一个个看看
这是左上角的小图标
<hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
显然,这是一个组件,在下面有引入
import Hamburger from '@/components/Hamburger'
<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”
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
仅保留相关代码
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中的对应值。
: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的值,并且子组件小图标会 动态变换。
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!topNav"/>
显然,只有topNav=false时显示,即不开启TopNav时显示
computed: {
topNav: {
get() {
return this.$store.state.settings.topNav
}
}
}
<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>
<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,是一个轻量级的模糊搜索
整体流程就是
盲猜一下,这个好解读
<el-tooltip content="源码地址" effect="dark" placement="bottom">
<ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />
el-tooltip>
<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>
<el-tooltip content="源码地址" effect="dark" placement="bottom">
<ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />
el-tooltip>
<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>
<screenfull id="screenfull" class="right-menu-item hover-effect" />
<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>
<el-tooltip content="布局大小" effect="dark" placement="bottom">
<size-select id="size-select" class="right-menu-item hover-effect" />
el-tooltip>
Vue.use(Element, {// 设置默认和刷新浏览器设置为你指定的大小
size: Cookies.get('size') || 'medium' // set element-ui default size
})
<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 暴露出来的尺寸修改
参数进行修改。