add dark mode

This commit is contained in:
landaiqing
2024-08-08 16:39:27 +08:00
parent a4b502717c
commit 562071cdc5
26 changed files with 479 additions and 112 deletions

View File

@@ -1,4 +1,7 @@
<template>
<router-view></router-view>
<LayOut/>
</template>
<script setup lang="ts">
import LayOut from "@/layout/default/Layout.vue";
</script>

View File

@@ -0,0 +1,48 @@
$red: #F5222D;
$orange: #FA541C;
$yellow: #FAAD14;
$cyan: #13C2C2;
$green: #52C41A;
$blue: #2F54EB;
$purple: #722ED1;
$colors: (
"red": (
primary: $red,
info: $red,
),
"orange": (
primary: $orange,
info: $orange,
),
"yellow": (
primary: $yellow,
info: $yellow,
),
"cyan": (
primary: $cyan,
info: $cyan,
),
"green": (
primary: $green,
info: $green,
),
"blue": (
primary: $blue,
info: $blue,
),
"purple": (
primary: $purple,
info: $purple,
)
);
:export {
red: $red;
orange: $orange;
yellow: $yellow;
cyan: $cyan;
green: $green;
blue: $blue;
purple: $purple;
}

View File

@@ -1,22 +1,19 @@
@import "theme";
::-webkit-scrollbar {
width: 6px;
height: 8px;
}
::-webkit-scrollbar-button {
display: none;
}
::-webkit-scrollbar-thumb {
background: rgba(144, 147, 153, 0.3);
cursor: pointer;
border-radius: 4px;
}
::-webkit-scrollbar-corner {
display: none;
}
@@ -24,3 +21,13 @@
::-webkit-resizer {
display: none;
}
body {
position: relative;
transition: background-color 0.3s,
color 0.3s;
@include useTheme {
background-color: getModeVar('bgColor');
color: getModeVar('infoColor');
}
}

View File

@@ -0,0 +1,36 @@
@import "colors.module";
$modes: (
light: (
bgColor: #fff,
infoColor: #000
),
dark: (
bgColor: #000,
infoColor: #fff
)
);
$curMode: light;
$curTheme: red;
@mixin useTheme() {
@each $key1, $value1 in $modes {
$curMode: $key1 !global;
@each $key2, $value2 in $colors {
$curTheme: $key2 !global;
html[data-dark='#{$key1}'][data-theme='#{$key2}'] & {
@content;
}
}
}
}
@function getModeVar($key) {
$modeMap: map-get($modes, $curMode);
@return map-get($modeMap, $key);
}
@function getColor($key) {
$themeMap: map-get($colors, $curTheme);
@return map-get($themeMap, $key);
}

View File

@@ -0,0 +1,22 @@
<template>
<AConfigProvider
:theme="{
token: {
colorPrimary: '#00b96b',
},
}"
>
<router-view>
</router-view>
</AConfigProvider>
</template>
<script lang="ts">
import {defineComponent} from 'vue';
export default defineComponent({
name: "LayOut"
});
</script>
<style scoped>
</style>

View File

@@ -1,6 +1,6 @@
import {createApp} from 'vue'
import App from './App.vue'
import '@/assets/styles/variables.scss'
import '@/assets/styles/scroll-bar.scss'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
//Pinia
import {createPinia, Pinia} from 'pinia'

View File

@@ -2,9 +2,9 @@ export default [
{
path: '/',
name: 'login',
component: () => import('@/views/login/LoginPage.vue'),
component: () => import('@/views/Login/LoginPage.vue'),
meta: {
title: '登录页'
}
}
]
];

View File

@@ -0,0 +1,10 @@
export default [
{
path: '/test',
name: 'test',
component: () => import('@/views/TestTheme.vue'),
meta: {
title: '测试'
}
}
];

View File

@@ -1,11 +1,17 @@
import {createRouter, createWebHistory, RouteRecordRaw} from 'vue-router'
import {createRouter, createWebHistory, RouteRecordRaw} from 'vue-router';
import login from './modules/login'
import login from './modules/login';
import test from "@/router/modules/test.ts";
const routes: Array<RouteRecordRaw> = [...login]
const routes: Array<RouteRecordRaw> = [
...login,
...test
];
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
});
export default router;

View File

@@ -1,7 +1,9 @@
import {useAuthStore} from '@/store/modules/user.ts'
import {useAuthStore} from '@/store/modules/userStore.ts';
import {useThemeStore} from "@/store/modules/themeStore.ts";
export default function useStore() {
return {
user: useAuthStore()
}
user: useAuthStore(),
theme: useThemeStore()
};
}

View File

@@ -0,0 +1,54 @@
import {defineStore} from 'pinia';
import {computed, ref} from 'vue';
import {theme} from 'ant-design-vue';
import variables from '@/assets/styles/colors.module.scss';
import {handleLocalforage} from "@/utils/localforage";
import {parse, stringify} from "zipson/lib";
/**
* theme 配置 开启持久化
*/
export const useThemeStore = defineStore(
'theme',
() => {
const themeName = ref('green'); // 主题名称
const darkMode = ref('light'); // 颜色模式
const darkModeComp = computed(() => {
document.documentElement.setAttribute('data-dark', darkMode.value);
return darkMode.value;
});
const themeConfig = computed(() => {
document.documentElement.setAttribute('data-theme', themeName.value);
// 主题配置
return {
token: {
colorPrimary: variables[themeName.value] || '#27ba9b',
colorSuccess: '#1dc779',
colorWarning: '#ffb302',
colorError: '#cf4444',
colorInfo: variables[themeName.value] || '#27ba9b',
wireframe: true
},
algorithm: darkMode.value === 'light' ? theme.defaultAlgorithm : theme.darkAlgorithm
};
});
const setThemeName = (value: string) => {
themeName.value = value;
};
const toggleDarkMode = () => {
darkMode.value = darkMode.value === 'light' ? 'dark' : 'light';
};
return {themeName, themeConfig, darkMode, darkModeComp, setThemeName, toggleDarkMode};
},
{
persist: {
key: 'theme',
paths: ['themeName','darkMode',"darkModeComp","themeConfig"],
storage: handleLocalforage,
serializer: {
deserialize: parse,
serialize: stringify,
},
}
}
);

View File

@@ -1,32 +0,0 @@
import {defineStore} from 'pinia'
import {ref} from 'vue'
export const useAuthStore = defineStore(
'user',
() => {
const user = ref<any>()
function setUser(data: any) {
user.value = data
}
function getUser() {
return user.value
}
function clearUser() {
user.value = void 0
}
return {
user,
setUser,
getUser,
clearUser
}
},
{
// 开启数据持久化
persist: true
}
)

View File

@@ -0,0 +1,44 @@
import {defineStore} from 'pinia';
import {ref} from 'vue';
import {User} from "@/types/user";
import {parse, stringify} from "zipson/lib";
import {handleLocalforage} from "@/utils/localforage";
export const useAuthStore = defineStore(
'user',
() => {
const user = ref<User>();
function setUser(data: User) {
user.value = data;
}
function getUser() {
return user.value;
}
function clearUser() {
user.value = void 0;
}
return {
user,
setUser,
getUser,
clearUser
};
},
{
// 开启数据持久化
persist: {
key: 'user',
paths: ['user'],
storage: handleLocalforage,
serializer: {
deserialize: parse,
serialize: stringify,
},
}
}
);

6
src/types/user.d.ts vendored Normal file
View File

@@ -0,0 +1,6 @@
export interface User {
token?: string
userId?: string
}

View File

@@ -10,7 +10,7 @@ const config: globalConfig = {
isEncrypt: true, //支持加密、解密数据处理
};
const setStorage = (key: string, value: any, expire: number = 24 * 60): boolean => {
const setStorage = (key: string, value: null | string, expire: number = 24 * 60): boolean => {
//设定值
if (value === "" || value === null || value === undefined) {
//空值重置
@@ -58,7 +58,7 @@ const getStorageFromKey = (key: string) => {
};
const getAllStorage = () => {
//获取所有值
const storageList: any = {};
const storageList: any= {};
const keys = Object.keys(window[config.type]);
keys.forEach((key) => {
const value = getStorageFromKey(autoRemovePreFix(key));

View File

@@ -10,7 +10,7 @@ const SECRET_IV = CryptoJS.enc.Utf8.parse("e3bbe7e3ba84431a"); //十六位十六
* @param data
* @param output
*/
export const encrypt = (data: string, output?: any) => {
export const encrypt = (data: string, output?: undefined) => {
const dataHex = CryptoJS.enc.Utf8.parse(data);
const encrypted = CryptoJS.AES.encrypt(dataHex, SECRET_KEY, {
iv: SECRET_IV,
@@ -24,11 +24,11 @@ export const encrypt = (data: string, output?: any) => {
* 解密
* @param data
*/
export const decrypt = (data: string | null) => {
export const decrypt = (data: string | unknown) => {
if (data === null) {
return;
}
const encryptedHex = CryptoJS.enc.Hex.parse(data);
const encryptedHex = CryptoJS.enc.Hex.parse(data as string);
const encryptedHexStr = CryptoJS.enc.Base64.stringify(encryptedHex);
const decrypted = CryptoJS.AES.decrypt(encryptedHexStr, SECRET_KEY, {
iv: SECRET_IV,
@@ -41,29 +41,18 @@ export const decrypt = (data: string | null) => {
export const handleLocalforage = {
config: async (options?: LocalForageOptions) => localforage.config(options || {}),
setItem: async (key: string, value: string): Promise<void> => {
await localforage.setItem(key, encrypt(value));
setItem: (key: string, value: string) => {
localforage.setItem(key, encrypt(value));
},
getItem: async function getItem<T>(key: string): Promise<T | string | null> {
try {
const value: any = decrypt(await localforage.getItem(key)) as any;
// 如果值是 undefined返回 null
if (value === undefined) {
return null;
}
// 如果值是 T 类型,直接返回
if (typeof value === "object" && value !== null) {
return value as T;
}
// 如果值是 string 类型,直接返回
return value as string;
} catch (error) {
console.error("Error retrieving data from localforage:", error);
return null;
}
getItem: (key: string) => {
let value: unknown = null;
localforage.getItem(key).then((res: unknown) => {
value = res;
});
return decrypt(value) as string;
},
removeItem: async (key: string): Promise<void> => {
await localforage.removeItem(key);
removeItem: (key: string) => {
localforage.removeItem(key);
},
clear: async () => {
return await localforage.clear();

View File

@@ -0,0 +1,15 @@
<template>
<AButton> 测试页面</AButton>
</template>
<script setup lang="ts">
import useStore from "@/store";
const store = useStore().user;
store.setUser({
token: "test",
userId: "1",
});
console.log(store.getUser()?.userId);
</script>
<style src="./index.scss" scoped>
</style>

37
src/views/TestTheme.vue Normal file
View File

@@ -0,0 +1,37 @@
<template>
<AConfigProvider :theme="app.themeConfig">
<ASelect v-model:value="app.themeName" style="width: 240px">
<ASelectOption v-for="(color, name) in variables" :key="color" :value="name"> {{ name }}:{{
color
}}
</ASelectOption>
</ASelect>
<ASelect v-model:value="app.darkMode" style="width: 120px">
<ASelectOption value="dark">dark</ASelectOption>
<ASelectOption value="light">light</ASelectOption>
</ASelect>
<AButtonGroup>
<AButton type="primary">切换主题- {{ app.themeName }}</AButton>
<AButton @click="app.toggleDarkMode">切换模式{{ app.darkModeComp }}</AButton>
</AButtonGroup>
<div class="test">test</div>
</AConfigProvider>
</template>
<script setup>
import variables from "@/assets/styles/colors.module.scss";
import useStore from "@/store/index.ts";
const app = useStore().theme;
</script>
<style lang="scss" scoped>
@import "@/assets/styles/theme.scss";
.test {
@include useTheme {
background: getModeVar('primary');
color: getColor('info');;
}
}
</style>

View File

@@ -1,9 +0,0 @@
<template>
<AButton> 测试页面</AButton>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>

16
src/vite-env.d.ts vendored
View File

@@ -12,5 +12,21 @@ interface ImportMeta {
readonly env: ImportMetaEnv;
}
declare module '*.vue' {
import type { DefineComponent } from 'vue';
const component: DefineComponent<object, object, any>;
export default component;
}
declare module '*.svg' {
const content: any;
export default content;
}
declare module '*.svg'
declare module '*.png'
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.gif'
declare module '*.bmp'
declare module '*.tiff'