✨ add dark mode
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
<template>
|
||||
<router-view></router-view>
|
||||
<LayOut/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import LayOut from "@/layout/default/Layout.vue";
|
||||
</script>
|
||||
|
48
src/assets/styles/colors.module.scss
Normal file
48
src/assets/styles/colors.module.scss
Normal 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;
|
||||
}
|
@@ -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');
|
||||
}
|
||||
}
|
36
src/assets/styles/theme.scss
Normal file
36
src/assets/styles/theme.scss
Normal 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);
|
||||
}
|
22
src/layout/default/Layout.vue
Normal file
22
src/layout/default/Layout.vue
Normal 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>
|
@@ -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'
|
||||
|
@@ -2,9 +2,9 @@ export default [
|
||||
{
|
||||
path: '/',
|
||||
name: 'login',
|
||||
component: () => import('@/views/login/LoginPage.vue'),
|
||||
component: () => import('@/views/Login/LoginPage.vue'),
|
||||
meta: {
|
||||
title: '登录页'
|
||||
}
|
||||
}
|
||||
]
|
||||
];
|
||||
|
10
src/router/modules/test.ts
Normal file
10
src/router/modules/test.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export default [
|
||||
{
|
||||
path: '/test',
|
||||
name: 'test',
|
||||
component: () => import('@/views/TestTheme.vue'),
|
||||
meta: {
|
||||
title: '测试'
|
||||
}
|
||||
}
|
||||
];
|
@@ -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;
|
||||
|
@@ -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()
|
||||
};
|
||||
}
|
||||
|
54
src/store/modules/themeStore.ts
Normal file
54
src/store/modules/themeStore.ts
Normal 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,
|
||||
},
|
||||
}
|
||||
}
|
||||
);
|
@@ -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
|
||||
}
|
||||
)
|
44
src/store/modules/userStore.ts
Normal file
44
src/store/modules/userStore.ts
Normal 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
6
src/types/user.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
export interface User {
|
||||
token?: string
|
||||
userId?: string
|
||||
|
||||
}
|
@@ -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));
|
||||
|
@@ -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();
|
||||
|
15
src/views/Login/LoginPage.vue
Normal file
15
src/views/Login/LoginPage.vue
Normal 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
37
src/views/TestTheme.vue
Normal 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>
|
||||
|
@@ -1,9 +0,0 @@
|
||||
<template>
|
||||
<AButton> 测试页面</AButton>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<style scoped>
|
||||
|
||||
</style>
|
16
src/vite-env.d.ts
vendored
16
src/vite-env.d.ts
vendored
@@ -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'
|
||||
|
||||
|
Reference in New Issue
Block a user