🧑🎓 个人主页:SilenceLamb
🧭 本章内容:主页模块
🌳Gitee仓库地址:👉🏽主页模块
一、顶部导航栏
🍑 顶部页面布局
<template>
<div class="navbar">
//引入面包屑
<div class="right-menu">
<el-dropdown class="avatar-container" trigger="click">
<div class="avatar-wrapper">
<img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar" alt="avatar">
<i class="el-icon-caret-bottom"/>
</div>
<el-dropdown-menu slot="dropdown" class="user-dropdown">
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</template>
🍑 引入面包屑
- 🍑 引入面包屑
<!--引入面包屑-->
<hamburger :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
<breadcrumb class="breadcrumb-container" />
- 🍑 引入组件
import Breadcrumb from '@/components/Breadcrumb'
import Hamburger from '@/components/Hamburger'
components: {Breadcrumb, Hamburger},
- 👉🏽 Breadcrumb 👉🏽Hamburger
🍑 关闭-打开-侧边栏
import Cookies from 'js-cookie'
const state = {
sidebar: {
opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
withoutAnimation: false
},
device: 'desktop',
}
const mutations = {
TOGGLE_SIDEBAR: state => {
state.sidebar.opened = !state.sidebar.opened
state.sidebar.withoutAnimation = false
if (state.sidebar.opened) {
Cookies.set('sidebarStatus', 1)
} else {
Cookies.set('sidebarStatus', 0)
}
},
CLOSE_SIDEBAR: (state, withoutAnimation) => {
Cookies.set('sidebarStatus', 0)
state.sidebar.opened = false
state.sidebar.withoutAnimation = withoutAnimation
},
TOGGLE_DEVICE: (state, device) => {
state.device = device
}
}
const actions = {
toggleSideBar({ commit }) {
commit('TOGGLE_SIDEBAR')
},
closeSideBar({ commit }, { withoutAnimation }) {
commit('CLOSE_SIDEBAR', withoutAnimation)
},
toggleDevice({ commit }, device) {
commit('TOGGLE_DEVICE', device)
},
}
export default {
namespaced: true,
state,
mutations,
actions
}
🍑 获取state中的值
const getters = {
sidebar: state => state.app.sidebar,
device: state => state.app.device,
}
export default getters
- 🍏可以通过以下方式从中获取值
import {mapGetters} from 'vuex'
computed: {
...mapGetters(['sidebar', 'avatar'])
},
🍑 创建侧边栏开关方法
export default {
components: {Breadcrumb, Hamburger},
computed: {
...mapGetters(['sidebar', 'avatar'])
},
methods: {
toggleSideBar() {
this.$store.dispatch('app/toggleSideBar')
},
}
}
- 🍓 添加顶部导航样式
- 👉 样式文件
<style rel="stylesheet/scss" lang="scss">
@import '@/layout/components/styles/Navbar.scss';
</style>
二、主页的左侧导航样式
🍎左侧logo和标题布局
<template>
<div class="sidebar-logo-container" :class="{'collapse':collapse}">
<transition name="sidebarLogoFade">
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" alt="logo">
<h1 v-else class="sidebar-title">{{ title }} </h1>
</router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" alt="logo">
<h1 class="sidebar-title">{{ title }} </h1>
</router-link>
</transition>
</div>
</template>
🍎设置logo和标题Action
import {title, logo, sidebarLogo, fixedHeader} from "@/setting/setting.js";
const state = {
title: title,
logo: logo,
sidebarLogo: sidebarLogo,
fixedHeader: fixedHeader,
}
const mutations = {
CHANGE_SETTING: (state, {key, value}) => {
if (state.hasOwnProperty(key)) {
state[key] = value
}
}
}
//异步执行
const actions = {
changeSetting({commit}, data) {
commit('CHANGE_SETTING', data)
},
// 设置网页标题
setTitle({commit}, title) {
state.title = title
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
🍎 获取store中的值
🌳 src/store/getters.js
title: state => state.setting.title,
logo: state => state.setting.logo,
🌳 获取值的方法
computed: {
...mapGetters(["title"]),
...mapGetters(["logo"])
}
- 🌳 src/layout/components/Sidebar/logo.vue
<script>
import {mapGetters} from "vuex"
export default {
name: 'SidebarLogo',
props: {
collapse: {
type: Boolean,
required: false,
}
},
computed: {
...mapGetters(["title"]),
...mapGetters(["logo"])
}
}
</script>
-
🍓 添加logo和标题样式
<style lang="scss" scoped>
@import "@/layout/components/styles/Sidebar-log.scss";
</style>
三、SidebarItem菜单栏
🍵 SidebarItem
<template>
<div v-if="!item.hidden">
<template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
<item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" />
</el-menu-item>
</app-link>
</template>
<el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
<template slot="title">
<item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
</template>
<sidebar-item
v-for="child in item.children"
:key="child.path"
:is-nest="true"
:item="child"
:base-path="resolvePath(child.path)"
class="nest-menu"
/>
</el-submenu>
</div>
</template>
🍵 Item.vue
<script>
export default {
name: 'MenuItem',
functional: true,
props: {
icon: {
type: String,
default: ''
},
title: {
type: String,
default: ''
}
},
render(h, context) {
const { icon, title } = context.props
const nodes = []
if (icon) {
if (icon.includes('el-icon')) {
nodes.push(<i class={[icon, 'sub-el-icon']} />)
} else {
nodes.push(<svg-icon icon-class={icon}/>)
}
}
if (title) {
if (title.length > 5) {
nodes.push(<span slot='title' title={(title)}>{(title)}</span>)
} else {
nodes.push(<span slot='title'>{(title)}</span>)
}
}
return nodes
}
}
</script>
🍵 Link.vue
<template>
<component :is="type" v-bind="linkProps(to)">
<slot/>
</component>
</template>
<script>
import {isExternal} from '@/utils/validate'
export default {
name: 'Link-view',
props: {
to: {
type: [String, Object],
required: true
}
},
computed: {
isExternal() {
return isExternal(this.to)
},
type() {
if (this.isExternal) {
return 'a'
}
return 'router-link'
}
},
methods: {
linkProps(to) {
if (this.isExternal) {
return {
href: to,
target: '_blank',
rel: 'noopener'
}
}
return {
to: to
}
}
}
}
</script>
🍵 整合页面
<template>
<div :class="{'has-logo':showLogo}">
<logo v-if="showLogo" :collapse="isCollapse"/>
<el-scrollbar :class="setting.sideTheme" wrap-class="scrollbar-wrapper">
<el-menu
:default-active="activeMenu"
:collapse="isCollapse"
:background-color="variables.menuBg"
:text-color="variables.menuText"
:unique-opened="true"
:active-text-color="setting.theme"
:collapse-transition="false"
mode="vertical"
>
<sidebar-item
v-for="(route, index) in sidebarRouters"
:key="route.path + index"
:item="route"
:base-path="route.path"
/>
</el-menu>
</el-scrollbar>
</div>
</template>
import {mapGetters, mapState} from 'vuex'
import Logo from './logo'
import SidebarItem from './SidebarItem'
import variables from '@/styles/variables.scss'
export default {
name: 'Sidebar-view',
components: {SidebarItem, Logo},
computed: {
...mapState(["setting"]),
...mapGetters(["sidebarRouters", "sidebar"]),
activeMenu() {
const {meta, path} = this.$route
if (meta.activeMenu) {
return meta.activeMenu
}
return path
},
showLogo() {
return this.$store.state.setting.sidebarLogo
},
variables() {
return variables
},
isCollapse() {
return !this.sidebar.opened
}
}
}
- 🍒 基础颜色src/styles/variables.scss**
// base color
$blue:#324157;
$light-blue:#3A71A8;
$red:#C03639;
$pink: #E65D6E;
$green: #30B08F;
$tiffany: #4AB7BD;
$yellow:#FEC171;
$panGreen: #30B08F;
//菜单文字颜色
$menuText: #ffffff;
//菜单选中之后的颜色
$menuActiveText: rgb(67, 217, 164);
//菜单悬停文字颜色
$menuHoverText: #51bd94;
//菜单背景颜色
$menuBg: rgb(64, 76, 91);
//菜单悬停背景颜色
$menuHoverBg: rgba(26, 39, 58, 0.45);
$sideBarWidth: 180px;
:export {
//菜单文字颜色
menuText:$menuText;
//菜单选中之后的颜色
menuActiveText: $menuActiveText;
//菜单悬停文字颜色
menuHoverText: $menuHoverText;
//菜单背景颜色
menuBg: $menuBg;
//菜单悬停背景颜色
menuHoverBg: $menuHoverBg;
sideBarWidth: $sideBarWidth;
}
四、AppMain模块
🧭 页面布局
<template>
<section class="app-main">
<transition name="fade-transform" mode="out-in">
<keep-alive :include="cachedViews">
<router-view v-if="!$route.meta.link" :key="key"/>
</keep-alive>
</transition>
<iframe-toggle/>
</section>
</template>
🧭 页面切换
默认情况下这两个页面切换时并不会触发 vue 的 created 或者 mounted 钩子,官方说你可以通过 watch $route 的变化来进行处理,但说真的还是蛮麻烦的。后来发现其实可以简单的在 router-view 上加上一个唯一的 key,来保证路由切换时都会重新渲染触发钩子了。
<script>
export default {
name: 'AppMain',
computed: {
key() {
return this.$route.path
}
}
}
</script>
- 🍑引入样式
五、主页模块
🕰️ 整体页面布局
<template>
<div :class="classObj" class="app-wrapper">
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
<sidebar class="sidebar-container" />
<div class="main-container">
<div :class="{'fixed-header':fixedHeader}">
<navbar />
</div>
</div>
</div>
</template>
- 🍑引入组件
export { default as AppMain } from './AppMain/AppMain'
export { default as Navbar } from './Navbar/Navbar'
export { default as Sidebar } from './Sidebar/index.vue'
<script>
import {AppMain, Navbar, Sidebar} from './components'
export default {
name: 'Layout',
components: {Navbar,Sidebar},
computed: {
sidebar() {
return this.$store.state.app.sidebar
},
device() {
return this.$store.state.app.device
},
fixedHeader() {
return this.$store.state.setting.fixedHeader
},
classObj() {
return {
hideSidebar: !this.sidebar.opened,
openSidebar: this.sidebar.opened,
withoutAnimation: this.sidebar.withoutAnimation,
mobile: this.device === 'mobile'
}
}
},
methods: {
handleClickOutside() {
this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
}
}
}
</script>
🕰️引入样式
- 引入样式 :👉🏽样式文件
<style lang="scss" scoped>
@import "~@/styles/mixin.scss";
@import "~@/styles/variables.scss";
@import "@/layout/components/styles/index.scss";
</style>
六、webpack5版本报错
webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it
-
🍑v4以前附带了许多node.js核心模块的polyfill,在构建时给 bundle附加了庞大的polyfills,在大部分情况下,polyfills并不是必须
-
🍑现在v5将要停止这一切,在模块的应用中不再自动引入Polyfills,明显的减小了打包体积。
v4 在v4中,crypto模块会主动添加 polyfill,也就是crypto-browserify,我们运行的代码是不需要的,反而会使最后的包变大,影响编译速度
v5 在v5编译中,会出现polyfill添加提示,如果不需要node polyfille,按照提示 alias 设置为 false 即可
- 🍏vue.config.js
resolve: {
fallback: {path: require.resolve("path-browserify")},
}
📢🌥️如果文章对你有帮助【关注👍点赞❤️收藏⭐】