index.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. import type { ProTableProps } from '@jetlinks/pro-table';
  2. import ProTable from '@jetlinks/pro-table';
  3. import type { ParamsType } from '@ant-design/pro-provider';
  4. import React, { Key, useCallback, useEffect, useRef, useState } from 'react';
  5. import { isFunction } from 'lodash';
  6. import { Pagination, Space } from 'antd';
  7. import { AppstoreOutlined, BarsOutlined } from '@ant-design/icons';
  8. import classNames from 'classnames';
  9. import LoadingComponent from '@ant-design/pro-layout/es/PageLoading';
  10. import './index.less';
  11. import { useDomFullHeight } from '@/hooks';
  12. import { Empty } from '@/components';
  13. enum ModelEnum {
  14. TABLE = 'TABLE',
  15. CARD = 'CARD',
  16. }
  17. const Default_Size = 6;
  18. type ModelType = keyof typeof ModelEnum;
  19. interface ProTableCardProps<T> {
  20. cardRender?: (data: T) => JSX.Element | React.ReactNode;
  21. gridColumn?: number;
  22. /**
  23. * 用于不同分辨率
  24. * gridColumns[0] 1366 ~ 1440 分辨率;
  25. * gridColumns[1] 1440 ~ 1600 分辨率;
  26. * gridColumns[2] > 1600 分辨率;
  27. */
  28. gridColumns?: [number, number, number];
  29. height?: 'none';
  30. onlyCard?: boolean; //只展示card
  31. onPageChange?: (page: number, size: number) => void;
  32. cardBodyClass?: string;
  33. noPadding?: boolean;
  34. cardScrollY?: number;
  35. }
  36. const ProTableCard = <
  37. T extends Record<string, any>,
  38. U extends ParamsType = ParamsType,
  39. ValueType = 'text',
  40. >(
  41. props: ProTableCardProps<T> & ProTableProps<T, U, ValueType>,
  42. ) => {
  43. const { cardRender, toolBarRender, request, onlyCard, ...extraProps } = props;
  44. const [model, setModel] = useState<ModelType>(ModelEnum.CARD);
  45. const [total, setTotal] = useState<number | undefined>(0);
  46. const [current, setCurrent] = useState(
  47. props.params && props.params.pageIndex ? props.params.pageIndex + 1 : 1,
  48. ); // 当前页
  49. const [pageIndex, setPageIndex] = useState(
  50. props.params && props.params.pageIndex ? props.params.pageIndex : 0,
  51. );
  52. const [pageSize, setPageSize] = useState(
  53. props.params && props.params.pageSize ? props.params.pageSize : Default_Size * 2,
  54. ); // 每页条数
  55. const [column, setColumn] = useState(props.gridColumn || 4);
  56. const [loading, setLoading] = useState(false);
  57. const [dataLength, setDataLength] = useState<number>(0);
  58. const domRef = useRef<HTMLDivElement>(null);
  59. const { minHeight } = useDomFullHeight(domRef);
  60. /**
  61. * 处理 Card
  62. * @param dataSource
  63. */
  64. const handleCard = useCallback(
  65. (dataSource: readonly T[] | undefined, rowSelection?: any): JSX.Element => {
  66. setDataLength(dataSource ? dataSource.length : 0);
  67. const Item = (dom: React.ReactNode) => {
  68. if (!rowSelection || (rowSelection && !rowSelection.selectedRowKeys)) {
  69. return dom;
  70. }
  71. const { selectedRowKeys, onChange, onSelect, type } = rowSelection;
  72. // @ts-ignore
  73. const id = dom.props.id;
  74. // @ts-ignore
  75. return React.cloneElement(dom, {
  76. // @ts-ignore
  77. className: classNames(dom.props.className, {
  78. 'item-active': selectedRowKeys && selectedRowKeys.includes(id),
  79. }),
  80. key: id,
  81. onClick: (e: any) => {
  82. e.stopPropagation();
  83. if (onChange || onSelect) {
  84. const isSelect = selectedRowKeys.includes(id);
  85. let nowRowKeys: Key[] = [];
  86. let nowRowNodes = [];
  87. if (isSelect) {
  88. nowRowKeys =
  89. type === 'radio' ? [id] : selectedRowKeys.filter((key: string) => key !== id);
  90. } else {
  91. // const nowRowKeys = [...selectedRowKeys, id];
  92. nowRowKeys = rowSelection.type === 'radio' ? [id] : [...selectedRowKeys, id];
  93. }
  94. nowRowNodes = dataSource!.filter((item) => nowRowKeys.includes(item.id));
  95. onChange?.(nowRowKeys, nowRowNodes);
  96. onSelect?.((dom as any).props, !isSelect, nowRowNodes);
  97. }
  98. },
  99. });
  100. };
  101. const style: React.CSSProperties = {};
  102. if (props.cardScrollY !== undefined) {
  103. style.maxHeight = props.cardScrollY;
  104. style.overflowY = 'auto';
  105. }
  106. return (
  107. <>
  108. {dataSource && dataSource.length ? (
  109. <div style={{ paddingBottom: 38 }}>
  110. <div
  111. className={classNames('pro-table-card-items', props.cardBodyClass)}
  112. style={{ gridTemplateColumns: `repeat(${column}, 1fr)`, ...style }}
  113. >
  114. {dataSource.map((item) =>
  115. cardRender && isFunction(cardRender) ? Item(cardRender(item)) : null,
  116. )}
  117. </div>
  118. </div>
  119. ) : (
  120. <div
  121. style={{
  122. display: 'flex',
  123. justifyContent: 'center',
  124. alignItems: 'center',
  125. minHeight: props.height === 'none' ? 'auto' : minHeight - 150,
  126. }}
  127. >
  128. <Empty />
  129. </div>
  130. )}
  131. </>
  132. );
  133. },
  134. [minHeight],
  135. );
  136. const windowChange = () => {
  137. if (window.innerWidth <= 1440) {
  138. const _column = props.gridColumn && props.gridColumn < 2 ? props.gridColumn : 2;
  139. setColumn(props.gridColumns ? props.gridColumns[0] : _column);
  140. } else if (window.innerWidth > 1440 && window.innerWidth <= 1600) {
  141. const _column = props.gridColumn && props.gridColumn < 3 ? props.gridColumn : 3;
  142. setColumn(props.gridColumns ? props.gridColumns[1] : _column);
  143. } else if (window.innerWidth > 1600) {
  144. const _column = props.gridColumn && props.gridColumn < 4 ? props.gridColumn : 4;
  145. setColumn(props.gridColumns ? props.gridColumns[2] : _column);
  146. }
  147. };
  148. useEffect(() => {
  149. window.addEventListener('resize', windowChange);
  150. windowChange();
  151. return () => {
  152. window.removeEventListener('resize', windowChange);
  153. };
  154. }, [props.gridColumns]);
  155. const pageSizeOptions = [Default_Size * 2, Default_Size * 4, Default_Size * 8, Default_Size * 16];
  156. useEffect(() => {
  157. if (props.params?.pageIndex) {
  158. setCurrent(props.params?.pageIndex + 1);
  159. setPageIndex(props.params?.pageIndex);
  160. if (props.params.pageSize) {
  161. setPageSize(props.params?.pageSize);
  162. }
  163. } else {
  164. setCurrent(1);
  165. setPageIndex(0);
  166. }
  167. }, [props.params]);
  168. return (
  169. <div
  170. className={classNames('pro-table-card', {
  171. noPadding: props.noPadding || 'noPadding' in props,
  172. })}
  173. style={{ minHeight: props.height === 'none' ? 'auto' : minHeight }}
  174. ref={domRef}
  175. >
  176. <ProTable<T, U, ValueType>
  177. {...extraProps}
  178. params={
  179. {
  180. ...props.params,
  181. current: current,
  182. pageIndex: pageIndex,
  183. pageSize,
  184. } as any
  185. }
  186. columnEmptyText={''}
  187. className={'pro-table-card-body'}
  188. options={model === ModelEnum.CARD ? false : { ...props.options, fullScreen: false }}
  189. request={async (param, sort, filter) => {
  190. if (request) {
  191. const resp = await request(param, sort, filter);
  192. setLoading(false);
  193. setTotal(resp.result ? resp.result.total : 0);
  194. return {
  195. code: resp.message,
  196. result: {
  197. data: resp.result ? resp.result.data : [],
  198. pageIndex: resp.result ? resp.result.pageIndex : 0,
  199. pageSize: resp.result ? resp.result.pageSize : 0,
  200. total: resp.result ? resp.result.total : 0,
  201. },
  202. status: resp.status,
  203. };
  204. }
  205. return {};
  206. }}
  207. onLoadingChange={(l) => {
  208. setLoading(!!l);
  209. }}
  210. pagination={{
  211. onChange: (page, size) => {
  212. setCurrent(page);
  213. setPageIndex(page - 1);
  214. setPageSize(size);
  215. props.onPageChange?.(page - 1, size);
  216. },
  217. pageSize: pageSize,
  218. current: current,
  219. pageSizeOptions: pageSizeOptions,
  220. }}
  221. toolBarRender={(action, row) => {
  222. if (onlyCard) return [];
  223. const oldBar = toolBarRender ? toolBarRender(action, row) : [];
  224. return [
  225. ...oldBar,
  226. <Space
  227. align="center"
  228. key={ModelEnum.TABLE}
  229. size={12}
  230. className={classNames(`pro-table-card-setting-item`, {
  231. active: model === ModelEnum.TABLE,
  232. })}
  233. onClick={() => {
  234. setModel(ModelEnum.TABLE);
  235. }}
  236. >
  237. <BarsOutlined />
  238. </Space>,
  239. <Space
  240. align="center"
  241. size={12}
  242. key={ModelEnum.CARD}
  243. className={classNames(`pro-table-card-setting-item`, {
  244. active: model === ModelEnum.CARD,
  245. })}
  246. onClick={() => {
  247. setModel(ModelEnum.CARD);
  248. }}
  249. >
  250. <AppstoreOutlined />
  251. </Space>,
  252. ];
  253. }}
  254. tableViewRender={
  255. model === ModelEnum.CARD
  256. ? (tableProps) => {
  257. return handleCard(tableProps.dataSource, extraProps?.rowSelection);
  258. }
  259. : undefined
  260. }
  261. />
  262. {model === ModelEnum.CARD && (
  263. <>
  264. <div className={classNames('mask-loading', { show: loading })}>
  265. <LoadingComponent />
  266. </div>
  267. {!!dataLength && (
  268. <Pagination
  269. showSizeChanger
  270. size="small"
  271. className={'pro-table-card-pagination'}
  272. total={total}
  273. current={current}
  274. onChange={(page, size) => {
  275. setCurrent(page);
  276. setPageIndex(page - 1);
  277. setPageSize(size);
  278. props.onPageChange?.(page - 1, size);
  279. }}
  280. pageSizeOptions={pageSizeOptions}
  281. pageSize={pageSize}
  282. showTotal={(num) => {
  283. const minSize = pageIndex * pageSize + 1;
  284. const MaxSize = (pageIndex + 1) * pageSize;
  285. return `第 ${minSize} - ${MaxSize > num ? num : MaxSize} 条/总共 ${num} 条`;
  286. }}
  287. />
  288. )}
  289. </>
  290. )}
  291. </div>
  292. );
  293. };
  294. export default ProTableCard;