diff --git a/package.json b/package.json index cf700b09..bf37383e 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,11 @@ "dependencies": { "@vueuse/core": "^7.5.1", "axios": "^0.24.0", + "colord": "^2.9.2", "crypto-js": "^4.1.1", "dayjs": "^1.10.7", "form-data": "^4.0.0", + "lodash-es": "^4.17.21", "naive-ui": "^2.23.2", "pinia": "^2.0.9", "qs": "^6.10.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f41ca153..86cdc563 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,6 +15,7 @@ specifiers: '@vue/eslint-config-typescript': ^10.0.0 '@vueuse/core': ^7.5.1 axios: ^0.24.0 + colord: ^2.9.2 commitizen: ^4.2.4 cross-env: ^7.0.3 crypto-js: ^4.1.1 @@ -30,6 +31,7 @@ specifiers: form-data: ^4.0.0 husky: ^7.0.4 lint-staged: ^12.1.4 + lodash-es: ^4.17.21 mockjs: ^1.1.0 naive-ui: ^2.23.2 patch-package: ^6.4.7 @@ -55,9 +57,11 @@ specifiers: dependencies: '@vueuse/core': registry.npmmirror.com/@vueuse/core/7.5.1_vue@3.2.26 axios: registry.npmmirror.com/axios/0.24.0 + colord: registry.npmmirror.com/colord/2.9.2 crypto-js: registry.npmmirror.com/crypto-js/4.1.1 dayjs: registry.npmmirror.com/dayjs/1.10.7 form-data: registry.nlark.com/form-data/4.0.0 + lodash-es: registry.npmmirror.com/lodash-es/4.17.21 naive-ui: registry.npmmirror.com/naive-ui/2.23.2_vue@3.2.26 pinia: registry.npmmirror.com/pinia/2.0.9_typescript@4.5.4+vue@3.2.26 qs: registry.npmmirror.com/qs/6.10.2 @@ -4217,6 +4221,12 @@ packages: dependencies: color-name: registry.nlark.com/color-name/1.1.4 + registry.npmmirror.com/colord/2.9.2: + resolution: {integrity: sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/colord/download/colord-2.9.2.tgz} + name: colord + version: 2.9.2 + dev: false + registry.npmmirror.com/colorette/2.0.16: resolution: {integrity: sha1-cTua+E/bAAE58EVGvUqT9ipQhdo=, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/colorette/download/colorette-2.0.16.tgz?cache=0&sync_timestamp=1633673609067&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fcolorette%2Fdownload%2Fcolorette-2.0.16.tgz} name: colorette diff --git a/src/App.vue b/src/App.vue index e905dd5b..9edec8f8 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,6 +1,19 @@ - + diff --git a/src/assets/base.css b/src/assets/base.css deleted file mode 100644 index 71dc55a3..00000000 --- a/src/assets/base.css +++ /dev/null @@ -1,74 +0,0 @@ -/* color palette from */ -:root { - --vt-c-white: #ffffff; - --vt-c-white-soft: #f8f8f8; - --vt-c-white-mute: #f2f2f2; - - --vt-c-black: #181818; - --vt-c-black-soft: #222222; - --vt-c-black-mute: #282828; - - --vt-c-indigo: #2c3e50; - - --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); - --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); - --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); - --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); - - --vt-c-text-light-1: var(--vt-c-indigo); - --vt-c-text-light-2: rgba(60, 60, 60, 0.66); - --vt-c-text-dark-1: var(--vt-c-white); - --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); -} - -/* semantic color variables for this project */ -:root { - --color-background: var(--vt-c-white); - --color-background-soft: var(--vt-c-white-soft); - --color-background-mute: var(--vt-c-white-mute); - - --color-border: var(--vt-c-divider-light-2); - --color-border-hover: var(--vt-c-divider-light-1); - - --color-heading: var(--vt-c-text-light-1); - --color-text: var(--vt-c-text-light-1); - - --section-gap: 160px; -} - -@media (prefers-color-scheme: dark) { - :root { - --color-background: var(--vt-c-black); - --color-background-soft: var(--vt-c-black-soft); - --color-background-mute: var(--vt-c-black-mute); - - --color-border: var(--vt-c-divider-dark-2); - --color-border-hover: var(--vt-c-divider-dark-1); - - --color-heading: var(--vt-c-text-dark-1); - --color-text: var(--vt-c-text-dark-2); - } -} - -*, -*::before, -*::after { - box-sizing: border-box; - margin: 0; - position: relative; - font-weight: normal; -} - -body { - min-height: 100vh; - color: var(--color-text); - background: var(--color-background); - transition: color 0.5s, background-color 0.5s; - line-height: 1.6; - font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, - Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; - font-size: 15px; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} diff --git a/src/assets/logo.svg b/src/assets/logo.svg deleted file mode 100644 index bc826fed..00000000 --- a/src/assets/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 07ace1c4..69e55df9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,10 +4,6 @@ import { setupRouter } from '@/router'; import { setupStore } from '@/store'; import App from './App.vue'; -function setupPlugins() { - setupAssets(); -} - async function setupApp() { const app = createApp(App); @@ -21,5 +17,6 @@ async function setupApp() { app.mount('#app'); } -setupPlugins(); +setupAssets(); + setupApp(); diff --git a/src/store/modules/theme/helpers.ts b/src/store/modules/theme/helpers.ts new file mode 100644 index 00000000..271b8dd3 --- /dev/null +++ b/src/store/modules/theme/helpers.ts @@ -0,0 +1,40 @@ +import { colord } from 'colord'; +import { getColorPalette } from '@/utils'; + +type ColorType = 'primary' | 'info' | 'success' | 'warning' | 'error'; + +type ColorScene = '' | 'Suppl' | 'Hover' | 'Pressed' | 'Active'; + +type ColorKey = `${ColorType}Color${ColorScene}`; + +type ThemeColor = { + [key in ColorKey]?: string; +}; + +interface ColorAction { + scene: ColorScene; + handler: (color: string) => string; +} + +/** 获取主题颜色的各种场景对应的颜色 */ +export function getThemeColors(colors: [ColorType, string][]) { + const colorActions: ColorAction[] = [ + { scene: '', handler: color => color }, + { scene: 'Suppl', handler: color => color }, + { scene: 'Hover', handler: color => getColorPalette(color, 5) }, + { scene: 'Pressed', handler: color => getColorPalette(color, 7) }, + { scene: 'Active', handler: color => colord(color).alpha(0.1).toHex() } + ]; + + const themeColor: ThemeColor = {}; + + colors.forEach(color => { + colorActions.forEach(action => { + const [colorType, colorValue] = color; + const colorKey: ColorKey = `${colorType}Color${action.scene}`; + themeColor[colorKey] = action.handler(colorValue); + }); + }); + + return themeColor; +} diff --git a/src/store/modules/theme/index.ts b/src/store/modules/theme/index.ts index 1d5a0fe0..23884f08 100644 --- a/src/store/modules/theme/index.ts +++ b/src/store/modules/theme/index.ts @@ -1,7 +1,87 @@ +import { ref, computed } from 'vue'; +import type { Ref, ComputedRef } from 'vue'; import { defineStore } from 'pinia'; +import { useThemeVars } from 'naive-ui'; +import type { GlobalThemeOverrides } from 'naive-ui'; +import { kebabCase } from 'lodash-es'; +import { getColorPalette } from '@/utils'; +import { getThemeColors } from './helpers'; -// interface ThemeStore { -// primary: string; -// } +interface OtherColor { + /** 信息 */ + info: string; + /** 成功 */ + success: string; + /** 警告 */ + warning: string; + /** 错误 */ + error: string; +} -export const useThemeStore = defineStore('theme-store', () => {}); +interface ThemeStore { + /** 主题颜色 */ + themeColor: Ref; + /** 其他颜色 */ + otherColor: ComputedRef; + /** naiveUI的主题配置 */ + naiveThemeOverrides: ComputedRef; + /** 添加css vars至html */ + addThemeCssVarsToRoot(): void; +} + +type ThemeVarsKeys = keyof Exclude; + +export const useThemeStore = defineStore('theme-store', () => { + const themeVars = useThemeVars(); + + const themeColor = ref('#1890ff'); + const otherColor = computed(() => ({ + info: getColorPalette(themeColor.value, 7), + success: '#52c41a', + warning: '#faad14', + error: '#f5222d' + })); + + const naiveThemeOverrides = computed(() => { + const { info, success, warning, error } = otherColor.value; + const themeColors = getThemeColors([ + ['primary', themeColor.value], + ['info', info], + ['success', success], + ['warning', warning], + ['error', error] + ]); + + const colorLoading = themeColor.value; + + return { + common: { + ...themeColors + }, + LoadingBar: { + colorLoading + } + }; + }); + + function addThemeCssVarsToRoot() { + const updatedThemeVars = { ...themeVars.value }; + Object.assign(updatedThemeVars, naiveThemeOverrides.value.common); + const keys = Object.keys(updatedThemeVars) as ThemeVarsKeys[]; + const style: string[] = []; + keys.forEach(key => { + style.push(`--${kebabCase(key)}: ${updatedThemeVars[key]}`); + }); + const styleStr = style.join(';'); + document.documentElement.style.cssText += styleStr; + } + + const themeStore: ThemeStore = { + themeColor, + otherColor, + naiveThemeOverrides, + addThemeCssVarsToRoot + }; + + return themeStore; +}); diff --git a/src/utils/common/color.ts b/src/utils/common/color.ts new file mode 100644 index 00000000..29a42176 --- /dev/null +++ b/src/utils/common/color.ts @@ -0,0 +1,116 @@ +import { colord } from 'colord'; +import type { HsvColor } from 'colord'; + +type ColorIndex = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10; + +const hueStep = 2; +const saturationStep = 16; +const saturationStep2 = 5; +const brightnessStep1 = 5; +const brightnessStep2 = 15; +const lightColorCount = 5; +const darkColorCount = 4; + +/** + * 根据颜色获取调色板颜色(从左至右颜色从浅到深,6为主色号) + * @param color - 颜色 + * @param index - 调色板的对应的色号(6为主色号) + * @description 算法实现从ant-design调色板算法中借鉴 https://github.com/ant-design/ant-design/blob/master/components/style/color/colorPalette.less + */ +export function getColorPalette(color: string, index: ColorIndex) { + if (index === 6) return color; + + const isLight = index < 6; + const hsv = colord(color).toHsv(); + const i = isLight ? lightColorCount + 1 - index : index - lightColorCount - 1; + + const newHsv: HsvColor = { + h: getHue(hsv, i, isLight), + s: getSaturation(hsv, i, isLight), + v: getValue(hsv, i, isLight) + }; + + return colord(newHsv).toHex(); +} + +/** + * 根据颜色获取调色板颜色所有颜色 + * @param color - 颜色 + */ +export function getAllColorPalette(color: string) { + const indexs: ColorIndex[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + return indexs.map(index => getColorPalette(color, index)); +} + +/** + * 获取色相渐变 + * @param hsv - hsv格式颜色值 + * @param i - 与6的相对距离 + * @param isLight - 是否是亮颜色 + */ +function getHue(hsv: HsvColor, i: number, isLight: boolean) { + let hue: number; + if (hsv.h >= 60 && hsv.h <= 240) { + // 冷色调 + // 减淡变亮 色相顺时针旋转 更暖 + // 加深变暗 色相逆时针旋转 更冷 + hue = isLight ? hsv.h - hueStep * i : hsv.h + hueStep * i; + } else { + // 暖色调 + // 减淡变亮 色相逆时针旋转 更暖 + // 加深变暗 色相顺时针旋转 更冷 + hue = isLight ? hsv.h + hueStep * i : hsv.h - hueStep * i; + } + if (hue < 0) { + hue += 360; + } else if (hue >= 360) { + hue -= 360; + } + return hue; +} + +/** + * 获取饱和度渐变 + * @param hsv - hsv格式颜色值 + * @param i - 与6的相对距离 + * @param isLight - 是否是亮颜色 + */ +function getSaturation(hsv: HsvColor, i: number, isLight: boolean) { + let saturation: number; + if (isLight) { + saturation = hsv.s - saturationStep * i; + } else if (i === darkColorCount) { + saturation = hsv.s + saturationStep; + } else { + saturation = hsv.s + saturationStep2 * i; + } + if (saturation > 100) { + saturation = 100; + } + if (isLight && i === lightColorCount && saturation > 10) { + saturation = 10; + } + if (saturation < 6) { + saturation = 6; + } + return saturation; +} + +/** + * 获取明度渐变 + * @param hsv - hsv格式颜色值 + * @param i - 与6的相对距离 + * @param isLight - 是否是亮颜色 + */ +function getValue(hsv: HsvColor, i: number, isLight: boolean) { + let value: number; + if (isLight) { + value = hsv.v + brightnessStep1 * i; + } else { + value = hsv.v - brightnessStep2 * i; + } + if (value > 100) { + value = 100; + } + return value; +} diff --git a/src/utils/common/index.ts b/src/utils/common/index.ts index 6502676e..72ad0c66 100644 --- a/src/utils/common/index.ts +++ b/src/utils/common/index.ts @@ -1,3 +1,4 @@ export * from './typeof'; export * from './console'; +export * from './color'; export * from './design-pattern'; diff --git a/src/views/system/login/index.vue b/src/views/system/login/index.vue index a49ec556..0813c3ca 100644 --- a/src/views/system/login/index.vue +++ b/src/views/system/login/index.vue @@ -1,5 +1,7 @@ diff --git a/windi.config.ts b/windi.config.ts index 49a1b798..94f59d86 100644 --- a/windi.config.ts +++ b/windi.config.ts @@ -42,7 +42,30 @@ export default defineConfig({ 'ellipsis-text': 'nowrap-hidden overflow-ellipsis' }, theme: { - extend: {} + extend: { + colors: { + primary: 'var(--primary-color)', + 'primary-hover': 'var(--primary-color-hover)', + 'primary-pressed': 'var(--primary-color-pressed)', + 'primary-active': 'var(--primary-color-active)', + info: 'var(--info-color)', + 'info-hover': 'var(--info-color-hover)', + 'info-pressed': 'var(--info-color-pressed)', + 'info-active': 'var(--info-color-active)', + success: 'var(--success-color)', + 'success-hover': 'var(--success-color-hover)', + 'success-pressed': 'var(--success-color-pressed)', + 'success-active': 'var(--success-color-active)', + warning: 'var(--warning-color)', + 'warning-hover': 'var(--warning-color-hover)', + 'warning-pressed': 'var(--warning-color-pressed)', + 'warning-active': 'var(--warning-color-active)', + error: 'var(--error-color)', + 'error-hover': 'var(--error-color-hover)', + 'error-pressed': 'var(--error-color-pressed)', + 'error-active': 'var(--error-color-active)' + } + } }, variants: {}, plugins: []