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

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);
```

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java面向对象(抽象类,接口,内部类)

文章目录 今日内容教学目标 第一章 抽象类1.1 概述1.1.1 抽象类引入 1.2 abstract使用格式1.2.1 抽象方法1.2.2 抽象类1.2.3 抽象类的使用 1.3 抽象类的特征1.4 抽象类的细节1.5 抽象类存在的意义 第二章 接口2.1 概述2.2 定义格式2.3 接口成分的特点2.3.1.抽象方法2.3.2 常量2…

麒麟KYLINOS禁用IPV6地址

原文链接&#xff1a;麒麟KYLINOS操作系统上禁用IPv6的方法 hello&#xff0c;大家好啊&#xff01;今天我要给大家介绍的是在麒麟KYLINOS操作系统上禁用IPv6的方法。IPv6是最新的网络通信协议&#xff0c;旨在解决IPv4地址耗尽的问题。然而&#xff0c;在某些特定的网络环境或…

MYSQL 索引使用规则

索引失效 最左前缀法则 where之后写的顺序不重要&#xff0c;存在就可以 范围查询后面的索引查询失效&#xff08;比如>&#xff09;,但是>或者<是不会失效的 不要在索引列上进行运算操作&#xff0c;否则索引失效。 字符串类型字段不加引号索引会失效 尾部模糊匹配…

ThreadLocalRandom类原理剖析

ThreadLocalRandom 类是JDK7在JUC包下新增的随机数生成器&#xff0c;它弥补了Random类在多线程下的缺陷。 Random 类及其局限性 在JDK7之前包括现在&#xff0c;java.util.Random都是使用比较广泛的随机数生成工具类&#xff0c;而且java.lang.Math中的随机数生成也使用的是…

网安面试三十到题(结束)

121 有文件上传了漏洞了&#xff0c;linux下怎么找xx.conf的文件 目录遍历,目录扫描 122 反序列化漏洞原理 ## 你要把别人序列化好的文件进行反序列化进行利用&#xff0c;但是在序列化的过程中&#xff0c;被别人注入了攻击代码、魔 法函数之类的&#xff0c;当你反序列化的时…

花为缘积萨伯爵名表工艺之美,传承卓越

腕表是时间的载体&#xff0c;也是品味的象征。在现代人眼中&#xff0c;它们不仅仅是时间的工具&#xff0c;更是一种艺术形式。在制表工艺的殿堂中&#xff0c;花为缘积萨伯爵名表以其独特的创造力和严谨缜密的要求&#xff0c;创作了一系列典范之作&#xff0c;将技术与美学…

伦敦银1盎司等于多少克?

1盎司的伦敦银大概等于31克&#xff0c;用于衡量伦敦银重量的“盎司”&#xff0c;是国际贵金属市场上专用的计量单位&#xff0c;它的全称是金衡盎司&#xff0c;英文的名字是troy ounce&#xff0c;它与西方日常用于计算重量的单位常衡盎司也不一样&#xff0c;一金衡盎司约等…

DTM分布式事务

DTM分布式事务 从内网看到了关于事务在业务中的讨论&#xff0c;评论区大佬有提及DTM开源项目[https://dtm.pub/]&#xff0c;开学开学 基础理论 一、Why DTM ​ 项目产生于实际生产中的问题&#xff0c;涉及订单支付的服务会将所有业务相关逻辑放到一个大的本地事务&#xff…

【性能测试入门】:压力测试概念!

压力测试可以验证软件应用程序的稳定性和可靠性。压力测试的目标是评估软件在极端负载条件下的鲁棒性和错误处理能力&#xff0c;并确保软件在紧急情况下不会崩溃。它甚至可以进行超出软件正常工作条件的测试&#xff0c;并评估软件在极端条件下的工作方式。 在软件工程中&…

简单介绍Java 的内存泄漏

java最明显的一个优势就是它的内存管理机制。你只需简单创建对象&#xff0c;java的垃圾回收机制负责分配和释放内存。然而情况并不像想像的那么简单&#xff0c;因为在Java应用中经常发生内存泄漏。 本教程演示了什么是内存泄漏&#xff0c;为什么会发生内存泄漏以及如何预防…

2024年第十届计算机与技术应用国际会议(ICCTA 2024)即将召开!

​ ​ 2024年第十届计算机与技术应用国际会议&#xff08;ICCTA 2024&#xff09; 会议时间&#xff1a;2024年5月15-17日 会议地点&#xff1a;奥地利维也纳 (线上线下会议) 会议官网&#xff1a; Home_ICCTA 2024 | Vienna, Austria 组织单位&#xff1a; 奥地利FH JOANN…

狂拿offer,这12道性能测试面试题你会多少?不要再被挖坑了

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、性能测试包含了…

NFS 共享存储实验

一、服务器部署 第一步、安装nfs和rpcbind包 [rootserver ~]# yum install -y nfs-utils rpcbind截图&#xff1a; 第二步、这里选择一个 lvm 挂载点做 NFS 共享目录 [rootserver ~]# df -HT截图&#xff1a; 第三步、修改配置文件 [rootserver ~]# vi /etc/exports /home …

2024农历新年是什么时候?电脑如何设置农历新年提醒

元旦的钟声已经远去&#xff0c;2024年的阳历新年就这样悄无声息地开始了。但对于我们很多人来说&#xff0c;真正的“过年”氛围&#xff0c;还得等到农历新年的到来。那么&#xff0c;今年的农历新年究竟是什么时候呢&#xff1f;答案是2月10日。 每当想到农历新年&#xff…

【docker笔记】Docker容器数据卷

Docker容器数据卷 卷就是目录或者文件&#xff0c;存在于一个或多个容器中&#xff0c;由docker挂载到容器&#xff0c;但不属于联合文件系统&#xff0c;因此能够绕过Union File System提供一些用于持续存储或共享数据的特性 卷的设计目的就是数据的持久化&#xff0c;完全独…

Element-Puls Form表单内嵌套el-table表格,根据表格复选框多选或单选动态设置行的验证规则

需求 根据 Table 表格内的复选框来控制当前选中行是否添加必填校验规则 效果图 实现思想 我们需要设置一个 flag 来标识已勾选的行&#xff0c;el-table渲染数据结构是数组对象形式&#xff0c;我们可以在每个对象中手动加如一个标识&#xff0c;例如默认&#xff1a;selected …

密码输入检测 - 华为OD统一考试

OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 100分 题解&#xff1a; Java / Python / C 题目描述 给定用户密码输入流input&#xff0c;输入流中字符 ‘<’ 表示退格&#xff0c;可以清除前一个输入的字符&#xff0c;请你编写程序&#xff0c;输出最终得到的…

fpmarkets盘点成功交易者的十个习惯(一)

在交易中能够盈利一次&#xff0c;fpmarkets认为这种情况100%的交易者都会做到&#xff0c;但是要做到每次交易都能盈利&#xff0c;即使是巴菲特也做到&#xff0c;我们只需要做到整体盈利就可以了&#xff0c;那么如何做到呢&#xff1f;今天fpmarkets就总结一下成功交易者的…

代码随想录算法训练营Day08|344.反转字符串、541. 反转字符串II、卡码网:替换数字、151.翻转字符串里的单词、卡码网:右旋字符串

文章目录 一、344.反转字符串1. 双指针法 二、541. 反转字符串II1. 字符串解法 三、卡码网&#xff1a;替换数字四、151.翻转字符串里的单词1.使用库函数2.自行编写函数3.创建字符数组填充3.双反转移位 五、卡码网&#xff1a;右旋字符串1. 自行编写函数 总结 一、344.反转字符…

【软件测试】白盒测试 / 逻辑覆盖法

《语句覆盖法》 使程序中的每个可执行语句至少执行一次 所有的可执行语句得到执行语句覆盖测试是较弱的一种测试发现错误能力最弱的逻辑覆盖 《判定覆盖法》 使每一个判定获得每一种可能的结果至少一次 每个判定得到真值和假值判断覆盖法满足了语句覆盖&#xff0c;因此比语…