Skip to content
This repository has been archived by the owner on Aug 28, 2024. It is now read-only.

Commit

Permalink
feat: support customize theme (#259)
Browse files Browse the repository at this point in the history
* chore: test themeOverrides

* chore: test useThemeVars

* Revert "chore: test useThemeVars"

This reverts commit 2596afa.

* generate colors

* chore: unocss colors

* chore: set cssVar

* chore: 颜色生成

* chore: 获取css变量值

* chore: theme store

* chore: remove theme logics

* chore: apply theme overrides

* chore: generate colors

* chore: 添加注释

* chore: 将组件库相关逻辑抽离到app.vue中

* chore: update ThemeColorConfig

* chore: remove @ant-design/colors
  • Loading branch information
likui628 authored Nov 8, 2023
1 parent 41e1d2f commit b9d944f
Show file tree
Hide file tree
Showing 10 changed files with 246 additions and 35 deletions.
27 changes: 23 additions & 4 deletions apps/admin/src/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import { useAppTheme, useWebTitle } from '@vben/hooks'
import { REDIRECT_NAME } from '@vben/constants'
import { getGlobalConfig, computedAsync } from '@vben/utils'
import AppProvider from '@/layout/components/app/AppProvider'
import { dateEnUS, dateZhCN, enUS, zhCN } from 'naive-ui'
import { unref, watch } from 'vue'
import { dateEnUS, dateZhCN, enUS, zhCN, darkTheme } from 'naive-ui'
import { unref, watch, computed } from 'vue'
// Support Multi-language
const { getLocale } = useLocale()
// Listening to page changes and dynamically changing site titles
const { title } = getGlobalConfig(import.meta.env)
useWebTitle(title, (route) => route.name !== REDIRECT_NAME)
const { isDark } = useAppTheme()
const { isDark, themeColors } = useAppTheme()
//监听是否暗黑模式
watch(
Expand Down Expand Up @@ -53,10 +53,29 @@ const locale = computedAsync(async () => {
return mod?.default ?? mod
})
const theme = computed(() => {
return unref(isDark) ? darkTheme : null
})
const themeOverrides = computed(() => {
return {
themeOverrides: {
common: {
...unref(themeColors),
},
},
}
})
</script>

<template>
<VbenConfig :locale="locale" :date-locale="dateLocale">
<VbenConfig
:locale="locale"
:date-locale="dateLocale"
:theme="theme"
v-bind="themeOverrides"
>
<VbenNotificationProvider>
<VbenMessageProvider>
<AppProvider>
Expand Down
122 changes: 119 additions & 3 deletions packages/hooks/src/web/useTheme.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useAppConfig } from '../config'
import { HandlerSettingEnum, ThemeEnum } from '@vben/constants'
import { useEventListener } from '@vben/utils'
import { computed, unref } from 'vue'
import { generateColors, setCssVar, useEventListener } from '@vben/utils'
import { computed, unref, watch } from 'vue'
import { useThemeStore, storeToRefs } from '@vben/stores'

export function createMediaPrefersColorSchemeListen() {
const { setAppConfig } = useAppConfig()
Expand All @@ -15,7 +16,34 @@ export function createMediaPrefersColorSchemeListen() {
)
}

interface ThemeColors {
primaryColor?: string
primaryColorHover?: string
primaryColorPressed?: string
primaryColorSuppl?: string
infoColor?: string
infoColorHover?: string
infoColorPressed?: string
infoColorSuppl?: string
successColor?: string
successColorHover?: string
successColorPressed?: string
successColorSuppl?: string
warningColor?: string
warningColorHover?: string
warningColorPressed?: string
warningColorSuppl?: string
errorColor?: string
errorColorHover?: string
errorColorPressed?: string
errorColorSuppl?: string
}

export const useAppTheme = () => {
const themeStore = useThemeStore()
//todo theme从themeStore里取
const { getTheme, getThemeConfig } = storeToRefs(themeStore)

const { theme, baseHandler } = useAppConfig()

const isDark = computed(() => {
Expand All @@ -27,5 +55,93 @@ export const useAppTheme = () => {
dark ? ThemeEnum.DARK : ThemeEnum.LIGHT,
)
}
return { isDark, theme, toggleTheme }

//TODO remove it
const themeColor = computed(() => {
return ''
})

const themeColors = computed(() => {
let colors: ThemeColors = {}
const themeConfig = getThemeConfig.value

if (themeConfig.primaryColor) {
const primaryColorList = generateColors(themeConfig.primaryColor)
colors = {
...colors,
...{
primaryColor: primaryColorList[5],
primaryColorHover: primaryColorList[4],
primaryColorPressed: primaryColorList[4],
primaryColorSuppl: primaryColorList[6],
},
}
}

if (themeConfig.infoColor) {
const infoColorList = generateColors(themeConfig.infoColor)
colors = {
...colors,
...{
infoColor: infoColorList[5],
infoColorHover: infoColorList[4],
infoColorPressed: infoColorList[4],
infoColorSuppl: infoColorList[6],
},
}
}

if (themeConfig.successColor) {
const successColorList = generateColors(themeConfig.successColor)
colors = {
...colors,
...{
successColor: successColorList[5],
successColorHover: successColorList[4],
successColorPressed: successColorList[4],
successColorSuppl: successColorList[6],
},
}
}

if (themeConfig.warningColor) {
const warningColorList = generateColors(themeConfig.warningColor)
colors = {
...colors,
...{
warningColor: warningColorList[5],
warningColorHover: warningColorList[4],
warningColorPressed: warningColorList[4],
warningColorSuppl: warningColorList[6],
},
}
}

if (themeConfig.errorColor) {
const errorColorList = generateColors(themeConfig.errorColor)
colors = {
...colors,
...{
errorColor: errorColorList[5],
errorColorHover: errorColorList[4],
errorColorPressed: errorColorList[4],
errorColorSuppl: errorColorList[6],
},
}
}
return colors
})

watch(
themeColors,
(val) => {
val.primaryColor && setCssVar('--primary-color', val.primaryColor)
val.successColor && setCssVar('--success-color', val.successColor)
val.errorColor && setCssVar('--error-color', val.errorColor)
val.warningColor && setCssVar('--warning-color', val.warningColor)
},
{ deep: true },
)

return { isDark, toggleTheme, themeColor, themeColors }
}
4 changes: 2 additions & 2 deletions packages/stores/src/modules/appConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ import {
} from '@vben/constants'

const defaultOptions: DefineAppConfigOptions = {
theme: ThemeEnum.LIGHT,
theme: ThemeEnum.LIGHT, //TODO remove
navBarMode: NavBarModeEnum.SIDEBAR,
themeColor: '',
themeColor: '', //TODO remove
showThemeModeToggle: true,
openKeepAlive: true,
useOpenBackTop: true,
Expand Down
1 change: 1 addition & 0 deletions packages/stores/src/modules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './layoutComposables'
export * from './appConfig'
export * from './siteGeneral'
export * from './multipleTab'
export * from './themeStore'
50 changes: 50 additions & 0 deletions packages/stores/src/modules/themeStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { defineStore } from 'pinia'
import { ThemeEnum } from '@vben/constants'

interface ThemeColorConfig {
primaryColor: string //主题色
infoColor: string //信息色
successColor: string //成功色
warningColor: string //警告色
errorColor: string //错误色
textBaseColor: string //文本色
bgBaseColor: string //背景色
}

interface ThemeStoreState {
themeConfig: ThemeColorConfig
theme: ThemeEnum
}

export const useThemeStore = defineStore({
id: 'APP_THEME',
state: (): ThemeStoreState => ({
themeConfig: {
primaryColor: '#2a64d4',
infoColor: '#2080F0',
successColor: '#52c41a',
warningColor: '#faad14',
errorColor: '#D03050',
textBaseColor: '#000000',
bgBaseColor: '#ffffff',
},
theme: ThemeEnum.LIGHT,
}),
getters: {
getThemeConfig(state) {
return state.themeConfig
},
getTheme(state) {
return state.theme
},
},
actions: {
setTheme(value: ThemeEnum) {
this.theme = value
},
setThemeConfig(config: Partial<ThemeColorConfig>) {
this.themeConfig = { ...this.themeConfig, ...config }
},
},
persist: true,
})
3 changes: 2 additions & 1 deletion packages/utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"pinia": "2.1.7",
"vue": "3.3.6",
"vue-router": "^4.2.5",
"sortablejs": "^1.15.0"
"sortablejs": "^1.15.0",
"@ant-design/colors": "^7.0.0"
},
"devDependencies": {
"@types/lodash-es": "^4.17.10",
Expand Down
28 changes: 28 additions & 0 deletions packages/utils/src/color.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { generate } from '@ant-design/colors'

/**
* 判断是否 十六进制颜色值.
* 输入形式可为 #fff000 #f00
Expand Down Expand Up @@ -176,3 +178,29 @@ export function pickTextColorBasedOnBgColor(
const L = 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]
return L < 0.8 ? darkColor : lightColor
}

interface GenerateColorsOptions {
theme?: 'dark' | 'default'
backgroundColor?: string
}

type GeneratedColors = [
string, //最浅色
string,
string,
string,
string,
string, //基本色
string,
string,
string,
string, //最深色
]
export function generateColors(
color: string,
opts: GenerateColorsOptions = { theme: 'default' },
) {
return generate(color, {
...opts,
}) as GeneratedColors
}
7 changes: 7 additions & 0 deletions packages/utils/src/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,10 @@ export function setCssVar(
) {
dom.style.setProperty(prop, val)
}

export function getCssVar(
prop: string,
dom: HTMLElement = document.documentElement,
) {
return dom.style.getPropertyValue(prop)
}
27 changes: 2 additions & 25 deletions packages/vbenComponents/src/config/src/Config.vue
Original file line number Diff line number Diff line change
@@ -1,33 +1,10 @@
<script lang="ts" setup name="VbenConfig">
import { maps } from '#/index'
import { computed, unref } from 'vue'
import { useAppTheme } from '@vben/hooks'
import { ThemeEnum } from '@vben/constants'
const Config = maps.get('Config')
const darkTheme = maps.get('DarkTheme')
const props = defineProps({
// Customize the configuration theme mode
themeMode: {
type: String,
default: 'dark'
},
// Whether to inherit the theme
inherit: {
type: Boolean,
default: true
}
})
const { isDark } = useAppTheme()
const appTheme = computed(() => {
if (!props.inherit){
return props.themeMode === ThemeEnum.DARK ? darkTheme : null
}
return unref(isDark) ? darkTheme : null
})
const Config = maps.get('Config')
</script>
<template>
<Config v-bind="$attrs" abstract :theme="appTheme"> <slot></slot></Config>
<Config abstract v-bind="$attrs"> <slot></slot></Config>
</template>

<style scoped></style>
12 changes: 12 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit b9d944f

Please sign in to comment.