Match-id-d9d1111f6e1761d1de56351a998c5c7fff5cb483
This commit is contained in:
parent
f5600dd58c
commit
d490707e78
|
@ -0,0 +1,11 @@
|
|||
.container {
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.item {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
|
||||
import { useState, useRef, useEffect } from 'horizon';
|
||||
import styles from './VList.less';
|
||||
|
||||
interface IProps<T extends { id: string }> {
|
||||
data: T[],
|
||||
width: number, // 暂时未用到,当需要支持横向滚动时使用
|
||||
height: number, // VList 的高度
|
||||
children: any, // horizon 组件,组件类型是 T
|
||||
itemHeight: number,
|
||||
scrollIndex?: number,
|
||||
onRendered:(renderInfo: renderInfoType) => void;
|
||||
filter?(data: T): boolean, // false 表示该行不显示
|
||||
}
|
||||
|
||||
const defaultRenderInfo = {
|
||||
visibleItems: ([] as string[])
|
||||
};
|
||||
|
||||
export type renderInfoType = typeof defaultRenderInfo;
|
||||
|
||||
export function VList<T extends { id: string }>(props: IProps<T>) {
|
||||
const {
|
||||
data,
|
||||
height,
|
||||
children,
|
||||
itemHeight,
|
||||
scrollIndex = 0,
|
||||
filter,
|
||||
onRendered,
|
||||
} = props;
|
||||
const [scrollTop, setScrollTop] = useState(scrollIndex * itemHeight);
|
||||
const renderInfo = useRef({visibleItems: []});
|
||||
useEffect(() => {
|
||||
onRendered(renderInfo.current);
|
||||
});
|
||||
|
||||
const handleScroll = (event: any) => {
|
||||
const scrollTop = event.target.scrollTop;
|
||||
setScrollTop(scrollTop);
|
||||
};
|
||||
const showList: any[] = [];
|
||||
let totalHeight = 0;
|
||||
// 顶部冗余
|
||||
const startShowTopValue = Math.max(scrollTop - itemHeight * 4, 0);
|
||||
// 底部冗余
|
||||
const showNum = Math.floor(height / itemHeight) + 4;
|
||||
// 如果最后一个显示不全,不统计在显示 ids 内
|
||||
const maxTop = scrollTop + height - itemHeight;
|
||||
// 清空记录的上次渲染的数据
|
||||
renderInfo.current.visibleItems.length = 0;
|
||||
data.forEach((item, i) => {
|
||||
if (filter && !filter(item)) {
|
||||
return;
|
||||
}
|
||||
if (totalHeight >= startShowTopValue && showList.length <= showNum) {
|
||||
showList.push(
|
||||
<div
|
||||
key={String(item.id)}
|
||||
className={styles.item}
|
||||
style={{ transform: `translateY(${totalHeight}px)` }} >
|
||||
{children(i, item)}
|
||||
</div>
|
||||
);
|
||||
if (totalHeight >= scrollTop && totalHeight < maxTop) {
|
||||
renderInfo.current.visibleItems.push(item);
|
||||
}
|
||||
}
|
||||
totalHeight += itemHeight;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.container} onScroll={handleScroll}>
|
||||
{showList}
|
||||
<div style={{ marginTop: totalHeight }} />
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,10 +1,7 @@
|
|||
@import 'assets.less';
|
||||
|
||||
.treeContainer {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
.treeItem {
|
||||
width: 100%;
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { useState } from 'horizon';
|
||||
import { useState, useEffect } from 'horizon';
|
||||
import styles from './VTree.less';
|
||||
import Arrow from '../svgs/Arrow';
|
||||
import Triangle from '../svgs/Triangle';
|
||||
import { createRegExp } from './../utils';
|
||||
import { SizeObserver } from './SizeObserver';
|
||||
import { renderInfoType, VList } from './VList';
|
||||
|
||||
export interface IData {
|
||||
id: string;
|
||||
|
@ -11,7 +13,6 @@ export interface IData {
|
|||
}
|
||||
|
||||
type IItem = {
|
||||
style: any,
|
||||
hasChild: boolean,
|
||||
onCollapse: (id: string) => void,
|
||||
onClick: (id: string) => void,
|
||||
|
@ -20,15 +21,11 @@ type IItem = {
|
|||
highlightValue: string,
|
||||
} & IData
|
||||
|
||||
// TODO: 计算可以展示的最多数量,并且监听显示器高度变化修改数值
|
||||
const showNum = 70;
|
||||
const lineHeight = 18;
|
||||
const indentationLength = 20;
|
||||
|
||||
function Item(props: IItem) {
|
||||
const {
|
||||
name,
|
||||
style,
|
||||
userKey,
|
||||
hasChild,
|
||||
onCollapse,
|
||||
|
@ -37,17 +34,17 @@ function Item(props: IItem) {
|
|||
indentation,
|
||||
onClick,
|
||||
isSelect,
|
||||
highlightValue,
|
||||
highlightValue = '',
|
||||
} = props;
|
||||
const isShowKey = userKey !== '';
|
||||
const showIcon = hasChild ? <Arrow director={isCollapsed ? 'right' : 'down'} /> : '';
|
||||
const showIcon = hasChild ? <Triangle director={isCollapsed ? 'right' : 'down'} /> : '';
|
||||
const handleClickCollapse = () => {
|
||||
onCollapse(id);
|
||||
};
|
||||
const handleClick = () => {
|
||||
onClick(id);
|
||||
};
|
||||
const itemAttr: any = { style, className: styles.treeItem, onClick: handleClick };
|
||||
const itemAttr: any = { className: styles.treeItem, onClick: handleClick };
|
||||
if (isSelect) {
|
||||
itemAttr.tabIndex = 0;
|
||||
itemAttr.className = styles.treeItem + ' ' + styles.select;
|
||||
|
@ -93,10 +90,17 @@ function Item(props: IItem) {
|
|||
);
|
||||
}
|
||||
|
||||
function VTree({ data, highlightValue }: { data: IData[], highlightValue: string }) {
|
||||
const [scrollTop, setScrollTop] = useState(0);
|
||||
function VTree({ data, highlightValue, selectedId, onRendered }: {
|
||||
data: IData[],
|
||||
highlightValue: string,
|
||||
selectedId: number,
|
||||
onRendered: (renderInfo: renderInfoType) => void
|
||||
}) {
|
||||
const [collapseNode, setCollapseNode] = useState(new Set<string>());
|
||||
const [selectItem, setSelectItem] = useState();
|
||||
const [selectItem, setSelectItem] = useState(selectedId);
|
||||
useEffect(() => {
|
||||
setSelectItem(selectedId);
|
||||
}, [selectedId]);
|
||||
const changeCollapseNode = (id: string) => {
|
||||
const nodes = new Set<string>();
|
||||
collapseNode.forEach(value => {
|
||||
|
@ -112,17 +116,14 @@ function VTree({ data, highlightValue }: { data: IData[], highlightValue: string
|
|||
const handleClickItem = (id: string) => {
|
||||
setSelectItem(id);
|
||||
};
|
||||
const showList: any[] = [];
|
||||
|
||||
let totalHeight = 0;
|
||||
let currentCollapseIndentation: null | number = null;
|
||||
data.forEach((item, index) => {
|
||||
// 存在未处理完的收起节点
|
||||
// 过滤掉折叠的 item,不展示在 VList 中
|
||||
const filter = (item: IData) => {
|
||||
if (currentCollapseIndentation !== null) {
|
||||
const indentation = item.indentation;
|
||||
// 缩进更大,不显示
|
||||
if (indentation > currentCollapseIndentation) {
|
||||
return;
|
||||
if (item.indentation > currentCollapseIndentation) {
|
||||
return false;
|
||||
} else {
|
||||
// 缩进小,说明完成了该收起节点的子节点处理。
|
||||
currentCollapseIndentation = null;
|
||||
|
@ -130,44 +131,45 @@ function VTree({ data, highlightValue }: { data: IData[], highlightValue: string
|
|||
}
|
||||
const id = item.id;
|
||||
const isCollapsed = collapseNode.has(id);
|
||||
if (totalHeight >= scrollTop && showList.length <= showNum) {
|
||||
const nextItem = data[index + 1];
|
||||
// 如果存在下一个节点,并且节点缩进比自己大,说明下个节点是子节点,节点本身需要显示展开收起图标
|
||||
const hasChild = nextItem ? nextItem.indentation > item.indentation : false;
|
||||
showList.push(
|
||||
<Item
|
||||
key={id}
|
||||
hasChild={hasChild}
|
||||
style={{
|
||||
transform: `translateY(${totalHeight}px)`,
|
||||
}}
|
||||
onCollapse={changeCollapseNode}
|
||||
onClick={handleClickItem}
|
||||
isCollapsed={isCollapsed}
|
||||
isSelect={id === selectItem}
|
||||
highlightValue={highlightValue}
|
||||
{...item} />
|
||||
);
|
||||
}
|
||||
totalHeight = totalHeight + lineHeight;
|
||||
if (isCollapsed) {
|
||||
// 该节点需要收起子节点
|
||||
currentCollapseIndentation = item.indentation;
|
||||
}
|
||||
});
|
||||
|
||||
const handleScroll = (event: any) => {
|
||||
const scrollTop = event.target.scrollTop;
|
||||
// 顶部留 100px 冗余高度
|
||||
setScrollTop(Math.max(scrollTop - 100, 0));
|
||||
return true;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.treeContainer} onScroll={handleScroll}>
|
||||
{showList}
|
||||
{/* 确保有足够的高度 */}
|
||||
<div style={{ marginTop: totalHeight }} />
|
||||
</div>
|
||||
<SizeObserver className={styles.treeContainer}>
|
||||
{(width, height) => {
|
||||
return (
|
||||
<VList
|
||||
data={data}
|
||||
width={width}
|
||||
height={height}
|
||||
itemHeight={18}
|
||||
scrollIndex={data.indexOf(selectItem)}
|
||||
filter={filter}
|
||||
onRendered={onRendered}
|
||||
>
|
||||
{(index: number, item: IData) => {
|
||||
// 如果存在下一个节点,并且节点缩进比自己大,说明下个节点是子节点,节点本身需要显示展开收起图标
|
||||
const nextItem = data[index + 1];
|
||||
const hasChild = nextItem && nextItem.indentation > item.indentation;
|
||||
return (
|
||||
<Item
|
||||
hasChild={hasChild}
|
||||
isCollapsed={collapseNode.has(item.id)}
|
||||
isSelect={selectItem === item.id}
|
||||
onCollapse={changeCollapseNode}
|
||||
onClick={handleClickItem}
|
||||
highlightValue={highlightValue}
|
||||
{...item} />
|
||||
);
|
||||
}}
|
||||
</VList>
|
||||
);
|
||||
}}
|
||||
</SizeObserver>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue