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';
|
@import 'assets.less';
|
||||||
|
|
||||||
.treeContainer {
|
.treeContainer {
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-y: auto;
|
|
||||||
|
|
||||||
.treeItem {
|
.treeItem {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { useState } from 'horizon';
|
import { useState, useEffect } from 'horizon';
|
||||||
import styles from './VTree.less';
|
import styles from './VTree.less';
|
||||||
import Arrow from '../svgs/Arrow';
|
import Triangle from '../svgs/Triangle';
|
||||||
import { createRegExp } from './../utils';
|
import { createRegExp } from './../utils';
|
||||||
|
import { SizeObserver } from './SizeObserver';
|
||||||
|
import { renderInfoType, VList } from './VList';
|
||||||
|
|
||||||
export interface IData {
|
export interface IData {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -11,7 +13,6 @@ export interface IData {
|
||||||
}
|
}
|
||||||
|
|
||||||
type IItem = {
|
type IItem = {
|
||||||
style: any,
|
|
||||||
hasChild: boolean,
|
hasChild: boolean,
|
||||||
onCollapse: (id: string) => void,
|
onCollapse: (id: string) => void,
|
||||||
onClick: (id: string) => void,
|
onClick: (id: string) => void,
|
||||||
|
@ -20,15 +21,11 @@ type IItem = {
|
||||||
highlightValue: string,
|
highlightValue: string,
|
||||||
} & IData
|
} & IData
|
||||||
|
|
||||||
// TODO: 计算可以展示的最多数量,并且监听显示器高度变化修改数值
|
|
||||||
const showNum = 70;
|
|
||||||
const lineHeight = 18;
|
|
||||||
const indentationLength = 20;
|
const indentationLength = 20;
|
||||||
|
|
||||||
function Item(props: IItem) {
|
function Item(props: IItem) {
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
style,
|
|
||||||
userKey,
|
userKey,
|
||||||
hasChild,
|
hasChild,
|
||||||
onCollapse,
|
onCollapse,
|
||||||
|
@ -37,17 +34,17 @@ function Item(props: IItem) {
|
||||||
indentation,
|
indentation,
|
||||||
onClick,
|
onClick,
|
||||||
isSelect,
|
isSelect,
|
||||||
highlightValue,
|
highlightValue = '',
|
||||||
} = props;
|
} = props;
|
||||||
const isShowKey = userKey !== '';
|
const isShowKey = userKey !== '';
|
||||||
const showIcon = hasChild ? <Arrow director={isCollapsed ? 'right' : 'down'} /> : '';
|
const showIcon = hasChild ? <Triangle director={isCollapsed ? 'right' : 'down'} /> : '';
|
||||||
const handleClickCollapse = () => {
|
const handleClickCollapse = () => {
|
||||||
onCollapse(id);
|
onCollapse(id);
|
||||||
};
|
};
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
onClick(id);
|
onClick(id);
|
||||||
};
|
};
|
||||||
const itemAttr: any = { style, className: styles.treeItem, onClick: handleClick };
|
const itemAttr: any = { className: styles.treeItem, onClick: handleClick };
|
||||||
if (isSelect) {
|
if (isSelect) {
|
||||||
itemAttr.tabIndex = 0;
|
itemAttr.tabIndex = 0;
|
||||||
itemAttr.className = styles.treeItem + ' ' + styles.select;
|
itemAttr.className = styles.treeItem + ' ' + styles.select;
|
||||||
|
@ -93,10 +90,17 @@ function Item(props: IItem) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function VTree({ data, highlightValue }: { data: IData[], highlightValue: string }) {
|
function VTree({ data, highlightValue, selectedId, onRendered }: {
|
||||||
const [scrollTop, setScrollTop] = useState(0);
|
data: IData[],
|
||||||
|
highlightValue: string,
|
||||||
|
selectedId: number,
|
||||||
|
onRendered: (renderInfo: renderInfoType) => void
|
||||||
|
}) {
|
||||||
const [collapseNode, setCollapseNode] = useState(new Set<string>());
|
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 changeCollapseNode = (id: string) => {
|
||||||
const nodes = new Set<string>();
|
const nodes = new Set<string>();
|
||||||
collapseNode.forEach(value => {
|
collapseNode.forEach(value => {
|
||||||
|
@ -112,17 +116,14 @@ function VTree({ data, highlightValue }: { data: IData[], highlightValue: string
|
||||||
const handleClickItem = (id: string) => {
|
const handleClickItem = (id: string) => {
|
||||||
setSelectItem(id);
|
setSelectItem(id);
|
||||||
};
|
};
|
||||||
const showList: any[] = [];
|
|
||||||
|
|
||||||
let totalHeight = 0;
|
|
||||||
let currentCollapseIndentation: null | number = null;
|
let currentCollapseIndentation: null | number = null;
|
||||||
data.forEach((item, index) => {
|
// 过滤掉折叠的 item,不展示在 VList 中
|
||||||
// 存在未处理完的收起节点
|
const filter = (item: IData) => {
|
||||||
if (currentCollapseIndentation !== null) {
|
if (currentCollapseIndentation !== null) {
|
||||||
const indentation = item.indentation;
|
|
||||||
// 缩进更大,不显示
|
// 缩进更大,不显示
|
||||||
if (indentation > currentCollapseIndentation) {
|
if (item.indentation > currentCollapseIndentation) {
|
||||||
return;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
// 缩进小,说明完成了该收起节点的子节点处理。
|
// 缩进小,说明完成了该收起节点的子节点处理。
|
||||||
currentCollapseIndentation = null;
|
currentCollapseIndentation = null;
|
||||||
|
@ -130,44 +131,45 @@ function VTree({ data, highlightValue }: { data: IData[], highlightValue: string
|
||||||
}
|
}
|
||||||
const id = item.id;
|
const id = item.id;
|
||||||
const isCollapsed = collapseNode.has(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) {
|
if (isCollapsed) {
|
||||||
// 该节点需要收起子节点
|
// 该节点需要收起子节点
|
||||||
currentCollapseIndentation = item.indentation;
|
currentCollapseIndentation = item.indentation;
|
||||||
}
|
}
|
||||||
});
|
return true;
|
||||||
|
|
||||||
const handleScroll = (event: any) => {
|
|
||||||
const scrollTop = event.target.scrollTop;
|
|
||||||
// 顶部留 100px 冗余高度
|
|
||||||
setScrollTop(Math.max(scrollTop - 100, 0));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.treeContainer} onScroll={handleScroll}>
|
<SizeObserver className={styles.treeContainer}>
|
||||||
{showList}
|
{(width, height) => {
|
||||||
{/* 确保有足够的高度 */}
|
return (
|
||||||
<div style={{ marginTop: totalHeight }} />
|
<VList
|
||||||
</div>
|
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