From 562071cdc542c7cd9cc522381523fb3f0ac95810 Mon Sep 17 00:00:00 2001
From: landaiqing <3517283258@qq.com>
Date: Thu, 8 Aug 2024 16:39:27 +0800
Subject: [PATCH] :sparkles: add dark mode
---
components.d.ts | 8 +-
eslint.config.js | 20 ++--
package.json | 4 +-
src/App.vue | 5 +-
src/assets/styles/colors.module.scss | 48 ++++++++
.../{variables.scss => scroll-bar.scss} | 15 ++-
src/assets/styles/theme.scss | 36 ++++++
src/layout/default/Layout.vue | 22 ++++
src/main.ts | 2 +-
src/router/modules/login.ts | 4 +-
src/router/modules/test.ts | 10 ++
src/router/router.ts | 16 ++-
src/store/index.ts | 8 +-
src/store/modules/themeStore.ts | 54 +++++++++
src/store/modules/user.ts | 32 ------
src/store/modules/userStore.ts | 44 +++++++
src/types/user.d.ts | 6 +
src/utils/localStorage/config.ts | 4 +-
src/utils/localforage/index.ts | 37 +++---
src/views/Login/LoginPage.vue | 15 +++
src/views/{login => Login}/index.scss | 0
src/views/TestTheme.vue | 37 ++++++
src/views/login/LoginPage.vue | 9 --
src/vite-env.d.ts | 16 +++
vite.config.ts | 31 +++--
yarn.lock | 108 +++++++++++++++++-
26 files changed, 479 insertions(+), 112 deletions(-)
create mode 100644 src/assets/styles/colors.module.scss
rename src/assets/styles/{variables.scss => scroll-bar.scss} (58%)
create mode 100644 src/assets/styles/theme.scss
create mode 100644 src/layout/default/Layout.vue
create mode 100644 src/router/modules/test.ts
create mode 100644 src/store/modules/themeStore.ts
delete mode 100644 src/store/modules/user.ts
create mode 100644 src/store/modules/userStore.ts
create mode 100644 src/types/user.d.ts
create mode 100644 src/views/Login/LoginPage.vue
rename src/views/{login => Login}/index.scss (100%)
create mode 100644 src/views/TestTheme.vue
delete mode 100644 src/views/login/LoginPage.vue
diff --git a/components.d.ts b/components.d.ts
index 621cae1..60b00bf 100644
--- a/components.d.ts
+++ b/components.d.ts
@@ -8,9 +8,13 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
AButton: typeof import('ant-design-vue/es')['Button']
- Login: typeof import('./src/views/login/LoginPage.vue')['default']
- LoginPage: typeof import('./src/views/login/LoginPage.vue')['default']
+ AButtonGroup: typeof import('ant-design-vue/es')['ButtonGroup']
+ AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider']
+ ASelect: typeof import('ant-design-vue/es')['Select']
+ ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
+ LoginPage: typeof import('./src/views/Login/LoginPage.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
+ TestTheme: typeof import('./src/views/TestTheme.vue')['default']
}
}
diff --git a/eslint.config.js b/eslint.config.js
index 13b2ea6..af9e7d4 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -5,10 +5,16 @@ import pluginVue from "eslint-plugin-vue";
export default [
- {files: ["**/*.{js,mjs,cjs,ts,vue}"]},
- {languageOptions: { globals: {...globals.browser, ...globals.node} }},
- pluginJs.configs.recommended,
- ...tseslint.configs.recommended,
- ...pluginVue.configs["flat/essential"],
- {files: ["**/*.vue"], languageOptions: {parserOptions: {parser: tseslint.parser}}},
-];
\ No newline at end of file
+ {files: ["**/*.{js,mjs,cjs,ts,vue}"]},
+ {languageOptions: {globals: {...globals.browser, ...globals.node}}},
+ pluginJs.configs.recommended,
+ ...tseslint.configs.recommended,
+ ...pluginVue.configs["flat/essential"],
+ {files: ["**/*.vue"], languageOptions: {parserOptions: {parser: tseslint.parser}}},
+ {
+ rules: {
+ semi: "error",
+ "@typescript-eslint/no-explicit-any": "off"
+ }
+ }
+];
diff --git a/package.json b/package.json
index b5cb92b..d186dd6 100644
--- a/package.json
+++ b/package.json
@@ -16,6 +16,7 @@
"axios": "^1.7.3",
"crypto-js": "^4.2.0",
"eslint": "9.x",
+ "less": "^4.2.0",
"localforage": "^1.10.0",
"pinia": "^2.2.1",
"pinia-plugin-persistedstate": "^3.2.1",
@@ -24,7 +25,8 @@
"vite-plugin-html": "^3.2.2",
"vite-plugin-node-polyfills": "^0.22.0",
"vue": "^3.4.31",
- "vue-router": "^4.4.3"
+ "vue-router": "^4.4.3",
+ "zipson": "^0.2.12"
},
"devDependencies": {
"@eslint/js": "^9.8.0",
diff --git a/src/App.vue b/src/App.vue
index 57b5ffe..68f3299 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,4 +1,7 @@
-
+
+
diff --git a/src/assets/styles/colors.module.scss b/src/assets/styles/colors.module.scss
new file mode 100644
index 0000000..696ea24
--- /dev/null
+++ b/src/assets/styles/colors.module.scss
@@ -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;
+}
diff --git a/src/assets/styles/variables.scss b/src/assets/styles/scroll-bar.scss
similarity index 58%
rename from src/assets/styles/variables.scss
rename to src/assets/styles/scroll-bar.scss
index 54ec063..cb83725 100644
--- a/src/assets/styles/variables.scss
+++ b/src/assets/styles/scroll-bar.scss
@@ -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');
+ }
+}
diff --git a/src/assets/styles/theme.scss b/src/assets/styles/theme.scss
new file mode 100644
index 0000000..a22dfe3
--- /dev/null
+++ b/src/assets/styles/theme.scss
@@ -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);
+}
diff --git a/src/layout/default/Layout.vue b/src/layout/default/Layout.vue
new file mode 100644
index 0000000..d505570
--- /dev/null
+++ b/src/layout/default/Layout.vue
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
diff --git a/src/main.ts b/src/main.ts
index 16e1634..f367e80 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -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'
diff --git a/src/router/modules/login.ts b/src/router/modules/login.ts
index 3a5a1e9..0914b75 100644
--- a/src/router/modules/login.ts
+++ b/src/router/modules/login.ts
@@ -2,9 +2,9 @@ export default [
{
path: '/',
name: 'login',
- component: () => import('@/views/login/LoginPage.vue'),
+ component: () => import('@/views/Login/LoginPage.vue'),
meta: {
title: '登录页'
}
}
-]
+];
diff --git a/src/router/modules/test.ts b/src/router/modules/test.ts
new file mode 100644
index 0000000..9699fa3
--- /dev/null
+++ b/src/router/modules/test.ts
@@ -0,0 +1,10 @@
+export default [
+ {
+ path: '/test',
+ name: 'test',
+ component: () => import('@/views/TestTheme.vue'),
+ meta: {
+ title: '测试'
+ }
+ }
+];
diff --git a/src/router/router.ts b/src/router/router.ts
index c34721b..f162710 100644
--- a/src/router/router.ts
+++ b/src/router/router.ts
@@ -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 = [...login]
+
+const routes: Array = [
+ ...login,
+ ...test
+
+];
const router = createRouter({
history: createWebHistory(),
routes
-})
-export default router
+});
+export default router;
diff --git a/src/store/index.ts b/src/store/index.ts
index d0c8d4d..90c1fe3 100644
--- a/src/store/index.ts
+++ b/src/store/index.ts
@@ -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()
+ };
}
diff --git a/src/store/modules/themeStore.ts b/src/store/modules/themeStore.ts
new file mode 100644
index 0000000..6715e71
--- /dev/null
+++ b/src/store/modules/themeStore.ts
@@ -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,
+ },
+ }
+ }
+);
diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts
deleted file mode 100644
index 59b8175..0000000
--- a/src/store/modules/user.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import {defineStore} from 'pinia'
-import {ref} from 'vue'
-
-export const useAuthStore = defineStore(
- 'user',
- () => {
- const user = ref()
-
- 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
- }
-)
diff --git a/src/store/modules/userStore.ts b/src/store/modules/userStore.ts
new file mode 100644
index 0000000..abce994
--- /dev/null
+++ b/src/store/modules/userStore.ts
@@ -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();
+
+ 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,
+ },
+ }
+ }
+);
diff --git a/src/types/user.d.ts b/src/types/user.d.ts
new file mode 100644
index 0000000..f01c3a0
--- /dev/null
+++ b/src/types/user.d.ts
@@ -0,0 +1,6 @@
+
+export interface User {
+ token?: string
+ userId?: string
+
+}
diff --git a/src/utils/localStorage/config.ts b/src/utils/localStorage/config.ts
index 825b8aa..ec9130a 100644
--- a/src/utils/localStorage/config.ts
+++ b/src/utils/localStorage/config.ts
@@ -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));
diff --git a/src/utils/localforage/index.ts b/src/utils/localforage/index.ts
index 7df293b..a075fab 100644
--- a/src/utils/localforage/index.ts
+++ b/src/utils/localforage/index.ts
@@ -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 => {
- await localforage.setItem(key, encrypt(value));
+ setItem: (key: string, value: string) => {
+ localforage.setItem(key, encrypt(value));
},
- getItem: async function getItem(key: string): Promise {
- 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 => {
- await localforage.removeItem(key);
+ removeItem: (key: string) => {
+ localforage.removeItem(key);
},
clear: async () => {
return await localforage.clear();
diff --git a/src/views/Login/LoginPage.vue b/src/views/Login/LoginPage.vue
new file mode 100644
index 0000000..994a9d1
--- /dev/null
+++ b/src/views/Login/LoginPage.vue
@@ -0,0 +1,15 @@
+
+ 测试页面
+
+
+
diff --git a/src/views/login/index.scss b/src/views/Login/index.scss
similarity index 100%
rename from src/views/login/index.scss
rename to src/views/Login/index.scss
diff --git a/src/views/TestTheme.vue b/src/views/TestTheme.vue
new file mode 100644
index 0000000..4408566
--- /dev/null
+++ b/src/views/TestTheme.vue
@@ -0,0 +1,37 @@
+
+
+
+ {{ name }}:{{
+ color
+ }}
+
+
+
+ dark
+ light
+
+
+ 切换主题- {{ app.themeName }}
+ 切换模式{{ app.darkModeComp }}
+
+ test
+
+
+
+
+
diff --git a/src/views/login/LoginPage.vue b/src/views/login/LoginPage.vue
deleted file mode 100644
index 6e52447..0000000
--- a/src/views/login/LoginPage.vue
+++ /dev/null
@@ -1,9 +0,0 @@
-
- 测试页面
-
-
-
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
index 8c4efa2..6634d44 100644
--- a/src/vite-env.d.ts
+++ b/src/vite-env.d.ts
@@ -12,5 +12,21 @@ interface ImportMeta {
readonly env: ImportMetaEnv;
}
+declare module '*.vue' {
+ import type { DefineComponent } from 'vue';
+ const component: DefineComponent