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

GridLayoutCard
import React, {useMemo} from 'react';import {array, number} from 'prop-types';import {Card, Empty} from 'antd';import {WidthProvider, Responsive} from 'react-grid-layout';import 'react-grid-layout/css/styles.css';import 'react-resizable/css/styles.css';import {LineChart} from '@/components';const WithGridLayout = WidthProvider(Responsive);const GridItemComponent = React.forwardRef(({item, style, children, ...rest}, ref) => {const height = window.parseInt(style.height) - 60 || 200;return (<Card{...rest}title={item.title}size='small'style={style}ref={ref}><LineChartdataSource={item.dataSource}xAxisData={item.xAxisData}height={height}/>{children}</Card>);});GridLayoutCard.propTypes = {dataSource: array.isRequired,gutter: array,rowHeight: number,};GridLayoutCard.defaultProps = {gutter: [16, 24],rowHeight: 80,}function GridLayoutCard({dataSource, gutter, rowHeight}) {const layout = useMemo(() => {return dataSource.map((it, index) => ({i: String(it.id), // 组件key值x: (index % 3) * 4, // 组件在x轴坐标y: Math.floor(index / 3), // 组件在y轴坐标w: 4, // 组件宽度,占多少列h: 3, // 组件高度 = rowHeight的倍数,3倍minW: 3, // 最小宽度// placeholder: index,}));}, [dataSource])if (!dataSource?.length) {return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/>}return (<WithGridLayoutlayouts={{lg: layout, md: layout, sm: layout}}cols={{lg: 12, md: 12, sm: 6, xs: 4, xxs: 2}}breakpoints={{lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0}}rowHeight={rowHeight}margin={gutter}containerPadding={[0, 0]}resizeHandles={['ne', 'se']}// innerRef={ref}// compactType="horizontal">{dataSource.map(it => {return (<GridItemComponentkey={it.id}item={it}/>);})}</WithGridLayout>);}export default GridLayoutCard;
使用组件
import GridLayout from '@/components';const mockData = [{id: 10,title: '00',dataSource: [{label: 'ONE', value: [150, 230, 224, 218, 135, 147, 260]}],xAxisData: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],},{id: 11,title: '11',dataSource: [{label: 'ONE', value: [150, 230, 224, 218, 135, 147, 260]}],xAxisData: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],},{id: 22,title: '22',dataSource: [{label: 'ONE', value: [150, 230, 224, 218, 135, 147, 260]}],xAxisData: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],},{id: 33,title: '33',dataSource: [{label: 'ONE', value: [150, 230, 224, 218, 135, 147, 260]}],xAxisData: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],},{id: 44,title: '44',dataSource: [{label: 'ONE', value: [150, 230, 224, 218, 135, 147, 260]}],xAxisData: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],},{id: 55,title: '55',dataSource: [{label: 'ONE', value: [150, 230, 224, 218, 135, 147, 260]}],xAxisData: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],},{id: 66,title: '66',dataSource: [{label: 'ONE', value: [150, 230, 224, 218, 135, 147, 260]}],xAxisData: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],},];function App()return (<Spin spinning={loading}><GridCarddataSource={mockData}onDragEnd={onDragEnd}gutter={[16, 24]}/></Spin>)}
GridLayoutComponent
import React, {useMemo} from 'react';import {array, number} from 'prop-types';import {Empty} from "antd";import {WidthProvider, Responsive} from 'react-grid-layout';import 'react-grid-layout/css/styles.css';import 'react-resizable/css/styles.css';import GridLayoutItem from './GridLayoutItem'// import styles from './style.module.less';const WithGridLayout = WidthProvider(Responsive);GridLayoutCard.propTypes = {dataSource: array.isRequired,gutter: array,rowHeight: number,};GridLayoutCard.defaultProps = {gutter: [16, 24],rowHeight: 80,}function GridLayoutCard({dataSource, gutter, rowHeight}) {const layout = useMemo(() => {return dataSource.map((it, index) => ({i: String(it.id), // 组件key值,唯一的x: (index % 3) * 4, // 组件在x轴坐标y: Math.floor(index / 3), // 组件在y轴坐标w: 4, // 组件宽度,占多少列h: 3, // 组件高度 = rowHeight的倍数,3就是 rowHeight的3倍minW: 3, // 最小宽度minH: 2, // 最小高度// placeholder: index,}));}, [dataSource])if (!dataSource?.length) {return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/>}return (<WithGridLayoutlayouts={{lg: layout, md: layout, sm: layout}}cols={{lg: 12, md: 12, sm: 6, xs: 4, xxs: 2}}breakpoints={{lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0}}rowHeight={rowHeight}margin={gutter}containerPadding={[0, 0]}resizeHandles={['ne', 'se']}// className={styles.grid}// compactType="horizontal">{dataSource.map(it => {return (<GridLayoutItemextrakey={it.id}item={it}onEdit={() => {}}onDelete={() => {}}/>);})}</WithGridLayout>);}export default GridLayoutCard;
GridLayoutItem
import {useMemo, forwardRef} from 'react';import {string, bool, object} from 'prop-types';import {Card} from 'antd';import moment from 'moment';import {LineChart, BarChart, PieChart} from '@/components';import Table from '../SortableHocCard/Table'import DropdownMenu from '../SortableHocCard/DropdownMenu'const COMPONENT = {LINE: LineChart,BAR: BarChart,PIE: PieChart,TABLE: Table,}function GridLayoutItem(props, ref) {const {extra, item, theme, style, children, onEdit, onDelete, ...rest} = props;const {name, type, source} = item;const Component = COMPONENT[type];const height = useMemo(() => {return window.parseInt(style.height) - 60 || 200;}, [style.height]);const chartProps = useMemo(initSource, [source]);function initSource() {const _props = {xAxisData: [], dataSource: []}if (!source) return _props;if (type === 'TABLE') {_props.dataSource = source;} else if (type === 'PIE') {_props.dataSource = source.map(it => {return {name: it.name, value: it.metricValue}});} else if (['BAR', 'LINE'].includes(type)) {_props.dataSource = source.map(it => {return {label: it.name, value: it.values[1]}});const {values} = source[0];_props.xAxisData = values[0].map(it => moment(it).format('HH:mm[\n]MM-DD'))}return _props;}return (<Card{...rest}size='small'style={style}ref={ref}title={name}bodyStyle={{padding: (type === 'TABLE') ? 0 : 12}}extra={extra && <DropdownMenu onEdit={onEdit} onDelete={onDelete}/>}>{Component && (<Component{...chartProps}height={height}theme={theme}/>)}{children}</Card>);}const GridLayoutItemRef = forwardRef(GridLayoutItem)GridLayoutItemRef.propTypes = {item: object.isRequired,extra: bool,theme: string,};export default GridLayoutItemRef;
antd 3x Card组件报错
react-grid-layout Error: 
不是 react版本的问题;是:antd 3x版本的 Card组件引起的 React.forwardRef,无法获取 ref
解决:
- 升级 antd到 4x版本
- 用 div代替 Card组件
�
