Commit a7380c6b authored by Sendya's avatar Sendya

refactor: route context

parent dfab7a66
.browser-nav {
padding: 2px 6px;
background-color: #ebedf1;
&::before {
content: '';
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
background-color: #fd6458;
box-shadow: 20px 0 0 #ffbf2b, 40px 0 0 #24cc3d;
}
}
import { createApp, defineComponent, ref, h, onMounted } from 'vue';
import { RouterLink } from './mock-router';
import * as Icon from '@ant-design/icons-vue';
import './demoBox.less';
export const DemoBox = defineComponent({
setup(_, { slots }) {
const instance = ref();
const frameRef = ref();
const content = (): void => {
const children = slots?.default();
console.log('frameRef.value', frameRef.value);
const body = frameRef.value.contentDocument.body;
const head = frameRef.value.contentDocument.head;
const el = document.createElement('div');
el.className = 'demoBox'
body.appendChild(el);
// const styleLink = document.createElement('link');
// styleLink.rel = 'stylesheet';
// styleLink.type = 'text/css';
// styleLink.href = './index.css';
head.innerHTML = `
<link href="/node_modules/normalize.css/normalize.css" type="text/css" rel="stylesheet">
<link href="./index.css" type="text/css" rel="stylesheet">
`
const box = createApp({
render() {
return children;
},
}).use(RouterLink);
const filterIcons = ['default', 'createFromIconfontCN', 'getTwoToneColor', 'setTwoToneColor'];
Object.keys(Icon)
.filter(k => !filterIcons.includes(k))
.forEach(k => {
box.component(Icon[k].displayName, Icon[k]);
});
box.mount(el);
instance.value = box;
}
onMounted(() => {
content();
})
return {
frameRef,
content,
}
},
render () {
return (
<div class="browser-mockup with-url">
<div class="browser-nav">
</div>
<iframe ref="frameRef" height="450px" width="100%" style="border: 0;" />
</div>
)
}
});
import 'ant-design-vue/dist/antd.less'; import 'ant-design-vue/dist/antd.less';
import { createApp, defineComponent, watch, ref } from 'vue'; import { createApp, defineComponent, onMounted, watch, ref, reactive } from 'vue';
import { RouterLink } from './mock-router'; import { RouterLink } from './mock-router';
import { Button, Avatar, message } from 'ant-design-vue'; import { Button, Avatar, message } from 'ant-design-vue';
import { default as ProLayout } from '../src/'; import { default as ProLayout } from '../src/';
import { menus } from './menus'; import { menus } from './menus';
import * as Icon from '@ant-design/icons-vue'; import * as Icon from '@ant-design/icons-vue';
import { createRouteContext } from '../src/RouteContext'; import { createRouteContext, RouteContextProps } from '../src/RouteContext';
import { DemoBox } from './demoBox';
const BasicLayout = defineComponent({ const BasicLayout = defineComponent({
name: 'BasicLayout', name: 'BasicLayout',
inheritAttrs: false, inheritAttrs: false,
setup(_, { attrs }) { setup() {
const [ state, RouteContextProvider ] = createRouteContext({ const state = reactive<RouteContextProps>({
collapsed: false, collapsed: false,
openKeys: ['/dashboard', '/form'], openKeys: ['/dashboard'],
onOpenKeys: (keys: string[]) => (state.openKeys = keys), setOpenKeys: (keys: string[]) => (state.openKeys = keys),
selectedKeys: ['/welcome'], selectedKeys: ['/welcome'],
onSelectedKeys: (keys: string[]) => (state.selectedKeys = keys), setSelectedKeys: (keys: string[]) => (state.selectedKeys = keys),
isMobile: false, isMobile: false,
fixSiderbar: false, fixSiderbar: false,
...@@ -28,7 +29,8 @@ const BasicLayout = defineComponent({ ...@@ -28,7 +29,8 @@ const BasicLayout = defineComponent({
hasHeader: true, hasHeader: true,
hasFooterToolbar: false, hasFooterToolbar: false,
setHasFooterToolbar: (has: boolean) => (state.hasFooterToolbar = has), setHasFooterToolbar: (has: boolean) => (state.hasFooterToolbar = has),
}); })
const [ RouteContextProvider ] = createRouteContext();
const cacheOpenKeys = ref<string[]>([]); const cacheOpenKeys = ref<string[]>([]);
watch( watch(
...@@ -44,9 +46,8 @@ const BasicLayout = defineComponent({ ...@@ -44,9 +46,8 @@ const BasicLayout = defineComponent({
); );
return () => ( return () => (
<RouteContextProvider> <RouteContextProvider value={state}>
<ProLayout <ProLayout
{...attrs}
v-model={[state.collapsed, 'collapsed']} v-model={[state.collapsed, 'collapsed']}
title={'Pro Layout'} title={'Pro Layout'}
layout={'side'} layout={'side'}
...@@ -57,7 +58,7 @@ const BasicLayout = defineComponent({ ...@@ -57,7 +58,7 @@ const BasicLayout = defineComponent({
fixedHeader={state.fixedHeader} fixedHeader={state.fixedHeader}
contentWidth={'Fixed'} contentWidth={'Fixed'}
primaryColor={'#1890ff'} primaryColor={'#1890ff'}
contentStyle={{ minHeight: '500px' }} contentStyle={{ minHeight: '200px' }}
siderWidth={state.sideWidth} siderWidth={state.sideWidth}
v-slots={{ v-slots={{
rightContentRender: () => ( rightContentRender: () => (
...@@ -91,7 +92,9 @@ const SimpleDemo = { ...@@ -91,7 +92,9 @@ const SimpleDemo = {
return () => ( return () => (
<div class="components"> <div class="components">
<h2># BasicLayout</h2> <h2># BasicLayout</h2>
<BasicLayout /> <DemoBox>
<BasicLayout />
</DemoBox>
</div> </div>
); );
}, },
......
import { createApp, defineComponent, ref, reactive, toRaw, onMounted } from 'vue'; import { createApp, defineComponent, ref, reactive, toRaw, onMounted } from 'vue';
import { Card, Space, Button } from 'ant-design-vue'; import { Card, Space, Button } from 'ant-design-vue';
import { createRouteContext, useRouteContext } from '../src/RouteContext'; import { createRouteContext, useRouteContext, RouteContextProps } from '../src/RouteContext';
import 'ant-design-vue/dist/antd.less'; import 'ant-design-vue/dist/antd.less';
...@@ -9,14 +9,15 @@ const DemoComponent = { ...@@ -9,14 +9,15 @@ const DemoComponent = {
const state = reactive({ const state = reactive({
name: 'value', name: 'value',
}); });
const context = reactive<RouteContextProps>({
const [ routeContext, RouteContextProvider ] = createRouteContext({ menuData: [],
hasSideMenu: true, selectedKeys: [],
collapsed: true, openKeys: [],
isMobile: false, collapsed: false,
menuData: []
}); });
const [ RouteContextProvider ] = createRouteContext();
return () => ( return () => (
<div class="components"> <div class="components">
<h2># Template</h2> <h2># Template</h2>
...@@ -26,8 +27,8 @@ const DemoComponent = { ...@@ -26,8 +27,8 @@ const DemoComponent = {
type="primary" type="primary"
onClick={() => { onClick={() => {
state.name = new Date().getTime().toString() state.name = new Date().getTime().toString()
routeContext.collapsed = !routeContext.collapsed context.collapsed = !context.collapsed
routeContext.menuData = [ context.menuData = [
{ {
path: `/dashboard/${state.name}`, path: `/dashboard/${state.name}`,
name: `${state.name}`, name: `${state.name}`,
...@@ -42,11 +43,11 @@ const DemoComponent = { ...@@ -42,11 +43,11 @@ const DemoComponent = {
<div style={{ margin: '12px 0' }}> <div style={{ margin: '12px 0' }}>
<p>state.name: { JSON.stringify(state.name) }</p> <p>state.name: { JSON.stringify(state.name) }</p>
routeContext: routeContext:
<pre>{ JSON.stringify(routeContext, null, 4) }</pre> <pre>{ JSON.stringify(context, null, 4) }</pre>
</div> </div>
</Card> </Card>
<div class="demo" style="background: rgb(244,244,244);"> <div class="demo" style="background: rgb(244,244,244);">
<RouteContextProvider> <RouteContextProvider value={context}>
<TestChildComponent /> <TestChildComponent />
</RouteContextProvider> </RouteContextProvider>
</div> </div>
......
...@@ -4,76 +4,94 @@ import 'ant-design-vue/dist/antd.less'; ...@@ -4,76 +4,94 @@ import 'ant-design-vue/dist/antd.less';
import './side-menu.less'; import './side-menu.less';
import { Layout, Input, Space, Switch, message } from 'ant-design-vue'; import { Layout, Input, Space, Switch, message } from 'ant-design-vue';
import { menus } from './menus'; import { menus } from './menus';
import { RouterLink } from './mock-router';
import { default as SiderMenuWrapper } from '../src/SiderMenu'; import { default as SiderMenuWrapper } from '../src/SiderMenu';
import { useMenu } from '../src/hooks/useMenu'; import { createRouteContext, RouteContextProps } from '../src/RouteContext';
import * as Icon from '@ant-design/icons-vue'; import * as Icon from '@ant-design/icons-vue';
import { MenuTheme } from '../src/typings'; import { MenuTheme } from '../src/typings';
const DemoComponent = { const DemoComponent = {
setup() { setup() {
const [menuState] = useMenu({ const state = reactive<RouteContextProps>({
collapsed: false, collapsed: false,
openKeys: [''],
openKeys: ['/dashboard', '/form'],
setOpenKeys: (keys: string[]) => (state.openKeys = keys),
selectedKeys: ['/welcome'], selectedKeys: ['/welcome'],
}); setSelectedKeys: (keys: string[]) => (state.selectedKeys = keys),
const state = reactive({
isMobile: false,
fixSiderbar: false,
fixedHeader: false,
menuData: [...menus],
sideWidth: 208,
hasSideMenu: true,
hasHeader: true,
hasFooterToolbar: false,
setHasFooterToolbar: (has: boolean) => (state.hasFooterToolbar = has),
})
const [ RouteContextProvider ] = createRouteContext();
const myState = reactive({
theme: 'light', theme: 'light',
}); });
const handleCollapse = (collapsed: boolean) => { const handleCollapse = (collapsed: boolean) => {
menuState.collapsed = collapsed; state.collapsed = collapsed;
} }
return () => ( return () => (
<div class="components"> <div class="components">
<h2># SideMenu</h2> <h2># SideMenu</h2>
<div> <div style="margin: 16px;">
<Space> <Space>
<Switch checked-children="dark" un-checked-children="light" checked={state.theme === 'dark'} onChange={() => { (state.theme = state.theme === 'dark' ? 'light' : 'dark')}} /> <Switch checked-children="dark" un-checked-children="light" checked={state.theme === 'dark'} onChange={() => { (myState.theme = myState.theme === 'dark' ? 'light' : 'dark')}} />
</Space> </Space>
</div> </div>
<div class="demo" style="background: rgb(244,244,244); min-height: 400px;"> <div class="demo" style="background: rgb(244,244,244); min-height: 400px;">
<div class="container side-menu-demo"> <div class="container side-menu-demo">
<Layout class="ant-pro-basicLayout"> <RouteContextProvider value={state}>
<SiderMenuWrapper <Layout class="ant-pro-basicLayout">
title={'Pro Layout'} <SiderMenuWrapper
layout={'side'} title={'Pro Layout'}
theme={state.theme as MenuTheme} layout={'side'}
isMobile={false} navTheme={myState.theme as MenuTheme}
collapsed={menuState.collapsed} isMobile={false}
menuData={menus} collapsed={state.collapsed}
// openKeys={menuState.openKeys} // openKeys={menuState.openKeys}
// selectedKeys={menuState.selectedKeys} // selectedKeys={menuState.selectedKeys}
onCollapse={handleCollapse} onCollapse={handleCollapse}
matchMenuKeys={[]} matchMenuKeys={[]}
contentWidth={'Fixed'} contentWidth={'Fixed'}
primaryColor={'#1890ff'} primaryColor={'#1890ff'}
siderWidth={208} siderWidth={208}
menuExtraRender={(props) => !props.collapsed ? ( menuExtraRender={(props) => !props.collapsed ? (
<div> <div>
<Input.Search placeholder="Search.." style={{ width: '100%' }} onSearch={(value: string) => { <Input.Search placeholder="Search.." style={{ width: '100%' }} onSearch={(value: string) => {
message.info(`Search click: ${value}`) message.info(`Search click: ${value}`)
}} /> }} />
</div> </div>
) : null} ) : null}
// menuFooterRender={(props) => props.collapsed ? undefined : ( // menuFooterRender={(props) => props.collapsed ? undefined : (
// <div style="color: #fff; padding: 8px 16px; overflow: hidden;"> // <div style="color: #fff; padding: 8px 16px; overflow: hidden;">
// <span>自定义页脚</span> // <span>自定义页脚</span>
// </div> // </div>
// )} // )}
/> />
<Layout> <Layout>
<Layout.Header style="background: #fff; padding: 0; height: 48px; line-height: 48px;"></Layout.Header> <Layout.Header style="background: #fff; padding: 0; height: 48px; line-height: 48px;"></Layout.Header>
<Layout.Content <Layout.Content
style={{ style={{
margin: '24px 16px', margin: '24px 16px',
padding: '24px', padding: '24px',
background: '#fff', background: '#fff',
minHeight: '280px', minHeight: '280px',
}} }}
> >
<div>Context</div> <div>Context</div>
</Layout.Content> </Layout.Content>
</Layout>
</Layout> </Layout>
</Layout> </RouteContextProvider>
</div> </div>
</div> </div>
</div> </div>
...@@ -90,4 +108,4 @@ Object.keys(Icon) ...@@ -90,4 +108,4 @@ Object.keys(Icon)
app.component(Icon[k].displayName, Icon[k]); app.component(Icon[k].displayName, Icon[k]);
}); });
app.mount('#__vue-content>div'); app.use(RouterLink).mount('#__vue-content>div');
...@@ -2,11 +2,13 @@ import { InjectionKey, VNodeChild } from 'vue'; ...@@ -2,11 +2,13 @@ import { InjectionKey, VNodeChild } from 'vue';
import { createContext, useContext } from './hooks/context'; import { createContext, useContext } from './hooks/context';
import { MenuDataItem } from './typings'; import { MenuDataItem } from './typings';
import { PureSettings } from './defaultSettings'; 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[];
...@@ -18,10 +20,10 @@ export interface BreadcrumbProps { ...@@ -18,10 +20,10 @@ export interface BreadcrumbProps {
export type BreadcrumbListReturn = Pick<BreadcrumbProps, Extract<keyof BreadcrumbProps, 'routes' | 'itemRender'>>; export type BreadcrumbListReturn = Pick<BreadcrumbProps, Extract<keyof BreadcrumbProps, 'routes' | 'itemRender'>>;
export interface MenuState { export interface MenuState {
selectedKeys?: string[]; selectedKeys: string[];
openKeys?: string[]; openKeys: string[];
onSelectedKeys: (key: string[]) => void; setSelectedKeys?: (key: string[]) => void;
onOpenKeys: (key: string[]) => void; setOpenKeys?: (key: string[]) => void;
} }
export interface RouteContextProps extends Partial<PureSettings>, MenuState { export interface RouteContextProps extends Partial<PureSettings>, MenuState {
...@@ -42,8 +44,8 @@ export interface RouteContextProps extends Partial<PureSettings>, MenuState { ...@@ -42,8 +44,8 @@ export interface RouteContextProps extends Partial<PureSettings>, MenuState {
const routeContextInjectKey: InjectionKey<RouteContextProps> = Symbol(); const routeContextInjectKey: InjectionKey<RouteContextProps> = Symbol();
export const createRouteContext = (context: RouteContextProps) => export const createRouteContext = () =>
createContext<RouteContextProps>(context, routeContextInjectKey); createContext<RouteContextProps>(routeContextInjectKey);
export const useRouteContext = () => export const useRouteContext = () =>
useContext<RouteContextProps>(routeContextInjectKey); useContext<RouteContextProps>(routeContextInjectKey);
...@@ -167,10 +167,8 @@ export default defineComponent({ ...@@ -167,10 +167,8 @@ export default defineComponent({
emits: ['update:openKeys', 'update:selectedKeys'], emits: ['update:openKeys', 'update:selectedKeys'],
setup(props, { emit }) { setup(props, { emit }) {
const { mode, i18n } = toRefs(props); const { mode, i18n } = toRefs(props);
const state = useMenuState();
const isInline = computed(() => mode.value === 'inline'); const isInline = computed(() => mode.value === 'inline');
const handleOpenChange: OpenEventHandler = (openKeys: string[]): void => { const handleOpenChange: OpenEventHandler = (openKeys: string[]): void => {
state?.setOpenKeys(openKeys);
emit('update:openKeys', openKeys); emit('update:openKeys', openKeys);
}; };
const handleSelect = (params: { const handleSelect = (params: {
...@@ -180,7 +178,6 @@ export default defineComponent({ ...@@ -180,7 +178,6 @@ export default defineComponent({
domEvent: MouseEvent; domEvent: MouseEvent;
selectedKeys: string[]; selectedKeys: string[];
}): void => { }): void => {
state?.setSelectedKeys(params.selectedKeys);
emit('update:selectedKeys', params.selectedKeys); emit('update:selectedKeys', params.selectedKeys);
}; };
return () => ( return () => (
...@@ -190,8 +187,8 @@ export default defineComponent({ ...@@ -190,8 +187,8 @@ export default defineComponent({
inlineIndent={16} inlineIndent={16}
mode={props.mode} mode={props.mode}
theme={props.theme as 'dark' | 'light'} theme={props.theme as 'dark' | 'light'}
openKeys={props.openKeys || state?.openKeys.value || []} openKeys={props.openKeys || []}
selectedKeys={props.selectedKeys || state?.selectedKeys.value || []} selectedKeys={props.selectedKeys || []}
onOpenChange={handleOpenChange} onOpenChange={handleOpenChange}
onSelect={handleSelect} onSelect={handleSelect}
> >
......
...@@ -133,10 +133,10 @@ const SiderMenu: FunctionalComponent<SiderMenuProps> = (props: SiderMenuProps) = ...@@ -133,10 +133,10 @@ const SiderMenu: FunctionalComponent<SiderMenuProps> = (props: SiderMenuProps) =
class={`${baseClassName}-menu`} class={`${baseClassName}-menu`}
{...{ {...{
'onUpdate:openKeys': ($event: string[]) => { 'onUpdate:openKeys': ($event: string[]) => {
context.onOpenKeys($event); context?.setOpenKeys($event);
}, },
'onUpdate:selectedKeys': ($event: string[]) => { 'onUpdate:selectedKeys': ($event: string[]) => {
context.onSelectedKeys($event); context?.setSelectedKeys($event);
}, },
}} }}
/> />
......
...@@ -3,47 +3,50 @@ import { ...@@ -3,47 +3,50 @@ import {
InjectionKey, InjectionKey,
provide, provide,
inject, inject,
reactive,
readonly, readonly,
SetupContext, SetupContext,
UnwrapRef,
VNode, VNode,
PropType,
DefineComponent, DefineComponent,
toRaw,
} from 'vue'; } from 'vue';
export type ContextType<T> = any; export type ContextType<T> = any;
export type CreateContext<T> = [ export type CreateContext<T> = [
UnwrapRef<T> | T, // UnwrapRef<T> | T,
DefineComponent<{}, () => VNode | VNode[] | undefined, any>, DefineComponent<{}, () => VNode | VNode[] | undefined, any>,
]; ];
export const createContext = <T>( export const createContext = <T>(
context: ContextType<T>, // context: ContextType<T>,
contextInjectKey: InjectionKey<ContextType<T>> = Symbol(), contextInjectKey: InjectionKey<ContextType<T>> = Symbol(),
): CreateContext<T> => { ): CreateContext<T> => {
const state = reactive<ContextType<T>>({ // const state = reactive<ContextType<T>>({
...toRaw(context), // ...toRaw(context),
}); // });
const ContextProvider = defineComponent({ const ContextProvider = defineComponent({
name: 'ContextProvider', name: 'ContextProvider',
inheritAttrs: false, props: {
setup(props, { slots }: SetupContext) { value: {
provide(contextInjectKey, readonly(state)); type: Object as PropType<ContextType<T>>,
required: true,
},
},
setup(props: { value: ContextType<T> }, { slots }: SetupContext) {
provide(contextInjectKey, readonly(props.value));
return () => slots.default?.(); return () => slots.default?.();
}, },
}); });
return [state, ContextProvider]; return [ContextProvider];
}; };
export const useContext = <T>( export const useContext = <T>(
contextInjectKey: InjectionKey<ContextType<T>> = Symbol(), contextInjectKey: InjectionKey<ContextType<T>> = Symbol(),
defaultValue?: ContextType<T>, defaultValue?: ContextType<T>,
): T => { ): T => {
return readonly(inject(contextInjectKey, defaultValue || ({} as T))); return inject(contextInjectKey, defaultValue || ({} as T));
}; };
// :: examples :: // :: examples ::
......
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