Browse Source

perf(setting-drawer): perf setting-drawer

vben 4 years ago
parent
commit
ed41e5082f

+ 32 - 0
build/vite/plugin/html.ts

@@ -0,0 +1,32 @@
+import type { Plugin } from 'vite';
+import ViteHtmlPlugin from 'vite-plugin-html';
+import { isProdFn, isSiteMode, ViteEnv } from '../../utils';
+
+import { hmScript } from '../hm';
+// @ts-ignore
+import pkg from '../../../package.json';
+import { GLOB_CONFIG_FILE_NAME } from '../../constant';
+
+export function setupHtmlPlugin(plugins: Plugin[], env: ViteEnv) {
+  const { VITE_GLOB_APP_TITLE, VITE_PUBLIC_PATH } = env;
+
+  const htmlPlugin = ViteHtmlPlugin({
+    // html title
+    title: VITE_GLOB_APP_TITLE,
+    minify: isProdFn(),
+    options: {
+      // Package and insert additional configuration files
+      injectConfig: isProdFn()
+        ? `<script src='${VITE_PUBLIC_PATH || './'}${GLOB_CONFIG_FILE_NAME}?v=${
+            pkg.version
+          }-${new Date().getTime()}'></script>`
+        : '',
+      // Insert Baidu statistics code
+      hmScript: isSiteMode() ? hmScript : '',
+      title: VITE_GLOB_APP_TITLE,
+    },
+  });
+
+  plugins.push(htmlPlugin);
+  return plugins;
+}

+ 18 - 72
build/vite/plugin/index.ts

@@ -1,84 +1,32 @@
 import type { Plugin as VitePlugin } from 'vite';
 import type { Plugin as rollupPlugin } from 'rollup';
 
-import { createMockServer } from 'vite-plugin-mock';
-import { VitePWA } from 'vite-plugin-pwa';
-import ViteHtmlPlugin from 'vite-plugin-html';
 import PurgeIcons from 'vite-plugin-purge-icons';
 
 import visualizer from 'rollup-plugin-visualizer';
 import gzipPlugin from './gzip/index';
 
-import { hmScript } from '../hm';
-
 // @ts-ignore
 import pkg from '../../../package.json';
-import { isDevFn, isProdFn, isSiteMode, ViteEnv, isReportMode, isBuildGzip } from '../../utils';
-import { GLOB_CONFIG_FILE_NAME } from '../../constant';
+import { isProdFn, isSiteMode, ViteEnv, isReportMode, isBuildGzip } from '../../utils';
+import { setupHtmlPlugin } from './html';
+import { setupPwaPlugin } from './pwa';
+import { setupMockPlugin } from './mock';
 
 // gen vite plugins
 export function createVitePlugins(viteEnv: ViteEnv) {
-  const { VITE_USE_MOCK, VITE_GLOB_APP_TITLE, VITE_PUBLIC_PATH, VITE_USE_PWA } = viteEnv;
-
   const vitePlugins: VitePlugin[] = [];
 
   // vite-plugin-html
-  vitePlugins.push(
-    ViteHtmlPlugin({
-      // html title
-      title: VITE_GLOB_APP_TITLE,
-      minify: isProdFn(),
-      options: {
-        // Package and insert additional configuration files
-        injectConfig: isProdFn()
-          ? `<script src='${VITE_PUBLIC_PATH || './'}${GLOB_CONFIG_FILE_NAME}?v=${
-              pkg.version
-            }-${new Date().getTime()}'></script>`
-          : '',
-        // Insert Baidu statistics code
-        hmScript: isSiteMode() ? hmScript : '',
-        title: VITE_GLOB_APP_TITLE,
-      },
-    })
-  );
+  setupHtmlPlugin(vitePlugins, viteEnv);
+  // vite-plugin-pwa
+  setupPwaPlugin(vitePlugins, viteEnv);
+  // vite-plugin-mock
+  setupMockPlugin(vitePlugins, viteEnv);
 
   // vite-plugin-purge-icons
   vitePlugins.push(PurgeIcons());
 
-  if (isProdFn() && VITE_USE_PWA) {
-    vitePlugins.push(
-      VitePWA({
-        manifest: {
-          name: 'Vben Admin',
-          short_name: 'vben_admin',
-          icons: [
-            {
-              src: './resource/img/pwa-192x192.png',
-              sizes: '192x192',
-              type: 'image/png',
-            },
-            {
-              src: './resource/img/pwa-512x512.png',
-              sizes: '512x512',
-              type: 'image/png',
-            },
-          ],
-        },
-      })
-    );
-  }
-
-  // vite-plugin-mock
-  if (isDevFn() && VITE_USE_MOCK) {
-    // open mock
-    vitePlugins.push(
-      createMockServer({
-        ignore: /^\_/,
-        mockPath: 'mock',
-        showTime: true,
-      })
-    );
-  }
   return vitePlugins;
 }
 
@@ -86,17 +34,15 @@ export function createVitePlugins(viteEnv: ViteEnv) {
 export function createRollupPlugin() {
   const rollupPlugins: rollupPlugin[] = [];
 
-  if (isProdFn()) {
-    if (isReportMode()) {
-      // rollup-plugin-visualizer
-      rollupPlugins.push(
-        visualizer({ filename: './build/.cache/stats.html', open: true }) as Plugin
-      );
-    }
-    if (isBuildGzip() || isSiteMode()) {
-      // rollup-plugin-gizp
-      rollupPlugins.push(gzipPlugin());
-    }
+  if (!isProdFn() && isReportMode()) {
+    // rollup-plugin-visualizer
+    rollupPlugins.push(visualizer({ filename: './build/.cache/stats.html', open: true }) as Plugin);
   }
+
+  if (!isProdFn() && (isBuildGzip() || isSiteMode())) {
+    // rollup-plugin-gizp
+    rollupPlugins.push(gzipPlugin());
+  }
+
   return rollupPlugins;
 }

+ 16 - 0
build/vite/plugin/mock.ts

@@ -0,0 +1,16 @@
+import { createMockServer } from 'vite-plugin-mock';
+import type { Plugin } from 'vite';
+import { isDevFn, ViteEnv } from '../../utils';
+
+export function setupMockPlugin(plugins: Plugin[], env: ViteEnv) {
+  const { VITE_USE_MOCK } = env;
+  const mockPlugin = createMockServer({
+    ignore: /^\_/,
+    mockPath: 'mock',
+    showTime: true,
+  });
+  if (isDevFn() && VITE_USE_MOCK) {
+    plugins.push(mockPlugin);
+  }
+  return plugins;
+}

+ 31 - 0
build/vite/plugin/pwa.ts

@@ -0,0 +1,31 @@
+import { VitePWA } from 'vite-plugin-pwa';
+import type { Plugin } from 'vite';
+import { isProdFn, ViteEnv } from '../../utils';
+
+export function setupPwaPlugin(plugins: Plugin[], env: ViteEnv) {
+  const { VITE_USE_PWA } = env;
+
+  const pwaPlugin = VitePWA({
+    manifest: {
+      name: 'Vben Admin',
+      short_name: 'vben_admin',
+      icons: [
+        {
+          src: './resource/img/pwa-192x192.png',
+          sizes: '192x192',
+          type: 'image/png',
+        },
+        {
+          src: './resource/img/pwa-512x512.png',
+          sizes: '512x512',
+          type: 'image/png',
+        },
+      ],
+    },
+  });
+
+  if (isProdFn() && VITE_USE_PWA) {
+    plugins.push(pwaPlugin);
+  }
+  return plugins;
+}

+ 1 - 1
src/components/Menu/src/BasicMenu.tsx

@@ -103,7 +103,7 @@ export default defineComponent({
         const isHorizontal = unref(getIsHorizontal) || getSplit.value;
 
         return {
-          height: isHorizontal ? `calc(100%)` : `calc(100% - ${props.showLogo ? '48px' : '0px'})`,
+          height: isHorizontal ? '100%' : `calc(100% - ${props.showLogo ? '48px' : '0px'})`,
           overflowY: isHorizontal ? 'hidden' : 'auto',
         };
       }

+ 2 - 2
src/hooks/web/useI18n.ts

@@ -25,9 +25,9 @@ export function useI18n(namespace?: string) {
 
   return {
     ...methods,
-    t: (key: string, ...arg: Parameters<typeof t>) => {
+    t: (key: string, ...arg: Partial<Parameters<typeof t>>) => {
       if (!key) return '';
-      return t(getKey(key), ...arg);
+      return t(getKey(key), ...(arg as Parameters<typeof t>));
     },
   };
 }

+ 209 - 412
src/layouts/default/setting/SettingDrawer.tsx

@@ -1,18 +1,17 @@
-import type { ProjectConfig } from '/@/types/config';
-
-import defaultSetting from '/@/settings/projectSetting';
-
-import { defineComponent, computed, unref, FunctionalComponent } from 'vue';
+import { defineComponent, computed, unref } from 'vue';
 import { BasicDrawer } from '/@/components/Drawer/index';
-import { Divider, Switch, Tooltip, InputNumber, Select } from 'ant-design-vue';
-import { Button } from '/@/components/Button';
-import { CopyOutlined, RedoOutlined, CheckOutlined } from '@ant-design/icons-vue';
+import { Divider } from 'ant-design-vue';
+import {
+  TypePicker,
+  ThemePicker,
+  SettingFooter,
+  SwitchItem,
+  SelectItem,
+  InputNumberItem,
+} from './components';
 
 import { MenuTypeEnum } from '/@/enums/menuEnum';
-import { appStore } from '/@/store/modules/app';
 
-import { useMessage } from '/@/hooks/web/useMessage';
-import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard';
 import { useRootSetting } from '/@/hooks/setting/useRootSetting';
 import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
 import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
@@ -20,8 +19,6 @@ import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
 import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting';
 import { useI18n } from '/@/hooks/web/useI18n';
 
-import { updateColorWeak, updateGrayMode } from '/@/setup/theme';
-
 import { baseHandler } from './handler';
 
 import {
@@ -35,146 +32,8 @@ import {
 
 import { HEADER_PRESET_BG_COLOR_LIST, SIDE_BAR_BG_COLOR_LIST } from '/@/settings/colorSetting';
 
-interface SwitchOptions {
-  config?: DeepPartial<ProjectConfig>;
-  def?: any;
-  disabled?: boolean;
-  handler?: Fn;
-}
-
-interface SelectConfig {
-  options?: LabelValueOptions;
-  def?: any;
-  disabled?: boolean;
-  handler?: Fn;
-}
-
-interface ThemePickerProps {
-  colorList: string[];
-  handler: Fn;
-  def: string;
-}
-
-const { createSuccessModal, createMessage } = useMessage();
 const { t } = useI18n();
 
-/**
- * Menu type Picker comp
- */
-const MenuTypePicker: FunctionalComponent = () => {
-  const { getIsHorizontal, getMenuType } = useMenuSetting();
-  return (
-    <div class={`setting-drawer__siderbar`}>
-      {menuTypeList.map((item) => {
-        const { title, type: ItemType, mode, src } = item;
-        return (
-          <Tooltip title={title} placement="bottom" key={title}>
-            {{
-              default: () => (
-                <div
-                  onClick={baseHandler.bind(null, HandlerEnum.CHANGE_LAYOUT, {
-                    mode: mode,
-                    type: ItemType,
-                    split: unref(getIsHorizontal) ? false : undefined,
-                  })}
-                >
-                  <CheckOutlined
-                    class={['check-icon', unref(getMenuType) === ItemType ? 'active' : '']}
-                  />
-                  <img src={src} />
-                </div>
-              ),
-            }}
-          </Tooltip>
-        );
-      })}
-    </div>
-  );
-};
-
-const ThemePicker: FunctionalComponent<ThemePickerProps> = (props) => {
-  return (
-    <div class={`setting-drawer__theme-item`}>
-      {props.colorList.map((color) => {
-        return (
-          <span
-            onClick={() => props.handler?.(color)}
-            key={color}
-            class={[props.def === color ? 'active' : '']}
-            style={{
-              background: color,
-            }}
-          >
-            <CheckOutlined class="icon" />
-          </span>
-        );
-      })}
-    </div>
-  );
-};
-
-/**
- * FooterButton component
- */
-const FooterButton: FunctionalComponent = () => {
-  const { getRootSetting } = useRootSetting();
-  function handleCopy() {
-    const { isSuccessRef } = useCopyToClipboard(JSON.stringify(unref(getRootSetting), null, 2));
-    unref(isSuccessRef) &&
-      createSuccessModal({
-        title: t('layout.setting.operatingTitle'),
-        content: t('layout.setting.operatingContent'),
-      });
-  }
-  function handleResetSetting() {
-    try {
-      appStore.commitProjectConfigState(defaultSetting);
-      const { colorWeak, grayMode } = defaultSetting;
-      // updateTheme(themeColor);
-      updateColorWeak(colorWeak);
-      updateGrayMode(grayMode);
-      createMessage.success(t('layout.setting.resetSuccess'));
-    } catch (error) {
-      createMessage.error(error);
-    }
-  }
-
-  function handleClearAndRedo() {
-    localStorage.clear();
-    appStore.resumeAllState();
-    location.reload();
-  }
-
-  return (
-    <div class="setting-drawer__footer">
-      <Button type="primary" block onClick={handleCopy}>
-        {() => (
-          <>
-            <CopyOutlined class="mr-2" />
-            {t('layout.setting.copyBtn')}
-          </>
-        )}
-      </Button>
-      <Button block class="mt-2" onClick={handleResetSetting} color="warning">
-        {() => (
-          <>
-            <RedoOutlined class="mr-2" />
-            {t('layout.setting.resetBtn')}
-          </>
-        )}
-      </Button>
-      <Button block class="mt-2" onClick={handleClearAndRedo} color="error">
-        {() => (
-          <>
-            <RedoOutlined class="mr-2" />
-            {t('layout.setting.clearBtn')}
-          </>
-        )}
-      </Button>
-    </div>
-  );
-};
-
 export default defineComponent({
   name: 'SettingDrawer',
   setup(_, { attrs }) {
@@ -187,6 +46,7 @@ export default defineComponent({
       getFullContent,
       getColorWeak,
       getGrayMode,
+      getLockTime,
     } = useRootSetting();
 
     const {
@@ -229,38 +89,44 @@ export default defineComponent({
     function renderSidebar() {
       return (
         <>
-          <MenuTypePicker />
-          {renderSwitchItem(t('layout.setting.splitMenu'), {
-            handler: (e) => {
-              baseHandler(HandlerEnum.MENU_SPLIT, e);
-            },
-            def: unref(getSplit),
-            disabled: !unref(getShowMenuRef) || unref(getMenuType) !== MenuTypeEnum.MIX,
-          })}
+          <TypePicker
+            menuTypeList={menuTypeList}
+            handler={(item: typeof menuTypeList[0]) => {
+              baseHandler(HandlerEnum.CHANGE_LAYOUT, {
+                mode: item.mode,
+                type: item.type,
+                split: unref(getIsHorizontal) ? false : undefined,
+              });
+            }}
+            def={unref(getMenuType)}
+          />
+          <SwitchItem
+            title={t('layout.setting.splitMenu')}
+            event={HandlerEnum.MENU_SPLIT}
+            def={unref(getSplit)}
+            disabled={!unref(getShowMenuRef) || unref(getMenuType) !== MenuTypeEnum.MIX}
+          />
         </>
       );
     }
 
-    function renderTheme() {
+    function renderHeaderTheme() {
       return (
-        <>
-          <Divider>{() => t('layout.setting.headerTheme')}</Divider>
-          <ThemePicker
-            colorList={HEADER_PRESET_BG_COLOR_LIST}
-            def={unref(getHeaderBgColor)}
-            handler={(e) => {
-              baseHandler(HandlerEnum.HEADER_THEME, e);
-            }}
-          />
-          <Divider>{() => t('layout.setting.sidebarTheme')}</Divider>
-          <ThemePicker
-            colorList={SIDE_BAR_BG_COLOR_LIST}
-            def={unref(getMenuBgColor)}
-            handler={(e) => {
-              baseHandler(HandlerEnum.MENU_THEME, e);
-            }}
-          />
-        </>
+        <ThemePicker
+          colorList={HEADER_PRESET_BG_COLOR_LIST}
+          def={unref(getHeaderBgColor)}
+          event={HandlerEnum.HEADER_THEME}
+        />
+      );
+    }
+
+    function renderSiderTheme() {
+      return (
+        <ThemePicker
+          colorList={SIDE_BAR_BG_COLOR_LIST}
+          def={unref(getMenuBgColor)}
+          event={HandlerEnum.MENU_THEME}
+        />
       );
     }
 
@@ -268,264 +134,192 @@ export default defineComponent({
      * @description:
      */
     function renderFeatures() {
-      return [
-        renderSwitchItem(t('layout.setting.menuDrag'), {
-          handler: (e) => {
-            baseHandler(HandlerEnum.MENU_HAS_DRAG, e);
-          },
-          def: unref(getCanDrag),
-          disabled: !unref(getShowMenuRef),
-        }),
-        renderSwitchItem(t('layout.setting.menuSearch'), {
-          handler: (e) => {
-            baseHandler(HandlerEnum.HEADER_SEARCH, e);
-          },
-          def: unref(getShowSearch),
-          disabled: !unref(getShowHeader),
-        }),
-        renderSwitchItem(t('layout.setting.menuAccordion'), {
-          handler: (e) => {
-            baseHandler(HandlerEnum.MENU_ACCORDION, e);
-          },
-          def: unref(getAccordion),
-          disabled: !unref(getShowMenuRef),
-        }),
-        renderSwitchItem(t('layout.setting.menuCollapse'), {
-          handler: (e) => {
-            baseHandler(HandlerEnum.MENU_COLLAPSED, e);
-          },
-          def: unref(getCollapsed),
-          disabled: !unref(getShowMenuRef),
-        }),
-        renderSwitchItem(t('layout.setting.collapseMenuDisplayName'), {
-          handler: (e) => {
-            baseHandler(HandlerEnum.MENU_COLLAPSED_SHOW_TITLE, e);
-          },
-          def: unref(getCollapsedShowTitle),
-          disabled: !unref(getShowMenuRef) || !unref(getCollapsed),
-        }),
-        renderSwitchItem(t('layout.setting.fixedHeader'), {
-          handler: (e) => {
-            baseHandler(HandlerEnum.HEADER_FIXED, e);
-          },
-          def: unref(getHeaderFixed),
-          disabled: !unref(getShowHeader),
-        }),
-        renderSwitchItem(t('layout.setting.fixedSideBar'), {
-          handler: (e) => {
-            baseHandler(HandlerEnum.MENU_FIXED, e);
-          },
-          def: unref(getMenuFixed),
-          disabled: !unref(getShowMenuRef),
-        }),
-        renderSelectItem(t('layout.setting.topMenuLayout'), {
-          handler: (e) => {
-            baseHandler(HandlerEnum.MENU_TOP_ALIGN, e);
-          },
-          def: unref(getTopMenuAlign),
-          options: topMenuAlignOptions,
-          disabled: !unref(getShowHeader) || (!unref(getIsTopMenu) && !unref(getSplit)),
-        }),
-        renderSelectItem(t('layout.setting.menuCollapseButton'), {
-          handler: (e) => {
-            baseHandler(HandlerEnum.MENU_TRIGGER, e);
-          },
-          disabled: !unref(getShowMenuRef),
-          def: unref(getTrigger),
-          options: menuTriggerOptions,
-        }),
-
-        renderSelectItem(t('layout.setting.contentMode'), {
-          handler: (e) => {
-            baseHandler(HandlerEnum.CONTENT_MODE, e);
-          },
-          def: unref(getContentMode),
-          options: contentModeOptions,
-        }),
-        <div class={`setting-drawer__cell-item`}>
-          <span>{t('layout.setting.autoScreenLock')}</span>
-          <InputNumber
-            style="width:126px"
-            size="small"
+      return (
+        <>
+          <SwitchItem
+            title={t('layout.setting.menuDrag')}
+            event={HandlerEnum.MENU_HAS_DRAG}
+            def={unref(getCanDrag)}
+            disabled={!unref(getShowMenuRef)}
+          />
+          <SwitchItem
+            title={t('layout.setting.menuSearch')}
+            event={HandlerEnum.HEADER_SEARCH}
+            def={unref(getShowSearch)}
+            disabled={!unref(getShowHeader)}
+          />
+          <SwitchItem
+            title={t('layout.setting.menuAccordion')}
+            event={HandlerEnum.MENU_ACCORDION}
+            def={unref(getAccordion)}
+            disabled={!unref(getShowMenuRef)}
+          />
+          <SwitchItem
+            title={t('layout.setting.menuCollapse')}
+            event={HandlerEnum.MENU_COLLAPSED}
+            def={unref(getCollapsed)}
+            disabled={!unref(getShowMenuRef)}
+          />
+          <SwitchItem
+            title={t('layout.setting.collapseMenuDisplayName')}
+            event={HandlerEnum.MENU_COLLAPSED_SHOW_TITLE}
+            def={unref(getCollapsedShowTitle)}
+            disabled={!unref(getShowMenuRef) || !unref(getCollapsed)}
+          />
+          <SwitchItem
+            title={t('layout.setting.fixedHeader')}
+            event={HandlerEnum.HEADER_FIXED}
+            def={unref(getHeaderFixed)}
+            disabled={!unref(getShowHeader)}
+          />
+          <SwitchItem
+            title={t('layout.setting.fixedSideBar')}
+            event={HandlerEnum.MENU_FIXED}
+            def={unref(getMenuFixed)}
+            disabled={!unref(getShowMenuRef)}
+          />
+          <SelectItem
+            title={t('layout.setting.topMenuLayout')}
+            event={HandlerEnum.MENU_TOP_ALIGN}
+            def={unref(getTopMenuAlign)}
+            options={topMenuAlignOptions}
+            disabled={!unref(getShowHeader) || (!unref(getIsTopMenu) && !unref(getSplit))}
+          />
+          <SelectItem
+            title={t('layout.setting.menuCollapseButton')}
+            event={HandlerEnum.MENU_TRIGGER}
+            def={unref(getTrigger)}
+            options={menuTriggerOptions}
+            disabled={!unref(getShowMenuRef)}
+          />
+          <SelectItem
+            title={t('layout.setting.contentMode')}
+            event={HandlerEnum.CONTENT_MODE}
+            def={unref(getContentMode)}
+            options={contentModeOptions}
+          />
+          <InputNumberItem
+            title={t('layout.setting.autoScreenLock')}
             min={0}
-            onChange={(e: any) => {
-              baseHandler(HandlerEnum.LOCK_TIME, e);
-            }}
-            defaultValue={appStore.getProjectConfig.lockTime}
+            event={HandlerEnum.LOCK_TIME}
+            defaultValue={unref(getLockTime)}
             formatter={(value: string) => {
-              if (parseInt(value) === 0) {
-                return `0(${t('layout.setting.notAutoScreenLock')})`;
-              }
-              return `${value}${t('layout.setting.minute')}`;
+              return parseInt(value) === 0
+                ? `0(${t('layout.setting.notAutoScreenLock')})`
+                : `${value}${t('layout.setting.minute')}`;
             }}
           />
-        </div>,
-        <div class={`setting-drawer__cell-item`}>
-          <span>{t('layout.setting.expandedMenuWidth')}</span>
-          <InputNumber
-            style="width:126px"
-            size="small"
+          <InputNumberItem
+            title={t('layout.setting.expandedMenuWidth')}
             max={600}
             min={100}
             step={10}
+            event={HandlerEnum.MENU_WIDTH}
             disabled={!unref(getShowMenuRef)}
             defaultValue={unref(getMenuWidth)}
             formatter={(value: string) => `${parseInt(value)}px`}
-            onChange={(e: any) => {
-              baseHandler(HandlerEnum.MENU_WIDTH, e);
-            }}
           />
-        </div>,
-      ];
+        </>
+      );
     }
 
     function renderContent() {
-      return [
-        renderSwitchItem(t('layout.setting.breadcrumb'), {
-          handler: (e) => {
-            baseHandler(HandlerEnum.SHOW_BREADCRUMB, e);
-          },
-          def: unref(getShowBreadCrumb),
-          disabled: !unref(getShowHeader),
-        }),
-        renderSwitchItem(t('layout.setting.breadcrumbIcon'), {
-          handler: (e) => {
-            baseHandler(HandlerEnum.SHOW_BREADCRUMB_ICON, e);
-          },
-          def: unref(getShowBreadCrumbIcon),
-          disabled: !unref(getShowHeader),
-        }),
-        renderSwitchItem(t('layout.setting.tabs'), {
-          handler: (e) => {
-            baseHandler(HandlerEnum.TABS_SHOW, e);
-          },
-          def: unref(getShowMultipleTab),
-        }),
-        renderSwitchItem(t('layout.setting.tabsQuickBtn'), {
-          handler: (e) => {
-            baseHandler(HandlerEnum.TABS_SHOW_QUICK, e);
-          },
-          def: unref(getShowQuick),
-          disabled: !unref(getShowMultipleTab),
-        }),
-
-        renderSwitchItem(t('layout.setting.sidebar'), {
-          handler: (e) => {
-            baseHandler(HandlerEnum.MENU_SHOW_SIDEBAR, e);
-          },
-          def: unref(getShowMenu),
-          disabled: unref(getIsHorizontal),
-        }),
-        renderSwitchItem(t('layout.setting.header'), {
-          handler: (e) => {
-            baseHandler(HandlerEnum.HEADER_SHOW, e);
-          },
-          def: unref(getShowHeader),
-        }),
-        renderSwitchItem('Logo', {
-          handler: (e) => {
-            baseHandler(HandlerEnum.SHOW_LOGO, e);
-          },
-          def: unref(getShowLogo),
-        }),
-        renderSwitchItem(t('layout.setting.footer'), {
-          handler: (e) => {
-            baseHandler(HandlerEnum.SHOW_FOOTER, e);
-          },
-          def: unref(getShowFooter),
-        }),
-        renderSwitchItem(t('layout.setting.fullContent'), {
-          handler: (e) => {
-            baseHandler(HandlerEnum.FULL_CONTENT, e);
-          },
-          def: unref(getFullContent),
-        }),
-        renderSwitchItem(t('layout.setting.grayMode'), {
-          handler: (e) => {
-            baseHandler(HandlerEnum.GRAY_MODE, e);
-          },
-          def: unref(getGrayMode),
-        }),
-        renderSwitchItem(t('layout.setting.colorWeak'), {
-          handler: (e) => {
-            baseHandler(HandlerEnum.COLOR_WEAK, e);
-          },
-          def: unref(getColorWeak),
-        }),
-      ];
-    }
-
-    function renderTransition() {
       return (
         <>
-          {renderSwitchItem(t('layout.setting.progress'), {
-            handler: (e) => {
-              baseHandler(HandlerEnum.OPEN_PROGRESS, e);
-            },
-            def: unref(getOpenNProgress),
-          })}
-          {renderSwitchItem(t('layout.setting.switchLoading'), {
-            handler: (e) => {
-              baseHandler(HandlerEnum.OPEN_PAGE_LOADING, e);
-            },
-            def: unref(getOpenPageLoading),
-          })}
+          <SwitchItem
+            title={t('layout.setting.breadcrumb')}
+            event={HandlerEnum.SHOW_BREADCRUMB}
+            def={unref(getShowBreadCrumb)}
+            disabled={!unref(getShowHeader)}
+          />
+
+          <SwitchItem
+            title={t('layout.setting.breadcrumbIcon')}
+            event={HandlerEnum.SHOW_BREADCRUMB_ICON}
+            def={unref(getShowBreadCrumbIcon)}
+            disabled={!unref(getShowHeader)}
+          />
+
+          <SwitchItem
+            title={t('layout.setting.tabs')}
+            event={HandlerEnum.TABS_SHOW}
+            def={unref(getShowMultipleTab)}
+          />
+
+          <SwitchItem
+            title={t('layout.setting.tabsQuickBtn')}
+            event={HandlerEnum.TABS_SHOW_QUICK}
+            def={unref(getShowQuick)}
+            disabled={!unref(getShowMultipleTab)}
+          />
+
+          <SwitchItem
+            title={t('layout.setting.sidebar')}
+            event={HandlerEnum.MENU_SHOW_SIDEBAR}
+            def={unref(getShowMenu)}
+            disabled={unref(getIsHorizontal)}
+          />
+
+          <SwitchItem
+            title={t('layout.setting.header')}
+            event={HandlerEnum.HEADER_SHOW}
+            def={unref(getShowHeader)}
+          />
+          <SwitchItem title="Logo" event={HandlerEnum.SHOW_LOGO} def={unref(getShowLogo)} />
+          <SwitchItem
+            title={t('layout.setting.footer')}
+            event={HandlerEnum.SHOW_FOOTER}
+            def={unref(getShowFooter)}
+          />
+          <SwitchItem
+            title={t('layout.setting.fullContent')}
+            event={HandlerEnum.FULL_CONTENT}
+            def={unref(getFullContent)}
+          />
 
-          {renderSwitchItem(t('layout.setting.switchAnimation'), {
-            handler: (e) => {
-              baseHandler(HandlerEnum.OPEN_ROUTE_TRANSITION, e);
-            },
-            def: unref(getEnableTransition),
-          })}
+          <SwitchItem
+            title={t('layout.setting.grayMode')}
+            event={HandlerEnum.GRAY_MODE}
+            def={unref(getGrayMode)}
+          />
 
-          {renderSelectItem(t('layout.setting.animationType'), {
-            handler: (e) => {
-              baseHandler(HandlerEnum.ROUTER_TRANSITION, e);
-            },
-            def: unref(getBasicTransition),
-            options: routerTransitionOptions,
-            disabled: !unref(getEnableTransition),
-          })}
+          <SwitchItem
+            title={t('layout.setting.colorWeak')}
+            event={HandlerEnum.COLOR_WEAK}
+            def={unref(getColorWeak)}
+          />
         </>
       );
     }
 
-    function renderSelectItem(text: string, config?: SelectConfig) {
-      const { handler, def, disabled = false, options } = config || {};
-      const opt = def ? { value: def, defaultValue: def } : {};
+    function renderTransition() {
       return (
-        <div class={`setting-drawer__cell-item`}>
-          <span>{text}</span>
-          <Select
-            {...opt}
-            disabled={disabled}
-            size="small"
-            style={{ width: '126px' }}
-            onChange={(e) => {
-              handler && handler(e);
-            }}
-            options={options}
+        <>
+          <SwitchItem
+            title={t('layout.setting.progress')}
+            event={HandlerEnum.OPEN_PROGRESS}
+            def={unref(getOpenNProgress)}
+          />
+          <SwitchItem
+            title={t('layout.setting.switchLoading')}
+            event={HandlerEnum.OPEN_PAGE_LOADING}
+            def={unref(getOpenPageLoading)}
           />
-        </div>
-      );
-    }
 
-    function renderSwitchItem(text: string, options?: SwitchOptions) {
-      const { handler, def, disabled = false } = options || {};
-      const opt = def ? { checked: def } : {};
-      return (
-        <div class={`setting-drawer__cell-item`}>
-          <span>{text}</span>
-          <Switch
-            {...opt}
-            disabled={disabled}
-            onChange={(e: any) => {
-              handler && handler(e);
-            }}
-            checkedChildren={t('layout.setting.on')}
-            unCheckedChildren={t('layout.setting.off')}
+          <SwitchItem
+            title={t('layout.setting.switchAnimation')}
+            event={HandlerEnum.OPEN_ROUTE_TRANSITION}
+            def={unref(getEnableTransition)}
           />
-        </div>
+
+          <SelectItem
+            title={t('layout.setting.animationType')}
+            event={HandlerEnum.ROUTER_TRANSITION}
+            def={unref(getBasicTransition)}
+            options={routerTransitionOptions}
+            disabled={!unref(getEnableTransition)}
+          />
+        </>
       );
     }
 
@@ -541,7 +335,10 @@ export default defineComponent({
             <>
               <Divider>{() => t('layout.setting.navMode')}</Divider>
               {renderSidebar()}
-              {renderTheme()}
+              <Divider>{() => t('layout.setting.headerTheme')}</Divider>
+              {renderHeaderTheme()}
+              <Divider>{() => t('layout.setting.sidebarTheme')}</Divider>
+              {renderSiderTheme()}
               <Divider>{() => t('layout.setting.interfaceFunction')}</Divider>
               {renderFeatures()}
               <Divider>{() => t('layout.setting.interfaceDisplay')}</Divider>
@@ -549,7 +346,7 @@ export default defineComponent({
               <Divider>{() => t('layout.setting.animation')}</Divider>
               {renderTransition()}
               <Divider />
-              <FooterButton />
+              <SettingFooter />
             </>
           ),
         }}

+ 58 - 0
src/layouts/default/setting/components/InputNumberItem.vue

@@ -0,0 +1,58 @@
+<template>
+  <div :class="prefixCls">
+    <span> {{ title }}</span>
+    <InputNumber
+      v-bind="$attrs"
+      size="small"
+      :class="`${prefixCls}-input-number`"
+      @change="handleChange"
+    />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, PropType } from 'vue';
+
+  import { InputNumber } from 'ant-design-vue';
+  import { useDesign } from '/@/hooks/web/useDesign';
+  import { baseHandler } from '../handler';
+  import { HandlerEnum } from '../enum';
+
+  export default defineComponent({
+    name: 'InputNumberItem',
+    components: { InputNumber },
+    props: {
+      event: {
+        type: Number as PropType<HandlerEnum>,
+        default: () => {},
+      },
+      title: {
+        type: String,
+      },
+    },
+    setup(props) {
+      const { prefixCls } = useDesign('setting-input-number-item');
+
+      function handleChange(e: ChangeEvent) {
+        props.event && baseHandler(props.event, e);
+      }
+      return {
+        prefixCls,
+        handleChange,
+      };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  @import (reference) '../../../../design/index.less';
+  @prefix-cls: ~'@{namespace}-setting-input-number-item';
+
+  .@{prefix-cls} {
+    display: flex;
+    justify-content: space-between;
+    margin: 16px 0;
+
+    &-input-number {
+      width: 126px;
+    }
+  }
+</style>

+ 77 - 0
src/layouts/default/setting/components/SelectItem.vue

@@ -0,0 +1,77 @@
+<template>
+  <div :class="prefixCls">
+    <span> {{ title }}</span>
+    <Select
+      v-bind="getBindValue"
+      :class="`${prefixCls}-select`"
+      @change="handleChange"
+      :disabled="disabled"
+      size="small"
+      :options="options"
+    />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, PropType, computed } from 'vue';
+
+  import { Select } from 'ant-design-vue';
+  import { useDesign } from '/@/hooks/web/useDesign';
+  import { baseHandler } from '../handler';
+  import { HandlerEnum } from '../enum';
+
+  export default defineComponent({
+    name: 'SelectItem',
+    components: { Select },
+    props: {
+      event: {
+        type: Number as PropType<HandlerEnum>,
+        default: () => {},
+      },
+      disabled: {
+        type: Boolean,
+      },
+      title: {
+        type: String,
+      },
+      def: {
+        type: [String, Number] as PropType<string | number>,
+      },
+      initValue: {
+        type: [String, Number] as PropType<string | number>,
+      },
+      options: {
+        type: Array as PropType<LabelValueOptions>,
+        default: [],
+      },
+    },
+    setup(props) {
+      const { prefixCls } = useDesign('setting-select-item');
+      const getBindValue = computed(() => {
+        return props.def ? { value: props.def, defaultValue: props.initValue || props.def } : {};
+      });
+
+      function handleChange(e: ChangeEvent) {
+        props.event && baseHandler(props.event, e);
+      }
+      return {
+        prefixCls,
+        handleChange,
+        getBindValue,
+      };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  @import (reference) '../../../../design/index.less';
+  @prefix-cls: ~'@{namespace}-setting-select-item';
+
+  .@{prefix-cls} {
+    display: flex;
+    justify-content: space-between;
+    margin: 16px 0;
+
+    &-select {
+      width: 126px;
+    }
+  }
+</style>

+ 86 - 0
src/layouts/default/setting/components/SettingFooter.vue

@@ -0,0 +1,86 @@
+<template>
+  <div :class="prefixCls">
+    <a-button type="primary" block @click="handleCopy">
+      <CopyOutlined class="mr-2" />
+      {{ t('layout.setting.copyBtn') }}
+    </a-button>
+
+    <a-button color="warning" block @click="handleResetSetting" class="my-3">
+      <RedoOutlined class="mr-2" />
+      {{ t('layout.setting.resetBtn') }}
+    </a-button>
+
+    <a-button color="error" block @click="handleClearAndRedo">
+      <RedoOutlined class="mr-2" />
+      {{ t('layout.setting.clearBtn') }}
+    </a-button>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, unref } from 'vue';
+
+  import { useDesign } from '/@/hooks/web/useDesign';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { CopyOutlined, RedoOutlined } from '@ant-design/icons-vue';
+  import { appStore } from '/@/store/modules/app';
+  import defaultSetting from '/@/settings/projectSetting';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard';
+  import { useRootSetting } from '/@/hooks/setting/useRootSetting';
+  import { updateColorWeak, updateGrayMode } from '/@/setup/theme';
+
+  export default defineComponent({
+    name: 'SettingFooter',
+    components: { CopyOutlined, RedoOutlined },
+    setup() {
+      const { getRootSetting } = useRootSetting();
+      const { prefixCls } = useDesign('setting-footer');
+      const { t } = useI18n();
+      const { createSuccessModal, createMessage } = useMessage();
+
+      function handleCopy() {
+        const { isSuccessRef } = useCopyToClipboard(JSON.stringify(unref(getRootSetting), null, 2));
+        unref(isSuccessRef) &&
+          createSuccessModal({
+            title: t('layout.setting.operatingTitle'),
+            content: t('layout.setting.operatingContent'),
+          });
+      }
+      function handleResetSetting() {
+        try {
+          appStore.commitProjectConfigState(defaultSetting);
+          const { colorWeak, grayMode } = defaultSetting;
+          // updateTheme(themeColor);
+          updateColorWeak(colorWeak);
+          updateGrayMode(grayMode);
+          createMessage.success(t('layout.setting.resetSuccess'));
+        } catch (error) {
+          createMessage.error(error);
+        }
+      }
+
+      function handleClearAndRedo() {
+        localStorage.clear();
+        appStore.resumeAllState();
+        location.reload();
+      }
+      return {
+        prefixCls,
+        t,
+        handleCopy,
+        handleResetSetting,
+        handleClearAndRedo,
+      };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  @import (reference) '../../../../design/index.less';
+  @prefix-cls: ~'@{namespace}-setting-footer';
+
+  .@{prefix-cls} {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+  }
+</style>

+ 68 - 0
src/layouts/default/setting/components/SwitchItem.vue

@@ -0,0 +1,68 @@
+<template>
+  <div :class="prefixCls">
+    <span> {{ title }}</span>
+    <Switch
+      v-bind="getBindValue"
+      @change="handleChange"
+      :disabled="disabled"
+      :checkedChildren="t('layout.setting.on')"
+      :unCheckedChildren="t('layout.setting.off')"
+    />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, PropType, computed } from 'vue';
+
+  import { Switch } from 'ant-design-vue';
+  import { useDesign } from '/@/hooks/web/useDesign';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { baseHandler } from '../handler';
+  import { HandlerEnum } from '../enum';
+
+  export default defineComponent({
+    name: 'SwitchItem',
+    components: { Switch },
+    props: {
+      event: {
+        type: Number as PropType<HandlerEnum>,
+        default: () => {},
+      },
+      disabled: {
+        type: Boolean,
+      },
+      title: {
+        type: String,
+      },
+      def: {
+        type: Boolean,
+      },
+    },
+    setup(props) {
+      const { prefixCls } = useDesign('setting-switch-item');
+      const { t } = useI18n();
+
+      const getBindValue = computed(() => {
+        return props.def ? { checked: props.def } : {};
+      });
+      function handleChange(e: ChangeEvent) {
+        props.event && baseHandler(props.event, e);
+      }
+      return {
+        prefixCls,
+        t,
+        handleChange,
+        getBindValue,
+      };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  @import (reference) '../../../../design/index.less';
+  @prefix-cls: ~'@{namespace}-setting-switch-item';
+
+  .@{prefix-cls} {
+    display: flex;
+    justify-content: space-between;
+    margin: 16px 0;
+  }
+</style>

+ 90 - 0
src/layouts/default/setting/components/ThemePicker.vue

@@ -0,0 +1,90 @@
+<template>
+  <div :class="prefixCls">
+    <template v-for="color in colorList || []" :key="color">
+      <span
+        @click="handleClick(color)"
+        :class="[
+          `${prefixCls}__item`,
+          {
+            [`${prefixCls}__item--active`]: def === color,
+          },
+        ]"
+        :style="{ background: color }"
+      >
+        <CheckOutlined />
+      </span>
+    </template>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, PropType } from 'vue';
+  import { CheckOutlined } from '@ant-design/icons-vue';
+
+  import { useDesign } from '/@/hooks/web/useDesign';
+
+  import { baseHandler } from '../handler';
+  import { HandlerEnum } from '../enum';
+
+  export default defineComponent({
+    name: 'ThemePicker',
+    components: { CheckOutlined },
+    props: {
+      colorList: {
+        type: Array as PropType<string[]>,
+        defualt: [],
+      },
+      event: {
+        type: Number as PropType<HandlerEnum>,
+        default: () => {},
+      },
+      def: {
+        type: String,
+      },
+    },
+    setup(props) {
+      const { prefixCls } = useDesign('setting-theme-picker');
+
+      function handleClick(color: string) {
+        props.event && baseHandler(props.event, color);
+      }
+      return {
+        prefixCls,
+        handleClick,
+      };
+    },
+  });
+</script>
+<style lang="less">
+  @import (reference) '../../../../design/index.less';
+  @prefix-cls: ~'@{namespace}-setting-theme-picker';
+
+  .@{prefix-cls} {
+    display: flex;
+    flex-wrap: wrap;
+    margin: 16px 0;
+    justify-content: space-around;
+
+    &__item {
+      width: 20px;
+      height: 20px;
+      cursor: pointer;
+      border: 1px solid #ddd;
+      border-radius: 2px;
+
+      svg {
+        display: none;
+      }
+
+      &--active {
+        border: 1px solid lighten(@primary-color, 10%);
+
+        svg {
+          display: inline-block;
+          margin: 0 0 3px 3px;
+          font-size: 12px;
+          fill: @white !important;
+        }
+      }
+    }
+  }
+</style>

+ 98 - 0
src/layouts/default/setting/components/TypePicker.vue

@@ -0,0 +1,98 @@
+<template>
+  <div :class="prefixCls">
+    <template v-for="item in menuTypeList || []" :key="item.title">
+      <Tooltip :title="item.title" placement="bottom">
+        <div
+          @click="handler(item)"
+          :class="[
+            `${prefixCls}__item`,
+            {
+              [`${prefixCls}__item--active`]: def === item.type,
+            },
+          ]"
+        >
+          <img :src="item.src" />
+        </div>
+      </Tooltip>
+    </template>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, PropType } from 'vue';
+
+  import { Tooltip } from 'ant-design-vue';
+  import { useDesign } from '/@/hooks/web/useDesign';
+
+  import { menuTypeList } from '../enum';
+  export default defineComponent({
+    name: 'MenuTypePicker',
+    components: { Tooltip },
+    props: {
+      menuTypeList: {
+        type: Array as PropType<typeof menuTypeList>,
+        defualt: [],
+      },
+      handler: {
+        type: Function as PropType<Fn>,
+        default: () => {},
+      },
+      def: {
+        type: String,
+      },
+    },
+    setup() {
+      const { prefixCls } = useDesign('setting-menu-type-picker');
+
+      return {
+        prefixCls,
+      };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  @import (reference) '../../../../design/index.less';
+  @prefix-cls: ~'@{namespace}-setting-menu-type-picker';
+
+  .@{prefix-cls} {
+    display: flex;
+
+    &__item {
+      position: relative;
+      width: 70px;
+      height: 50px;
+      margin: 0 20px 20px 0;
+      cursor: pointer;
+      border-radius: 6px;
+
+      &::after {
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        width: 0;
+        height: 0;
+        content: '';
+        opacity: 0;
+        transition: all 0.3s;
+      }
+
+      &:hover,
+      &--active {
+        &::after {
+          top: -8px;
+          left: -4px;
+          width: 80px;
+          height: 64px;
+          border: 2px solid @primary-color;
+          border-radius: 6px;
+          opacity: 1;
+        }
+      }
+    }
+
+    img {
+      width: 100%;
+      height: 100%;
+      cursor: pointer;
+    }
+  }
+</style>

+ 8 - 0
src/layouts/default/setting/components/index.ts

@@ -0,0 +1,8 @@
+import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
+
+export const TypePicker = createAsyncComponent(() => import('./TypePicker.vue'));
+export const ThemePicker = createAsyncComponent(() => import('./ThemePicker.vue'));
+export const SettingFooter = createAsyncComponent(() => import('./SettingFooter.vue'));
+export const SwitchItem = createAsyncComponent(() => import('./SwitchItem.vue'));
+export const SelectItem = createAsyncComponent(() => import('./SelectItem.vue'));
+export const InputNumberItem = createAsyncComponent(() => import('./InputNumberItem.vue'));

+ 0 - 73
src/layouts/default/setting/index.less

@@ -1,73 +0,0 @@
-.setting-drawer {
-  .ant-drawer-body {
-    padding-top: 0;
-    background: @white;
-  }
-
-  &__footer {
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-  }
-
-  &__cell-item {
-    display: flex;
-    justify-content: space-between;
-    margin: 16px 0;
-  }
-
-  &__theme-item {
-    display: flex;
-    flex-wrap: wrap;
-    margin: 16px 0;
-    justify-content: space-around;
-
-    > span {
-      width: 20px;
-      height: 20px;
-      cursor: pointer;
-      border: 1px solid #ddd;
-      border-radius: 2px;
-
-      svg {
-        display: none;
-      }
-
-      &.active {
-        border: 1px solid lighten(@primary-color, 10%);
-
-        svg {
-          display: inline-block;
-          margin: 0 0 3px 3px;
-          font-size: 12px;
-          fill: @white;
-        }
-      }
-    }
-  }
-
-  &__siderbar {
-    display: flex;
-
-    > div {
-      position: relative;
-
-      .check-icon {
-        position: absolute;
-        top: 40%;
-        left: 40%;
-        display: none;
-        color: @primary-color;
-
-        &.active {
-          display: inline-block;
-        }
-      }
-    }
-
-    img {
-      margin-right: 10px;
-      cursor: pointer;
-    }
-  }
-}

+ 7 - 3
src/layouts/default/setting/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div @click="openDrawer" class="setting-button">
+  <div @click="openDrawer" :class="prefixCls">
     <SettingOutlined />
     <SettingDrawer @register="register" />
   </div>
@@ -10,13 +10,17 @@
   import SettingDrawer from './SettingDrawer';
 
   import { useDrawer } from '/@/components/Drawer';
+  import { useDesign } from '/@/hooks/web/useDesign';
 
   export default defineComponent({
     name: 'SettingBtn',
     components: { SettingOutlined, SettingDrawer },
     setup() {
       const [register, { openDrawer }] = useDrawer();
+
+      const { prefixCls } = useDesign('setting-button');
       return {
+        prefixCls,
         register,
         openDrawer,
       };
@@ -25,9 +29,9 @@
 </script>
 <style lang="less">
   @import (reference) '../../../design/index.less';
-  @import './index.less';
+  @prefix-cls: ~'@{namespace}-setting-button';
 
-  .setting-button {
+  .@{prefix-cls} {
     position: absolute;
     top: 45%;
     right: 0;