diff --git a/CHANGELOG.zh_CN.md b/CHANGELOG.zh_CN.md index ccef47a853c85b849e0506c3ccde89f37687b899..21327f6a9e1ab60e4ae5273af4ed9c6ee9a06271 100644 --- a/CHANGELOG.zh_CN.md +++ b/CHANGELOG.zh_CN.md @@ -4,6 +4,7 @@ - 表单项的`componentsProps`支持函数类型 - 菜单新增 tag 显示 +- 新增菜单及顶栏颜色选择配色 ### ⚡ Performance Improvements diff --git a/index.html b/index.html index e729b5daa494a6009c8f04f3065e8bf38b6fd4d0..5702b863a57f363392a7dbd1bb2a908ddf832ec9 100644 --- a/index.html +++ b/index.html @@ -43,7 +43,7 @@ .app-loading .g-loading { display: block; - width: 64px; + width: 48px; margin: 30px auto; -webkit-animation: load 1.2s linear infinite; animation: load 1.2s linear infinite; diff --git a/src/components/Menu/src/BasicMenu.tsx b/src/components/Menu/src/BasicMenu.tsx index 41ca01217997217eca517251f76828d2f084405d..5842134c403a52e3e5d01be41212f45be39b54a9 100644 --- a/src/components/Menu/src/BasicMenu.tsx +++ b/src/components/Menu/src/BasicMenu.tsx @@ -1,7 +1,7 @@ import type { MenuState } from './types'; import type { Menu as MenuType } from '/@/router/types'; -import { computed, defineComponent, unref, reactive, toRef, watch, onMounted, ref } from 'vue'; +import { computed, defineComponent, unref, reactive, watch, onMounted, ref, toRefs } from 'vue'; import { Menu } from 'ant-design-vue'; import SearchInput from './SearchInput.vue'; import MenuContent from './MenuContent'; @@ -40,8 +40,10 @@ export default defineComponent({ }); const { currentRoute } = useRouter(); + const { items, flatItems, isAppMenu, mode, accordion } = toRefs(props); + const { handleInputChange, handleInputClick } = useSearchInput({ - flatMenusRef: toRef(props, 'flatItems'), + flatMenusRef: flatItems, emit: emit, menuState, handleMenuChange, @@ -49,11 +51,11 @@ export default defineComponent({ const { handleOpenChange, resetKeys, setOpenKeys } = useOpenKeys( menuState, - toRef(props, 'items'), - toRef(props, 'flatItems'), - toRef(props, 'isAppMenu'), - toRef(props, 'mode'), - toRef(props, 'accordion') + items, + flatItems, + isAppMenu, + mode, + accordion ); const getOpenKeys = computed(() => { @@ -98,6 +100,8 @@ export default defineComponent({ return cls; }); + const showTitle = computed(() => props.collapsedShowTitle && menuStore.getCollapsedState); + watch( () => currentRoute.value.name, (name: string) => { @@ -130,9 +134,7 @@ export default defineComponent({ const { beforeClickFn } = props; if (beforeClickFn && isFunction(beforeClickFn)) { const flag = await beforeClickFn(menu); - if (!flag) { - return; - } + if (!flag) return; } const { path } = menu; menuState.selectedKeys = [path]; @@ -141,9 +143,7 @@ export default defineComponent({ function handleMenuChange() { const { flatItems } = props; - if (!unref(flatItems) || flatItems.length === 0) { - return; - } + if (!unref(flatItems) || flatItems.length === 0) return; const findMenu = flatItems.find((menu) => menu.path === unref(currentRoute).path); if (findMenu) { if (menuState.mode !== MenuModeEnum.HORIZONTAL) { @@ -155,10 +155,6 @@ export default defineComponent({ } } - const showTitle = computed(() => { - return props.collapsedShowTitle && menuStore.getCollapsedState; - }); - // render menu item function renderMenuItem(menuList?: MenuType[], index = 1) { if (!menuList) return; @@ -183,6 +179,7 @@ export default defineComponent({ , @@ -198,6 +195,7 @@ export default defineComponent({ showTitle={unref(showTitle)} item={menu} level={index} + isTop={props.isTop} searchValue={menuState.searchValue} />, ], diff --git a/src/components/Menu/src/MenuContent.tsx b/src/components/Menu/src/MenuContent.tsx index f0ffb026d65bdf9e4dd1deebca53b06d12a2918a..d4321a145575d85bb2235dd12f744b7ac7022a08 100644 --- a/src/components/Menu/src/MenuContent.tsx +++ b/src/components/Menu/src/MenuContent.tsx @@ -26,6 +26,10 @@ export default defineComponent({ type: Number as PropType, default: 0, }, + isTop: { + type: Boolean as PropType, + default: true, + }, }, setup(props) { /** @@ -56,14 +60,16 @@ export default defineComponent({ if (!props.item) { return null; } - const { showTitle } = props; + const { showTitle, isTop } = props; const { name, icon } = props.item; const searchValue = props.searchValue || ''; const index = name.indexOf(searchValue); const beforeStr = name.substr(0, index); const afterStr = name.substr(index + searchValue.length); - const cls = showTitle ? 'show-title' : 'basic-menu__name'; + let cls = showTitle ? ['show-title'] : ['basic-menu__name']; + + isTop && !showTitle && (cls = []); return ( <> {renderIcon(icon!)} diff --git a/src/components/Menu/src/SearchInput.vue b/src/components/Menu/src/SearchInput.vue index 7e60bc0104c747711827c0255abc0a953c9ffafa..80e00541c26ed85edf16b6559b0ae42dc65c7bda 100644 --- a/src/components/Menu/src/SearchInput.vue +++ b/src/components/Menu/src/SearchInput.vue @@ -102,7 +102,7 @@ .set-bg() { color: #fff; - background: @input-dark-bg-color; + background: @sider-dark-lighten-1-bg-color; border: 0; outline: none; } diff --git a/src/components/Menu/src/index.less b/src/components/Menu/src/index.less index 096a3f8fca6709d87a061188305b3573e9d3c76e..3cfa59f58da31e956f1ed09061fd199ca4e5f30f 100644 --- a/src/components/Menu/src/index.less +++ b/src/components/Menu/src/index.less @@ -52,10 +52,11 @@ // collapsed show title end .ant-menu-submenu-title { > .basic-menu__name { - display: flex; - width: 100%; - justify-content: space-between; - align-items: center; + .basic-menu__tag { + float: right; + margin-top: @app-menu-item-height / 2; + transform: translate(0%, -50%); + } } } @@ -254,7 +255,7 @@ // 层级样式 &.ant-menu-dark:not(.basic-menu__sidebar-hor) { overflow-x: hidden; - background: @menu-item-dark-bg-color; + background: @sider-dark-bg-color; .active-menu-style(); .ant-menu-item.ant-menu-item-selected.basic-menu-menu-item__level1, @@ -263,21 +264,20 @@ } .basic-menu-item__level1 { - background-color: @menu-item-dark-bg-color; + background-color: @sider-dark-bg-color; > .ant-menu-sub > li { - background-color: lighten(@menu-item-dark-bg-color, 6%); + background-color: @sider-dark-lighten-1-bg-color; } } .basic-menu-item__level2:not(.ant-menu-item-selected), .ant-menu-sub { - background-color: lighten(@menu-item-dark-bg-color, 6%); - // background-color: @sub-menu-item-dark-bg-color; + background-color: @sider-dark-lighten-1-bg-color; } .basic-menu-item__level3:not(.ant-menu-item-selected) { - background-color: lighten(@menu-item-dark-bg-color, 10%); + background-color: @sider-dark-lighten-2-bg-color; } .ant-menu-submenu-title { @@ -290,7 +290,7 @@ &.ant-menu-inline-collapsed { .ant-menu-submenu-selected, .ant-menu-item-selected { - background: darken(@menu-item-dark-bg-color, 6%) !important; + background: @sider-dark-darken-bg-color !important; } } } @@ -359,7 +359,7 @@ .ant-menu-dark { &.ant-menu-submenu-popup { > ul { - background: @menu-item-dark-bg-color; + background: @sider-dark-bg-color; } .active-menu-style(); diff --git a/src/design/color.less b/src/design/color.less index 7b8491785a8b8838529e89f1402c06b1e5f5823d..836755ad11a71868b73b193377106bfd2755bdd5 100644 --- a/src/design/color.less +++ b/src/design/color.less @@ -1,3 +1,17 @@ +:root { + // header + --header-bg-color: #394664; + --header-bg-hover-color: #273352; + --header-active-menu-bg-color: #273352; + + // sider + --sider-dark-bg-color: #273352; + --sider-dark-darken-bg-color: #273352; + --sider-dark-lighten-1-bg-color: #273352; + --sider-dark-lighten-2-bg-color: #273352; + --sider-dark-lighten-3-bg-color: #273352; +} + @white: #fff; @info-color: @primary-color; @@ -53,21 +67,24 @@ // ==============Header============= // ================================= -@header-dark-bg-color: #394664; -@header-dark-bg-hover-color: #273352; +@header-dark-bg-color: var(--header-bg-color); +@header-dark-bg-hover-color: var(--header-bg-hover-color); @header-light-bg-hover-color: #f6f6f6; @header-light-desc-color: #7c8087; @header-light-bottom-border-color: #eee; +// top-menu +@top-menu-active-bg-color: var(--header-active-menu-bg-color); // ================================= // ==============Menu============ // ================================= // let -menu -@menu-item-dark-bg-color: #273352; - -// top-menu -@top-menu-active-bg-color: #273352; +@sider-dark-bg-color: var(--sider-dark-bg-color); +@sider-dark-darken-bg-color: var(--sider-dark-darken-bg-color); +@sider-dark-lighten-1-bg-color: var(--sider-dark-lighten-1-bg-color); +@sider-dark-lighten-2-bg-color: var(--sider-dark-lighten-2-bg-color); +@sider-dark-lighten-3-bg-color: var(--sider-dark-lighten-3-bg-color); // trigger @trigger-dark-hover-bg-color: rgba(255, 255, 255, 0.2); diff --git a/src/layouts/default/header/LayoutHeader.tsx b/src/layouts/default/header/LayoutHeader.tsx index 830070f8f6af2c6434fc750d5c01531be994eac8..f81f4020d4fe04b84fd14dc08603846706ff94fc 100644 --- a/src/layouts/default/header/LayoutHeader.tsx +++ b/src/layouts/default/header/LayoutHeader.tsx @@ -79,9 +79,8 @@ export default defineComponent({ }); const showHeaderTrigger = computed(() => { - const { show, trigger, hidden } = unref(getProjectConfigRef).menuSetting; - - if (!show || !hidden) return false; + const { show, trigger, hidden, type } = unref(getProjectConfigRef).menuSetting; + if (type === MenuTypeEnum.TOP_MENU || !show || !hidden) return false; return trigger === TriggerEnum.HEADER; }); diff --git a/src/layouts/default/index.less b/src/layouts/default/index.less index 831411c11a1c15a2c0935a22e7138dad08a8225d..681e9b390b2939a04805eec2971b45eb2284af15 100644 --- a/src/layouts/default/index.less +++ b/src/layouts/default/index.less @@ -41,7 +41,7 @@ background-size: 100% 100%; &.ant-layout-sider-dark { - background: @menu-item-dark-bg-color; + background: @sider-dark-bg-color; } &:not(.ant-layout-sider-dark) { diff --git a/src/layouts/default/index.tsx b/src/layouts/default/index.tsx index f765b8d36ebcc7445d19de454282fbcef512714c..7d98ca152c40405efce8f5b4f4f5bee9381b3530 100644 --- a/src/layouts/default/index.tsx +++ b/src/layouts/default/index.tsx @@ -25,13 +25,9 @@ export default defineComponent({ const { getFullContent } = useFullContent(); - const getProjectConfigRef = computed(() => { - return appStore.getProjectConfig; - }); + const getProjectConfigRef = computed(() => appStore.getProjectConfig); - const getLockMainScrollStateRef = computed(() => { - return appStore.getLockMainScrollState; - }); + const getLockMainScrollStateRef = computed(() => appStore.getLockMainScrollState); const showHeaderRef = computed(() => { const { @@ -47,6 +43,12 @@ export default defineComponent({ return type !== MenuTypeEnum.SIDEBAR && unref(showHeaderRef); }); + const getIsLockRef = computed(() => { + const { getLockInfo } = appStore; + const { isLock } = getLockInfo; + return isLock; + }); + const showSideBarRef = computed(() => { const { menuSetting: { show, mode, split }, @@ -54,59 +56,74 @@ export default defineComponent({ return split || (show && mode !== MenuModeEnum.HORIZONTAL && !unref(getFullContent)); }); - function getTarget(): any { - const { - headerSetting: { fixed }, - } = unref(getProjectConfigRef); - return document.querySelector(`.default-layout__${fixed ? 'main' : 'content'}`); - } + const showFullHeaderRef = computed(() => { + return !unref(getFullContent) && unref(isShowMixHeaderRef) && unref(showHeaderRef); + }); - return () => { - const { getLockInfo } = appStore; + const showInsetHeaderRef = computed(() => { + return !unref(getFullContent) && !unref(isShowMixHeaderRef) && unref(showHeaderRef); + }); + + const fixedHeaderClsRef = computed(() => { const { - useOpenBackTop, - showSettingButton, - multiTabsSetting: { show: showTabs }, headerSetting: { fixed }, - menuSetting: { split, hidden }, } = unref(getProjectConfigRef); - const fixedHeaderCls = fixed ? 'fixed' + (unref(getLockMainScrollStateRef) ? ' lock' : '') : ''; + return fixedHeaderCls; + }); - const { isLock } = getLockInfo; + const showTabsRef = computed(() => { + const { + multiTabsSetting: { show }, + } = unref(getProjectConfigRef); + return show && !unref(getFullContent); + }); + + const showClassSideBarRef = computed(() => { + const { + menuSetting: { split, hidden }, + } = unref(getProjectConfigRef); + return split ? hidden : true; + }); - const showSideBar = split ? hidden : true; + function getTarget(): any { + const { + headerSetting: { fixed }, + } = unref(getProjectConfigRef); + return document.querySelector(`.default-layout__${fixed ? 'main' : 'content'}`); + } + + return () => { + const { useOpenBackTop, showSettingButton } = unref(getProjectConfigRef); return ( {() => ( <> {/* lock page */} - {isLock && } + {unref(getIsLockRef) && } {/* back top */} {useOpenBackTop && } {/* open setting drawer */} {showSettingButton && } - {!unref(getFullContent) && unref(isShowMixHeaderRef) && unref(showHeaderRef) && ( - - )} + {unref(showFullHeaderRef) && } {() => ( <> - {unref(showSideBarRef) && } - + {unref(showSideBarRef) && ( + + )} + {() => ( <> - {!unref(getFullContent) && - !unref(isShowMixHeaderRef) && - unref(showHeaderRef) && } + {unref(showInsetHeaderRef) && } - {showTabs && !unref(getFullContent) && } + {unref(showTabsRef) && } - + > )} diff --git a/src/layouts/default/setting/SettingDrawer.tsx b/src/layouts/default/setting/SettingDrawer.tsx index 0d67d3c9aa2c0ea32ae324f19f55636191a998a0..381fed4d1cd2379a567a67cffc7a80bf335ca90d 100644 --- a/src/layouts/default/setting/SettingDrawer.tsx +++ b/src/layouts/default/setting/SettingDrawer.tsx @@ -20,12 +20,12 @@ import { updateColorWeak, updateGrayMode } from '/@/setup/theme'; import { baseHandler } from './handler'; import { HandlerEnum, - themeOptions, contentModeOptions, topMenuAlignOptions, menuTriggerOptions, routerTransitionOptions, } from './const'; +import { HEADER_PRESET_BG_COLOR_LIST, SIDE_BAR_BG_COLOR_LIST } from '/@/settings/colorSetting'; interface SwitchOptions { config?: DeepPartial; @@ -41,6 +41,11 @@ interface SelectConfig { handler?: Fn; } +interface ThemeOptions { + def?: string; + handler?: Fn; +} + export default defineComponent({ name: 'SettingDrawer', setup(_, { attrs }) { @@ -98,8 +103,7 @@ export default defineComponent({ function renderSidebar() { const { - headerSetting: { theme: headerTheme }, - menuSetting: { type, theme: menuTheme, split }, + menuSetting: { type, split }, } = unref(getProjectConfigRef); const typeList = ref([ @@ -154,22 +158,22 @@ export default defineComponent({ def: split, disabled: !unref(getShowMenuRef) || type !== MenuTypeEnum.MIX, }), - renderSelectItem('顶栏主题', { - handler: (e) => { - baseHandler(HandlerEnum.HEADER_THEME, e); - }, - def: headerTheme, - options: themeOptions, - disabled: !unref(getShowHeaderRef), - }), - renderSelectItem('菜单主题', { - handler: (e) => { - baseHandler(HandlerEnum.MENU_THEME, e); - }, - def: menuTheme, - options: themeOptions, - disabled: !unref(getShowMenuRef), - }), + // renderSelectItem('顶栏主题', { + // handler: (e) => { + // baseHandler(HandlerEnum.HEADER_THEME, e); + // }, + // def: headerTheme, + // options: themeOptions, + // disabled: !unref(getShowHeaderRef), + // }), + // renderSelectItem('菜单主题', { + // handler: (e) => { + // baseHandler(HandlerEnum.MENU_THEME, e); + // }, + // def: menuTheme, + // options: themeOptions, + // disabled: !unref(getShowMenuRef), + // }), ]; } /** @@ -413,7 +417,6 @@ export default defineComponent({ return ( {text} - {/* @ts-ignore */} + {() => '顶栏主题'} + {renderThemeItem(HEADER_PRESET_BG_COLOR_LIST, { + def: headerBgColor, + handler: (e) => { + baseHandler(HandlerEnum.HEADER_THEME, e); + }, + })} + {() => '菜单主题'} + {renderThemeItem(SIDE_BAR_BG_COLOR_LIST, { + def: menuBgColor, + handler: (e) => { + baseHandler(HandlerEnum.MENU_THEME, e); + }, + })} + > + ); + } + + function renderThemeItem(colorList: string[], opt: ThemeOptions) { + const { def, handler } = opt; + return ( + + {colorList.map((item) => { + return ( + handler && handler(item)} + key={item} + class={[def === item ? 'active' : '']} + style={{ + background: item, + }} + > + + + ); + })} + + ); + } + return () => ( {{ @@ -454,6 +501,9 @@ export default defineComponent({ <> {() => '导航栏模式'} {renderSidebar()} + + {renderTheme()} + {() => '界面功能'} {renderFeatures()} {() => '界面显示'} diff --git a/src/layouts/default/setting/handler.ts b/src/layouts/default/setting/handler.ts index a30818b665635d1a8860274585398041c3b1c103..7e98d8a9dbfa872840679ef636f2bdc5324f1e40 100644 --- a/src/layouts/default/setting/handler.ts +++ b/src/layouts/default/setting/handler.ts @@ -1,6 +1,11 @@ import { HandlerEnum } from './const'; -import { MenuThemeEnum, MenuTypeEnum } from '/@/enums/menuEnum'; -import { updateColorWeak, updateGrayMode } from '/@/setup/theme'; +// import { MenuThemeEnum, MenuTypeEnum } from '/@/enums/menuEnum'; +import { + updateColorWeak, + updateGrayMode, + updateHeaderBgColor, + updateSidebarBgColor, +} from '/@/setup/theme'; import { appStore } from '/@/store/modules/app'; import { ProjectConfig } from '/@/types/config'; @@ -14,12 +19,12 @@ export function handler(event: HandlerEnum, value: any): DeepPartial span { width: 20px; height: 20px; - margin-top: 10px; - margin-right: 10px; cursor: pointer; - border-radius: 4px; + border: 1px solid #ddd; + border-radius: 2px; svg { display: none; } &.active { + border: 1px solid lighten(@primary-color, 10%); + svg { display: inline-block; - margin-left: 4px; - font-size: 0.8em; + margin: 0 0 3px 3px; + font-size: 12px; fill: @white; } } diff --git a/src/layouts/page/index.tsx b/src/layouts/page/index.tsx index fd31615cb75b90fd5eae5bb8486a6ecacf46c3b5..68154df71df8121cb90dba98ac7281ff46542b19 100644 --- a/src/layouts/page/index.tsx +++ b/src/layouts/page/index.tsx @@ -41,13 +41,12 @@ export default defineComponent({ // No longer show animations that are already in the tab const name = route.meta.inTab ? 'fade' : null; - // TODO add key? const Content = openCache ? ( - + ) : ( - + ); return openRouterTransition ? ( { await scrollWaiter.wait(); if (savedPosition) { diff --git a/src/utils/scrollWaiter.ts b/src/router/scrollWaiter.ts similarity index 82% rename from src/utils/scrollWaiter.ts rename to src/router/scrollWaiter.ts index 8311962a909430e2324593a6a3561aa248d3b1f7..df234a8a7c0dc7890103b23bc146c9e66acf2288 100644 --- a/src/utils/scrollWaiter.ts +++ b/src/router/scrollWaiter.ts @@ -1,3 +1,4 @@ +// see https://github.com/vuejs/vue-router-next/blob/master/playground/scrollWaiter.ts class ScrollQueue { private resolve: (() => void) | null = null; private promise: Promise | null = null; diff --git a/src/settings/colorSetting.ts b/src/settings/colorSetting.ts new file mode 100644 index 0000000000000000000000000000000000000000..50e7f21995836059345fed5f633d55da9cf270ca --- /dev/null +++ b/src/settings/colorSetting.ts @@ -0,0 +1,24 @@ +// header preset color +export const HEADER_PRESET_BG_COLOR_LIST: string[] = [ + '#ffffff', + '#009688', + '#18bc9c', + '#1E9FFF', + '#018ffb', + '#409eff', + '#4e73df', + '#e74c3c', + '#f39c12', + '#394664', + '#001529', +]; + +// sider preset color +export const SIDE_BAR_BG_COLOR_LIST: string[] = [ + '#273352', + '#ffffff', + '#001529', + '#304156', + '#28333E', + '#344058', +]; diff --git a/src/settings/projectSetting.ts b/src/settings/projectSetting.ts index 2249b27e7d108c1a560eece417a857f9c3c0ccab..dfbf70e0d817db45bfb6727e5118e81ec9459d72 100644 --- a/src/settings/projectSetting.ts +++ b/src/settings/projectSetting.ts @@ -7,6 +7,16 @@ import { isProdMode } from '/@/utils/env'; // ! You need to clear the browser cache after the change const setting: ProjectConfig = { + // color + // TODO 主题色 + themeColor: primaryColor, + + // header bg color + headerBgColor: '#ffffff', + + // sidebar menu bg color + menuBgColor: '#273352', + // Whether to show the configuration button showSettingButton: true, // 权限模式 @@ -15,8 +25,7 @@ const setting: ProjectConfig = { grayMode: false, // 色弱模式 colorWeak: false, - // 主题色 - themeColor: primaryColor, + // 是否取消菜单,顶部,多标签页显示, 用于可能内嵌在别的系统内 fullContent: false, // content mode diff --git a/src/setup/theme/index.ts b/src/setup/theme/index.ts index 6889fd78df1500f3a5852ff125040b1fcfcfcd3e..72b317d42c49bab139661fe69adfa7c843641cdd 100644 --- a/src/setup/theme/index.ts +++ b/src/setup/theme/index.ts @@ -1,9 +1,24 @@ +import useCssVar from '/@/hooks/web/useCssVar'; +import { isHexColor, colorIsDark, lighten, darken } from '/@/utils/color'; +import { appStore } from '/@/store/modules/app'; +import { MenuThemeEnum } from '/@/enums/menuEnum'; + +const HEADER_BG_COLOR_VAR = '--header-bg-color'; +const HEADER_BG_HOVER_COLOR_VAR = '--header-bg-hover-color'; +const HEADER_MENU_ACTIVE_BG_COLOR_VAR = '--header-active-menu-bg-color'; + +const SIDER_DARK_BG_COLOR = '--sider-dark-bg-color'; +const SIDER_DARK_DARKEN_BG_COLOR = '--sider-dark-darken-bg-color'; +const SIDER_LIGHTEN_1_BG_COLOR = '--sider-dark-lighten-1-bg-color'; +const SIDER_LIGHTEN_2_BG_COLOR = '--sider-dark-lighten-2-bg-color'; + function toggleClass(flag: boolean, clsName: string) { const body = document.body; let { className } = body; className = className.replace(clsName, ''); document.body.className = flag ? `${className} ${clsName} ` : className; } + export const updateColorWeak = (colorWeak: boolean) => { toggleClass(colorWeak, 'color-weak'); }; @@ -11,3 +26,46 @@ export const updateColorWeak = (colorWeak: boolean) => { export const updateGrayMode = (gray: boolean) => { toggleClass(gray, 'gray-mode'); }; + +export function updateHeaderBgColor(color: string) { + if (!isHexColor(color)) return; + const bgColorRef = useCssVar(HEADER_BG_COLOR_VAR); + const bgHoverColorRef = useCssVar(HEADER_BG_HOVER_COLOR_VAR); + const topMenuActiveBgColorRef = useCssVar(HEADER_MENU_ACTIVE_BG_COLOR_VAR); + // bg color + bgColorRef.value = color; + // hover color + const hoverColor = lighten(color, 6); + bgHoverColorRef.value = hoverColor; + topMenuActiveBgColorRef.value = hoverColor; + + const isDark = colorIsDark(color); + + appStore.commitProjectConfigState({ + headerSetting: { + theme: isDark ? MenuThemeEnum.DARK : MenuThemeEnum.LIGHT, + }, + }); +} + +export function updateSidebarBgColor(color: string) { + if (!isHexColor(color)) return; + + const siderBgColor = useCssVar(SIDER_DARK_BG_COLOR); + const darkenBgColor = useCssVar(SIDER_DARK_DARKEN_BG_COLOR); + const lighten1Color = useCssVar(SIDER_LIGHTEN_1_BG_COLOR); + const lighten2Color = useCssVar(SIDER_LIGHTEN_2_BG_COLOR); + + siderBgColor.value = color; + darkenBgColor.value = darken(color, 6); + lighten1Color.value = lighten(color, 4); + lighten2Color.value = lighten(color, 8); + // only #ffffff is light + const isLight = ['#fff', '#ffffff'].includes(color.toLowerCase()); + + appStore.commitProjectConfigState({ + menuSetting: { + theme: isLight ? MenuThemeEnum.LIGHT : MenuThemeEnum.DARK, + }, + }); +} diff --git a/src/types/config.d.ts b/src/types/config.d.ts index c6f22bca6b6b5e0155eda1d651854209c68a4e11..26e363dd599f7c7194b6fc31b7625ae9dc4c21b7 100644 --- a/src/types/config.d.ts +++ b/src/types/config.d.ts @@ -55,6 +55,10 @@ export interface HeaderSetting { showNotice: boolean; } export interface ProjectConfig { + // header背景色 + headerBgColor: string; + // 左侧菜单背景色 + menuBgColor: string; // 是否显示配置按钮 showSettingButton: boolean; // 权限模式 diff --git a/src/useApp.ts b/src/useApp.ts index aa279fcc4ee0d0f6603d9ed55a2d960645f7ffec..5f0d66d9bdcd1ca246b0817e7b58f7aac33b6034 100644 --- a/src/useApp.ts +++ b/src/useApp.ts @@ -9,7 +9,12 @@ import { PROJ_CFG_KEY } from '/@/enums/cacheEnum'; import projectSetting from '/@/settings/projectSetting'; import { getLocal } from '/@/utils/helper/persistent'; import { isUnDef, isNull } from '/@/utils/is'; -import { updateGrayMode, updateColorWeak } from '/@/setup/theme'; +import { + updateGrayMode, + updateColorWeak, + updateHeaderBgColor, + updateSidebarBgColor, +} from '/@/setup/theme'; import { appStore } from '/@/store/modules/app'; import { useNetWork } from '/@/hooks/web/useNetWork'; @@ -48,7 +53,7 @@ export function useInitAppConfigStore() { if (!projCfg) { projCfg = projectSetting; } - const { colorWeak, grayMode } = projCfg; + const { colorWeak, grayMode, headerBgColor, menuBgColor } = projCfg; try { // if ( // themeColor !== primaryColor && @@ -57,6 +62,8 @@ export function useInitAppConfigStore() { // ) { // updateTheme(themeColor); // } + headerBgColor && updateHeaderBgColor(headerBgColor); + menuBgColor && updateSidebarBgColor(menuBgColor); grayMode && updateGrayMode(grayMode); colorWeak && updateColorWeak(colorWeak); } catch (error) { diff --git a/src/utils/color.ts b/src/utils/color.ts index a720f185dbafe97f5388b997e9de8c50ee4504a5..2bf6cafeee81dc3b4de5cf3d31d688806a0fcafd 100644 --- a/src/utils/color.ts +++ b/src/utils/color.ts @@ -31,13 +31,31 @@ export const rgbToHex = function (r: number, g: number, b: number) { * @returns The RGB representation of the passed color */ export const hexToRGB = function (hex: string) { - return ( - parseInt(hex.substring(0, 2), 16) + - ',' + - parseInt(hex.substring(2, 4), 16) + - ',' + - parseInt(hex.substring(4, 6), 16) - ); + let sHex = hex.toLowerCase(); + if (isHexColor(hex)) { + if (sHex.length === 4) { + let sColorNew = '#'; + for (let i = 1; i < 4; i += 1) { + sColorNew += sHex.slice(i, i + 1).concat(sHex.slice(i, i + 1)); + } + sHex = sColorNew; + } + const sColorChange = []; + for (let i = 1; i < 7; i += 2) { + sColorChange.push(parseInt('0x' + sHex.slice(i, i + 2))); + } + return 'RGB(' + sColorChange.join(',') + ')'; + } + return sHex; +}; + +export const colorIsDark = (color: string) => { + if (!isHexColor(color)) return; + const [r, g, b] = hexToRGB(color) + .replace(/(?:\(|\)|rgb|RGB)*/g, '') + .split(',') + .map((item) => Number(item)); + return r * 0.299 + g * 0.578 + b * 0.114 < 192; }; /** @@ -89,7 +107,7 @@ const addLight = (color: string, amount: number) => { * @param {number} g green * @param {number} b blue */ -const luminanace = (r: stri, g: number, b: number) => { +const luminanace = (r: number, g: number, b: number) => { const a = [r, g, b].map((v) => { v /= 255; return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); @@ -103,7 +121,7 @@ const luminanace = (r: stri, g: number, b: number) => { * @param {string} rgb2 rgb color 2 */ const contrast = (rgb1: string[], rgb2: number[]) => - (luminanace(rgb1[0], ~~rgb1[1], ~~rgb1[2]) + 0.05) / + (luminanace(~~rgb1[0], ~~rgb1[1], ~~rgb1[2]) + 0.05) / (luminanace(rgb2[0], rgb2[1], rgb2[2]) + 0.05); /**