wip: refactor layout

This commit is contained in:
vben
2020-11-23 23:24:13 +08:00
parent 234c1d1fae
commit ba068ba1df
79 changed files with 1393 additions and 1196 deletions

View File

@@ -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>
);
};
},
});

View File

@@ -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>
);
};

View File

@@ -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>
);
},

View File

@@ -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>
);
};

View File

@@ -1,7 +1,6 @@
.lock-modal {
&__entry {
position: relative;
// width: 500px;
height: 240px;
padding: 130px 30px 60px 30px;
background: #fff;

View File

@@ -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}>
{() => '锁屏'}

View File

@@ -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>
);

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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',
// },
],
},
{

View File

@@ -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;
}

View File

@@ -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(() => {

View File

@@ -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>
);

View File

@@ -9,17 +9,5 @@
width: @logo-width;
height: @logo-width;
}
&.light {
.logo-title {
color: @text-color-base;
}
}
&.dark {
.logo-title {
color: @white;
}
}
}
}

View 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 };
}

View File

@@ -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,
() => {

View 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>
);
};
},
});

View 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;
}

View 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 };
}

View File

@@ -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 };
}

View File

@@ -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>

View File

@@ -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>
);
};

View File

@@ -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,
};
}