Commit 7692ffb95b94672b6fbc8c25fd43d9dd1a1da81e

Authored by vben
1 parent bda3e5da

feat: new menu and top bar color selection color matching

CHANGELOG.zh_CN.md
... ... @@ -4,6 +4,7 @@
4 4  
5 5 - 表单项的`componentsProps`支持函数类型
6 6 - 菜单新增 tag 显示
  7 +- 新增菜单及顶栏颜色选择配色
7 8  
8 9 ### ⚡ Performance Improvements
9 10  
... ...
index.html
... ... @@ -43,7 +43,7 @@
43 43  
44 44 .app-loading .g-loading {
45 45 display: block;
46   - width: 64px;
  46 + width: 48px;
47 47 margin: 30px auto;
48 48 -webkit-animation: load 1.2s linear infinite;
49 49 animation: load 1.2s linear infinite;
... ...
src/components/Menu/src/BasicMenu.tsx
1 1 import type { MenuState } from './types';
2 2 import type { Menu as MenuType } from '/@/router/types';
3 3  
4   -import { computed, defineComponent, unref, reactive, toRef, watch, onMounted, ref } from 'vue';
  4 +import { computed, defineComponent, unref, reactive, watch, onMounted, ref, toRefs } from 'vue';
5 5 import { Menu } from 'ant-design-vue';
6 6 import SearchInput from './SearchInput.vue';
7 7 import MenuContent from './MenuContent';
... ... @@ -40,8 +40,10 @@ export default defineComponent({
40 40 });
41 41 const { currentRoute } = useRouter();
42 42  
  43 + const { items, flatItems, isAppMenu, mode, accordion } = toRefs(props);
  44 +
43 45 const { handleInputChange, handleInputClick } = useSearchInput({
44   - flatMenusRef: toRef(props, 'flatItems'),
  46 + flatMenusRef: flatItems,
45 47 emit: emit,
46 48 menuState,
47 49 handleMenuChange,
... ... @@ -49,11 +51,11 @@ export default defineComponent({
49 51  
50 52 const { handleOpenChange, resetKeys, setOpenKeys } = useOpenKeys(
51 53 menuState,
52   - toRef(props, 'items'),
53   - toRef(props, 'flatItems'),
54   - toRef(props, 'isAppMenu'),
55   - toRef(props, 'mode'),
56   - toRef(props, 'accordion')
  54 + items,
  55 + flatItems,
  56 + isAppMenu,
  57 + mode,
  58 + accordion
57 59 );
58 60  
59 61 const getOpenKeys = computed(() => {
... ... @@ -98,6 +100,8 @@ export default defineComponent({
98 100 return cls;
99 101 });
100 102  
  103 + const showTitle = computed(() => props.collapsedShowTitle && menuStore.getCollapsedState);
  104 +
101 105 watch(
102 106 () => currentRoute.value.name,
103 107 (name: string) => {
... ... @@ -130,9 +134,7 @@ export default defineComponent({
130 134 const { beforeClickFn } = props;
131 135 if (beforeClickFn && isFunction(beforeClickFn)) {
132 136 const flag = await beforeClickFn(menu);
133   - if (!flag) {
134   - return;
135   - }
  137 + if (!flag) return;
136 138 }
137 139 const { path } = menu;
138 140 menuState.selectedKeys = [path];
... ... @@ -141,9 +143,7 @@ export default defineComponent({
141 143  
142 144 function handleMenuChange() {
143 145 const { flatItems } = props;
144   - if (!unref(flatItems) || flatItems.length === 0) {
145   - return;
146   - }
  146 + if (!unref(flatItems) || flatItems.length === 0) return;
147 147 const findMenu = flatItems.find((menu) => menu.path === unref(currentRoute).path);
148 148 if (findMenu) {
149 149 if (menuState.mode !== MenuModeEnum.HORIZONTAL) {
... ... @@ -155,10 +155,6 @@ export default defineComponent({
155 155 }
156 156 }
157 157  
158   - const showTitle = computed(() => {
159   - return props.collapsedShowTitle && menuStore.getCollapsedState;
160   - });
161   -
162 158 // render menu item
163 159 function renderMenuItem(menuList?: MenuType[], index = 1) {
164 160 if (!menuList) return;
... ... @@ -183,6 +179,7 @@ export default defineComponent({
183 179 <MenuContent
184 180 item={menu}
185 181 level={index}
  182 + isTop={props.isTop}
186 183 showTitle={unref(showTitle)}
187 184 searchValue={menuState.searchValue}
188 185 />,
... ... @@ -198,6 +195,7 @@ export default defineComponent({
198 195 showTitle={unref(showTitle)}
199 196 item={menu}
200 197 level={index}
  198 + isTop={props.isTop}
201 199 searchValue={menuState.searchValue}
202 200 />,
203 201 ],
... ...
src/components/Menu/src/MenuContent.tsx
... ... @@ -26,6 +26,10 @@ export default defineComponent({
26 26 type: Number as PropType<number>,
27 27 default: 0,
28 28 },
  29 + isTop: {
  30 + type: Boolean as PropType<boolean>,
  31 + default: true,
  32 + },
29 33 },
30 34 setup(props) {
31 35 /**
... ... @@ -56,14 +60,16 @@ export default defineComponent({
56 60 if (!props.item) {
57 61 return null;
58 62 }
59   - const { showTitle } = props;
  63 + const { showTitle, isTop } = props;
60 64 const { name, icon } = props.item;
61 65 const searchValue = props.searchValue || '';
62 66 const index = name.indexOf(searchValue);
63 67  
64 68 const beforeStr = name.substr(0, index);
65 69 const afterStr = name.substr(index + searchValue.length);
66   - const cls = showTitle ? 'show-title' : 'basic-menu__name';
  70 + let cls = showTitle ? ['show-title'] : ['basic-menu__name'];
  71 +
  72 + isTop && !showTitle && (cls = []);
67 73 return (
68 74 <>
69 75 {renderIcon(icon!)}
... ...
src/components/Menu/src/SearchInput.vue
... ... @@ -102,7 +102,7 @@
102 102  
103 103 .set-bg() {
104 104 color: #fff;
105   - background: @input-dark-bg-color;
  105 + background: @sider-dark-lighten-1-bg-color;
106 106 border: 0;
107 107 outline: none;
108 108 }
... ...
src/components/Menu/src/index.less
... ... @@ -52,10 +52,11 @@
52 52 // collapsed show title end
53 53 .ant-menu-submenu-title {
54 54 > .basic-menu__name {
55   - display: flex;
56   - width: 100%;
57   - justify-content: space-between;
58   - align-items: center;
  55 + .basic-menu__tag {
  56 + float: right;
  57 + margin-top: @app-menu-item-height / 2;
  58 + transform: translate(0%, -50%);
  59 + }
59 60 }
60 61 }
61 62  
... ... @@ -254,7 +255,7 @@
254 255 // 层级样式
255 256 &.ant-menu-dark:not(.basic-menu__sidebar-hor) {
256 257 overflow-x: hidden;
257   - background: @menu-item-dark-bg-color;
  258 + background: @sider-dark-bg-color;
258 259 .active-menu-style();
259 260  
260 261 .ant-menu-item.ant-menu-item-selected.basic-menu-menu-item__level1,
... ... @@ -263,21 +264,20 @@
263 264 }
264 265  
265 266 .basic-menu-item__level1 {
266   - background-color: @menu-item-dark-bg-color;
  267 + background-color: @sider-dark-bg-color;
267 268  
268 269 > .ant-menu-sub > li {
269   - background-color: lighten(@menu-item-dark-bg-color, 6%);
  270 + background-color: @sider-dark-lighten-1-bg-color;
270 271 }
271 272 }
272 273  
273 274 .basic-menu-item__level2:not(.ant-menu-item-selected),
274 275 .ant-menu-sub {
275   - background-color: lighten(@menu-item-dark-bg-color, 6%);
276   - // background-color: @sub-menu-item-dark-bg-color;
  276 + background-color: @sider-dark-lighten-1-bg-color;
277 277 }
278 278  
279 279 .basic-menu-item__level3:not(.ant-menu-item-selected) {
280   - background-color: lighten(@menu-item-dark-bg-color, 10%);
  280 + background-color: @sider-dark-lighten-2-bg-color;
281 281 }
282 282  
283 283 .ant-menu-submenu-title {
... ... @@ -290,7 +290,7 @@
290 290 &.ant-menu-inline-collapsed {
291 291 .ant-menu-submenu-selected,
292 292 .ant-menu-item-selected {
293   - background: darken(@menu-item-dark-bg-color, 6%) !important;
  293 + background: @sider-dark-darken-bg-color !important;
294 294 }
295 295 }
296 296 }
... ... @@ -359,7 +359,7 @@
359 359 .ant-menu-dark {
360 360 &.ant-menu-submenu-popup {
361 361 > ul {
362   - background: @menu-item-dark-bg-color;
  362 + background: @sider-dark-bg-color;
363 363 }
364 364  
365 365 .active-menu-style();
... ...
src/design/color.less
  1 +:root {
  2 + // header
  3 + --header-bg-color: #394664;
  4 + --header-bg-hover-color: #273352;
  5 + --header-active-menu-bg-color: #273352;
  6 +
  7 + // sider
  8 + --sider-dark-bg-color: #273352;
  9 + --sider-dark-darken-bg-color: #273352;
  10 + --sider-dark-lighten-1-bg-color: #273352;
  11 + --sider-dark-lighten-2-bg-color: #273352;
  12 + --sider-dark-lighten-3-bg-color: #273352;
  13 +}
  14 +
1 15 @white: #fff;
2 16 @info-color: @primary-color;
3 17  
... ... @@ -53,21 +67,24 @@
53 67 // ==============Header=============
54 68 // =================================
55 69  
56   -@header-dark-bg-color: #394664;
57   -@header-dark-bg-hover-color: #273352;
  70 +@header-dark-bg-color: var(--header-bg-color);
  71 +@header-dark-bg-hover-color: var(--header-bg-hover-color);
58 72 @header-light-bg-hover-color: #f6f6f6;
59 73 @header-light-desc-color: #7c8087;
60 74 @header-light-bottom-border-color: #eee;
  75 +// top-menu
  76 +@top-menu-active-bg-color: var(--header-active-menu-bg-color);
61 77  
62 78 // =================================
63 79 // ==============Menu============
64 80 // =================================
65 81  
66 82 // let -menu
67   -@menu-item-dark-bg-color: #273352;
68   -
69   -// top-menu
70   -@top-menu-active-bg-color: #273352;
  83 +@sider-dark-bg-color: var(--sider-dark-bg-color);
  84 +@sider-dark-darken-bg-color: var(--sider-dark-darken-bg-color);
  85 +@sider-dark-lighten-1-bg-color: var(--sider-dark-lighten-1-bg-color);
  86 +@sider-dark-lighten-2-bg-color: var(--sider-dark-lighten-2-bg-color);
  87 +@sider-dark-lighten-3-bg-color: var(--sider-dark-lighten-3-bg-color);
71 88  
72 89 // trigger
73 90 @trigger-dark-hover-bg-color: rgba(255, 255, 255, 0.2);
... ...
src/layouts/default/header/LayoutHeader.tsx
... ... @@ -79,9 +79,8 @@ export default defineComponent({
79 79 });
80 80  
81 81 const showHeaderTrigger = computed(() => {
82   - const { show, trigger, hidden } = unref(getProjectConfigRef).menuSetting;
83   -
84   - if (!show || !hidden) return false;
  82 + const { show, trigger, hidden, type } = unref(getProjectConfigRef).menuSetting;
  83 + if (type === MenuTypeEnum.TOP_MENU || !show || !hidden) return false;
85 84 return trigger === TriggerEnum.HEADER;
86 85 });
87 86  
... ...
src/layouts/default/index.less
... ... @@ -41,7 +41,7 @@
41 41 background-size: 100% 100%;
42 42  
43 43 &.ant-layout-sider-dark {
44   - background: @menu-item-dark-bg-color;
  44 + background: @sider-dark-bg-color;
45 45 }
46 46  
47 47 &:not(.ant-layout-sider-dark) {
... ...
src/layouts/default/index.tsx
... ... @@ -25,13 +25,9 @@ export default defineComponent({
25 25  
26 26 const { getFullContent } = useFullContent();
27 27  
28   - const getProjectConfigRef = computed(() => {
29   - return appStore.getProjectConfig;
30   - });
  28 + const getProjectConfigRef = computed(() => appStore.getProjectConfig);
31 29  
32   - const getLockMainScrollStateRef = computed(() => {
33   - return appStore.getLockMainScrollState;
34   - });
  30 + const getLockMainScrollStateRef = computed(() => appStore.getLockMainScrollState);
35 31  
36 32 const showHeaderRef = computed(() => {
37 33 const {
... ... @@ -47,6 +43,12 @@ export default defineComponent({
47 43 return type !== MenuTypeEnum.SIDEBAR && unref(showHeaderRef);
48 44 });
49 45  
  46 + const getIsLockRef = computed(() => {
  47 + const { getLockInfo } = appStore;
  48 + const { isLock } = getLockInfo;
  49 + return isLock;
  50 + });
  51 +
50 52 const showSideBarRef = computed(() => {
51 53 const {
52 54 menuSetting: { show, mode, split },
... ... @@ -54,59 +56,74 @@ export default defineComponent({
54 56 return split || (show && mode !== MenuModeEnum.HORIZONTAL && !unref(getFullContent));
55 57 });
56 58  
57   - function getTarget(): any {
58   - const {
59   - headerSetting: { fixed },
60   - } = unref(getProjectConfigRef);
61   - return document.querySelector(`.default-layout__${fixed ? 'main' : 'content'}`);
62   - }
  59 + const showFullHeaderRef = computed(() => {
  60 + return !unref(getFullContent) && unref(isShowMixHeaderRef) && unref(showHeaderRef);
  61 + });
63 62  
64   - return () => {
65   - const { getLockInfo } = appStore;
  63 + const showInsetHeaderRef = computed(() => {
  64 + return !unref(getFullContent) && !unref(isShowMixHeaderRef) && unref(showHeaderRef);
  65 + });
  66 +
  67 + const fixedHeaderClsRef = computed(() => {
66 68 const {
67   - useOpenBackTop,
68   - showSettingButton,
69   - multiTabsSetting: { show: showTabs },
70 69 headerSetting: { fixed },
71   - menuSetting: { split, hidden },
72 70 } = unref(getProjectConfigRef);
73   -
74 71 const fixedHeaderCls = fixed
75 72 ? 'fixed' + (unref(getLockMainScrollStateRef) ? ' lock' : '')
76 73 : '';
  74 + return fixedHeaderCls;
  75 + });
77 76  
78   - const { isLock } = getLockInfo;
  77 + const showTabsRef = computed(() => {
  78 + const {
  79 + multiTabsSetting: { show },
  80 + } = unref(getProjectConfigRef);
  81 + return show && !unref(getFullContent);
  82 + });
  83 +
  84 + const showClassSideBarRef = computed(() => {
  85 + const {
  86 + menuSetting: { split, hidden },
  87 + } = unref(getProjectConfigRef);
  88 + return split ? hidden : true;
  89 + });
79 90  
80   - const showSideBar = split ? hidden : true;
  91 + function getTarget(): any {
  92 + const {
  93 + headerSetting: { fixed },
  94 + } = unref(getProjectConfigRef);
  95 + return document.querySelector(`.default-layout__${fixed ? 'main' : 'content'}`);
  96 + }
  97 +
  98 + return () => {
  99 + const { useOpenBackTop, showSettingButton } = unref(getProjectConfigRef);
81 100 return (
82 101 <Layout class="default-layout relative">
83 102 {() => (
84 103 <>
85 104 {/* lock page */}
86   - {isLock && <LockPage />}
  105 + {unref(getIsLockRef) && <LockPage />}
87 106 {/* back top */}
88 107 {useOpenBackTop && <BackTop target={getTarget} />}
89 108 {/* open setting drawer */}
90 109 {showSettingButton && <SettingBtn />}
91 110  
92   - {!unref(getFullContent) && unref(isShowMixHeaderRef) && unref(showHeaderRef) && (
93   - <LayoutHeader />
94   - )}
  111 + {unref(showFullHeaderRef) && <LayoutHeader />}
95 112  
96 113 <Layout>
97 114 {() => (
98 115 <>
99   - {unref(showSideBarRef) && <LayoutSideBar class={showSideBar ? '' : 'hidden'} />}
100   - <Layout class={[`default-layout__content`, fixedHeaderCls]}>
  116 + {unref(showSideBarRef) && (
  117 + <LayoutSideBar class={unref(showClassSideBarRef) ? '' : 'hidden'} />
  118 + )}
  119 + <Layout class={[`default-layout__content`, unref(fixedHeaderClsRef)]}>
101 120 {() => (
102 121 <>
103   - {!unref(getFullContent) &&
104   - !unref(isShowMixHeaderRef) &&
105   - unref(showHeaderRef) && <LayoutHeader />}
  122 + {unref(showInsetHeaderRef) && <LayoutHeader />}
106 123  
107   - {showTabs && !unref(getFullContent) && <MultipleTabs />}
  124 + {unref(showTabsRef) && <MultipleTabs />}
108 125  
109   - <LayoutContent class={fixedHeaderCls} />
  126 + <LayoutContent class={unref(fixedHeaderClsRef)} />
110 127 </>
111 128 )}
112 129 </Layout>
... ...
src/layouts/default/setting/SettingDrawer.tsx
... ... @@ -20,12 +20,12 @@ import { updateColorWeak, updateGrayMode } from &#39;/@/setup/theme&#39;;
20 20 import { baseHandler } from './handler';
21 21 import {
22 22 HandlerEnum,
23   - themeOptions,
24 23 contentModeOptions,
25 24 topMenuAlignOptions,
26 25 menuTriggerOptions,
27 26 routerTransitionOptions,
28 27 } from './const';
  28 +import { HEADER_PRESET_BG_COLOR_LIST, SIDE_BAR_BG_COLOR_LIST } from '/@/settings/colorSetting';
29 29  
30 30 interface SwitchOptions {
31 31 config?: DeepPartial<ProjectConfig>;
... ... @@ -41,6 +41,11 @@ interface SelectConfig {
41 41 handler?: Fn;
42 42 }
43 43  
  44 +interface ThemeOptions {
  45 + def?: string;
  46 + handler?: Fn;
  47 +}
  48 +
44 49 export default defineComponent({
45 50 name: 'SettingDrawer',
46 51 setup(_, { attrs }) {
... ... @@ -98,8 +103,7 @@ export default defineComponent({
98 103  
99 104 function renderSidebar() {
100 105 const {
101   - headerSetting: { theme: headerTheme },
102   - menuSetting: { type, theme: menuTheme, split },
  106 + menuSetting: { type, split },
103 107 } = unref(getProjectConfigRef);
104 108  
105 109 const typeList = ref([
... ... @@ -154,22 +158,22 @@ export default defineComponent({
154 158 def: split,
155 159 disabled: !unref(getShowMenuRef) || type !== MenuTypeEnum.MIX,
156 160 }),
157   - renderSelectItem('顶栏主题', {
158   - handler: (e) => {
159   - baseHandler(HandlerEnum.HEADER_THEME, e);
160   - },
161   - def: headerTheme,
162   - options: themeOptions,
163   - disabled: !unref(getShowHeaderRef),
164   - }),
165   - renderSelectItem('菜单主题', {
166   - handler: (e) => {
167   - baseHandler(HandlerEnum.MENU_THEME, e);
168   - },
169   - def: menuTheme,
170   - options: themeOptions,
171   - disabled: !unref(getShowMenuRef),
172   - }),
  161 + // renderSelectItem('顶栏主题', {
  162 + // handler: (e) => {
  163 + // baseHandler(HandlerEnum.HEADER_THEME, e);
  164 + // },
  165 + // def: headerTheme,
  166 + // options: themeOptions,
  167 + // disabled: !unref(getShowHeaderRef),
  168 + // }),
  169 + // renderSelectItem('菜单主题', {
  170 + // handler: (e) => {
  171 + // baseHandler(HandlerEnum.MENU_THEME, e);
  172 + // },
  173 + // def: menuTheme,
  174 + // options: themeOptions,
  175 + // disabled: !unref(getShowMenuRef),
  176 + // }),
173 177 ];
174 178 }
175 179 /**
... ... @@ -413,7 +417,6 @@ export default defineComponent({
413 417 return (
414 418 <div class={`setting-drawer__cell-item`}>
415 419 <span>{text}</span>
416   - {/* @ts-ignore */}
417 420 <Select
418 421 {...opt}
419 422 disabled={disabled}
... ... @@ -447,6 +450,50 @@ export default defineComponent({
447 450 );
448 451 }
449 452  
  453 + function renderTheme() {
  454 + const { headerBgColor, menuBgColor } = unref(getProjectConfigRef);
  455 + return (
  456 + <>
  457 + <Divider>{() => '顶栏主题'}</Divider>
  458 + {renderThemeItem(HEADER_PRESET_BG_COLOR_LIST, {
  459 + def: headerBgColor,
  460 + handler: (e) => {
  461 + baseHandler(HandlerEnum.HEADER_THEME, e);
  462 + },
  463 + })}
  464 + <Divider>{() => '菜单主题'}</Divider>
  465 + {renderThemeItem(SIDE_BAR_BG_COLOR_LIST, {
  466 + def: menuBgColor,
  467 + handler: (e) => {
  468 + baseHandler(HandlerEnum.MENU_THEME, e);
  469 + },
  470 + })}
  471 + </>
  472 + );
  473 + }
  474 +
  475 + function renderThemeItem(colorList: string[], opt: ThemeOptions) {
  476 + const { def, handler } = opt;
  477 + return (
  478 + <div class={`setting-drawer__theme-item`}>
  479 + {colorList.map((item) => {
  480 + return (
  481 + <span
  482 + onClick={() => handler && handler(item)}
  483 + key={item}
  484 + class={[def === item ? 'active' : '']}
  485 + style={{
  486 + background: item,
  487 + }}
  488 + >
  489 + <CheckOutlined class="icon" />
  490 + </span>
  491 + );
  492 + })}
  493 + </div>
  494 + );
  495 + }
  496 +
450 497 return () => (
451 498 <BasicDrawer {...attrs} title="项目配置" width={300} wrapClassName="setting-drawer">
452 499 {{
... ... @@ -454,6 +501,9 @@ export default defineComponent({
454 501 <>
455 502 <Divider>{() => '导航栏模式'}</Divider>
456 503 {renderSidebar()}
  504 +
  505 + {renderTheme()}
  506 +
457 507 <Divider>{() => '界面功能'}</Divider>
458 508 {renderFeatures()}
459 509 <Divider>{() => '界面显示'}</Divider>
... ...
src/layouts/default/setting/handler.ts
1 1 import { HandlerEnum } from './const';
2   -import { MenuThemeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
3   -import { updateColorWeak, updateGrayMode } from '/@/setup/theme';
  2 +// import { MenuThemeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
  3 +import {
  4 + updateColorWeak,
  5 + updateGrayMode,
  6 + updateHeaderBgColor,
  7 + updateSidebarBgColor,
  8 +} from '/@/setup/theme';
4 9 import { appStore } from '/@/store/modules/app';
5 10 import { ProjectConfig } from '/@/types/config';
6 11  
... ... @@ -14,12 +19,12 @@ export function handler(event: HandlerEnum, value: any): DeepPartial&lt;ProjectConf
14 19 case HandlerEnum.CHANGE_LAYOUT:
15 20 const { mode, type, split } = value;
16 21 const splitOpt = split === undefined ? { split } : {};
17   - let headerSetting = {};
18   - if (type === MenuTypeEnum.TOP_MENU) {
19   - headerSetting = {
20   - theme: MenuThemeEnum.DARK,
21   - };
22   - }
  22 + // let headerSetting = {};
  23 + // if (type === MenuTypeEnum.TOP_MENU) {
  24 + // headerSetting = {
  25 + // theme: MenuThemeEnum.DARK,
  26 + // };
  27 + // }
23 28 return {
24 29 menuSetting: {
25 30 mode,
... ... @@ -28,7 +33,7 @@ export function handler(event: HandlerEnum, value: any): DeepPartial&lt;ProjectConf
28 33 show: true,
29 34 ...splitOpt,
30 35 },
31   - headerSetting,
  36 + // headerSetting,
32 37 };
33 38  
34 39 case HandlerEnum.MENU_HAS_DRAG:
... ... @@ -81,10 +86,12 @@ export function handler(event: HandlerEnum, value: any): DeepPartial&lt;ProjectConf
81 86 },
82 87 };
83 88 case HandlerEnum.MENU_THEME:
  89 + updateSidebarBgColor(value);
84 90 return {
85   - menuSetting: {
86   - theme: value,
87   - },
  91 + menuBgColor: value,
  92 + // menuSetting: {
  93 + // theme: value,
  94 + // },
88 95 };
89 96 case HandlerEnum.MENU_SPLIT:
90 97 return {
... ... @@ -150,7 +157,7 @@ export function handler(event: HandlerEnum, value: any): DeepPartial&lt;ProjectConf
150 157 showQuick: value,
151 158 },
152 159 };
153   - case HandlerEnum.TABS_SHOW_QUICK:
  160 + case HandlerEnum.TABS_SHOW_ICON:
154 161 return {
155 162 multiTabsSetting: {
156 163 showIcon: value,
... ... @@ -163,10 +170,9 @@ export function handler(event: HandlerEnum, value: any): DeepPartial&lt;ProjectConf
163 170 },
164 171 };
165 172 case HandlerEnum.HEADER_THEME:
  173 + updateHeaderBgColor(value);
166 174 return {
167   - headerSetting: {
168   - theme: value,
169   - },
  175 + headerBgColor: value,
170 176 };
171 177 case HandlerEnum.HEADER_FIXED:
172 178 return {
... ...
src/layouts/default/setting/index.less
... ... @@ -20,25 +20,26 @@
20 20 display: flex;
21 21 flex-wrap: wrap;
22 22 margin: 16px 0;
  23 + justify-content: space-around;
23 24  
24   - span {
25   - display: inline-block;
  25 + > span {
26 26 width: 20px;
27 27 height: 20px;
28   - margin-top: 10px;
29   - margin-right: 10px;
30 28 cursor: pointer;
31   - border-radius: 4px;
  29 + border: 1px solid #ddd;
  30 + border-radius: 2px;
32 31  
33 32 svg {
34 33 display: none;
35 34 }
36 35  
37 36 &.active {
  37 + border: 1px solid lighten(@primary-color, 10%);
  38 +
38 39 svg {
39 40 display: inline-block;
40   - margin-left: 4px;
41   - font-size: 0.8em;
  41 + margin: 0 0 3px 3px;
  42 + font-size: 12px;
42 43 fill: @white;
43 44 }
44 45 }
... ...
src/layouts/page/index.tsx
... ... @@ -41,13 +41,12 @@ export default defineComponent({
41 41 // No longer show animations that are already in the tab
42 42 const name = route.meta.inTab ? 'fade' : null;
43 43  
44   - // TODO add key?
45 44 const Content = openCache ? (
46 45 <KeepAlive max={max} include={cacheTabs}>
47   - <Component key={route.path} />
  46 + <Component key={route.fullPath} />
48 47 </KeepAlive>
49 48 ) : (
50   - <Component key={route.path} />
  49 + <Component key={route.fullPath} />
51 50 );
52 51 return openRouterTransition ? (
53 52 <Transition
... ...
src/router/index.ts
... ... @@ -3,7 +3,7 @@ import type { App } from &#39;vue&#39;;
3 3  
4 4 import { createRouter, createWebHashHistory } from 'vue-router';
5 5  
6   -import { scrollWaiter } from '../utils/scrollWaiter';
  6 +import { scrollWaiter } from './scrollWaiter';
7 7  
8 8 import { createGuard } from './guard/';
9 9  
... ... @@ -13,6 +13,7 @@ import { basicRoutes } from &#39;./routes/&#39;;
13 13 const router = createRouter({
14 14 history: createWebHashHistory(),
15 15 routes: basicRoutes as RouteRecordRaw[],
  16 + strict: true,
16 17 scrollBehavior: async (to, from, savedPosition) => {
17 18 await scrollWaiter.wait();
18 19 if (savedPosition) {
... ...
src/utils/scrollWaiter.ts renamed to src/router/scrollWaiter.ts
  1 +// see https://github.com/vuejs/vue-router-next/blob/master/playground/scrollWaiter.ts
1 2 class ScrollQueue {
2 3 private resolve: (() => void) | null = null;
3 4 private promise: Promise<any> | null = null;
... ...
src/settings/colorSetting.ts 0 → 100644
  1 +// header preset color
  2 +export const HEADER_PRESET_BG_COLOR_LIST: string[] = [
  3 + '#ffffff',
  4 + '#009688',
  5 + '#18bc9c',
  6 + '#1E9FFF',
  7 + '#018ffb',
  8 + '#409eff',
  9 + '#4e73df',
  10 + '#e74c3c',
  11 + '#f39c12',
  12 + '#394664',
  13 + '#001529',
  14 +];
  15 +
  16 +// sider preset color
  17 +export const SIDE_BAR_BG_COLOR_LIST: string[] = [
  18 + '#273352',
  19 + '#ffffff',
  20 + '#001529',
  21 + '#304156',
  22 + '#28333E',
  23 + '#344058',
  24 +];
... ...
src/settings/projectSetting.ts
... ... @@ -7,6 +7,16 @@ import { isProdMode } from &#39;/@/utils/env&#39;;
7 7  
8 8 // ! You need to clear the browser cache after the change
9 9 const setting: ProjectConfig = {
  10 + // color
  11 + // TODO 主题色
  12 + themeColor: primaryColor,
  13 +
  14 + // header bg color
  15 + headerBgColor: '#ffffff',
  16 +
  17 + // sidebar menu bg color
  18 + menuBgColor: '#273352',
  19 +
10 20 // Whether to show the configuration button
11 21 showSettingButton: true,
12 22 // 权限模式
... ... @@ -15,8 +25,7 @@ const setting: ProjectConfig = {
15 25 grayMode: false,
16 26 // 色弱模式
17 27 colorWeak: false,
18   - // 主题色
19   - themeColor: primaryColor,
  28 +
20 29 // 是否取消菜单,顶部,多标签页显示, 用于可能内嵌在别的系统内
21 30 fullContent: false,
22 31 // content mode
... ...
src/setup/theme/index.ts
  1 +import useCssVar from '/@/hooks/web/useCssVar';
  2 +import { isHexColor, colorIsDark, lighten, darken } from '/@/utils/color';
  3 +import { appStore } from '/@/store/modules/app';
  4 +import { MenuThemeEnum } from '/@/enums/menuEnum';
  5 +
  6 +const HEADER_BG_COLOR_VAR = '--header-bg-color';
  7 +const HEADER_BG_HOVER_COLOR_VAR = '--header-bg-hover-color';
  8 +const HEADER_MENU_ACTIVE_BG_COLOR_VAR = '--header-active-menu-bg-color';
  9 +
  10 +const SIDER_DARK_BG_COLOR = '--sider-dark-bg-color';
  11 +const SIDER_DARK_DARKEN_BG_COLOR = '--sider-dark-darken-bg-color';
  12 +const SIDER_LIGHTEN_1_BG_COLOR = '--sider-dark-lighten-1-bg-color';
  13 +const SIDER_LIGHTEN_2_BG_COLOR = '--sider-dark-lighten-2-bg-color';
  14 +
1 15 function toggleClass(flag: boolean, clsName: string) {
2 16 const body = document.body;
3 17 let { className } = body;
4 18 className = className.replace(clsName, '');
5 19 document.body.className = flag ? `${className} ${clsName} ` : className;
6 20 }
  21 +
7 22 export const updateColorWeak = (colorWeak: boolean) => {
8 23 toggleClass(colorWeak, 'color-weak');
9 24 };
... ... @@ -11,3 +26,46 @@ export const updateColorWeak = (colorWeak: boolean) =&gt; {
11 26 export const updateGrayMode = (gray: boolean) => {
12 27 toggleClass(gray, 'gray-mode');
13 28 };
  29 +
  30 +export function updateHeaderBgColor(color: string) {
  31 + if (!isHexColor(color)) return;
  32 + const bgColorRef = useCssVar(HEADER_BG_COLOR_VAR);
  33 + const bgHoverColorRef = useCssVar(HEADER_BG_HOVER_COLOR_VAR);
  34 + const topMenuActiveBgColorRef = useCssVar(HEADER_MENU_ACTIVE_BG_COLOR_VAR);
  35 + // bg color
  36 + bgColorRef.value = color;
  37 + // hover color
  38 + const hoverColor = lighten(color, 6);
  39 + bgHoverColorRef.value = hoverColor;
  40 + topMenuActiveBgColorRef.value = hoverColor;
  41 +
  42 + const isDark = colorIsDark(color);
  43 +
  44 + appStore.commitProjectConfigState({
  45 + headerSetting: {
  46 + theme: isDark ? MenuThemeEnum.DARK : MenuThemeEnum.LIGHT,
  47 + },
  48 + });
  49 +}
  50 +
  51 +export function updateSidebarBgColor(color: string) {
  52 + if (!isHexColor(color)) return;
  53 +
  54 + const siderBgColor = useCssVar(SIDER_DARK_BG_COLOR);
  55 + const darkenBgColor = useCssVar(SIDER_DARK_DARKEN_BG_COLOR);
  56 + const lighten1Color = useCssVar(SIDER_LIGHTEN_1_BG_COLOR);
  57 + const lighten2Color = useCssVar(SIDER_LIGHTEN_2_BG_COLOR);
  58 +
  59 + siderBgColor.value = color;
  60 + darkenBgColor.value = darken(color, 6);
  61 + lighten1Color.value = lighten(color, 4);
  62 + lighten2Color.value = lighten(color, 8);
  63 + // only #ffffff is light
  64 + const isLight = ['#fff', '#ffffff'].includes(color.toLowerCase());
  65 +
  66 + appStore.commitProjectConfigState({
  67 + menuSetting: {
  68 + theme: isLight ? MenuThemeEnum.LIGHT : MenuThemeEnum.DARK,
  69 + },
  70 + });
  71 +}
... ...
src/types/config.d.ts
... ... @@ -55,6 +55,10 @@ export interface HeaderSetting {
55 55 showNotice: boolean;
56 56 }
57 57 export interface ProjectConfig {
  58 + // header背景色
  59 + headerBgColor: string;
  60 + // 左侧菜单背景色
  61 + menuBgColor: string;
58 62 // 是否显示配置按钮
59 63 showSettingButton: boolean;
60 64 // 权限模式
... ...
src/useApp.ts
... ... @@ -9,7 +9,12 @@ import { PROJ_CFG_KEY } from &#39;/@/enums/cacheEnum&#39;;
9 9 import projectSetting from '/@/settings/projectSetting';
10 10 import { getLocal } from '/@/utils/helper/persistent';
11 11 import { isUnDef, isNull } from '/@/utils/is';
12   -import { updateGrayMode, updateColorWeak } from '/@/setup/theme';
  12 +import {
  13 + updateGrayMode,
  14 + updateColorWeak,
  15 + updateHeaderBgColor,
  16 + updateSidebarBgColor,
  17 +} from '/@/setup/theme';
13 18  
14 19 import { appStore } from '/@/store/modules/app';
15 20 import { useNetWork } from '/@/hooks/web/useNetWork';
... ... @@ -48,7 +53,7 @@ export function useInitAppConfigStore() {
48 53 if (!projCfg) {
49 54 projCfg = projectSetting;
50 55 }
51   - const { colorWeak, grayMode } = projCfg;
  56 + const { colorWeak, grayMode, headerBgColor, menuBgColor } = projCfg;
52 57 try {
53 58 // if (
54 59 // themeColor !== primaryColor &&
... ... @@ -57,6 +62,8 @@ export function useInitAppConfigStore() {
57 62 // ) {
58 63 // updateTheme(themeColor);
59 64 // }
  65 + headerBgColor && updateHeaderBgColor(headerBgColor);
  66 + menuBgColor && updateSidebarBgColor(menuBgColor);
60 67 grayMode && updateGrayMode(grayMode);
61 68 colorWeak && updateColorWeak(colorWeak);
62 69 } catch (error) {
... ...
src/utils/color.ts
... ... @@ -31,13 +31,31 @@ export const rgbToHex = function (r: number, g: number, b: number) {
31 31 * @returns The RGB representation of the passed color
32 32 */
33 33 export const hexToRGB = function (hex: string) {
34   - return (
35   - parseInt(hex.substring(0, 2), 16) +
36   - ',' +
37   - parseInt(hex.substring(2, 4), 16) +
38   - ',' +
39   - parseInt(hex.substring(4, 6), 16)
40   - );
  34 + let sHex = hex.toLowerCase();
  35 + if (isHexColor(hex)) {
  36 + if (sHex.length === 4) {
  37 + let sColorNew = '#';
  38 + for (let i = 1; i < 4; i += 1) {
  39 + sColorNew += sHex.slice(i, i + 1).concat(sHex.slice(i, i + 1));
  40 + }
  41 + sHex = sColorNew;
  42 + }
  43 + const sColorChange = [];
  44 + for (let i = 1; i < 7; i += 2) {
  45 + sColorChange.push(parseInt('0x' + sHex.slice(i, i + 2)));
  46 + }
  47 + return 'RGB(' + sColorChange.join(',') + ')';
  48 + }
  49 + return sHex;
  50 +};
  51 +
  52 +export const colorIsDark = (color: string) => {
  53 + if (!isHexColor(color)) return;
  54 + const [r, g, b] = hexToRGB(color)
  55 + .replace(/(?:\(|\)|rgb|RGB)*/g, '')
  56 + .split(',')
  57 + .map((item) => Number(item));
  58 + return r * 0.299 + g * 0.578 + b * 0.114 < 192;
41 59 };
42 60  
43 61 /**
... ... @@ -89,7 +107,7 @@ const addLight = (color: string, amount: number) =&gt; {
89 107 * @param {number} g green
90 108 * @param {number} b blue
91 109 */
92   -const luminanace = (r: stri, g: number, b: number) => {
  110 +const luminanace = (r: number, g: number, b: number) => {
93 111 const a = [r, g, b].map((v) => {
94 112 v /= 255;
95 113 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) =&gt; {
103 121 * @param {string} rgb2 rgb color 2
104 122 */
105 123 const contrast = (rgb1: string[], rgb2: number[]) =>
106   - (luminanace(rgb1[0], ~~rgb1[1], ~~rgb1[2]) + 0.05) /
  124 + (luminanace(~~rgb1[0], ~~rgb1[1], ~~rgb1[2]) + 0.05) /
107 125 (luminanace(rgb2[0], rgb2[1], rgb2[2]) + 0.05);
108 126  
109 127 /**
... ...