Commit e0039d25 authored by Sendya's avatar Sendya

fix: router-context

parent 9bdab4c1
import 'ant-design-vue/dist/antd.less';
import { createApp, defineComponent, reactive } from 'vue';
import { createApp, defineComponent, watch, ref } from 'vue';
import { RouterLink } from './mock-router';
import { Button, Avatar, message } from 'ant-design-vue';
import { default as ProLayout } from '../src/';
......@@ -12,49 +12,54 @@ const BasicLayout = defineComponent({
name: 'BasicLayout',
inheritAttrs: false,
setup(_, { attrs }) {
const [menuState] = useMenuState({
const [ state, RouteContextProvider ] = createRouteContext({
collapsed: false,
openKeys: [],
openKeys: ['/dashboard', '/form'],
onOpenKeys: (keys: string[]) => (state.openKeys = keys),
selectedKeys: ['/welcome'],
});
onSelectedKeys: (keys: string[]) => (state.selectedKeys = keys),
const [ routeContext, RouteContextProvider ] = createRouteContext({
isMobile: false,
menuData: [],
fixSiderbar: false,
fixedHeader: false,
menuData: menus,
sideWidth: 208,
hasSideMenu: true,
hasHeader: true,
hasFooterToolbar: false,
setHasFooterToolbar: (has: boolean) => (routeContext.hasFooterToolbar = has),
setHasFooterToolbar: (has: boolean) => (state.hasFooterToolbar = has),
});
const cacheOpenKeys = ref<string[]>([]);
watch(
() => state.collapsed,
(collapsed: boolean) => {
if (collapsed) {
cacheOpenKeys.value = state.openKeys;
state.openKeys = [];
} else {
state.openKeys = cacheOpenKeys.value;
}
}
);
return () => (
<RouteContextProvider>
<ProLayout
{...attrs}
v-model={[menuState.collapsed, 'collapsed']}
v-model={[state.collapsed, 'collapsed']}
title={'Pro Layout'}
layout={'mix'}
theme={'light'}
layout={'side'}
navTheme={'dark'}
i18n={(key: string) => key}
isMobile={false}
menuData={menus}
matchMenuKeys={[]}
isMobile={state.isMobile}
fixSiderbar={state.fixSiderbar}
fixedHeader={state.fixedHeader}
contentWidth={'Fixed'}
primaryColor={'#1890ff'}
contentStyle={{ minHeight: '500px' }}
siderWidth={208}
openKeys={menuState.openKeys}
selectedKeys={menuState.selectedKeys}
{...{
'onUpdate:openKeys':$event => {
$event && (menuState.openKeys = $event);
},
'onUpdate:selectedKeys': $event => {
$event && (menuState.selectedKeys = $event);
}
}}
siderWidth={state.sideWidth}
v-slots={{
rightContentRender: () => (
<div style="color: #FFF;margin-right: 16px;">
......@@ -64,11 +69,8 @@ const BasicLayout = defineComponent({
menuHeaderRender: () => (
<a>
<img src="https://gw.alipayobjects.com/zos/antfincdn/PmY%24TNNDBI/logo.svg" />
{menuState.collapsed ? null : (<h1>Pro Layout</h1>)}
{state.collapsed ? null : (<h1>Pro Layout</h1>)}
</a>
),
footerRender: () => (
<div>123</div>
)
}}
>
......
......@@ -8,7 +8,7 @@ import { default as SiderMenuWrapper, SiderMenuWrapperProps } from './SiderMenu'
import { WrapContent } from './WrapContent';
import { default as Header, HeaderViewProps } from './Header';
import { RenderVNodeType, WithFalse } from './typings';
import { getComponentOrSlot } from './utils';
import { getComponentOrSlot, PropRenderType, PropTypes } from './utils';
import useMergedState from './hooks/useMergedState';
import './BasicLayout.less';
......@@ -141,8 +141,6 @@ const ProLayout: FunctionalComponent<BasicLayoutProps> = (props, { emit, slots,
{ !isTop.value && (<SiderMenuWrapper
{...props}
menuHeaderRender={menuHeaderRenderFunc || (menuHeaderRenderSlot && (() => menuHeaderRenderSlot()))}
onSelect={handleSelect}
onOpenChange={handleOpenChange}
onCollapse={handleCollapse}
/>)}
<Layout>
......@@ -150,35 +148,7 @@ const ProLayout: FunctionalComponent<BasicLayoutProps> = (props, { emit, slots,
<WrapContent style={props.contentStyle}>
{slots.default?.()}
</WrapContent>
{ footerRender && footerRender || footerRender !== false && (
<GlobalFooter
links={[
{
key: '1',
title: 'Pro Layout',
href: 'https://www.github.com/vueComponent/pro-layout',
blankTarget: true,
},
{
key: '2',
title: 'Github',
href: 'https://www.github.com/vueComponent/ant-design-vue-pro',
blankTarget: true,
},
{
key: '3',
title: '@Sendya',
href: 'https://www.github.com/sendya/',
blankTarget: true,
},
]}
copyright={
<a href="https://github.com/vueComponent" target="_blank">
vueComponent
</a>
}
/>
)}
{ footerRender !== false && (footerRender && footerRender)}
</Layout>
</Layout>
</div>
......@@ -191,61 +161,80 @@ ProLayout.inheritAttrs = false;
ProLayout.displayName = 'ProLayout';
ProLayout.emits = ['update:collapsed', 'update:openKeys', 'update:selectedKeys'];
ProLayout.props = {
prefixCls: {
type: String,
default: 'ant-pro',
},
title: String,
colSize: String,
isChildrenLayout: Boolean,
isMobile: Boolean,
fixSiderbar: {
type: Boolean,
default: () => false,
},
fixedHeader: {
type: Boolean,
default: () => false,
},
layout: String,
openKeys: Array,
selectedKeys: Array,
collapsed: Boolean,
menuData: Array,
contentStyle: Object,
theme: String,
headerTheme: {
type: String,
defualt: 'light',
},
navTheme: {
type: String,
default: 'light',
},
headerRender: {
type: [Function, Boolean],
default: () => undefined,
},
footerRender: {
type: [Function, Boolean],
default: () => undefined,
},
menuRender: {
type: [Function, Boolean],
default: () => undefined,
},
menuHeaderRender: {
type: [Function, Boolean],
default: () => undefined,
},
rightContentRender: {
type: [Function, Boolean],
default: () => undefined,
},
rightContent: {
type: [Function, Boolean],
default: () => undefined,
},
prefixCls: PropTypes.string.def('ant-pro'),
title: PropTypes.VNodeChild.def('Ant Design Pro'),
logo: PropTypes.VNodeChild,
/* 是否删除掉所有的自带界面 */
pure: PropTypes.bool,
/* layout 的加载态 */
loading: PropTypes.bool,
/* 用于生成菜单和面包屑 请从 RouterContext 注入 */
// menuData: PropTypes.array,
// location: PropTypes.string,
// Custom render
menuHeaderRender: PropRenderType,
menuFooterRender: PropRenderType,
menuExtraRender: PropRenderType,
/* 自定义头的 render 方法 (props: BasicLayoutProps) => VNode */
headerRender: PropRenderType,
/* 自定义头标题的方法,mix 模式下生效 (props: BasicLayoutProps) => VNode */
headerTitleRender: PropRenderType,
/* 自定义头内容的方法 (props: BasicLayoutProps) => VNode */
headerContentRender: PropRenderType,
/* 自定义头右部的 render 方法 (props: HeaderViewProps) => VNode */
rightContentRender: PropRenderType,
/* 自定义 collapsed button 的方法 (collapsed: boolean) => VNode */
collapsedButtonRender: PropRenderType,
/* 自定义页脚的 render 方法 (props: BasicLayoutProps) => VNode */
footerRender: PropRenderType,
/* 自定义页面标题的显示方法 (props: BasicLayoutProps) => VNode */
pageTitleRender: PropRenderType,
/* 自定义菜单的 render 方法 (props: HeaderViewProps) => VNode */
menuRender: PropRenderType,
/* 自定义菜单项的 render 方法 */
menuItemRender: PropRenderType,
/* 自定义拥有子菜单菜单项的 render 方法 */
subMenuItemRender: PropRenderType,
/* 自定义面包屑的数据 */
breadcrumbRender: PropRenderType,
// Event
onMenuHeaderClick: PropTypes.func,
onTopMixMenuHeaderClick: PropTypes.func,
// settings
contentStyle: PropTypes.style,
layout: PropTypes.string.def('side'),
contentWidth: PropTypes.string.def('Fluid'),
/* 导航的主题,side 和 mix 模式下是左侧菜单的主题,top 模式下是顶部菜单 */
navTheme: PropTypes.string.def('dark'),
/* 顶部导航的主题,mix 模式生效 */
headerTheme: PropTypes.string.def('dark'),
/* 是否固定导航 */
fixSiderbar: PropTypes.bool,
/* 是否固定 header 到顶部 */
fixedHeader: PropTypes.bool,
/* 触发响应式布局的断点 https://ant.design/components/grid-cn/#Col */
breakpoint: PropTypes.string.def('lg'),
/* 关于 menu 的配置,暂时只有 locale,locale 可以关闭 menu 的自带的全球化 */
menu: PropTypes.object,
/* 传递到 antd menu 组件的 props */
menuProps: PropTypes.object,
/* 使用 IconFont 的图标配置 */
iconfontUrl: PropTypes.string,
/* 当前 layout 的语言设置 */
locale: PropTypes.func.def((key: string) => key),
// settings
/* 侧边菜单宽度 */
siderWidth: PropTypes.number.def(208),
/* 控制菜单的收起和展开 */
collapsed: PropTypes.bool,
/* 菜单的折叠收起事件 (collapsed: boolean) => void */
onCollapse: PropTypes.func,
// onPageChange // 请使用 vue-router 监听
/* 禁止自动切换到移动页面 */
disableMobile: PropTypes.bool,
} as any;
export default withInstall(ProLayout);
......@@ -6,6 +6,7 @@ import { TopNavHeader } from '../TopNavHeader';
import { clearMenuItem } from '../utils';
import type { HeaderViewProps } from '../Header';
import './index.less';
import { useProProvider } from '../ProProvider';
export interface GlobalHeaderProps extends Partial<PureSettings> {
collapsed?: boolean;
......@@ -52,8 +53,10 @@ export const GlobalHeader: FunctionalComponent<GlobalHeaderProps & PrivateSiderM
headerTheme = 'dark',
splitMenus,
menuData,
prefixCls,
prefixCls: customPrefixCls,
} = props;
const { getPrefixCls } = useProProvider();
const prefixCls = customPrefixCls || getPrefixCls();
const baseClassName = computed(() => `${prefixCls}-global-header`);
const className = computed(() => {
return {
......@@ -89,7 +92,7 @@ export const GlobalHeader: FunctionalComponent<GlobalHeaderProps & PrivateSiderM
{isMobile && renderLogo(menuHeaderRender, logoDom)}
{isMobile && collapsedButtonRender && (
<span
class={`${baseClassName.value}-collapsed-button`}
class={`${baseClassName.value}-collapsed-button`}
onClick={() => {
if (onCollapse) {
onCollapse(!collapsed);
......
import { InjectionKey } from 'vue';
import { InjectionKey, VNodeChild } from 'vue';
import { createContext, useContext } from './hooks/context';
import { MenuDataItem } from './typings';
import { PureSettings } from './defaultSettings';
export interface Route {
path: string;
breadcrumbName: string;
children?: Omit<Route, 'children'>[];
}
export interface BreadcrumbProps {
prefixCls?: string;
routes?: Route[];
params?: any;
separator?: VNodeChild;
itemRender?: (route: Route, params: any, routes: Array<Route>, paths: Array<string>) => VNodeChild;
}
export type BreadcrumbListReturn = Pick<BreadcrumbProps, Extract<keyof BreadcrumbProps, 'routes' | 'itemRender'>>;
export interface MenuState {
selectedKeys?: string[];
openKeys?: string[];
onSelectedKeys: (key: string[]) => void;
onOpenKeys: (key: string[]) => void;
}
export interface RouteContextProps extends Partial<PureSettings> {
breadcrumb?: any;
menuData?: any[];
export interface RouteContextProps extends Partial<PureSettings>, MenuState {
breadcrumb?: BreadcrumbListReturn;
menuData?: MenuDataItem[];
isMobile?: boolean;
prefixCls?: string;
collapsed?: boolean;
......@@ -14,6 +36,8 @@ export interface RouteContextProps extends Partial<PureSettings> {
hasFooterToolbar?: boolean;
hasFooter?: boolean;
setHasFooterToolbar?: (bool: boolean) => void;
/* 附加属性 */
[key: string]: any;
}
const routeContextInjectKey: InjectionKey<RouteContextProps> = Symbol();
......@@ -21,4 +45,5 @@ const routeContextInjectKey: InjectionKey<RouteContextProps> = Symbol();
export const createRouteContext = (context: RouteContextProps) =>
createContext<RouteContextProps>(context, routeContextInjectKey);
export const useRouteContext = () => useContext<RouteContextProps>(routeContextInjectKey);
export const useRouteContext = () =>
useContext<RouteContextProps>(routeContextInjectKey);
......@@ -9,6 +9,7 @@ import { SiderProps } from './typings';
import { MenuUnfoldOutlined, MenuFoldOutlined } from '@ant-design/icons-vue';
import { useProProvider } from '../ProProvider';
import './index.less';
import { useRouteContext } from '../RouteContext';
const { Sider } = Layout;
......@@ -34,8 +35,8 @@ export interface SiderMenuProps
onMenuHeaderClick?: (e: MouseEvent) => void;
fixed?: boolean;
hide?: boolean;
onOpenChange?: (openKeys: WithFalse<string[]>) => void;
onSelect?: (selectedKeys: WithFalse<string[]>) => void;
// onOpenChange?: (openKeys: WithFalse<string[]>) => void;
// onSelect?: (selectedKeys: WithFalse<string[]>) => void;
}
export const defaultRenderLogo = (logo: RenderVNodeType): RenderVNodeType => {
......@@ -84,12 +85,10 @@ export const defaultRenderCollapsedButton = (collapsed?: boolean): RenderVNodeTy
const SiderMenu: FunctionalComponent<SiderMenuProps> = (props: SiderMenuProps) => {
const {
theme,
menuData,
navTheme,
// menuData,
collapsed,
siderWidth,
onOpenChange,
onSelect,
onCollapse,
breakpoint,
collapsedWidth = 48,
......@@ -99,42 +98,45 @@ const SiderMenu: FunctionalComponent<SiderMenuProps> = (props: SiderMenuProps) =
collapsedButtonRender = defaultRenderCollapsedButton,
} = props;
const { getPrefixCls } = useProProvider();
const context = useRouteContext();
const baseClassName = getPrefixCls('sider');
// const isMix = computed(() => props.layout === 'mix');
const fixed = computed(() => props.fixed);
// const fixed = computed(() => context.fixSiderbar);
// const runtimeTheme = computed(() => (props.layout === 'mix' && 'light') || 'dark');
const runtimeSideWidth = computed(() =>
props.collapsed ? props.collapsedWidth : props.siderWidth,
);
const classNames = ref({
[baseClassName]: true,
[`${baseClassName}-${theme}`]: true,
[`${baseClassName}-${props.layout}`]: true,
[`${baseClassName}-fixed`]: fixed,
const classNames = computed(() => {
return {
[baseClassName]: true,
[`${baseClassName}-${navTheme}`]: true,
[`${baseClassName}-${props.layout}`]: true,
[`${baseClassName}-fixed`]: context.fixSiderbar,
}
});
// call menuHeaderRender
const headerDom = defaultRenderLogoAndTitle(props);
const extraDom = menuExtraRender && menuExtraRender(props);
const defaultMenuDom = (
<BaseMenu
menuData={menuData}
theme={props.theme === 'realDark' ? 'dark' : props.theme}
theme={props.navTheme === 'realDark' ? 'dark' : props.navTheme}
mode="inline"
menuData={context.menuData}
collapsed={props.collapsed}
openKeys={props.openKeys}
selectedKeys={props.selectedKeys}
openKeys={context.openKeys}
selectedKeys={context.selectedKeys}
style={{
width: '100%',
}}
class={`${baseClassName}-menu`}
{...{
'onUpdate:openKeys': ($event: any) => {
onOpenChange && onOpenChange($event);
'onUpdate:openKeys': ($event: string[]) => {
context.onOpenKeys($event);
},
'onUpdate:selectedKeys': ($event: any) => {
onSelect && onSelect($event);
'onUpdate:selectedKeys': ($event: string[]) => {
context.onSelectedKeys($event);
},
}}
/>
......@@ -142,7 +144,7 @@ const SiderMenu: FunctionalComponent<SiderMenuProps> = (props: SiderMenuProps) =
return (
<>
{fixed.value && (
{context.fixSiderbar && (
<div
style={{
width: `${runtimeSideWidth.value}px`,
......@@ -174,7 +176,7 @@ const SiderMenu: FunctionalComponent<SiderMenuProps> = (props: SiderMenuProps) =
<Menu
class={`${baseClassName}-link-menu`}
inlineIndent={16}
theme={theme}
theme={navTheme as 'light' | 'dark'}
selectedKeys={[]}
openKeys={[]}
mode="inline"
......
......@@ -9,6 +9,7 @@ import { GlobalHeaderProps } from '../GlobalHeader';
import { default as ResizeObserver } from 'ant-design-vue/es/vc-resize-observer';
import './index.less';
import { useRouteContext } from "../RouteContext";
export type TopNavHeaderProps = SiderMenuProps & GlobalHeaderProps & PrivateSiderMenuProps & {};
......@@ -52,10 +53,9 @@ export const TopNavHeader: FunctionalComponent<TopNavHeaderProps> = (props, { em
contentWidth,
rightContentRender,
layout,
onOpenChange,
onSelect,
...restProps
} = props;
const context = useRouteContext();
const prefixCls = `${propPrefixCls || 'ant-pro'}-top-nav-header`;
const headerDom = defaultRenderLogoAndTitle(
{ ...props, collapsed: false },
......@@ -79,14 +79,19 @@ export const TopNavHeader: FunctionalComponent<TopNavHeaderProps> = (props, { em
)}
<div style={{ flex: 1 }} class={`${prefixCls}-menu`}>
<BaseMenu
{...restProps}
theme={props.theme === 'realDark' ? 'dark' : props.theme}
mode={props.mode}
collapsed={props.collapsed}
menuData={context.menuData}
openKeys={context.openKeys}
selectedKeys={context.selectedKeys}
class={{ 'top-nav-menu': props.mode === 'horizontal' }}
{...{
'onUpdate:openKeys': ($event: any) => {
onOpenChange && onOpenChange($event);
'onUpdate:openKeys': ($event: string[]) => {
context.onOpenKeys($event);
},
'onUpdate:selectedKeys': ($event: any) => {
onSelect && onSelect($event);
context.onSelectedKeys($event);
},
}}
/>
......
......@@ -52,11 +52,9 @@ export interface MenuDataItem {
/**
* @name 用于标定选中的值,默认是 path
*/
key?: string | symbol;
path: string;
name?: string | symbol;
meta?: MetaRecord;
hidden?: boolean;
/**
* @name 子菜单
*/
......
import { Slots, VNodeChild } from 'vue';
import { MenuDataItem } from '../typings';
export { getComponent } from 'ant-design-vue/es/_util/props-util';
export { default as PropTypes } from 'ant-design-vue/es/_util/vue-types';
export { default as isUrl } from './isUrl';
export { default as isImg } from './isImg';
......@@ -48,6 +49,11 @@ export function clearMenuItem(menusData: MenuDataItem[]): MenuDataItem[] {
.filter(item => item) as MenuDataItem[];
}
export const PropRenderType = {
type: [Function, Boolean],
default: () => undefined,
};
export interface Attrs {
[key: string]: string;
}
......
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