index.tsx 11 KB

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