GridCard效果图

  • 支持上下调整高度
  • 支持左右调整列宽
  • 图表自适应拖拽宽高
  • 支持拖拽后的排序

image.png

GridLayoutCard

  1. import React, {useMemo} from 'react';
  2. import {array, number} from 'prop-types';
  3. import {Card, Empty} from 'antd';
  4. import {WidthProvider, Responsive} from 'react-grid-layout';
  5. import 'react-grid-layout/css/styles.css';
  6. import 'react-resizable/css/styles.css';
  7. import {LineChart} from '@/components';
  8. const WithGridLayout = WidthProvider(Responsive);
  9. const GridItemComponent = React.forwardRef(({item, style, children, ...rest}, ref) => {
  10. const height = window.parseInt(style.height) - 60 || 200;
  11. return (
  12. <Card
  13. {...rest}
  14. title={item.title}
  15. size='small'
  16. style={style}
  17. ref={ref}
  18. >
  19. <LineChart
  20. dataSource={item.dataSource}
  21. xAxisData={item.xAxisData}
  22. height={height}
  23. />
  24. {children}
  25. </Card>
  26. );
  27. });
  28. GridLayoutCard.propTypes = {
  29. dataSource: array.isRequired,
  30. gutter: array,
  31. rowHeight: number,
  32. };
  33. GridLayoutCard.defaultProps = {
  34. gutter: [16, 24],
  35. rowHeight: 80,
  36. }
  37. function GridLayoutCard({dataSource, gutter, rowHeight}) {
  38. const layout = useMemo(() => {
  39. return dataSource.map((it, index) => ({
  40. i: String(it.id), // 组件key值
  41. x: (index % 3) * 4, // 组件在x轴坐标
  42. y: Math.floor(index / 3), // 组件在y轴坐标
  43. w: 4, // 组件宽度,占多少列
  44. h: 3, // 组件高度 = rowHeight的倍数,3倍
  45. minW: 3, // 最小宽度
  46. // placeholder: index,
  47. }));
  48. }, [dataSource])
  49. if (!dataSource?.length) {
  50. return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/>
  51. }
  52. return (
  53. <WithGridLayout
  54. layouts={{lg: layout, md: layout, sm: layout}}
  55. cols={{lg: 12, md: 12, sm: 6, xs: 4, xxs: 2}}
  56. breakpoints={{lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0}}
  57. rowHeight={rowHeight}
  58. margin={gutter}
  59. containerPadding={[0, 0]}
  60. resizeHandles={['ne', 'se']}
  61. // innerRef={ref}
  62. // compactType="horizontal"
  63. >
  64. {dataSource.map(it => {
  65. return (
  66. <GridItemComponent
  67. key={it.id}
  68. item={it}
  69. />
  70. );
  71. })}
  72. </WithGridLayout>
  73. );
  74. }
  75. export default GridLayoutCard;

使用组件

  1. import GridLayout from '@/components';
  2. const mockData = [
  3. {
  4. id: 10,
  5. title: '00',
  6. dataSource: [{label: 'ONE', value: [150, 230, 224, 218, 135, 147, 260]}],
  7. xAxisData: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
  8. },
  9. {
  10. id: 11,
  11. title: '11',
  12. dataSource: [{label: 'ONE', value: [150, 230, 224, 218, 135, 147, 260]}],
  13. xAxisData: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
  14. },
  15. {
  16. id: 22,
  17. title: '22',
  18. dataSource: [{label: 'ONE', value: [150, 230, 224, 218, 135, 147, 260]}],
  19. xAxisData: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
  20. },
  21. {
  22. id: 33,
  23. title: '33',
  24. dataSource: [{label: 'ONE', value: [150, 230, 224, 218, 135, 147, 260]}],
  25. xAxisData: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
  26. },
  27. {
  28. id: 44,
  29. title: '44',
  30. dataSource: [{label: 'ONE', value: [150, 230, 224, 218, 135, 147, 260]}],
  31. xAxisData: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
  32. },
  33. {
  34. id: 55,
  35. title: '55',
  36. dataSource: [{label: 'ONE', value: [150, 230, 224, 218, 135, 147, 260]}],
  37. xAxisData: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
  38. },
  39. {
  40. id: 66,
  41. title: '66',
  42. dataSource: [{label: 'ONE', value: [150, 230, 224, 218, 135, 147, 260]}],
  43. xAxisData: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
  44. },
  45. ];
  46. function App()
  47. return (
  48. <Spin spinning={loading}>
  49. <GridCard
  50. dataSource={mockData}
  51. onDragEnd={onDragEnd}
  52. gutter={[16, 24]}
  53. />
  54. </Spin>
  55. )
  56. }

GridLayoutComponent

  1. import React, {useMemo} from 'react';
  2. import {array, number} from 'prop-types';
  3. import {Empty} from "antd";
  4. import {WidthProvider, Responsive} from 'react-grid-layout';
  5. import 'react-grid-layout/css/styles.css';
  6. import 'react-resizable/css/styles.css';
  7. import GridLayoutItem from './GridLayoutItem'
  8. // import styles from './style.module.less';
  9. const WithGridLayout = WidthProvider(Responsive);
  10. GridLayoutCard.propTypes = {
  11. dataSource: array.isRequired,
  12. gutter: array,
  13. rowHeight: number,
  14. };
  15. GridLayoutCard.defaultProps = {
  16. gutter: [16, 24],
  17. rowHeight: 80,
  18. }
  19. function GridLayoutCard({dataSource, gutter, rowHeight}) {
  20. const layout = useMemo(() => {
  21. return dataSource.map((it, index) => ({
  22. i: String(it.id), // 组件key值,唯一的
  23. x: (index % 3) * 4, // 组件在x轴坐标
  24. y: Math.floor(index / 3), // 组件在y轴坐标
  25. w: 4, // 组件宽度,占多少列
  26. h: 3, // 组件高度 = rowHeight的倍数,3就是 rowHeight的3倍
  27. minW: 3, // 最小宽度
  28. minH: 2, // 最小高度
  29. // placeholder: index,
  30. }));
  31. }, [dataSource])
  32. if (!dataSource?.length) {
  33. return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/>
  34. }
  35. return (
  36. <WithGridLayout
  37. layouts={{lg: layout, md: layout, sm: layout}}
  38. cols={{lg: 12, md: 12, sm: 6, xs: 4, xxs: 2}}
  39. breakpoints={{lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0}}
  40. rowHeight={rowHeight}
  41. margin={gutter}
  42. containerPadding={[0, 0]}
  43. resizeHandles={['ne', 'se']}
  44. // className={styles.grid}
  45. // compactType="horizontal"
  46. >
  47. {dataSource.map(it => {
  48. return (
  49. <GridLayoutItem
  50. extra
  51. key={it.id}
  52. item={it}
  53. onEdit={() => {
  54. }}
  55. onDelete={() => {
  56. }}
  57. />
  58. );
  59. })}
  60. </WithGridLayout>
  61. );
  62. }
  63. export default GridLayoutCard;

GridLayoutItem

  1. import {useMemo, forwardRef} from 'react';
  2. import {string, bool, object} from 'prop-types';
  3. import {Card} from 'antd';
  4. import moment from 'moment';
  5. import {LineChart, BarChart, PieChart} from '@/components';
  6. import Table from '../SortableHocCard/Table'
  7. import DropdownMenu from '../SortableHocCard/DropdownMenu'
  8. const COMPONENT = {
  9. LINE: LineChart,
  10. BAR: BarChart,
  11. PIE: PieChart,
  12. TABLE: Table,
  13. }
  14. function GridLayoutItem(props, ref) {
  15. const {extra, item, theme, style, children, onEdit, onDelete, ...rest} = props;
  16. const {name, type, source} = item;
  17. const Component = COMPONENT[type];
  18. const height = useMemo(() => {
  19. return window.parseInt(style.height) - 60 || 200;
  20. }, [style.height]);
  21. const chartProps = useMemo(initSource, [source]);
  22. function initSource() {
  23. const _props = {xAxisData: [], dataSource: []}
  24. if (!source) return _props;
  25. if (type === 'TABLE') {
  26. _props.dataSource = source;
  27. } else if (type === 'PIE') {
  28. _props.dataSource = source.map(it => {
  29. return {name: it.name, value: it.metricValue}
  30. });
  31. } else if (['BAR', 'LINE'].includes(type)) {
  32. _props.dataSource = source.map(it => {
  33. return {label: it.name, value: it.values[1]}
  34. });
  35. const {values} = source[0];
  36. _props.xAxisData = values[0].map(it => moment(it).format('HH:mm[\n]MM-DD'))
  37. }
  38. return _props;
  39. }
  40. return (
  41. <Card
  42. {...rest}
  43. size='small'
  44. style={style}
  45. ref={ref}
  46. title={name}
  47. bodyStyle={{padding: (type === 'TABLE') ? 0 : 12}}
  48. extra={extra && <DropdownMenu onEdit={onEdit} onDelete={onDelete}/>}
  49. >
  50. {Component && (
  51. <Component
  52. {...chartProps}
  53. height={height}
  54. theme={theme}
  55. />
  56. )}
  57. {children}
  58. </Card>
  59. );
  60. }
  61. const GridLayoutItemRef = forwardRef(GridLayoutItem)
  62. GridLayoutItemRef.propTypes = {
  63. item: object.isRequired,
  64. extra: bool,
  65. theme: string,
  66. };
  67. export default GridLayoutItemRef;

antd 3x Card组件报错

react-grid-layout Error: not mounted on DragStart
image.png
不是 react版本的问题;是:antd 3x版本的 Card组件引起的 React.forwardRef,无法获取 ref
解决:

  1. 升级 antd到 4x版本
  2. 用 div代替 Card组件