Vue3+Pinia实现持久化动态主题切换

news2024/11/16 10:29:46

PC端主题切换大家都用过,下面用Vue3+Pinia实现一下这个过程;
【源码地址】

在这里插入图片描述

1、准备工作

npm install pinia
npm install pinia-plugin-persist

image.png

2、基础配置

// main.js
import { createApp } from 'vue'
import App from './App.vue'
import bootstrap from "../bootstrap";
import { createPinia } from 'pinia'
import piniaPluginPersist from 'pinia-plugin-persist';

const app = createApp(App);
const store = createPinia()
store.use(piniaPluginPersist);

app.use(store);

// APP.vue
<template>
  <div>
    <a-config-provider :locale="locale" :theme="{ token: { colorPrimary: themeState.themes && themeState.currTheme ? themeState.themes[themeState.currTheme].themeColor1 : '#4A51FF', } }" >
      <RouterView/>
    </a-config-provider>
  </div>
</template>

<script setup>
import {ref, reactive, provide, onMounted, onBeforeUnmount } from 'vue'
import {useRouter} from "vue-router";
import zhCN from 'ant-design-vue/es/locale/zh_CN';
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
import { themeStore } from '@/stores/theme';

dayjs.locale('zh-cn');

// 国际化配置-默认中文
const locale = ref(zhCN);
const $router = useRouter();
const themeState = themeStore();

let timer = reactive(null)

onMounted(() => {
  // 初始化主题色
  themeState.getTheme && themeState.getTheme('themeColor');
});

</script>

3、Stores 部分

// src/stores/index.js
import { createPinia, } from 'pinia';

const pinia = createPinia()

export default pinia;

// src/stores/theme.js
import { defineStore } from 'pinia'
import {setStorage, getStorage} from "@/utils/util";

export const themeStore = defineStore('theme',{
    state: () => {
        return {
            currTheme: "默认", // 当前主题
            themes: {
                "默认": {
                    themeColor1: '#4A51FF',
                    themeColor2: '#4A51FF',
                    themeColor7: '#4A51FF', //
                    textColor1: '#181818',
                    textColor2: '#555555',
                },
                "海盐蓝": {
                    themeColor1: '#4691C8',
                    themeColor2: '#4691C8',
                    themeColor7: '#4691C8', //
                    textColor1: '#181818',
                    textColor2: '#555555',
                },
                "翠竹绿": {
                    themeColor1: '#347B45',
                    themeColor2: '#347B45',
                    themeColor7: '#347B45', //
                    textColor1: '#181818',
                    textColor2: '#555555',
                },
                "魅力紫": {
                    themeColor1: '#6837C9',
                    themeColor2: '#6837C9',
                    themeColor7: '#6837C9', //
                    textColor1: '#181818',
                    textColor2: '#555555',
                },
            }
        }
    },
    persist: {
        enabled: true,
        // 自定义持久化参数
        strategies: [
            {
                // 自定义key
                key: 'theme',
                // 自定义存储方式,默认sessionStorage
                storage: localStorage, // localStorage,
                // 指定要持久化的数据,默认所有 state 都会进行缓存,可以通过 paths 指定要持久化的字段,其他的则不会进行持久化。
                paths: ['currTheme', 'themes']
            }
        ]
    },
    // 相当于计算属性(有数据缓存)
    getters: {
        getThemes(state){
            return state.themes
        },
    },
    // actions即可以是同步函数也可以是异步函数
    actions: {
        // 切换主题
        changeStyle (obj) {
            for (let key in obj) {
                document.getElementsByTagName("body")[0].style.setProperty(`--${key}`, obj[key]);
            }
        },
        setThemeColor (themeName){
            let { showLock, currTheme, sideCollapsed, themes } = this;
            let theme = { showLock, currTheme, sideCollapsed, themes }
            setStorage("theme", JSON.stringify(theme));
            const themeConfig = this.getThemes[themeName];
            let themeInfo = {};
            if(getStorage("theme")) {
                themeInfo = JSON.parse(getStorage("theme"));
            }
            // 如果有主题名称,那么则采用我们定义的主题
            if (themeConfig) { // 保存主题色到本地
                this.changeStyle(themeConfig); // 改变样式
            } else {
                this.changeStyle(themeInfo.themes); // 改变样式
            }
        },
        setTheme ( theme, type ){
            if (type === 'themeColor') {
                this.setThemeColor(theme);
            } else if (type === 'FontFamily') {
                this.setFontFamily(theme);
            }
        },
        getTheme (type){
            let { currTheme } = this;
            if (type === 'themeColor') {
                if(getStorage("theme")) {
                    let themeInfo = JSON.parse(getStorage("theme"));
                    this.setThemeColor(themeInfo.currTheme);
                } else {
                    this.setThemeColor(currTheme);
                }

            } else if (type === 'FontFamily') {
                let FontFamily = getStorage("FontFamily");
                this.setFontFamily(FontFamily);
            }
        },
    }
});

4、页面使用

// header.vue
<template>
    <div class="headerCompView">
        <div class="header-left">
            <slot name="left"></slot>
        </div>
        <div class="header-right">
            <div class="theme-list">
              <a-popover placement="bottom" trigger="click" overlayClassName="themeUserPop" :overlayInnerStyle="{width: '230px'}">
                <template #content>
                  <div class="theme-item" v-for="(item, index) in themeOptions" :key="index" @click="onPressTheme(item.name)"
                       :style="{color: item.name === currentThemeName ? '#4A51FF' : ''}" >
                    <div class="item-left">
                      <a-tag :color="item.data.themeColor1" style="height: 20px; width: 20px;"></a-tag>
                      <span class="title"> {{item.name}} </span>
                    </div>
                    <div class="item-right">
                      <CheckOutlined v-if="item.name === currentThemeName" :style="{color: item.data.themeColor1 ? item.data.themeColor1 : ''}"/>
                    </div>
                  </div>
                </template>
                <div class="theme-options">
                  <BgColorsOutlined />
                  <span style="margin-left: 10px;">切换主题</span>
                </div>
              </a-popover>
            </div>
        </div>
    </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import {themeStore} from "@/stores/theme"

const $router = useRouter();
const themeState = themeStore(); 

let themeOptions = ref([]);
let currentThemeName = ref("默认");

onMounted(() => {
  collapsed.value = props.collapsedStatus;
  initTheme();
});

// 初始化主题
const initTheme = () => {
  let arr = [];
  for (let index in themeState.themes) {
    let item = {
      name: index,
      data: themeState.themes[index],
    }
    arr.push(item)
  }
  themeOptions.value = arr;
  currentThemeName.value = localStorage.getItem('themeName');
}

// 设置主题
const onPressTheme = (e) =>{
  themeState.currTheme = e;
  // console.log("themeState.currTheme", themeState.currTheme);
  themeState.setTheme(e, 'themeColor');
  currentThemeName.value = e;
};

</script>

<style lang="less" scoped>
@import (reference) "@/utils/common";

.themeUserPop{
  .theme-item{
    height: 40px;
    cursor: pointer;
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-weight: 500;
    line-height: 16px;
    border-bottom: 1px dashed #EFF1F5;
    .item-left{
      .flexCenter;
      .title{
        font-size: 14px;
        vertical-align: middle;
        line-height: 20px;
      }
    }
  }
  .theme-item:hover{
    color: #4A51FF;
  }
}
</style>
// src/utils/common.less

```
@theme: var(--themeColor1);
// 默认的主题颜色
@themeColor1: var(--themeColor1);
@themeColor2: var(--themeColor2);
@themeColor3: var(--themeColor3);
@themeColor4: var(--themeColor4);
@themeColor5: var(--themeColor5);
@themeColor6: var(--themeColor6);
@themeColor7: var(--themeColor7);
@textColor1: var(--textColor1);
@textColor2: var(--textColor2);
```

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1364357.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关于无人机上层控制的PID算法的思考

一、前言 背景介绍&#xff1a;PID虽然出现了很多年&#xff0c;但是目前工业界还是把PID作为主流的控制算法&#xff08;尽管学术界有很多非常时尚的控制算法&#xff0c;包括鲁邦控制&#xff0c;神经网络控制等等&#xff09;&#xff0c;PID的算法在于其不需要对系统进行复…

跟着小德学C++之安全模块

嗨&#xff0c;大家好&#xff0c;我是出生在达纳苏斯的一名德鲁伊&#xff0c;我是要立志成为海贼王&#xff0c;啊不&#xff0c;是立志成为科学家的德鲁伊。最近&#xff0c;我发现我们所处的世界是一个虚拟的世界&#xff0c;并由此开始&#xff0c;我展开了对我们这个世界…

Spring Security 6.x 系列(15)—— 会话管理之源码分析

一、前言 在上篇 Spring Security 6.x 系列(13)—— 会话管理之会话概念及常用配置 Spring Security 6.x 系列(14)—— 会话管理之会话固定攻击防护及Session共享 中了清晰了协议和会话的概念、对 Spring Security 中的常用会话配置进行了说明,并了解会话固定攻击防护…

WorkPlus完备的企业级功能堆栈,打造高效的企业移动平台

在如今的数字化时代&#xff0c;企业需要一个完备的功能堆栈来满足复杂的业务需求。WorkPlus作为一个完整的企业级移动平台&#xff0c;拥有完备的企业级功能&#xff0c;如IM、通讯录、内部群、模板群、工作台、权限管控、应用中心、日程管理、邮箱、同事圈、服务号、智能表单…

【Docker-Dev】Mac M2 搭建docker的redis环境

Redis的dev环境docker搭建 1、前言2、官方文档重点信息提取2.1、创建redis实例2.2、使用自己的redis.conf文件。 3、单机版redis搭建4、redis集群版4.1、一些验证4.2、一些问题 结语 1、前言 本文主要针对M2下&#xff0c;相应进行开发环境搭建&#xff0c;然后做一个文档记录…

FreeRTOS学习第6篇–任务状态挂起恢复删除等操作

目录 FreeRTOS学习第6篇--任务状态挂起恢复删除等操作任务的状态设计实验IRReceiver_Task任务相关代码片段实验现象本文中使用的测试工程 FreeRTOS学习第6篇–任务状态挂起恢复删除等操作 本文目标&#xff1a;学习与使用FreeRTOS中的几项操作&#xff0c;有挂起恢复删除等操作…

自动驾驶apollo9.0 Dreamview Debug方法

Apollo 9.0 安装&编译方法 # 拉取源码 git clone gitgithub.com:ApolloAuto/apollo.git git checkout v9.0.0# 启动docker bash docker/scripts/dev_start.sh bash docker/scripts/dev_into.sh# 编译project ./apollo.sh build默认启动方式 default mode wget https:…

MybatisPlus—自定义SQL

目录 1. 自定义SQL介绍 2. 自定义SQL使用步骤 3. 自定义SQL实例 4.总结 1. 自定义SQL介绍 介绍&#xff1a;自定义SQL并不是由我们来编写全部SQL语句&#xff0c;而是通过利用MyBatisPlus的Wrapper来构建复杂的Where条件&#xff0c;然后自己定义SQL语句中剩下的部分。 使…

详细全面的postman接口测试实战教程

基本介绍 postman是一款流程的接口调试工具&#xff0c;其特点就是使用简单&#xff0c;功能强大。使用角色也非常广泛&#xff0c;后端开发&#xff0c;前端人员&#xff0c;测试人员都可以使用它进行接口调试或测试。 基本框架 如果把postman去其内容只保留框架的话&#…

WorkPlus安全专属的即时通讯解决方案,助力企业高效沟通协作

在当今快节奏的商业环境中&#xff0c;高效的即时通讯是企业成功的关键。而WorkPlus作为一种领先的即时通讯工具&#xff0c;以其卓越的性能和创新的功能&#xff0c;助力企业高效沟通和协作。 WorkPlus作为即时通讯的新选择&#xff0c;为何备受企业的青睐&#xff1f;首先&am…

【JaveWeb教程】(7)Web前端基础:Vue组件库Element介绍与快速入门程序编写并运行 示例

目录 Element介绍快速入门示例 Element介绍 不知道同学们还否记得我们之前讲解的前端开发模式MVVM&#xff0c;我们之前学习的vue是侧重于VM开发的&#xff0c;主要用于数据绑定到视图的&#xff0c;那么接下来我们学习的ElementUI就是一款侧重于V开发的前端框架&#xff0c;主…

使用ChatGPT生成i项目需求文档模板

前言 我们在工作中需要编写的技术文档有多种形式&#xff0c;包括Word、Excel、PDF及一些在线形式。我们可以借助ChatGPT生成文本&#xff0c;然而&#xff0c;它不能直接生成Word、Excel、PDF等格式的文档。因此&#xff0c;我们需要利用其他工具来帮助我们生成一些模板&…

linux反汇编工具: ida pro、rizinorg/cutter; ubuntu 22 flameshot延迟截图 以应对下拉菜单

rizinorg/cutter rizinorg/cutter 是 命令行反汇编工具 rizinorg/rizin 的图形化界面, 这比 ida pro跑在kvm虚拟机中方便多了, ubuntu22.04下直接下载Cutter-v2.3.2-Linux-x86_64.AppImage后即可运行,如下图: 注意 有个同名的报废品: radare2/Cutter 即 radare2的图形化界…

软件测试|Linux三剑客之grep命令详解

简介 grep是一款在 Linux 和类 Unix 系统中广泛使用的文本搜索工具。它的名字来源于 Global Regular Expression Print&#xff08;全局正则表达式打印&#xff09;&#xff0c;它的主要功能是根据指定的模式&#xff08;正则表达式&#xff09;在文本文件中搜索并打印匹配的行…

JavaScript异常处理实战

前言 之前在对公司的前端代码脚本错误进行排查&#xff0c;试图降低 JS Error 的错误量&#xff0c;结合自己之前的经验对这方面内容进行了实践并总结&#xff0c;下面就此谈谈我对前端代码异常监控的一些见解。 本文大致围绕下面几点展开讨论&#xff1a; JS 处理异常的方式…

抖音在线查权重系统源码,附带查询接口

抖音权重在线查询只需输入抖音主页链接&#xff0c;即可查询作品情况。 搭建教程 上传源码并解压 修改数据库“bygoukai.sql” 修改“config.php” 如需修改水印请修改第40行 如需修改限制次数&#xff0c;请修改第156行 访问域名user.php即可查看访问用户&#xff0c;停…

学习笔记——C++运算符之赋值运算符

上次我们说到C的运算符共有四种&#xff0c;分别是算术运算符&#xff0c;赋值运算符&#xff0c;比较运算符和逻辑运算符 &#xff0c;下面介绍赋值运算符&#xff0c;赋值运算符主要的种类及作用如下表所示。 #include<bits/stdc.h> using namespace std; int main(){…

MediaPipeUnityPlugin(最新版)摇摆拳人脸识别

1、从https://github.com/homuler/MediaPipeUnityPlugin 下载Release Package 目前是MediaPipeUnity.0.12.0.unitypackage 2、导入Unity工程 3、打开Face Detection场景&#xff0c;做一些设置修改 1、打开Bootstrap&#xff0c;图像源改成Video&#xff0c;把Solution拖拽到…

车辆运动学方程推导和代码实现

文章目录 1. 运动学方程2. 模型实现 1. 运动学方程 自行车模型&#xff08;Bicycle Model&#xff09;是车辆数字化模型中最常见的一种运动学模型。其除了可以反映车辆的一些基础特性外&#xff0c;更重要的是简单易用。通常情况下我们会把车辆模型简化为二自由度的自行车模型…

Vue 中的 ref 与 reactive:让你的应用更具响应性(中)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…