hooks.tsx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. import {
  2. createColumnHelper,
  3. useReactTable,
  4. getCoreRowModel,
  5. Header,
  6. RowSelectionState,
  7. SortingState,
  8. getSortedRowModel,
  9. ColumnSort,
  10. getExpandedRowModel,
  11. ExpandedState,
  12. } from '@tanstack/react-table';
  13. import {
  14. CSSProperties,
  15. Dispatch,
  16. SetStateAction,
  17. useEffect,
  18. useMemo,
  19. useRef,
  20. useState,
  21. } from 'react';
  22. import {DragStartEvent, DragEndEvent} from '@dnd-kit/core';
  23. import {arrayMove} from '@dnd-kit/sortable';
  24. import {createTableSettingContext, useContextSection} from '@hooks';
  25. import TableCheck from './Check';
  26. import {LDColumnsType} from '.';
  27. import {TABLE_CELL_WIDTH} from '@utils';
  28. import {useLatest} from 'ahooks';
  29. function parseColumn<T extends Record<string, unknown>>(
  30. columns: LDColumnsType<T>[],
  31. enableSelect?: boolean,
  32. enableNo?: boolean,
  33. ) {
  34. const helper = createColumnHelper<Record<string, any>>();
  35. let hasSort = false, hasGroup = false;
  36. const parseColumns: LDColumnsType<T>[] = [...columns];
  37. if (enableNo) {
  38. parseColumns.unshift({
  39. title: '序号',
  40. dataIndex: 'no',
  41. align: 'center',
  42. width: TABLE_CELL_WIDTH.no,
  43. render(_, index, row) {
  44. return row.depth > 0 ? null : index + 1;
  45. },
  46. });
  47. }
  48. let leftPosition = enableSelect ? TABLE_CELL_WIDTH.checkbox : 0;
  49. let rightPosition = parseColumns.reduce(function(prev, next) {
  50. if (next.fixed !== 'right') return prev;
  51. return prev + next.width;
  52. }, 0);
  53. const columnList = parseColumns.map(function(val) {
  54. const {dataIndex, title, render, align, fixed, width, sort, children} = val;
  55. let fixedStyle: CSSProperties = {};
  56. if (fixed === 'left') {
  57. fixedStyle = {left: leftPosition};
  58. leftPosition += width;
  59. }
  60. if (fixed === 'right') {
  61. rightPosition -= width;
  62. fixedStyle = {right: rightPosition};
  63. }
  64. const meta = {
  65. align,
  66. fixed,
  67. fixedStyle,
  68. disabledSort: Boolean(fixed) || dataIndex === 'no',
  69. sort,
  70. };
  71. if (children && children.length) {
  72. if (!hasGroup) hasGroup = true;
  73. const {columnList: columns} = parseColumn(
  74. children,
  75. false,
  76. false,
  77. );
  78. const group = helper.group({
  79. header: title,
  80. columns,
  81. minSize: 0,
  82. size: width,
  83. meta,
  84. });
  85. return group;
  86. }
  87. if (!hasSort && sort) hasSort = true;
  88. if (render) {
  89. return helper.display({
  90. id: dataIndex.toString(),
  91. header: title.toString(),
  92. size: width,
  93. minSize: 0,
  94. cell({row}) {
  95. return render(
  96. row.original as T,
  97. row.index,
  98. row,
  99. );
  100. },
  101. meta,
  102. });
  103. }
  104. return helper.accessor(dataIndex.toString(), {
  105. header: title?.toString(),
  106. cell: props => props.getValue(),
  107. size: width,
  108. meta,
  109. });
  110. });
  111. enableSelect && columnList.unshift(
  112. helper.accessor('select', {
  113. header({table}) {
  114. return (
  115. <TableCheck
  116. checked={table.getIsAllRowsSelected()}
  117. indeterminate={table.getIsSomeRowsSelected()}
  118. onChange={table.getToggleAllRowsSelectedHandler()}
  119. />
  120. );
  121. },
  122. cell({row}) {
  123. return (
  124. <TableCheck
  125. checked={row.getIsSelected()}
  126. indeterminate={row.getIsSomeSelected()}
  127. onChange={row.getToggleSelectedHandler()}
  128. enabled={row.getCanSelect()}
  129. />
  130. );
  131. },
  132. size: TABLE_CELL_WIDTH.checkbox,
  133. meta: {
  134. align: 'center',
  135. fixed: 'left',
  136. disabledSort: true,
  137. fixedStyle: {left: 0},
  138. },
  139. }),
  140. );
  141. return {
  142. columnList,
  143. hasSort,
  144. hasGroup,
  145. };
  146. }
  147. export function useTable<T extends Record<string, any>>(
  148. columns: LDColumnsType<T>[],
  149. data: T[],
  150. options: {
  151. settingContext: ReturnType<typeof createTableSettingContext>;
  152. rowSelection?: RowSelectionState;
  153. setRowSelection?: Dispatch<SetStateAction<RowSelectionState>>;
  154. rawKey?: keyof T;
  155. onSortingChange?: (state: ColumnSort | null) => void;
  156. subRowKey?: keyof T;
  157. hasRenderSubComponent: boolean;
  158. },
  159. ) {
  160. const {
  161. settingContext,
  162. rowSelection,
  163. setRowSelection,
  164. rawKey,
  165. onSortingChange,
  166. subRowKey,
  167. hasRenderSubComponent,
  168. } = options;
  169. const {columnList, hasSort, hasGroup} = useMemo(
  170. function() {
  171. return parseColumn(
  172. columns,
  173. Boolean(setRowSelection),
  174. true,
  175. );
  176. },
  177. [columns, setRowSelection],
  178. );
  179. const preloadData = useContextSection(settingContext, state => state[0]);
  180. const [columnSizing, setColumnSizing] = useState(function() {
  181. if (preloadData?.tableWidth)
  182. return JSON.parse(preloadData.tableWidth) as Record<string, number>;
  183. return {};
  184. });
  185. const [columnOrder, setColumnOrder] = useState(function() {
  186. if (preloadData?.tableOrder)
  187. return JSON.parse(preloadData?.tableOrder) as string[];
  188. const nextList = columns.map(val => val.dataIndex.toString());
  189. return hasGroup ? [] : ['select', 'no', ...nextList];
  190. });
  191. const [sorting, setSorting] = useState<SortingState>([]);
  192. const onSortingChangeFn = useLatest(onSortingChange);
  193. useEffect(function() {
  194. onSortingChangeFn.current?.(sorting.length ? sorting[0] : null);
  195. }, [onSortingChangeFn, sorting]);
  196. const [expanded, setExpanded] = useState<ExpandedState>({});
  197. const table = useReactTable({
  198. data,
  199. columns: columnList,
  200. state: {
  201. columnSizing,
  202. columnOrder,
  203. rowSelection,
  204. sorting,
  205. expanded,
  206. },
  207. getSortedRowModel: onSortingChange ? void 0 : getSortedRowModel(),
  208. getCoreRowModel: getCoreRowModel(),
  209. onColumnSizingChange: setColumnSizing,
  210. columnResizeMode: 'onChange',
  211. onColumnOrderChange: setColumnOrder,
  212. onRowSelectionChange: setRowSelection,
  213. getRowId: row => row[rawKey as string | undefined ?? 'id'],
  214. onSortingChange: setSorting,
  215. getSubRows: subRowKey ? row => row[subRowKey as string] : void 0,
  216. getExpandedRowModel: subRowKey && !hasRenderSubComponent
  217. ? getExpandedRowModel()
  218. : void 0,
  219. onExpandedChange: setExpanded,
  220. });
  221. type ActiveState = Header<Record<string, any>, unknown> | null;
  222. const [active, setActive] = useState<ActiveState>(null);
  223. function onDragStart(event: DragStartEvent) {
  224. setActive(event.active.data.current?.header);
  225. }
  226. function onDragEnd({active, over}: DragEndEvent) {
  227. setActive(null);
  228. if (!over)
  229. return;
  230. const {id: activeId} = active,
  231. {id: overId} = over;
  232. const {disabledSort} = (over.data.current as any).header.column.columnDef
  233. .meta;
  234. if (disabledSort)
  235. return;
  236. if (activeId === overId)
  237. return;
  238. const {setColumnOrder} = table;
  239. setColumnOrder(function(prev) {
  240. const fromIdx = prev.findIndex(val => val === activeId),
  241. toIdx = prev.findIndex(val => val === overId);
  242. return arrayMove(prev, fromIdx, toIdx);
  243. });
  244. }
  245. return [
  246. {...table, active, hasSort, hasGroup},
  247. {onDragStart, onDragEnd},
  248. ] as const;
  249. }
  250. export function useTableShadow(tableSize: number) {
  251. const [scrollProgess, setScrollProgess] = useState<'start' | 'process' | 'end'>(
  252. 'start',
  253. );
  254. const scrollTableRef = useRef<HTMLTableElement>(null);
  255. const headerTableRef = useRef<HTMLDivElement>(null);
  256. useEffect(
  257. function() {
  258. const el = scrollTableRef.current;
  259. function listener() {
  260. const elWidth = el?.getBoundingClientRect().width ?? 0,
  261. scrollWidth = el?.scrollWidth ?? 0,
  262. scrollLeft = el?.scrollLeft ?? 0;
  263. headerTableRef.current && (
  264. headerTableRef.current.scrollLeft = el?.scrollLeft ?? 0
  265. );
  266. setScrollProgess(function() {
  267. if (scrollLeft + elWidth === scrollWidth) return 'end';
  268. if (scrollLeft === 0) return 'start';
  269. return 'process';
  270. });
  271. }
  272. listener();
  273. window.addEventListener('resize', listener);
  274. el?.addEventListener('scroll', listener);
  275. return function() {
  276. window.addEventListener('resize', listener);
  277. el?.removeEventListener('scroll', listener);
  278. };
  279. },
  280. [tableSize],
  281. );
  282. return {scrollProgess, scrollTableRef, headerTableRef};
  283. }