对于element组件size属性尺寸共有 default medium small mini这四种

效果图
image.png
image.png

2-1 创建sizeSelect组件

切换element size主要通过动态修改$ELEMENT.size 为default | medium | small | mini

我们已经挂载到 vue组件实例上 可通过组件实例直接修改

  1. // 全局配置 https://element-plus.gitee.io/#/zh-CN/component/quickstart#quan-ju-pei-zhi
  2. // 该对象目前支持 size 与 zIndex 字段。size 用于改变组件的默认尺寸 small,zIndex 设置弹框的初始 z-index(默认值:2000)。
  3. app.config.globalProperties.$ELEMENT = {
  4. size: options.size
  5. }

导入相关elemment 组件

src/plugins/element.ts
image.png

创建sizeSelect组件

src/components/SizeSelect/index.vue

  1. <template>
  2. <div>
  3. <el-dropdown trigger="click" @command="handleSize">
  4. <div>
  5. <svg-icon class-name="size-icon" icon-class="size"></svg-icon>
  6. </div>
  7. <template #dropdown>
  8. <el-dropdown-menu>
  9. <el-dropdown-item
  10. v-for="item in sizeOptions"
  11. :key="item.value"
  12. :command="item.value"
  13. :disabled="item.value === size"
  14. >
  15. {{ item.label }}
  16. </el-dropdown-item>
  17. </el-dropdown-menu>
  18. </template>
  19. </el-dropdown>
  20. </div>
  21. </template>
  22. <script lang="ts">
  23. import { Size } from '@/plugins/element'
  24. import {
  25. defineComponent,
  26. ref,
  27. getCurrentInstance,
  28. ComponentInternalInstance,
  29. ComponentPublicInstance,
  30. computed,
  31. nextTick
  32. } from 'vue'
  33. import { useRoute, useRouter } from 'vue-router'
  34. import { useStore } from '@/store'
  35. export default defineComponent({
  36. name: 'SizeSelect',
  37. setup() {
  38. const store = useStore()
  39. const route = useRoute()
  40. const router = useRouter()
  41. const { proxy } = getCurrentInstance() as ComponentInternalInstance
  42. // store中获取size 这里需要注意通过computed获取store状态 确保获取到正确更新
  43. const size = computed(() => store.getters.size)
  44. // element size 选项
  45. const sizeOptions = ref([
  46. { label: 'Default', value: 'default' },
  47. { label: 'Medium', value: 'medium' },
  48. { label: 'Small', value: 'small' },
  49. { label: 'Mini', value: 'mini' }
  50. ])
  51. // 刷新当前路由
  52. const refreshView = () => {
  53. const { fullPath } = route
  54. nextTick(() => {
  55. // 重定向到中间页 实现vue中当前路由刷新
  56. router.replace({
  57. path: '/redirect' + fullPath
  58. })
  59. })
  60. }
  61. // command 获取点击按钮的command属性值 作为size值
  62. const handleSize = (command: Size) => {
  63. // 修改element-plus组件尺寸
  64. (proxy as ComponentPublicInstance).$ELEMENT.size = command
  65. // 更新store
  66. store.dispatch('app/setSize', command)
  67. // 切换size需要刷新路由才能生效
  68. refreshView()
  69. proxy?.$message.success({
  70. type: 'success',
  71. message: 'Switch Size Success'
  72. })
  73. }
  74. return {
  75. sizeOptions,
  76. size,
  77. handleSize
  78. }
  79. }
  80. })
  81. </script>
  82. <style lang="scss">
  83. .size-icon {
  84. font-size: 18px;
  85. }
  86. </style>

navbar导入组件并调整样式

image.png
navbar样式调整
image.png
src/layout/components/Navbar.vue

  1. <template>
  2. <div class="navbar">
  3. <hambuger @toggleClick="toggleSidebar" :is-active="sidebar.opened"/>
  4. <breadcrumb />
  5. <div class="right-menu">
  6. <!-- 全屏 -->
  7. <screenfull id="screefull" class="right-menu-item hover-effect" />
  8. <!-- element组件size切换 -->
  9. <el-tooltip content="Global Size" effect="dark" placement="bottom">
  10. <size-select class="right-menu-item hover-effect" />
  11. </el-tooltip>
  12. </div>
  13. </div>
  14. </template>
  15. <script lang="ts">
  16. import { defineComponent, computed } from 'vue'
  17. import Breadcrumb from '@/components/Breadcrumb/index.vue'
  18. import Hambuger from '@/components/Hambuger/index.vue'
  19. import { useStore } from '@/store/index'
  20. import Screenfull from '@/components/Screenfull/index.vue'
  21. import SizeSelect from '@/components/SizeSelect/index.vue'
  22. export default defineComponent({
  23. name: 'Navbar',
  24. components: {
  25. Breadcrumb,
  26. Hambuger,
  27. Screenfull,
  28. SizeSelect
  29. },
  30. setup() {
  31. // 使用我们自定义的useStore 具备类型提示
  32. // store.state.app.sidebar 对于getters里的属性没有类型提示
  33. const store = useStore()
  34. const toggleSidebar = () => {
  35. store.dispatch('app/toggleSidebar')
  36. }
  37. // 从getters中获取sidebar
  38. const sidebar = computed(() => store.getters.sidebar)
  39. return {
  40. toggleSidebar,
  41. sidebar
  42. }
  43. }
  44. })
  45. </script>
  46. <style lang="scss">
  47. .navbar {
  48. display: flex;
  49. .right-menu {
  50. flex: 1;
  51. display: flex;
  52. align-items: center;
  53. justify-content: flex-end;
  54. padding-right: 15px;
  55. &-item {
  56. padding: 0 8px;
  57. font-size: 18px;
  58. color: #5a5e66;
  59. vertical-align: text-bottom;
  60. &.hover-effect {
  61. cursor: pointer;
  62. transition: background .3s;
  63. &:hover {
  64. background: rgba(0, 0, 0, .025);
  65. }
  66. }
  67. }
  68. }
  69. }
  70. </style>

当每次切换size时,不仅需要修改$ELEMENT.size 还要让当前路由刷新才能生效。

2-2 size属性添加到store

修改src/store/modules/app.ts

添加 state size、actions setSize、mutations SET_SIZE

  1. import { Size } from '@/plugins/element'
  2. import { ActionTree, Module, MutationTree } from 'vuex'
  3. import { IRootState } from '../index'
  4. // 定义app里state类型
  5. export interface IAppState {
  6. sidebar: {
  7. opened: boolean
  8. },
  9. size: Size
  10. }
  11. // 定义mutations
  12. const mutations: MutationTree<IAppState> = {
  13. TOGGLE_SIDEBAR(state) {
  14. state.sidebar.opened = !state.sidebar.opened
  15. },
  16. SET_SIZE(state, size: Size) {
  17. state.size = size
  18. }
  19. }
  20. // 定义actions
  21. const actions: ActionTree<IAppState, IRootState> = {
  22. toggleSidebar({ commit }) {
  23. commit('TOGGLE_SIDEBAR')
  24. },
  25. setSize({ commit }, size: Size) {
  26. commit('SET_SIZE', size)
  27. }
  28. }
  29. // 定义module
  30. const app: Module<IAppState, IRootState> = {
  31. namespaced: true,
  32. state: {
  33. sidebar: {
  34. opened: true
  35. },
  36. size: 'medium'
  37. },
  38. mutations,
  39. actions
  40. }
  41. export default app

sotre缓存中将size缓存

修改src/store/index.ts
image.png

getters中添加size

image.png

2-3 实现当前路由刷新

解决一下两点问题
- vue中通过一个重定向中间页实现当前路由刷新 利用路由router.replace
- 实现当前路由刷新 路由不能被keep-alive缓存 目前我们是默认全部缓存

添加redirect路由

创建redirect 路由组件
src/views/redirect/index.vue

  1. <script lang="ts">
  2. import { h } from 'vue'
  3. import { useRoute, useRouter } from 'vue-router'
  4. export default {
  5. name: 'Redirect',
  6. setup() {
  7. const route = useRoute()
  8. const router = useRouter()
  9. const { query, params } = route
  10. router.replace({
  11. path: '/' + params.path,
  12. query
  13. })
  14. return () => {
  15. return h('template')
  16. }
  17. }
  18. }
  19. </script>

注册路由

src/router/index.ts
image.png

  1. import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
  2. import Layout from '@/layout/index.vue'
  3. // 看作是异步获取路由
  4. export const asyncRoutes: Array<RouteRecordRaw> = [
  5. {
  6. path: '/documentation',
  7. component: Layout, // 布局组件作为一级路由
  8. redirect: '/documentation/index',
  9. children: [
  10. {
  11. path: 'index',
  12. name: 'Documentation',
  13. component: () => import(/* webpackChunkName: "documentation" */ '@/views/documentation/index.vue'),
  14. meta: {
  15. title: 'Documentation',
  16. icon: 'documentation',
  17. hidden: false // 菜单栏不显示
  18. }
  19. }
  20. ]
  21. },
  22. {
  23. path: '/guide',
  24. component: Layout,
  25. redirect: '/guide/index',
  26. children: [
  27. {
  28. path: 'index',
  29. name: 'Guide',
  30. component: () => import(/* webpackChunkName: "guide" */ '@/views/guide/index.vue'),
  31. meta: {
  32. title: 'Guide',
  33. icon: 'guide'
  34. // 当guide路由激活时高亮选中的是 documentation/index菜单
  35. // activeMenu: '/documentation/index'
  36. }
  37. }
  38. ]
  39. },
  40. {
  41. path: '/system',
  42. component: Layout,
  43. redirect: '/system/user',
  44. meta: {
  45. title: 'System',
  46. icon: 'lock',
  47. alwaysShow: true // 根路由始终显示 哪怕只有一个子路由
  48. },
  49. children: [
  50. {
  51. path: 'menu',
  52. component: () => import(/* webpackChunkName: "menu" */ '@/views/system/menu.vue'),
  53. meta: {
  54. title: 'Menu Management',
  55. hidden: false,
  56. breadcrumb: false
  57. }
  58. },
  59. {
  60. path: 'role',
  61. component: () => import(/* webpackChunkName: "role" */ '@/views/system/role.vue'),
  62. meta: {
  63. title: 'Role Management',
  64. hidden: false
  65. }
  66. },
  67. {
  68. path: 'user',
  69. component: () => import(/* webpackChunkName: "user" */ '@/views/system/user.vue'),
  70. meta: {
  71. title: 'User Management'
  72. }
  73. }
  74. ]
  75. },
  76. { // 外链路由
  77. path: '/external-link',
  78. component: Layout,
  79. children: [
  80. {
  81. path: 'https://www.baidu.com/',
  82. redirect: '/',
  83. meta: {
  84. title: 'External Link',
  85. icon: 'link'
  86. }
  87. }
  88. ]
  89. },
  90. { // 404一定放在要在最后面
  91. path: '/:pathMatch(.*)*',
  92. redirect: '/404',
  93. meta: {
  94. hidden: true
  95. }
  96. }
  97. ]
  98. export const constantRoutes: Array<RouteRecordRaw> = [
  99. {
  100. path: '/',
  101. component: Layout,
  102. redirect: '/dashboard',
  103. children: [
  104. {
  105. path: 'dashboard',
  106. name: 'Dashboard',
  107. component: () => import(/* webpackChunkName: "dashboard" */ '@/views/dashboard/index.vue'),
  108. meta: {
  109. title: 'Dashboard',
  110. // icon: 'dashboard'
  111. icon: 'el-icon-platform-eleme'
  112. }
  113. }
  114. ]
  115. },
  116. {
  117. path: '/redirect',
  118. component: Layout,
  119. meta: {
  120. hidden: true
  121. },
  122. children: [
  123. { // 带参数的动态路由正则匹配 文档说明
  124. // https://next.router.vuejs.org/zh/guide/essentials/route-matching-syntax.html#%E5%8F%AF%E9%87%8D%E5%A4%8D%E7%9A%84%E5%8F%82%E6%95%B0
  125. path: '/redirect/:path(.*)', // 要匹配多级路由 应该加*号
  126. component: () => import('@/views/redirect/index.vue')
  127. }
  128. ]
  129. },
  130. {
  131. path: '/401',
  132. component: Layout,
  133. children: [
  134. {
  135. path: '',
  136. component: () => import('@/views/error-page/401.vue'),
  137. meta: {
  138. title: '401',
  139. icon: '404',
  140. hidden: true
  141. }
  142. }
  143. ]
  144. },
  145. {
  146. path: '/404',
  147. component: () => import('@/views/error-page/404.vue'),
  148. meta: {
  149. hidden: true // 404 hidden掉
  150. }
  151. }
  152. ]
  153. export const routes = [
  154. ...constantRoutes,
  155. ...asyncRoutes
  156. ]
  157. const router = createRouter({
  158. history: createWebHashHistory(),
  159. routes
  160. })
  161. export default router

解决keep-alive缓存问题

暂时先给keep-alive添加 include属性为空数组

image.png
修改src/layout/components/AppMain.vue

  1. <template>
  2. <div class="app-main">
  3. <!-- vue3 路由缓存 https://next.router.vuejs.org/guide/migration/index.html#router-view-keep-alive-and-transition -->
  4. <router-view v-slot={Component}>
  5. <transition name="fade-transform" mode="out-in">
  6. <keep-alive :include="cachedViews">
  7. <component :is="Component" :key="key" />
  8. </keep-alive>
  9. </transition>
  10. </router-view>
  11. </div>
  12. </template>
  13. <script lang="ts">
  14. import { computed, defineComponent, ref } from 'vue'
  15. import { useRoute } from 'vue-router'
  16. export default defineComponent({
  17. name: 'AppMain',
  18. setup() {
  19. const route = useRoute()
  20. const key = computed(() => route.path)
  21. // 缓存路由集合 暂时先是空数组 后面会放到store
  22. const cachedViews = ref([])
  23. return {
  24. key,
  25. cachedViews
  26. }
  27. }
  28. })
  29. </script>
  30. <style lang="scss" scoped>
  31. .app-main {
  32. /* navbar 50px */
  33. min-height: calc(100vh - 50px);
  34. }
  35. .fade-transform-enter-active,
  36. .fade-transform-leave-active {
  37. transition: all .5s;
  38. }
  39. .fade-transform-enter-from {
  40. opacity: 0;
  41. transform: translateX(-30px);
  42. }
  43. .fade-transform-leave-to {
  44. opacity: 0;
  45. transform: translateX(30px);
  46. }
  47. </style>

2-4 插件size统一从store里获取

利用vue plugin进行参数传递

image.png
修改src/main.ts

  1. import { createApp } from 'vue'
  2. import App from './App.vue'
  3. import router from './router'
  4. import store, { key } from './store'
  5. // 初始化css
  6. import 'normalize.css/normalize.css'
  7. // element-plus
  8. import installElementPlus, { Size } from './plugins/element'
  9. // 挂载到vue实例上
  10. import { ElMessageBox, ElMessage, ElNotification } from 'element-plus'
  11. // 全局 css
  12. import '@/styles/index.scss'
  13. // svg icons
  14. import initSvgIcon from '@/icons/index'
  15. const app = createApp(App)
  16. // 获取store里存储的size
  17. const size = store.state.app.size
  18. app
  19. .use(store, key)
  20. .use(router)
  21. .use(installElementPlus, {
  22. size
  23. })
  24. .use(initSvgIcon)
  25. .mount('#app')
  26. /**
  27. * 相关issue问题
  28. * Why not on the d.ts use it ?
  29. (为什么不能在shims-d.ts 中设置这个?
  30. * https://github.com/vuejs/vue-next/pull/982
  31. */
  32. // 挂载到vue实例上
  33. declare module '@vue/runtime-core' {
  34. interface ComponentCustomProperties {
  35. $message: typeof ElMessage;
  36. $notify: typeof ElNotification;
  37. $confirm: typeof ElMessageBox.confirm;
  38. $alert: typeof ElMessageBox.alert;
  39. $prompt: typeof ElMessageBox.prompt;
  40. $ELEMENT: {
  41. size: Size;
  42. };
  43. }
  44. }

修改element.ts接收plugin参数
image.png

  1. import { App } from 'vue'
  2. import {
  3. locale,
  4. ElButton,
  5. ElMessage,
  6. ElNotification,
  7. ElMessageBox,
  8. ElMenu,
  9. ElMenuItem,
  10. ElSubmenu,
  11. ElRow,
  12. ElCol,
  13. ElBreadcrumb,
  14. ElBreadcrumbItem,
  15. ElTooltip,
  16. ElDropdown,
  17. ElDropdownMenu,
  18. ElDropdownItem
  19. } from 'element-plus'
  20. import 'element-plus/lib/theme-chalk/index.css'
  21. // Element Plus 组件内部默认使用英语
  22. // https://element-plus.gitee.io/#/zh-CN/component/i18n
  23. import lang from 'element-plus/lib/locale/lang/zh-cn'
  24. // Element Plus 直接使用了 Day.js 项目的时间日期国际化设置, 并且会自动全局设置已经导入的 Day.js 国际化配置。
  25. import 'dayjs/locale/zh-cn'
  26. // $ELEMENT size属性类型
  27. export type Size = 'default' | 'medium' | 'small' | 'mini'
  28. interface ElementOptions {
  29. size: Size
  30. }
  31. export default (app: App, options: ElementOptions): void => {
  32. locale(lang)
  33. // 按需导入组件列表
  34. const components = [
  35. ElButton,
  36. ElMessage,
  37. ElNotification,
  38. ElMessageBox,
  39. ElMenu,
  40. ElMenuItem,
  41. ElSubmenu,
  42. ElRow,
  43. ElCol,
  44. ElBreadcrumb,
  45. ElBreadcrumbItem,
  46. ElTooltip,
  47. ElDropdown,
  48. ElDropdownMenu,
  49. ElDropdownItem
  50. ]
  51. components.forEach(component => {
  52. app.component(component.name, component)
  53. })
  54. // Vue.prototype 替换为 config.globalProperties
  55. // 文档说明 https://v3.cn.vuejs.org/guide/migration/global-api.html#vue-prototype-%E6%9B%BF%E6%8D%A2%E4%B8%BA-config-globalproperties
  56. app.config.globalProperties.$message = ElMessage
  57. app.config.globalProperties.$notify = ElNotification
  58. app.config.globalProperties.$confirm = ElMessageBox.confirm
  59. app.config.globalProperties.$alert = ElMessageBox.alert
  60. app.config.globalProperties.$prompt = ElMessageBox.prompt
  61. // 全局配置 https://element-plus.gitee.io/#/zh-CN/component/quickstart#quan-ju-pei-zhi
  62. // 该对象目前支持 size 与 zIndex 字段。size 用于改变组件的默认尺寸 small,zIndex 设置弹框的初始 z-index(默认值:2000)。
  63. app.config.globalProperties.$ELEMENT = {
  64. size: options.size
  65. }
  66. }

2-5 测试

dashboard页面导入button组件

src/views/dashboard/index.vue

  1. <template>
  2. <div>
  3. <h1>Dashboard page</h1>
  4. 缓存测试 <input type="text">
  5. <el-button type="primary">size改变</el-button>
  6. </div>
  7. </template>
  8. <script>
  9. export default {
  10. name: 'Dashboard'
  11. }
  12. </script>
  13. <style lang="scss">
  14. .custom-class { // 自定义样式404
  15. font-size: 200px;
  16. color: green;
  17. }
  18. </style>

本节参考源码

https://gitee.com/brolly/vue3-element-admin/commit/1c1d7cc4c41ea4fe0dc4cf61fed9efc5bfec9b36