-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(layout): ProHelp support navigationSwitch
- Loading branch information
1 parent
b445d0b
commit 2406457
Showing
10 changed files
with
783 additions
and
672 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { Spin } from 'antd'; | ||
import { useContext, useState, useEffect } from 'react'; | ||
import { ProHelpDataSource, ProHelpProvide, ProHelpDataSourceChildren } from './HelpProvide'; | ||
import { RenderContentPanel } from './RenderContentPanel'; | ||
|
||
/** | ||
* 异步加载内容的面板组件 | ||
* @param item 指向当前面板的 ProHelpDataSource | ||
*/ | ||
export const AsyncContentPanel: React.FC<{ | ||
item: ProHelpDataSource<any>['children'][number]; | ||
onInit?: (ref: HTMLDivElement) => void; | ||
}> = ({ item, onInit }) => { | ||
const { onLoadContext } = useContext(ProHelpProvide); // 获取上下文中的 onLoadContext | ||
const [loading, setLoading] = useState(false); // 加载状态 | ||
const [content, setContent] = useState<ProHelpDataSourceChildren<any>[]>(); // 内容数据 | ||
|
||
useEffect(() => { | ||
if (!item.key) return; // 如果没有key则返回 | ||
setLoading(true); // 开始加载 | ||
onLoadContext?.(item.key, item).then((res) => { | ||
// 调用加载方法 | ||
setLoading(false); // 加载完成 | ||
setContent(res); // 设置内容数据 | ||
}); | ||
}, [item.key]); | ||
|
||
// 如果没有key,则返回null | ||
if (!item.key) return null; | ||
|
||
// 如果正在加载并且有key,则显示加载中的状态 | ||
if (loading && item.key) { | ||
return ( | ||
<div | ||
key={item.key} | ||
style={{ | ||
display: 'flex', | ||
justifyContent: 'center', | ||
width: '100%', | ||
boxSizing: 'border-box', | ||
padding: 24, | ||
}} | ||
> | ||
<Spin /> | ||
</div> | ||
); | ||
} | ||
|
||
// 加载完成后,渲染内容面板 | ||
return ( | ||
<RenderContentPanel | ||
onInit={(ref) => { | ||
onInit?.(ref); | ||
}} | ||
dataSourceChildren={content!} | ||
/> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
150 changes: 150 additions & 0 deletions
150
packages/layout/src/components/Help/ProHelpContentPanel.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
import { useEffect, useRef } from 'react'; | ||
import React, { useContext, useMemo } from 'react'; | ||
import type { ProHelpDataSource } from './HelpProvide'; | ||
import { ProHelpProvide } from './HelpProvide'; | ||
import { useDebounceFn } from '@ant-design/pro-utils'; | ||
import { RenderContentPanel } from './RenderContentPanel'; | ||
import { AsyncContentPanel } from './AsyncContentPanel'; | ||
|
||
export type ProHelpContentPanelProps = { | ||
/** | ||
* 控制当前选中的帮助文档 | ||
*/ | ||
selectedKey: React.Key; | ||
className?: string; | ||
parentItem?: ProHelpDataSource<any>; | ||
|
||
onScroll?: (key?: string) => void; | ||
}; | ||
|
||
/** | ||
* 控制具体的帮助文档显示组件 | ||
* selectedKey 来展示对应的内容。它会根据不同的item.valueType值来展示不同的内容,包括标题、图片、超链接等。 | ||
* @param ProHelpContentPanelProps | ||
* @returns | ||
*/ | ||
export const ProHelpContentPanel: React.FC<ProHelpContentPanelProps> = ({ | ||
className, | ||
parentItem, | ||
selectedKey, | ||
onScroll, | ||
}) => { | ||
const { dataSource } = useContext(ProHelpProvide); | ||
|
||
// 记录每个面板的滚动高度 | ||
const scrollHeightMap = useRef<Map<React.Key, HTMLDivElement>>(new Map()); | ||
|
||
const divRef = useRef<HTMLDivElement>(null); | ||
|
||
useEffect(() => { | ||
if (!selectedKey || !parentItem?.infiniteScrollFull) return; | ||
const div = scrollHeightMap.current.get(selectedKey); | ||
if (div?.offsetTop && divRef.current) { | ||
if (Math.abs(divRef.current!.scrollTop - div?.offsetTop + 40) > div?.clientHeight) { | ||
divRef.current!.scrollTop = div?.offsetTop - 40; | ||
} | ||
} | ||
}, [selectedKey]); | ||
|
||
/** | ||
* debounce(防抖)处理滚动事件,并根据滚动位置来实现找到当前列表的 key | ||
*/ | ||
const onScrollEvent = useDebounceFn(async (e: Event) => { | ||
const dom = e?.target as HTMLDivElement; | ||
// 根据滚动位置来找到当前列表的 key | ||
const list = Array.from(scrollHeightMap.current.entries()).find(([, value]) => { | ||
if (dom?.scrollTop < value.offsetTop) { | ||
return true; | ||
} | ||
return false; | ||
}); | ||
|
||
if (!list) { | ||
return; | ||
} | ||
// 如果获取的 key 和当前 key 不同丢弃掉 | ||
if (list.at(0) !== selectedKey) { | ||
onScroll?.(list.at(0) as string | undefined); | ||
} | ||
}, 200); | ||
|
||
/** | ||
* 当 parentItem 组件中的 infiniteScrollFull 属性变化时 | ||
* 如果该属性为真值,则开始监听滚动事件; | ||
* 如果为假值,则停止监听滚动事件并取消防抖处理。 | ||
* 在监听滚动事件时,可以实现分页(瀑布流)效果。同时,该代码还会根据 selectedKey 的变化来触发跳转 | ||
*/ | ||
useEffect(() => { | ||
if (!parentItem?.infiniteScrollFull) return; | ||
onScrollEvent.cancel(); | ||
divRef.current?.addEventListener('scroll', onScrollEvent.run, false); | ||
return () => { | ||
onScrollEvent.cancel(); | ||
divRef.current?.removeEventListener('scroll', onScrollEvent.run, false); | ||
}; | ||
}, [parentItem?.infiniteScrollFull, selectedKey]); | ||
|
||
/** | ||
* 生成一个 Map 能根据 key 找到所有的 index | ||
*/ | ||
const dataSourceMap = useMemo(() => { | ||
const map = new Map< | ||
React.Key, | ||
ProHelpDataSource<any>['children'][number] & { | ||
parentKey?: React.Key; | ||
} | ||
>(); | ||
dataSource.forEach((page) => { | ||
page.children.forEach((item) => { | ||
map.set(item.key || item.title, { ...item, parentKey: page.key }); | ||
}); | ||
}); | ||
return map; | ||
}, [dataSource]); | ||
|
||
const renderItem = (item: ProHelpDataSource<any>['children'][number]) => { | ||
if (item?.asyncLoad) { | ||
return ( | ||
<div className={className} id={item.title}> | ||
<AsyncContentPanel | ||
key={item?.key} | ||
item={item!} | ||
onInit={(ref) => { | ||
if (!scrollHeightMap.current) return; | ||
scrollHeightMap.current.set(item.key, ref); | ||
}} | ||
/> | ||
</div> | ||
); | ||
} | ||
|
||
return ( | ||
<div className={className} id={item.title}> | ||
<RenderContentPanel | ||
onInit={(ref) => { | ||
if (!scrollHeightMap.current) return; | ||
scrollHeightMap.current.set(item.key, ref); | ||
}} | ||
dataSourceChildren={item?.children || []} | ||
/> | ||
</div> | ||
); | ||
}; | ||
|
||
if (parentItem && parentItem.infiniteScrollFull) { | ||
return ( | ||
<div | ||
ref={divRef} | ||
className={`${className}-infinite-scroll`} | ||
style={{ | ||
overflow: 'auto', | ||
}} | ||
> | ||
{parentItem.children?.map((item) => { | ||
return <React.Fragment key={item.key}>{renderItem(item)}</React.Fragment>; | ||
}) || null} | ||
</div> | ||
); | ||
} | ||
return renderItem(dataSourceMap.get(selectedKey!)!); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { Drawer, DrawerProps } from 'antd'; | ||
import useMergedState from 'rc-util/es/hooks/useMergedState'; | ||
import { ProHelpPanelProps, ProHelpPanel } from './ProHelpPanel'; | ||
|
||
export type ProHelpDrawerProps = { | ||
/** | ||
* Ant Design Drawer 组件的 Props,可以传递一些选项,如位置、大小、关闭方式等等。 | ||
*/ | ||
drawerProps: DrawerProps; | ||
} & Omit<ProHelpPanelProps, 'onClose'>; | ||
|
||
/** | ||
* 渲染一个抽屉,其中显示了一个 ProHelpPanel。 | ||
* @param drawerProps 要传递给 Drawer 组件的属性。 | ||
* @param props 要传递给 ProHelpPanel 组件的属性。 | ||
*/ | ||
export const ProHelpDrawer: React.FC<ProHelpDrawerProps> = ({ drawerProps, ...props }) => { | ||
const [drawerOpen, setDrawerOpen] = useMergedState<boolean>(false, { | ||
value: drawerProps.open, | ||
onChange: drawerProps.afterOpenChange, | ||
}); | ||
return ( | ||
<Drawer | ||
width={720} | ||
closeIcon={null} | ||
headerStyle={{ display: 'none' }} | ||
bodyStyle={{ padding: 0 }} | ||
maskClosable | ||
{...drawerProps} | ||
open={drawerOpen} | ||
onClose={() => setDrawerOpen(false)} | ||
afterOpenChange={(open) => { | ||
setDrawerOpen(open); | ||
}} | ||
> | ||
<ProHelpPanel {...props} onClose={() => setDrawerOpen(false)} bordered={false} /> | ||
</Drawer> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { ModalProps, Modal } from 'antd'; | ||
import useMergedState from 'rc-util/es/hooks/useMergedState'; | ||
import { ProHelpPanelProps, ProHelpPanel } from './ProHelpPanel'; | ||
export type ProHelpModalProps = { | ||
/** | ||
* Ant Design Modal 组件的 props,可以传递一些选项,如位置、大小、关闭方式等等。 | ||
*/ | ||
modalProps?: ModalProps; | ||
} & Omit<ProHelpPanelProps, 'onClose'>; | ||
|
||
/** | ||
* 渲染一个模态对话框,其中显示了一个 ProHelpPanel。 | ||
* @param modalProps 要传递给 Modal 组件的属性。 | ||
* @param props 要传递给 ProHelpPanel 组件的属性。 | ||
*/ | ||
export const ProHelpModal: React.FC<ProHelpModalProps> = ({ modalProps, ...props }) => { | ||
const [modalOpen, setModalOpen] = useMergedState<boolean>(false, { | ||
value: modalProps?.open, | ||
onChange: modalProps?.afterClose, | ||
}); | ||
return ( | ||
<Modal | ||
onCancel={() => { | ||
setModalOpen(false); | ||
}} | ||
bodyStyle={{ | ||
margin: -24, | ||
}} | ||
centered | ||
closable={false} | ||
footer={null} | ||
width={720} | ||
open={modalOpen} | ||
maskClosable | ||
{...modalProps} | ||
> | ||
<ProHelpPanel height={648} {...props} onClose={() => setModalOpen(false)} /> | ||
</Modal> | ||
); | ||
}; |
Oops, something went wrong.