Procházet zdrojové kódy

feat: added settingButtonPosition configuration close #275

vben před 4 roky
rodič
revize
da04913ef3

+ 6 - 0
CHANGELOG.zh_CN.md

@@ -1,3 +1,9 @@
+## Wip
+
+### ✨ Features
+
+- 新增 `settingButtonPosition`配置项,用于配置`设置`按钮位置
+
 ## 2.0.0 (2021-02-18)
 
 ## (破坏性更新) Breaking changes

+ 1 - 0
build/vite/plugin/compress.ts

@@ -1,5 +1,6 @@
 /**
  * Used to package and output gzip. Note that this does not work properly in Vite, the specific reason is still being investigated
+ * https://github.com/anncwb/vite-plugin-compression
  */
 import type { Plugin } from 'vite';
 

+ 1 - 0
build/vite/plugin/imagemin.ts

@@ -1,4 +1,5 @@
 // Image resource files used to compress the output of the production environment
+// https://github.com/anncwb/vite-plugin-imagemin
 
 import viteImagemin from 'vite-plugin-imagemin';
 

+ 4 - 2
build/vite/plugin/index.ts

@@ -4,7 +4,6 @@ import vue from '@vitejs/plugin-vue';
 import vueJsx from '@vitejs/plugin-vue-jsx';
 import legacy from '@vitejs/plugin-legacy';
 
-import windiCSS from 'vite-plugin-windicss';
 import PurgeIcons from 'vite-plugin-purge-icons';
 
 import { ViteEnv } from '../../utils';
@@ -16,6 +15,7 @@ import { configStyleImportPlugin } from './styleImport';
 import { configVisualizerConfig } from './visualizer';
 import { configThemePlugin } from './theme';
 import { configImageminPlugin } from './imagemin';
+import { configWindiCssPlugin } from './windicss';
 
 export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
   const { VITE_USE_IMAGEMIN, VITE_USE_MOCK, VITE_LEGACY, VITE_BUILD_COMPRESS } = viteEnv;
@@ -25,7 +25,6 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
     vue(),
     // have to
     vueJsx(),
-    ...windiCSS(),
   ];
 
   // @vitejs/plugin-legacy
@@ -34,6 +33,9 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
   // vite-plugin-html
   vitePlugins.push(configHtmlPlugin(viteEnv, isBuild));
 
+  // vite-plugin-windicss
+  vitePlugins.push(configWindiCssPlugin());
+
   // vite-plugin-mock
   VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild));
 

+ 12 - 0
build/vite/plugin/windicss.ts

@@ -0,0 +1,12 @@
+import windiCSS from 'vite-plugin-windicss';
+
+import type { Plugin } from 'vite';
+
+export function configWindiCssPlugin(): Plugin[] {
+  return windiCSS({
+    safelist: 'shadow shadow-xl',
+    preflight: {
+      enableAll: true,
+    },
+  });
+}

+ 1 - 1
package.json

@@ -107,7 +107,7 @@
     "vite-plugin-pwa": "^0.4.7",
     "vite-plugin-style-import": "^0.7.2",
     "vite-plugin-theme": "^0.4.3",
-    "vite-plugin-windicss": "0.2.2",
+    "vite-plugin-windicss": "0.3.3",
     "vue-eslint-parser": "^7.5.0",
     "yargs": "^16.2.0"
   },

+ 6 - 0
src/enums/appEnum.ts

@@ -22,6 +22,12 @@ export enum ThemeEnum {
   LIGHT = 'light',
 }
 
+export enum SettingButtonPositionEnum {
+  AUTO = 'auto',
+  HEADER = 'header',
+  FIXED = 'fixed',
+}
+
 /**
  * 权限模式
  */

+ 1 - 9
src/hooks/event/useKeyPress.ts

@@ -160,13 +160,5 @@ export function getTargetElement(
   if (!target) {
     return defaultElement;
   }
-
-  let targetElement: TargetElement | undefined | null;
-
-  if (isFunction(target)) {
-    targetElement = target();
-  } else {
-    targetElement = unref(target);
-  }
-  return targetElement;
+  return isFunction(target) ? target() : unref(target);
 }

+ 3 - 0
src/hooks/setting/useRootSetting.ts

@@ -16,6 +16,8 @@ const getPageLoading = computed(() => appStore.getPageLoading);
 
 const getOpenKeepAlive = computed(() => unref(getRootSetting).openKeepAlive);
 
+const getSettingButtonPosition = computed(() => unref(getRootSetting).settingButtonPosition);
+
 const getCanEmbedIFramePage = computed(() => unref(getRootSetting).canEmbedIFramePage);
 
 const getPermissionMode = computed(() => unref(getRootSetting).permissionMode);
@@ -58,6 +60,7 @@ export function useRootSetting() {
   return {
     setRootSetting,
 
+    getSettingButtonPosition,
     getFullContent,
     getColorWeak,
     getGrayMode,

+ 1 - 6
src/hooks/web/useFullScreen.ts

@@ -57,12 +57,7 @@ export function useFullscreen(
 
   async function toggleFullscreen(): Promise<void> {
     if (!unref(target)) return;
-
-    if (isFullscreen()) {
-      return exitFullscreen();
-    } else {
-      return enterFullscreen();
-    }
+    return isFullscreen() ? exitFullscreen() : enterFullscreen();
   }
 
   return {

+ 61 - 7
src/layouts/default/feature/index.vue

@@ -1,26 +1,80 @@
-<template>
-  <LayoutLockPage />
-  <BackTop v-if="getUseOpenBackTop" :target="getTarget" />
-</template>
 <script lang="ts">
-  import { defineComponent } from 'vue';
-  import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
+  import { defineComponent, computed, unref } from 'vue';
   import { BackTop } from 'ant-design-vue';
+
   import { useRootSetting } from '/@/hooks/setting/useRootSetting';
+  import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
+  import { useDesign } from '/@/hooks/web/useDesign';
+
+  import { SettingButtonPositionEnum } from '/@/enums/appEnum';
+  import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
 
   export default defineComponent({
     name: 'LayoutFeatures',
     components: {
       BackTop,
       LayoutLockPage: createAsyncComponent(() => import('/@/views/sys/lock/index.vue')),
+      SettingDrawer: createAsyncComponent(() => import('/@/layouts/default/setting/index.vue')),
     },
     setup() {
-      const { getUseOpenBackTop } = useRootSetting();
+      const {
+        getUseOpenBackTop,
+        getShowSettingButton,
+        getSettingButtonPosition,
+        getFullContent,
+      } = useRootSetting();
+
+      const { prefixCls } = useDesign('setting-drawer-fearure');
+      const { getShowHeader } = useHeaderSetting();
+
+      const getIsFixedSettingDrawer = computed(() => {
+        if (!unref(getShowSettingButton)) {
+          return false;
+        }
+        const settingButtonPosition = unref(getSettingButtonPosition);
+
+        if (settingButtonPosition === SettingButtonPositionEnum.AUTO) {
+          return !unref(getShowHeader) || unref(getFullContent);
+        }
+        return settingButtonPosition === SettingButtonPositionEnum.FIXED;
+      });
 
       return {
         getTarget: () => document.body,
         getUseOpenBackTop,
+        getIsFixedSettingDrawer,
+        prefixCls,
       };
     },
   });
 </script>
+
+<template>
+  <LayoutLockPage />
+  <BackTop v-if="getUseOpenBackTop" :target="getTarget" />
+  <SettingDrawer v-if="getIsFixedSettingDrawer" :class="prefixCls" />
+</template>
+
+<style lang="less">
+  @prefix-cls: ~'@{namespace}-setting-drawer-fearure';
+
+  .@{prefix-cls} {
+    position: absolute;
+    top: 45%;
+    right: 0;
+    z-index: 10;
+    display: flex;
+    padding: 10px;
+    color: @white;
+    cursor: pointer;
+    background: @primary-color;
+    border-radius: 6px 0 0 6px;
+    justify-content: center;
+    align-items: center;
+
+    svg {
+      width: 1em;
+      height: 1em;
+    }
+  }
+</style>

+ 3 - 3
src/layouts/default/header/components/user-dropdown/index.vue

@@ -22,7 +22,7 @@
           icon="ion:lock-closed-outline"
         />
         <MenuItem
-          key="loginOut"
+          key="logout"
           :text="t('layout.header.dropdownItemLoginOut')"
           icon="ion:power-outline"
         />
@@ -51,7 +51,7 @@
 
   import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
 
-  type MenuEvent = 'loginOut' | 'doc' | 'lock';
+  type MenuEvent = 'logout' | 'doc' | 'lock';
 
   export default defineComponent({
     name: 'UserDropdown',
@@ -93,7 +93,7 @@
 
       function handleMenuClick(e: { key: MenuEvent }) {
         switch (e.key) {
-          case 'loginOut':
+          case 'logout':
             handleLoginOut();
             break;
           case 'doc':

+ 21 - 2
src/layouts/default/header/index.vue

@@ -50,7 +50,7 @@
 
       <UserDropDown :theme="getHeaderTheme" />
 
-      <SettingDrawer v-if="getShowSettingButton" :class="`${prefixCls}-action__item`" />
+      <SettingDrawer v-if="getShowSetting" :class="`${prefixCls}-action__item`" />
     </div>
   </Header>
 </template>
@@ -72,6 +72,7 @@
   import { useLocaleSetting } from '/@/hooks/setting/useLocaleSetting';
 
   import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum';
+  import { SettingButtonPositionEnum } from '/@/enums/appEnum';
   import { AppLocalePicker } from '/@/components/Application';
 
   import { UserDropDown, LayoutBreadcrumb, FullScreen, Notify, ErrorAction } from './components';
@@ -112,7 +113,11 @@
         getIsMixSidebar,
       } = useMenuSetting();
       const { getShowLocale } = useLocaleSetting();
-      const { getUseErrorHandle, getShowSettingButton } = useRootSetting();
+      const {
+        getUseErrorHandle,
+        getShowSettingButton,
+        getSettingButtonPosition,
+      } = useRootSetting();
 
       const {
         getHeaderTheme,
@@ -122,6 +127,7 @@
         getShowContent,
         getShowBread,
         getShowHeaderLogo,
+        getShowHeader,
       } = useHeaderSetting();
 
       const { getIsMobile } = useAppInject();
@@ -138,6 +144,18 @@
         ];
       });
 
+      const getShowSetting = computed(() => {
+        if (!unref(getShowSettingButton)) {
+          return false;
+        }
+        const settingButtonPosition = unref(getSettingButtonPosition);
+
+        if (settingButtonPosition === SettingButtonPositionEnum.AUTO) {
+          return unref(getShowHeader);
+        }
+        return settingButtonPosition === SettingButtonPositionEnum.HEADER;
+      });
+
       const getLogoWidth = computed(() => {
         if (!unref(getIsMixMode) || unref(getIsMobile)) {
           return {};
@@ -175,6 +193,7 @@
         getLogoWidth,
         getIsMixSidebar,
         getShowSettingButton,
+        getShowSetting,
       };
     },
   });

+ 1 - 4
src/layouts/default/setting/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div @click="openDrawer" :class="prefixCls">
+  <div @click="openDrawer">
     <Icon icon="ion:settings-outline" />
     <SettingDrawer @register="register" />
   </div>
@@ -10,7 +10,6 @@
   import Icon from '/@/components/Icon';
 
   import { useDrawer } from '/@/components/Drawer';
-  import { useDesign } from '/@/hooks/web/useDesign';
 
   export default defineComponent({
     name: 'SettingButton',
@@ -18,9 +17,7 @@
     setup() {
       const [register, { openDrawer }] = useDrawer();
 
-      const { prefixCls } = useDesign('setting-button');
       return {
-        prefixCls,
         register,
         openDrawer,
       };

+ 2 - 2
src/locales/lang/en/sys/app.ts

@@ -1,5 +1,5 @@
 export default {
-  loginOutTip: 'Reminder',
-  loginOutMessage: 'Confirm to exit the system?',
+  logoutTip: 'Reminder',
+  logoutMessage: 'Confirm to exit the system?',
   menuLoading: 'Menu loading...',
 };

+ 2 - 2
src/locales/lang/zh_CN/sys/app.ts

@@ -1,5 +1,5 @@
 export default {
-  loginOutTip: '温馨提醒',
-  loginOutMessage: '是否确认退出系统?',
+  logoutTip: '温馨提醒',
+  logoutMessage: '是否确认退出系统?',
   menuLoading: '菜单加载中...',
 };

+ 10 - 1
src/settings/projectSetting.ts

@@ -2,7 +2,13 @@ import type { ProjectConfig } from '/@/types/config';
 
 import { MenuTypeEnum, MenuModeEnum, TriggerEnum, MixSidebarTriggerEnum } from '/@/enums/menuEnum';
 import { CacheTypeEnum } from '/@/enums/cacheEnum';
-import { ContentEnum, PermissionModeEnum, ThemeEnum, RouterTransitionEnum } from '/@/enums/appEnum';
+import {
+  ContentEnum,
+  PermissionModeEnum,
+  ThemeEnum,
+  RouterTransitionEnum,
+  SettingButtonPositionEnum,
+} from '/@/enums/appEnum';
 import { primaryColor, themeMode } from '../../build/config/themeConfig';
 
 // ! You need to clear the browser cache after the change
@@ -10,6 +16,9 @@ const setting: ProjectConfig = {
   // Whether to show the configuration button
   showSettingButton: true,
 
+  // `Settings` button position
+  settingButtonPosition: SettingButtonPositionEnum.AUTO,
+
   // Permission mode
   permissionMode: PermissionModeEnum.ROLE,
 

+ 4 - 4
src/store/modules/permission.ts

@@ -98,22 +98,22 @@ class Permission extends VuexModule {
         if (!roles) return true;
         return roleList.some((role) => roles.includes(role));
       });
-      //  如果确定不需要做后台动态权限,请将下面整个判断注释
+      //  If you are sure that you do not need to do background dynamic permissions, please comment the entire judgment below
     } else if (permissionMode === PermissionModeEnum.BACK) {
       createMessage.loading({
         content: t('sys.app.menuLoading'),
         duration: 1,
       });
-      // 这里获取后台路由菜单逻辑自行修改
+      // Here to get the background routing menu logic to modify by yourself
       const paramId = id || userStore.getUserInfoState.userId;
       if (!paramId) {
         throw new Error('paramId is undefined!');
       }
       let routeList = (await getMenuListById({ id: paramId })) as AppRouteRecordRaw[];
 
-      // 动态引入组件
+      // Dynamically introduce components
       routeList = transformObjToRoute(routeList);
-      //  后台路由转菜单结构
+      //  Background routing to menu structure
       const backMenuList = transformRouteToMenu(routeList);
 
       this.commitBackMenuListState(backMenuList);

+ 5 - 5
src/store/modules/user.ts

@@ -131,10 +131,10 @@ class User extends VuexModule {
   }
 
   /**
-   * @description: login out
+   * @description: logout
    */
   @Action
-  async loginOut(goLogin = false) {
+  async logout(goLogin = false) {
     goLogin && router.push(PageEnum.BASE_LOGIN);
   }
 
@@ -147,10 +147,10 @@ class User extends VuexModule {
     const { t } = useI18n();
     createConfirm({
       iconType: 'warning',
-      title: t('sys.app.loginOutTip'),
-      content: t('sys.app.loginOutMessage'),
+      title: t('sys.app.logoutTip'),
+      content: t('sys.app.logoutMessage'),
       onOk: async () => {
-        await this.loginOut(true);
+        await this.logout(true);
       },
     });
   }

+ 8 - 1
src/types/config.d.ts

@@ -1,5 +1,11 @@
 import { MenuTypeEnum, MenuModeEnum, TriggerEnum, MixSidebarTriggerEnum } from '/@/enums/menuEnum';
-import { ContentEnum, PermissionModeEnum, ThemeEnum, RouterTransitionEnum } from '/@/enums/appEnum';
+import {
+  ContentEnum,
+  PermissionModeEnum,
+  ThemeEnum,
+  RouterTransitionEnum,
+  SettingButtonPositionEnum,
+} from '/@/enums/appEnum';
 import { CacheTypeEnum } from '/@/enums/cacheEnum';
 import type { LocaleType } from '/@/locales/types';
 import { ThemeMode } from '../../build/config/lessModifyVars';
@@ -88,6 +94,7 @@ export interface ProjectConfig {
 
   // 是否显示配置按钮
   showSettingButton: boolean;
+  settingButtonPosition: SettingButtonPositionEnum;
   // 权限模式
   permissionMode: PermissionModeEnum;
   // 网站灰色模式,用于可能悼念的日期开启

+ 4 - 4
src/utils/helper/treeHelper.ts

@@ -100,9 +100,9 @@ export function findPath<T = any>(
 
 export function findPathAll(tree: any, func: Fn, config: Partial<TreeHelperConfig> = {}) {
   config = getConfig(config);
-  const path = [];
+  const path: any[] = [];
   const list = [...tree];
-  const result = [];
+  const result: any[] = [];
   const visitedSet = new Set(),
     { children } = config;
   while (list.length) {
@@ -153,14 +153,14 @@ export function forEach<T = any>(
 }
 
 /**
- * @description: 提取tree指定结构
+ * @description: Extract tree specified structure
  */
 export function treeMap<T = any>(treeData: T[], opt: { children?: string; conversion: Fn }): T[] {
   return treeData.map((item) => treeMapEach(item, opt));
 }
 
 /**
- * @description: 提取tree指定结构
+ * @description: Extract tree specified structure
  */
 export function treeMapEach(
   data: any,

+ 1 - 1
src/utils/http/axios/checkStatus.ts

@@ -15,7 +15,7 @@ export function checkStatus(status: number, msg: string): void {
     // Return to the current page after successful login. This step needs to be operated on the login page.
     case 401:
       error(t('sys.api.errMsg401'));
-      userStore.loginOut(true);
+      userStore.logout(true);
       break;
     case 403:
       error(t('sys.api.errMsg403'));

+ 3 - 9
src/utils/index.ts

@@ -23,17 +23,11 @@ export function getPopupContainer(node?: HTMLElement): HTMLElement {
  */
 export function setObjToUrlParams(baseUrl: string, obj: any): string {
   let parameters = '';
-  let url = '';
   for (const key in obj) {
     parameters += key + '=' + encodeURIComponent(obj[key]) + '&';
   }
   parameters = parameters.replace(/&$/, '');
-  if (/\?$/.test(baseUrl)) {
-    url = baseUrl + parameters;
-  } else {
-    url = baseUrl.replace(/\/?$/, '?') + parameters;
-  }
-  return url;
+  return /\?$/.test(baseUrl) ? baseUrl + parameters : baseUrl.replace(/\/?$/, '?') + parameters;
 }
 
 export function deepMerge<T = any>(src: any = {}, target: any = {}): T {
@@ -45,7 +39,7 @@ export function deepMerge<T = any>(src: any = {}, target: any = {}): T {
 }
 
 /**
- * @description: 根据数组中某个对象值去重
+ * @description: Deduplication according to the value of an object in the array
  */
 export function unique<T = any>(arr: T[], key: string): T[] {
   const map = new Map();
@@ -56,7 +50,7 @@ export function unique<T = any>(arr: T[], key: string): T[] {
 }
 
 /**
- * @description: es6数组去重复
+ * @description: es6 array to repeat
  */
 export function es6Unique<T>(arr: T[]): T[] {
   return Array.from(new Set(arr));

+ 2 - 1
src/views/sys/lock/LockPage.vue

@@ -115,7 +115,7 @@
       }
 
       function goLogin() {
-        userStore.loginOut(true);
+        userStore.logout(true);
         lockStore.resetLockInfo();
       }
 
@@ -287,6 +287,7 @@
 
         &-img {
           width: 70px;
+          margin: 0 auto;
           border-radius: 50%;
         }
 

+ 5 - 4
yarn.lock

@@ -9370,12 +9370,13 @@ vite-plugin-theme@^0.4.3:
     es-module-lexer "^0.3.26"
     tinycolor2 "^1.4.2"
 
-vite-plugin-windicss@0.2.2:
-  version "0.2.2"
-  resolved "https://registry.npmjs.org/vite-plugin-windicss/-/vite-plugin-windicss-0.2.2.tgz#2abf1533153f5dc214a9e1a06fb58274e5892c19"
-  integrity sha512-P+iyrcuLjLfjiYP+bBisfKbg9bmeQMUBpjsTFJ9kWWX2fyqo968CHmS3euz+MzRcK5ZECccpOxx60ZXzc12VAw==
+vite-plugin-windicss@0.3.3:
+  version "0.3.3"
+  resolved "https://registry.npmjs.org/vite-plugin-windicss/-/vite-plugin-windicss-0.3.3.tgz#9ff2fc485dd5cf1717cde6eed462fd9893ea9eab"
+  integrity sha512-2gm0sTexkmvx9PR4NP1UESly8hX2souOruQztu1qghfw4M3tlUUvwneRpJG5HVJCKCctmAgYRcW4e04TE5R1fA==
   dependencies:
     fast-glob "^3.2.5"
+    micromatch "^4.0.2"
     windicss "^2.1.11"
 
 vite@2.0.1: