mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-27 16:15:19 +08:00
initial commit
This commit is contained in:
93
src/layouts/default/LayoutBreadcrumb.tsx
Normal file
93
src/layouts/default/LayoutBreadcrumb.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import type { AppRouteRecordRaw } from '/@/router/types';
|
||||
import type { RouteLocationMatched } from 'vue-router';
|
||||
|
||||
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';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BasicBreadcrumb',
|
||||
setup() {
|
||||
const itemList = ref<AppRouteRecordRaw[]>([]);
|
||||
const { currentRoute, push } = useRouter();
|
||||
|
||||
function getBreadcrumb() {
|
||||
const { matched } = unref(currentRoute);
|
||||
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);
|
||||
}
|
||||
itemList.value = ((matchedList as any) as AppRouteRecordRaw[]).filter(
|
||||
(item) => item.meta && item.meta.title && !item.meta.hideBreadcrumb
|
||||
);
|
||||
}
|
||||
|
||||
function getHomeRoute(firstItem: RouteLocationMatched) {
|
||||
if (!firstItem || !firstItem.name) return false;
|
||||
const routes = router.getRoutes();
|
||||
const homeRoute = routes.find((item) => item.path === PageEnum.BASE_HOME);
|
||||
if (!homeRoute) return false;
|
||||
if (homeRoute.name === firstItem.name) return false;
|
||||
return homeRoute;
|
||||
}
|
||||
function pathCompile(path: string) {
|
||||
const { params } = unref(currentRoute);
|
||||
const toPath = compile(path);
|
||||
return toPath(params);
|
||||
}
|
||||
function handleItemClick(item: AppRouteRecordRaw) {
|
||||
const { redirect, path, meta } = item;
|
||||
if (meta.disabledRedirect) return;
|
||||
if (redirect) {
|
||||
push(redirect as string);
|
||||
return;
|
||||
}
|
||||
return push(pathCompile(path));
|
||||
}
|
||||
|
||||
watch(
|
||||
() => currentRoute.value,
|
||||
() => {
|
||||
if (unref(currentRoute).name === 'Redirect') return;
|
||||
getBreadcrumb();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
return () => (
|
||||
<>
|
||||
<Breadcrumb class="layout-breadcrumb">
|
||||
{() => (
|
||||
<>
|
||||
<TransitionGroup name="breadcrumb">
|
||||
{() => {
|
||||
return unref(itemList).map((item) => {
|
||||
const isLink = !!item.redirect && !item.meta.disabledRedirect;
|
||||
return (
|
||||
<BreadcrumbItem
|
||||
key={item.path}
|
||||
isLink={isLink}
|
||||
onClick={handleItemClick.bind(null, item)}
|
||||
>
|
||||
{() => item.meta.title}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
});
|
||||
}}
|
||||
</TransitionGroup>
|
||||
</>
|
||||
)}
|
||||
</Breadcrumb>
|
||||
</>
|
||||
);
|
||||
},
|
||||
});
|
25
src/layouts/default/LayoutContent.tsx
Normal file
25
src/layouts/default/LayoutContent.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { defineComponent } from 'vue';
|
||||
import { Layout } from 'ant-design-vue';
|
||||
// hooks
|
||||
|
||||
import { ContentEnum } from '/@/enums/appEnum';
|
||||
import { appStore } from '/@/store/modules/app';
|
||||
// import { RouterView } from 'vue-router';
|
||||
import PageLayout from '/@/layouts/page/index';
|
||||
export default defineComponent({
|
||||
name: 'DefaultLayoutContent',
|
||||
setup() {
|
||||
return () => {
|
||||
const { getProjectConfig } = appStore;
|
||||
const { contentMode } = getProjectConfig;
|
||||
const wrapClass = contentMode === ContentEnum.FULL ? 'full' : 'fixed';
|
||||
return (
|
||||
<Layout.Content class={`layout-content ${wrapClass} `}>
|
||||
{{
|
||||
default: () => <PageLayout />,
|
||||
}}
|
||||
</Layout.Content>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
154
src/layouts/default/LayoutHeader.tsx
Normal file
154
src/layouts/default/LayoutHeader.tsx
Normal file
@@ -0,0 +1,154 @@
|
||||
import { defineComponent, unref, computed } from 'vue';
|
||||
import { Layout, Tooltip } from 'ant-design-vue';
|
||||
import Logo from '/@/layouts/Logo.vue';
|
||||
import UserDropdown from './UserDropdown';
|
||||
import LayoutMenu from './LayoutMenu';
|
||||
import { appStore } from '/@/store/modules/app';
|
||||
import { MenuModeEnum, MenuSplitTyeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
|
||||
import LayoutBreadcrumb from './LayoutBreadcrumb';
|
||||
import {
|
||||
RedoOutlined,
|
||||
FullscreenExitOutlined,
|
||||
FullscreenOutlined,
|
||||
GithubFilled,
|
||||
LockOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import { useFullscreen } from '/@/hooks/web/useFullScreen';
|
||||
import { useTabs } from '/@/hooks/web/useTabs';
|
||||
import { GITHUB_URL } from '/@/settings/siteSetting';
|
||||
import LockAction from './actions/LockActionItem';
|
||||
import { useModal } from '/@/components/Modal/index';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DefaultLayoutHeader',
|
||||
setup() {
|
||||
const { refreshPage } = useTabs();
|
||||
const [register, { openModal }] = useModal();
|
||||
const { toggleFullscreen, isFullscreenRef } = useFullscreen();
|
||||
const getProjectConfigRef = computed(() => {
|
||||
return appStore.getProjectConfig;
|
||||
});
|
||||
|
||||
function goToGithub() {
|
||||
window.open(GITHUB_URL, '__blank');
|
||||
}
|
||||
|
||||
const headerClass = computed(() => {
|
||||
const theme = unref(getProjectConfigRef).headerSetting.theme;
|
||||
return theme ? `layout-header__header--${theme}` : '';
|
||||
});
|
||||
/**
|
||||
* @description: 锁定屏幕
|
||||
*/
|
||||
function handleLockPage() {
|
||||
openModal(true);
|
||||
}
|
||||
return () => {
|
||||
const getProjectConfig = unref(getProjectConfigRef);
|
||||
const {
|
||||
// useErrorHandle,
|
||||
showLogo,
|
||||
headerSetting: { theme: headerTheme, showRedo, showGithub, showFullScreen },
|
||||
menuSetting: { mode, type: menuType, split: splitMenu, topMenuAlign },
|
||||
showBreadCrumb,
|
||||
} = getProjectConfig;
|
||||
|
||||
const isSidebarType = menuType === MenuTypeEnum.SIDEBAR;
|
||||
return (
|
||||
<Layout.Header
|
||||
class={[
|
||||
'layout-header',
|
||||
'bg-white flex p-0 px-4 justify-items-center',
|
||||
unref(headerClass),
|
||||
]}
|
||||
>
|
||||
{() => (
|
||||
<>
|
||||
<div class="flex-grow flex justify-center items-center">
|
||||
{showLogo && !isSidebarType && <Logo class={`layout-header__logo`} />}
|
||||
|
||||
{mode !== MenuModeEnum.HORIZONTAL && showBreadCrumb && !splitMenu && (
|
||||
<LayoutBreadcrumb class="flex-grow " />
|
||||
)}
|
||||
{(mode === MenuModeEnum.HORIZONTAL || splitMenu) && (
|
||||
<div class={[`layout-header__menu flex-grow `, `justify-${topMenuAlign}`]}>
|
||||
<LayoutMenu
|
||||
theme={headerTheme}
|
||||
splitType={splitMenu ? MenuSplitTyeEnum.TOP : MenuSplitTyeEnum.NONE}
|
||||
menuMode={splitMenu ? MenuModeEnum.HORIZONTAL : null}
|
||||
showSearch={false}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div class={`layout-header__action`}>
|
||||
{showGithub && (
|
||||
// @ts-ignore
|
||||
<Tooltip>
|
||||
{{
|
||||
title: () => 'github',
|
||||
default: () => (
|
||||
<div class={`layout-header__action-item`} onClick={goToGithub}>
|
||||
<GithubFilled class={`layout-header__action-icon`} />
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
</Tooltip>
|
||||
)}
|
||||
{showGithub && (
|
||||
// @ts-ignore
|
||||
<Tooltip>
|
||||
{{
|
||||
title: () => '锁定屏幕',
|
||||
default: () => (
|
||||
<div class={`layout-header__action-item`} onClick={handleLockPage}>
|
||||
<LockOutlined class={`layout-header__action-icon`} />
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
</Tooltip>
|
||||
)}
|
||||
{showRedo && (
|
||||
// @ts-ignore
|
||||
<Tooltip>
|
||||
{{
|
||||
title: () => '刷新',
|
||||
default: () => (
|
||||
<div class={`layout-header__action-item`} onClick={refreshPage}>
|
||||
<RedoOutlined class={`layout-header__action-icon`} />
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
</Tooltip>
|
||||
)}
|
||||
{showFullScreen && (
|
||||
// @ts-ignore
|
||||
<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} />
|
||||
</>
|
||||
)}
|
||||
</Layout.Header>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
215
src/layouts/default/LayoutMenu.tsx
Normal file
215
src/layouts/default/LayoutMenu.tsx
Normal file
@@ -0,0 +1,215 @@
|
||||
import type { PropType } from 'vue';
|
||||
import type { Menu } from '/@/router/types';
|
||||
|
||||
import { computed, defineComponent, unref, ref, onMounted, watch } from 'vue';
|
||||
import { BasicMenu } from '/@/components/Menu/index';
|
||||
import Logo from '/@/layouts/Logo.vue';
|
||||
|
||||
import { PageEnum } from '/@/enums/pageEnum';
|
||||
import { MenuModeEnum, MenuSplitTyeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
|
||||
|
||||
// store
|
||||
import { appStore } from '/@/store/modules/app';
|
||||
import { menuStore } from '/@/store/modules/menu';
|
||||
|
||||
import {
|
||||
getMenus,
|
||||
getFlatMenus,
|
||||
getShallowMenus,
|
||||
getChildrenMenus,
|
||||
getFlatChildrenMenus,
|
||||
getCurrentParentPath,
|
||||
} from '/@/router/menus/index';
|
||||
import { useGo } from '/@/hooks/web/usePage';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useThrottle } from '/@/hooks/core/useThrottle';
|
||||
import { permissionStore } from '/@/store/modules/permission';
|
||||
|
||||
// import
|
||||
export default defineComponent({
|
||||
name: 'DefaultLayoutMenu',
|
||||
props: {
|
||||
theme: {
|
||||
type: String as PropType<string>,
|
||||
default: '',
|
||||
},
|
||||
splitType: {
|
||||
type: Number as PropType<MenuSplitTyeEnum>,
|
||||
default: MenuSplitTyeEnum.NONE,
|
||||
},
|
||||
parentMenuPath: {
|
||||
type: String as PropType<string>,
|
||||
default: '',
|
||||
},
|
||||
showSearch: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
menuMode: {
|
||||
type: [String] as PropType<MenuModeEnum | null>,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const menusRef = ref<Menu[]>([]);
|
||||
const flatMenusRef = ref<Menu[]>([]);
|
||||
const { currentRoute } = useRouter();
|
||||
|
||||
const getProjectConfigRef = computed(() => {
|
||||
return appStore.getProjectConfig;
|
||||
});
|
||||
|
||||
const getIsHorizontalRef = computed(() => {
|
||||
return unref(getProjectConfigRef).menuSetting.mode === MenuModeEnum.HORIZONTAL;
|
||||
});
|
||||
|
||||
const go = useGo();
|
||||
onMounted(() => {
|
||||
genMenus();
|
||||
});
|
||||
const [throttleHandleSplitLeftMenu] = useThrottle(handleSplitLeftMenu, 50);
|
||||
|
||||
// watch(
|
||||
// () => menuStore.getCurrentTopSplitMenuPathState,
|
||||
// async (parentPath: string) => {
|
||||
// throttleHandleSplitLeftMenu(parentPath);
|
||||
// }
|
||||
// );
|
||||
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,
|
||||
}
|
||||
);
|
||||
watch(
|
||||
[() => permissionStore.getLastBuildMenuTimeState, permissionStore.getBackMenuListState],
|
||||
() => {
|
||||
genMenus();
|
||||
}
|
||||
);
|
||||
|
||||
watch([() => appStore.getProjectConfig.menuSetting.split], () => {
|
||||
if (props.splitType !== MenuSplitTyeEnum.LEFT && !unref(getIsHorizontalRef)) return;
|
||||
genMenus();
|
||||
});
|
||||
|
||||
async function handleSplitLeftMenu(parentPath: string) {
|
||||
const isSplitMenu = unref(getProjectConfigRef).menuSetting.split;
|
||||
if (!isSplitMenu) return;
|
||||
const { splitType } = props;
|
||||
// 菜单分割模式-left
|
||||
if (splitType === MenuSplitTyeEnum.LEFT) {
|
||||
const children = await getChildrenMenus(parentPath);
|
||||
if (!children) return;
|
||||
const flatChildren = await getFlatChildrenMenus(children);
|
||||
flatMenusRef.value = flatChildren;
|
||||
menusRef.value = children;
|
||||
}
|
||||
}
|
||||
|
||||
async function genMenus() {
|
||||
const isSplitMenu = unref(getProjectConfigRef).menuSetting.split;
|
||||
|
||||
// 普通模式
|
||||
|
||||
const { splitType } = props;
|
||||
if (splitType === MenuSplitTyeEnum.NONE || !isSplitMenu) {
|
||||
flatMenusRef.value = await getFlatMenus();
|
||||
menusRef.value = await getMenus();
|
||||
return;
|
||||
}
|
||||
|
||||
// 菜单分割模式-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;
|
||||
}
|
||||
}
|
||||
|
||||
function handleMenuClick(menu: Menu) {
|
||||
const { path } = menu;
|
||||
if (path) {
|
||||
const { splitType } = props;
|
||||
// 菜单分割模式-top
|
||||
if (splitType === MenuSplitTyeEnum.TOP) {
|
||||
menuStore.commitCurrentTopSplitMenuPathState(path);
|
||||
}
|
||||
go(path as PageEnum);
|
||||
}
|
||||
}
|
||||
|
||||
async function beforeMenuClickFn(menu: Menu) {
|
||||
const { meta: { externalLink } = {} } = menu;
|
||||
|
||||
if (externalLink) {
|
||||
window.open(externalLink, '_blank');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function handleClickSearchInput() {
|
||||
if (menuStore.getCollapsedState) {
|
||||
menuStore.commitCollapsedState(false);
|
||||
}
|
||||
}
|
||||
|
||||
const showSearchRef = computed(() => {
|
||||
const { showSearch, type, mode } = unref(getProjectConfigRef).menuSetting;
|
||||
return (
|
||||
showSearch &&
|
||||
props.showSearch &&
|
||||
!(type === MenuTypeEnum.MIX && mode === MenuModeEnum.HORIZONTAL)
|
||||
);
|
||||
});
|
||||
|
||||
return () => {
|
||||
const {
|
||||
showLogo,
|
||||
menuSetting: { type: menuType, mode, theme, collapsed },
|
||||
} = 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"
|
||||
theme={themeData}
|
||||
showLogo={isShowLogo}
|
||||
search={unref(showSearchRef)}
|
||||
items={unref(menusRef)}
|
||||
flatItems={unref(flatMenusRef)}
|
||||
onClickSearchInput={handleClickSearchInput}
|
||||
appendClass={props.splitType === MenuSplitTyeEnum.TOP}
|
||||
>
|
||||
{{
|
||||
header: () =>
|
||||
isShowLogo && (
|
||||
<Logo
|
||||
showTitle={!collapsed}
|
||||
class={[`layout-menu__logo`, collapsed ? 'justify-center' : '', themeData]}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
</BasicMenu>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
193
src/layouts/default/LayoutSideBar.tsx
Normal file
193
src/layouts/default/LayoutSideBar.tsx
Normal file
@@ -0,0 +1,193 @@
|
||||
import { computed, defineComponent, nextTick, onMounted, ref, unref } from 'vue';
|
||||
|
||||
import { Layout } from 'ant-design-vue';
|
||||
import SideBarTrigger from './SideBarTrigger';
|
||||
import { menuStore } from '/@/store/modules/menu';
|
||||
|
||||
import darkMiniIMg from '/@/assets/images/sidebar/dark-mini.png';
|
||||
import lightMiniImg from '/@/assets/images/sidebar/light-mini.png';
|
||||
import darkImg from '/@/assets/images/sidebar/dark.png';
|
||||
import lightImg from '/@/assets/images/sidebar/light.png';
|
||||
import { appStore } from '/@/store/modules/app';
|
||||
import { MenuModeEnum, MenuSplitTyeEnum, MenuThemeEnum } from '/@/enums/menuEnum';
|
||||
import { useDebounce } from '/@/hooks/core/useDebounce';
|
||||
import LayoutMenu from './LayoutMenu';
|
||||
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 getStyle = computed((): any => {
|
||||
const collapse = unref(collapseRef);
|
||||
|
||||
const theme = unref(getProjectConfigRef).menuSetting.theme;
|
||||
let bg = '';
|
||||
if (theme === MenuThemeEnum.DARK) {
|
||||
bg = collapse ? darkMiniIMg : darkImg;
|
||||
}
|
||||
if (theme === MenuThemeEnum.LIGHT) {
|
||||
bg = collapse ? lightMiniImg : lightImg;
|
||||
}
|
||||
return {
|
||||
'background-image': `url(${bg})`,
|
||||
};
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 菜单区域拖拽 - 鼠标移动
|
||||
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 = 80;
|
||||
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 > 100) {
|
||||
setMenuWidth(width);
|
||||
} else {
|
||||
menuStore.commitCollapsedState(true);
|
||||
}
|
||||
} else {
|
||||
if (width > 80) {
|
||||
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;
|
||||
// const eleWidth = 6;
|
||||
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;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
const [exec] = useDebounce(changeWrapWidth, 20);
|
||||
exec();
|
||||
});
|
||||
});
|
||||
|
||||
const getDragBarStyle = computed(() => {
|
||||
if (menuStore.getCollapsedState) {
|
||||
return { left: '80px' };
|
||||
}
|
||||
return {};
|
||||
});
|
||||
|
||||
const getCollapsedWidth = computed(() => {
|
||||
return unref(brokenRef) ? 0 : 80;
|
||||
});
|
||||
|
||||
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;
|
||||
|
||||
return (
|
||||
<Layout.Sider
|
||||
onCollapse={onCollapseChange}
|
||||
breakpoint="md"
|
||||
width={getMenuWidthState}
|
||||
collapsed={getCollapsedState}
|
||||
collapsible
|
||||
collapsedWidth={unref(getCollapsedWidth)}
|
||||
theme={theme}
|
||||
class="layout-sidebar"
|
||||
ref={sideRef}
|
||||
onBreakpoint={handleBreakpoint}
|
||||
style={unref(getStyle)}
|
||||
>
|
||||
{{
|
||||
trigger: () => <SideBarTrigger />,
|
||||
default: () => (
|
||||
<>
|
||||
<LayoutMenu
|
||||
theme={theme}
|
||||
menuMode={splitMenu ? MenuModeEnum.INLINE : null}
|
||||
splitType={splitMenu ? MenuSplitTyeEnum.LEFT : MenuSplitTyeEnum.NONE}
|
||||
/>
|
||||
{renderDragLine()}
|
||||
</>
|
||||
),
|
||||
}}
|
||||
</Layout.Sider>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
12
src/layouts/default/SideBarTrigger.tsx
Normal file
12
src/layouts/default/SideBarTrigger.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { DoubleRightOutlined, DoubleLeftOutlined } from '@ant-design/icons-vue';
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
// store
|
||||
import { menuStore } from '/@/store/modules/menu';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SideBarTrigger',
|
||||
setup() {
|
||||
return () => (menuStore.getCollapsedState ? <DoubleRightOutlined /> : <DoubleLeftOutlined />);
|
||||
},
|
||||
});
|
103
src/layouts/default/UserDropdown.tsx
Normal file
103
src/layouts/default/UserDropdown.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
// components
|
||||
import { Dropdown, Menu, Divider } from 'ant-design-vue';
|
||||
|
||||
import { defineComponent, computed, unref } from 'vue';
|
||||
|
||||
// res
|
||||
import headerImg from '/@/assets/images/header.jpg';
|
||||
|
||||
import Icon from '/@/components/Icon/index';
|
||||
|
||||
import { userStore } from '/@/store/modules/user';
|
||||
|
||||
import { DOC_URL } from '/@/settings/siteSetting';
|
||||
import { appStore } from '/@/store/modules/app';
|
||||
|
||||
const prefixCls = 'user-dropdown';
|
||||
export default defineComponent({
|
||||
name: 'UserDropdown',
|
||||
setup() {
|
||||
const getProjectConfigRef = computed(() => {
|
||||
return appStore.getProjectConfig;
|
||||
});
|
||||
|
||||
/**
|
||||
* @description: 退出登录
|
||||
*/
|
||||
function handleLoginOut() {
|
||||
userStore.confirmLoginOut();
|
||||
}
|
||||
|
||||
// 打开文档
|
||||
function openDoc() {
|
||||
window.open(DOC_URL, '__blank');
|
||||
}
|
||||
|
||||
function handleMenuClick(e: any) {
|
||||
if (e.key === 'loginOut') {
|
||||
handleLoginOut();
|
||||
}
|
||||
if (e.key === 'doc') {
|
||||
openDoc();
|
||||
}
|
||||
}
|
||||
const getUserInfo = computed(() => {
|
||||
const { realName = '', desc } = userStore.getUserInfoState || {};
|
||||
return { realName, desc };
|
||||
});
|
||||
return () => {
|
||||
const { realName } = unref(getUserInfo);
|
||||
const {
|
||||
headerSetting: { showDoc },
|
||||
} = unref(getProjectConfigRef);
|
||||
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 && (
|
||||
<Menu.Item key="doc">
|
||||
{() => (
|
||||
<>
|
||||
<span class="flex items-center">
|
||||
<Icon icon="gg:loadbar-doc" class="mr-1" />
|
||||
<span>文档</span>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</Menu.Item>
|
||||
)}
|
||||
{showDoc && <Divider />}
|
||||
|
||||
<Menu.Item key="loginOut">
|
||||
{() => (
|
||||
<>
|
||||
<span class="flex items-center">
|
||||
<Icon icon="ant-design:poweroff-outlined" class="mr-1" />
|
||||
<span>退出系统</span>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
),
|
||||
}}
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
31
src/layouts/default/actions/LockActionItem.less
Normal file
31
src/layouts/default/actions/LockActionItem.less
Normal file
@@ -0,0 +1,31 @@
|
||||
.lock-modal {
|
||||
&__entry {
|
||||
position: relative;
|
||||
width: 500px;
|
||||
height: 240px;
|
||||
padding: 80px 30px 0 30px;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
&__header {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: calc(50% - 45px);
|
||||
width: auto;
|
||||
text-align: center;
|
||||
|
||||
&-img {
|
||||
width: 70px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&-name {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
82
src/layouts/default/actions/LockActionItem.tsx
Normal file
82
src/layouts/default/actions/LockActionItem.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
// 组件相关
|
||||
import { defineComponent } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal/index';
|
||||
|
||||
// hook
|
||||
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 [registerForm, { validateFields, resetFields }] = useForm({
|
||||
// 隐藏按钮
|
||||
showActionButtonGroup: false,
|
||||
// 表单项
|
||||
schemas: [
|
||||
{
|
||||
field: 'password',
|
||||
label: '锁屏密码',
|
||||
component: 'InputPassword',
|
||||
componentProps: {
|
||||
placeholder: '请输入锁屏密码',
|
||||
},
|
||||
rules: [{ required: true }],
|
||||
},
|
||||
],
|
||||
});
|
||||
/**
|
||||
* @description: lock
|
||||
*/
|
||||
async function lock(valid = true) {
|
||||
let password: string | undefined = '';
|
||||
|
||||
try {
|
||||
const values = (await validateFields()) as any;
|
||||
password = values.password;
|
||||
if (!valid) {
|
||||
password = undefined;
|
||||
}
|
||||
setModalProps({
|
||||
visible: false,
|
||||
});
|
||||
|
||||
appStore.commitLockInfoState({
|
||||
isLock: true,
|
||||
pwd: password,
|
||||
});
|
||||
resetFields();
|
||||
} catch (error) {}
|
||||
}
|
||||
// 账号密码登录
|
||||
return () => (
|
||||
<BasicModal footer={null} title="锁定屏幕" {...attrs} class={prefixCls} onRegister={register}>
|
||||
{() => (
|
||||
<div class={`${prefixCls}__entry`}>
|
||||
<div class={`${prefixCls}__header`}>
|
||||
<img src={headerImg} class={`${prefixCls}__header-img`} />
|
||||
<p class={`${prefixCls}__header-name`}>{userStore.getUserInfoState.realName}</p>
|
||||
</div>
|
||||
<BasicForm onRegister={registerForm} />
|
||||
<div class={`${prefixCls}__footer`}>
|
||||
<Button type="primary" block class="mt-2" onClick={lock}>
|
||||
{() => '锁屏'}
|
||||
</Button>
|
||||
<Button block class="mt-2" onClick={lock.bind(null, false)}>
|
||||
{() => ' 不设置密码锁屏'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</BasicModal>
|
||||
);
|
||||
},
|
||||
});
|
401
src/layouts/default/index.less
Normal file
401
src/layouts/default/index.less
Normal file
@@ -0,0 +1,401 @@
|
||||
@import (reference) '../../design/index.less';
|
||||
|
||||
.default-layout {
|
||||
&__content {
|
||||
position: relative;
|
||||
|
||||
&.fixed {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&__loading {
|
||||
position: absolute;
|
||||
z-index: @page-loading-z-index;
|
||||
}
|
||||
|
||||
&__main {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
// overflow: hidden;
|
||||
// overflow: auto;
|
||||
|
||||
&.fixed {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&.fixed.lock {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.layout-content {
|
||||
position: relative;
|
||||
// height: 100%;
|
||||
|
||||
&.fixed {
|
||||
width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.layout-menu {
|
||||
&__logo {
|
||||
height: @header-height;
|
||||
padding: 10px;
|
||||
|
||||
img {
|
||||
width: @logo-width;
|
||||
height: @logo-width;
|
||||
}
|
||||
|
||||
&.light {
|
||||
.logo-title {
|
||||
color: @text-color-base;
|
||||
}
|
||||
}
|
||||
|
||||
&.dark {
|
||||
.logo-title {
|
||||
color: @white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout-sidebar {
|
||||
background-size: 100% 100%;
|
||||
|
||||
.ant-layout-sider-zero-width-trigger {
|
||||
top: 40%;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
&__dargbar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -2px;
|
||||
z-index: @sider-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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.setting-button {
|
||||
top: 45%;
|
||||
right: 0;
|
||||
border-radius: 10px 0 0 10px;
|
||||
|
||||
.svg {
|
||||
width: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
&__tabs {
|
||||
z-index: 10;
|
||||
height: @multiple-height;
|
||||
padding: 0;
|
||||
line-height: @multiple-height;
|
||||
background: @border-color-shallow-light;
|
||||
box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
.setting-drawer {
|
||||
.ant-drawer-body {
|
||||
padding-top: 0;
|
||||
background: @white;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__cell-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
&__theme-item {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 16px 0;
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-top: 10px;
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
|
||||
svg {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.active {
|
||||
svg {
|
||||
display: inline-block;
|
||||
margin-left: 4px;
|
||||
font-size: 0.8em;
|
||||
fill: @white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__siderbar {
|
||||
display: flex;
|
||||
|
||||
> div {
|
||||
position: relative;
|
||||
|
||||
.check-icon {
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 40%;
|
||||
display: none;
|
||||
color: @primary-color;
|
||||
|
||||
&.active {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout-header {
|
||||
display: flex;
|
||||
height: @header-height;
|
||||
padding: 0 20px 0 0;
|
||||
color: @white;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
&__header--light {
|
||||
background: @white;
|
||||
border-bottom: 1px solid @header-light-bottom-border-color;
|
||||
|
||||
.layout-header__menu {
|
||||
height: calc(@header-height - 1px);
|
||||
|
||||
.ant-menu-submenu {
|
||||
height: @header-height;
|
||||
line-height: @header-height;
|
||||
}
|
||||
}
|
||||
|
||||
.layout-header__logo {
|
||||
height: @header-height;
|
||||
color: @text-color-base;
|
||||
|
||||
img {
|
||||
width: @logo-width;
|
||||
height: @logo-width;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: @header-light-bg-hover-color;
|
||||
}
|
||||
}
|
||||
|
||||
.layout-header__action {
|
||||
&-item {
|
||||
&:hover {
|
||||
background: @header-light-bg-hover-color;
|
||||
}
|
||||
}
|
||||
|
||||
&-icon {
|
||||
color: @text-color-base;
|
||||
}
|
||||
}
|
||||
|
||||
.layout-header__user-dropdown {
|
||||
&:hover {
|
||||
background: @header-light-bg-hover-color;
|
||||
}
|
||||
}
|
||||
|
||||
.user-dropdown {
|
||||
&__name {
|
||||
color: @text-color-base;
|
||||
}
|
||||
|
||||
&__desc {
|
||||
color: @header-light-desc-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__header--dark {
|
||||
background: @header-dark-bg-color;
|
||||
|
||||
.layout-header__action {
|
||||
&-item {
|
||||
&:hover {
|
||||
background: @header-dark-bg-hover-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout-header__logo {
|
||||
height: @header-height;
|
||||
|
||||
img {
|
||||
width: @logo-width;
|
||||
height: @logo-width;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: @header-dark-bg-hover-color;
|
||||
}
|
||||
}
|
||||
|
||||
.layout-header__user-dropdown {
|
||||
&:hover {
|
||||
background: @header-dark-bg-hover-color;
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
&__item:last-child .breadcrumb__inner,
|
||||
&__item:last-child &__inner a,
|
||||
&__item:last-child &__inner a:hover,
|
||||
&__item:last-child &__inner:hover {
|
||||
font-weight: 400;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
&__inner,
|
||||
&__separator {
|
||||
color: @white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-lm {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&__logo {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
&__bread {
|
||||
flex: 1;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: @header-height;
|
||||
font-size: 1.3em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&-icon {
|
||||
padding: 0 12px;
|
||||
}
|
||||
}
|
||||
|
||||
&__menu {
|
||||
display: flex;
|
||||
margin-left: 20px;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__user-dropdown {
|
||||
height: 52px;
|
||||
padding: 0 0 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-dropdown {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
width: @logo-width;
|
||||
height: @logo-width;
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
&__header {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&__divider {
|
||||
width: 1px;
|
||||
height: 30px;
|
||||
margin-right: 20px;
|
||||
background: #c6d9ee;
|
||||
}
|
||||
|
||||
&__exit {
|
||||
margin-top: -40px;
|
||||
font-size: 12px;
|
||||
color: #c6d9ee;
|
||||
text-align: center;
|
||||
|
||||
> section {
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
&__info {
|
||||
display: flex;
|
||||
margin-right: 12px;
|
||||
flex-direction: column;
|
||||
|
||||
> section {
|
||||
line-height: 1.8;
|
||||
}
|
||||
}
|
||||
|
||||
&__name {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&__desc {
|
||||
font-size: 12px;
|
||||
.text-truncate();
|
||||
}
|
||||
}
|
||||
|
||||
.layout-breadcrumb {
|
||||
padding: 0 16px;
|
||||
}
|
126
src/layouts/default/index.tsx
Normal file
126
src/layouts/default/index.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
import { defineComponent, unref, onMounted, computed } from 'vue';
|
||||
import { Layout, BackTop } from 'ant-design-vue';
|
||||
import LayoutHeader from './LayoutHeader';
|
||||
|
||||
import { appStore } from '/@/store/modules/app';
|
||||
import LayoutContent from './LayoutContent';
|
||||
import LayoutSideBar from './LayoutSideBar';
|
||||
import SettingBtn from './setting/index.vue';
|
||||
import MultipleTabs from './multitabs/index';
|
||||
import { FullLoading } from '/@/components/Loading/index';
|
||||
|
||||
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
|
||||
import { useFullContent } from '/@/hooks/web/useFullContent';
|
||||
|
||||
import LockPage from '/@/views/sys/lock/index.vue';
|
||||
|
||||
import './index.less';
|
||||
import { userStore } from '/@/store/modules/user';
|
||||
export default defineComponent({
|
||||
name: 'DefaultLayout',
|
||||
setup() {
|
||||
// 获取项目配置
|
||||
const { getFullContent } = useFullContent();
|
||||
|
||||
const getProjectConfigRef = computed(() => {
|
||||
return appStore.getProjectConfig;
|
||||
});
|
||||
const getLockMainScrollStateRef = computed(() => {
|
||||
return appStore.getLockMainScrollState;
|
||||
});
|
||||
|
||||
const showHeaderRef = computed(() => {
|
||||
const {
|
||||
headerSetting: { show },
|
||||
} = unref(getProjectConfigRef);
|
||||
return show;
|
||||
});
|
||||
const isShowMixHeaderRef = computed(() => {
|
||||
const {
|
||||
menuSetting: { type },
|
||||
} = unref(getProjectConfigRef);
|
||||
return type !== MenuTypeEnum.SIDEBAR && unref(showHeaderRef);
|
||||
});
|
||||
|
||||
const showSideBarRef = computed(() => {
|
||||
const {
|
||||
menuSetting: { show, mode },
|
||||
} = unref(getProjectConfigRef);
|
||||
return show && mode !== MenuModeEnum.HORIZONTAL && !unref(getFullContent);
|
||||
});
|
||||
|
||||
// const { currentRoute } = useRouter();
|
||||
onMounted(() => {
|
||||
// Each refresh will request the latest user information, if you don’t need it, you can delete it
|
||||
userStore.getUserInfoAction({ userId: userStore.getUserInfoState.userId });
|
||||
});
|
||||
|
||||
// Get project configuration
|
||||
// const { getFullContent } = useFullContent(currentRoute);
|
||||
function getTarget(): any {
|
||||
const {
|
||||
headerSetting: { fixed },
|
||||
} = unref(getProjectConfigRef);
|
||||
return document.querySelector(`.default-layout__${fixed ? 'main' : 'content'}`);
|
||||
}
|
||||
return () => {
|
||||
const { getPageLoading, getLockInfo } = appStore;
|
||||
const {
|
||||
openPageLoading,
|
||||
useOpenBackTop,
|
||||
showSettingButton,
|
||||
multiTabsSetting: { show: showTabs },
|
||||
headerSetting: { fixed },
|
||||
} = unref(getProjectConfigRef);
|
||||
// const fixedHeaderCls = fixed ? ('fixed' + getLockMainScrollState ? ' lock' : '') : '';
|
||||
const fixedHeaderCls = fixed
|
||||
? 'fixed' + (unref(getLockMainScrollStateRef) ? ' lock' : '')
|
||||
: '';
|
||||
const { isLock } = getLockInfo;
|
||||
return (
|
||||
<Layout class="default-layout relative">
|
||||
{() => (
|
||||
<>
|
||||
{isLock && <LockPage />}
|
||||
{!unref(getFullContent) && unref(isShowMixHeaderRef) && unref(showHeaderRef) && (
|
||||
<LayoutHeader />
|
||||
)}
|
||||
{showSettingButton && <SettingBtn />}
|
||||
<Layout>
|
||||
{() => (
|
||||
<>
|
||||
{unref(showSideBarRef) && <LayoutSideBar />}
|
||||
<Layout class={[`default-layout__content`, fixedHeaderCls]}>
|
||||
{() => (
|
||||
<>
|
||||
{!unref(getFullContent) &&
|
||||
!unref(isShowMixHeaderRef) &&
|
||||
unref(showHeaderRef) && <LayoutHeader />}
|
||||
|
||||
{showTabs && !unref(getFullContent) && (
|
||||
<Layout.Header class={`default-layout__tabs`}>
|
||||
{() => <MultipleTabs />}
|
||||
</Layout.Header>
|
||||
)}
|
||||
{useOpenBackTop && <BackTop target={getTarget} />}
|
||||
<div class={[`default-layout__main`, fixedHeaderCls]}>
|
||||
{openPageLoading && (
|
||||
<FullLoading
|
||||
class={[`default-layout__loading`, !getPageLoading && 'hidden']}
|
||||
/>
|
||||
)}
|
||||
<LayoutContent />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Layout>
|
||||
</>
|
||||
)}
|
||||
</Layout>
|
||||
</>
|
||||
)}
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
108
src/layouts/default/multitabs/TabContent.tsx
Normal file
108
src/layouts/default/multitabs/TabContent.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import { TabItem, tabStore } from '/@/store/modules/tab';
|
||||
import type { PropType } from 'vue';
|
||||
import { getScaleAction, TabContentProps } from './tab.data';
|
||||
|
||||
import { defineComponent, unref, computed } from 'vue';
|
||||
import { Dropdown } from '/@/components/Dropdown/index';
|
||||
import Icon from '/@/components/Icon/index';
|
||||
import { DoubleRightOutlined } from '@ant-design/icons-vue';
|
||||
import { appStore } from '/@/store/modules/app';
|
||||
|
||||
import { TabContentEnum } from './tab.data';
|
||||
import { useTabDropdown } from './useTabDropdown';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TabContent',
|
||||
props: {
|
||||
tabItem: {
|
||||
type: Object as PropType<TabItem>,
|
||||
default: null,
|
||||
},
|
||||
type: {
|
||||
type: Number as PropType<number>,
|
||||
default: TabContentEnum.TAB_TYPE,
|
||||
},
|
||||
trigger: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => {
|
||||
return ['contextmenu'];
|
||||
},
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const getProjectConfigRef = computed(() => {
|
||||
return appStore.getProjectConfig;
|
||||
});
|
||||
|
||||
const getIsScaleRef = computed(() => {
|
||||
const {
|
||||
menuSetting: { show: showMenu },
|
||||
headerSetting: { show: showHeader },
|
||||
} = unref(getProjectConfigRef);
|
||||
return !showMenu && !showHeader;
|
||||
});
|
||||
|
||||
function handleContextMenu(e: Event) {
|
||||
if (!props.tabItem) return;
|
||||
const tableItem = props.tabItem;
|
||||
e.preventDefault();
|
||||
const index = unref(tabStore.getTabsState).findIndex((tab) => tab.path === tableItem.path);
|
||||
|
||||
tabStore.commitCurrentContextMenuIndexState(index);
|
||||
tabStore.commitCurrentContextMenuState(props.tabItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 渲染图标
|
||||
*/
|
||||
function renderIcon() {
|
||||
const { tabItem } = props;
|
||||
if (!tabItem) return;
|
||||
const icon = tabItem.meta && tabItem.meta.icon;
|
||||
if (!icon || !unref(getProjectConfigRef).multiTabsSetting.showIcon) return null;
|
||||
return <Icon icon={icon} class="align-middle mb-1" />;
|
||||
}
|
||||
function renderTabContent() {
|
||||
const { tabItem: { meta } = {} } = props;
|
||||
return (
|
||||
<div class={`multiple-tabs-content__content `} onContextmenu={handleContextMenu}>
|
||||
{renderIcon()}
|
||||
<span class="ml-1">{meta && meta.title}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
function renderExtraContent() {
|
||||
return (
|
||||
<span class={`multiple-tabs-content__extra `}>
|
||||
<DoubleRightOutlined />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
const { getDropMenuList, handleMenuEvent } = useTabDropdown(props as TabContentProps);
|
||||
|
||||
return () => {
|
||||
const { trigger, type } = props;
|
||||
const {
|
||||
multiTabsSetting: { showQuick },
|
||||
} = unref(getProjectConfigRef);
|
||||
|
||||
const isTab = !showQuick ? true : type === TabContentEnum.TAB_TYPE;
|
||||
const scaleAction = getScaleAction(
|
||||
unref(getIsScaleRef) ? '缩小' : '放大',
|
||||
unref(getIsScaleRef)
|
||||
);
|
||||
const dropMenuList = unref(getDropMenuList) || [];
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
dropMenuList={!isTab ? [scaleAction, ...dropMenuList] : dropMenuList}
|
||||
trigger={isTab ? trigger : ['hover']}
|
||||
onMenuEvent={handleMenuEvent}
|
||||
>
|
||||
{() => (isTab ? renderTabContent() : renderExtraContent())}
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
135
src/layouts/default/multitabs/index.less
Normal file
135
src/layouts/default/multitabs/index.less
Normal file
@@ -0,0 +1,135 @@
|
||||
@import (reference) '../../../design/index.less';
|
||||
|
||||
.multiple-tabs {
|
||||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||
|
||||
.ant-tabs-small {
|
||||
height: @multiple-height;
|
||||
}
|
||||
|
||||
.ant-tabs.ant-tabs-card {
|
||||
.ant-tabs-card-bar {
|
||||
height: @multiple-height;
|
||||
margin: 0;
|
||||
background: @white;
|
||||
border: 0;
|
||||
box-shadow: 0 4px 26px 1px rgba(0, 0, 0, 0.08);
|
||||
|
||||
.ant-tabs-nav-container {
|
||||
height: @multiple-height;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.ant-tabs-tab {
|
||||
height: calc(@multiple-height - 2px);
|
||||
font-size: 14px;
|
||||
line-height: calc(@multiple-height - 2px);
|
||||
color: @text-color-call-out;
|
||||
background: @white;
|
||||
border: 1px solid @border-color-shallow-dark;
|
||||
border-radius: 4px 4px 0 0;
|
||||
transition: none;
|
||||
|
||||
.ant-tabs-close-x {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: @text-color-base;
|
||||
}
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 4px;
|
||||
background-color: @primary-color;
|
||||
border-radius: 16px 6px 0 0;
|
||||
content: '';
|
||||
transform: scaleX(0);
|
||||
transform-origin: bottom right;
|
||||
}
|
||||
|
||||
&:hover::before {
|
||||
transform: scaleX(1);
|
||||
transition: transform 0.4s ease;
|
||||
transform-origin: bottom left;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-tabs-tab-active {
|
||||
height: calc(@multiple-height - 3px);
|
||||
color: @white;
|
||||
// background: @primary-color;
|
||||
background: linear-gradient(
|
||||
118deg,
|
||||
rgba(@primary-color, 0.8),
|
||||
rgba(@primary-color, 1)
|
||||
) !important;
|
||||
border: 0;
|
||||
box-shadow: 0 0 6px 1px rgba(@primary-color, 0.4);
|
||||
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: @white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-tabs-nav > div:nth-child(1) {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.ant-tabs-tab-prev,
|
||||
.ant-tabs-tab-next {
|
||||
color: @border-color-dark;
|
||||
background: @white;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-tabs-tab:not(.ant-tabs-tab-active) {
|
||||
.anticon-close {
|
||||
font-size: 12px;
|
||||
|
||||
svg {
|
||||
width: 0.8em;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.anticon-close {
|
||||
color: @white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.multiple-tabs-content {
|
||||
&__extra {
|
||||
display: inline-block;
|
||||
width: @multiple-height;
|
||||
height: @multiple-height;
|
||||
line-height: @multiple-height;
|
||||
color: @primary-color;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||
|
||||
span[role='img'] {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
padding-left: 10px;
|
||||
margin-left: -10px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
131
src/layouts/default/multitabs/index.tsx
Normal file
131
src/layouts/default/multitabs/index.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
import type { TabContentProps } from './tab.data';
|
||||
import type { TabItem } from '/@/store/modules/tab';
|
||||
import type { AppRouteRecordRaw } from '/@/router/types';
|
||||
|
||||
import { defineComponent, watch, computed, ref, unref } from 'vue';
|
||||
import { Tabs } from 'ant-design-vue';
|
||||
import TabContent from './TabContent';
|
||||
|
||||
import { useGo } from '/@/hooks/web/usePage';
|
||||
|
||||
import { TabContentEnum } from './tab.data';
|
||||
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import './index.less';
|
||||
import { tabStore } from '/@/store/modules/tab';
|
||||
import { closeTab } from './useTabDropdown';
|
||||
import router from '/@/router';
|
||||
export default defineComponent({
|
||||
name: 'MultiTabs',
|
||||
setup() {
|
||||
let isAddAffix = false;
|
||||
const go = useGo();
|
||||
const { currentRoute } = useRouter();
|
||||
// 当前激活tab
|
||||
const activeKeyRef = ref<string>('');
|
||||
// 当前tab列表
|
||||
const getTabsState = computed(() => {
|
||||
return tabStore.getTabsState;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => unref(currentRoute).path,
|
||||
(path) => {
|
||||
if (!isAddAffix) {
|
||||
addAffixTabs();
|
||||
isAddAffix = true;
|
||||
}
|
||||
activeKeyRef.value = path;
|
||||
|
||||
tabStore.commitAddTab((unref(currentRoute) as unknown) as AppRouteRecordRaw);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
/**
|
||||
* @description: 过滤所有固定路由
|
||||
*/
|
||||
function filterAffixTabs(routes: AppRouteRecordRaw[]) {
|
||||
const tabs: TabItem[] = [];
|
||||
routes &&
|
||||
routes.forEach((route) => {
|
||||
if (route.meta && route.meta.affix) {
|
||||
tabs.push({
|
||||
path: route.path,
|
||||
name: route.name,
|
||||
meta: { ...route.meta },
|
||||
});
|
||||
}
|
||||
});
|
||||
return tabs;
|
||||
}
|
||||
/**
|
||||
* @description: 设置固定tabs
|
||||
*/
|
||||
function addAffixTabs(): void {
|
||||
const affixTabs = filterAffixTabs((router.getRoutes() as unknown) as AppRouteRecordRaw[]);
|
||||
for (const tab of affixTabs) {
|
||||
tabStore.commitAddTab(tab);
|
||||
}
|
||||
}
|
||||
|
||||
// tab切换
|
||||
function handleChange(activeKey: any) {
|
||||
activeKeyRef.value = activeKey;
|
||||
go(activeKey, false);
|
||||
}
|
||||
// 关闭当前ab
|
||||
function handleEdit(targetKey: string) {
|
||||
// 新增操作隐藏,目前只使用删除操作
|
||||
const index = unref(getTabsState).findIndex((item) => item.path === targetKey);
|
||||
index !== -1 && closeTab(unref(getTabsState)[index]);
|
||||
}
|
||||
|
||||
function renderQuick() {
|
||||
const tabContentProps: TabContentProps = {
|
||||
tabItem: (currentRoute as unknown) as AppRouteRecordRaw,
|
||||
type: TabContentEnum.EXTRA_TYPE,
|
||||
trigger: ['click', 'contextmenu'],
|
||||
};
|
||||
return (
|
||||
<span>
|
||||
<TabContent {...tabContentProps} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
function renderTabs() {
|
||||
return unref(getTabsState).map((item: TabItem) => {
|
||||
return (
|
||||
<Tabs.TabPane key={item.path} closable={!(item && item.meta && item.meta.affix)}>
|
||||
{{
|
||||
tab: () => <TabContent tabItem={item} />,
|
||||
}}
|
||||
</Tabs.TabPane>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<div class="multiple-tabs">
|
||||
<Tabs
|
||||
type="editable-card"
|
||||
size="small"
|
||||
hideAdd={true}
|
||||
tabBarGutter={2}
|
||||
activeKey={unref(activeKeyRef)}
|
||||
onChange={handleChange}
|
||||
onEdit={handleEdit}
|
||||
>
|
||||
{{
|
||||
default: () => renderTabs(),
|
||||
tabBarExtraContent: () => renderQuick(),
|
||||
}}
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
84
src/layouts/default/multitabs/tab.data.ts
Normal file
84
src/layouts/default/multitabs/tab.data.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { DropMenu } from '/@/components/Dropdown/index';
|
||||
import { AppRouteRecordRaw } from '/@/router/types';
|
||||
import type { TabItem } from '/@/store/modules/tab';
|
||||
|
||||
export enum TabContentEnum {
|
||||
TAB_TYPE,
|
||||
EXTRA_TYPE,
|
||||
}
|
||||
export interface TabContentProps {
|
||||
tabItem: TabItem | AppRouteRecordRaw;
|
||||
type?: TabContentEnum;
|
||||
trigger?: Array<'click' | 'hover' | 'contextmenu'>;
|
||||
}
|
||||
/**
|
||||
* @description: 右键:下拉菜单文字
|
||||
*/
|
||||
export enum MenuEventEnum {
|
||||
// 刷新
|
||||
REFRESH_PAGE,
|
||||
// 关闭当前
|
||||
CLOSE_CURRENT,
|
||||
// 关闭左侧
|
||||
CLOSE_LEFT,
|
||||
// 关闭右侧
|
||||
CLOSE_RIGHT,
|
||||
// 关闭其他
|
||||
CLOSE_OTHER,
|
||||
// 关闭所有
|
||||
CLOSE_ALL,
|
||||
// 放大
|
||||
SCALE,
|
||||
}
|
||||
|
||||
export function getActions() {
|
||||
const REFRESH_PAGE: DropMenu = {
|
||||
icon: 'ant-design:reload-outlined',
|
||||
event: MenuEventEnum.REFRESH_PAGE,
|
||||
text: '刷新',
|
||||
disabled: false,
|
||||
};
|
||||
const CLOSE_CURRENT: DropMenu = {
|
||||
icon: 'ant-design:close-outlined',
|
||||
event: MenuEventEnum.CLOSE_CURRENT,
|
||||
text: '关闭',
|
||||
disabled: false,
|
||||
divider: true,
|
||||
};
|
||||
const CLOSE_LEFT: DropMenu = {
|
||||
icon: 'ant-design:pic-left-outlined',
|
||||
event: MenuEventEnum.CLOSE_LEFT,
|
||||
text: '关闭左侧',
|
||||
disabled: false,
|
||||
divider: false,
|
||||
};
|
||||
const CLOSE_RIGHT: DropMenu = {
|
||||
icon: 'ant-design:pic-right-outlined',
|
||||
event: MenuEventEnum.CLOSE_RIGHT,
|
||||
text: '关闭右侧',
|
||||
disabled: false,
|
||||
divider: true,
|
||||
};
|
||||
const CLOSE_OTHER: DropMenu = {
|
||||
icon: 'ant-design:pic-center-outlined',
|
||||
event: MenuEventEnum.CLOSE_OTHER,
|
||||
text: '关闭其他',
|
||||
disabled: false,
|
||||
};
|
||||
const CLOSE_ALL: DropMenu = {
|
||||
icon: 'ant-design:line-outlined',
|
||||
event: MenuEventEnum.CLOSE_ALL,
|
||||
text: '关闭全部',
|
||||
disabled: false,
|
||||
};
|
||||
return [REFRESH_PAGE, CLOSE_CURRENT, CLOSE_LEFT, CLOSE_RIGHT, CLOSE_OTHER, CLOSE_ALL];
|
||||
}
|
||||
|
||||
export function getScaleAction(text: string, isZoom = false) {
|
||||
return {
|
||||
icon: isZoom ? 'codicon:screen-normal' : 'codicon:screen-full',
|
||||
event: MenuEventEnum.SCALE,
|
||||
text: text,
|
||||
disabled: false,
|
||||
};
|
||||
}
|
227
src/layouts/default/multitabs/useTabDropdown.ts
Normal file
227
src/layouts/default/multitabs/useTabDropdown.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
import type { AppRouteRecordRaw } from '/@/router/types';
|
||||
import type { TabContentProps } from './tab.data';
|
||||
import type { Ref } from 'vue';
|
||||
import type { TabItem } from '/@/store/modules/tab';
|
||||
import type { DropMenu } from '/@/components/Dropdown';
|
||||
|
||||
import { computed, unref } from 'vue';
|
||||
import { TabContentEnum, MenuEventEnum, getActions } from './tab.data';
|
||||
import { tabStore } from '/@/store/modules/tab';
|
||||
import { appStore } from '/@/store/modules/app';
|
||||
import { PageEnum } from '/@/enums/pageEnum';
|
||||
import { useGo, useRedo } from '/@/hooks/web/usePage';
|
||||
import router from '/@/router';
|
||||
import { useTabs, isInitUseTab } from '/@/hooks/web/useTabs';
|
||||
|
||||
const { initTabFn } = useTabs();
|
||||
/**
|
||||
* @description: 右键下拉
|
||||
*/
|
||||
export function useTabDropdown(tabContentProps: TabContentProps) {
|
||||
const { currentRoute } = router;
|
||||
const redo = useRedo();
|
||||
const go = useGo();
|
||||
|
||||
const isTabsRef = computed(() => tabContentProps.type === TabContentEnum.TAB_TYPE);
|
||||
const getCurrentTab: Ref<TabItem | AppRouteRecordRaw> = computed(() => {
|
||||
return unref(isTabsRef)
|
||||
? tabContentProps.tabItem
|
||||
: ((unref(currentRoute) as any) as AppRouteRecordRaw);
|
||||
});
|
||||
|
||||
// 当前tab列表
|
||||
const getTabsState = computed(() => {
|
||||
return tabStore.getTabsState;
|
||||
});
|
||||
|
||||
/**
|
||||
* @description: 下拉列表
|
||||
*/
|
||||
const getDropMenuList = computed(() => {
|
||||
const dropMenuList = getActions();
|
||||
// 重置为初始状态
|
||||
for (const item of dropMenuList) {
|
||||
item.disabled = false;
|
||||
}
|
||||
|
||||
// 没有tab
|
||||
if (!unref(getTabsState) || unref(getTabsState).length <= 0) {
|
||||
return dropMenuList;
|
||||
} else if (unref(getTabsState).length === 1) {
|
||||
// 只有一个tab
|
||||
for (const item of dropMenuList) {
|
||||
if (item.event !== MenuEventEnum.REFRESH_PAGE) {
|
||||
item.disabled = true;
|
||||
}
|
||||
}
|
||||
return dropMenuList;
|
||||
}
|
||||
if (!unref(getCurrentTab)) {
|
||||
return;
|
||||
}
|
||||
const { meta, path } = unref(getCurrentTab);
|
||||
// console.log(unref(getCurrentTab));
|
||||
|
||||
// 刷新按钮
|
||||
const curItem = tabStore.getCurrentContextMenuState;
|
||||
const index = tabStore.getCurrentContextMenuIndexState;
|
||||
const refreshDisabled = curItem ? curItem.path !== path : true;
|
||||
// 关闭左侧
|
||||
const closeLeftDisabled = index === 0;
|
||||
|
||||
// 关闭右侧
|
||||
const closeRightDisabled = index === unref(getTabsState).length - 1;
|
||||
// 当前为固定tab
|
||||
dropMenuList[0].disabled = unref(isTabsRef) ? refreshDisabled : false;
|
||||
if (meta && meta.affix) {
|
||||
dropMenuList[1].disabled = true;
|
||||
}
|
||||
dropMenuList[2].disabled = closeLeftDisabled;
|
||||
dropMenuList[3].disabled = closeRightDisabled;
|
||||
|
||||
return dropMenuList;
|
||||
});
|
||||
|
||||
/**
|
||||
* @description: 关闭所有页面时,跳转页面
|
||||
*/
|
||||
function gotoPage() {
|
||||
const len = unref(getTabsState).length;
|
||||
const { path } = unref(currentRoute);
|
||||
|
||||
let toPath: PageEnum | string = PageEnum.BASE_HOME;
|
||||
|
||||
if (len > 0) {
|
||||
toPath = unref(getTabsState)[len - 1].path;
|
||||
}
|
||||
// 跳到当前页面报错
|
||||
path !== toPath && go(toPath as PageEnum, true);
|
||||
}
|
||||
|
||||
function isGotoPage(currentTab?: TabItem) {
|
||||
const { path } = unref(currentRoute);
|
||||
const currentPath = (currentTab || unref(getCurrentTab)).path;
|
||||
// 不是当前tab,关闭左侧/右侧时,需跳转页面
|
||||
if (path !== currentPath) {
|
||||
go(currentPath as PageEnum, true);
|
||||
}
|
||||
}
|
||||
function refreshPage(tabItem?: TabItem) {
|
||||
try {
|
||||
tabStore.commitCloseTabKeepAlive(tabItem || unref(getCurrentTab));
|
||||
} catch (error) {}
|
||||
redo();
|
||||
}
|
||||
function closeAll() {
|
||||
tabStore.commitCloseAllTab();
|
||||
gotoPage();
|
||||
}
|
||||
function closeLeft(tabItem?: TabItem) {
|
||||
tabStore.closeLeftTabAction(tabItem || unref(getCurrentTab));
|
||||
isGotoPage(tabItem);
|
||||
}
|
||||
function closeRight(tabItem?: TabItem) {
|
||||
tabStore.closeRightTabAction(tabItem || unref(getCurrentTab));
|
||||
isGotoPage(tabItem);
|
||||
}
|
||||
function closeOther(tabItem?: TabItem) {
|
||||
tabStore.closeOtherTabAction(tabItem || unref(getCurrentTab));
|
||||
isGotoPage(tabItem);
|
||||
}
|
||||
function closeCurrent(tabItem?: TabItem) {
|
||||
closeTab(unref(tabItem || unref(getCurrentTab)));
|
||||
}
|
||||
function scaleScreen() {
|
||||
const {
|
||||
headerSetting: { show: showHeader },
|
||||
menuSetting: { show: showMenu },
|
||||
} = appStore.getProjectConfig;
|
||||
const isScale = !showHeader && !showMenu;
|
||||
appStore.commitProjectConfigState({
|
||||
headerSetting: { show: isScale },
|
||||
menuSetting: { show: isScale },
|
||||
});
|
||||
}
|
||||
|
||||
if (!isInitUseTab) {
|
||||
initTabFn({
|
||||
refreshPageFn: refreshPage,
|
||||
closeAllFn: closeAll,
|
||||
closeCurrentFn: closeCurrent,
|
||||
closeLeftFn: closeLeft,
|
||||
closeOtherFn: closeOther,
|
||||
closeRightFn: closeRight,
|
||||
});
|
||||
}
|
||||
|
||||
// 处理右键事件
|
||||
function handleMenuEvent(menu: DropMenu): void {
|
||||
const { event } = menu;
|
||||
|
||||
switch (event) {
|
||||
case MenuEventEnum.SCALE:
|
||||
scaleScreen();
|
||||
break;
|
||||
case MenuEventEnum.REFRESH_PAGE:
|
||||
// 刷新页面
|
||||
refreshPage();
|
||||
break;
|
||||
// 关闭当前
|
||||
case MenuEventEnum.CLOSE_CURRENT:
|
||||
closeCurrent();
|
||||
break;
|
||||
// 关闭左侧
|
||||
case MenuEventEnum.CLOSE_LEFT:
|
||||
closeLeft();
|
||||
break;
|
||||
// 关闭右侧
|
||||
case MenuEventEnum.CLOSE_RIGHT:
|
||||
closeRight();
|
||||
break;
|
||||
// 关闭其他
|
||||
case MenuEventEnum.CLOSE_OTHER:
|
||||
closeOther();
|
||||
break;
|
||||
// 关闭其他
|
||||
case MenuEventEnum.CLOSE_ALL:
|
||||
closeAll();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { getDropMenuList, handleMenuEvent };
|
||||
}
|
||||
export function closeTab(closedTab: TabItem) {
|
||||
const { currentRoute, replace } = router;
|
||||
// 当前tab列表
|
||||
const getTabsState = computed(() => {
|
||||
return tabStore.getTabsState;
|
||||
});
|
||||
const { path } = unref(currentRoute);
|
||||
if (path !== closedTab.path) {
|
||||
// 关闭的不是激活tab
|
||||
tabStore.commitCloseTab(closedTab);
|
||||
return;
|
||||
}
|
||||
// 关闭的为激活atb
|
||||
let toPath: PageEnum | string;
|
||||
const index = unref(getTabsState).findIndex((item) => item.path === path);
|
||||
|
||||
// 如果当前为最左边tab
|
||||
if (index === 0) {
|
||||
// 只有一个tab,则跳转至首页,否则跳转至右tab
|
||||
if (unref(getTabsState).length === 1) {
|
||||
toPath = PageEnum.BASE_HOME;
|
||||
} else {
|
||||
// 跳转至右边tab
|
||||
toPath = unref(getTabsState)[index + 1].path;
|
||||
}
|
||||
} else {
|
||||
// 跳转至左边tab
|
||||
toPath = unref(getTabsState)[index - 1].path;
|
||||
}
|
||||
const route = (unref(currentRoute) as unknown) as AppRouteRecordRaw;
|
||||
tabStore.commitCloseTab(route);
|
||||
replace(toPath);
|
||||
}
|
670
src/layouts/default/setting/SettingDrawer.tsx
Normal file
670
src/layouts/default/setting/SettingDrawer.tsx
Normal file
@@ -0,0 +1,670 @@
|
||||
import { defineComponent, computed, unref, ref } from 'vue';
|
||||
import { BasicDrawer } from '/@/components/Drawer/index';
|
||||
import { Divider, Switch, Tooltip, InputNumber, Select } from 'ant-design-vue';
|
||||
import Button from '/@/components/Button/index.vue';
|
||||
import { MenuModeEnum, MenuTypeEnum, MenuThemeEnum, TopMenuAlignEnum } from '/@/enums/menuEnum';
|
||||
import { ContentEnum, RouterTransitionEnum } from '/@/enums/appEnum';
|
||||
import { CopyOutlined, RedoOutlined, CheckOutlined } from '@ant-design/icons-vue';
|
||||
import { appStore } from '/@/store/modules/app';
|
||||
import { userStore } from '/@/store/modules/user';
|
||||
import { ProjectConfig } from '/@/types/config';
|
||||
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard';
|
||||
|
||||
import defaultSetting from '/@/settings/projectSetting';
|
||||
|
||||
import mixImg from '/@/assets/images/layout/menu-mix.svg';
|
||||
import sidebarImg from '/@/assets/images/layout/menu-sidebar.svg';
|
||||
import menuTopImg from '/@/assets/images/layout/menu-top.svg';
|
||||
import { updateColorWeak, updateGrayMode } from '/@/setup/theme';
|
||||
|
||||
const themeOptions = [
|
||||
{
|
||||
value: MenuThemeEnum.LIGHT,
|
||||
label: '亮色',
|
||||
key: MenuThemeEnum.LIGHT,
|
||||
},
|
||||
{
|
||||
value: MenuThemeEnum.DARK,
|
||||
label: '暗色',
|
||||
key: MenuThemeEnum.DARK,
|
||||
},
|
||||
];
|
||||
const contentModeOptions = [
|
||||
{
|
||||
value: ContentEnum.FULL,
|
||||
label: '流式',
|
||||
key: ContentEnum.FULL,
|
||||
},
|
||||
{
|
||||
value: ContentEnum.FIXED,
|
||||
label: '定宽',
|
||||
key: ContentEnum.FIXED,
|
||||
},
|
||||
];
|
||||
const topMenuAlignOptions = [
|
||||
{
|
||||
value: TopMenuAlignEnum.CENTER,
|
||||
label: '居中',
|
||||
key: TopMenuAlignEnum.CENTER,
|
||||
},
|
||||
{
|
||||
value: TopMenuAlignEnum.START,
|
||||
label: '居左',
|
||||
key: TopMenuAlignEnum.START,
|
||||
},
|
||||
{
|
||||
value: TopMenuAlignEnum.END,
|
||||
label: '居右',
|
||||
key: TopMenuAlignEnum.END,
|
||||
},
|
||||
];
|
||||
|
||||
const routerTransitionOptions = [
|
||||
RouterTransitionEnum.ZOOM_FADE,
|
||||
RouterTransitionEnum.FADE,
|
||||
RouterTransitionEnum.ZOOM_OUT,
|
||||
RouterTransitionEnum.SIDE_FADE,
|
||||
RouterTransitionEnum.FADE_BOTTOM,
|
||||
].map((item) => {
|
||||
return {
|
||||
label: item,
|
||||
value: item,
|
||||
key: item,
|
||||
};
|
||||
});
|
||||
|
||||
interface SwitchOptions {
|
||||
config?: DeepPartial<ProjectConfig>;
|
||||
def?: any;
|
||||
disabled?: boolean;
|
||||
handler?: Fn;
|
||||
}
|
||||
|
||||
interface SelectConfig {
|
||||
options?: SelectOptions;
|
||||
def?: any;
|
||||
disabled?: boolean;
|
||||
handler?: Fn;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SettingDrawer',
|
||||
setup(_, { attrs }) {
|
||||
const { createSuccessModal, createMessage } = useMessage();
|
||||
|
||||
const getProjectConfigRef = computed(() => {
|
||||
return appStore.getProjectConfig;
|
||||
});
|
||||
|
||||
const getIsHorizontalRef = computed(() => {
|
||||
return unref(getProjectConfigRef).menuSetting.mode === MenuModeEnum.HORIZONTAL;
|
||||
});
|
||||
|
||||
const getShowHeaderRef = computed(() => {
|
||||
return unref(getProjectConfigRef).headerSetting.show;
|
||||
});
|
||||
|
||||
const getShowMenuRef = computed(() => {
|
||||
return unref(getProjectConfigRef).menuSetting.show && !unref(getIsHorizontalRef);
|
||||
});
|
||||
|
||||
const getShowTabsRef = computed(() => {
|
||||
return unref(getProjectConfigRef).multiTabsSetting.show;
|
||||
});
|
||||
|
||||
function handleCopy() {
|
||||
const { isSuccessRef } = useCopyToClipboard(
|
||||
JSON.stringify(unref(getProjectConfigRef), null, 2)
|
||||
);
|
||||
unref(isSuccessRef) &&
|
||||
createSuccessModal({
|
||||
title: '操作成功',
|
||||
content: '复制成功,请到 src/settings/projectSetting.ts 中修改配置!',
|
||||
});
|
||||
}
|
||||
|
||||
function renderSidebar() {
|
||||
const {
|
||||
headerSetting: { theme: headerTheme },
|
||||
menuSetting: { type, theme: menuTheme, split },
|
||||
} = unref(getProjectConfigRef);
|
||||
|
||||
const typeList = ref([
|
||||
{
|
||||
title: '左侧菜单模式',
|
||||
mode: MenuModeEnum.INLINE,
|
||||
type: MenuTypeEnum.SIDEBAR,
|
||||
src: sidebarImg,
|
||||
},
|
||||
{
|
||||
title: '混合模式',
|
||||
mode: MenuModeEnum.INLINE,
|
||||
type: MenuTypeEnum.MIX,
|
||||
src: mixImg,
|
||||
},
|
||||
|
||||
{
|
||||
title: '顶部菜单模式',
|
||||
mode: MenuModeEnum.HORIZONTAL,
|
||||
type: MenuTypeEnum.TOP_MENU,
|
||||
src: menuTopImg,
|
||||
},
|
||||
]);
|
||||
return [
|
||||
<div class={`setting-drawer__siderbar`}>
|
||||
{unref(typeList).map((item) => {
|
||||
const { title, type: ItemType, mode, src } = item;
|
||||
return (
|
||||
<Tooltip title={title} placement="bottom" key={title}>
|
||||
{{
|
||||
default: () => (
|
||||
<div
|
||||
onClick={baseHandler.bind(null, 'layout', {
|
||||
mode: mode,
|
||||
type: ItemType,
|
||||
split: unref(getIsHorizontalRef) ? false : undefined,
|
||||
})}
|
||||
>
|
||||
<CheckOutlined class={['check-icon', type === ItemType ? 'active' : '']} />
|
||||
<img src={src} />
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</div>,
|
||||
renderSwitchItem('分割菜单', {
|
||||
handler: (e) => {
|
||||
baseHandler('splitMenu', e);
|
||||
},
|
||||
def: split,
|
||||
disabled: !unref(getShowMenuRef),
|
||||
}),
|
||||
renderSelectItem('顶栏主题', {
|
||||
handler: (e) => {
|
||||
baseHandler('headerMenu', e);
|
||||
},
|
||||
def: headerTheme,
|
||||
options: themeOptions,
|
||||
disabled: !unref(getShowHeaderRef),
|
||||
}),
|
||||
renderSelectItem('菜单主题', {
|
||||
handler: (e) => {
|
||||
baseHandler('menuTheme', e);
|
||||
},
|
||||
def: menuTheme,
|
||||
options: themeOptions,
|
||||
disabled: !unref(getShowMenuRef),
|
||||
}),
|
||||
];
|
||||
}
|
||||
/**
|
||||
* @description:
|
||||
*/
|
||||
function renderFeatures() {
|
||||
const {
|
||||
contentMode,
|
||||
headerSetting: { fixed },
|
||||
menuSetting: { hasDrag, collapsed, showSearch, menuWidth, topMenuAlign } = {},
|
||||
} = appStore.getProjectConfig;
|
||||
return [
|
||||
renderSwitchItem('侧边菜单拖拽', {
|
||||
handler: (e) => {
|
||||
baseHandler('hasDrag', e);
|
||||
},
|
||||
def: hasDrag,
|
||||
disabled: !unref(getShowMenuRef),
|
||||
}),
|
||||
renderSwitchItem('侧边菜单搜索', {
|
||||
handler: (e) => {
|
||||
baseHandler('showSearch', e);
|
||||
},
|
||||
def: showSearch,
|
||||
disabled: !unref(getShowMenuRef),
|
||||
}),
|
||||
renderSwitchItem('折叠菜单', {
|
||||
handler: (e) => {
|
||||
baseHandler('collapsed', e);
|
||||
},
|
||||
def: collapsed,
|
||||
disabled: !unref(getShowMenuRef),
|
||||
}),
|
||||
|
||||
renderSwitchItem('固定header', {
|
||||
handler: (e) => {
|
||||
baseHandler('headerFixed', e);
|
||||
},
|
||||
def: fixed,
|
||||
disabled: !unref(getShowHeaderRef),
|
||||
}),
|
||||
renderSelectItem('顶部菜单布局', {
|
||||
handler: (e) => {
|
||||
baseHandler('topMenuAlign', e);
|
||||
},
|
||||
def: topMenuAlign,
|
||||
options: topMenuAlignOptions,
|
||||
disabled: !unref(getShowHeaderRef),
|
||||
}),
|
||||
renderSelectItem('内容区域宽度', {
|
||||
handler: (e) => {
|
||||
baseHandler('contentMode', e);
|
||||
},
|
||||
def: contentMode,
|
||||
options: contentModeOptions,
|
||||
}),
|
||||
<div class={`setting-drawer__cell-item`}>
|
||||
<span>自动锁屏</span>
|
||||
<InputNumber
|
||||
style="width:120px"
|
||||
size="small"
|
||||
min={0}
|
||||
onChange={(e) => {
|
||||
baseHandler('menuWidth', e);
|
||||
}}
|
||||
defaultValue={appStore.getProjectConfig.lockTime}
|
||||
formatter={(value: string) => {
|
||||
if (parseInt(value) === 0) {
|
||||
return '0(不自动锁屏)';
|
||||
}
|
||||
return `${value}分钟`;
|
||||
}}
|
||||
/>
|
||||
</div>,
|
||||
<div class={`setting-drawer__cell-item`}>
|
||||
<span>菜单展开宽度</span>
|
||||
<InputNumber
|
||||
style="width:120px"
|
||||
size="small"
|
||||
max={600}
|
||||
min={100}
|
||||
step={10}
|
||||
disabled={!unref(getShowMenuRef)}
|
||||
defaultValue={menuWidth}
|
||||
formatter={(value: string) => `${parseInt(value)}px`}
|
||||
onChange={(e) => {
|
||||
baseHandler('menuWidth', e);
|
||||
}}
|
||||
/>
|
||||
</div>,
|
||||
];
|
||||
}
|
||||
function renderTransition() {
|
||||
const { routerTransition, openRouterTransition, openPageLoading } = appStore.getProjectConfig;
|
||||
|
||||
return (
|
||||
<>
|
||||
{renderSwitchItem('页面切换loading', {
|
||||
handler: (e) => {
|
||||
baseHandler('openPageLoading', e);
|
||||
},
|
||||
def: openPageLoading,
|
||||
})}
|
||||
{renderSwitchItem('切换动画', {
|
||||
handler: (e) => {
|
||||
baseHandler('openRouterTransition', e);
|
||||
},
|
||||
def: openRouterTransition,
|
||||
})}
|
||||
{renderSelectItem('路由动画', {
|
||||
handler: (e) => {
|
||||
baseHandler('routerTransition', e);
|
||||
},
|
||||
def: routerTransition,
|
||||
options: routerTransitionOptions,
|
||||
disabled: !openRouterTransition,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
function renderContent() {
|
||||
const {
|
||||
grayMode,
|
||||
colorWeak,
|
||||
fullContent,
|
||||
showLogo,
|
||||
headerSetting: { show: showHeader },
|
||||
menuSetting: { show: showMenu },
|
||||
multiTabsSetting: { show: showMultiple, showQuick, showIcon: showTabIcon },
|
||||
showBreadCrumb,
|
||||
} = unref(getProjectConfigRef);
|
||||
return [
|
||||
renderSwitchItem('面包屑', {
|
||||
handler: (e) => {
|
||||
baseHandler('showBreadCrumb', e);
|
||||
},
|
||||
def: showBreadCrumb,
|
||||
disabled: !unref(getShowHeaderRef),
|
||||
}),
|
||||
renderSwitchItem('标签页', {
|
||||
handler: (e) => {
|
||||
baseHandler('showMultiple', e);
|
||||
},
|
||||
def: showMultiple,
|
||||
}),
|
||||
renderSwitchItem('标签页快捷按钮', {
|
||||
handler: (e) => {
|
||||
baseHandler('showQuick', e);
|
||||
},
|
||||
def: showQuick,
|
||||
disabled: !unref(getShowTabsRef),
|
||||
}),
|
||||
renderSwitchItem('标签页图标', {
|
||||
handler: (e) => {
|
||||
baseHandler('showTabIcon', e);
|
||||
},
|
||||
def: showTabIcon,
|
||||
disabled: !unref(getShowTabsRef),
|
||||
}),
|
||||
renderSwitchItem('左侧菜单', {
|
||||
handler: (e) => {
|
||||
baseHandler('showSidebar', e);
|
||||
},
|
||||
def: showMenu,
|
||||
disabled: unref(getIsHorizontalRef),
|
||||
}),
|
||||
renderSwitchItem('顶栏', {
|
||||
handler: (e) => {
|
||||
baseHandler('showHeader', e);
|
||||
},
|
||||
def: showHeader,
|
||||
}),
|
||||
renderSwitchItem('Logo', {
|
||||
handler: (e) => {
|
||||
baseHandler('showLogo', e);
|
||||
},
|
||||
def: showLogo,
|
||||
}),
|
||||
renderSwitchItem('全屏内容', {
|
||||
handler: (e) => {
|
||||
baseHandler('fullContent', e);
|
||||
},
|
||||
def: fullContent,
|
||||
}),
|
||||
renderSwitchItem('灰色模式', {
|
||||
handler: (e) => {
|
||||
baseHandler('grayMode', e);
|
||||
},
|
||||
def: grayMode,
|
||||
}),
|
||||
renderSwitchItem('色弱模式', {
|
||||
handler: (e) => {
|
||||
baseHandler('colorWeak', e);
|
||||
},
|
||||
def: colorWeak,
|
||||
}),
|
||||
];
|
||||
}
|
||||
function baseHandler(event: string, value: any) {
|
||||
let config: DeepPartial<ProjectConfig> = {};
|
||||
if (event === 'layout') {
|
||||
const { mode, type, split } = value;
|
||||
const splitOpt = split === undefined ? { split } : {};
|
||||
config = {
|
||||
menuSetting: {
|
||||
mode,
|
||||
type,
|
||||
...splitOpt,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (event === 'hasDrag') {
|
||||
config = {
|
||||
menuSetting: {
|
||||
hasDrag: value,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (event === 'openPageLoading') {
|
||||
config = {
|
||||
openPageLoading: value,
|
||||
};
|
||||
}
|
||||
if (event === 'topMenuAlign') {
|
||||
config = {
|
||||
menuSetting: {
|
||||
topMenuAlign: value,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (event === 'showBreadCrumb') {
|
||||
config = {
|
||||
showBreadCrumb: value,
|
||||
};
|
||||
}
|
||||
if (event === 'collapsed') {
|
||||
config = {
|
||||
menuSetting: {
|
||||
collapsed: value,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (event === 'menuWidth') {
|
||||
config = {
|
||||
menuSetting: {
|
||||
menuWidth: value,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (event === 'menuWidth') {
|
||||
config = {
|
||||
lockTime: value,
|
||||
};
|
||||
}
|
||||
if (event === 'showQuick') {
|
||||
config = {
|
||||
multiTabsSetting: {
|
||||
showQuick: value,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (event === 'showTabIcon') {
|
||||
config = {
|
||||
multiTabsSetting: {
|
||||
showIcon: value,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (event === 'contentMode') {
|
||||
config = {
|
||||
contentMode: value,
|
||||
};
|
||||
}
|
||||
if (event === 'menuTheme') {
|
||||
config = {
|
||||
menuSetting: {
|
||||
theme: value,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (event === 'splitMenu') {
|
||||
config = {
|
||||
menuSetting: {
|
||||
split: value,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (event === 'showMultiple') {
|
||||
config = {
|
||||
multiTabsSetting: {
|
||||
show: value,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (event === 'headerMenu') {
|
||||
config = {
|
||||
headerSetting: {
|
||||
theme: value,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (event === 'grayMode') {
|
||||
config = {
|
||||
grayMode: value,
|
||||
};
|
||||
updateGrayMode(value);
|
||||
}
|
||||
if (event === 'colorWeak') {
|
||||
config = {
|
||||
colorWeak: value,
|
||||
};
|
||||
updateColorWeak(value);
|
||||
}
|
||||
if (event === 'showLogo') {
|
||||
config = {
|
||||
showLogo: value,
|
||||
};
|
||||
}
|
||||
if (event === 'showSearch') {
|
||||
config = {
|
||||
menuSetting: {
|
||||
showSearch: value,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (event === 'showSidebar') {
|
||||
config = {
|
||||
menuSetting: {
|
||||
show: value,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (event === 'openRouterTransition') {
|
||||
config = {
|
||||
openRouterTransition: value,
|
||||
};
|
||||
}
|
||||
if (event === 'routerTransition') {
|
||||
config = {
|
||||
routerTransition: value,
|
||||
};
|
||||
}
|
||||
if (event === 'headerFixed') {
|
||||
config = {
|
||||
headerSetting: {
|
||||
fixed: value,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (event === 'fullContent') {
|
||||
config = {
|
||||
fullContent: value,
|
||||
};
|
||||
}
|
||||
if (event === 'showHeader') {
|
||||
config = {
|
||||
headerSetting: {
|
||||
show: value,
|
||||
},
|
||||
};
|
||||
}
|
||||
appStore.commitProjectConfigState(config);
|
||||
}
|
||||
|
||||
function handleResetSetting() {
|
||||
try {
|
||||
appStore.commitProjectConfigState(defaultSetting);
|
||||
const { colorWeak, grayMode } = defaultSetting;
|
||||
// updateTheme(themeColor);
|
||||
updateColorWeak(colorWeak);
|
||||
updateGrayMode(grayMode);
|
||||
createMessage.success('重置成功!');
|
||||
} catch (error) {
|
||||
createMessage.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
function handleClearAndRedo() {
|
||||
localStorage.clear();
|
||||
userStore.resumeAllState();
|
||||
location.reload();
|
||||
}
|
||||
|
||||
function renderSelectItem(text: string, config?: SelectConfig) {
|
||||
const { handler, def, disabled = false, options } = config || {};
|
||||
const opt = def ? { value: def, defaultValue: def } : {};
|
||||
return (
|
||||
<div class={`setting-drawer__cell-item`}>
|
||||
<span>{text}</span>
|
||||
{/* @ts-ignore */}
|
||||
<Select
|
||||
{...opt}
|
||||
disabled={disabled}
|
||||
size="small"
|
||||
style={{ width: '120px' }}
|
||||
onChange={(e) => {
|
||||
handler && handler(e);
|
||||
}}
|
||||
options={options}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function renderSwitchItem(text: string, options?: SwitchOptions) {
|
||||
const { handler, def, disabled = false } = options || {};
|
||||
const opt = def ? { checked: def } : {};
|
||||
return (
|
||||
<div class={`setting-drawer__cell-item`}>
|
||||
<span>{text}</span>
|
||||
<Switch
|
||||
{...opt}
|
||||
disabled={disabled}
|
||||
onChange={(e) => {
|
||||
handler && handler(e);
|
||||
}}
|
||||
checkedChildren="开"
|
||||
unCheckedChildren="关"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return () => (
|
||||
<BasicDrawer {...attrs} title="项目配置" width={300} wrapClassName="setting-drawer">
|
||||
{{
|
||||
default: () => (
|
||||
<>
|
||||
<Divider>{() => '导航栏模式'}</Divider>
|
||||
{renderSidebar()}
|
||||
<Divider>{() => '界面功能'}</Divider>
|
||||
{renderFeatures()}
|
||||
<Divider>{() => '界面显示'}</Divider>
|
||||
{renderContent()}
|
||||
<Divider>{() => '切换动画'}</Divider>
|
||||
{renderTransition()}
|
||||
<Divider />
|
||||
<div class="setting-drawer__footer">
|
||||
<Button type="primary" block onClick={handleCopy}>
|
||||
{() => (
|
||||
<>
|
||||
<CopyOutlined class="mr-2" />
|
||||
拷贝
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<Button block class="mt-2" onClick={handleResetSetting} color="warning">
|
||||
{() => (
|
||||
<>
|
||||
<RedoOutlined class="mr-2" />
|
||||
重置
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<Button block class="mt-2" onClick={handleClearAndRedo} color="error">
|
||||
{() => (
|
||||
<>
|
||||
<RedoOutlined class="mr-2" />
|
||||
清空缓存并返回登录页
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
}}
|
||||
</BasicDrawer>
|
||||
);
|
||||
},
|
||||
});
|
28
src/layouts/default/setting/index.vue
Normal file
28
src/layouts/default/setting/index.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div
|
||||
@click="openDrawer"
|
||||
class="setting-button bg-primary flex justify-center items-center text-white p-4 absolute z-10 cursor-pointer"
|
||||
>
|
||||
<SettingOutlined :spin="true" />
|
||||
<SettingDrawer @register="register" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { SettingOutlined } from '@ant-design/icons-vue';
|
||||
import SettingDrawer from './SettingDrawer';
|
||||
|
||||
import { useDrawer } from '/@/components/Drawer';
|
||||
//
|
||||
export default defineComponent({
|
||||
name: 'SettingBtn',
|
||||
components: { SettingOutlined, SettingDrawer },
|
||||
setup() {
|
||||
const [register, { openDrawer }] = useDrawer();
|
||||
return {
|
||||
register,
|
||||
openDrawer,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
Reference in New Issue
Block a user