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

@ -23,7 +23,7 @@ module.exports = {
'@typescript-eslint/no-empty-function': 'off',
'vue/custom-event-name-casing': 'off',
'no-use-before-define': 'off',
// 'no-use-before-define': [
// 'no-setting-before-define': [
// 'error',
// {
// functions: false,
@ -31,7 +31,7 @@ module.exports = {
// },
// ],
'@typescript-eslint/no-use-before-define': 'off',
// '@typescript-eslint/no-use-before-define': [
// '@typescript-eslint/no-setting-before-define': [
// 'error',
// {
// functions: false,

View File

@ -1,7 +1,8 @@
import AppLocalPicker from './src/AppLocalPicker.vue';
import AppFooterToolbar from './src/AppFooterToolbar.vue';
import AppPageFooter from './src/AppPageFooter.vue';
import AppLogo from './src/AppLogo.vue';
import { withInstall } from '../util';
export { AppLocalPicker, AppFooterToolbar };
export { AppLocalPicker, AppPageFooter, AppLogo };
export default withInstall(AppLocalPicker, AppFooterToolbar);
export default withInstall(AppLocalPicker, AppPageFooter, AppLogo);

View File

@ -1,60 +0,0 @@
<template>
<div class="app-footer" :style="{ width: getWidth }">
<div class="app-footer__left">
<slot name="left" />
</div>
<div class="app-footer__right">
<slot name="right" />
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, computed, unref } from 'vue';
import { SIDE_BAR_MINI_WIDTH, SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appEnum';
import { appStore } from '/@/store/modules/app';
import { menuStore } from '/@/store/modules/menu';
export default defineComponent({
name: 'AppFooterToolbar',
setup() {
const getMiniWidth = computed(() => {
const {
menuSetting: { collapsedShowTitle },
} = appStore.getProjectConfig;
return collapsedShowTitle ? SIDE_BAR_SHOW_TIT_MINI_WIDTH : SIDE_BAR_MINI_WIDTH;
});
const getWidth = computed(() => {
const { getCollapsedState, getMenuWidthState } = menuStore;
const width = getCollapsedState ? unref(getMiniWidth) : getMenuWidthState;
return `calc(100% - ${width}px)`;
});
return { getWidth };
},
});
</script>
<style lang="less" scoped>
.app-footer {
position: fixed;
right: 0;
bottom: 0;
z-index: 99;
display: flex;
width: 100%;
align-items: center;
padding: 0 24px;
line-height: 44px;
background: #fff;
border-top: 1px solid #f0f0f0;
box-shadow: 0 -6px 16px -8px rgba(0, 0, 0, 0.08), 0 -9px 28px 0 rgba(0, 0, 0, 0.05),
0 -12px 48px 16px rgba(0, 0, 0, 0.03);
transition: width 0.3s;
&__left {
flex: 1 1;
}
}
</style>

View File

@ -5,29 +5,44 @@
:selectedKeys="selectedKeys"
@menuEvent="handleMenuEvent"
>
<GlobalOutlined class="app-locale" />
<span class="app-local-picker">
<GlobalOutlined class="app-local-picker__icon" />
<span v-if="showText">{{ getLangText }}</span>
</span>
</Dropdown>
</template>
<script lang="ts">
import { defineComponent, ref, watchEffect, unref } from 'vue';
import { defineComponent, ref, watchEffect, unref, computed } from 'vue';
import { Dropdown, DropMenu } from '/@/components/Dropdown';
import { GlobalOutlined } from '@ant-design/icons-vue';
import { useLocale } from '/@/hooks/web/useLocale';
import { useLocaleSetting } from '/@/settings/use/useLocaleSetting';
import { useLocaleSetting } from '/@/hooks/setting/useLocaleSetting';
import { LocaleType } from '/@/locales/types';
export default defineComponent({
name: 'AppLocalPicker',
components: { GlobalOutlined, Dropdown },
props: {
showText: {
type: Boolean,
default: true,
},
},
setup() {
const { localeList } = useLocaleSetting();
const selectedKeys = ref<string[]>([]);
const { changeLocale, getLang } = useLocale();
const getLangText = computed(() => {
const key = selectedKeys.value[0];
if (!key) return '';
return localeList.find((item) => item.event === key)?.text;
});
watchEffect(() => {
selectedKeys.value = [unref(getLang)];
});
@ -41,13 +56,19 @@
toggleLocale(menu.event as string);
}
return { localeList, handleMenuEvent, selectedKeys };
return { localeList, handleMenuEvent, selectedKeys, getLangText };
},
});
</script>
<style lang="less" scoped>
.app-locale {
.app-local-picker {
display: flex;
align-items: center;
cursor: pointer;
&__icon {
margin-right: 4px;
}
}
</style>

View File

@ -0,0 +1,72 @@
<template>
<div class="app-logo anticon" :class="theme" @click="handleGoHome">
<img src="/@/assets/images/logo.png" />
<div class="app-logo__title ml-2 ellipsis">{{ globSetting.title }}</div>
</div>
</template>
<script lang="ts">
import type { PropType } from 'vue';
import { defineComponent } from 'vue';
import { useGlobSetting } from '/@/hooks/setting';
import { useGo } from '/@/hooks/web/usePage';
import { PageEnum } from '/@/enums/pageEnum';
export default defineComponent({
name: 'AppLogo',
props: {
/**
* The theme of the current parent component
*/
theme: {
type: String as PropType<string>,
},
},
setup() {
const globSetting = useGlobSetting();
const go = useGo();
function handleGoHome(): void {
go(PageEnum.BASE_HOME);
}
return {
handleGoHome,
globSetting,
};
},
});
</script>
<style lang="less" scoped>
@import (reference) '../../../design/index.less';
.app-logo {
display: flex;
align-items: center;
padding-left: 16px;
cursor: pointer;
&.light {
border-bottom: 1px solid @border-color-base;
}
&.light &__title {
color: @primary-color;
}
&.dark &__title {
color: @white;
}
&__title {
font-size: 18px;
font-weight: 700;
opacity: 0;
transition: all 0.5s;
.respond-to(medium,{
opacity: 1;
});
}
}
</style>

View File

@ -0,0 +1,46 @@
<template>
<div class="app-footer" :style="{ width: getCalcContentWidth }">
<div class="app-footer__left">
<slot name="left" />
</div>
<div class="app-footer__right">
<slot name="right" />
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
export default defineComponent({
name: 'AppFooterToolbar',
setup() {
const { getCalcContentWidth } = useMenuSetting();
return { getCalcContentWidth };
},
});
</script>
<style lang="less" scoped>
.app-footer {
position: fixed;
right: 0;
bottom: 0;
z-index: 99;
display: flex;
width: 100%;
align-items: center;
padding: 0 24px;
line-height: 44px;
background: #fff;
border-top: 1px solid #f0f0f0;
box-shadow: 0 -6px 16px -8px rgba(0, 0, 0, 0.08), 0 -9px 28px 0 rgba(0, 0, 0, 0.05),
0 -12px 48px 16px rgba(0, 0, 0, 0.03);
transition: width 0.4s;
&__left {
flex: 1 1;
}
}
</style>

View File

@ -1,16 +1,15 @@
<!--
* @Author: Vben
* @Description:Access control component for fine-grained access control.
Access control component for fine-grained access control.
-->
<script lang="ts">
import type { PropType } from 'vue';
import { defineComponent, computed, unref } from 'vue';
import { defineComponent, unref } from 'vue';
import { PermissionModeEnum } from '/@/enums/appEnum';
import { RoleEnum } from '/@/enums/roleEnum';
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
import { usePermission } from '/@/hooks/web/usePermission';
import { appStore } from '/@/store/modules/app';
import { getSlot } from '/@/utils/helper/tsxHelper';
@ -29,9 +28,8 @@
},
},
setup(props, { slots }) {
const getModeRef = computed(() => {
return appStore.getProjectConfig.permissionMode;
});
const { getPermissionMode } = useRootSetting();
const { hasPermission } = usePermission();
/**
* Render role button
@ -41,7 +39,6 @@
if (!value) {
return getSlot(slots);
}
const { hasPermission } = usePermission();
return hasPermission(value) ? getSlot(slots) : null;
}
@ -52,12 +49,11 @@
if (!value) {
return getSlot(slots);
}
const { hasPermission } = usePermission();
return hasPermission(value) ? getSlot(slots) : null;
}
return () => {
const mode = unref(getModeRef);
const mode = unref(getPermissionMode);
// Role-based value control
if (mode === PermissionModeEnum.ROLE) {
return renderRoleAuth();

View File

@ -33,7 +33,7 @@ export default defineComponent({
});
/**
* @description: Whether to use title
* @description: Whether to setting title
*/
const useWrapper = computed(() => {
return !!unref(getMergeProps).title;

View File

@ -15,7 +15,7 @@ export default defineComponent({
const getMenuList = computed(() => props.dropMenuList);
function handleClickMenu({ key }: any) {
const menu = unref(getMenuList).find((item) => item.event === key);
const menu = unref(getMenuList).find((item) => `${item.event}` === `${key}`);
emit('menuEvent', menu);
}

View File

@ -104,7 +104,7 @@
*/
function handleInputClick(e: Event) {
const files = e && (e.target as HTMLInputElement).files;
const rawFile = files && files[0]; // only use files[0]
const rawFile = files && files[0]; // only setting files[0]
if (!rawFile) return;
upload(rawFile);
}

View File

@ -2,7 +2,7 @@ import { Component } from 'vue';
import type { ComponentType } from './types/index';
/**
* Component list, register here to use it in the form
* Component list, register here to setting it in the form
*/
import {
Input,

View File

@ -31,7 +31,7 @@ export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref<
wrapperCol: globWrapperCol,
} = unref(propsRef) as any;
// If labelWidth is set globally, all items use
// If labelWidth is set globally, all items setting
if ((!globalLabelWidth && !labelWidth && !globalLabelCol) || disabledLabelWidth) {
return { labelCol, wrapperCol };
}

View File

@ -68,7 +68,7 @@ export interface FormItem {
*/
labelAlign?: 'left' | 'right';
/**
* a key of model. In the use of validate and resetFields method, the attribute is required
* a key of model. In the setting of validate and resetFields method, the attribute is required
*/
name?: NamePath;
/**
@ -76,7 +76,7 @@ export interface FormItem {
*/
rules?: object | object[];
/**
* Whether to automatically associate form fields. In most cases, you can use automatic association.
* Whether to automatically associate form fields. In most cases, you can setting automatic association.
* If the conditions for automatic association are not met, you can manually associate them. See the notes below.
*/
autoLink?: boolean;

View File

@ -1 +1,5 @@
export { default as BasicMenu } from './src/BasicMenu';
import BasicMenu from './src/BasicMenu';
import { withInstall } from '../util';
export default withInstall(BasicMenu);
export { BasicMenu };

View File

@ -1,3 +1,5 @@
import './index.less';
import type { MenuState } from './types';
import type { Menu as MenuType } from '/@/router/types';
@ -9,11 +11,10 @@ import MenuContent from './MenuContent';
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
import { ThemeEnum } from '/@/enums/appEnum';
import { menuStore } from '/@/store/modules/menu';
import { appStore } from '/@/store/modules/app';
import { useSearchInput } from './useSearchInput';
import { useOpenKeys } from './useOpenKeys';
import { useSearchInput } from './hooks/useSearchInput';
import { useOpenKeys } from './hooks/useOpenKeys';
import { useRouter } from 'vue-router';
import { isFunction } from '/@/utils/is';
@ -23,7 +24,7 @@ import { menuHasChildren } from './helper';
import { getCurrentParentPath } from '/@/router/menus';
import { basicProps } from './props';
import './index.less';
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
export default defineComponent({
name: 'BasicMenu',
props: basicProps,
@ -39,6 +40,8 @@ export default defineComponent({
selectedKeys: [],
collapsedOpenKeys: [],
});
const { getCollapsed } = useMenuSetting();
const { currentRoute } = useRouter();
const { items, flatItems, isAppMenu, mode, accordion } = toRefs(props);
@ -61,7 +64,7 @@ export default defineComponent({
const getOpenKeys = computed(() => {
if (props.isAppMenu) {
return menuStore.getCollapsedState ? menuState.collapsedOpenKeys : menuState.openKeys;
return unref(getCollapsed) ? menuState.collapsedOpenKeys : menuState.openKeys;
}
return menuState.openKeys;
});
@ -95,20 +98,20 @@ export default defineComponent({
cls.push('basic-menu__sidebar-hor');
}
if (!props.isTop && props.isAppMenu && appStore.getProjectConfig.menuSetting.split) {
if (!props.isHorizontal && props.isAppMenu && appStore.getProjectConfig.menuSetting.split) {
cls.push('basic-menu__second');
}
return cls;
});
const showTitle = computed(() => props.collapsedShowTitle && menuStore.getCollapsedState);
const showTitle = computed(() => props.collapsedShowTitle && unref(getCollapsed));
watch(
() => currentRoute.value.name,
(name: string) => {
if (name === 'Redirect') return;
handleMenuChange();
props.isTop && appStore.getProjectConfig.menuSetting.split && getParentPath();
props.isHorizontal && appStore.getProjectConfig.menuSetting.split && getParentPath();
}
);
@ -180,7 +183,7 @@ export default defineComponent({
<MenuContent
item={menu}
level={index}
isTop={props.isTop}
isHorizontal={props.isHorizontal}
showTitle={unref(showTitle)}
searchValue={menuState.searchValue}
/>,
@ -196,7 +199,7 @@ export default defineComponent({
showTitle={unref(showTitle)}
item={menu}
level={index}
isTop={props.isTop}
isHorizontal={props.isHorizontal}
searchValue={menuState.searchValue}
/>,
],
@ -214,7 +217,7 @@ export default defineComponent({
const inlineCollapsedObj = isInline
? props.isAppMenu
? {
inlineCollapsed: menuStore.getCollapsedState,
inlineCollapsed: unref(getCollapsed),
}
: { inlineCollapsed: props.inlineCollapsed }
: {};
@ -246,7 +249,6 @@ export default defineComponent({
});
return () => {
const { getCollapsedState } = menuStore;
const { mode } = props;
return mode === MenuModeEnum.HORIZONTAL ? (
renderMenu()
@ -258,7 +260,7 @@ export default defineComponent({
theme={props.theme as ThemeEnum}
onChange={handleInputChange}
onClick={handleInputClick}
collapsed={getCollapsedState}
collapsed={unref(getCollapsed)}
/>
<section style={unref(getMenuWrapStyle)} class="basic-menu__content">
{renderMenu()}

View File

@ -26,7 +26,7 @@ export default defineComponent({
type: Number as PropType<number>,
default: 0,
},
isTop: {
isHorizontal: {
type: Boolean as PropType<boolean>,
default: true,
},
@ -40,8 +40,8 @@ export default defineComponent({
}
function renderTag() {
const { item, showTitle, isTop } = props;
if (!item || showTitle || isTop) return null;
const { item, showTitle, isHorizontal } = props;
if (!item || showTitle || isHorizontal) return null;
const { tag } = item;
if (!tag) return null;

View File

@ -1,12 +1,12 @@
import { MenuModeEnum } from '/@/enums/menuEnum';
import type { Menu as MenuType } from '/@/router/types';
import type { MenuState } from './types';
import type { MenuState } from '../types';
import type { Ref } from 'vue';
import { unref } from 'vue';
import { menuStore } from '/@/store/modules/menu';
import { getAllParentPath } from '/@/utils/helper/menuHelper';
import { es6Unique } from '/@/utils';
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
export function useOpenKeys(
menuState: MenuState,
@ -16,6 +16,7 @@ export function useOpenKeys(
mode: Ref<MenuModeEnum>,
accordion: Ref<boolean>
) {
const { getCollapsed } = useMenuSetting();
/**
* @description:
*/
@ -49,7 +50,7 @@ export function useOpenKeys(
rootSubMenuKeys.push(path);
}
}
if (!menuStore.getCollapsedState || !unref(isAppMenu)) {
if (!unref(getCollapsed) || !unref(isAppMenu)) {
const latestOpenKey = openKeys.find((key) => menuState.openKeys.indexOf(key) === -1);
if (rootSubMenuKeys.indexOf(latestOpenKey as string) === -1) {
menuState.openKeys = openKeys;

View File

@ -1,5 +1,5 @@
import type { Menu as MenuType } from '/@/router/types';
import type { MenuState } from './types';
import type { MenuState } from '../types';
import type { Ref } from 'vue';
import { isString } from '/@/utils/is';

View File

@ -8,6 +8,10 @@ export const basicProps = {
type: Array as PropType<Menu[]>,
default: () => [],
},
flatItems: {
type: Array as PropType<Menu[]>,
default: () => [],
},
appendClass: {
type: Boolean as PropType<boolean>,
default: false,
@ -16,10 +20,6 @@ export const basicProps = {
type: Boolean as PropType<boolean>,
default: false,
},
flatItems: {
type: Array as PropType<Menu[]>,
default: () => [],
},
// 是否显示搜索框
search: {
type: Boolean as PropType<boolean>,
@ -55,7 +55,7 @@ export const basicProps = {
type: Boolean as PropType<boolean>,
default: true,
},
isTop: {
isHorizontal: {
type: Boolean as PropType<boolean>,
default: false,
},

View File

@ -35,7 +35,7 @@ export const basicProps = Object.assign({}, modalProps, {
},
// Warm reminder message
helpMessage: [String, Array] as PropType<string | string[]>,
// Whether to use wrapper
// Whether to setting wrapper
useWrapper: {
type: Boolean as PropType<boolean>,
default: true,

View File

@ -190,7 +190,7 @@ export interface ColumnProps<T> {
onFilterDropdownVisibleChange?: (visible: boolean) => void;
/**
* When using columns, you can use this property to configure the properties that support the slot,
* When using columns, you can setting this property to configure the properties that support the slot,
* such as slots: { filterIcon: 'XXX'}
* @type object
*/

View File

@ -86,7 +86,7 @@ export interface PaginationProps {
size?: string;
/**
* whether to use simple mode
* whether to setting simple mode
* @type boolean
*/
simple?: boolean;

View File

@ -1,4 +1,4 @@
// Any plugins you want to use has to be imported
// Any plugins you want to setting has to be imported
// Detail plugins list see https://www.tinymce.com/docs/plugins/
// Custom builds see https://www.tinymce.com/download/custom-builds/
// colorpicker/contextmenu/textcolor plugin is now built in to the core editor, please remove it from your editor configuration

View File

@ -31,6 +31,7 @@ import {
PageHeader,
Result,
Empty,
Avatar,
} from 'ant-design-vue';
import { getApp } from '/@/setup/App';
@ -76,5 +77,6 @@ export function registerGlobComp() {
.use(PageHeader)
.use(Result)
.use(Empty)
.use(Avatar)
.use(Tabs);
}

3
src/components/types.ts Normal file
View File

@ -0,0 +1,3 @@
import { defineComponent } from 'vue';
export type Component = ReturnType<typeof defineComponent>;

View File

@ -16,4 +16,4 @@
@page-loading-z-index: 10000;
// left-menu
@app-menu-item-height: 44px;
@app-menu-item-height: 42px;

View File

@ -5,4 +5,6 @@ export enum PageEnum {
BASE_HOME = '/dashboard',
// error page path
ERROR_PAGE = '/exception',
// error log page path
ERROR_LOG_PAGE = '/exception/error-log',
}

View File

@ -0,0 +1,20 @@
import { customRef } from 'vue';
export function useDebouncedRef<T = any>(value: T, delay = 100) {
let timeout: TimeoutHandle;
return customRef((track, trigger) => {
return {
get() {
track();
return value;
},
set(newValue: T) {
clearTimeout(timeout);
timeout = setTimeout(() => {
value = newValue;
trigger();
}, delay);
},
};
});
}

View File

@ -0,0 +1,67 @@
import type { HeaderSetting } from '/@/types/config';
import { computed, unref } from 'vue';
import { appStore } from '/@/store/modules/app';
import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
import { MenuModeEnum } from '/@/enums/menuEnum';
export function useHeaderSetting() {
const { getShow: getShowMultipleTab } = useMultipleTabSetting();
const { getMode, getSplit, getShowHeaderTrigger, getIsSidebarType } = useMenuSetting();
const { getShowBreadCrumb, getShowLogo } = useRootSetting();
// Get header configuration
const getHeaderSetting = computed(() => appStore.getProjectConfig.headerSetting);
const getShowDoc = computed(() => unref(getHeaderSetting).showDoc);
const getTheme = computed(() => unref(getHeaderSetting).theme);
const getShowRedo = computed(() => unref(getHeaderSetting).showRedo && unref(getShowMultipleTab));
const getUseLockPage = computed(() => unref(getHeaderSetting).useLockPage);
const getShowFullScreen = computed(() => unref(getHeaderSetting).showFullScreen);
const getShowNotice = computed(() => unref(getHeaderSetting).showNotice);
const getShowBread = computed(() => {
return (
unref(getMode) !== MenuModeEnum.HORIZONTAL && unref(getShowBreadCrumb) && !unref(getSplit)
);
});
const getShowHeaderLogo = computed(() => {
return unref(getShowLogo) && !unref(getIsSidebarType);
});
const getShowContent = computed(() => {
return unref(getShowBread) || unref(getShowHeaderTrigger);
});
// Set header configuration
function setHeaderSetting(headerSetting: Partial<HeaderSetting>): void {
appStore.commitProjectConfigState({ headerSetting });
}
return {
setHeaderSetting,
getHeaderSetting,
getShowDoc,
getTheme,
getShowRedo,
getUseLockPage,
getShowFullScreen,
getShowNotice,
getShowBread,
getShowContent,
getShowHeaderLogo,
};
}

View File

@ -1,6 +1,6 @@
import type { LocaleSetting } from '/@/types/config';
import { computed } from 'vue';
import { computed, unref } from 'vue';
import { appStore } from '/@/store/modules/app';
import getProjectSetting from '/@/settings/projectSetting';
@ -8,24 +8,16 @@ import { localeList } from '/@/locales';
export function useLocaleSetting() {
// Get locale configuration
const getLocale = computed(() => {
return appStore.getProjectConfig.locale || getProjectSetting.locale;
});
const getLocale = computed(() => appStore.getProjectConfig.locale || getProjectSetting.locale);
// get current language
const getLang = computed(() => {
return getLocale.value.lang;
});
const getLang = computed(() => unref(getLocale).lang);
// get Available Locales
const getAvailableLocales = computed((): string[] => {
return getLocale.value.availableLocales;
});
const getAvailableLocales = computed((): string[] => unref(getLocale).availableLocales);
// get Fallback Locales
const getFallbackLocale = computed((): string => {
return getLocale.value.fallback;
});
const getFallbackLocale = computed((): string => unref(getLocale).fallback);
// Set locale configuration
function setLocale(locale: Partial<LocaleSetting>): void {

View File

@ -0,0 +1,120 @@
import type { MenuSetting } from '/@/types/config';
import { computed, unref } from 'vue';
import { appStore } from '/@/store/modules/app';
import { SIDE_BAR_MINI_WIDTH, SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appEnum';
import { MenuModeEnum, MenuTypeEnum, TriggerEnum } from '/@/enums/menuEnum';
export function useMenuSetting() {
// Get menu configuration
const getMenuSetting = computed(() => appStore.getProjectConfig.menuSetting);
const getMiniWidth = computed(() => unref(getMenuSetting).menuWidth);
const getCollapsed = computed(() => unref(getMenuSetting).collapsed);
const getType = computed(() => unref(getMenuSetting).type);
const getMode = computed(() => unref(getMenuSetting).mode);
const getShow = computed(() => unref(getMenuSetting).show);
const getMenuWidth = computed(() => unref(getMenuSetting).menuWidth);
const getTrigger = computed(() => unref(getMenuSetting).trigger);
const getTheme = computed(() => unref(getMenuSetting).theme);
const getSplit = computed(() => unref(getMenuSetting).split);
const getHasDrag = computed(() => unref(getMenuSetting).hasDrag);
const getAccordion = computed(() => unref(getMenuSetting).accordion);
const getCollapsedShowTitle = computed(() => unref(getMenuSetting).collapsedShowTitle);
const getCollapsedShowSearch = computed(() => unref(getMenuSetting).collapsedShowSearch);
const getTopMenuAlign = computed(() => unref(getMenuSetting).topMenuAlign);
const getIsSidebarType = computed(() => unref(getType) === MenuTypeEnum.SIDEBAR);
const getShowTopMenu = computed(() => {
return unref(getMode) === MenuModeEnum.HORIZONTAL || unref(getSplit);
});
const getShowHeaderTrigger = computed(() => {
if (
unref(getType) === MenuTypeEnum.TOP_MENU ||
!unref(getShow) ||
!unref(getMenuSetting).hidden
) {
return false;
}
return unref(getTrigger) === TriggerEnum.HEADER;
});
const getShowSearch = computed(() => {
return (
unref(getMenuSetting).showSearch &&
!(unref(getType) === MenuTypeEnum.MIX && unref(getMode) === MenuModeEnum.HORIZONTAL)
);
});
const getIsHorizontal = computed(() => {
return unref(getMode) === MenuModeEnum.HORIZONTAL;
});
const getMiniWidthNumber = computed(() => {
const { collapsedShowTitle } = unref(getMenuSetting);
return collapsedShowTitle ? SIDE_BAR_SHOW_TIT_MINI_WIDTH : SIDE_BAR_MINI_WIDTH;
});
const getCalcContentWidth = computed(() => {
const width = unref(getCollapsed) ? unref(getMiniWidthNumber) : unref(getMiniWidth);
return `calc(100% - ${width}px)`;
});
// Set menu configuration
function setMenuSetting(menuSetting: Partial<MenuSetting>): void {
appStore.commitProjectConfigState({ menuSetting });
}
function toggleCollapsed() {
setMenuSetting({
collapsed: !unref(getCollapsed),
});
}
return {
setMenuSetting,
toggleCollapsed,
getMenuSetting,
getMiniWidth,
getType,
getMode,
getShow,
getCollapsed,
getMiniWidthNumber,
getCalcContentWidth,
getMenuWidth,
getTrigger,
getSplit,
getTheme,
getHasDrag,
getIsHorizontal,
getShowSearch,
getCollapsedShowTitle,
getCollapsedShowSearch,
getIsSidebarType,
getAccordion,
getShowTopMenu,
getShowHeaderTrigger,
getTopMenuAlign,
};
}

View File

@ -0,0 +1,25 @@
import type { MultiTabsSetting } from '/@/types/config';
import { computed, unref } from 'vue';
import { appStore } from '/@/store/modules/app';
export function useMultipleTabSetting() {
const getMultipleTabSetting = computed(() => appStore.getProjectConfig.multiTabsSetting);
const getMax = computed(() => unref(getMultipleTabSetting).max);
const getShow = computed(() => unref(getMultipleTabSetting).show);
function setMultipleTabSetting(multiTabsSetting: Partial<MultiTabsSetting>) {
appStore.commitProjectConfigState({ multiTabsSetting });
}
return {
setMultipleTabSetting,
getMultipleTabSetting,
getMax,
getShow,
};
}

View File

@ -0,0 +1,53 @@
import type { ProjectConfig } from '/@/types/config';
import { computed, unref } from 'vue';
import { appStore } from '/@/store/modules/app';
type RootSetting = Omit<
ProjectConfig,
'locale' | 'headerSetting' | 'menuSetting' | 'multiTabsSetting'
>;
export function useRootSetting() {
const getRootSetting = computed((): RootSetting => appStore.getProjectConfig);
const getOpenPageLoading = computed(() => unref(getRootSetting).openPageLoading);
const getOpenRouterTransition = computed(() => unref(getRootSetting).openRouterTransition);
const getOpenKeepAlive = computed(() => unref(getRootSetting).openKeepAlive);
const getRouterTransition = computed(() => unref(getRootSetting).routerTransition);
const getCanEmbedIFramePage = computed(() => unref(getRootSetting).canEmbedIFramePage);
const getPermissionMode = computed(() => unref(getRootSetting).permissionMode);
const getShowLogo = computed(() => unref(getRootSetting).showLogo);
const getUseErrorHandle = computed(() => unref(getRootSetting).useErrorHandle);
const getShowBreadCrumb = computed(() => unref(getRootSetting).showBreadCrumb);
const getShowBreadCrumbIcon = computed(() => unref(getRootSetting).showBreadCrumbIcon);
function setRootSetting(setting: RootSetting) {
appStore.commitProjectConfigState(setting);
}
return {
setRootSetting,
getRootSetting,
getOpenPageLoading,
getOpenRouterTransition,
getOpenKeepAlive,
getRouterTransition,
getCanEmbedIFramePage,
getPermissionMode,
getShowLogo,
getUseErrorHandle,
getShowBreadCrumb,
getShowBreadCrumbIcon,
};
}

View File

@ -7,7 +7,7 @@ import { unref, ref } from 'vue';
import { getI18n } from '/@/setup/i18n';
import { useLocaleSetting } from '/@/settings/use/useLocaleSetting';
import { useLocaleSetting } from '/@/hooks/setting/useLocaleSetting';
import moment from 'moment';
@ -67,7 +67,7 @@ export function useLocale() {
}
/**
* For non-setup use
* For non-setup setting
*/
export function useExternalI18n() {
return getI18n().global;

View File

@ -20,6 +20,7 @@ function handleError(e: Error) {
export function useGo() {
const { push, replace } = useRouter();
function go(opt: PageEnum | RouteLocationRawEx | string = PageEnum.BASE_HOME, isReplace = false) {
if (!opt) return;
if (isString(opt)) {
isReplace ? replace(opt).catch(handleError) : push(opt).catch(handleError);
} else {

View File

@ -1,20 +0,0 @@
import { computed } from 'vue';
import { appStore } from '/@/store/modules/app';
export function useSideBar() {
const currentCollapsedRef = computed(() => {
return appStore.getProjectConfig.menuSetting.collapsed;
});
const changeCollapsed = (collapsed: boolean) => {
appStore.commitProjectConfigState({
menuSetting: {
collapsed: collapsed,
},
});
};
return {
openSider: changeCollapsed(false),
closeSider: changeCollapsed(true),
currentCollapsedRef,
};
}

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

View File

@ -49,7 +49,7 @@ if (isDevMode()) {
window.__APP__ = app;
}
// If you do not need to use the mock service in the production environment, you can comment the code
// If you do not need to setting the mock service in the production environment, you can comment the code
if (isProdMode() && isUseMock()) {
setupProdMockServer();
}

View File

@ -6,7 +6,7 @@ import { createProgressGuard } from './progressGuard';
import { createPermissionGuard } from './permissionGuard';
import { createPageLoadingGuard } from './pageLoadingGuard';
import { useGlobSetting, useProjectSetting } from '/@/settings/use';
import { useGlobSetting, useProjectSetting } from '/@/hooks/setting';
import { getIsOpenTab, setCurrentTo } from '/@/utils/helper/routeHelper';
import { setTitle } from '/@/utils/browser';

View File

@ -7,6 +7,7 @@ import { filter } from '/@/utils/helper/treeHelper';
import router from '/@/router';
import { PermissionModeEnum } from '/@/enums/appEnum';
import { pathToRegexp } from 'path-to-regexp';
import modules from 'globby!/@/router/menus/modules/**/*.@(ts)';
const menuModules: MenuModule[] = [];
@ -44,7 +45,6 @@ async function getAsyncMenus() {
// 获取深层扁平化菜单
export const getFlatMenus = async () => {
const menus = await getAsyncMenus();
return flatMenus(menus);
};

View File

@ -9,7 +9,7 @@ import { isProdMode } from '/@/utils/env';
const setting: ProjectConfig = {
// locale setting
locale: {
// Locales
// Locale
lang: 'zh_CN',
// Default locale
fallback: 'zh_CN',
@ -29,17 +29,22 @@ const setting: ProjectConfig = {
// Whether to show the configuration button
showSettingButton: true,
// 权限模式
permissionMode: PermissionModeEnum.ROLE,
// 网站灰色模式,用于可能悼念的日期开启
grayMode: false,
// 色弱模式
colorWeak: false,
// 是否取消菜单,顶部,多标签页显示, 用于可能内嵌在别的系统内
fullContent: false,
// content mode
contentMode: ContentEnum.FULL,
// 是否显示logo
showLogo: true,
@ -58,11 +63,10 @@ const setting: ProjectConfig = {
showFullScreen: true,
// 显示文档按钮
showDoc: true,
// 是否显示github
showGithub: true,
// 显示消息中心按钮
showNotice: true,
},
// 菜单配置
menuSetting: {
// 菜单折叠
@ -108,13 +112,16 @@ const setting: ProjectConfig = {
// 标签页缓存最大数量
max: 12,
},
// 是否开启KeepAlive缓存 开发时候最好关闭,不然每次都需要清除缓存
openKeepAlive: true,
// 自动锁屏时间为0不锁屏。 单位分钟 默认0
lockTime: 0,
// 显示面包屑
showBreadCrumb: true,
// 显示面包屑图标
showBreadCrumbIcon: false,

View File

@ -3,7 +3,7 @@
*/
import { errorStore, ErrorInfo } from '/@/store/modules/error';
import { useProjectSetting } from '/@/settings/use';
import { useProjectSetting } from '/@/hooks/setting';
import { ErrorTypeEnum } from '/@/enums/exceptionEnum';
import { App } from 'vue';

View File

@ -4,7 +4,7 @@ import type { I18n, I18nOptions } from 'vue-i18n';
import { createI18n } from 'vue-i18n';
import localeMessages from '/@/locales';
import { useLocale } from '/@/hooks/web/useLocale';
import { useLocaleSetting } from '/@/settings/use/useLocaleSetting';
import { useLocaleSetting } from '/@/hooks/setting/useLocaleSetting';
const { setupLocale } = useLocale();

View File

@ -4,7 +4,7 @@ import { VuexModule, getModule, Module, Mutation, Action } from 'vuex-module-dec
import { formatToDateTime } from '/@/utils/dateUtil';
import { ErrorTypeEnum } from '/@/enums/exceptionEnum';
import { useProjectSetting } from '/@/settings/use';
import { useProjectSetting } from '/@/hooks/setting';
export interface ErrorInfo {
type: ErrorTypeEnum;

View File

@ -1,67 +0,0 @@
import store from '/@/store';
import { hotModuleUnregisterModule } from '/@/utils/helper/vuexHelper';
import { VuexModule, Module, getModule, Mutation } from 'vuex-module-decorators';
import { appStore } from '/@/store/modules/app';
const NAME = 'menu';
hotModuleUnregisterModule(NAME);
@Module({ namespaced: true, name: NAME, dynamic: true, store })
class Menu extends VuexModule {
// 是否开始拖拽
private dragStartState = false;
private currentTopSplitMenuPathState = '';
/**
* @description:
*/
get getCollapsedState() {
return appStore.getProjectConfig.menuSetting.collapsed;
}
get getCurrentTopSplitMenuPathState() {
return this.currentTopSplitMenuPathState;
}
get getDragStartState() {
return this.dragStartState;
}
get getMenuWidthState() {
return appStore.getProjectConfig.menuSetting.menuWidth;
}
@Mutation
commitDragStartState(dragStart: boolean): void {
this.dragStartState = dragStart;
}
@Mutation
commitCurrentTopSplitMenuPathState(path: string): void {
this.currentTopSplitMenuPathState = path;
}
// 改变菜单展开状态
@Mutation
commitCollapsedState(collapsed: boolean): void {
// this.collapsedState = collapsed;
appStore.commitProjectConfigState({
menuSetting: {
collapsed: collapsed,
},
});
}
@Mutation
commitMenuWidthState(menuWidth: number): void {
// this.menuWidthState = menuWidth;
appStore.commitProjectConfigState({
menuSetting: {
menuWidth: menuWidth,
},
});
}
}
export const menuStore = getModule<Menu>(Menu);

View File

@ -97,11 +97,6 @@ class Permission extends VuexModule {
if (!roles) return true;
return roleList.some((role) => roles.includes(role));
});
// this.commitRoutesState(routes);
// Background permissions
// warn(
// `当前权限模式为:${PermissionModeEnum.ROLE},请将src/store/modules/permission.ts内的后台菜单获取函数注释,如果已注释可以忽略此信息!`
// );
// 如果确定不需要做后台动态权限,请将下面整个判断注释
} else if (permissionMode === PermissionModeEnum.BACK) {
const messageKey = 'loadMenu';

View File

@ -44,7 +44,6 @@ export interface HeaderSetting {
useLockPage: boolean;
// 显示文档按钮
showDoc: boolean;
showGithub: boolean;
// 显示消息中心按钮
showNotice: boolean;
}

View File

@ -1,3 +1,4 @@
import { openWindow } from '..';
import { dataURLtoBlob, urlToBase64 } from './base64Conver';
/**
@ -93,6 +94,6 @@ export function downloadByUrl({
url += '?download';
}
window.open(url, target);
openWindow(url, { target });
return true;
}

View File

@ -1,5 +1,5 @@
import { getEnv } from '/@/utils/env';
import { useGlobSetting } from '/@/settings/use';
import { useGlobSetting } from '/@/hooks/setting';
import pkg from '../../../package.json';
const globSetting = useGlobSetting();

View File

@ -10,7 +10,7 @@ import { AxiosTransform } from './axiosTransform';
import { checkStatus } from './checkStatus';
import { useGlobSetting } from '/@/settings/use';
import { useGlobSetting } from '/@/hooks/setting';
import { useMessage } from '/@/hooks/web/useMessage';
import { RequestEnum, ResultEnum, ContentTypeEnum } from '/@/enums/httpEnum';

View File

@ -11,6 +11,7 @@ export function getPopupContainer(node?: HTMLElement): HTMLElement {
}
return document.body;
}
/**
* Add the object as a parameter to the URL
* @param baseUrl url
@ -64,3 +65,16 @@ export function unique<T = any>(arr: T[], key: string): T[] {
export function es6Unique<T>(arr: T[]): T[] {
return Array.from(new Set(arr));
}
export function openWindow(
url: string,
opt?: { target?: TargetContext | string; noopener?: boolean; noreferrer?: boolean }
) {
const { target = '__blank', noopener = true, noreferrer = true } = opt || {};
const feature: string[] = [];
noopener && feature.push('noopener=yes');
noreferrer && feature.push('noreferrer=yes');
window.open(url, target, feature.join(','));
}

View File

@ -45,6 +45,8 @@
import Icon from '/@/components/Icon/index';
import { openWindow } from '/@/utils';
export default defineComponent({
components: {
CollapseContainer,
@ -61,7 +63,7 @@
setup() {
return {
toIconify: () => {
window.open('https://iconify.design/', '__blank');
openWindow('https://iconify.design/');
},
};
},

View File

@ -16,23 +16,23 @@
</a-card>
</div>
<AppFooterToolbar>
<AppPageFooter>
<template #right>
<a-button type="primary" @click="submitAll">提交</a-button>
</template>
</AppFooterToolbar>
</AppPageFooter>
</div>
</template>
<script lang="ts">
import { BasicForm, useForm } from '/@/components/Form';
import { defineComponent, ref } from 'vue';
import PersonTable from './PersonTable.vue';
import { AppFooterToolbar } from '/@/components/Application';
import { AppPageFooter } from '/@/components/Application';
import { schemas, taskSchemas } from './data';
export default defineComponent({
components: { BasicForm, PersonTable, AppFooterToolbar },
components: { BasicForm, PersonTable, AppPageFooter },
setup() {
const tableRef = ref<{ getDataSource: () => any } | null>(null);

View File

@ -72,7 +72,7 @@
// import { appStore } from '/@/store/modules/app';
import { useMessage } from '/@/hooks/web/useMessage';
import { useGlobSetting } from '/@/settings/use';
import { useGlobSetting } from '/@/hooks/setting';
import logo from '/@/assets/images/logo.png';
export default defineComponent({

View File

@ -30,7 +30,7 @@ function pathResolve(dir: string) {
const viteConfig: UserConfig = {
/**
* Entry. Use this to specify a js entry file in use cases where an
* Entry. Use this to specify a js entry file in setting cases where an
* `index.html` does not exist (e.g. serving vite assets from a different host)
* @default 'index.html'
*/
@ -51,7 +51,7 @@ const viteConfig: UserConfig = {
*/
open: false,
/**
* Set to `false` to disable minification, or specify the minifier to use.
* Set to `false` to disable minification, or specify the minifier to setting.
* Available options are 'terser' or 'esbuild'.
* @default 'terser'
*/
@ -112,7 +112,7 @@ const viteConfig: UserConfig = {
},
define: {
__VERSION__: pkg.version,
// use vue-i18-next
// setting vue-i18-next
// Suppress warning
__VUE_I18N_LEGACY_API__: false,
__VUE_I18N_FULL_INSTALL__: false,