From ba068ba1df797627e88dcf61af3ea13d0c2929ab Mon Sep 17 00:00:00 2001 From: vben <anncwb@126.com> Date: Mon, 23 Nov 2020 23:24:13 +0800 Subject: [PATCH] wip: refactor layout --- .eslintrc.js | 4 ++-- src/components/Application/index.ts | 7 ++++--- src/components/Application/src/AppFooterToolbar.vue | 60 ------------------------------------------------------------ src/components/Application/src/AppLocalPicker.vue | 31 ++++++++++++++++++++++++++----- src/components/Application/src/AppLogo.vue | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/components/Application/src/AppPageFooter.vue | 46 ++++++++++++++++++++++++++++++++++++++++++++++ src/components/Authority/src/index.vue | 16 ++++++---------- src/components/Description/src/index.tsx | 2 +- src/components/Dropdown/src/Dropdown.tsx | 2 +- src/components/Excel/src/ImportExcel.vue | 2 +- src/components/Form/src/componentMap.ts | 2 +- src/components/Form/src/hooks/useLabelWidth.ts | 2 +- src/components/Form/src/types/formItem.ts | 4 ++-- src/components/Menu/index.ts | 6 +++++- src/components/Menu/src/BasicMenu.tsx | 28 +++++++++++++++------------- src/components/Menu/src/MenuContent.tsx | 6 +++--- src/components/Menu/src/hooks/useOpenKeys.ts | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/components/Menu/src/hooks/useSearchInput.ts | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/components/Menu/src/props.ts | 10 +++++----- src/components/Menu/src/types.d.ts | 26 -------------------------- src/components/Menu/src/types.ts | 26 ++++++++++++++++++++++++++ src/components/Menu/src/useOpenKeys.ts | 65 ----------------------------------------------------------------- src/components/Menu/src/useSearchInput.ts | 58 ---------------------------------------------------------- src/components/Modal/src/props.ts | 2 +- src/components/Table/src/types/column.ts | 2 +- src/components/Table/src/types/pagination.ts | 2 +- src/components/Tinymce/src/plugins.ts | 2 +- src/components/registerGlobComp.ts | 2 ++ src/components/types.ts | 3 +++ src/design/var/index.less | 2 +- src/enums/pageEnum.ts | 2 ++ src/hooks/core/useDebouncedRef.ts | 20 ++++++++++++++++++++ src/hooks/setting/index.ts | 43 +++++++++++++++++++++++++++++++++++++++++++ src/hooks/setting/useHeaderSetting.ts | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/hooks/setting/useLocaleSetting.ts | 28 ++++++++++++++++++++++++++++ src/hooks/setting/useMenuSetting.ts | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/hooks/setting/useMultipleTabSetting.ts | 25 +++++++++++++++++++++++++ src/hooks/setting/useRootSetting.ts | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/hooks/web/useLocale.ts | 4 ++-- src/hooks/web/usePage.ts | 1 + src/hooks/web/useSideBar.ts | 20 -------------------- src/layouts/default/LayoutSideBar.tsx | 214 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- src/layouts/default/LayoutTrigger.tsx | 30 ++++++++++++++---------------- src/layouts/default/header/LayoutBreadcrumb.tsx | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------------- src/layouts/default/header/LayoutHeader.tsx | 335 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ src/layouts/default/header/LockActionItem.less | 1 - src/layouts/default/header/LockActionItem.tsx | 36 ++++++++++++++---------------------- src/layouts/default/header/UserDropdown.tsx | 80 +++++++++++++++++++++++++++++++++++++++++++++++--------------------------------- src/layouts/default/header/notice/NoticeActionItem.vue | 8 ++++++-- src/layouts/default/header/notice/NoticeList.vue | 36 ++++++++++++------------------------ src/layouts/default/header/notice/data.ts | 8 -------- src/layouts/default/index.less | 43 ------------------------------------------- src/layouts/default/index.tsx | 8 ++++---- src/layouts/default/menu/LayoutMenu.tsx | 243 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- src/layouts/default/menu/index.less | 12 ------------ src/layouts/default/menu/useLayoutMenu.ts | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/layouts/default/multitabs/index.tsx | 2 +- src/layouts/default/sider/LayoutSideBar.tsx | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/layouts/default/sider/index.less | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/layouts/default/sider/useLayoutSider.tsx | 163 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/layouts/iframe/useFrameKeepAlive.ts | 38 +++++++++++++++++++------------------- src/layouts/logo/index.vue | 105 --------------------------------------------------------------------------------------------------------- src/layouts/page/index.tsx | 75 +++++++++++++++++++++++++++++++++++++++------------------------------------ src/layouts/page/useTransition.ts | 12 +++++------- src/main.ts | 2 +- src/router/guard/index.ts | 2 +- src/router/menus/index.ts | 2 +- src/settings/projectSetting.ts | 13 ++++++++++--- src/settings/use/index.ts | 43 ------------------------------------------- src/settings/use/useLocaleSetting.ts | 36 ------------------------------------ src/setup/error-handle/index.ts | 2 +- src/setup/i18n/index.ts | 2 +- src/store/modules/error.ts | 2 +- src/store/modules/menu.ts | 67 ------------------------------------------------------------------- src/store/modules/permission.ts | 5 ----- src/types/config.d.ts | 1 - src/utils/file/download.ts | 3 ++- src/utils/helper/envHelper.ts | 2 +- src/utils/http/axios/index.ts | 2 +- src/utils/index.ts | 14 ++++++++++++++ src/views/demo/feat/icon/index.vue | 4 +++- src/views/demo/page/form/high/index.vue | 8 ++++---- src/views/sys/login/Login.vue | 2 +- vite.config.ts | 6 +++--- 84 files changed, 1592 insertions(+), 1395 deletions(-) delete mode 100644 src/components/Application/src/AppFooterToolbar.vue create mode 100644 src/components/Application/src/AppLogo.vue create mode 100644 src/components/Application/src/AppPageFooter.vue create mode 100644 src/components/Menu/src/hooks/useOpenKeys.ts create mode 100644 src/components/Menu/src/hooks/useSearchInput.ts delete mode 100644 src/components/Menu/src/types.d.ts create mode 100644 src/components/Menu/src/types.ts delete mode 100644 src/components/Menu/src/useOpenKeys.ts delete mode 100644 src/components/Menu/src/useSearchInput.ts create mode 100644 src/components/types.ts create mode 100644 src/hooks/core/useDebouncedRef.ts create mode 100644 src/hooks/setting/index.ts create mode 100644 src/hooks/setting/useHeaderSetting.ts create mode 100644 src/hooks/setting/useLocaleSetting.ts create mode 100644 src/hooks/setting/useMenuSetting.ts create mode 100644 src/hooks/setting/useMultipleTabSetting.ts create mode 100644 src/hooks/setting/useRootSetting.ts delete mode 100644 src/hooks/web/useSideBar.ts delete mode 100644 src/layouts/default/LayoutSideBar.tsx create mode 100644 src/layouts/default/menu/useLayoutMenu.ts create mode 100644 src/layouts/default/sider/LayoutSideBar.tsx create mode 100644 src/layouts/default/sider/index.less create mode 100644 src/layouts/default/sider/useLayoutSider.tsx delete mode 100644 src/layouts/logo/index.vue delete mode 100644 src/settings/use/index.ts delete mode 100644 src/settings/use/useLocaleSetting.ts delete mode 100644 src/store/modules/menu.ts diff --git a/.eslintrc.js b/.eslintrc.js index 5487446..d642a97 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -23,7 +23,7 @@ module.exports = { '@typescript-eslint/no-empty-function': 'off', 'vue/custom-event-name-casing': 'off', 'no-use-before-define': 'off', - // 'no-use-before-define': [ + // 'no-setting-before-define': [ // 'error', // { // functions: false, @@ -31,7 +31,7 @@ module.exports = { // }, // ], '@typescript-eslint/no-use-before-define': 'off', - // '@typescript-eslint/no-use-before-define': [ + // '@typescript-eslint/no-setting-before-define': [ // 'error', // { // functions: false, diff --git a/src/components/Application/index.ts b/src/components/Application/index.ts index b48b927..ee01d07 100644 --- a/src/components/Application/index.ts +++ b/src/components/Application/index.ts @@ -1,7 +1,8 @@ import AppLocalPicker from './src/AppLocalPicker.vue'; -import AppFooterToolbar from './src/AppFooterToolbar.vue'; +import AppPageFooter from './src/AppPageFooter.vue'; +import AppLogo from './src/AppLogo.vue'; import { withInstall } from '../util'; -export { AppLocalPicker, AppFooterToolbar }; +export { AppLocalPicker, AppPageFooter, AppLogo }; -export default withInstall(AppLocalPicker, AppFooterToolbar); +export default withInstall(AppLocalPicker, AppPageFooter, AppLogo); diff --git a/src/components/Application/src/AppFooterToolbar.vue b/src/components/Application/src/AppFooterToolbar.vue deleted file mode 100644 index b0550ba..0000000 --- a/src/components/Application/src/AppFooterToolbar.vue +++ /dev/null @@ -1,60 +0,0 @@ -<template> - <div class="app-footer" :style="{ width: getWidth }"> - <div class="app-footer__left"> - <slot name="left" /> - </div> - <div class="app-footer__right"> - <slot name="right" /> - </div> - </div> -</template> -<script lang="ts"> - import { defineComponent, computed, unref } from 'vue'; - - import { SIDE_BAR_MINI_WIDTH, SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appEnum'; - - import { appStore } from '/@/store/modules/app'; - import { menuStore } from '/@/store/modules/menu'; - - export default defineComponent({ - name: 'AppFooterToolbar', - setup() { - const getMiniWidth = computed(() => { - const { - menuSetting: { collapsedShowTitle }, - } = appStore.getProjectConfig; - return collapsedShowTitle ? SIDE_BAR_SHOW_TIT_MINI_WIDTH : SIDE_BAR_MINI_WIDTH; - }); - - const getWidth = computed(() => { - const { getCollapsedState, getMenuWidthState } = menuStore; - const width = getCollapsedState ? unref(getMiniWidth) : getMenuWidthState; - return `calc(100% - ${width}px)`; - }); - - return { getWidth }; - }, - }); -</script> -<style lang="less" scoped> - .app-footer { - position: fixed; - right: 0; - bottom: 0; - z-index: 99; - display: flex; - width: 100%; - align-items: center; - padding: 0 24px; - line-height: 44px; - background: #fff; - border-top: 1px solid #f0f0f0; - box-shadow: 0 -6px 16px -8px rgba(0, 0, 0, 0.08), 0 -9px 28px 0 rgba(0, 0, 0, 0.05), - 0 -12px 48px 16px rgba(0, 0, 0, 0.03); - transition: width 0.3s; - - &__left { - flex: 1 1; - } - } -</style> diff --git a/src/components/Application/src/AppLocalPicker.vue b/src/components/Application/src/AppLocalPicker.vue index 544e888..b30472e 100644 --- a/src/components/Application/src/AppLocalPicker.vue +++ b/src/components/Application/src/AppLocalPicker.vue @@ -5,29 +5,44 @@ :selectedKeys="selectedKeys" @menuEvent="handleMenuEvent" > - <GlobalOutlined class="app-locale" /> + <span class="app-local-picker"> + <GlobalOutlined class="app-local-picker__icon" /> + <span v-if="showText">{{ getLangText }}</span> + </span> </Dropdown> </template> <script lang="ts"> - import { defineComponent, ref, watchEffect, unref } from 'vue'; + import { defineComponent, ref, watchEffect, unref, computed } from 'vue'; import { Dropdown, DropMenu } from '/@/components/Dropdown'; import { GlobalOutlined } from '@ant-design/icons-vue'; import { useLocale } from '/@/hooks/web/useLocale'; - import { useLocaleSetting } from '/@/settings/use/useLocaleSetting'; + import { useLocaleSetting } from '/@/hooks/setting/useLocaleSetting'; import { LocaleType } from '/@/locales/types'; export default defineComponent({ name: 'AppLocalPicker', components: { GlobalOutlined, Dropdown }, + props: { + showText: { + type: Boolean, + default: true, + }, + }, setup() { const { localeList } = useLocaleSetting(); const selectedKeys = ref<string[]>([]); const { changeLocale, getLang } = useLocale(); + const getLangText = computed(() => { + const key = selectedKeys.value[0]; + if (!key) return ''; + return localeList.find((item) => item.event === key)?.text; + }); + watchEffect(() => { selectedKeys.value = [unref(getLang)]; }); @@ -41,13 +56,19 @@ toggleLocale(menu.event as string); } - return { localeList, handleMenuEvent, selectedKeys }; + return { localeList, handleMenuEvent, selectedKeys, getLangText }; }, }); </script> <style lang="less" scoped> - .app-locale { + .app-local-picker { + display: flex; + align-items: center; cursor: pointer; + + &__icon { + margin-right: 4px; + } } </style> diff --git a/src/components/Application/src/AppLogo.vue b/src/components/Application/src/AppLogo.vue new file mode 100644 index 0000000..990b683 --- /dev/null +++ b/src/components/Application/src/AppLogo.vue @@ -0,0 +1,72 @@ +<template> + <div class="app-logo anticon" :class="theme" @click="handleGoHome"> + <img src="/@/assets/images/logo.png" /> + <div class="app-logo__title ml-2 ellipsis">{{ globSetting.title }}</div> + </div> +</template> +<script lang="ts"> + import type { PropType } from 'vue'; + import { defineComponent } from 'vue'; + + import { useGlobSetting } from '/@/hooks/setting'; + import { useGo } from '/@/hooks/web/usePage'; + + import { PageEnum } from '/@/enums/pageEnum'; + + export default defineComponent({ + name: 'AppLogo', + props: { + /** + * The theme of the current parent component + */ + theme: { + type: String as PropType<string>, + }, + }, + setup() { + const globSetting = useGlobSetting(); + const go = useGo(); + + function handleGoHome(): void { + go(PageEnum.BASE_HOME); + } + + return { + handleGoHome, + globSetting, + }; + }, + }); +</script> +<style lang="less" scoped> + @import (reference) '../../../design/index.less'; + + .app-logo { + display: flex; + align-items: center; + padding-left: 16px; + cursor: pointer; + + &.light { + border-bottom: 1px solid @border-color-base; + } + + &.light &__title { + color: @primary-color; + } + + &.dark &__title { + color: @white; + } + + &__title { + font-size: 18px; + font-weight: 700; + opacity: 0; + transition: all 0.5s; + .respond-to(medium,{ + opacity: 1; + }); + } + } +</style> diff --git a/src/components/Application/src/AppPageFooter.vue b/src/components/Application/src/AppPageFooter.vue new file mode 100644 index 0000000..4c74153 --- /dev/null +++ b/src/components/Application/src/AppPageFooter.vue @@ -0,0 +1,46 @@ +<template> + <div class="app-footer" :style="{ width: getCalcContentWidth }"> + <div class="app-footer__left"> + <slot name="left" /> + </div> + <div class="app-footer__right"> + <slot name="right" /> + </div> + </div> +</template> +<script lang="ts"> + import { defineComponent } from 'vue'; + + import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; + + export default defineComponent({ + name: 'AppFooterToolbar', + setup() { + const { getCalcContentWidth } = useMenuSetting(); + + return { getCalcContentWidth }; + }, + }); +</script> +<style lang="less" scoped> + .app-footer { + position: fixed; + right: 0; + bottom: 0; + z-index: 99; + display: flex; + width: 100%; + align-items: center; + padding: 0 24px; + line-height: 44px; + background: #fff; + border-top: 1px solid #f0f0f0; + box-shadow: 0 -6px 16px -8px rgba(0, 0, 0, 0.08), 0 -9px 28px 0 rgba(0, 0, 0, 0.05), + 0 -12px 48px 16px rgba(0, 0, 0, 0.03); + transition: width 0.4s; + + &__left { + flex: 1 1; + } + } +</style> diff --git a/src/components/Authority/src/index.vue b/src/components/Authority/src/index.vue index f394f99..7410db5 100644 --- a/src/components/Authority/src/index.vue +++ b/src/components/Authority/src/index.vue @@ -1,16 +1,15 @@ <!-- - * @Author: Vben - * @Description:Access control component for fine-grained access control. + Access control component for fine-grained access control. --> <script lang="ts"> import type { PropType } from 'vue'; - import { defineComponent, computed, unref } from 'vue'; + import { defineComponent, unref } from 'vue'; import { PermissionModeEnum } from '/@/enums/appEnum'; import { RoleEnum } from '/@/enums/roleEnum'; + import { useRootSetting } from '/@/hooks/setting/useRootSetting'; import { usePermission } from '/@/hooks/web/usePermission'; - import { appStore } from '/@/store/modules/app'; import { getSlot } from '/@/utils/helper/tsxHelper'; @@ -29,9 +28,8 @@ }, }, setup(props, { slots }) { - const getModeRef = computed(() => { - return appStore.getProjectConfig.permissionMode; - }); + const { getPermissionMode } = useRootSetting(); + const { hasPermission } = usePermission(); /** * Render role button @@ -41,7 +39,6 @@ if (!value) { return getSlot(slots); } - const { hasPermission } = usePermission(); return hasPermission(value) ? getSlot(slots) : null; } @@ -52,12 +49,11 @@ if (!value) { return getSlot(slots); } - const { hasPermission } = usePermission(); return hasPermission(value) ? getSlot(slots) : null; } return () => { - const mode = unref(getModeRef); + const mode = unref(getPermissionMode); // Role-based value control if (mode === PermissionModeEnum.ROLE) { return renderRoleAuth(); diff --git a/src/components/Description/src/index.tsx b/src/components/Description/src/index.tsx index 697c575..3fe92c7 100644 --- a/src/components/Description/src/index.tsx +++ b/src/components/Description/src/index.tsx @@ -33,7 +33,7 @@ export default defineComponent({ }); /** - * @description: Whether to use title + * @description: Whether to setting title */ const useWrapper = computed(() => { return !!unref(getMergeProps).title; diff --git a/src/components/Dropdown/src/Dropdown.tsx b/src/components/Dropdown/src/Dropdown.tsx index 9bea06b..76c52f1 100644 --- a/src/components/Dropdown/src/Dropdown.tsx +++ b/src/components/Dropdown/src/Dropdown.tsx @@ -15,7 +15,7 @@ export default defineComponent({ const getMenuList = computed(() => props.dropMenuList); function handleClickMenu({ key }: any) { - const menu = unref(getMenuList).find((item) => item.event === key); + const menu = unref(getMenuList).find((item) => `${item.event}` === `${key}`); emit('menuEvent', menu); } diff --git a/src/components/Excel/src/ImportExcel.vue b/src/components/Excel/src/ImportExcel.vue index f31bec0..e335306 100644 --- a/src/components/Excel/src/ImportExcel.vue +++ b/src/components/Excel/src/ImportExcel.vue @@ -104,7 +104,7 @@ */ function handleInputClick(e: Event) { const files = e && (e.target as HTMLInputElement).files; - const rawFile = files && files[0]; // only use files[0] + const rawFile = files && files[0]; // only setting files[0] if (!rawFile) return; upload(rawFile); } diff --git a/src/components/Form/src/componentMap.ts b/src/components/Form/src/componentMap.ts index be2cee1..b86e7b3 100644 --- a/src/components/Form/src/componentMap.ts +++ b/src/components/Form/src/componentMap.ts @@ -2,7 +2,7 @@ import { Component } from 'vue'; import type { ComponentType } from './types/index'; /** - * Component list, register here to use it in the form + * Component list, register here to setting it in the form */ import { Input, diff --git a/src/components/Form/src/hooks/useLabelWidth.ts b/src/components/Form/src/hooks/useLabelWidth.ts index 3e29a56..522f44d 100644 --- a/src/components/Form/src/hooks/useLabelWidth.ts +++ b/src/components/Form/src/hooks/useLabelWidth.ts @@ -31,7 +31,7 @@ export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref< wrapperCol: globWrapperCol, } = unref(propsRef) as any; - // If labelWidth is set globally, all items use + // If labelWidth is set globally, all items setting if ((!globalLabelWidth && !labelWidth && !globalLabelCol) || disabledLabelWidth) { return { labelCol, wrapperCol }; } diff --git a/src/components/Form/src/types/formItem.ts b/src/components/Form/src/types/formItem.ts index ba050a6..3dcfa41 100644 --- a/src/components/Form/src/types/formItem.ts +++ b/src/components/Form/src/types/formItem.ts @@ -68,7 +68,7 @@ export interface FormItem { */ labelAlign?: 'left' | 'right'; /** - * a key of model. In the use of validate and resetFields method, the attribute is required + * a key of model. In the setting of validate and resetFields method, the attribute is required */ name?: NamePath; /** @@ -76,7 +76,7 @@ export interface FormItem { */ rules?: object | object[]; /** - * Whether to automatically associate form fields. In most cases, you can use automatic association. + * Whether to automatically associate form fields. In most cases, you can setting automatic association. * If the conditions for automatic association are not met, you can manually associate them. See the notes below. */ autoLink?: boolean; diff --git a/src/components/Menu/index.ts b/src/components/Menu/index.ts index 5471c68..9c380ee 100644 --- a/src/components/Menu/index.ts +++ b/src/components/Menu/index.ts @@ -1 +1,5 @@ -export { default as BasicMenu } from './src/BasicMenu'; +import BasicMenu from './src/BasicMenu'; +import { withInstall } from '../util'; + +export default withInstall(BasicMenu); +export { BasicMenu }; diff --git a/src/components/Menu/src/BasicMenu.tsx b/src/components/Menu/src/BasicMenu.tsx index 7fd39e4..07a3402 100644 --- a/src/components/Menu/src/BasicMenu.tsx +++ b/src/components/Menu/src/BasicMenu.tsx @@ -1,3 +1,5 @@ +import './index.less'; + import type { MenuState } from './types'; import type { Menu as MenuType } from '/@/router/types'; @@ -9,11 +11,10 @@ import MenuContent from './MenuContent'; import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum'; import { ThemeEnum } from '/@/enums/appEnum'; -import { menuStore } from '/@/store/modules/menu'; import { appStore } from '/@/store/modules/app'; -import { useSearchInput } from './useSearchInput'; -import { useOpenKeys } from './useOpenKeys'; +import { useSearchInput } from './hooks/useSearchInput'; +import { useOpenKeys } from './hooks/useOpenKeys'; import { useRouter } from 'vue-router'; import { isFunction } from '/@/utils/is'; @@ -23,7 +24,7 @@ import { menuHasChildren } from './helper'; import { getCurrentParentPath } from '/@/router/menus'; import { basicProps } from './props'; -import './index.less'; +import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; export default defineComponent({ name: 'BasicMenu', props: basicProps, @@ -39,6 +40,8 @@ export default defineComponent({ selectedKeys: [], collapsedOpenKeys: [], }); + + const { getCollapsed } = useMenuSetting(); const { currentRoute } = useRouter(); const { items, flatItems, isAppMenu, mode, accordion } = toRefs(props); @@ -61,7 +64,7 @@ export default defineComponent({ const getOpenKeys = computed(() => { if (props.isAppMenu) { - return menuStore.getCollapsedState ? menuState.collapsedOpenKeys : menuState.openKeys; + return unref(getCollapsed) ? menuState.collapsedOpenKeys : menuState.openKeys; } return menuState.openKeys; }); @@ -95,20 +98,20 @@ export default defineComponent({ cls.push('basic-menu__sidebar-hor'); } - if (!props.isTop && props.isAppMenu && appStore.getProjectConfig.menuSetting.split) { + if (!props.isHorizontal && props.isAppMenu && appStore.getProjectConfig.menuSetting.split) { cls.push('basic-menu__second'); } return cls; }); - const showTitle = computed(() => props.collapsedShowTitle && menuStore.getCollapsedState); + const showTitle = computed(() => props.collapsedShowTitle && unref(getCollapsed)); watch( () => currentRoute.value.name, (name: string) => { if (name === 'Redirect') return; handleMenuChange(); - props.isTop && appStore.getProjectConfig.menuSetting.split && getParentPath(); + props.isHorizontal && appStore.getProjectConfig.menuSetting.split && getParentPath(); } ); @@ -180,7 +183,7 @@ export default defineComponent({ <MenuContent item={menu} level={index} - isTop={props.isTop} + isHorizontal={props.isHorizontal} showTitle={unref(showTitle)} searchValue={menuState.searchValue} />, @@ -196,7 +199,7 @@ export default defineComponent({ showTitle={unref(showTitle)} item={menu} level={index} - isTop={props.isTop} + isHorizontal={props.isHorizontal} searchValue={menuState.searchValue} />, ], @@ -214,7 +217,7 @@ export default defineComponent({ const inlineCollapsedObj = isInline ? props.isAppMenu ? { - inlineCollapsed: menuStore.getCollapsedState, + inlineCollapsed: unref(getCollapsed), } : { inlineCollapsed: props.inlineCollapsed } : {}; @@ -246,7 +249,6 @@ export default defineComponent({ }); return () => { - const { getCollapsedState } = menuStore; const { mode } = props; return mode === MenuModeEnum.HORIZONTAL ? ( renderMenu() @@ -258,7 +260,7 @@ export default defineComponent({ theme={props.theme as ThemeEnum} onChange={handleInputChange} onClick={handleInputClick} - collapsed={getCollapsedState} + collapsed={unref(getCollapsed)} /> <section style={unref(getMenuWrapStyle)} class="basic-menu__content"> {renderMenu()} diff --git a/src/components/Menu/src/MenuContent.tsx b/src/components/Menu/src/MenuContent.tsx index 22d10a2..29fe815 100644 --- a/src/components/Menu/src/MenuContent.tsx +++ b/src/components/Menu/src/MenuContent.tsx @@ -26,7 +26,7 @@ export default defineComponent({ type: Number as PropType<number>, default: 0, }, - isTop: { + isHorizontal: { type: Boolean as PropType<boolean>, default: true, }, @@ -40,8 +40,8 @@ export default defineComponent({ } function renderTag() { - const { item, showTitle, isTop } = props; - if (!item || showTitle || isTop) return null; + const { item, showTitle, isHorizontal } = props; + if (!item || showTitle || isHorizontal) return null; const { tag } = item; if (!tag) return null; diff --git a/src/components/Menu/src/hooks/useOpenKeys.ts b/src/components/Menu/src/hooks/useOpenKeys.ts new file mode 100644 index 0000000..272ab48 --- /dev/null +++ b/src/components/Menu/src/hooks/useOpenKeys.ts @@ -0,0 +1,66 @@ +import { MenuModeEnum } from '/@/enums/menuEnum'; +import type { Menu as MenuType } from '/@/router/types'; +import type { MenuState } from '../types'; +import type { Ref } from 'vue'; + +import { unref } from 'vue'; +import { getAllParentPath } from '/@/utils/helper/menuHelper'; +import { es6Unique } from '/@/utils'; +import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; + +export function useOpenKeys( + menuState: MenuState, + menus: Ref<MenuType[]>, + flatMenusRef: Ref<MenuType[]>, + isAppMenu: Ref<boolean>, + mode: Ref<MenuModeEnum>, + accordion: Ref<boolean> +) { + const { getCollapsed } = useMenuSetting(); + /** + * @description:设置展开 + */ + function setOpenKeys(menu: MenuType) { + const flatMenus = unref(flatMenusRef); + if (!unref(accordion)) { + menuState.openKeys = es6Unique([ + ...menuState.openKeys, + ...getAllParentPath(flatMenus, menu.path), + ]); + } else { + menuState.openKeys = getAllParentPath(flatMenus, menu.path); + } + } + + /** + * @description: 重置值 + */ + function resetKeys() { + menuState.selectedKeys = []; + menuState.openKeys = []; + } + + function handleOpenChange(openKeys: string[]) { + if (unref(mode) === MenuModeEnum.HORIZONTAL || !unref(accordion)) { + menuState.openKeys = openKeys; + } else { + const rootSubMenuKeys: string[] = []; + for (const { children, path } of unref(menus)) { + if (children && children.length > 0) { + rootSubMenuKeys.push(path); + } + } + if (!unref(getCollapsed) || !unref(isAppMenu)) { + const latestOpenKey = openKeys.find((key) => menuState.openKeys.indexOf(key) === -1); + if (rootSubMenuKeys.indexOf(latestOpenKey as string) === -1) { + menuState.openKeys = openKeys; + } else { + menuState.openKeys = latestOpenKey ? [latestOpenKey] : []; + } + } else { + menuState.collapsedOpenKeys = openKeys; + } + } + } + return { setOpenKeys, resetKeys, handleOpenChange }; +} diff --git a/src/components/Menu/src/hooks/useSearchInput.ts b/src/components/Menu/src/hooks/useSearchInput.ts new file mode 100644 index 0000000..837a6d0 --- /dev/null +++ b/src/components/Menu/src/hooks/useSearchInput.ts @@ -0,0 +1,58 @@ +import type { Menu as MenuType } from '/@/router/types'; +import type { MenuState } from '../types'; +import type { Ref } from 'vue'; + +import { isString } from '/@/utils/is'; +import { unref } from 'vue'; +import { es6Unique } from '/@/utils'; +import { getAllParentPath } from '/@/utils/helper/menuHelper'; + +interface UseSearchInputOptions { + menuState: MenuState; + flatMenusRef: Ref<MenuType[]>; + emit: EmitType; + handleMenuChange: Fn; +} +export function useSearchInput({ + menuState, + flatMenusRef, + handleMenuChange, + emit, +}: UseSearchInputOptions) { + /** + * @description: 输入框搜索 + */ + function handleInputChange(value?: string): void { + if (!isString(value)) { + value = (value as any).target.value; + } + if (!value) { + handleMenuChange && handleMenuChange(); + } + + menuState.searchValue = value || ''; + if (!value) { + menuState.openKeys = []; + return; + } + + const flatMenus = unref(flatMenusRef); + let openKeys: string[] = []; + for (const menu of flatMenus) { + const { name, path } = menu; + if (!name.includes(value)) { + continue; + } + openKeys = openKeys.concat(getAllParentPath(flatMenus, path)); + } + openKeys = es6Unique(openKeys); + menuState.openKeys = openKeys; + } + + // 搜索框点击 + function handleInputClick(e: any): void { + emit('clickSearchInput', e); + } + + return { handleInputChange, handleInputClick }; +} diff --git a/src/components/Menu/src/props.ts b/src/components/Menu/src/props.ts index b07dd52..4df3281 100644 --- a/src/components/Menu/src/props.ts +++ b/src/components/Menu/src/props.ts @@ -8,6 +8,10 @@ export const basicProps = { type: Array as PropType<Menu[]>, default: () => [], }, + flatItems: { + type: Array as PropType<Menu[]>, + default: () => [], + }, appendClass: { type: Boolean as PropType<boolean>, default: false, @@ -16,10 +20,6 @@ export const basicProps = { type: Boolean as PropType<boolean>, default: false, }, - flatItems: { - type: Array as PropType<Menu[]>, - default: () => [], - }, // 是否显示搜索框 search: { type: Boolean as PropType<boolean>, @@ -55,7 +55,7 @@ export const basicProps = { type: Boolean as PropType<boolean>, default: true, }, - isTop: { + isHorizontal: { type: Boolean as PropType<boolean>, default: false, }, diff --git a/src/components/Menu/src/types.d.ts b/src/components/Menu/src/types.d.ts deleted file mode 100644 index ea7c2d9..0000000 --- a/src/components/Menu/src/types.d.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ThemeEnum } from '/@/enums/appEnum'; -export interface MenuState { - // 默认选中的列表 - defaultSelectedKeys: string[]; - - // 模式 - mode: MenuModeEnum; - - // 主题 - theme: ComputedRef<ThemeEnum> | ThemeEnum; - - // 缩进 - inlineIndent?: number; - - // 展开数组 - openKeys: string[]; - - // 搜索值 - searchValue: string; - - // 当前选中的菜单项 key 数组 - selectedKeys: string[]; - - // 收缩状态下展开的数组 - collapsedOpenKeys: string[]; -} diff --git a/src/components/Menu/src/types.ts b/src/components/Menu/src/types.ts new file mode 100644 index 0000000..ea7c2d9 --- /dev/null +++ b/src/components/Menu/src/types.ts @@ -0,0 +1,26 @@ +import { ThemeEnum } from '/@/enums/appEnum'; +export interface MenuState { + // 默认选中的列表 + defaultSelectedKeys: string[]; + + // 模式 + mode: MenuModeEnum; + + // 主题 + theme: ComputedRef<ThemeEnum> | ThemeEnum; + + // 缩进 + inlineIndent?: number; + + // 展开数组 + openKeys: string[]; + + // 搜索值 + searchValue: string; + + // 当前选中的菜单项 key 数组 + selectedKeys: string[]; + + // 收缩状态下展开的数组 + collapsedOpenKeys: string[]; +} diff --git a/src/components/Menu/src/useOpenKeys.ts b/src/components/Menu/src/useOpenKeys.ts deleted file mode 100644 index 7cf4440..0000000 --- a/src/components/Menu/src/useOpenKeys.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { MenuModeEnum } from '/@/enums/menuEnum'; -import type { Menu as MenuType } from '/@/router/types'; -import type { MenuState } from './types'; -import type { Ref } from 'vue'; - -import { unref } from 'vue'; -import { menuStore } from '/@/store/modules/menu'; -import { getAllParentPath } from '/@/utils/helper/menuHelper'; -import { es6Unique } from '/@/utils'; - -export function useOpenKeys( - menuState: MenuState, - menus: Ref<MenuType[]>, - flatMenusRef: Ref<MenuType[]>, - isAppMenu: Ref<boolean>, - mode: Ref<MenuModeEnum>, - accordion: Ref<boolean> -) { - /** - * @description:设置展开 - */ - function setOpenKeys(menu: MenuType) { - const flatMenus = unref(flatMenusRef); - if (!unref(accordion)) { - menuState.openKeys = es6Unique([ - ...menuState.openKeys, - ...getAllParentPath(flatMenus, menu.path), - ]); - } else { - menuState.openKeys = getAllParentPath(flatMenus, menu.path); - } - } - - /** - * @description: 重置值 - */ - function resetKeys() { - menuState.selectedKeys = []; - menuState.openKeys = []; - } - - function handleOpenChange(openKeys: string[]) { - if (unref(mode) === MenuModeEnum.HORIZONTAL || !unref(accordion)) { - menuState.openKeys = openKeys; - } else { - const rootSubMenuKeys: string[] = []; - for (const { children, path } of unref(menus)) { - if (children && children.length > 0) { - rootSubMenuKeys.push(path); - } - } - if (!menuStore.getCollapsedState || !unref(isAppMenu)) { - const latestOpenKey = openKeys.find((key) => menuState.openKeys.indexOf(key) === -1); - if (rootSubMenuKeys.indexOf(latestOpenKey as string) === -1) { - menuState.openKeys = openKeys; - } else { - menuState.openKeys = latestOpenKey ? [latestOpenKey] : []; - } - } else { - menuState.collapsedOpenKeys = openKeys; - } - } - } - return { setOpenKeys, resetKeys, handleOpenChange }; -} diff --git a/src/components/Menu/src/useSearchInput.ts b/src/components/Menu/src/useSearchInput.ts deleted file mode 100644 index 1b39c4f..0000000 --- a/src/components/Menu/src/useSearchInput.ts +++ /dev/null @@ -1,58 +0,0 @@ -import type { Menu as MenuType } from '/@/router/types'; -import type { MenuState } from './types'; -import type { Ref } from 'vue'; - -import { isString } from '/@/utils/is'; -import { unref } from 'vue'; -import { es6Unique } from '/@/utils'; -import { getAllParentPath } from '/@/utils/helper/menuHelper'; - -interface UseSearchInputOptions { - menuState: MenuState; - flatMenusRef: Ref<MenuType[]>; - emit: EmitType; - handleMenuChange: Fn; -} -export function useSearchInput({ - menuState, - flatMenusRef, - handleMenuChange, - emit, -}: UseSearchInputOptions) { - /** - * @description: 输入框搜索 - */ - function handleInputChange(value?: string): void { - if (!isString(value)) { - value = (value as any).target.value; - } - if (!value) { - handleMenuChange && handleMenuChange(); - } - - menuState.searchValue = value || ''; - if (!value) { - menuState.openKeys = []; - return; - } - - const flatMenus = unref(flatMenusRef); - let openKeys: string[] = []; - for (const menu of flatMenus) { - const { name, path } = menu; - if (!name.includes(value)) { - continue; - } - openKeys = openKeys.concat(getAllParentPath(flatMenus, path)); - } - openKeys = es6Unique(openKeys); - menuState.openKeys = openKeys; - } - - // 搜索框点击 - function handleInputClick(e: any): void { - emit('clickSearchInput', e); - } - - return { handleInputChange, handleInputClick }; -} diff --git a/src/components/Modal/src/props.ts b/src/components/Modal/src/props.ts index 0ed4281..43d738f 100644 --- a/src/components/Modal/src/props.ts +++ b/src/components/Modal/src/props.ts @@ -35,7 +35,7 @@ export const basicProps = Object.assign({}, modalProps, { }, // Warm reminder message helpMessage: [String, Array] as PropType<string | string[]>, - // Whether to use wrapper + // Whether to setting wrapper useWrapper: { type: Boolean as PropType<boolean>, default: true, diff --git a/src/components/Table/src/types/column.ts b/src/components/Table/src/types/column.ts index 80d2b13..4df61bb 100644 --- a/src/components/Table/src/types/column.ts +++ b/src/components/Table/src/types/column.ts @@ -190,7 +190,7 @@ export interface ColumnProps<T> { onFilterDropdownVisibleChange?: (visible: boolean) => void; /** - * When using columns, you can use this property to configure the properties that support the slot, + * When using columns, you can setting this property to configure the properties that support the slot, * such as slots: { filterIcon: 'XXX'} * @type object */ diff --git a/src/components/Table/src/types/pagination.ts b/src/components/Table/src/types/pagination.ts index 6e27e5a..fd2ecbe 100644 --- a/src/components/Table/src/types/pagination.ts +++ b/src/components/Table/src/types/pagination.ts @@ -86,7 +86,7 @@ export interface PaginationProps { size?: string; /** - * whether to use simple mode + * whether to setting simple mode * @type boolean */ simple?: boolean; diff --git a/src/components/Tinymce/src/plugins.ts b/src/components/Tinymce/src/plugins.ts index 59602d1..7946db9 100644 --- a/src/components/Tinymce/src/plugins.ts +++ b/src/components/Tinymce/src/plugins.ts @@ -1,4 +1,4 @@ -// Any plugins you want to use has to be imported +// Any plugins you want to setting has to be imported // Detail plugins list see https://www.tinymce.com/docs/plugins/ // Custom builds see https://www.tinymce.com/download/custom-builds/ // colorpicker/contextmenu/textcolor plugin is now built in to the core editor, please remove it from your editor configuration diff --git a/src/components/registerGlobComp.ts b/src/components/registerGlobComp.ts index a7984fe..c1165fb 100644 --- a/src/components/registerGlobComp.ts +++ b/src/components/registerGlobComp.ts @@ -31,6 +31,7 @@ import { PageHeader, Result, Empty, + Avatar, } from 'ant-design-vue'; import { getApp } from '/@/setup/App'; @@ -76,5 +77,6 @@ export function registerGlobComp() { .use(PageHeader) .use(Result) .use(Empty) + .use(Avatar) .use(Tabs); } diff --git a/src/components/types.ts b/src/components/types.ts new file mode 100644 index 0000000..66ae167 --- /dev/null +++ b/src/components/types.ts @@ -0,0 +1,3 @@ +import { defineComponent } from 'vue'; + +export type Component = ReturnType<typeof defineComponent>; diff --git a/src/design/var/index.less b/src/design/var/index.less index aab4bfe..680b249 100644 --- a/src/design/var/index.less +++ b/src/design/var/index.less @@ -16,4 +16,4 @@ @page-loading-z-index: 10000; // left-menu -@app-menu-item-height: 44px; +@app-menu-item-height: 42px; diff --git a/src/enums/pageEnum.ts b/src/enums/pageEnum.ts index 136fa46..ef74eba 100644 --- a/src/enums/pageEnum.ts +++ b/src/enums/pageEnum.ts @@ -5,4 +5,6 @@ export enum PageEnum { BASE_HOME = '/dashboard', // error page path ERROR_PAGE = '/exception', + // error log page path + ERROR_LOG_PAGE = '/exception/error-log', } diff --git a/src/hooks/core/useDebouncedRef.ts b/src/hooks/core/useDebouncedRef.ts new file mode 100644 index 0000000..2345e36 --- /dev/null +++ b/src/hooks/core/useDebouncedRef.ts @@ -0,0 +1,20 @@ +import { customRef } from 'vue'; + +export function useDebouncedRef<T = any>(value: T, delay = 100) { + let timeout: TimeoutHandle; + return customRef((track, trigger) => { + return { + get() { + track(); + return value; + }, + set(newValue: T) { + clearTimeout(timeout); + timeout = setTimeout(() => { + value = newValue; + trigger(); + }, delay); + }, + }; + }); +} diff --git a/src/hooks/setting/index.ts b/src/hooks/setting/index.ts new file mode 100644 index 0000000..dccf7d7 --- /dev/null +++ b/src/hooks/setting/index.ts @@ -0,0 +1,43 @@ +import type { ProjectConfig, GlobConfig, GlobEnvConfig } from '/@/types/config'; + +import getProjectSetting from '/@/settings/projectSetting'; + +import { getShortName } from '../../../build/getShortName'; +import { warn } from '/@/utils/log'; +import { getGlobEnvConfig, isDevMode } from '/@/utils/env'; + +const reg = /[a-zA-Z\_]*/; + +const ENV_NAME = getShortName(import.meta.env); +const ENV = ((isDevMode() + ? getGlobEnvConfig() + : window[ENV_NAME as any]) as unknown) as GlobEnvConfig; + +const { + VITE_GLOB_APP_TITLE, + VITE_GLOB_API_URL, + VITE_GLOB_APP_SHORT_NAME, + VITE_GLOB_API_URL_PREFIX, +} = ENV; + +if (!reg.test(VITE_GLOB_APP_SHORT_NAME)) { + warn( + `VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.` + ); +} + +export const useGlobSetting = (): Readonly<GlobConfig> => { + // Take global configuration + const glob: Readonly<GlobConfig> = { + title: VITE_GLOB_APP_TITLE, + apiUrl: VITE_GLOB_API_URL, + shortName: VITE_GLOB_APP_SHORT_NAME, + urlPrefix: VITE_GLOB_API_URL_PREFIX, + }; + return glob as Readonly<GlobConfig>; +}; + +export const useProjectSetting = (): ProjectConfig => { + // TODO computed + return getProjectSetting; +}; diff --git a/src/hooks/setting/useHeaderSetting.ts b/src/hooks/setting/useHeaderSetting.ts new file mode 100644 index 0000000..7c8f1b1 --- /dev/null +++ b/src/hooks/setting/useHeaderSetting.ts @@ -0,0 +1,67 @@ +import type { HeaderSetting } from '/@/types/config'; + +import { computed, unref } from 'vue'; + +import { appStore } from '/@/store/modules/app'; + +import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting'; +import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; +import { useRootSetting } from '/@/hooks/setting/useRootSetting'; + +import { MenuModeEnum } from '/@/enums/menuEnum'; + +export function useHeaderSetting() { + const { getShow: getShowMultipleTab } = useMultipleTabSetting(); + const { getMode, getSplit, getShowHeaderTrigger, getIsSidebarType } = useMenuSetting(); + const { getShowBreadCrumb, getShowLogo } = useRootSetting(); + + // Get header configuration + const getHeaderSetting = computed(() => appStore.getProjectConfig.headerSetting); + + const getShowDoc = computed(() => unref(getHeaderSetting).showDoc); + + const getTheme = computed(() => unref(getHeaderSetting).theme); + + const getShowRedo = computed(() => unref(getHeaderSetting).showRedo && unref(getShowMultipleTab)); + + const getUseLockPage = computed(() => unref(getHeaderSetting).useLockPage); + + const getShowFullScreen = computed(() => unref(getHeaderSetting).showFullScreen); + + const getShowNotice = computed(() => unref(getHeaderSetting).showNotice); + + const getShowBread = computed(() => { + return ( + unref(getMode) !== MenuModeEnum.HORIZONTAL && unref(getShowBreadCrumb) && !unref(getSplit) + ); + }); + + const getShowHeaderLogo = computed(() => { + return unref(getShowLogo) && !unref(getIsSidebarType); + }); + + const getShowContent = computed(() => { + return unref(getShowBread) || unref(getShowHeaderTrigger); + }); + + // Set header configuration + function setHeaderSetting(headerSetting: Partial<HeaderSetting>): void { + appStore.commitProjectConfigState({ headerSetting }); + } + + return { + setHeaderSetting, + + getHeaderSetting, + + getShowDoc, + getTheme, + getShowRedo, + getUseLockPage, + getShowFullScreen, + getShowNotice, + getShowBread, + getShowContent, + getShowHeaderLogo, + }; +} diff --git a/src/hooks/setting/useLocaleSetting.ts b/src/hooks/setting/useLocaleSetting.ts new file mode 100644 index 0000000..a5128e1 --- /dev/null +++ b/src/hooks/setting/useLocaleSetting.ts @@ -0,0 +1,28 @@ +import type { LocaleSetting } from '/@/types/config'; + +import { computed, unref } from 'vue'; +import { appStore } from '/@/store/modules/app'; + +import getProjectSetting from '/@/settings/projectSetting'; +import { localeList } from '/@/locales'; + +export function useLocaleSetting() { + // Get locale configuration + const getLocale = computed(() => appStore.getProjectConfig.locale || getProjectSetting.locale); + + // get current language + const getLang = computed(() => unref(getLocale).lang); + + // get Available Locales + const getAvailableLocales = computed((): string[] => unref(getLocale).availableLocales); + + // get Fallback Locales + const getFallbackLocale = computed((): string => unref(getLocale).fallback); + + // Set locale configuration + function setLocale(locale: Partial<LocaleSetting>): void { + appStore.commitProjectConfigState({ locale }); + } + + return { getLocale, getLang, localeList, setLocale, getAvailableLocales, getFallbackLocale }; +} diff --git a/src/hooks/setting/useMenuSetting.ts b/src/hooks/setting/useMenuSetting.ts new file mode 100644 index 0000000..befaa45 --- /dev/null +++ b/src/hooks/setting/useMenuSetting.ts @@ -0,0 +1,120 @@ +import type { MenuSetting } from '/@/types/config'; + +import { computed, unref } from 'vue'; + +import { appStore } from '/@/store/modules/app'; + +import { SIDE_BAR_MINI_WIDTH, SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appEnum'; +import { MenuModeEnum, MenuTypeEnum, TriggerEnum } from '/@/enums/menuEnum'; + +export function useMenuSetting() { + // Get menu configuration + const getMenuSetting = computed(() => appStore.getProjectConfig.menuSetting); + + const getMiniWidth = computed(() => unref(getMenuSetting).menuWidth); + + const getCollapsed = computed(() => unref(getMenuSetting).collapsed); + + const getType = computed(() => unref(getMenuSetting).type); + + const getMode = computed(() => unref(getMenuSetting).mode); + + const getShow = computed(() => unref(getMenuSetting).show); + + const getMenuWidth = computed(() => unref(getMenuSetting).menuWidth); + + const getTrigger = computed(() => unref(getMenuSetting).trigger); + + const getTheme = computed(() => unref(getMenuSetting).theme); + + const getSplit = computed(() => unref(getMenuSetting).split); + + const getHasDrag = computed(() => unref(getMenuSetting).hasDrag); + + const getAccordion = computed(() => unref(getMenuSetting).accordion); + + const getCollapsedShowTitle = computed(() => unref(getMenuSetting).collapsedShowTitle); + + const getCollapsedShowSearch = computed(() => unref(getMenuSetting).collapsedShowSearch); + + const getTopMenuAlign = computed(() => unref(getMenuSetting).topMenuAlign); + + const getIsSidebarType = computed(() => unref(getType) === MenuTypeEnum.SIDEBAR); + + const getShowTopMenu = computed(() => { + return unref(getMode) === MenuModeEnum.HORIZONTAL || unref(getSplit); + }); + + const getShowHeaderTrigger = computed(() => { + if ( + unref(getType) === MenuTypeEnum.TOP_MENU || + !unref(getShow) || + !unref(getMenuSetting).hidden + ) { + return false; + } + + return unref(getTrigger) === TriggerEnum.HEADER; + }); + + const getShowSearch = computed(() => { + return ( + unref(getMenuSetting).showSearch && + !(unref(getType) === MenuTypeEnum.MIX && unref(getMode) === MenuModeEnum.HORIZONTAL) + ); + }); + + const getIsHorizontal = computed(() => { + return unref(getMode) === MenuModeEnum.HORIZONTAL; + }); + + const getMiniWidthNumber = computed(() => { + const { collapsedShowTitle } = unref(getMenuSetting); + return collapsedShowTitle ? SIDE_BAR_SHOW_TIT_MINI_WIDTH : SIDE_BAR_MINI_WIDTH; + }); + + const getCalcContentWidth = computed(() => { + const width = unref(getCollapsed) ? unref(getMiniWidthNumber) : unref(getMiniWidth); + return `calc(100% - ${width}px)`; + }); + + // Set menu configuration + function setMenuSetting(menuSetting: Partial<MenuSetting>): void { + appStore.commitProjectConfigState({ menuSetting }); + } + + function toggleCollapsed() { + setMenuSetting({ + collapsed: !unref(getCollapsed), + }); + } + + return { + setMenuSetting, + + toggleCollapsed, + + getMenuSetting, + getMiniWidth, + getType, + getMode, + getShow, + getCollapsed, + getMiniWidthNumber, + getCalcContentWidth, + getMenuWidth, + getTrigger, + getSplit, + getTheme, + getHasDrag, + getIsHorizontal, + getShowSearch, + getCollapsedShowTitle, + getCollapsedShowSearch, + getIsSidebarType, + getAccordion, + getShowTopMenu, + getShowHeaderTrigger, + getTopMenuAlign, + }; +} diff --git a/src/hooks/setting/useMultipleTabSetting.ts b/src/hooks/setting/useMultipleTabSetting.ts new file mode 100644 index 0000000..69a1192 --- /dev/null +++ b/src/hooks/setting/useMultipleTabSetting.ts @@ -0,0 +1,25 @@ +import type { MultiTabsSetting } from '/@/types/config'; + +import { computed, unref } from 'vue'; + +import { appStore } from '/@/store/modules/app'; + +export function useMultipleTabSetting() { + const getMultipleTabSetting = computed(() => appStore.getProjectConfig.multiTabsSetting); + + const getMax = computed(() => unref(getMultipleTabSetting).max); + + const getShow = computed(() => unref(getMultipleTabSetting).show); + + function setMultipleTabSetting(multiTabsSetting: Partial<MultiTabsSetting>) { + appStore.commitProjectConfigState({ multiTabsSetting }); + } + + return { + setMultipleTabSetting, + + getMultipleTabSetting, + getMax, + getShow, + }; +} diff --git a/src/hooks/setting/useRootSetting.ts b/src/hooks/setting/useRootSetting.ts new file mode 100644 index 0000000..f6f38af --- /dev/null +++ b/src/hooks/setting/useRootSetting.ts @@ -0,0 +1,53 @@ +import type { ProjectConfig } from '/@/types/config'; + +import { computed, unref } from 'vue'; + +import { appStore } from '/@/store/modules/app'; + +type RootSetting = Omit< + ProjectConfig, + 'locale' | 'headerSetting' | 'menuSetting' | 'multiTabsSetting' +>; +export function useRootSetting() { + const getRootSetting = computed((): RootSetting => appStore.getProjectConfig); + + const getOpenPageLoading = computed(() => unref(getRootSetting).openPageLoading); + + const getOpenRouterTransition = computed(() => unref(getRootSetting).openRouterTransition); + + const getOpenKeepAlive = computed(() => unref(getRootSetting).openKeepAlive); + + const getRouterTransition = computed(() => unref(getRootSetting).routerTransition); + + const getCanEmbedIFramePage = computed(() => unref(getRootSetting).canEmbedIFramePage); + + const getPermissionMode = computed(() => unref(getRootSetting).permissionMode); + + const getShowLogo = computed(() => unref(getRootSetting).showLogo); + + const getUseErrorHandle = computed(() => unref(getRootSetting).useErrorHandle); + + const getShowBreadCrumb = computed(() => unref(getRootSetting).showBreadCrumb); + + const getShowBreadCrumbIcon = computed(() => unref(getRootSetting).showBreadCrumbIcon); + + function setRootSetting(setting: RootSetting) { + appStore.commitProjectConfigState(setting); + } + + return { + setRootSetting, + + getRootSetting, + getOpenPageLoading, + getOpenRouterTransition, + getOpenKeepAlive, + getRouterTransition, + getCanEmbedIFramePage, + getPermissionMode, + getShowLogo, + getUseErrorHandle, + getShowBreadCrumb, + getShowBreadCrumbIcon, + }; +} diff --git a/src/hooks/web/useLocale.ts b/src/hooks/web/useLocale.ts index 5e8122d..6c313d4 100644 --- a/src/hooks/web/useLocale.ts +++ b/src/hooks/web/useLocale.ts @@ -7,7 +7,7 @@ import { unref, ref } from 'vue'; import { getI18n } from '/@/setup/i18n'; -import { useLocaleSetting } from '/@/settings/use/useLocaleSetting'; +import { useLocaleSetting } from '/@/hooks/setting/useLocaleSetting'; import moment from 'moment'; @@ -67,7 +67,7 @@ export function useLocale() { } /** - * For non-setup use + * For non-setup setting */ export function useExternalI18n() { return getI18n().global; diff --git a/src/hooks/web/usePage.ts b/src/hooks/web/usePage.ts index 5c543eb..6dcd8cf 100644 --- a/src/hooks/web/usePage.ts +++ b/src/hooks/web/usePage.ts @@ -20,6 +20,7 @@ function handleError(e: Error) { export function useGo() { const { push, replace } = useRouter(); function go(opt: PageEnum | RouteLocationRawEx | string = PageEnum.BASE_HOME, isReplace = false) { + if (!opt) return; if (isString(opt)) { isReplace ? replace(opt).catch(handleError) : push(opt).catch(handleError); } else { diff --git a/src/hooks/web/useSideBar.ts b/src/hooks/web/useSideBar.ts deleted file mode 100644 index 9b1ed3a..0000000 --- a/src/hooks/web/useSideBar.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { computed } from 'vue'; -import { appStore } from '/@/store/modules/app'; - -export function useSideBar() { - const currentCollapsedRef = computed(() => { - return appStore.getProjectConfig.menuSetting.collapsed; - }); - const changeCollapsed = (collapsed: boolean) => { - appStore.commitProjectConfigState({ - menuSetting: { - collapsed: collapsed, - }, - }); - }; - return { - openSider: changeCollapsed(false), - closeSider: changeCollapsed(true), - currentCollapsedRef, - }; -} diff --git a/src/layouts/default/LayoutSideBar.tsx b/src/layouts/default/LayoutSideBar.tsx deleted file mode 100644 index 2ac6372..0000000 --- a/src/layouts/default/LayoutSideBar.tsx +++ /dev/null @@ -1,214 +0,0 @@ -import { computed, defineComponent, nextTick, onMounted, ref, unref } from 'vue'; - -import { Layout } from 'ant-design-vue'; -import LayoutTrigger from './LayoutTrigger'; -import LayoutMenu from '/@/layouts/default/menu/LayoutMenu'; - -import { menuStore } from '/@/store/modules/menu'; -import { appStore } from '/@/store/modules/app'; - -import { MenuModeEnum, MenuSplitTyeEnum, TriggerEnum } from '/@/enums/menuEnum'; -import { SIDE_BAR_MINI_WIDTH, SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appEnum'; - -import { useDebounce } from '/@/hooks/core/useDebounce'; - -export default defineComponent({ - name: 'DefaultLayoutSideBar', - setup() { - const initRef = ref(false); - const brokenRef = ref(false); - const collapseRef = ref(true); - const dragBarRef = ref<Nullable<HTMLDivElement>>(null); - const sideRef = ref<any>(null); - - const getProjectConfigRef = computed(() => { - return appStore.getProjectConfig; - }); - - const getMiniWidth = computed(() => { - const { - menuSetting: { collapsedShowTitle }, - } = unref(getProjectConfigRef); - return collapsedShowTitle ? SIDE_BAR_SHOW_TIT_MINI_WIDTH : SIDE_BAR_MINI_WIDTH; - }); - - function onCollapseChange(val: boolean) { - if (initRef.value) { - collapseRef.value = val; - menuStore.commitCollapsedState(val); - } else { - const collapsed = appStore.getProjectConfig.menuSetting.collapsed; - !collapsed && menuStore.commitCollapsedState(val); - } - initRef.value = true; - } - - // Menu area drag and drop-mouse movement - function handleMouseMove(ele: any, wrap: any, clientX: number) { - document.onmousemove = function (innerE) { - let iT = ele.left + ((innerE || event).clientX - clientX); - innerE = innerE || window.event; - // let tarnameb = innerE.target || innerE.srcElement; - const maxT = 600; - const minT = unref(getMiniWidth); - iT < 0 && (iT = 0); - iT > maxT && (iT = maxT); - iT < minT && (iT = minT); - ele.style.left = wrap.style.width = iT + 'px'; - return false; - }; - } - - // 菜单区域拖拽 - 鼠标松开 - function removeMouseup(ele: any) { - const wrap = unref(sideRef).$el; - document.onmouseup = function () { - document.onmousemove = null; - document.onmouseup = null; - const width = parseInt(wrap.style.width); - menuStore.commitDragStartState(false); - if (!menuStore.getCollapsedState) { - if (width > unref(getMiniWidth) + 20) { - setMenuWidth(width); - } else { - menuStore.commitCollapsedState(true); - } - } else { - if (width > unref(getMiniWidth)) { - setMenuWidth(width); - menuStore.commitCollapsedState(false); - } - } - - ele.releaseCapture && ele.releaseCapture(); - }; - } - - function setMenuWidth(width: number) { - appStore.commitProjectConfigState({ - menuSetting: { - menuWidth: width, - }, - }); - } - - function changeWrapWidth() { - const ele = unref(dragBarRef) as any; - const side = unref(sideRef); - - const wrap = (side || {}).$el; - ele && - (ele.onmousedown = (e: any) => { - menuStore.commitDragStartState(true); - wrap.style.transition = 'unset'; - const clientX = (e || event).clientX; - ele.left = ele.offsetLeft; - handleMouseMove(ele, wrap, clientX); - removeMouseup(ele); - ele.setCapture && ele.setCapture(); - return false; - }); - } - function handleBreakpoint(broken: boolean) { - brokenRef.value = broken; - } - - const getDragBarStyle = computed(() => { - if (menuStore.getCollapsedState) { - return { left: `${unref(getMiniWidth)}px` }; - } - return {}; - }); - - const getCollapsedWidth = computed(() => { - return unref(brokenRef) ? 0 : unref(getMiniWidth); - }); - - const showTrigger = computed(() => { - const { - menuSetting: { trigger }, - } = unref(getProjectConfigRef); - return trigger !== TriggerEnum.NONE && trigger === TriggerEnum.FOOTER; - }); - - onMounted(() => { - nextTick(() => { - const [exec] = useDebounce(changeWrapWidth, 20); - exec(); - }); - }); - - function handleSiderClick(e: ChangeEvent) { - if (!e || !e.target || e.target.className !== 'basic-menu__content') return; - - const { collapsed, show } = appStore.getProjectConfig.menuSetting; - if (!collapsed || !show) return; - appStore.commitProjectConfigState({ - menuSetting: { - collapsed: false, - }, - }); - } - - function renderDragLine() { - const { menuSetting: { hasDrag = true } = {} } = unref(getProjectConfigRef); - return ( - <div - class={[`layout-sidebar__dargbar`, !hasDrag ? 'hide' : '']} - style={unref(getDragBarStyle)} - ref={dragBarRef} - /> - ); - } - - return () => { - const { - menuSetting: { theme, split: splitMenu }, - } = unref(getProjectConfigRef); - const { getCollapsedState, getMenuWidthState } = menuStore; - - const triggerDom = unref(showTrigger) - ? { - trigger: () => <LayoutTrigger />, - } - : {}; - - const triggerAttr = unref(showTrigger) - ? {} - : { - trigger: null, - }; - - return ( - <Layout.Sider - onClick={handleSiderClick} - onCollapse={onCollapseChange} - breakpoint="md" - width={getMenuWidthState} - collapsed={getCollapsedState} - collapsible - collapsedWidth={unref(getCollapsedWidth)} - theme={theme} - class="layout-sidebar" - ref={sideRef} - onBreakpoint={handleBreakpoint} - {...triggerAttr} - > - {{ - ...triggerDom, - default: () => ( - <> - <LayoutMenu - theme={theme} - menuMode={splitMenu ? MenuModeEnum.INLINE : null} - splitType={splitMenu ? MenuSplitTyeEnum.LEFT : MenuSplitTyeEnum.NONE} - /> - {renderDragLine()} - </> - ), - }} - </Layout.Sider> - ); - }; - }, -}); diff --git a/src/layouts/default/LayoutTrigger.tsx b/src/layouts/default/LayoutTrigger.tsx index 65a4d9a..5a7cda6 100644 --- a/src/layouts/default/LayoutTrigger.tsx +++ b/src/layouts/default/LayoutTrigger.tsx @@ -1,41 +1,39 @@ +import type { PropType } from 'vue'; + +import { defineComponent, unref } from 'vue'; import { DoubleRightOutlined, DoubleLeftOutlined, MenuUnfoldOutlined, MenuFoldOutlined, } from '@ant-design/icons-vue'; -import { defineComponent } from 'vue'; -// store -import { menuStore } from '/@/store/modules/menu'; +import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; export default defineComponent({ name: 'LayoutTrigger', props: { sider: { - type: Boolean, + type: Boolean as PropType<boolean>, default: true, }, theme: { - type: String, + type: String as PropType<string>, }, }, setup(props) { - function toggleMenu() { - menuStore.commitCollapsedState(!menuStore.getCollapsedState); - } + const { toggleCollapsed, getCollapsed } = useMenuSetting(); return () => { - const siderTrigger = menuStore.getCollapsedState ? ( - <DoubleRightOutlined /> - ) : ( - <DoubleLeftOutlined /> - ); - if (props.sider) return siderTrigger; + const siderTrigger = unref(getCollapsed) ? <DoubleRightOutlined /> : <DoubleLeftOutlined />; + + if (props.sider) { + return siderTrigger; + } return ( - <span class={['layout-trigger', props.theme]} onClick={toggleMenu}> - {menuStore.getCollapsedState ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />} + <span class={['layout-trigger', props.theme]} onClick={toggleCollapsed}> + {unref(getCollapsed) ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />} </span> ); }; diff --git a/src/layouts/default/header/LayoutBreadcrumb.tsx b/src/layouts/default/header/LayoutBreadcrumb.tsx index 93449ce..9b2941f 100644 --- a/src/layouts/default/header/LayoutBreadcrumb.tsx +++ b/src/layouts/default/header/LayoutBreadcrumb.tsx @@ -4,14 +4,17 @@ import type { PropType } from 'vue'; import { defineComponent, TransitionGroup, unref, watch, ref } from 'vue'; import Breadcrumb from '/@/components/Breadcrumb/Breadcrumb.vue'; +import Icon from '/@/components/Icon'; import BreadcrumbItem from '/@/components/Breadcrumb/BreadcrumbItem.vue'; + import { useRouter } from 'vue-router'; -import router from '/@/router'; -import { PageEnum } from '/@/enums/pageEnum'; -import { isBoolean } from '/@/utils/is'; +import { isBoolean } from '/@/utils/is'; import { compile } from 'path-to-regexp'; -import Icon from '/@/components/Icon'; + +import router from '/@/router'; + +import { PageEnum } from '/@/enums/pageEnum'; export default defineComponent({ name: 'BasicBreadcrumb', @@ -40,7 +43,6 @@ export default defineComponent({ const matchedList = matched.filter((item) => item.meta && item.meta.title).slice(1); const firstItem = matchedList[0]; const ret = getHomeRoute(firstItem); - if (!isBoolean(ret)) { matchedList.unshift(ret); } @@ -74,42 +76,51 @@ export default defineComponent({ return push(pathCompile(path)); } + function renderItemContent(item: AppRouteRecordRaw) { + return ( + <> + {props.showIcon && item.meta.icon && item.meta.icon.trim() !== '' && ( + <Icon + icon={item.meta.icon} + class="icon mr-1 " + style={{ + marginBottom: '2px', + }} + /> + )} + {item.meta.title} + </> + ); + } + + function renderBreadcrumbItemList() { + return unref(itemList).map((item) => { + const isLink = + (!!item.redirect && !item.meta.disabledRedirect) || + !item.children || + item.children.length === 0; + + return ( + <BreadcrumbItem + key={item.path} + isLink={isLink} + onClick={handleItemClick.bind(null, item)} + > + {() => renderItemContent(item as AppRouteRecordRaw)} + </BreadcrumbItem> + ); + }); + } + + function renderBreadcrumbDefault() { + return ( + <TransitionGroup name="breadcrumb">{() => renderBreadcrumbItemList()}</TransitionGroup> + ); + } + return () => ( <Breadcrumb class={['layout-breadcrumb', unref(itemList).length === 0 ? 'hidden' : '']}> - {() => ( - <TransitionGroup name="breadcrumb"> - {() => { - return unref(itemList).map((item) => { - const isLink = - (!!item.redirect && !item.meta.disabledRedirect) || - !item.children || - item.children.length === 0; - return ( - <BreadcrumbItem - key={item.path} - isLink={isLink} - onClick={handleItemClick.bind(null, item)} - > - {() => ( - <> - {props.showIcon && item.meta.icon && item.meta.icon.trim() !== '' && ( - <Icon - icon={item.meta.icon} - class="icon mr-1 " - style={{ - marginBottom: '2px', - }} - /> - )} - {item.meta.title} - </> - )} - </BreadcrumbItem> - ); - }); - }} - </TransitionGroup> - )} + {() => renderBreadcrumbDefault()} </Breadcrumb> ); }, diff --git a/src/layouts/default/header/LayoutHeader.tsx b/src/layouts/default/header/LayoutHeader.tsx index 0fa71aa..d695b86 100644 --- a/src/layouts/default/header/LayoutHeader.tsx +++ b/src/layouts/default/header/LayoutHeader.tsx @@ -1,7 +1,9 @@ +import './index.less'; + import { defineComponent, unref, computed, ref } from 'vue'; import { Layout, Tooltip, Badge } from 'ant-design-vue'; -import Logo from '/@/layouts/logo/index.vue'; +import { AppLogo } from '/@/components/Application'; import UserDropdown from './UserDropdown'; import LayoutMenu from '/@/layouts/default/menu/LayoutMenu'; import LayoutBreadcrumb from './LayoutBreadcrumb'; @@ -12,50 +14,57 @@ import { RedoOutlined, FullscreenExitOutlined, FullscreenOutlined, - GithubFilled, LockOutlined, BugOutlined, } from '@ant-design/icons-vue'; +import { useModal } from '/@/components/Modal'; import { useFullscreen } from '/@/hooks/web/useFullScreen'; import { useTabs } from '/@/hooks/web/useTabs'; import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn'; +import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'; +import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; +import { useRootSetting } from '/@/hooks/setting/useRootSetting'; + import { useRouter } from 'vue-router'; -import { useModal } from '/@/components/Modal'; -import { appStore } from '/@/store/modules/app'; import { errorStore } from '/@/store/modules/error'; -import { MenuModeEnum, MenuSplitTyeEnum, MenuTypeEnum, TriggerEnum } from '/@/enums/menuEnum'; -import { GITHUB_URL } from '/@/settings/siteSetting'; +import { PageEnum } from '/@/enums/pageEnum'; +import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum'; +import { Component } from '/@/components/types'; -import './index.less'; export default defineComponent({ - name: 'DefaultLayoutHeader', + name: 'LayoutHeader', setup() { - const widthRef = ref(200); let logoEl: Element | null; + const widthRef = ref(200); + const { refreshPage } = useTabs(); + + const { getShowTopMenu, getShowHeaderTrigger, getSplit, getTopMenuAlign } = useMenuSetting(); + + const { getUseErrorHandle, getShowBreadCrumbIcon } = useRootSetting(); + + const { + getTheme, + getShowRedo, + getUseLockPage, + getShowFullScreen, + getShowNotice, + getShowContent, + getShowBread, + getShowHeaderLogo, + } = useHeaderSetting(); + const { push } = useRouter(); const [register, { openModal }] = useModal(); const { toggleFullscreen, isFullscreenRef } = useFullscreen(); - const getProjectConfigRef = computed(() => { - return appStore.getProjectConfig; - }); - - const showTopMenu = computed(() => { - const getProjectConfig = unref(getProjectConfigRef); - const { - menuSetting: { mode, split: splitMenu }, - } = getProjectConfig; - return mode === MenuModeEnum.HORIZONTAL || splitMenu; - }); - useWindowSizeFn( () => { - if (!unref(showTopMenu)) return; + if (!unref(getShowTopMenu)) return; let width = 0; if (!logoEl) { logoEl = document.querySelector('.layout-header__logo'); @@ -69,24 +78,23 @@ export default defineComponent({ { immediate: true } ); - function goToGithub() { - window.open(GITHUB_URL, '__blank'); - } - const headerClass = computed(() => { - const theme = unref(getProjectConfigRef).headerSetting.theme; + const theme = unref(getTheme); return theme ? `layout-header__header--${theme}` : ''; }); - const showHeaderTrigger = computed(() => { - const { show, trigger, hidden, type } = unref(getProjectConfigRef).menuSetting; - if (type === MenuTypeEnum.TOP_MENU || !show || !hidden) return false; - return trigger === TriggerEnum.HEADER; + const getSplitType = computed(() => { + return unref(getSplit) ? MenuSplitTyeEnum.TOP : MenuSplitTyeEnum.NONE; + }); + + const getMenuMode = computed(() => { + return unref(getSplit) ? MenuModeEnum.HORIZONTAL : null; }); function handleToErrorList() { - errorStore.commitErrorListCountState(0); - push('/exception/error-log'); + push(PageEnum.ERROR_LOG_PAGE).then(() => { + errorStore.commitErrorListCountState(0); + }); } /** @@ -96,162 +104,129 @@ export default defineComponent({ openModal(true); } - return () => { - const getProjectConfig = unref(getProjectConfigRef); - const { - useErrorHandle, - showLogo, - multiTabsSetting: { show: showTab }, - headerSetting: { - theme: headerTheme, - useLockPage, - showRedo, - showGithub, - showFullScreen, - showNotice, - }, - menuSetting: { mode, type: menuType, split: splitMenu, topMenuAlign }, - showBreadCrumb, - showBreadCrumbIcon, - } = getProjectConfig; + function renderHeaderContent() { + const width = unref(widthRef); + return ( + <div class="layout-header__content "> + {unref(getShowHeaderLogo) && ( + <AppLogo class={`layout-header__logo`} theme={unref(getTheme)} /> + )} - const isSidebarType = menuType === MenuTypeEnum.SIDEBAR; + {unref(getShowContent) && ( + <div class="layout-header__left"> + {unref(getShowHeaderTrigger) && ( + <LayoutTrigger theme={unref(getTheme)} sider={false} /> + )} + {unref(getShowBread) && <LayoutBreadcrumb showIcon={unref(getShowBreadCrumbIcon)} />} + </div> + )} - const width = unref(widthRef); + {unref(getShowTopMenu) && ( + <div class={[`layout-header__menu `]} style={{ width: `calc(100% - ${width}px)` }}> + <LayoutMenu + isHorizontal={true} + class={`justify-${unref(getTopMenuAlign)}`} + theme={unref(getTheme)} + splitType={unref(getSplitType)} + menuMode={unref(getMenuMode)} + showSearch={false} + /> + </div> + )} + </div> + ); + } - const showLeft = - (mode !== MenuModeEnum.HORIZONTAL && showBreadCrumb && !splitMenu) || - unref(showHeaderTrigger); + function renderActionDefault(Comp: Component | any, event: Fn) { return ( - <Layout.Header class={['layout-header', 'flex p-0 px-4 ', unref(headerClass)]}> - {() => ( - <> - <div class="layout-header__content "> - {showLogo && !isSidebarType && ( - <Logo class={`layout-header__logo`} theme={headerTheme} /> - )} - - {showLeft && ( - <div class="layout-header__left"> - {unref(showHeaderTrigger) && ( - <LayoutTrigger theme={headerTheme} sider={false} /> - )} - {mode !== MenuModeEnum.HORIZONTAL && showBreadCrumb && !splitMenu && ( - <LayoutBreadcrumb showIcon={showBreadCrumbIcon} /> - )} - </div> - )} + <div class={`layout-header__action-item`} onClick={event}> + <Comp class={`layout-header__action-icon`} /> + </div> + ); + } - {unref(showTopMenu) && ( - <div - class={[`layout-header__menu `]} - style={{ width: `calc(100% - ${unref(width)}px)` }} + function renderAction() { + return ( + <div class={`layout-header__action`}> + {unref(getUseErrorHandle) && ( + <Tooltip> + {{ + title: () => '错误日志', + default: () => ( + <Badge + count={errorStore.getErrorListCountState} + offset={[0, 10]} + dot + overflowCount={99} > - <LayoutMenu - isTop={true} - class={`justify-${topMenuAlign}`} - theme={headerTheme} - splitType={splitMenu ? MenuSplitTyeEnum.TOP : MenuSplitTyeEnum.NONE} - menuMode={splitMenu ? MenuModeEnum.HORIZONTAL : null} - showSearch={false} - /> - </div> - )} - </div> + {() => renderActionDefault(BugOutlined, handleToErrorList)} + </Badge> + ), + }} + </Tooltip> + )} - <div class={`layout-header__action`}> - {useErrorHandle && ( - <Tooltip> - {{ - title: () => '错误日志', - default: () => ( - <Badge - count={errorStore.getErrorListCountState} - offset={[0, 10]} - dot - overflowCount={99} - > - {() => ( - <div class={`layout-header__action-item`} onClick={handleToErrorList}> - <BugOutlined class={`layout-header__action-icon`} /> - </div> - )} - </Badge> - ), - }} - </Tooltip> - )} + {unref(getUseLockPage) && ( + <Tooltip> + {{ + title: () => '锁定屏幕', + default: () => renderActionDefault(LockOutlined, handleLockPage), + }} + </Tooltip> + )} + + {unref(getShowNotice) && ( + <Tooltip> + {{ + title: () => '消息通知', + default: () => <NoticeAction />, + }} + </Tooltip> + )} - {showGithub && ( - <Tooltip> - {{ - title: () => 'github', - default: () => ( - <div class={`layout-header__action-item`} onClick={goToGithub}> - <GithubFilled class={`layout-header__action-icon`} /> - </div> - ), - }} - </Tooltip> - )} - {useLockPage && ( - <Tooltip> - {{ - title: () => '锁定屏幕', - default: () => ( - <div class={`layout-header__action-item`} onClick={handleLockPage}> - <LockOutlined class={`layout-header__action-icon`} /> - </div> - ), - }} - </Tooltip> - )} - {showNotice && ( - <div> - <Tooltip> - {{ - title: () => '消息通知', - default: () => <NoticeAction />, - }} - </Tooltip> - </div> - )} - {showRedo && showTab && ( - <Tooltip> - {{ - title: () => '刷新', - default: () => ( - <div class={`layout-header__action-item`} onClick={refreshPage}> - <RedoOutlined class={`layout-header__action-icon`} /> - </div> - ), - }} - </Tooltip> - )} - {showFullScreen && ( - <Tooltip> - {{ - title: () => (unref(isFullscreenRef) ? '退出全屏' : '全屏'), - default: () => { - const Icon: any = !unref(isFullscreenRef) ? ( - <FullscreenOutlined /> - ) : ( - <FullscreenExitOutlined /> - ); - return ( - <div class={`layout-header__action-item`} onClick={toggleFullscreen}> - <Icon class={`layout-header__action-icon`} /> - </div> - ); - }, - }} - </Tooltip> - )} - <UserDropdown class={`layout-header__user-dropdown`} /> - </div> - <LockAction onRegister={register} /> - </> + {unref(getShowRedo) && ( + <Tooltip> + {{ + title: () => '刷新', + default: () => renderActionDefault(RedoOutlined, refreshPage), + }} + </Tooltip> )} + + {unref(getShowFullScreen) && ( + <Tooltip> + {{ + title: () => (unref(isFullscreenRef) ? '退出全屏' : '全屏'), + default: () => { + const Icon = !unref(isFullscreenRef) ? ( + <FullscreenOutlined /> + ) : ( + <FullscreenExitOutlined /> + ); + return renderActionDefault(Icon, toggleFullscreen); + }, + }} + </Tooltip> + )} + <UserDropdown class={`layout-header__user-dropdown`} /> + </div> + ); + } + + function renderHeaderDefault() { + return ( + <> + {renderHeaderContent()} + {renderAction()} + <LockAction onRegister={register} /> + </> + ); + } + + return () => { + return ( + <Layout.Header class={['layout-header', 'flex p-0 px-4 ', unref(headerClass)]}> + {() => renderHeaderDefault()} </Layout.Header> ); }; diff --git a/src/layouts/default/header/LockActionItem.less b/src/layouts/default/header/LockActionItem.less index 21e4c64..ffd25bb 100644 --- a/src/layouts/default/header/LockActionItem.less +++ b/src/layouts/default/header/LockActionItem.less @@ -1,7 +1,6 @@ .lock-modal { &__entry { position: relative; - // width: 500px; height: 240px; padding: 130px 30px 60px 30px; background: #fff; diff --git a/src/layouts/default/header/LockActionItem.tsx b/src/layouts/default/header/LockActionItem.tsx index a6f7db0..589fdd5 100644 --- a/src/layouts/default/header/LockActionItem.tsx +++ b/src/layouts/default/header/LockActionItem.tsx @@ -1,41 +1,33 @@ -// 组件相关 +import './LockActionItem.less'; + import { defineComponent } from 'vue'; import { BasicModal, useModalInner } from '/@/components/Modal/index'; - -// hook +import Button from '/@/components/Button/index.vue'; import { BasicForm, useForm } from '/@/components/Form/index'; import headerImg from '/@/assets/images/header.jpg'; import { appStore } from '/@/store/modules/app'; import { userStore } from '/@/store/modules/user'; -import Button from '/@/components/Button/index.vue'; -import './LockActionItem.less'; + const prefixCls = 'lock-modal'; export default defineComponent({ name: 'LockModal', setup(_, { attrs }) { - const [register, { setModalProps }] = useModalInner(); - // 样式前缀 + const [register, { closeModal }] = useModalInner(); + const [registerForm, { validateFields, resetFields }] = useForm({ - // 隐藏按钮 showActionButtonGroup: false, - // 表单项 schemas: [ { field: 'password', - label: '', + label: '锁屏密码', component: 'InputPassword', - componentProps: { - placeholder: '请输入锁屏密码', - }, - rules: [{ required: true }], + required: true, }, ], }); - /** - * @description: lock - */ + async function lock(valid = true) { let password: string | undefined = ''; @@ -46,9 +38,7 @@ export default defineComponent({ const values = (await validateFields()) as any; password = values.password; } - setModalProps({ - visible: false, - }); + closeModal(); appStore.commitLockInfoState({ isLock: true, @@ -57,7 +47,7 @@ export default defineComponent({ await resetFields(); } catch (error) {} } - // 账号密码登录 + return () => ( <BasicModal footer={null} title="锁定屏幕" {...attrs} class={prefixCls} onRegister={register}> {() => ( @@ -66,7 +56,9 @@ export default defineComponent({ <img src={headerImg} class={`${prefixCls}__header-img`} /> <p class={`${prefixCls}__header-name`}>{userStore.getUserInfoState.realName}</p> </div> - <BasicForm onRegister={registerForm} /> + + <BasicForm onRegister={registerForm} layout="vertical" /> + <div class={`${prefixCls}__footer`}> <Button type="primary" block class="mt-2" onClick={lock}> {() => '锁屏'} diff --git a/src/layouts/default/header/UserDropdown.tsx b/src/layouts/default/header/UserDropdown.tsx index 05a00f6..c033a3d 100644 --- a/src/layouts/default/header/UserDropdown.tsx +++ b/src/layouts/default/header/UserDropdown.tsx @@ -11,15 +11,23 @@ import Icon from '/@/components/Icon/index'; import { userStore } from '/@/store/modules/user'; import { DOC_URL } from '/@/settings/siteSetting'; -import { appStore } from '/@/store/modules/app'; + +import { openWindow } from '/@/utils'; + +import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'; + +interface RenderItemParams { + icon: string; + text: string; + key: string; +} const prefixCls = 'user-dropdown'; + export default defineComponent({ name: 'UserDropdown', setup() { - const getProjectConfigRef = computed(() => { - return appStore.getProjectConfig; - }); + const { getShowDoc } = useHeaderSetting(); const getUserInfo = computed(() => { const { realName = '', desc } = userStore.getUserInfoState || {}; @@ -33,7 +41,7 @@ export default defineComponent({ // open doc function openDoc() { - window.open(DOC_URL, '__blank'); + openWindow(DOC_URL); } function handleMenuClick(e: any) { @@ -44,7 +52,7 @@ export default defineComponent({ } } - function renderItem({ icon, text, key }: { icon: string; text: string; key: string }) { + function renderItem({ icon, text, key }: RenderItemParams) { return ( <Menu.Item key={key}> {() => ( @@ -57,37 +65,43 @@ export default defineComponent({ ); } - return () => { + function renderSlotsDefault() { const { realName } = unref(getUserInfo); - const { - headerSetting: { showDoc }, - } = unref(getProjectConfigRef); + return ( + <section class={prefixCls}> + <img class={`${prefixCls}__header`} src={headerImg} /> + <section class={`${prefixCls}__info`}> + <section class={`${prefixCls}__name`}>{realName}</section> + </section> + </section> + ); + } + + function renderSlotOverlay() { + const showDoc = unref(getShowDoc); + return ( + <Menu onClick={handleMenuClick}> + {() => ( + <> + {showDoc && renderItem({ key: 'doc', text: '文档', icon: 'gg:loadbar-doc' })} + {showDoc && <Divider />} + {renderItem({ + key: 'loginOut', + text: '退出系统', + icon: 'ant-design:poweroff-outlined', + })} + </> + )} + </Menu> + ); + } + + return () => { return ( <Dropdown placement="bottomLeft"> {{ - default: () => ( - <section class={prefixCls}> - <img class={`${prefixCls}__header`} src={headerImg} /> - <section class={`${prefixCls}__info`}> - <section class={`${prefixCls}__name`}>{realName}</section> - </section> - </section> - ), - overlay: () => ( - <Menu slot="overlay" onClick={handleMenuClick}> - {() => ( - <> - {showDoc && renderItem({ key: 'doc', text: '文档', icon: 'gg:loadbar-doc' })} - {showDoc && <Divider />} - {renderItem({ - key: 'loginOut', - text: '退出系统', - icon: 'ant-design:poweroff-outlined', - })} - </> - )} - </Menu> - ), + default: () => renderSlotsDefault(), + overlay: () => renderSlotOverlay(), }} </Dropdown> ); diff --git a/src/layouts/default/header/notice/NoticeActionItem.vue b/src/layouts/default/header/notice/NoticeActionItem.vue index a76c43a..6751caa 100644 --- a/src/layouts/default/header/notice/NoticeActionItem.vue +++ b/src/layouts/default/header/notice/NoticeActionItem.vue @@ -1,6 +1,6 @@ <template> <div class="layout-header__action-item notify-action"> - <Popover title="" trigger="click"> + <Popover title="" trigger="click" overlayClassName="layout-header__notify-action"> <Badge :count="count" dot :numberStyle="numberStyle"> <BellOutlined class="layout-header__action-icon" /> </Badge> @@ -31,6 +31,7 @@ components: { Popover, BellOutlined, Tabs, TabPane: Tabs.TabPane, Badge, NoticeList }, setup() { let count = 0; + for (let i = 0; i < tabListData.length; i++) { count += tabListData[i].list.length; } @@ -44,6 +45,10 @@ }); </script> <style lang="less"> + .layout-header__notify-action { + max-width: 360px; + } + .notify-action { padding-top: 2px; @@ -56,7 +61,6 @@ .ant-badge-multiple-words { padding: 0 4px; - // transform: translate(26%, -40%); } svg { diff --git a/src/layouts/default/header/notice/NoticeList.vue b/src/layouts/default/header/notice/NoticeList.vue index dfc566f..b3b9619 100644 --- a/src/layouts/default/header/notice/NoticeList.vue +++ b/src/layouts/default/header/notice/NoticeList.vue @@ -1,36 +1,37 @@ <template> - <List class="list"> + <a-list class="list"> <template v-for="item in list" :key="item.id"> - <ListItem class="list__item"> - <ListItemMeta> + <a-list-item class="list-item"> + <a-list-item-meta> <template #title> <div class="title"> {{ item.title }} <div class="extra" v-if="item.extra"> - <Tag class="tag" :color="item.color"> + <a-tag class="tag" :color="item.color"> {{ item.extra }} - </Tag> + </a-tag> </div> </div> </template> + <template #avatar> - <Avatar v-if="item.avatar" class="avatar" :src="item.avatar" /> + <a-avatar v-if="item.avatar" class="avatar" :src="item.avatar" /> <span v-else> {{ item.avatar }}</span> </template> + <template #description> <div> <div class="description">{{ item.description }}</div> <div class="datetime">{{ item.datetime }}</div> </div> </template> - </ListItemMeta> - </ListItem> + </a-list-item-meta> + </a-list-item> </template> - </List> + </a-list> </template> <script lang="ts"> import { defineComponent, PropType } from 'vue'; - import { List, Avatar, Tag } from 'ant-design-vue'; import { ListItem } from './data'; export default defineComponent({ @@ -40,19 +41,6 @@ default: () => [], }, }, - components: { - List, - ListItem: List.Item, - ListItemMeta: List.Item.Meta, - Avatar, - Tag, - }, - setup(props) { - const { list = [] } = props; - return { - list, - }; - }, }); </script> <style lang="less" scoped> @@ -61,7 +49,7 @@ display: none; } - &__item { + &-item { padding: 6px; overflow: hidden; cursor: pointer; diff --git a/src/layouts/default/header/notice/data.ts b/src/layouts/default/header/notice/data.ts index 1764a95..07bc4a8 100644 --- a/src/layouts/default/header/notice/data.ts +++ b/src/layouts/default/header/notice/data.ts @@ -56,14 +56,6 @@ export const tabListData: TabItem[] = [ datetime: '2017-08-07', type: '1', }, - // { - // id: '000000005', - // avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png', - // title: '内容不要超过两行字,超出时自动截断', - // description: '', - // datetime: '2017-08-07', - // type: '1', - // }, ], }, { diff --git a/src/layouts/default/index.less b/src/layouts/default/index.less index 681e9b3..13ebe02 100644 --- a/src/layouts/default/index.less +++ b/src/layouts/default/index.less @@ -36,47 +36,4 @@ margin: 0 auto; } } - - .layout-sidebar { - background-size: 100% 100%; - - &.ant-layout-sider-dark { - background: @sider-dark-bg-color; - } - - &:not(.ant-layout-sider-dark) { - border-right: 1px solid @border-color-light; - } - - .ant-layout-sider-zero-width-trigger { - top: 40%; - z-index: 10; - } - - &__dargbar { - position: absolute; - top: 0; - right: -2px; - z-index: @side-drag-z-index; - width: 2px; - height: 100%; - cursor: col-resize; - border-top: none; - border-bottom: none; - - &.hide { - display: none; - } - - &:hover { - background: @primary-color; - box-shadow: 0 0 4px 0 rgba(28, 36, 56, 0.15); - } - } - } -} - -.ant-layout-sider-trigger { - height: 36px; - line-height: 36px; } diff --git a/src/layouts/default/index.tsx b/src/layouts/default/index.tsx index 7d98ca1..36ab266 100644 --- a/src/layouts/default/index.tsx +++ b/src/layouts/default/index.tsx @@ -4,7 +4,7 @@ import LayoutHeader from './header/LayoutHeader'; import { appStore } from '/@/store/modules/app'; import LayoutContent from './LayoutContent'; -import LayoutSideBar from './LayoutSideBar'; +import LayoutSideBar from './sider/LayoutSideBar'; import SettingBtn from './setting/index.vue'; import MultipleTabs from './multitabs/index'; @@ -36,7 +36,7 @@ export default defineComponent({ return show; }); - const isShowMixHeaderRef = computed(() => { + const showMixHeaderRef = computed(() => { const { menuSetting: { type }, } = unref(getProjectConfigRef); @@ -57,11 +57,11 @@ export default defineComponent({ }); const showFullHeaderRef = computed(() => { - return !unref(getFullContent) && unref(isShowMixHeaderRef) && unref(showHeaderRef); + return !unref(getFullContent) && unref(showMixHeaderRef) && unref(showHeaderRef); }); const showInsetHeaderRef = computed(() => { - return !unref(getFullContent) && !unref(isShowMixHeaderRef) && unref(showHeaderRef); + return !unref(getFullContent) && !unref(showMixHeaderRef) && unref(showHeaderRef); }); const fixedHeaderClsRef = computed(() => { diff --git a/src/layouts/default/menu/LayoutMenu.tsx b/src/layouts/default/menu/LayoutMenu.tsx index 41e1feb..7e2c108 100644 --- a/src/layouts/default/menu/LayoutMenu.tsx +++ b/src/layouts/default/menu/LayoutMenu.tsx @@ -1,29 +1,21 @@ -import type { PropType } from 'vue'; +import './index.less'; + +import { PropType, toRef } from 'vue'; import type { Menu } from '/@/router/types'; -import { computed, defineComponent, unref, ref, onMounted, watch } from 'vue'; +import { computed, defineComponent, unref } from 'vue'; import { BasicMenu } from '/@/components/Menu/index'; -import Logo from '/@/layouts/logo/index.vue'; - -import { MenuModeEnum, MenuSplitTyeEnum, MenuTypeEnum } from '/@/enums/menuEnum'; - -// store -import { appStore } from '/@/store/modules/app'; -import { menuStore } from '/@/store/modules/menu'; - -import { - getMenus, - getFlatMenus, - getShallowMenus, - getChildrenMenus, - getFlatChildrenMenus, - getCurrentParentPath, -} from '/@/router/menus/index'; -import { useRouter } from 'vue-router'; -import { useThrottle } from '/@/hooks/core/useThrottle'; -import { permissionStore } from '/@/store/modules/permission'; +import { AppLogo } from '/@/components/Application'; + +import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum'; + +import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; +import { useRootSetting } from '/@/hooks/setting/useRootSetting'; + +import { useGo } from '/@/hooks/web/usePage'; +import { useSplitMenu } from './useLayoutMenu'; +import { openWindow } from '/@/utils'; -import './index.less'; export default defineComponent({ name: 'DefaultLayoutMenu', props: { @@ -43,7 +35,7 @@ export default defineComponent({ type: Boolean as PropType<boolean>, default: true, }, - isTop: { + isHorizontal: { type: Boolean as PropType<boolean>, default: false, }, @@ -53,190 +45,99 @@ export default defineComponent({ }, }, setup(props) { - // Menu array - const menusRef = ref<Menu[]>([]); - // flat menu array - const flatMenusRef = ref<Menu[]>([]); - const { currentRoute, push } = useRouter(); - - // get app config - const getProjectConfigRef = computed(() => { - return appStore.getProjectConfig; - }); + const go = useGo(); - // get is Horizontal - const getIsHorizontalRef = computed(() => { - return unref(getProjectConfigRef).menuSetting.mode === MenuModeEnum.HORIZONTAL; - }); + const { + setMenuSetting, + getShowSearch, + getMode, + getType, + getCollapsedShowTitle, + getCollapsedShowSearch, + getIsSidebarType, + getTheme, + getCollapsed, + getAccordion, + } = useMenuSetting(); - const [throttleHandleSplitLeftMenu] = useThrottle(handleSplitLeftMenu, 50); - - // Route change split menu - watch( - [() => unref(currentRoute).path, () => props.splitType], - async ([path, splitType]: [string, MenuSplitTyeEnum]) => { - if (splitType !== MenuSplitTyeEnum.LEFT && !unref(getIsHorizontalRef)) return; - const parentPath = await getCurrentParentPath(path); - parentPath && throttleHandleSplitLeftMenu(parentPath); - }, - { - immediate: true, - } - ); + const { getShowLogo } = useRootSetting(); - // Menu changes - watch( - [() => permissionStore.getLastBuildMenuTimeState, () => permissionStore.getBackMenuListState], - () => { - genMenus(); - } - ); - - // split Menu changes - watch([() => appStore.getProjectConfig.menuSetting.split], () => { - if (props.splitType !== MenuSplitTyeEnum.LEFT && !unref(getIsHorizontalRef)) return; - genMenus(); - }); + const { flatMenusRef, menusRef } = useSplitMenu(toRef(props, 'splitType')); - // Handle left menu split - async function handleSplitLeftMenu(parentPath: string) { - const isSplitMenu = unref(getProjectConfigRef).menuSetting.split; - if (!isSplitMenu) return; - const { splitType } = props; - // spilt mode left - if (splitType === MenuSplitTyeEnum.LEFT) { - const children = await getChildrenMenus(parentPath); - if (!children) { - appStore.commitProjectConfigState({ - menuSetting: { - hidden: false, - }, - }); - flatMenusRef.value = []; - menusRef.value = []; - return; - } - const flatChildren = await getFlatChildrenMenus(children); - appStore.commitProjectConfigState({ - menuSetting: { - hidden: true, - }, - }); - flatMenusRef.value = flatChildren; - menusRef.value = children; - } - } + const showLogo = computed(() => unref(getShowLogo) && unref(getIsSidebarType)); - // get menus - async function genMenus() { - const isSplitMenu = unref(getProjectConfigRef).menuSetting.split; + const getMenuMode = computed(() => props.menuMode || unref(getMode)); - // normal mode - const { splitType } = props; - if (splitType === MenuSplitTyeEnum.NONE || !isSplitMenu) { - flatMenusRef.value = await getFlatMenus(); - menusRef.value = await getMenus(); - return; - } + const getMenuTheme = computed(() => props.theme || unref(getTheme)); - // split-top - if (splitType === MenuSplitTyeEnum.TOP) { - const parentPath = await getCurrentParentPath(unref(currentRoute).path); - menuStore.commitCurrentTopSplitMenuPathState(parentPath); - const shallowMenus = await getShallowMenus(); + const appendClass = computed(() => props.splitType === MenuSplitTyeEnum.TOP); - flatMenusRef.value = shallowMenus; - menusRef.value = shallowMenus; - return; - } - } + const showSearch = computed(() => { + return ( + unref(getShowSearch) && + props.showSearch && + (unref(getCollapsedShowSearch) ? true : !unref(getCollapsed)) + ); + }); + /** + * click menu + * @param menu + */ function handleMenuClick(menu: Menu) { - const { path } = menu; - if (path) { - push(path); - const { splitType } = props; - // split mode top - if (splitType === MenuSplitTyeEnum.TOP) { - menuStore.commitCurrentTopSplitMenuPathState(path); - } - } + go(menu.path); } + /** + * before click menu + * @param menu + */ async function beforeMenuClickFn(menu: Menu) { const { meta: { externalLink } = {} } = menu; if (externalLink) { - window.open(externalLink, '_blank'); + openWindow(externalLink); return false; } - return true; } function handleClickSearchInput() { - if (menuStore.getCollapsedState) { - menuStore.commitCollapsedState(false); - } + unref(getCollapsed) && setMenuSetting({ collapsed: false }); } - const showSearchRef = computed(() => { - const { showSearch, type, mode } = unref(getProjectConfigRef).menuSetting; + function renderHeader() { + if (!unref(showLogo)) return null; return ( - showSearch && - props.showSearch && - !(type === MenuTypeEnum.MIX && mode === MenuModeEnum.HORIZONTAL) + <AppLogo + showTitle={!unref(getCollapsed)} + class={[`layout-menu__logo`, unref(getMenuTheme)]} + theme={unref(getMenuTheme)} + /> ); - }); - - onMounted(() => { - genMenus(); - }); + } return () => { - const { - showLogo, - menuSetting: { - type: menuType, - mode, - theme, - collapsed, - collapsedShowTitle, - collapsedShowSearch, - accordion, - }, - } = unref(getProjectConfigRef); - - const isSidebarType = menuType === MenuTypeEnum.SIDEBAR; - const isShowLogo = showLogo && isSidebarType; - const themeData = props.theme || theme; return ( <BasicMenu - beforeClickFn={beforeMenuClickFn} - onMenuClick={handleMenuClick} - type={menuType} - mode={props.menuMode || mode} class="layout-menu" - collapsedShowTitle={collapsedShowTitle} - theme={themeData} - showLogo={isShowLogo} - search={unref(showSearchRef) && (collapsedShowSearch ? true : !collapsed)} + beforeClickFn={beforeMenuClickFn} + isHorizontal={props.isHorizontal} + appendClass={unref(appendClass)} + type={unref(getType)} + mode={unref(getMenuMode)} + collapsedShowTitle={unref(getCollapsedShowTitle)} + theme={unref(getMenuTheme)} + showLogo={unref(showLogo)} + search={unref(showSearch)} items={unref(menusRef)} flatItems={unref(flatMenusRef)} + accordion={unref(getAccordion)} + onMenuClick={handleMenuClick} onClickSearchInput={handleClickSearchInput} - appendClass={props.splitType === MenuSplitTyeEnum.TOP} - isTop={props.isTop} - accordion={accordion} > {{ - header: () => - isShowLogo && ( - <Logo - showTitle={!collapsed} - class={[`layout-menu__logo`, themeData]} - theme={themeData} - /> - ), + header: () => renderHeader(), }} </BasicMenu> ); diff --git a/src/layouts/default/menu/index.less b/src/layouts/default/menu/index.less index 181398f..721cc58 100644 --- a/src/layouts/default/menu/index.less +++ b/src/layouts/default/menu/index.less @@ -9,17 +9,5 @@ width: @logo-width; height: @logo-width; } - - &.light { - .logo-title { - color: @text-color-base; - } - } - - &.dark { - .logo-title { - color: @white; - } - } } } diff --git a/src/layouts/default/menu/useLayoutMenu.ts b/src/layouts/default/menu/useLayoutMenu.ts new file mode 100644 index 0000000..1df595a --- /dev/null +++ b/src/layouts/default/menu/useLayoutMenu.ts @@ -0,0 +1,113 @@ +import type { Menu } from '/@/router/types'; +import type { Ref } from 'vue'; + +import { watch, unref, ref, computed } from 'vue'; +import { useRouter } from 'vue-router'; + +import { MenuSplitTyeEnum } from '/@/enums/menuEnum'; +import { useThrottle } from '/@/hooks/core/useThrottle'; +import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; + +import { + getChildrenMenus, + getCurrentParentPath, + getFlatChildrenMenus, + getFlatMenus, + getMenus, + getShallowMenus, +} from '/@/router/menus'; +import { permissionStore } from '/@/store/modules/permission'; + +export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) { + // Menu array + const menusRef = ref<Menu[]>([]); + // flat menu array + const flatMenusRef = ref<Menu[]>([]); + + const { currentRoute } = useRouter(); + + const { setMenuSetting, getIsHorizontal, getSplit } = useMenuSetting(); + + const [throttleHandleSplitLeftMenu] = useThrottle(handleSplitLeftMenu, 50); + + const splitNotLeft = computed( + () => unref(splitType) !== MenuSplitTyeEnum.LEFT && !unref(getIsHorizontal) + ); + + const splitLeft = computed(() => !unref(getSplit) || unref(splitType) !== MenuSplitTyeEnum.LEFT); + + const spiltTop = computed(() => unref(splitType) === MenuSplitTyeEnum.TOP); + + const normalType = computed(() => { + return unref(splitType) === MenuSplitTyeEnum.NONE || !unref(getSplit); + }); + + watch( + [() => unref(currentRoute).path, () => unref(splitType)], + async ([path]: [string, MenuSplitTyeEnum]) => { + if (unref(splitNotLeft)) return; + + const parentPath = await getCurrentParentPath(path); + parentPath && throttleHandleSplitLeftMenu(parentPath); + }, + { + immediate: true, + } + ); + + // Menu changes + watch( + [() => permissionStore.getLastBuildMenuTimeState, () => permissionStore.getBackMenuListState], + () => { + genMenus(); + }, + { + immediate: true, + } + ); + + // split Menu changes + watch([() => getSplit.value], () => { + if (unref(splitNotLeft)) return; + genMenus(); + }); + + // Handle left menu split + async function handleSplitLeftMenu(parentPath: string) { + if (unref(splitLeft)) return; + + // spilt mode left + const children = await getChildrenMenus(parentPath); + if (!children) { + setMenuSetting({ hidden: false }); + flatMenusRef.value = []; + menusRef.value = []; + return; + } + + const flatChildren = await getFlatChildrenMenus(children); + setMenuSetting({ hidden: true }); + flatMenusRef.value = flatChildren; + menusRef.value = children; + } + + // get menus + async function genMenus() { + // normal mode + if (unref(normalType)) { + flatMenusRef.value = await getFlatMenus(); + menusRef.value = await getMenus(); + return; + } + + // split-top + if (unref(spiltTop)) { + const shallowMenus = await getShallowMenus(); + + flatMenusRef.value = shallowMenus; + menusRef.value = shallowMenus; + return; + } + } + return { flatMenusRef, menusRef }; +} diff --git a/src/layouts/default/multitabs/index.tsx b/src/layouts/default/multitabs/index.tsx index f91ae3b..714aa41 100644 --- a/src/layouts/default/multitabs/index.tsx +++ b/src/layouts/default/multitabs/index.tsx @@ -33,7 +33,7 @@ export default defineComponent({ return tabStore.getTabsState; }); - // If you monitor routing changes, tab switching will be stuck. So use this method + // If you monitor routing changes, tab switching will be stuck. So setting this method watch( () => tabStore.getLastChangeRouteState, () => { diff --git a/src/layouts/default/sider/LayoutSideBar.tsx b/src/layouts/default/sider/LayoutSideBar.tsx new file mode 100644 index 0000000..05e1b5f --- /dev/null +++ b/src/layouts/default/sider/LayoutSideBar.tsx @@ -0,0 +1,77 @@ +import './index.less'; + +import { computed, defineComponent, ref, unref } from 'vue'; + +import { Layout } from 'ant-design-vue'; +import LayoutMenu from '/@/layouts/default/menu/LayoutMenu'; + +import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum'; + +import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; +import { useTrigger, useDragLine, useSiderEvent } from './useLayoutSider'; + +export default defineComponent({ + name: 'LayoutSideBar', + setup() { + const dragBarRef = ref<Nullable<HTMLDivElement>>(null); + const sideRef = ref<Nullable<HTMLDivElement>>(null); + + const { getCollapsed, getMenuWidth, getSplit, getTheme } = useMenuSetting(); + + const { getTriggerAttr, getTriggerSlot } = useTrigger(); + + const { renderDragLine } = useDragLine(sideRef, dragBarRef); + + const { + getCollapsedWidth, + onBreakpointChange, + onCollapseChange, + onSiderClick, + } = useSiderEvent(); + + const getMode = computed(() => { + return unref(getSplit) ? MenuModeEnum.INLINE : null; + }); + + const getSplitType = computed(() => { + return unref(getSplit) ? MenuSplitTyeEnum.LEFT : MenuSplitTyeEnum.NONE; + }); + + function renderDefault() { + return ( + <> + <LayoutMenu + theme={unref(getTheme)} + menuMode={unref(getMode)} + splitType={unref(getSplitType)} + /> + {renderDragLine()} + </> + ); + } + + return () => { + return ( + <Layout.Sider + ref={sideRef} + class="layout-sidebar" + breakpoint="md" + collapsible + width={unref(getMenuWidth)} + collapsed={unref(getCollapsed)} + collapsedWidth={unref(getCollapsedWidth)} + theme={unref(getTheme)} + onClick={onSiderClick} + onCollapse={onCollapseChange} + onBreakpoint={onBreakpointChange} + {...unref(getTriggerAttr)} + > + {{ + ...unref(getTriggerSlot), + default: () => renderDefault(), + }} + </Layout.Sider> + ); + }; + }, +}); diff --git a/src/layouts/default/sider/index.less b/src/layouts/default/sider/index.less new file mode 100644 index 0000000..0e8d099 --- /dev/null +++ b/src/layouts/default/sider/index.less @@ -0,0 +1,44 @@ +@import (reference) '../../../design/index.less'; + +.layout-sidebar { + background-size: 100% 100%; + + &.ant-layout-sider-dark { + background: @sider-dark-bg-color; + } + + &:not(.ant-layout-sider-dark) { + border-right: 1px solid @border-color-light; + } + + .ant-layout-sider-zero-width-trigger { + top: 40%; + z-index: 10; + } + + &__darg-bar { + position: absolute; + top: 0; + right: -2px; + z-index: @side-drag-z-index; + width: 2px; + height: 100%; + cursor: col-resize; + border-top: none; + border-bottom: none; + + &.hide { + display: none; + } + + &:hover { + background: @primary-color; + box-shadow: 0 0 4px 0 rgba(28, 36, 56, 0.15); + } + } +} + +.ant-layout-sider-trigger { + height: 36px; + line-height: 36px; +} diff --git a/src/layouts/default/sider/useLayoutSider.tsx b/src/layouts/default/sider/useLayoutSider.tsx new file mode 100644 index 0000000..3e52c2a --- /dev/null +++ b/src/layouts/default/sider/useLayoutSider.tsx @@ -0,0 +1,163 @@ +import type { Ref } from 'vue'; + +import { computed, unref, onMounted, nextTick, ref } from 'vue'; +import LayoutTrigger from '/@/layouts/default/LayoutTrigger'; + +import { TriggerEnum } from '/@/enums/menuEnum'; + +import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; +import { useDebounce } from '/@/hooks/core/useDebounce'; + +/** + * Handle related operations of menu events + */ +export function useSiderEvent() { + const initRef = ref(false); + const brokenRef = ref(false); + const collapseRef = ref(true); + + const { setMenuSetting, getCollapsed, getMiniWidthNumber, getShow } = useMenuSetting(); + + const getCollapsedWidth = computed(() => { + return unref(brokenRef) ? 0 : unref(getMiniWidthNumber); + }); + + function onCollapseChange(val: boolean) { + if (initRef.value) { + collapseRef.value = val; + setMenuSetting({ collapsed: val }); + } else { + !unref(getCollapsed) && setMenuSetting({ collapsed: val }); + } + initRef.value = true; + } + + function onBreakpointChange(broken: boolean) { + brokenRef.value = broken; + } + + function onSiderClick(e: ChangeEvent) { + if (!e || !e.target || e.target.className !== 'basic-menu__content') return; + if (!unref(getCollapsed) || !unref(getShow)) return; + setMenuSetting({ collapsed: false }); + } + return { getCollapsedWidth, onCollapseChange, onBreakpointChange, onSiderClick }; +} + +/** + * Handle related operations of menu folding + */ +export function useTrigger() { + const { getTrigger } = useMenuSetting(); + + const showTrigger = computed(() => { + const trigger = unref(getTrigger); + return trigger !== TriggerEnum.NONE && trigger === TriggerEnum.FOOTER; + }); + + const getTriggerAttr = computed(() => { + if (unref(showTrigger)) { + return {}; + } + return { + trigger: null, + }; + }); + + const getTriggerSlot = computed(() => { + if (unref(showTrigger)) { + return { + trigger: () => <LayoutTrigger />, + }; + } + return {}; + }); + + return { getTriggerAttr, getTriggerSlot }; +} + +/** + * Handle menu drag and drop related operations + * @param siderRef + * @param dragBarRef + */ +export function useDragLine(siderRef: Ref<any>, dragBarRef: Ref<any>) { + const { getMiniWidthNumber, getCollapsed, setMenuSetting, getHasDrag } = useMenuSetting(); + + const getDragBarStyle = computed(() => { + if (unref(getCollapsed)) { + return { left: `${unref(getMiniWidthNumber)}px` }; + } + return {}; + }); + + onMounted(() => { + nextTick(() => { + const [exec] = useDebounce(changeWrapWidth, 20); + exec(); + }); + }); + + function renderDragLine() { + return ( + <div + class={[`layout-sidebar__darg-bar`, !unref(getHasDrag) ? 'hide' : '']} + style={unref(getDragBarStyle)} + ref={dragBarRef} + /> + ); + } + + function handleMouseMove(ele: HTMLElement, wrap: HTMLElement, clientX: number) { + document.onmousemove = function (innerE) { + let iT = (ele as any).left + (innerE.clientX - clientX); + innerE = innerE || window.event; + const maxT = 600; + const minT = unref(getMiniWidthNumber); + iT < 0 && (iT = 0); + iT > maxT && (iT = maxT); + iT < minT && (iT = minT); + ele.style.left = wrap.style.width = iT + 'px'; + return false; + }; + } + + // Drag and drop in the menu area-release the mouse + function removeMouseup(ele: any) { + const wrap = unref(siderRef).$el; + document.onmouseup = function () { + document.onmousemove = null; + document.onmouseup = null; + const width = parseInt(wrap.style.width); + const miniWidth = unref(getMiniWidthNumber); + + if (!unref(getCollapsed)) { + width > miniWidth + 20 + ? setMenuSetting({ menuWidth: width }) + : setMenuSetting({ collapsed: true }); + } else { + width > miniWidth && setMenuSetting({ collapsed: false, menuWidth: width }); + } + ele.releaseCapture?.(); + }; + } + + function changeWrapWidth() { + const ele = unref(dragBarRef) as any; + const side = unref(siderRef); + + const wrap = (side || {}).$el; + ele && + (ele.onmousedown = (e: any) => { + wrap.style.transition = 'unset'; + const clientX = e?.clientX; + ele.left = ele.offsetLeft; + handleMouseMove(ele, wrap, clientX); + removeMouseup(ele); + ele.setCapture?.(); + return false; + }); + } + + return { renderDragLine }; +} diff --git a/src/layouts/iframe/useFrameKeepAlive.ts b/src/layouts/iframe/useFrameKeepAlive.ts index 64ef99d..ceba512 100644 --- a/src/layouts/iframe/useFrameKeepAlive.ts +++ b/src/layouts/iframe/useFrameKeepAlive.ts @@ -5,12 +5,29 @@ import { useRouter } from 'vue-router'; import router from '/@/router'; import { tabStore } from '/@/store/modules/tab'; -import { appStore } from '/@/store/modules/app'; import { unique } from '/@/utils'; +import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting'; + export function useFrameKeepAlive() { const { currentRoute } = useRouter(); + const { getShow } = useMultipleTabSetting(); + + const getFramePages = computed(() => { + const ret = + getAllFramePages((toRaw(router.getRoutes()) as unknown) as AppRouteRecordRaw[]) || []; + return ret; + }); + + const getOpenTabList = computed((): string[] => { + return tabStore.getTabsState.reduce((prev: string[], next) => { + if (next.meta && Reflect.has(next.meta, 'frameSrc')) { + prev.push(next.path!); + } + return prev; + }, []); + }); function getAllFramePages(routes: AppRouteRecordRaw[]): AppRouteRecordRaw[] { let res: AppRouteRecordRaw[] = []; @@ -30,26 +47,9 @@ export function useFrameKeepAlive() { function showIframe(item: AppRouteRecordRaw) { return item.path === unref(currentRoute).path; } - const getFramePages = computed(() => { - const ret = - getAllFramePages((toRaw(router.getRoutes()) as unknown) as AppRouteRecordRaw[]) || []; - return ret; - }); - - const getOpenTabList = computed((): string[] => { - return tabStore.getTabsState.reduce((prev: string[], next) => { - if (next.meta && Reflect.has(next.meta, 'frameSrc')) { - prev.push(next.path!); - } - return prev; - }, []); - }); function hasRenderFrame(path: string) { - const { - multiTabsSetting: { show }, - } = appStore.getProjectConfig; - return show ? unref(getOpenTabList).includes(path) : true; + return unref(getShow) ? unref(getOpenTabList).includes(path) : true; } return { hasRenderFrame, getFramePages, showIframe, getAllFramePages }; } diff --git a/src/layouts/logo/index.vue b/src/layouts/logo/index.vue deleted file mode 100644 index f34d80d..0000000 --- a/src/layouts/logo/index.vue +++ /dev/null @@ -1,105 +0,0 @@ -<template> - <div class="app-logo anticon" :class="theme" @click="handleGoHome" :style="wrapStyle"> - <img src="/@/assets/images/logo.png" /> - <div v-if="show" class="logo-title ml-2 ellipsis">{{ globSetting.title }}</div> - </div> -</template> -<script lang="ts"> - import { computed, defineComponent, PropType, ref, watch } from 'vue'; - // hooks - import { useGlobSetting } from '/@/settings/use'; - import { useTimeoutFn } from '/@/hooks/core/useTimeout'; - import { useGo } from '/@/hooks/web/usePage'; - - import { PageEnum } from '/@/enums/pageEnum'; - import { MenuTypeEnum } from '/@/enums/menuEnum'; - - import { menuStore } from '/@/store/modules/menu'; - import { appStore } from '/@/store/modules/app'; - - export default defineComponent({ - name: 'Logo', - props: { - showTitle: { - type: Boolean as PropType<boolean>, - default: true, - }, - theme: { - type: String, - }, - }, - setup(props) { - const showRef = ref<boolean>(!!props.showTitle); - const globSetting = useGlobSetting(); - const go = useGo(); - - function handleGoHome() { - go(PageEnum.BASE_HOME); - } - - watch( - () => props.showTitle, - (show: boolean) => { - if (show) { - useTimeoutFn(() => { - showRef.value = show; - }, 280); - } else { - showRef.value = show; - } - } - ); - - const wrapStyle = computed(() => { - const { getCollapsedState } = menuStore; - const { - menuSetting: { menuWidth, type }, - } = appStore.getProjectConfig; - const miniWidth = { minWidth: `${menuWidth}px` }; - if (type !== MenuTypeEnum.SIDEBAR) { - return miniWidth; - } - return getCollapsedState ? {} : miniWidth; - }); - - return { - handleGoHome, - globSetting, - show: showRef, - wrapStyle, - }; - }, - }); -</script> -<style lang="less" scoped> - @import (reference) '../../design/index.less'; - - .app-logo { - display: flex; - align-items: center; - padding-left: 16px; - cursor: pointer; - // justify-content: center; - &.light { - border-bottom: 1px solid @border-color-base; - } - - .logo-title { - font-size: 18px; - font-weight: 700; - opacity: 0; - transition: all 0.5s; - .respond-to(medium,{ - opacity: 1; - }); - } - - // &.dark .logo-title { - // font-weight: 400; - // } - - &.light .logo-title { - color: @primary-color; - } - } -</style> diff --git a/src/layouts/page/index.tsx b/src/layouts/page/index.tsx index c6471b9..232c4ed 100644 --- a/src/layouts/page/index.tsx +++ b/src/layouts/page/index.tsx @@ -1,75 +1,78 @@ -import { computed, defineComponent, unref, Transition, KeepAlive, toRaw } from 'vue'; +import type { FunctionalComponent } from 'vue'; + +import { computed, defineComponent, unref, Transition, KeepAlive } from 'vue'; import { RouterView, RouteLocation } from 'vue-router'; import FrameLayout from '/@/layouts/iframe/index.vue'; import { useTransition } from './useTransition'; -import { useProjectSetting } from '/@/settings/use'; +import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; +import { useRootSetting } from '/@/hooks/setting/useRootSetting'; +import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting'; import { tabStore } from '/@/store/modules/tab'; -import { appStore } from '/@/store/modules/app'; + +interface DefaultContext { + Component: FunctionalComponent; + route: RouteLocation; +} export default defineComponent({ name: 'PageLayout', setup() { - const getProjectConfigRef = computed(() => appStore.getProjectConfig); - const openCacheRef = computed(() => { - const { - openKeepAlive, - multiTabsSetting: { show }, - } = unref(getProjectConfigRef); - return openKeepAlive && show; - }); - const getCacheTabsRef = computed(() => toRaw(tabStore.getKeepAliveTabsState) as string[]); + const { getShow } = useMenuSetting(); + const { + getOpenKeepAlive, + getRouterTransition, + getOpenRouterTransition, + getCanEmbedIFramePage, + } = useRootSetting(); - const { openPageLoading } = unref(getProjectConfigRef); + const { getMax } = useMultipleTabSetting(); - let on = {}; - if (openPageLoading) { - const { on: transitionOn } = useTransition(); - on = transitionOn; - } - const projectSetting = useProjectSetting(); - return () => { - const { - routerTransition, - openRouterTransition, - multiTabsSetting: { max }, - } = unref(getProjectConfigRef); + const transitionEvent = useTransition(); + + const openCacheRef = computed(() => unref(getOpenKeepAlive) && unref(getShow)); + const getCacheTabsRef = computed(() => tabStore.getKeepAliveTabsState as string[]); + + return () => { return ( <div> <RouterView> {{ - default: ({ Component, route }: { Component: any; route: RouteLocation }) => { + default: ({ Component, route }: DefaultContext) => { // No longer show animations that are already in the tab const cacheTabs = unref(getCacheTabsRef); const isInCache = cacheTabs.includes(route.name as string); const name = isInCache && route.meta.inTab ? 'fade' : null; - const Content = unref(openCacheRef) ? ( - <KeepAlive max={max} include={cacheTabs}> - <Component key={route.fullPath} /> + const renderComp = () => <Component key={route.fullPath} />; + + const PageContent = unref(openCacheRef) ? ( + <KeepAlive max={unref(getMax)} include={cacheTabs}> + {renderComp()} </KeepAlive> ) : ( - <Component key={route.fullPath} /> + renderComp() ); - return openRouterTransition ? ( + + return unref(getOpenRouterTransition) ? ( <Transition - {...on} - name={name || route.meta.transitionName || routerTransition} + {...transitionEvent} + name={name || route.meta.transitionName || unref(getRouterTransition)} mode="out-in" appear={true} > - {() => Content} + {() => PageContent} </Transition> ) : ( - Content + PageContent ); }, }} </RouterView> - {projectSetting.canEmbedIFramePage && <FrameLayout />} + {unref(getCanEmbedIFramePage) && <FrameLayout />} </div> ); }; diff --git a/src/layouts/page/useTransition.ts b/src/layouts/page/useTransition.ts index 2c1a594..6a2e622 100644 --- a/src/layouts/page/useTransition.ts +++ b/src/layouts/page/useTransition.ts @@ -1,10 +1,11 @@ +import { useRootSetting } from '/@/hooks/setting/useRootSetting'; import { appStore } from '/@/store/modules/app'; import { tryOnUnmounted } from '/@/utils/helper/vueHelper'; + export function useTransition() { function handleAfterEnter() { - const { openRouterTransition, openPageLoading } = appStore.getProjectConfig; - - if (!openRouterTransition || !openPageLoading) return; + const { getOpenPageLoading, getOpenRouterTransition } = useRootSetting(); + if (!getOpenPageLoading.value || !getOpenRouterTransition.value) return; // Close loading after the route switching animation ends appStore.setPageLoadingAction(false); } @@ -15,9 +16,6 @@ export function useTransition() { }); return { - handleAfterEnter, - on: { - onAfterEnter: handleAfterEnter, - }, + onAfterEnter: handleAfterEnter, }; } diff --git a/src/main.ts b/src/main.ts index 60de292..d7747a7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -49,7 +49,7 @@ if (isDevMode()) { window.__APP__ = app; } -// If you do not need to use the mock service in the production environment, you can comment the code +// If you do not need to setting the mock service in the production environment, you can comment the code if (isProdMode() && isUseMock()) { setupProdMockServer(); } diff --git a/src/router/guard/index.ts b/src/router/guard/index.ts index c3b1a26..efd5bf5 100644 --- a/src/router/guard/index.ts +++ b/src/router/guard/index.ts @@ -6,7 +6,7 @@ import { createProgressGuard } from './progressGuard'; import { createPermissionGuard } from './permissionGuard'; import { createPageLoadingGuard } from './pageLoadingGuard'; -import { useGlobSetting, useProjectSetting } from '/@/settings/use'; +import { useGlobSetting, useProjectSetting } from '/@/hooks/setting'; import { getIsOpenTab, setCurrentTo } from '/@/utils/helper/routeHelper'; import { setTitle } from '/@/utils/browser'; diff --git a/src/router/menus/index.ts b/src/router/menus/index.ts index 55b942b..4f3fd19 100644 --- a/src/router/menus/index.ts +++ b/src/router/menus/index.ts @@ -7,6 +7,7 @@ import { filter } from '/@/utils/helper/treeHelper'; import router from '/@/router'; import { PermissionModeEnum } from '/@/enums/appEnum'; import { pathToRegexp } from 'path-to-regexp'; + import modules from 'globby!/@/router/menus/modules/**/*.@(ts)'; const menuModules: MenuModule[] = []; @@ -44,7 +45,6 @@ async function getAsyncMenus() { // 获取深层扁平化菜单 export const getFlatMenus = async () => { const menus = await getAsyncMenus(); - return flatMenus(menus); }; diff --git a/src/settings/projectSetting.ts b/src/settings/projectSetting.ts index c3fa133..307ac8a 100644 --- a/src/settings/projectSetting.ts +++ b/src/settings/projectSetting.ts @@ -9,7 +9,7 @@ import { isProdMode } from '/@/utils/env'; const setting: ProjectConfig = { // locale setting locale: { - // Locales + // Locale lang: 'zh_CN', // Default locale fallback: 'zh_CN', @@ -29,17 +29,22 @@ const setting: ProjectConfig = { // Whether to show the configuration button showSettingButton: true, + // 权限模式 permissionMode: PermissionModeEnum.ROLE, + // 网站灰色模式,用于可能悼念的日期开启 grayMode: false, + // 色弱模式 colorWeak: false, // 是否取消菜单,顶部,多标签页显示, 用于可能内嵌在别的系统内 fullContent: false, + // content mode contentMode: ContentEnum.FULL, + // 是否显示logo showLogo: true, @@ -58,11 +63,10 @@ const setting: ProjectConfig = { showFullScreen: true, // 显示文档按钮 showDoc: true, - // 是否显示github - showGithub: true, // 显示消息中心按钮 showNotice: true, }, + // 菜单配置 menuSetting: { // 菜单折叠 @@ -108,13 +112,16 @@ const setting: ProjectConfig = { // 标签页缓存最大数量 max: 12, }, + // 是否开启KeepAlive缓存 开发时候最好关闭,不然每次都需要清除缓存 openKeepAlive: true, // 自动锁屏时间,为0不锁屏。 单位分钟 默认0 lockTime: 0, + // 显示面包屑 showBreadCrumb: true, + // 显示面包屑图标 showBreadCrumbIcon: false, diff --git a/src/settings/use/index.ts b/src/settings/use/index.ts deleted file mode 100644 index dccf7d7..0000000 --- a/src/settings/use/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { ProjectConfig, GlobConfig, GlobEnvConfig } from '/@/types/config'; - -import getProjectSetting from '/@/settings/projectSetting'; - -import { getShortName } from '../../../build/getShortName'; -import { warn } from '/@/utils/log'; -import { getGlobEnvConfig, isDevMode } from '/@/utils/env'; - -const reg = /[a-zA-Z\_]*/; - -const ENV_NAME = getShortName(import.meta.env); -const ENV = ((isDevMode() - ? getGlobEnvConfig() - : window[ENV_NAME as any]) as unknown) as GlobEnvConfig; - -const { - VITE_GLOB_APP_TITLE, - VITE_GLOB_API_URL, - VITE_GLOB_APP_SHORT_NAME, - VITE_GLOB_API_URL_PREFIX, -} = ENV; - -if (!reg.test(VITE_GLOB_APP_SHORT_NAME)) { - warn( - `VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.` - ); -} - -export const useGlobSetting = (): Readonly<GlobConfig> => { - // Take global configuration - const glob: Readonly<GlobConfig> = { - title: VITE_GLOB_APP_TITLE, - apiUrl: VITE_GLOB_API_URL, - shortName: VITE_GLOB_APP_SHORT_NAME, - urlPrefix: VITE_GLOB_API_URL_PREFIX, - }; - return glob as Readonly<GlobConfig>; -}; - -export const useProjectSetting = (): ProjectConfig => { - // TODO computed - return getProjectSetting; -}; diff --git a/src/settings/use/useLocaleSetting.ts b/src/settings/use/useLocaleSetting.ts deleted file mode 100644 index 8c12a0b..0000000 --- a/src/settings/use/useLocaleSetting.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { LocaleSetting } from '/@/types/config'; - -import { computed } from 'vue'; -import { appStore } from '/@/store/modules/app'; - -import getProjectSetting from '/@/settings/projectSetting'; -import { localeList } from '/@/locales'; - -export function useLocaleSetting() { - // Get locale configuration - const getLocale = computed(() => { - return appStore.getProjectConfig.locale || getProjectSetting.locale; - }); - - // get current language - const getLang = computed(() => { - return getLocale.value.lang; - }); - - // get Available Locales - const getAvailableLocales = computed((): string[] => { - return getLocale.value.availableLocales; - }); - - // get Fallback Locales - const getFallbackLocale = computed((): string => { - return getLocale.value.fallback; - }); - - // Set locale configuration - function setLocale(locale: Partial<LocaleSetting>): void { - appStore.commitProjectConfigState({ locale }); - } - - return { getLocale, getLang, localeList, setLocale, getAvailableLocales, getFallbackLocale }; -} diff --git a/src/setup/error-handle/index.ts b/src/setup/error-handle/index.ts index 0040de7..7b8bae6 100644 --- a/src/setup/error-handle/index.ts +++ b/src/setup/error-handle/index.ts @@ -3,7 +3,7 @@ */ import { errorStore, ErrorInfo } from '/@/store/modules/error'; -import { useProjectSetting } from '/@/settings/use'; +import { useProjectSetting } from '/@/hooks/setting'; import { ErrorTypeEnum } from '/@/enums/exceptionEnum'; import { App } from 'vue'; diff --git a/src/setup/i18n/index.ts b/src/setup/i18n/index.ts index 01e4bf3..c3e03b7 100644 --- a/src/setup/i18n/index.ts +++ b/src/setup/i18n/index.ts @@ -4,7 +4,7 @@ import type { I18n, I18nOptions } from 'vue-i18n'; import { createI18n } from 'vue-i18n'; import localeMessages from '/@/locales'; import { useLocale } from '/@/hooks/web/useLocale'; -import { useLocaleSetting } from '/@/settings/use/useLocaleSetting'; +import { useLocaleSetting } from '/@/hooks/setting/useLocaleSetting'; const { setupLocale } = useLocale(); diff --git a/src/store/modules/error.ts b/src/store/modules/error.ts index cd49760..64252c6 100644 --- a/src/store/modules/error.ts +++ b/src/store/modules/error.ts @@ -4,7 +4,7 @@ import { VuexModule, getModule, Module, Mutation, Action } from 'vuex-module-dec import { formatToDateTime } from '/@/utils/dateUtil'; import { ErrorTypeEnum } from '/@/enums/exceptionEnum'; -import { useProjectSetting } from '/@/settings/use'; +import { useProjectSetting } from '/@/hooks/setting'; export interface ErrorInfo { type: ErrorTypeEnum; diff --git a/src/store/modules/menu.ts b/src/store/modules/menu.ts deleted file mode 100644 index 5d3f01b..0000000 --- a/src/store/modules/menu.ts +++ /dev/null @@ -1,67 +0,0 @@ -import store from '/@/store'; -import { hotModuleUnregisterModule } from '/@/utils/helper/vuexHelper'; -import { VuexModule, Module, getModule, Mutation } from 'vuex-module-decorators'; - -import { appStore } from '/@/store/modules/app'; - -const NAME = 'menu'; -hotModuleUnregisterModule(NAME); -@Module({ namespaced: true, name: NAME, dynamic: true, store }) -class Menu extends VuexModule { - // 是否开始拖拽 - private dragStartState = false; - - private currentTopSplitMenuPathState = ''; - - /** - * @description: 获取窗口名称 - */ - get getCollapsedState() { - return appStore.getProjectConfig.menuSetting.collapsed; - } - - get getCurrentTopSplitMenuPathState() { - return this.currentTopSplitMenuPathState; - } - - get getDragStartState() { - return this.dragStartState; - } - - get getMenuWidthState() { - return appStore.getProjectConfig.menuSetting.menuWidth; - } - - @Mutation - commitDragStartState(dragStart: boolean): void { - this.dragStartState = dragStart; - } - - @Mutation - commitCurrentTopSplitMenuPathState(path: string): void { - this.currentTopSplitMenuPathState = path; - } - - // 改变菜单展开状态 - @Mutation - commitCollapsedState(collapsed: boolean): void { - // this.collapsedState = collapsed; - appStore.commitProjectConfigState({ - menuSetting: { - collapsed: collapsed, - }, - }); - } - - @Mutation - commitMenuWidthState(menuWidth: number): void { - // this.menuWidthState = menuWidth; - appStore.commitProjectConfigState({ - menuSetting: { - menuWidth: menuWidth, - }, - }); - } -} - -export const menuStore = getModule<Menu>(Menu); diff --git a/src/store/modules/permission.ts b/src/store/modules/permission.ts index 03d4cc1..7790601 100644 --- a/src/store/modules/permission.ts +++ b/src/store/modules/permission.ts @@ -97,11 +97,6 @@ class Permission extends VuexModule { if (!roles) return true; return roleList.some((role) => roles.includes(role)); }); - // this.commitRoutesState(routes); - // Background permissions - // warn( - // `当前权限模式为:${PermissionModeEnum.ROLE},请将src/store/modules/permission.ts内的后台菜单获取函数注释,如果已注释可以忽略此信息!` - // ); // 如果确定不需要做后台动态权限,请将下面整个判断注释 } else if (permissionMode === PermissionModeEnum.BACK) { const messageKey = 'loadMenu'; diff --git a/src/types/config.d.ts b/src/types/config.d.ts index 68ef0b9..c3484fc 100644 --- a/src/types/config.d.ts +++ b/src/types/config.d.ts @@ -44,7 +44,6 @@ export interface HeaderSetting { useLockPage: boolean; // 显示文档按钮 showDoc: boolean; - showGithub: boolean; // 显示消息中心按钮 showNotice: boolean; } diff --git a/src/utils/file/download.ts b/src/utils/file/download.ts index 3511486..fd519ae 100644 --- a/src/utils/file/download.ts +++ b/src/utils/file/download.ts @@ -1,3 +1,4 @@ +import { openWindow } from '..'; import { dataURLtoBlob, urlToBase64 } from './base64Conver'; /** @@ -93,6 +94,6 @@ export function downloadByUrl({ url += '?download'; } - window.open(url, target); + openWindow(url, { target }); return true; } diff --git a/src/utils/helper/envHelper.ts b/src/utils/helper/envHelper.ts index 3bacdff..0b98912 100644 --- a/src/utils/helper/envHelper.ts +++ b/src/utils/helper/envHelper.ts @@ -1,5 +1,5 @@ import { getEnv } from '/@/utils/env'; -import { useGlobSetting } from '/@/settings/use'; +import { useGlobSetting } from '/@/hooks/setting'; import pkg from '../../../package.json'; const globSetting = useGlobSetting(); diff --git a/src/utils/http/axios/index.ts b/src/utils/http/axios/index.ts index f0ebcc0..da8d08d 100644 --- a/src/utils/http/axios/index.ts +++ b/src/utils/http/axios/index.ts @@ -10,7 +10,7 @@ import { AxiosTransform } from './axiosTransform'; import { checkStatus } from './checkStatus'; -import { useGlobSetting } from '/@/settings/use'; +import { useGlobSetting } from '/@/hooks/setting'; import { useMessage } from '/@/hooks/web/useMessage'; import { RequestEnum, ResultEnum, ContentTypeEnum } from '/@/enums/httpEnum'; diff --git a/src/utils/index.ts b/src/utils/index.ts index e5ecd50..f935959 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -11,6 +11,7 @@ export function getPopupContainer(node?: HTMLElement): HTMLElement { } return document.body; } + /** * Add the object as a parameter to the URL * @param baseUrl url @@ -64,3 +65,16 @@ export function unique<T = any>(arr: T[], key: string): T[] { export function es6Unique<T>(arr: T[]): T[] { return Array.from(new Set(arr)); } + +export function openWindow( + url: string, + opt?: { target?: TargetContext | string; noopener?: boolean; noreferrer?: boolean } +) { + const { target = '__blank', noopener = true, noreferrer = true } = opt || {}; + const feature: string[] = []; + + noopener && feature.push('noopener=yes'); + noreferrer && feature.push('noreferrer=yes'); + + window.open(url, target, feature.join(',')); +} diff --git a/src/views/demo/feat/icon/index.vue b/src/views/demo/feat/icon/index.vue index 0ed56af..fa4a9dd 100644 --- a/src/views/demo/feat/icon/index.vue +++ b/src/views/demo/feat/icon/index.vue @@ -45,6 +45,8 @@ import Icon from '/@/components/Icon/index'; + import { openWindow } from '/@/utils'; + export default defineComponent({ components: { CollapseContainer, @@ -61,7 +63,7 @@ setup() { return { toIconify: () => { - window.open('https://iconify.design/', '__blank'); + openWindow('https://iconify.design/'); }, }; }, diff --git a/src/views/demo/page/form/high/index.vue b/src/views/demo/page/form/high/index.vue index ca1f050..d927971 100644 --- a/src/views/demo/page/form/high/index.vue +++ b/src/views/demo/page/form/high/index.vue @@ -16,23 +16,23 @@ </a-card> </div> - <AppFooterToolbar> + <AppPageFooter> <template #right> <a-button type="primary" @click="submitAll">提交</a-button> </template> - </AppFooterToolbar> + </AppPageFooter> </div> </template> <script lang="ts"> import { BasicForm, useForm } from '/@/components/Form'; import { defineComponent, ref } from 'vue'; import PersonTable from './PersonTable.vue'; - import { AppFooterToolbar } from '/@/components/Application'; + import { AppPageFooter } from '/@/components/Application'; import { schemas, taskSchemas } from './data'; export default defineComponent({ - components: { BasicForm, PersonTable, AppFooterToolbar }, + components: { BasicForm, PersonTable, AppPageFooter }, setup() { const tableRef = ref<{ getDataSource: () => any } | null>(null); diff --git a/src/views/sys/login/Login.vue b/src/views/sys/login/Login.vue index b18f62b..fe809ff 100644 --- a/src/views/sys/login/Login.vue +++ b/src/views/sys/login/Login.vue @@ -72,7 +72,7 @@ // import { appStore } from '/@/store/modules/app'; import { useMessage } from '/@/hooks/web/useMessage'; - import { useGlobSetting } from '/@/settings/use'; + import { useGlobSetting } from '/@/hooks/setting'; import logo from '/@/assets/images/logo.png'; export default defineComponent({ diff --git a/vite.config.ts b/vite.config.ts index 0f99d9b..4ce31d4 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -30,7 +30,7 @@ function pathResolve(dir: string) { const viteConfig: UserConfig = { /** - * Entry. Use this to specify a js entry file in use cases where an + * Entry. Use this to specify a js entry file in setting cases where an * `index.html` does not exist (e.g. serving vite assets from a different host) * @default 'index.html' */ @@ -51,7 +51,7 @@ const viteConfig: UserConfig = { */ open: false, /** - * Set to `false` to disable minification, or specify the minifier to use. + * Set to `false` to disable minification, or specify the minifier to setting. * Available options are 'terser' or 'esbuild'. * @default 'terser' */ @@ -112,7 +112,7 @@ const viteConfig: UserConfig = { }, define: { __VERSION__: pkg.version, - // use vue-i18-next + // setting vue-i18-next // Suppress warning __VUE_I18N_LEGACY_API__: false, __VUE_I18N_FULL_INSTALL__: false, -- libgit2 0.23.3