第三章:主页模块

195 阅读1分钟

🧑‍🎓 个人主页:SilenceLamb

🧭 本章内容:主页模块

🌳Gitee仓库地址:👉🏽主页模块


主页模块.png

一、顶部导航栏

image.png

🍑 顶部页面布局

<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},

🍑 关闭-打开-侧边栏

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和标题布局

image.png

<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>
<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>

🍵 整合页面

image.png

<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模块

image.png

🧭 页面布局

<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版本报错

image.png

 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 即可

image.png

  • 🍏vue.config.js
resolve: {
    fallback: {path: require.resolve("path-browserify")},
}

扫码_搜索联合传播样式-标准色版.png

📢🌥️如果文章对你有帮助【关注👍点赞❤️收藏⭐】