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 { createApp, defineComponent, watch, ref } from 'vue';
import { createApp, defineComponent, onMounted, watch, ref, reactive } from 'vue';
import { RouterLink } from './mock-router';
import { Button, Avatar, message } from 'ant-design-vue';
import { default as ProLayout } from '../src/';
import { menus } from './menus';
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({
name: 'BasicLayout',
inheritAttrs: false,
setup(_, { attrs }) {
const [ state, RouteContextProvider ] = createRouteContext({
setup() {
const state = reactive<RouteContextProps>({
collapsed: false,
openKeys: ['/dashboard', '/form'],
onOpenKeys: (keys: string[]) => (state.openKeys = keys),
openKeys: ['/dashboard'],
setOpenKeys: (keys: string[]) => (state.openKeys = keys),
selectedKeys: ['/welcome'],
onSelectedKeys: (keys: string[]) => (state.selectedKeys = keys),
setSelectedKeys: (keys: string[]) => (state.selectedKeys = keys),
isMobile: false,
fixSiderbar: false,
......@@ -28,7 +29,8 @@ const BasicLayout = defineComponent({
hasHeader: true,
hasFooterToolbar: false,
setHasFooterToolbar: (has: boolean) => (state.hasFooterToolbar = has),
});
})
const [ RouteContextProvider ] = createRouteContext();
const cacheOpenKeys = ref<string[]>([]);
watch(
......@@ -44,9 +46,8 @@ const BasicLayout = defineComponent({
);
return () => (
<RouteContextProvider>
<RouteContextProvider value={state}>
<ProLayout
{...attrs}
v-model={[state.collapsed, 'collapsed']}
title={'Pro Layout'}
layout={'side'}
......@@ -57,7 +58,7 @@ const BasicLayout = defineComponent({
fixedHeader={state.fixedHeader}
contentWidth={'Fixed'}
primaryColor={'#1890ff'}
contentStyle={{ minHeight: '500px' }}
contentStyle={{ minHeight: '200px' }}
siderWidth={state.sideWidth}
v-slots={{
rightContentRender: () => (
......@@ -91,7 +92,9 @@ const SimpleDemo = {
return () => (
<div class="components">
<h2># BasicLayout</h2>
<BasicLayout />
<DemoBox>
<BasicLayout />
</DemoBox>
</div>
);
},
......
import { createApp, defineComponent, ref, reactive, toRaw, onMounted } from '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';
......@@ -9,14 +9,15 @@ const DemoComponent = {
const state = reactive({
name: 'value',
});
const [ routeContext, RouteContextProvider ] = createRouteContext({
hasSideMenu: true,
collapsed: true,
isMobile: false,
menuData: []
const context = reactive<RouteContextProps>({
menuData: [],
selectedKeys: [],
openKeys: [],
collapsed: false,
});
const [ RouteContextProvider ] = createRouteContext();
return () => (
<div class="components">
<h2># Template</h2>
......@@ -26,8 +27,8 @@ const DemoComponent = {
type="primary"
onClick={() => {
state.name = new Date().getTime().toString()
routeContext.collapsed = !routeContext.collapsed
routeContext.menuData = [
context.collapsed = !context.collapsed
context.menuData = [
{
path: `/dashboard/${state.name}`,
name: `${state.name}`,
......@@ -42,11 +43,11 @@ const DemoComponent = {
<div style={{ margin: '12px 0' }}>
<p>state.name: { JSON.stringify(state.name) }</p>
routeContext:
<pre>{ JSON.stringify(routeContext, null, 4) }</pre>
<pre>{ JSON.stringify(context, null, 4) }</pre>
</div>
</Card>
<div class="demo" style="background: rgb(244,244,244);">
<RouteContextProvider>
<RouteContextProvider value={context}>
<TestChildComponent />
</RouteContextProvider>
</div>
......
......@@ -4,76 +4,94 @@ import 'ant-design-vue/dist/antd.less';
import './side-menu.less';
import { Layout, Input, Space, Switch, message } from 'ant-design-vue';
import { menus } from './menus';
import { RouterLink } from './mock-router';
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 { MenuTheme } from '../src/typings';
const DemoComponent = {
setup() {
const [menuState] = useMenu({
const state = reactive<RouteContextProps>({
collapsed: false,
openKeys: [''],
openKeys: ['/dashboard', '/form'],
setOpenKeys: (keys: string[]) => (state.openKeys = keys),
selectedKeys: ['/welcome'],
});
const state = reactive({
setSelectedKeys: (keys: string[]) => (state.selectedKeys = keys),
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',
});
const handleCollapse = (collapsed: boolean) => {
menuState.collapsed = collapsed;
state.collapsed = collapsed;
}
return () => (
<div class="components">
<h2># SideMenu</h2>
<div>
<div style="margin: 16px;">
<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>
</div>
<div class="demo" style="background: rgb(244,244,244); min-height: 400px;">
<div class="container side-menu-demo">
<Layout class="ant-pro-basicLayout">
<SiderMenuWrapper
title={'Pro Layout'}
layout={'side'}
theme={state.theme as MenuTheme}
isMobile={false}
collapsed={menuState.collapsed}
menuData={menus}
// openKeys={menuState.openKeys}
// selectedKeys={menuState.selectedKeys}
onCollapse={handleCollapse}
matchMenuKeys={[]}
contentWidth={'Fixed'}
primaryColor={'#1890ff'}
siderWidth={208}
menuExtraRender={(props) => !props.collapsed ? (
<div>
<Input.Search placeholder="Search.." style={{ width: '100%' }} onSearch={(value: string) => {
message.info(`Search click: ${value}`)
}} />
</div>
) : null}
// menuFooterRender={(props) => props.collapsed ? undefined : (
// <div style="color: #fff; padding: 8px 16px; overflow: hidden;">
// <span>自定义页脚</span>
// </div>
// )}
/>
<Layout>
<Layout.Header style="background: #fff; padding: 0; height: 48px; line-height: 48px;"></Layout.Header>
<Layout.Content
style={{
margin: '24px 16px',
padding: '24px',
background: '#fff',
minHeight: '280px',
}}
>
<div>Context</div>
</Layout.Content>
<RouteContextProvider value={state}>
<Layout class="ant-pro-basicLayout">
<SiderMenuWrapper
title={'Pro Layout'}
layout={'side'}
navTheme={myState.theme as MenuTheme}
isMobile={false}
collapsed={state.collapsed}
// openKeys={menuState.openKeys}
// selectedKeys={menuState.selectedKeys}
onCollapse={handleCollapse}
matchMenuKeys={[]}
contentWidth={'Fixed'}
primaryColor={'#1890ff'}
siderWidth={208}
menuExtraRender={(props) => !props.collapsed ? (
<div>
<Input.Search placeholder="Search.." style={{ width: '100%' }} onSearch={(value: string) => {
message.info(`Search click: ${value}`)
}} />
</div>
) : null}
// menuFooterRender={(props) => props.collapsed ? undefined : (
// <div style="color: #fff; padding: 8px 16px; overflow: hidden;">
// <span>自定义页脚</span>
// </div>
// )}
/>
<Layout>
<Layout.Header style="background: #fff; padding: 0; height: 48px; line-height: 48px;"></Layout.Header>
<Layout.Content
style={{
margin: '24px 16px',
padding: '24px',
background: '#fff',
minHeight: '280px',
}}
>
<div>Context</div>
</Layout.Content>
</Layout>
</Layout>
</Layout>
</RouteContextProvider>
</div>
</div>
</div>
......@@ -90,4 +108,4 @@ Object.keys(Icon)
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';
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[];
......@@ -18,10 +20,10 @@ export interface BreadcrumbProps {
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;
selectedKeys: string[];
openKeys: string[];
setSelectedKeys?: (key: string[]) => void;
setOpenKeys?: (key: string[]) => void;
}
export interface RouteContextProps extends Partial<PureSettings>, MenuState {
......@@ -42,8 +44,8 @@ export interface RouteContextProps extends Partial<PureSettings>, MenuState {
const routeContextInjectKey: InjectionKey<RouteContextProps> = Symbol();
export const createRouteContext = (context: RouteContextProps) =>
createContext<RouteContextProps>(context, routeContextInjectKey);
export const createRouteContext = () =>
createContext<RouteContextProps>(routeContextInjectKey);
export const useRouteContext = () =>
useContext<RouteContextProps>(routeContextInjectKey);
......@@ -167,10 +167,8 @@ export default defineComponent({
emits: ['update:openKeys', 'update:selectedKeys'],
setup(props, { emit }) {
const { mode, i18n } = toRefs(props);
const state = useMenuState();
const isInline = computed(() => mode.value === 'inline');
const handleOpenChange: OpenEventHandler = (openKeys: string[]): void => {
state?.setOpenKeys(openKeys);
emit('update:openKeys', openKeys);
};
const handleSelect = (params: {
......@@ -180,7 +178,6 @@ export default defineComponent({
domEvent: MouseEvent;
selectedKeys: string[];
}): void => {
state?.setSelectedKeys(params.selectedKeys);
emit('update:selectedKeys', params.selectedKeys);
};
return () => (
......@@ -190,8 +187,8 @@ export default defineComponent({
inlineIndent={16}
mode={props.mode}
theme={props.theme as 'dark' | 'light'}
openKeys={props.openKeys || state?.openKeys.value || []}
selectedKeys={props.selectedKeys || state?.selectedKeys.value || []}
openKeys={props.openKeys || []}
selectedKeys={props.selectedKeys || []}
onOpenChange={handleOpenChange}
onSelect={handleSelect}
>
......
......@@ -133,10 +133,10 @@ const SiderMenu: FunctionalComponent<SiderMenuProps> = (props: SiderMenuProps) =
class={`${baseClassName}-menu`}
{...{
'onUpdate:openKeys': ($event: string[]) => {
context.onOpenKeys($event);
context?.setOpenKeys($event);
},
'onUpdate:selectedKeys': ($event: string[]) => {
context.onSelectedKeys($event);
context?.setSelectedKeys($event);
},
}}
/>
......
......@@ -3,47 +3,50 @@ import {
InjectionKey,
provide,
inject,
reactive,
readonly,
SetupContext,
UnwrapRef,
VNode,
PropType,
DefineComponent,
toRaw,
} from 'vue';
export type ContextType<T> = any;
export type CreateContext<T> = [
UnwrapRef<T> | T,
// UnwrapRef<T> | T,
DefineComponent<{}, () => VNode | VNode[] | undefined, any>,
];
export const createContext = <T>(
context: ContextType<T>,
// context: ContextType<T>,
contextInjectKey: InjectionKey<ContextType<T>> = Symbol(),
): CreateContext<T> => {
const state = reactive<ContextType<T>>({
...toRaw(context),
});
// const state = reactive<ContextType<T>>({
// ...toRaw(context),
// });
const ContextProvider = defineComponent({
name: 'ContextProvider',
inheritAttrs: false,
setup(props, { slots }: SetupContext) {
provide(contextInjectKey, readonly(state));
props: {
value: {
type: Object as PropType<ContextType<T>>,
required: true,
},
},
setup(props: { value: ContextType<T> }, { slots }: SetupContext) {
provide(contextInjectKey, readonly(props.value));
return () => slots.default?.();
},
});
return [state, ContextProvider];
return [ContextProvider];
};
export const useContext = <T>(
contextInjectKey: InjectionKey<ContextType<T>> = Symbol(),
defaultValue?: ContextType<T>,
): T => {
return readonly(inject(contextInjectKey, defaultValue || ({} as T)));
return inject(contextInjectKey, defaultValue || ({} as T));
};
// :: 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