index.tsx 11 KB

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