【Vue3+Element Plus】从0-1搭建后台管理系统(04)-暗黑模式

2,117 阅读5分钟

【Vue3+Element Plus】从0-1搭建后台管理系统(04)-暗黑模式

这一章主要记录通过头部导航栏的开关实现暗黑模式与白昼模式的切换。

image.png

当开关上的图标显示太阳的时候,后台呈现为白昼模式其实就是以白色调为主的模式,显示为月亮时,呈现暗黑模式其实就是以暗色调为主的模式。

具体实现

起初去翻阅了Element-plus官方文档查看到是支持暗黑模式的,但是官方说的非常明白,想要动态切换暗黑模式的话推荐使用useDark进行实现。所以,使用useDark来实现暗黑模式动态切换的。

image.png

  • 首先我们要使用useDark这个工具函数的话,需要先安装vueuse:
    npm install @vueuse/core
    

image.png

  • 然后安装tailwind.css:

    1. npm install -D tailwindcss postcss autoprefixernpx 
      
      tailwindcss init -p
      
    2. 配置模板路径:

      在文件中添加所有模板文件的路径tailwind.config.js

      /** @type {import('tailwindcss').Config} */ 
      export default { 
          content: [ 
              "./index.html", 
              "./src/**/*.{vue,js,ts,jsx,tsx}", 
          ], 
          theme: { extend: {}, }, 
          plugins: [], 
      }
      
    3. 将 Tailwind 指令添加到您的 styles/index.scss 中:

      @tailwind base; 
      @tailwind components; 
      @tailwind utilities;
      
  • main.ts中引入Element-plus的暗黑模式样式表:

    /** element-plus暗黑主题 */
    import 'element-plus/theme-chalk/dark/css-vars.css'
    

代码:

DarkMode.vue:

<template>
  <el-switch
    v-model="darkMode"
    style="--el-switch-on-color: #0960bd; --el-switch-off-color: #f60"
    inline-prompt
    :active-icon="Moon"
    :inactive-icon="Sunny"
    @change="toggleDark"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { useDark, useToggle } from '@vueuse/core'
/** 引入Element-Plus图标 */
import { Sunny, Moon } from '@element-plus/icons-vue'
defineOptions({
  name: 'DarkMode'
})

/** 切换模式 */
const isDark = useDark()
const toggleDark = useToggle(isDark)
/** 是否切换为暗黑模式 */
const darkMode = ref(false)
</script>

<style lang="scss" scoped></style>

Nav.vue:

<div class="nav dark:bg-black/75 dark:text-white">
    <!-- 左侧区域 -->
    <div class="nav-left">
      <!-- 折叠图标 -->
      <iconify-icon
        :icon-name="isCollapse ? 'ant-design:menu-unfold-outlined' : 'ant-design:menu-fold-outlined'"
        @click="changeIsCpllapse"
      ></iconify-icon>
      <!-- 面包屑 -->
      <Breadcrumb />
    </div>
    <!-- 右侧区域 -->
    <ul class="nav-right">
      <!-- 搜索图标 -->
      <li class="nav-right__item">
        <Search />
      </li>
      <!-- 暗黑模式切换开关 -->
      <li class="nav-right__item">
        <DarkMode />
      </li>
      <!-- 中英文切换 -->
      <li class="nav-right__item">
        <Language />
      </li>
      <!-- 消息通知 -->
      <li class="nav-right__item">
        <Message />
      </li>
      <!-- 全屏 -->
      <li class="nav-right__item">
        <FullScreen />
      </li>
      <!-- 用户头像和信息 -->
      <li class="nav-right__item nav-right__user">
        <UserAvatar />
      </li>
    </ul>
</div>

dark:bg-black/75:表示html根元素上有dark这个类的话,那么背景颜色为黑色

dark:text-white:表示html根元素上有dark这个类的话,那么字体颜色就为白色

但是!但是!但是!出现bug了!

使用了useDark后,我惊奇的发现el-switch开关的过渡效果没了!

dark.gif

问了度娘后找到了解决方案,所以又换成了下面的这种方式来完成暗黑模式的切换:

<script setup lang="ts">
import { ref } from 'vue'
/** 引入Element-Plus图标 */
import { Sunny, Moon } from '@element-plus/icons-vue'
defineOptions({
  name: 'DarkMode'
})

/** 是否切换为暗黑模式 */
const darkMode = ref(false)

/** switch开关值发生变化触发的回调,用来控制html元素类的切换 */
const toggleDark = (): void => {
  document.documentElement.classList.toggle('dark')
}
</script>

效果:

dark.gif

换成这种方式以后,switch开关的过渡效果又正常啦~

刷新后保持切换的模式

当刷新浏览器后,暗黑模式又变回到了之前的白天模式了。所以我们得让暗黑模式这个状态保持持久化,不管浏览器怎么刷新都还是保持暗黑模式。

  • 实现思路:

    1. 首先想到的是将暗黑模式的这个状态(类名)存储到本地缓存中去
    2. 刷新的时候从缓存中获取暗黑模式的状态,并给html设置类名
    3. 在切换模式的时候设置本地缓存中去模式的状态(类名)
  • 具体实现:

    <script lang="ts" setup>
    /** 当前模式,初始值:如果缓存中有htmlModeClass的值否则取系统当前所处的模式 */
    const htmlModeClass = getCacheOfLocal('htmlModeClass')
    
    /** 开关绑定的值 */
    const isDark = ref(!!htmlModeClass)
    
    /** 组件在页面挂载(渲染)完毕时触发 */
    onMounted(() => {
      /** 组件渲染完毕就设置html类名 */
      document.documentElement.className = htmlModeClass
    })
    
    /** 切换模式 */
    const toggleDark = () => {
        /** 向本地缓存中添加htmlModeClass,来记录当前所处模式 */
        /** 如果isDark为true,那么表示为暗黑模式,反之雪白模式 */
        isDark.value ? setCacheOfLocal('htmlModeClass', 'dark') : setCacheOfLocal('htmlModeClass', '')
      ...
    }
    </script>
    

darkmode.gif

跟随系统

当一进来后台跟随系统的模式,系统是暗黑模式则后台也呈现出暗黑模式。

Tips:

可以通过window.matchMedia('(prefers-color-schem:dark)').matches来获取系统当前是否处于暗黑模式

实现思路

  1. 优先先取系统的模式
  2. 如果本地缓存中记录了用户切换的模式,那么取本地记录的模式

代码

/** 当前系统所处模式 */
const systemDarkMode = window.matchMedia('(prefers-color-schem:dark)').matches ? 'dark' : ''

/** 当前模式,初始值:如果缓存中有htmlModeClass的值否则取系统当前所处的模式 */
const htmlModeClass = getCacheOfLocal('htmlModeClass') ?? systemDarkMode

封装useDark为hook函数

src/hooks目录下新建useDark.ts:

import { onMounted, ref, type Ref } from 'vue'
import { setCacheOfLocal, getCacheOfLocal } from '@/utils/cache'
/** useDark函数的返回值类型 */
type UseDark = (isDark: boolean) => void

/** 当前系统所处模式 */
const systemDarkMode = window.matchMedia('(prefers-color-schem:dark)').matches ? 'dark' : ''

/** 当前模式,初始值:如果缓存中有htmlModeClass的值否则取系统当前所处的模式 */
const htmlModeClass = getCacheOfLocal('htmlModeClass') ?? systemDarkMode

/** 初始化开关绑定的值 */
export const useDark = (): Ref<boolean> => {
  /** 开关绑定的值 */
  const isDark = ref(!!htmlModeClass)
  /** 是否切换为暗黑模式,初始值为当前模式,当前模式为dark则为true,否则为false */
  return isDark
}

/** 返回 */
export const useToggle = (isDark: Ref<boolean>): UseDark => {
  /** 组件在页面挂载(渲染)完毕时触发 */
  onMounted(() => {
    /** 组件渲染完毕就设置html类名 */

    document.documentElement.className = htmlModeClass
  })

  return function () {
    /** 向本地缓存中添加htmlModeClass,来记录当前所处模式 */
    /** 如果isDark为true,那么表示为暗黑模式,反之雪白模式 */
    isDark.value ? setCacheOfLocal('htmlModeClass', 'dark') : setCacheOfLocal('htmlModeClass', '')

    /** 设置html元素的类名 */
    document.documentElement.classList.toggle('dark')
  }
}

在组件中使用

<script setup lang="ts">
/** 引入Element-Plus图标 */
import { Sunny, Moon } from '@element-plus/icons-vue'
/** 引入暗黑模式hook函数 */
import { useDark, useToggle } from '@/hooks/useDark'
defineOptions({
  name: 'DarkMode'
})
/** 是否切换为暗黑模式,初始值为当前模式,当前模式为dark则为true,否则为false */
const isDark = useDark()

/** 开关值发生变化事件的事件处理函数 */
const toggleDark = useToggle(isDark)
</script>