Commit c6c2a115 authored by Sendya's avatar Sendya

fix: FooterToolbar auto margin

parent 0f46541a
...@@ -224,8 +224,14 @@ const layoutConf = reactive({ ...@@ -224,8 +224,14 @@ const layoutConf = reactive({
</template> </template>
``` ```
### Use WaterMark
```vue
<router-view v-slot="{ Component }">
<WaterMark content="Pro Layout">
<component :is="Component" />
</WaterMark>
</router-view>
```
## Build project ## Build project
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
v-bind="state" v-bind="state"
:loading="loading" :loading="loading"
:breadcrumb="{ routes: breadcrumb }" :breadcrumb="{ routes: breadcrumb }"
iconfont-url="//at.alicdn.com/t/font_2804900_26tw52dc2pl.js" iconfont-url="//at.alicdn.com/t/font_2804900_nzigh7z84gc.js"
> >
<template #menuHeaderRender> <template #menuHeaderRender>
<a> <a>
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
</router-link> </router-link>
</template> </template>
<template #menuExtraRender="{ collapsed }"> <template #menuExtraRender="{ collapsed }">
<a-input-search v-if="!collapsed" /> <a-input-search v-if="!collapsed" @search="handleSearch" />
</template> </template>
<template #menuFooterRender> <template #menuFooterRender>
<a <a
...@@ -67,26 +67,13 @@ ...@@ -67,26 +67,13 @@
</a> </a>
</template> </template>
<!-- custom menu-item -->
<template #menuItemRender="{ item, icon }">
<a-menu-item
:key="item.path"
:disabled="item.meta?.disabled"
:danger="item.meta?.danger"
:icon="icon"
>
<router-link :to="{ path: item.path }">
<span class="ant-pro-menu-item">
<a-badge count="5" dot>
<span class="ant-pro-menu-item-title">{{ item.meta.title }}</span>
</a-badge>
</span>
</router-link>
</a-menu-item>
</template>
<!-- content begin --> <!-- content begin -->
<router-view /> <router-view v-slot="{ Component }">
<WaterMark content="Pro Layout">
<component :is="Component" />
</WaterMark>
</router-view>
<!-- content end --> <!-- content end -->
<FooterToolbar> <FooterToolbar>
<template #extra> <template #extra>
...@@ -120,8 +107,8 @@ ...@@ -120,8 +107,8 @@
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, reactive, ref, watchEffect } from 'vue'; import { computed, defineComponent, reactive, ref, watchEffect } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { Button, Input, Switch, Select, Avatar, Space, Badge, Menu } from 'ant-design-vue'; import { message, Button, Input, Switch, Select, Avatar, Space, Badge, Menu } from 'ant-design-vue';
import { getMenuData, clearMenuItem, FooterToolbar } from '@ant-design-vue/pro-layout'; import { getMenuData, clearMenuItem, WaterMark, FooterToolbar } from '@ant-design-vue/pro-layout';
import type { RouteContextProps } from '@ant-design-vue/pro-layout'; import type { RouteContextProps } from '@ant-design-vue/pro-layout';
const i18n = (t: string) => t; const i18n = (t: string) => t;
...@@ -130,6 +117,8 @@ export default defineComponent({ ...@@ -130,6 +117,8 @@ export default defineComponent({
name: 'BasicLayout', name: 'BasicLayout',
components: { components: {
FooterToolbar, FooterToolbar,
WaterMark,
[Button.name]: Button, [Button.name]: Button,
[Input.name]: Input, [Input.name]: Input,
[Input.Search.name]: Input.Search, [Input.Search.name]: Input.Search,
...@@ -137,7 +126,6 @@ export default defineComponent({ ...@@ -137,7 +126,6 @@ export default defineComponent({
[Select.name]: Select, [Select.name]: Select,
[Select.Option.displayName!]: Select.Option, [Select.Option.displayName!]: Select.Option,
[Space.name]: Space, [Space.name]: Space,
[Badge.name]: Badge, [Badge.name]: Badge,
[Avatar.name]: Avatar, [Avatar.name]: Avatar,
[Menu.Item.name]: Menu.Item, [Menu.Item.name]: Menu.Item,
...@@ -161,7 +149,7 @@ export default defineComponent({ ...@@ -161,7 +149,7 @@ export default defineComponent({
// title: 'ProLayout', // title: 'ProLayout',
// logo: 'https://alicdn.antdv.com/v2/assets/logo.1ef800a8.svg', // logo: 'https://alicdn.antdv.com/v2/assets/logo.1ef800a8.svg',
navTheme: 'dark', navTheme: 'dark',
layout: 'mix', layout: 'side',
fixSiderbar: true, fixSiderbar: true,
}); });
const breadcrumb = computed(() => const breadcrumb = computed(() =>
...@@ -202,6 +190,9 @@ export default defineComponent({ ...@@ -202,6 +190,9 @@ export default defineComponent({
handlePageLoadingClick, handlePageLoadingClick,
handleCollapsed, handleCollapsed,
handleSearch: () => {
message.info('search..');
},
}; };
}, },
}); });
......
import { computed, reactive, unref, defineComponent, toRefs } from 'vue'; import { computed, reactive, unref, defineComponent, toRefs } from 'vue'
import type { CSSProperties, PropType, ExtractPropTypes } from 'vue'; import type { CSSProperties, PropType, ExtractPropTypes } from 'vue'
import 'ant-design-vue/es/layout/style'; import 'ant-design-vue/es/layout/style'
import { Layout } from 'ant-design-vue'; import { Layout } from 'ant-design-vue'
import { withInstall } from 'ant-design-vue/es/_util/type'; import { withInstall } from 'ant-design-vue/es/_util/type'
import useMediaQuery from './hooks/useMediaQuery'; import useMediaQuery from './hooks/useMediaQuery'
import { defaultSettingProps } from './defaultSettings'; import { defaultSettingProps } from './defaultSettings'
import { provideRouteContext, defaultRouteContext, RouteContextProps } from './RouteContext'; import {
import SiderMenuWrapper, { siderMenuProps } from './SiderMenu'; provideRouteContext,
import { WrapContent } from './WrapContent'; defaultRouteContext,
import globalHeaderProps from './GlobalHeader/headerProps'; RouteContextProps,
import { HeaderView as Header, headerViewProps } from './Header'; } from './RouteContext'
import { getPropsSlot, getPropsSlotfn, PropTypes, pick } from './utils'; import SiderMenuWrapper, { siderMenuProps } from './SiderMenu'
import type { BreadcrumbProps } from './RouteContext'; import { WrapContent } from './WrapContent'
import type { CustomRender, FormatMessage, WithFalse } from './typings'; import globalHeaderProps from './GlobalHeader/headerProps'
import { HeaderView as Header, headerViewProps } from './Header'
import {
getPropsSlot,
getPropsSlotfn,
PropTypes,
getMenuFirstChildren,
pick,
} from './utils'
import type { BreadcrumbProps } from './RouteContext'
import type { CustomRender, FormatMessage, WithFalse } from './typings'
import PageLoading from './PageLoading'; import PageLoading from './PageLoading'
import './BasicLayout.less'; import './BasicLayout.less'
export const basicLayoutProps = { export const basicLayoutProps = {
...defaultSettingProps, ...defaultSettingProps,
...@@ -30,7 +40,7 @@ export const basicLayoutProps = { ...@@ -30,7 +40,7 @@ export const basicLayoutProps = {
locale: { locale: {
type: [Function, Boolean] as PropType<WithFalse<FormatMessage>>, type: [Function, Boolean] as PropType<WithFalse<FormatMessage>>,
default() { default() {
return (s: string) => s; return (s: string) => s
}, },
}, },
/** /**
...@@ -47,11 +57,15 @@ export const basicLayoutProps = { ...@@ -47,11 +57,15 @@ export const basicLayoutProps = {
default: () => null, default: () => null,
}, },
collapsedButtonRender: { collapsedButtonRender: {
type: [Function, Object, Boolean] as PropType<WithFalse<(collapsed?: boolean) => any>>, type: [Function, Object, Boolean] as PropType<
WithFalse<(collapsed?: boolean) => any>
>,
default: () => undefined, default: () => undefined,
}, },
breadcrumbRender: { breadcrumbRender: {
type: [Object, Function, Boolean] as PropType<WithFalse<BreadcrumbProps['itemRender']>>, type: [Object, Function, Boolean] as PropType<
WithFalse<BreadcrumbProps['itemRender']>
>,
default: () => {}, default: () => {},
}, },
headerContentRender: { headerContentRender: {
...@@ -59,18 +73,24 @@ export const basicLayoutProps = { ...@@ -59,18 +73,24 @@ export const basicLayoutProps = {
default: () => undefined, default: () => undefined,
}, },
headerRender: { headerRender: {
type: [Object, Function, Boolean] as PropType<WithFalse<(props: any /* HeaderProps */) => any>>, type: [Object, Function, Boolean] as PropType<
WithFalse<(props: any /* HeaderProps */) => any>
>,
default: () => undefined, default: () => undefined,
}, },
footerRender: { footerRender: {
type: [Object, Function, Boolean] as PropType<WithFalse<(props: any /* FooterProps */) => any>>, type: [Object, Function, Boolean] as PropType<
WithFalse<(props: any /* FooterProps */) => any>
>,
default: () => undefined, default: () => undefined,
}, },
colSize: PropTypes.string, colSize: PropTypes.string,
contentStyle: PropTypes.style, contentStyle: PropTypes.style,
}; }
export type BasicLayoutProps = Partial<ExtractPropTypes<typeof basicLayoutProps>>; export type BasicLayoutProps = Partial<
ExtractPropTypes<typeof basicLayoutProps>
>
const ProLayout = defineComponent({ const ProLayout = defineComponent({
name: 'ProLayout', name: 'ProLayout',
...@@ -87,34 +107,49 @@ const ProLayout = defineComponent({ ...@@ -87,34 +107,49 @@ const ProLayout = defineComponent({
'menuClick', 'menuClick',
], ],
setup(props, { emit, slots }) { setup(props, { emit, slots }) {
const isTop = computed(() => props.layout === 'top'); console.log('props', props)
const siderWidth = computed(() => (props.collapsed ? props.collapsedWidth : props.siderWidth)); const isTop = computed(() => props.layout === 'top')
const hasSide = computed(
() => props.layout === 'mix' || props.layout === 'side' || false
)
const hasSplitMenu = computed(
() => props.layout === 'mix' && props.splitMenus
)
const hasFlatMenu = computed(() => {
return hasSide.value && hasSplitMenu.value
})
const siderWidth = computed(() =>
props.collapsed ? props.collapsedWidth : props.siderWidth
)
// if on event and @event // if on event and @event
const onCollapse = (collapsed: boolean) => { const onCollapse = (collapsed: boolean) => {
emit('update:collapsed', collapsed); emit('update:collapsed', collapsed)
emit('collapse', collapsed); emit('collapse', collapsed)
}; }
const onOpenKeys = (openKeys: string[] | false) => { const onOpenKeys = (openKeys: string[] | false) => {
emit('update:open-keys', openKeys); emit('update:open-keys', openKeys)
emit('openKeys', openKeys); emit('openKeys', openKeys)
}; }
const onSelect = (selectedKeys: string[] | false) => { const onSelect = (selectedKeys: string[] | false) => {
emit('update:selected-keys', selectedKeys); emit('update:selected-keys', selectedKeys)
emit('select', selectedKeys); emit('select', selectedKeys)
}; }
const onMenuHeaderClick = (e: MouseEvent) => { const onMenuHeaderClick = (e: MouseEvent) => {
emit('menuHeaderClick', e); emit('menuHeaderClick', e)
}; }
const onMenuClick = (args: any) => { const onMenuClick = (args: any) => {
emit('menuClick', args); emit('menuClick', args)
}; }
const colSize = useMediaQuery(); const colSize = useMediaQuery()
const isMobile = computed( const isMobile = computed(
() => (colSize.value === 'sm' || colSize.value === 'xs') && !props.disableMobile, () =>
); (colSize.value === 'sm' || colSize.value === 'xs') &&
const baseClassName = computed(() => `${props.prefixCls}-basicLayout`); !props.disableMobile
)
const baseClassName = computed(() => `${props.prefixCls}-basicLayout`)
// gen className // gen className
const className = computed(() => { const className = computed(() => {
return { return {
...@@ -124,41 +159,52 @@ const ProLayout = defineComponent({ ...@@ -124,41 +159,52 @@ const ProLayout = defineComponent({
[`${baseClassName.value}-is-children`]: props.isChildrenLayout, [`${baseClassName.value}-is-children`]: props.isChildrenLayout,
[`${baseClassName.value}-fix-siderbar`]: props.fixSiderbar, [`${baseClassName.value}-fix-siderbar`]: props.fixSiderbar,
[`${baseClassName.value}-${props.layout}`]: props.layout, [`${baseClassName.value}-${props.layout}`]: props.layout,
}; }
}); })
// siderMenuDom 为空的时候,不需要 padding // siderMenuDom 为空的时候,不需要 padding
const genLayoutStyle = reactive<CSSProperties>({ const genLayoutStyle = reactive<CSSProperties>({
position: 'relative', position: 'relative',
}); })
// if is some layout children, don't need min height // if is some layout children, don't need min height
if (props.isChildrenLayout || (props.contentStyle && props.contentStyle.minHeight)) { if (
genLayoutStyle.minHeight = 0; props.isChildrenLayout ||
(props.contentStyle && props.contentStyle.minHeight)
) {
genLayoutStyle.minHeight = 0
} }
const headerRender = ( const headerRender = (
p: BasicLayoutProps & { p: BasicLayoutProps & {
hasSiderMenu: boolean; hasSiderMenu: boolean
headerRender: WithFalse<CustomRender>; headerRender: WithFalse<CustomRender>
rightContentRender: WithFalse<CustomRender>; rightContentRender: WithFalse<CustomRender>
}, },
matchMenuKeys?: string[], matchMenuKeys?: string[]
): CustomRender | null => { ): CustomRender | null => {
if (p.headerRender === false || p.pure) { if (p.headerRender === false || p.pure) {
return null; return null
}
return <Header {...p} matchMenuKeys={matchMenuKeys || []} />
} }
return <Header {...p} matchMenuKeys={matchMenuKeys || []} />;
};
const breadcrumb = computed<BreadcrumbProps>(() => ({ const breadcrumb = computed<BreadcrumbProps>(() => ({
...props.breadcrumb, ...props.breadcrumb,
itemRender: getPropsSlotfn(slots, props, 'breadcrumbRender'), itemRender: getPropsSlotfn(slots, props, 'breadcrumbRender'),
})); }))
const flatMenuData = computed(
() =>
(hasFlatMenu.value &&
props.selectedKeys &&
getMenuFirstChildren(props.menuData, props.selectedKeys[0])) ||
[]
)
const routeContext = reactive<RouteContextProps>( const routeContext = reactive<RouteContextProps>(
Object.assign( Object.assign(
{...defaultRouteContext}, { ...defaultRouteContext },
pick(toRefs(props), [ pick(toRefs(props), [
'locale', 'locale',
'menuData', 'menuData',
...@@ -176,11 +222,15 @@ const ProLayout = defineComponent({ ...@@ -176,11 +222,15 @@ const ProLayout = defineComponent({
]) as any, ]) as any,
{ {
isMobile, isMobile,
siderWidth,
breadcrumb, breadcrumb,
flatMenuData,
hasSide,
flatMenu: hasFlatMenu,
} }
) )
); )
provideRouteContext(routeContext); provideRouteContext(routeContext)
return () => { return () => {
const { const {
...@@ -190,24 +240,44 @@ const ProLayout = defineComponent({ ...@@ -190,24 +240,44 @@ const ProLayout = defineComponent({
onSelect: propsOnSelect, onSelect: propsOnSelect,
onMenuClick: propsOnMenuClick, onMenuClick: propsOnMenuClick,
...restProps ...restProps
} = props; } = props
const collapsedButtonRender = getPropsSlotfn(slots, props, 'collapsedButtonRender'); const collapsedButtonRender = getPropsSlotfn(
const headerContentRender = getPropsSlot(slots, props, 'headerContentRender'); slots,
const rightContentRender = getPropsSlot(slots, props, 'rightContentRender'); props,
const customHeaderRender = getPropsSlot(slots, props, 'headerRender'); 'collapsedButtonRender'
const menuHeaderRender = getPropsSlotfn(slots, props, 'menuHeaderRender'); )
const menuContentRender = getPropsSlotfn(slots, props, 'menuContentRender'); const headerContentRender = getPropsSlot(
const menuExtraRender = getPropsSlotfn(slots, props, 'menuExtraRender'); slots,
const menuFooterRender = getPropsSlotfn(slots, props, 'menuFooterRender'); props,
const footerRender = getPropsSlot(slots, props, 'footerRender'); 'headerContentRender'
)
const rightContentRender = getPropsSlot(
slots,
props,
'rightContentRender'
)
const customHeaderRender = getPropsSlot(slots, props, 'headerRender')
const menuHeaderRender = getPropsSlotfn(slots, props, 'menuHeaderRender')
const menuContentRender = getPropsSlotfn(
slots,
props,
'menuContentRender'
)
const menuExtraRender = getPropsSlotfn(slots, props, 'menuExtraRender')
const menuFooterRender = getPropsSlotfn(slots, props, 'menuFooterRender')
const footerRender = getPropsSlot(slots, props, 'footerRender')
// menu render // menu render
const menuItemRender = getPropsSlotfn(slots, props, 'menuItemRender'); const menuItemRender = getPropsSlotfn(slots, props, 'menuItemRender')
const subMenuItemRender = getPropsSlotfn(slots, props, 'subMenuItemRender'); const subMenuItemRender = getPropsSlotfn(
slots,
props,
'subMenuItemRender'
)
const menuRenders = { const menuRenders = {
menuItemRender, menuItemRender,
subMenuItemRender, subMenuItemRender,
}; }
const headerDom = computed(() => const headerDom = computed(() =>
headerRender( headerRender(
...@@ -227,13 +297,15 @@ const ProLayout = defineComponent({ ...@@ -227,13 +297,15 @@ const ProLayout = defineComponent({
menuContentRender: menuContentRender, menuContentRender: menuContentRender,
headerContentRender, headerContentRender,
headerRender: customHeaderRender, headerRender: customHeaderRender,
theme: (props.navTheme || 'dark').toLocaleLowerCase().includes('dark') theme: (props.navTheme || 'dark')
.toLocaleLowerCase()
.includes('dark')
? 'dark' ? 'dark'
: 'light', : 'light',
}, },
props.matchMenuKeys, props.matchMenuKeys
), )
); )
return ( return (
<> <>
...@@ -262,7 +334,11 @@ const ProLayout = defineComponent({ ...@@ -262,7 +334,11 @@ const ProLayout = defineComponent({
{headerDom.value} {headerDom.value}
<WrapContent <WrapContent
isChildrenLayout={props.isChildrenLayout} isChildrenLayout={props.isChildrenLayout}
style={props.disableContentMargin ? undefined : props.contentStyle} style={
props.disableContentMargin
? undefined
: props.contentStyle
}
> >
{props.loading ? <PageLoading /> : slots.default?.()} {props.loading ? <PageLoading /> : slots.default?.()}
</WrapContent> </WrapContent>
...@@ -272,9 +348,9 @@ const ProLayout = defineComponent({ ...@@ -272,9 +348,9 @@ const ProLayout = defineComponent({
</div> </div>
)} )}
</> </>
); )
}; }
}, },
}); })
export default withInstall(ProLayout); export default withInstall(ProLayout)
import './index.less'; import './index.less'
import { computed, defineComponent, onBeforeUnmount, onMounted, PropType } from 'vue'; import {
import { RouteContextProps, useRouteContext } from '../RouteContext'; computed,
import { getMenuFirstChildren, getPropsSlot, getPropsSlotfn } from '../utils'; defineComponent,
import type { CustomRender } from '../typings'; onBeforeUnmount,
onMounted,
unref,
PropType,
} from 'vue'
import { RouteContextProps, useRouteContext } from '../RouteContext'
import { getPropsSlotfn } from '../utils'
import type { CustomRender } from '../typings'
export interface FooterToolbarProps { export interface FooterToolbarProps {
extra?: CustomRender | JSX.Element; extra?: CustomRender | JSX.Element
renderContent?: ( renderContent?: (
props: FooterToolbarProps & RouteContextProps & { leftWidth?: string }, props: FooterToolbarProps & RouteContextProps & { leftWidth?: string },
dom: CustomRender | JSX.Element, dom: CustomRender | JSX.Element
) => CustomRender | JSX.Element; ) => CustomRender | JSX.Element
getContainer?: (triggerNode: HTMLElement) => HTMLElement | null; getContainer?: (triggerNode: HTMLElement) => HTMLElement | null
prefixCls?: string; prefixCls?: string
} }
const footerToolbarProps = { const footerToolbarProps = {
...@@ -25,52 +32,47 @@ const footerToolbarProps = { ...@@ -25,52 +32,47 @@ const footerToolbarProps = {
type: [Function, Object] as PropType<FooterToolbarProps['getContainer']>, type: [Function, Object] as PropType<FooterToolbarProps['getContainer']>,
}, },
prefixCls: { type: String as PropType<string> }, prefixCls: { type: String as PropType<string> },
}; }
const FooterToolbar = defineComponent({ const FooterToolbar = defineComponent({
name: 'FooterToolbar', name: 'FooterToolbar',
props: footerToolbarProps, props: footerToolbarProps,
setup(props, { slots }) { setup(props, { slots }) {
const routeContext = useRouteContext(); const context = useRouteContext()
const { getPrefixCls } = routeContext; const baseClassName = props.prefixCls || context.getPrefixCls('footer-bar')
const baseClassName = props.prefixCls || getPrefixCls('footer-bar');
// matchMenuKeys const hasFlatMenu = computed(() => {
const matchMenuChildrenSize = computed( return unref(context.flatMenuData).length > 0
() => })
(
(routeContext.menuData &&
getMenuFirstChildren(
routeContext.menuData,
(routeContext.selectedKeys && routeContext.selectedKeys[0]) || undefined,
)) ||
[]
).length,
);
const hasSide = computed(() => {
return routeContext.layout === 'mix' && routeContext.splitMenus
? matchMenuChildrenSize.value > 0
: true;
});
const width = computed(() => { const width = computed(() => {
const { isMobile, sideWidth, layout } = routeContext; const { isMobile, hasSide, siderWidth, layout } = context
if (!sideWidth || layout === 'top') { if (!siderWidth || layout === 'top') {
return '100%'; return '100%'
} }
if (!hasSide.value) { console.log(
return '100%'; 'x',
unref(siderWidth),
'hasFlatMenu',
unref(hasFlatMenu),
'hasSide',
unref(context.hasSide)
)
if (!hasFlatMenu.value && !unref(hasSide)) {
return '100%'
} }
return isMobile ? '100%' : `calc(100% - ${sideWidth}px)`; console.log('x2', unref(context.hasSide))
}); return isMobile ? '100%' : `calc(100% - ${siderWidth}px)`
})
onMounted(() => { onMounted(() => {
routeContext.setHasFooterToolbar && routeContext.setHasFooterToolbar(true); context.setHasFooterToolbar && context.setHasFooterToolbar(true)
}); })
onBeforeUnmount(() => { onBeforeUnmount(() => {
routeContext.setHasFooterToolbar && routeContext.setHasFooterToolbar(false); context.setHasFooterToolbar && context.setHasFooterToolbar(false)
}); })
return () => { return () => {
const extra = getPropsSlotfn(slots, props, 'extra'); const extra = getPropsSlotfn(slots, props, 'extra')
const dom = () => { const dom = () => {
return ( return (
<> <>
...@@ -79,24 +81,24 @@ const FooterToolbar = defineComponent({ ...@@ -79,24 +81,24 @@ const FooterToolbar = defineComponent({
</div> </div>
<div class={`${baseClassName}-right`}>{slots.default?.()}</div> <div class={`${baseClassName}-right`}>{slots.default?.()}</div>
</> </>
); )
}; }
return ( return (
<div class={baseClassName} style={{ width: width.value }}> <div class={baseClassName} style={{ width: width.value }}>
{props.renderContent {props.renderContent
? props.renderContent( ? props.renderContent(
{ {
...props, ...props,
...routeContext, ...context,
leftWidth: width.value, leftWidth: width.value,
}, },
dom(), dom()
) )
: dom()} : dom()}
</div> </div>
); )
}; }
}, },
}); })
export default FooterToolbar; export default FooterToolbar
import { InjectionKey, provide, reactive, Ref, VNodeChild, ComputedRef } from 'vue'; import {
import { createContext, useContext } from './hooks/context'; InjectionKey,
import { MenuDataItem, FormatMessage, WithFalse } from './typings'; provide,
import { PureSettings } from './defaultSettings'; reactive,
Ref,
VNodeChild,
ComputedRef,
} from 'vue'
import { createContext, useContext } from './hooks/context'
import { MenuDataItem, FormatMessage, WithFalse } from './typings'
import { PureSettings } from './defaultSettings'
export interface Route { export interface Route {
path: string; path: string
breadcrumbName: string; breadcrumbName: string
children?: Omit<Route, 'children'>[]; children?: Omit<Route, 'children'>[]
} }
export interface BreadcrumbProps { export interface BreadcrumbProps {
prefixCls?: string; prefixCls?: string
routes?: Route[]; routes?: Route[]
params?: any; params?: any
separator?: VNodeChild; separator?: VNodeChild
itemRender?: (opts: { itemRender?: (opts: {
route: Route; route: Route
params: any; params: any
routes: Array<Route>; routes: Array<Route>
paths: Array<string>; paths: Array<string>
}) => VNodeChild; }) => VNodeChild
} }
export type BreadcrumbListReturn = Pick< export type BreadcrumbListReturn = Pick<
BreadcrumbProps, BreadcrumbProps,
Extract<keyof BreadcrumbProps, 'routes' | 'itemRender'> Extract<keyof BreadcrumbProps, 'routes' | 'itemRender'>
>; >
export interface MenuState { export interface MenuState {
selectedKeys: string[]; selectedKeys: string[]
openKeys: string[]; openKeys: string[]
} }
export interface RouteContextProps extends Partial<PureSettings>, MenuState { export interface RouteContextProps extends Partial<PureSettings>, MenuState {
menuData: MenuDataItem[]; menuData: MenuDataItem[]
flatMenuData?: MenuDataItem[]
getPrefixCls?: (suffixCls?: string, customizePrefixCls?: string) => string; getPrefixCls?: (suffixCls?: string, customizePrefixCls?: string) => string
locale?: WithFalse<FormatMessage>; locale?: WithFalse<FormatMessage>
breadcrumb?: BreadcrumbListReturn | ComputedRef<BreadcrumbListReturn>; breadcrumb?: BreadcrumbListReturn | ComputedRef<BreadcrumbListReturn>
isMobile?: boolean; isMobile?: boolean
prefixCls?: string; prefixCls?: string
collapsed?: boolean; collapsed?: boolean
hasSideMenu?: boolean; hasSideMenu?: boolean
hasHeader?: boolean; hasHeader?: boolean
sideWidth?: number; siderWidth?: number
headerHeight?: number; headerHeight?: number
hasFooterToolbar?: boolean; hasFooterToolbar?: boolean
hasFooter?: boolean; hasFooter?: boolean
setHasFooterToolbar?: (bool: boolean) => void; hasSide?: boolean
setHasFooterToolbar?: (bool: boolean) => void
/* 附加属性 */ /* 附加属性 */
[key: string]: any; [key: string]: any
} }
export const defaultPrefixCls = 'ant-pro'; export const defaultPrefixCls = 'ant-pro'
export const getPrefixCls = (suffixCls?: string, customizePrefixCls?: string) => { export const getPrefixCls = (
if (customizePrefixCls) return customizePrefixCls; suffixCls?: string,
return suffixCls ? `${defaultPrefixCls}-${suffixCls}` : defaultPrefixCls; customizePrefixCls?: string
}; ) => {
if (customizePrefixCls) return customizePrefixCls
return suffixCls ? `${defaultPrefixCls}-${suffixCls}` : defaultPrefixCls
}
// set default context // set default context
export const defaultRouteContext = reactive({ export const defaultRouteContext = reactive({
...@@ -65,22 +77,31 @@ export const defaultRouteContext = reactive({ ...@@ -65,22 +77,31 @@ export const defaultRouteContext = reactive({
locale: (t: string) => t, locale: (t: string) => t,
contentWidth: 'Fluid', contentWidth: 'Fluid',
hasFooterToolbar: false, hasFooterToolbar: false,
}); })
const routeContextInjectKey: InjectionKey<RouteContextProps> = Symbol('route-context'); const routeContextInjectKey: InjectionKey<RouteContextProps> =
Symbol('route-context')
export const createRouteContext = () => export const createRouteContext = () =>
createContext<RouteContextProps>(routeContextInjectKey, 'RouteContext.Provider'); createContext<RouteContextProps>(
routeContextInjectKey,
'RouteContext.Provider'
)
export const provideRouteContext = (value: RouteContextProps | Ref<RouteContextProps>) => { export const provideRouteContext = (
provide(routeContextInjectKey, value); value: RouteContextProps | Ref<RouteContextProps>
}; ) => {
provide(routeContextInjectKey, value)
}
export const useRouteContext = () => export const useRouteContext = () =>
useContext<Required<RouteContextProps>>(routeContextInjectKey, defaultRouteContext); useContext<Required<RouteContextProps>>(
routeContextInjectKey,
defaultRouteContext
)
const Provider = createRouteContext(); const Provider = createRouteContext()
export default { export default {
Provider, Provider,
}; }
...@@ -5,24 +5,24 @@ import { ...@@ -5,24 +5,24 @@ import {
PropType, PropType,
CSSProperties, CSSProperties,
unref, unref,
} from 'vue'; } from 'vue'
import 'ant-design-vue/es/layout/style'; import 'ant-design-vue/es/layout/style'
import 'ant-design-vue/es/menu/style'; import 'ant-design-vue/es/menu/style'
import { Layout, Menu } from 'ant-design-vue'; import { Layout, Menu } from 'ant-design-vue'
import { MenuUnfoldOutlined, MenuFoldOutlined } from '@ant-design/icons-vue'; import { MenuUnfoldOutlined, MenuFoldOutlined } from '@ant-design/icons-vue'
import BaseMenu, { baseMenuProps } from './BaseMenu'; import BaseMenu, { baseMenuProps } from './BaseMenu'
import { WithFalse, CustomRender } from '../typings'; import { WithFalse, CustomRender } from '../typings'
import { SiderProps } from './typings'; import { SiderProps } from './typings'
import { defaultSettingProps } from '../defaultSettings'; import { defaultSettingProps } from '../defaultSettings'
import { useRouteContext } from '../RouteContext'; import { useRouteContext } from '../RouteContext'
import { PropTypes, getMenuFirstChildren } from '../utils'; import { PropTypes, getMenuFirstChildren } from '../utils'
import './index.less'; import './index.less'
const { Sider } = Layout; const { Sider } = Layout
export type PrivateSiderMenuProps = { export type PrivateSiderMenuProps = {
matchMenuKeys?: string[]; matchMenuKeys?: string[]
}; }
export const siderMenuProps = { export const siderMenuProps = {
...defaultSettingProps, ...defaultSettingProps,
...@@ -40,12 +40,16 @@ export const siderMenuProps = { ...@@ -40,12 +40,16 @@ export const siderMenuProps = {
collapsedWidth: PropTypes.number.def(48), collapsedWidth: PropTypes.number.def(48),
menuHeaderRender: { menuHeaderRender: {
type: [Function, Object] as PropType< type: [Function, Object] as PropType<
WithFalse<(logo: CustomRender, title: CustomRender, props?: any) => CustomRender> WithFalse<
(logo: CustomRender, title: CustomRender, props?: any) => CustomRender
>
>, >,
default: () => undefined, default: () => undefined,
}, },
menuFooterRender: { menuFooterRender: {
type: [Function, Object] as PropType<WithFalse<(props?: any) => CustomRender>>, type: [Function, Object] as PropType<
WithFalse<(props?: any) => CustomRender>
>,
default: () => undefined, default: () => undefined,
}, },
menuContentRender: { menuContentRender: {
...@@ -55,11 +59,15 @@ export const siderMenuProps = { ...@@ -55,11 +59,15 @@ export const siderMenuProps = {
default: () => undefined, default: () => undefined,
}, },
menuExtraRender: { menuExtraRender: {
type: [Function, Object] as PropType<WithFalse<(props?: any) => CustomRender>>, type: [Function, Object] as PropType<
WithFalse<(props?: any) => CustomRender>
>,
default: () => undefined, default: () => undefined,
}, },
collapsedButtonRender: { collapsedButtonRender: {
type: [Function, Object, Boolean] as PropType<WithFalse<(collapsed?: boolean) => CustomRender>>, type: [Function, Object, Boolean] as PropType<
WithFalse<(collapsed?: boolean) => CustomRender>
>,
default: () => undefined, default: () => undefined,
}, },
breakpoint: { breakpoint: {
...@@ -87,49 +95,52 @@ export const siderMenuProps = { ...@@ -87,49 +95,52 @@ export const siderMenuProps = {
onSelect: { onSelect: {
type: Function as PropType<(selectedKeys: WithFalse<string[]>) => void>, type: Function as PropType<(selectedKeys: WithFalse<string[]>) => void>,
}, },
}; }
export type SiderMenuProps = Partial<ExtractPropTypes<typeof siderMenuProps>>; export type SiderMenuProps = Partial<ExtractPropTypes<typeof siderMenuProps>>
export const defaultRenderLogo = (logo?: CustomRender, logoStyle?: CSSProperties): CustomRender => { export const defaultRenderLogo = (
logo?: CustomRender,
logoStyle?: CSSProperties
): CustomRender => {
if (!logo) { if (!logo) {
return null; return null
} }
if (typeof logo === 'string') { if (typeof logo === 'string') {
return <img src={logo} alt="logo" style={logoStyle} />; return <img src={logo} alt="logo" style={logoStyle} />
} }
if (typeof logo === 'function') { if (typeof logo === 'function') {
return logo(); return logo()
} }
return logo; return logo
}; }
export const defaultRenderLogoAndTitle = ( export const defaultRenderLogoAndTitle = (
props: SiderMenuProps, props: SiderMenuProps,
renderKey: string | undefined = 'menuHeaderRender', renderKey: string | undefined = 'menuHeaderRender'
): CustomRender | null => { ): CustomRender | null => {
const { const {
logo = 'https://gw.alipayobjects.com/zos/antfincdn/PmY%24TNNDBI/logo.svg', logo = 'https://gw.alipayobjects.com/zos/antfincdn/PmY%24TNNDBI/logo.svg',
logoStyle, logoStyle,
title, title,
layout, layout,
} = props; } = props
const renderFunction = (props as any)[renderKey || '']; const renderFunction = (props as any)[renderKey || '']
if (renderFunction === false) { if (renderFunction === false) {
return null; return null
} }
const logoDom = defaultRenderLogo(logo, logoStyle); const logoDom = defaultRenderLogo(logo, logoStyle)
const titleDom = <h1>{title}</h1>; const titleDom = <h1>{title}</h1>
if (layout === 'mix' && renderKey === 'menuHeaderRender') { if (layout === 'mix' && renderKey === 'menuHeaderRender') {
return null; return null
} }
// call menuHeaderRender // call menuHeaderRender
if (typeof renderFunction === 'function') { if (typeof renderFunction === 'function') {
// when collapsed, no render title // when collapsed, no render title
return renderFunction(logoDom, props.collapsed ? null : titleDom, props); return renderFunction(logoDom, props.collapsed ? null : titleDom, props)
} }
if (Array.isArray(renderFunction)) { if (Array.isArray(renderFunction)) {
return <>{renderFunction}</>; return <>{renderFunction}</>
} }
return ( return (
...@@ -137,11 +148,12 @@ export const defaultRenderLogoAndTitle = ( ...@@ -137,11 +148,12 @@ export const defaultRenderLogoAndTitle = (
{logoDom} {logoDom}
{props.collapsed ? null : titleDom} {props.collapsed ? null : titleDom}
</a> </a>
); )
}; }
export const defaultRenderCollapsedButton = (collapsed?: boolean): CustomRender => export const defaultRenderCollapsedButton = (
collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />; collapsed?: boolean
): CustomRender => (collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />)
const SiderMenu: FC<SiderMenuProps> = (props: SiderMenuProps) => { const SiderMenu: FC<SiderMenuProps> = (props: SiderMenuProps) => {
const { const {
...@@ -158,44 +170,42 @@ const SiderMenu: FC<SiderMenuProps> = (props: SiderMenuProps) => { ...@@ -158,44 +170,42 @@ const SiderMenu: FC<SiderMenuProps> = (props: SiderMenuProps) => {
onOpenKeys, onOpenKeys,
onSelect, onSelect,
onMenuHeaderClick, onMenuHeaderClick,
} = props; } = props
const context = useRouteContext(); const context = useRouteContext()
const { getPrefixCls } = context; const { getPrefixCls } = context
const baseClassName = getPrefixCls('sider'); const baseClassName = getPrefixCls('sider')
const hasSplitMenu = computed(() => props.layout === 'mix' && props.splitMenus); const hasSplitMenu = computed(
const hasSide = computed(() => props.layout === 'mix' || props.layout === 'side' || false); () => props.layout === 'mix' && props.splitMenus
)
const sTheme = computed(() => (props.layout === 'mix' && 'light') || props.navTheme); const sTheme = computed(
const sSideWidth = computed(() => (props.collapsed ? props.collapsedWidth : props.siderWidth)); () => (props.layout === 'mix' && 'light') || props.navTheme
)
const sSideWidth = computed(() =>
props.collapsed ? props.collapsedWidth : props.siderWidth
)
const classNames = computed(() => { const classNames = computed(() => {
return { return {
[baseClassName]: true, [baseClassName]: true,
[`${baseClassName}-${sTheme.value}`]: true, [`${baseClassName}-${sTheme.value}`]: true,
[`${baseClassName}-${props.layout}`]: true, [`${baseClassName}-${props.layout}`]: true,
[`${baseClassName}-fixed`]: context.fixSiderbar, [`${baseClassName}-fixed`]: context.fixSiderbar,
}; }
}); })
const flatMenuData = computed(
() =>
(hasSide.value &&
hasSplitMenu.value &&
getMenuFirstChildren(context.menuData, context.selectedKeys[0])) ||
[],
);
const handleSelect = ($event: string[]) => { const handleSelect = ($event: string[]) => {
if (props.onSelect) { if (props.onSelect) {
if (unref(hasSplitMenu)) { if (unref(hasSplitMenu)) {
props.onSelect([context.selectedKeys[0], ...$event]); props.onSelect([context.selectedKeys[0], ...$event])
return; return
}
props.onSelect($event)
} }
props.onSelect($event);
} }
};
// call menuHeaderRender // call menuHeaderRender
const headerDom = defaultRenderLogoAndTitle(props); const headerDom = defaultRenderLogoAndTitle(props)
const extraDom = menuExtraRender && menuExtraRender(props); const extraDom = menuExtraRender && menuExtraRender(props)
if (hasSplitMenu.value && flatMenuData.value.length === 0) { if (hasSplitMenu.value && unref(context.flatMenuData).length === 0) {
return null; return null
} }
const defaultMenuDom = ( const defaultMenuDom = (
<BaseMenu <BaseMenu
...@@ -203,7 +213,7 @@ const SiderMenu: FC<SiderMenuProps> = (props: SiderMenuProps) => { ...@@ -203,7 +213,7 @@ const SiderMenu: FC<SiderMenuProps> = (props: SiderMenuProps) => {
locale={props.locale || context.locale} locale={props.locale || context.locale}
theme={sTheme.value === 'realDark' ? 'dark' : sTheme.value} theme={sTheme.value === 'realDark' ? 'dark' : sTheme.value}
mode="inline" mode="inline"
menuData={hasSplitMenu.value ? flatMenuData.value : context.menuData} menuData={hasSplitMenu.value ? context.flatMenuData : context.menuData}
collapsed={props.collapsed} collapsed={props.collapsed}
openKeys={context.openKeys} openKeys={context.openKeys}
selectedKeys={context.selectedKeys} selectedKeys={context.selectedKeys}
...@@ -216,11 +226,12 @@ const SiderMenu: FC<SiderMenuProps> = (props: SiderMenuProps) => { ...@@ -216,11 +226,12 @@ const SiderMenu: FC<SiderMenuProps> = (props: SiderMenuProps) => {
}} }}
class={`${baseClassName}-menu`} class={`${baseClassName}-menu`}
{...{ {...{
'onUpdate:openKeys': ($event: string[]) => onOpenKeys && onOpenKeys($event), 'onUpdate:openKeys': ($event: string[]) =>
onOpenKeys && onOpenKeys($event),
'onUpdate:selectedKeys': handleSelect, 'onUpdate:selectedKeys': handleSelect,
}} }}
/> />
); )
return ( return (
<> <>
...@@ -242,14 +253,16 @@ const SiderMenu: FC<SiderMenuProps> = (props: SiderMenuProps) => { ...@@ -242,14 +253,16 @@ const SiderMenu: FC<SiderMenuProps> = (props: SiderMenuProps) => {
collapsed={collapsed} collapsed={collapsed}
breakpoint={breakpoint || undefined} breakpoint={breakpoint || undefined}
onCollapse={(collapse: boolean) => { onCollapse={(collapse: boolean) => {
if (props.isMobile) return; if (props.isMobile) return
onCollapse?.(collapse); onCollapse?.(collapse)
}} }}
collapsedWidth={collapsedWidth} collapsedWidth={collapsedWidth}
style={{ style={{
overflow: 'hidden', overflow: 'hidden',
paddingTop: paddingTop:
props.layout === 'mix' && !props.isMobile ? `${props.headerHeight}px` : undefined, props.layout === 'mix' && !props.isMobile
? `${props.headerHeight}px`
: undefined,
}} }}
width={siderWidth} width={siderWidth}
theme={sTheme.value === 'realDark' ? 'dark' : sTheme.value} theme={sTheme.value === 'realDark' ? 'dark' : sTheme.value}
...@@ -276,7 +289,8 @@ const SiderMenu: FC<SiderMenuProps> = (props: SiderMenuProps) => { ...@@ -276,7 +289,8 @@ const SiderMenu: FC<SiderMenuProps> = (props: SiderMenuProps) => {
</div> </div>
)} )}
<div style="flex: 1; overflow: hidden auto;"> <div style="flex: 1; overflow: hidden auto;">
{(menuContentRender && menuContentRender(props, defaultMenuDom)) || defaultMenuDom} {(menuContentRender && menuContentRender(props, defaultMenuDom)) ||
defaultMenuDom}
</div> </div>
<div class={`${baseClassName}-links`}> <div class={`${baseClassName}-links`}>
{collapsedButtonRender !== false ? ( {collapsedButtonRender !== false ? (
...@@ -290,7 +304,7 @@ const SiderMenu: FC<SiderMenuProps> = (props: SiderMenuProps) => { ...@@ -290,7 +304,7 @@ const SiderMenu: FC<SiderMenuProps> = (props: SiderMenuProps) => {
// @ts-ignore // @ts-ignore
onClick={() => { onClick={() => {
if (onCollapse) { if (onCollapse) {
onCollapse(!props.collapsed); onCollapse(!props.collapsed)
} }
}} }}
> >
...@@ -299,17 +313,20 @@ const SiderMenu: FC<SiderMenuProps> = (props: SiderMenuProps) => { ...@@ -299,17 +313,20 @@ const SiderMenu: FC<SiderMenuProps> = (props: SiderMenuProps) => {
class={`${baseClassName}-collapsed-button`} class={`${baseClassName}-collapsed-button`}
title={false} title={false}
> >
{collapsedButtonRender && typeof collapsedButtonRender === 'function' {collapsedButtonRender &&
typeof collapsedButtonRender === 'function'
? collapsedButtonRender(collapsed) ? collapsedButtonRender(collapsed)
: collapsedButtonRender} : collapsedButtonRender}
</Menu.Item> </Menu.Item>
</Menu> </Menu>
) : null} ) : null}
</div> </div>
{menuFooterRender && <div class={`${baseClassName}-footer`}>{menuFooterRender(props)}</div>} {menuFooterRender && (
<div class={`${baseClassName}-footer`}>{menuFooterRender(props)}</div>
)}
</Sider> </Sider>
</> </>
); )
}; }
export default SiderMenu; export default SiderMenu
...@@ -197,6 +197,7 @@ ...@@ -197,6 +197,7 @@
.@{ant-prefix}-menu-submenu-title { .@{ant-prefix}-menu-submenu-title {
.anticon { .anticon {
transition: none; transition: none;
font-size: 16px;
} }
} }
......
...@@ -68,6 +68,7 @@ const getPixelRatio = (context: any) => { ...@@ -68,6 +68,7 @@ const getPixelRatio = (context: any) => {
} }
const WaterMark = defineComponent({ const WaterMark = defineComponent({
name: 'WaterMark',
props: waterMarkProps, props: waterMarkProps,
setup(props, { slots }) { setup(props, { slots }) {
const { const {
...@@ -145,6 +146,7 @@ const WaterMark = defineComponent({ ...@@ -145,6 +146,7 @@ const WaterMark = defineComponent({
} }
}) })
return () => {
return ( return (
<div <div
style={{ style={{
...@@ -171,6 +173,7 @@ const WaterMark = defineComponent({ ...@@ -171,6 +173,7 @@ const WaterMark = defineComponent({
/> />
</div> </div>
) )
}
}, },
}) })
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment