import React, { useContext } from 'react'; import type { ListProps, TableColumnType, TableProps } from 'infrad'; import { ConfigProvider, List } from 'infrad'; import type { GetRowKey } from 'infrad/lib/table/interface'; import type { ActionType } from 'infrad-pro-table'; import type { GetComponentProps } from './index'; import get from 'rc-util/lib/utils/get'; import useLazyKVMap from 'infrad/lib/table/hooks/useLazyKVMap'; import useSelection from 'infrad/lib/table/hooks/useSelection'; import usePagination from 'infrad/lib/table/hooks/usePagination'; import type { ItemProps } from './Item'; import ProListItem from './Item'; import { PRO_LIST_KEYS_MAP } from './constants'; import classNames from 'classnames'; import type { ProCardProps } from 'infrad-pro-card'; type AntdListProps = Omit, 'rowKey'>; type Key = React.Key; type TriggerEventHandler = (record: RecordType) => void; export type ListViewProps = Omit, 'renderItem'> & Pick, 'columns' | 'dataSource' | 'expandable'> & { rowKey?: string | GetRowKey; showActions?: 'hover' | 'always'; showExtra?: 'hover' | 'always'; rowSelection?: TableProps['rowSelection']; prefixCls?: string; dataSource: readonly RecordType[]; renderItem?: (item: RecordType, index: number, defaultDom: JSX.Element) => React.ReactNode; actionRef: React.MutableRefObject; // 当非卡片模式时,用于为每一行的项目绑定事件,用户设置 `grid`时将会失效 onRow?: GetComponentProps; // 兼容普通和卡片模式的事件绑定,代表每一个项目的事件,是对`onRow`的补充 onItem?: GetComponentProps; rowClassName?: string | ((item: RecordType, index: number) => string); /** Render 除了 header 之后的代码 */ itemHeaderRender?: ItemProps['itemHeaderRender']; itemTitleRender?: ItemProps['itemTitleRender']; itemCardProps?: ProCardProps; }; function ListView(props: ListViewProps) { const { dataSource, columns, rowKey, showActions, showExtra, prefixCls: customizePrefixCls, actionRef, itemTitleRender, renderItem, itemCardProps, itemHeaderRender, expandable: expandableConfig, rowSelection, pagination, // List 的 pagination 默认是 false onRow, onItem, rowClassName, ...rest } = props; const { getPrefixCls } = useContext(ConfigProvider.ConfigContext); const getRowKey = React.useMemo>((): GetRowKey => { if (typeof rowKey === 'function') { return rowKey; } return (record: RecordType, index?: number) => (record as any)[rowKey as string] || index; }, [rowKey]); const [getRecordByKey] = useLazyKVMap(dataSource, 'children', getRowKey); // 合并分页的的配置 const [mergedPagination] = usePagination( dataSource.length, { responsive: true, ...pagination } as any, () => {}, ); /** 根据分页来返回不同的数据,模拟 table */ const pageData = React.useMemo(() => { if ( pagination === false || !mergedPagination.pageSize || dataSource.length < mergedPagination.total! ) { return dataSource; } const { current = 1, pageSize = 10 } = mergedPagination; const currentPageData = dataSource.slice((current - 1) * pageSize, current * pageSize); return currentPageData; }, [dataSource, mergedPagination, pagination]); const prefixCls = getPrefixCls('pro-list', customizePrefixCls); /** 提供和 table 一样的 rowSelection 配置 */ const [selectItemRender, selectedKeySet] = useSelection(rowSelection, { getRowKey, getRecordByKey, prefixCls, data: dataSource, pageData, expandType: 'row', childrenColumnName: 'children', locale: {}, }); // 提供和 Table 一样的 expand 支持 const { expandedRowKeys, defaultExpandedRowKeys, defaultExpandAllRows = true, onExpand, onExpandedRowsChange, rowExpandable, } = expandableConfig || {}; /** 展开收起功能区域 star */ const [innerExpandedKeys, setInnerExpandedKeys] = React.useState(() => { if (defaultExpandedRowKeys) { return defaultExpandedRowKeys as Key[]; } if (defaultExpandAllRows !== false) { return dataSource.map(getRowKey); } return []; }); const mergedExpandedKeys = React.useMemo( () => new Set(expandedRowKeys || innerExpandedKeys || []), [expandedRowKeys, innerExpandedKeys], ); const onTriggerExpand: TriggerEventHandler = React.useCallback( (record: RecordType) => { const key = getRowKey(record, dataSource.indexOf(record)); let newExpandedKeys: Key[]; const hasKey = mergedExpandedKeys.has(key); if (hasKey) { mergedExpandedKeys.delete(key); newExpandedKeys = [...mergedExpandedKeys]; } else { newExpandedKeys = [...mergedExpandedKeys, key]; } setInnerExpandedKeys(newExpandedKeys); if (onExpand) { onExpand(!hasKey, record); } if (onExpandedRowsChange) { onExpandedRowsChange(newExpandedKeys); } }, [getRowKey, mergedExpandedKeys, dataSource, onExpand, onExpandedRowsChange], ); /** 展开收起功能区域 end */ /** 这个是 选择框的 render 方法 为了兼容 antd 的 table,用了同样的渲染逻辑 所以看起来有点奇怪 */ const selectItemDom = selectItemRender([])[0]; return ( {...rest} className={classNames(getPrefixCls('pro-list-container', customizePrefixCls), rest.className)} dataSource={pageData} pagination={pagination && (mergedPagination as ListViewProps['pagination'])} renderItem={(item, index) => { const listItemProps: Partial> = { className: typeof rowClassName === 'function' ? rowClassName(item, index) : rowClassName, }; ( columns as (TableColumnType & { listKey: string; cardActionProps: string })[] )?.forEach((column) => { const { listKey, cardActionProps } = column; if (!PRO_LIST_KEYS_MAP.has(listKey)) { return; } const dataIndex = (column.dataIndex || listKey || column.key) as string; const rawData = Array.isArray(dataIndex) ? get(item, dataIndex as string[]) : item[dataIndex]; /** 如果cardActionProps 需要直接使用源数组,因为 action 必须要源数组 */ if (cardActionProps === 'actions' && listKey === 'actions') { listItemProps.cardActionProps = cardActionProps; } // 调用protable的列配置渲染数据 const data = column.render ? column.render(rawData, item, index) : rawData; if (data !== '-') listItemProps[column.listKey] = data; }); let checkboxDom; if (selectItemDom && selectItemDom.render) { checkboxDom = selectItemDom.render(item, item, index) || undefined; } const { isEditable, recordKey } = actionRef.current?.isEditable({ ...item, index }) || {}; const isChecked = selectedKeySet.has(recordKey || index); const defaultDom = ( { onTriggerExpand(item); }} index={index} record={item} item={item} showActions={showActions} showExtra={showExtra} itemTitleRender={itemTitleRender} itemHeaderRender={itemHeaderRender} rowSupportExpand={!rowExpandable || (rowExpandable && rowExpandable(item))} selected={selectedKeySet.has(getRowKey(item, index))} checkbox={checkboxDom} onRow={onRow} onItem={onItem} /> ); if (renderItem) { return renderItem(item, index, defaultDom); } return defaultDom; }} /> ); } export default ListView;