import type { DatePickerProps } from 'antd';
import { DatePicker, Radio } from 'antd';
import moment from 'moment';
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
export enum TimeKey {
'hour' = 'hour',
'today' = 'today',
'yesterday' = 'yesterday',
'week' = 'week',
'month' = 'month',
'year' = 'year',
}
export type TimeType = keyof typeof TimeKey | undefined;
type ValueType = { start: number; end: number; type: TimeType };
type timeToolOptions = {
label: string;
value: string;
};
interface ExtraTimePickerProps extends Omit<DatePickerProps, 'onChange' | 'value'> {
onChange?: (data: ValueType) => void;
value?: ValueType;
defaultTime?: TimeType;
pickerTimeChange?: () => void;
showTime?: boolean;
showTimeTool?: boolean;
timeToolOptions?: timeToolOptions[];
}
export const getTimeByType = (type: TimeType) => {
switch (type) {
case TimeKey.hour:
return moment().subtract(1, 'hours').valueOf();
case TimeKey.week:
return moment().subtract(6, 'days').valueOf();
case TimeKey.month:
return moment().subtract(29, 'days').valueOf();
case TimeKey.year:
return moment().subtract(365, 'days').valueOf();
default:
return moment().startOf('day').valueOf();
}
};
export default forwardRef((props: ExtraTimePickerProps, ref) => {
const [radioValue, setRadioValue] = useState<TimeType | undefined>(undefined);
const { value, onChange, ...extraProps } = props;
const change = (startTime: number, endTime: number, type: TimeType) => {
if (onChange) {
onChange({
start: startTime,
end: endTime,
type,
});
}
};
const timeChange = (type: TimeType) => {
let endTime = moment(new Date()).valueOf();
let startTime: number = getTimeByType(type);
if (type === TimeKey.yesterday) {
startTime = moment().subtract(1, 'days').startOf('day').valueOf();
endTime = moment().subtract(1, 'days').endOf('day').valueOf();
}
setRadioValue(type);
change(startTime, endTime, type);
};
useImperativeHandle(ref, () => ({
timeChange,
}));
useEffect(() => {
timeChange(props.defaultTime || TimeKey.today);
}, []);
return (
<>
{
// @ts-ignore
<DatePicker.RangePicker
{...extraProps}
allowClear={false}
showTime={props.showTime}
value={
value && [
moment(value && value.start ? value.start : new Date()),
moment(value && value.end ? value.end : new Date()),
]
}
onChange={(rangeValue) => {
setRadioValue(undefined);
if (rangeValue && rangeValue.length === 2) {
change(rangeValue[0]!.valueOf(), rangeValue[1]!.valueOf(), undefined);
}
if (props.pickerTimeChange) {
props.pickerTimeChange();
}
}}
renderExtraFooter={
props.showTimeTool !== true
? () => (
<div style={{ padding: '12px 0' }}>
<Radio.Group
defaultValue="day"
buttonStyle="solid"
value={radioValue}
onChange={(e) => {
timeChange(e.target.value);
}}
>
{props.timeToolOptions && Array.isArray(props.timeToolOptions) ? (
props.timeToolOptions.map((item) => (
<Radio.Button value={item.value}>{item.label}</Radio.Button>
))
) : (
<>
<Radio.Button value={TimeKey.today}>今日</Radio.Button>
<Radio.Button value={TimeKey.week}>近一周</Radio.Button>
<Radio.Button value={TimeKey.month}>近一月</Radio.Button>
<Radio.Button value={TimeKey.year}>近一年</Radio.Button>
</>
)}
</Radio.Group>
</div>
)
: undefined
}
/>
}
</>
);
});
上面是timepicker.tsx
import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
import Style from './index.less';
import { Col, Form, Radio, Row } from 'antd';
import type { TimeType } from './timePicker';//引用了*&*&&*……&……
interface timeToolOptions {
label: string;
value: string;
}
export interface HeaderProps {
title: string;
extraParams?: {
key: string;
Children: React.ReactNode;
};
initialValues?: any;
/**
* true 关闭初始化时触发onParamsChange
*/
closeInitialParams?: boolean;
defaultTime?: TimeType & string;
showTime?: boolean;
showTimeTool?: boolean;
timeToolOptions?: timeToolOptions[];
}
const NoTimeHeader=(props: HeaderProps) => {
const [form] = Form.useForm();
return (
<div className={Style.header}>
<div className={Style.title}>{props.title}</div>
</div>
);
};
export default NoTimeHeader;
上面是NoTimeHeader.tsx
import type { HeaderProps } from './noTimeHeader';//引用&&*…………(&((
import type { EchartsProps } from './echarts';
import Header from './noTimeHeader';//引用&……&……*&**&((
import Echarts from './echarts';
import Style from './index.less';
import classNames from 'classnames';
import { Empty } from '@/components';
import React, { forwardRef, useEffect, useState } from 'react';
interface BaseCardProps extends HeaderProps, EchartsProps {
height: number;
className?: string;
echartsAfter?: React.ReactNode;
}
const NoTimeBaseCard=(props: BaseCardProps) => {
const { height, className, options, ...formProps } = props;
const [myOptions, setMyOptions] = useState(options);
useEffect(() => {
console.log('myOptions-change');
setMyOptions(options);
}, [options]);
return (
<div
className={classNames(Style['dash-board'], className)}
style={{
height: height || 200,
}}
>
<Header {...formProps} />
<div className={Style['echarts-content']}>
{!!myOptions?.series?.[0]?.data?.length ? (
<Echarts options={myOptions} className={Style['echarts']} />
) : (
<Empty />
)}
{props.echartsAfter}
</div>
</div>
);
};
export default NoTimeBaseCard;
上面是NoTimeBaseCard.tsx
import BaseCard from './baseCard';
export { default as DashBoardEcharts } from './echarts';
export { default as DashBoardHeader } from './header';
export { default as DashBoardTopCard } from './topCard';
export { default as DashBoardDeviceCard } from './deviceCard';
export { default as NoTimeBaseCard } from './noTimeBaseCard';
export default BaseCard;
上面是index.tsx
import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
import Style from './index.less';
import { Col, Form, Radio, Row } from 'antd';
import type { TimeType } from './timePicker'; //&()()**()*
import RangePicker, { TimeKey } from './timePicker';//&*&(*(()*()(*
interface timeToolOptions {
label: string;
value: string;
}
export interface HeaderProps {
title: string;
/**
* 参数发生变化时的回调
* @param data
*/
onParamsChange: (data: any) => void;
extraParams?: {
key: string;
Children: React.ReactNode;
};
initialValues?: any;
/**
* true 关闭初始化时触发onParamsChange
*/
closeInitialParams?: boolean;
defaultTime?: TimeType & string;
showTime?: boolean;
showTimeTool?: boolean;
timeToolOptions?: timeToolOptions[];
}
export default forwardRef((props: HeaderProps, ref) => {
const [form] = Form.useForm();
const [radioValue, setRadioValue] = useState<TimeType | undefined>(undefined);
const isCloseInitial = useRef<boolean>(false);
const pickerRef = useRef<any>(null);
const change = async (data: any) => {
if (props.onParamsChange) {
props.onParamsChange(data);
}
};
useImperativeHandle(ref, () => ({
getValues: form.getFieldsValue,
}));
useEffect(() => {
setRadioValue(props.defaultTime || TimeKey.today);
}, []);
return (
<div className={Style.header}>
<div className={Style.title}>{props.title}</div>
<div className={Style.form}>
<Form
form={form}
colon={false}
layout={'inline'}
preserve={false}
initialValues={props.initialValues}
onValuesChange={(_, allValue) => {
console.log(allValue, 'allValue');
if (props.closeInitialParams && !isCloseInitial.current) {
isCloseInitial.current = true;
} else {
change(allValue);
}
}}
>
<Row style={{ width: '100%' }}>
{props.extraParams && (
<Col span={6}>
<Form.Item name={props.extraParams.key}>{props.extraParams.Children}</Form.Item>
</Col>
)}
<Col span={props.extraParams ? 18 : 24}>
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 12 }}>
{props.showTimeTool ? (
<Radio.Group
defaultValue="day"
buttonStyle="solid"
value={radioValue}
onChange={(e) => {
setRadioValue(e.target.value);
if (pickerRef.current) {
pickerRef.current.timeChange(e.target.value);
}
}}
>
{props.timeToolOptions && Array.isArray(props.timeToolOptions) ? (
props.timeToolOptions.map((item) => (
<Radio.Button value={item.value}>{item.label}</Radio.Button>
))
) : (
<>
<Radio.Button value={TimeKey.today}>今日</Radio.Button>
<Radio.Button value={TimeKey.week}>近一周</Radio.Button>
<Radio.Button value={TimeKey.month}>近一月</Radio.Button>
<Radio.Button value={TimeKey.year}>近一年</Radio.Button>
</>
)}
</Radio.Group>
) : null}
<Form.Item noStyle name={'time'}>
<RangePicker
ref={pickerRef}
defaultTime={props.defaultTime}
timeToolOptions={props.timeToolOptions}
showTime={props.showTime}
showTimeTool={props.showTimeTool}
pickerTimeChange={() => {
setRadioValue(undefined);
}}
/>
</Form.Item>
</div>
</Col>
</Row>
</Form>
</div>
</div>
);
});
上面是Header.tsx
import { useEffect, useRef, useState } from 'react';
import * as echarts from 'echarts/core';
import type { ECharts, EChartsOption } from 'echarts';
import {
GridComponent,
LegendComponent,
MarkLineComponent,
TitleComponent,
ToolboxComponent,
TooltipComponent,
} from 'echarts/components';
import { BarChart, LineChart, PieChart } from 'echarts/charts';
import { UniversalTransition } from 'echarts/features';
import { CanvasRenderer } from 'echarts/renderers';
import Style from './index.less';
import classNames from 'classnames';
export interface EchartsProps {
options?: EChartsOption;
className?: string;
}
echarts.use([
TitleComponent,
ToolboxComponent,
TooltipComponent,
GridComponent,
LegendComponent,
LineChart,
CanvasRenderer,
UniversalTransition,
BarChart,
MarkLineComponent,
PieChart,
]);
const DefaultOptions = {
xAxis: {
type: 'category',
data: [0],
},
yAxis: {
type: 'value',
},
series: [
{
data: [],
type: 'line',
},
],
};
export { echarts };
export default (props: EchartsProps) => {
const chartsRef = useRef<any>(null);
const chartsDom = useRef<any>(null);
const [loading, setLoading] = useState(false);
const initEcharts = (dom: HTMLDivElement) => {
console.log('------------init');
if (!dom) return;
chartsRef.current = chartsRef.current || echarts.init(dom);
// chartsRef.current.clear()
if (props.options) {
chartsRef.current.setOption(props.options);
} else {
chartsRef.current.setOption(DefaultOptions);
}
};
const updateSize = () => {
if (chartsRef.current) {
// 自适应屏幕变化
(chartsRef.current as ECharts).resize();
}
};
useEffect(() => {
setTimeout(() => (window as Window).addEventListener('resize', updateSize), 100);
return () => {
(window as Window).removeEventListener('resize', updateSize);
if (chartsRef.current) {
// chartsRef.current.clear();
chartsRef.current.dispose();
chartsRef.current = null;
}
};
}, []);
useEffect(() => {
if (chartsRef.current && props.options) {
// chartsRef.current.clear()
chartsRef.current.setOption(props.options);
}
}, [props.options, chartsRef.current]);
useEffect(() => {
// console.log('------------init')
if (loading) {
setTimeout(() => {
initEcharts(chartsDom.current);
}, 300);
}
}, [loading]);
useEffect(() => {
if (chartsDom.current) {
setLoading(true);
}
}, [chartsDom.current]);
return <div className={classNames(Style['content'], props.className)} ref={chartsDom} />;
};
上面是echarts.tsx
import React from 'react';
import { Badge, Col, Row } from 'antd';
import classNames from 'classnames';
import './index.less';
interface DeviceCardProps {
children: React.ReactNode;
className?: string;
style?: React.CSSProperties;
}
interface FooterItem {
status?: 'error' | 'success' | 'warning';
title: string;
value: string | number;
}
interface CardItemProps {
span: number;
title: string | React.ReactNode;
value: any;
showValue?: boolean;
children: React.ReactNode;
}
const CardItem = (props: CardItemProps) => {
return (
<Col span={props.span}>
<div className={'dash-board-top-item'}>
<div
className={classNames('top-item-content', { 'show-value': props.showValue !== false })}
>
<div className={'content-left'}>
<div className={'content-left-title'}>{props.title}</div>
{props.showValue !== false && <div className={'content-left-value'}>{props.value}</div>}
</div>
<div className={'content-right'}>{props.children}</div>
</div>
</div>
</Col>
);
};
const DeviceCard = (props: DeviceCardProps) => {
return (
<div className={classNames('dash-board-top', props.className)} style={props.style}>
<Row gutter={24}>{props.children}</Row>
</div>
);
};
DeviceCard.Item = CardItem;
export default DeviceCard;
上面是DeviceCard
import type { HeaderProps } from './header';
import Header from './header';
import type { EchartsProps } from './echarts';
import Echarts from './echarts';
import Style from './index.less';
import classNames from 'classnames';
import React, { forwardRef, useEffect, useState } from 'react';
import { Empty } from '@/components';
interface BaseCardProps extends HeaderProps, EchartsProps {
height: number;
className?: string;
echartsAfter?: React.ReactNode;
}
export default forwardRef((props: BaseCardProps, ref) => {
const { height, className, options, ...formProps } = props;
const [myOptions, setMyOptions] = useState(options);
useEffect(() => {
console.log('myOptions-change');
setMyOptions(options);
}, [options]);
return (
<div
className={classNames(Style['dash-board'], className)}
style={{
height: height || 200,
}}
>
<Header ref={ref} {...formProps} />
<div className={Style['echarts-content']}>
{!!myOptions?.series?.[0]?.data?.length ? (
<Echarts options={myOptions} className={Style['echarts']} />
) : (
<Empty />
)}
{props.echartsAfter}
</div>
</div>
);
});
上面是BaseCard