mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-01-23 17:50:25 +08:00
wip: refactor layout
This commit is contained in:
parent
234c1d1fae
commit
ba068ba1df
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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>
|
@ -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>
|
||||
|
72
src/components/Application/src/AppLogo.vue
Normal file
72
src/components/Application/src/AppLogo.vue
Normal file
@ -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>
|
46
src/components/Application/src/AppPageFooter.vue
Normal file
46
src/components/Application/src/AppPageFooter.vue
Normal file
@ -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>
|
@ -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();
|
||||
|
@ -33,7 +33,7 @@ export default defineComponent({
|
||||
});
|
||||
|
||||
/**
|
||||
* @description: Whether to use title
|
||||
* @description: Whether to setting title
|
||||
*/
|
||||
const useWrapper = computed(() => {
|
||||
return !!unref(getMergeProps).title;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 };
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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 };
|
||||
|
@ -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()}
|
||||
|
@ -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;
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { MenuModeEnum } from '/@/enums/menuEnum';
|
||||
import type { Menu as MenuType } from '/@/router/types';
|
||||
import type { MenuState } from './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';
|
||||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
|
||||
|
||||
export function useOpenKeys(
|
||||
menuState: MenuState,
|
||||
@ -16,6 +16,7 @@ export function useOpenKeys(
|
||||
mode: Ref<MenuModeEnum>,
|
||||
accordion: Ref<boolean>
|
||||
) {
|
||||
const { getCollapsed } = useMenuSetting();
|
||||
/**
|
||||
* @description:设置展开
|
||||
*/
|
||||
@ -49,7 +50,7 @@ export function useOpenKeys(
|
||||
rootSubMenuKeys.push(path);
|
||||
}
|
||||
}
|
||||
if (!menuStore.getCollapsedState || !unref(isAppMenu)) {
|
||||
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;
|
@ -1,5 +1,5 @@
|
||||
import type { Menu as MenuType } from '/@/router/types';
|
||||
import type { MenuState } from './types';
|
||||
import type { MenuState } from '../types';
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import { isString } from '/@/utils/is';
|
@ -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,
|
||||
},
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -86,7 +86,7 @@ export interface PaginationProps {
|
||||
size?: string;
|
||||
|
||||
/**
|
||||
* whether to use simple mode
|
||||
* whether to setting simple mode
|
||||
* @type boolean
|
||||
*/
|
||||
simple?: boolean;
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
3
src/components/types.ts
Normal file
3
src/components/types.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export type Component = ReturnType<typeof defineComponent>;
|
@ -16,4 +16,4 @@
|
||||
@page-loading-z-index: 10000;
|
||||
|
||||
// left-menu
|
||||
@app-menu-item-height: 44px;
|
||||
@app-menu-item-height: 42px;
|
||||
|
@ -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',
|
||||
}
|
||||
|
20
src/hooks/core/useDebouncedRef.ts
Normal file
20
src/hooks/core/useDebouncedRef.ts
Normal file
@ -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);
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
67
src/hooks/setting/useHeaderSetting.ts
Normal file
67
src/hooks/setting/useHeaderSetting.ts
Normal file
@ -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,
|
||||
};
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import type { LocaleSetting } from '/@/types/config';
|
||||
|
||||
import { computed } from 'vue';
|
||||
import { computed, unref } from 'vue';
|
||||
import { appStore } from '/@/store/modules/app';
|
||||
|
||||
import getProjectSetting from '/@/settings/projectSetting';
|
||||
@ -8,24 +8,16 @@ import { localeList } from '/@/locales';
|
||||
|
||||
export function useLocaleSetting() {
|
||||
// Get locale configuration
|
||||
const getLocale = computed(() => {
|
||||
return appStore.getProjectConfig.locale || getProjectSetting.locale;
|
||||
});
|
||||
const getLocale = computed(() => appStore.getProjectConfig.locale || getProjectSetting.locale);
|
||||
|
||||
// get current language
|
||||
const getLang = computed(() => {
|
||||
return getLocale.value.lang;
|
||||
});
|
||||
const getLang = computed(() => unref(getLocale).lang);
|
||||
|
||||
// get Available Locales
|
||||
const getAvailableLocales = computed((): string[] => {
|
||||
return getLocale.value.availableLocales;
|
||||
});
|
||||
const getAvailableLocales = computed((): string[] => unref(getLocale).availableLocales);
|
||||
|
||||
// get Fallback Locales
|
||||
const getFallbackLocale = computed((): string => {
|
||||
return getLocale.value.fallback;
|
||||
});
|
||||
const getFallbackLocale = computed((): string => unref(getLocale).fallback);
|
||||
|
||||
// Set locale configuration
|
||||
function setLocale(locale: Partial<LocaleSetting>): void {
|
120
src/hooks/setting/useMenuSetting.ts
Normal file
120
src/hooks/setting/useMenuSetting.ts
Normal file
@ -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,
|
||||
};
|
||||
}
|
25
src/hooks/setting/useMultipleTabSetting.ts
Normal file
25
src/hooks/setting/useMultipleTabSetting.ts
Normal file
@ -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,
|
||||
};
|
||||
}
|
53
src/hooks/setting/useRootSetting.ts
Normal file
53
src/hooks/setting/useRootSetting.ts
Normal file
@ -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,
|
||||
};
|
||||
}
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
@ -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>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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 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 { compile } from 'path-to-regexp';
|
||||
import Icon from '/@/components/Icon';
|
||||
import BreadcrumbItem from '/@/components/Breadcrumb/BreadcrumbItem.vue';
|
||||
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { isBoolean } from '/@/utils/is';
|
||||
import { compile } from 'path-to-regexp';
|
||||
|
||||
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>
|
||||
);
|
||||
},
|
||||
|
@ -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 { useRouter } from 'vue-router';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
|
||||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
|
||||
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
|
||||
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
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;
|
||||
|
||||
const isSidebarType = menuType === MenuTypeEnum.SIDEBAR;
|
||||
|
||||
function renderHeaderContent() {
|
||||
const width = unref(widthRef);
|
||||
return (
|
||||
<div class="layout-header__content ">
|
||||
{unref(getShowHeaderLogo) && (
|
||||
<AppLogo class={`layout-header__logo`} theme={unref(getTheme)} />
|
||||
)}
|
||||
|
||||
const showLeft =
|
||||
(mode !== MenuModeEnum.HORIZONTAL && showBreadCrumb && !splitMenu) ||
|
||||
unref(showHeaderTrigger);
|
||||
{unref(getShowContent) && (
|
||||
<div class="layout-header__left">
|
||||
{unref(getShowHeaderTrigger) && (
|
||||
<LayoutTrigger theme={unref(getTheme)} sider={false} />
|
||||
)}
|
||||
{unref(getShowBread) && <LayoutBreadcrumb showIcon={unref(getShowBreadCrumbIcon)} />}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{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>
|
||||
);
|
||||
}
|
||||
|
||||
function renderActionDefault(Comp: Component | any, event: Fn) {
|
||||
return (
|
||||
<div class={`layout-header__action-item`} onClick={event}>
|
||||
<Comp class={`layout-header__action-icon`} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function renderAction() {
|
||||
return (
|
||||
<div class={`layout-header__action`}>
|
||||
{unref(getUseErrorHandle) && (
|
||||
<Tooltip>
|
||||
{{
|
||||
title: () => '错误日志',
|
||||
default: () => (
|
||||
<Badge
|
||||
count={errorStore.getErrorListCountState}
|
||||
offset={[0, 10]}
|
||||
dot
|
||||
overflowCount={99}
|
||||
>
|
||||
{() => renderActionDefault(BugOutlined, handleToErrorList)}
|
||||
</Badge>
|
||||
),
|
||||
}}
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{unref(getUseLockPage) && (
|
||||
<Tooltip>
|
||||
{{
|
||||
title: () => '锁定屏幕',
|
||||
default: () => renderActionDefault(LockOutlined, handleLockPage),
|
||||
}}
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{unref(getShowNotice) && (
|
||||
<Tooltip>
|
||||
{{
|
||||
title: () => '消息通知',
|
||||
default: () => <NoticeAction />,
|
||||
}}
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{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)]}>
|
||||
{() => (
|
||||
<>
|
||||
<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>
|
||||
)}
|
||||
|
||||
{unref(showTopMenu) && (
|
||||
<div
|
||||
class={[`layout-header__menu `]}
|
||||
style={{ width: `calc(100% - ${unref(width)}px)` }}
|
||||
>
|
||||
<LayoutMenu
|
||||
isTop={true}
|
||||
class={`justify-${topMenuAlign}`}
|
||||
theme={headerTheme}
|
||||
splitType={splitMenu ? MenuSplitTyeEnum.TOP : MenuSplitTyeEnum.NONE}
|
||||
menuMode={splitMenu ? MenuModeEnum.HORIZONTAL : null}
|
||||
showSearch={false}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<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>
|
||||
)}
|
||||
|
||||
{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} />
|
||||
</>
|
||||
)}
|
||||
{() => renderHeaderDefault()}
|
||||
</Layout.Header>
|
||||
);
|
||||
};
|
||||
|
@ -1,7 +1,6 @@
|
||||
.lock-modal {
|
||||
&__entry {
|
||||
position: relative;
|
||||
// width: 500px;
|
||||
height: 240px;
|
||||
padding: 130px 30px 60px 30px;
|
||||
background: #fff;
|
||||
|
@ -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}>
|
||||
{() => '锁屏'}
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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',
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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(() => {
|
||||
|
@ -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 { AppLogo } from '/@/components/Application';
|
||||
|
||||
import { MenuModeEnum, MenuSplitTyeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
|
||||
import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum';
|
||||
|
||||
// store
|
||||
import { appStore } from '/@/store/modules/app';
|
||||
import { menuStore } from '/@/store/modules/menu';
|
||||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
|
||||
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
|
||||
|
||||
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 { 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();
|
||||
const go = useGo();
|
||||
|
||||
// get app config
|
||||
const getProjectConfigRef = computed(() => {
|
||||
return appStore.getProjectConfig;
|
||||
const {
|
||||
setMenuSetting,
|
||||
getShowSearch,
|
||||
getMode,
|
||||
getType,
|
||||
getCollapsedShowTitle,
|
||||
getCollapsedShowSearch,
|
||||
getIsSidebarType,
|
||||
getTheme,
|
||||
getCollapsed,
|
||||
getAccordion,
|
||||
} = useMenuSetting();
|
||||
|
||||
const { getShowLogo } = useRootSetting();
|
||||
|
||||
const { flatMenusRef, menusRef } = useSplitMenu(toRef(props, 'splitType'));
|
||||
|
||||
const showLogo = computed(() => unref(getShowLogo) && unref(getIsSidebarType));
|
||||
|
||||
const getMenuMode = computed(() => props.menuMode || unref(getMode));
|
||||
|
||||
const getMenuTheme = computed(() => props.theme || unref(getTheme));
|
||||
|
||||
const appendClass = computed(() => props.splitType === MenuSplitTyeEnum.TOP);
|
||||
|
||||
const showSearch = computed(() => {
|
||||
return (
|
||||
unref(getShowSearch) &&
|
||||
props.showSearch &&
|
||||
(unref(getCollapsedShowSearch) ? true : !unref(getCollapsed))
|
||||
);
|
||||
});
|
||||
|
||||
// get is Horizontal
|
||||
const getIsHorizontalRef = computed(() => {
|
||||
return unref(getProjectConfigRef).menuSetting.mode === MenuModeEnum.HORIZONTAL;
|
||||
});
|
||||
|
||||
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,
|
||||
}
|
||||
);
|
||||
|
||||
// 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();
|
||||
});
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// get menus
|
||||
async function genMenus() {
|
||||
const isSplitMenu = unref(getProjectConfigRef).menuSetting.split;
|
||||
|
||||
// normal mode
|
||||
const { splitType } = props;
|
||||
if (splitType === MenuSplitTyeEnum.NONE || !isSplitMenu) {
|
||||
flatMenusRef.value = await getFlatMenus();
|
||||
menusRef.value = await getMenus();
|
||||
return;
|
||||
}
|
||||
|
||||
// split-top
|
||||
if (splitType === MenuSplitTyeEnum.TOP) {
|
||||
const parentPath = await getCurrentParentPath(unref(currentRoute).path);
|
||||
menuStore.commitCurrentTopSplitMenuPathState(parentPath);
|
||||
const shallowMenus = await getShallowMenus();
|
||||
|
||||
flatMenusRef.value = shallowMenus;
|
||||
menusRef.value = shallowMenus;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>
|
||||
);
|
||||
|
@ -9,17 +9,5 @@
|
||||
width: @logo-width;
|
||||
height: @logo-width;
|
||||
}
|
||||
|
||||
&.light {
|
||||
.logo-title {
|
||||
color: @text-color-base;
|
||||
}
|
||||
}
|
||||
|
||||
&.dark {
|
||||
.logo-title {
|
||||
color: @white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
113
src/layouts/default/menu/useLayoutMenu.ts
Normal file
113
src/layouts/default/menu/useLayoutMenu.ts
Normal file
@ -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 };
|
||||
}
|
@ -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,
|
||||
() => {
|
||||
|
77
src/layouts/default/sider/LayoutSideBar.tsx
Normal file
77
src/layouts/default/sider/LayoutSideBar.tsx
Normal file
@ -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>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
44
src/layouts/default/sider/index.less
Normal file
44
src/layouts/default/sider/index.less
Normal file
@ -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;
|
||||
}
|
163
src/layouts/default/sider/useLayoutSider.tsx
Normal file
163
src/layouts/default/sider/useLayoutSider.tsx
Normal file
@ -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 };
|
||||
}
|
@ -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 };
|
||||
}
|
||||
|
@ -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>
|
@ -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();
|
||||
|
||||
const transitionEvent = useTransition();
|
||||
|
||||
const openCacheRef = computed(() => unref(getOpenKeepAlive) && unref(getShow));
|
||||
|
||||
const getCacheTabsRef = computed(() => tabStore.getKeepAliveTabsState as string[]);
|
||||
|
||||
let on = {};
|
||||
if (openPageLoading) {
|
||||
const { on: transitionOn } = useTransition();
|
||||
on = transitionOn;
|
||||
}
|
||||
const projectSetting = useProjectSetting();
|
||||
return () => {
|
||||
const {
|
||||
routerTransition,
|
||||
openRouterTransition,
|
||||
multiTabsSetting: { max },
|
||||
} = unref(getProjectConfigRef);
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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';
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
@ -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,
|
||||
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
@ -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';
|
||||
|
1
src/types/config.d.ts
vendored
1
src/types/config.d.ts
vendored
@ -44,7 +44,6 @@ export interface HeaderSetting {
|
||||
useLockPage: boolean;
|
||||
// 显示文档按钮
|
||||
showDoc: boolean;
|
||||
showGithub: boolean;
|
||||
// 显示消息中心按钮
|
||||
showNotice: boolean;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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';
|
||||
|
@ -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(','));
|
||||
}
|
||||
|
@ -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/');
|
||||
},
|
||||
};
|
||||
},
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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({
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user