From 34e00408d7dc590c3ae1fa4361c6f23f345632bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=B8=85?= Date: Tue, 16 Apr 2019 21:04:58 +0800 Subject: [PATCH] Monitor finish --- AccountSettings/package.json | 1 - AdvancedForm/package.json | 1 - Analysis/package.json | 5 +- BasicList/package.json | 1 - BasicProfile/package.json | 1 - CardList/package.json | 1 - Exception403/package.json | 1 - Monitor/package.json | 7 +- Monitor/src/{_mock.js => _mock.ts} | 0 .../ActiveChart/{index.js => index.tsx} | 25 +- Monitor/src/components/Charts/Gauge/index.tsx | 177 ++++++++++ .../src/components/Charts/MiniArea/index.tsx | 134 ++++++++ Monitor/src/components/Charts/Pie/index.less | 94 ++++++ Monitor/src/components/Charts/Pie/index.tsx | 308 ++++++++++++++++++ .../src/components/Charts/TagCloud/index.less | 6 + .../src/components/Charts/TagCloud/index.tsx | 205 ++++++++++++ .../components/Charts/WaterWave/index.less | 28 ++ .../src/components/Charts/WaterWave/index.tsx | 230 +++++++++++++ Monitor/src/components/Charts/autoHeight.tsx | 75 +++++ Monitor/src/components/Charts/index.less | 19 ++ Monitor/src/components/Charts/index.tsx | 14 + Monitor/src/data.d.ts | 5 + Monitor/src/index.js | 243 -------------- Monitor/src/index.tsx | 273 ++++++++++++++++ Monitor/src/locales/{en-US.js => en-US.ts} | 0 Monitor/src/locales/{pt-BR.js => pt-BR.ts} | 0 Monitor/src/locales/{zh-CN.js => zh-CN.ts} | 0 Monitor/src/locales/{zh-TW.js => zh-TW.ts} | 0 Monitor/src/model.js | 28 -- Monitor/src/model.ts | 54 +++ Monitor/src/{service.js => service.tsx} | 0 ResultFail/package.json | 1 - package.json | 2 +- typings.d.ts | 1 + 34 files changed, 1646 insertions(+), 294 deletions(-) rename Monitor/src/{_mock.js => _mock.ts} (100%) rename Monitor/src/components/ActiveChart/{index.js => index.tsx} (81%) create mode 100644 Monitor/src/components/Charts/Gauge/index.tsx create mode 100644 Monitor/src/components/Charts/MiniArea/index.tsx create mode 100644 Monitor/src/components/Charts/Pie/index.less create mode 100644 Monitor/src/components/Charts/Pie/index.tsx create mode 100644 Monitor/src/components/Charts/TagCloud/index.less create mode 100644 Monitor/src/components/Charts/TagCloud/index.tsx create mode 100644 Monitor/src/components/Charts/WaterWave/index.less create mode 100644 Monitor/src/components/Charts/WaterWave/index.tsx create mode 100644 Monitor/src/components/Charts/autoHeight.tsx create mode 100644 Monitor/src/components/Charts/index.less create mode 100644 Monitor/src/components/Charts/index.tsx create mode 100644 Monitor/src/data.d.ts delete mode 100644 Monitor/src/index.js create mode 100644 Monitor/src/index.tsx rename Monitor/src/locales/{en-US.js => en-US.ts} (100%) rename Monitor/src/locales/{pt-BR.js => pt-BR.ts} (100%) rename Monitor/src/locales/{zh-CN.js => zh-CN.ts} (100%) rename Monitor/src/locales/{zh-TW.js => zh-TW.ts} (100%) delete mode 100644 Monitor/src/model.js create mode 100644 Monitor/src/model.ts rename Monitor/src/{service.js => service.tsx} (100%) diff --git a/AccountSettings/package.json b/AccountSettings/package.json index 808a0638..aa8984ad 100644 --- a/AccountSettings/package.json +++ b/AccountSettings/package.json @@ -12,7 +12,6 @@ "dev": "umi dev" }, "dependencies": { - "@ant-design/pro-layout": "^4.0.5", "antd": "^3.10.9", "dva": "^2.4.0", "react": "^16.6.3", diff --git a/AdvancedForm/package.json b/AdvancedForm/package.json index eb255871..f9433b1b 100644 --- a/AdvancedForm/package.json +++ b/AdvancedForm/package.json @@ -12,7 +12,6 @@ "dev": "umi dev" }, "dependencies": { - "@ant-design/pro-layout": "^4.0.5", "ant-design-pro": "^2.1.1", "antd": "^3.10.9", "dva": "^2.4.0", diff --git a/Analysis/package.json b/Analysis/package.json index d73b4cb2..170d660d 100644 --- a/Analysis/package.json +++ b/Analysis/package.json @@ -12,9 +12,7 @@ "dev": "umi dev" }, "dependencies": { - "@ant-design/pro-layout": "^4.0.5", "@antv/data-set": "^0.10.2", - "@types/numeral": "^0.0.25", "antd": "^3.10.9", "bizcharts": "^3.5.2", "bizcharts-plugin-slider": "^2.1.1-beta.1", @@ -28,6 +26,7 @@ "devDependencies": { "umi": "^2.6.9", "umi-plugin-block-dev": "^1.1.0", - "umi-plugin-react": "^1.7.2" + "umi-plugin-react": "^1.7.2", + "@types/numeral": "^0.0.25" } } \ No newline at end of file diff --git a/BasicList/package.json b/BasicList/package.json index 40cfd481..27957a83 100644 --- a/BasicList/package.json +++ b/BasicList/package.json @@ -12,7 +12,6 @@ "dev": "umi dev" }, "dependencies": { - "@ant-design/pro-layout": "^4.0.5", "antd": "^3.10.9", "dva": "^2.4.0", "hash.js": "^1.1.5", diff --git a/BasicProfile/package.json b/BasicProfile/package.json index 93dc636e..9fc8477a 100644 --- a/BasicProfile/package.json +++ b/BasicProfile/package.json @@ -11,7 +11,6 @@ "url": "https://github.com/umijs/umi-blocks/ant-design-pro/basicprofile" }, "dependencies": { - "@ant-design/pro-layout": "^4.0.5", "antd": "^3.10.9", "dva": "^2.4.0", "react": "^16.6.3", diff --git a/CardList/package.json b/CardList/package.json index 1ac8514d..4fc5e6b0 100644 --- a/CardList/package.json +++ b/CardList/package.json @@ -11,7 +11,6 @@ "url": "https://github.com/umijs/umi-blocks/ant-design-pro/cardlist" }, "dependencies": { - "@ant-design/pro-layout": "^4.0.5", "ant-design-pro": "^2.1.1", "antd": "^3.10.9", "dva": "^2.4.0", diff --git a/Exception403/package.json b/Exception403/package.json index 88293ebc..a7c7db7d 100644 --- a/Exception403/package.json +++ b/Exception403/package.json @@ -16,7 +16,6 @@ "react": "^16.6.3", "umi-request": "^1.0.4", "umi": "^2.6.8", - "@ant-design/pro-layout": "^4.0.5", "umi-plugin-react": "^1.7.2" }, "devDependencies": { diff --git a/Monitor/package.json b/Monitor/package.json index 181a2a21..2af3f5f5 100644 --- a/Monitor/package.json +++ b/Monitor/package.json @@ -11,13 +11,18 @@ "url": "https://github.com/umijs/umi-blocks/ant-design-pro/monitor" }, "dependencies": { + "@antv/data-set": "^0.10.2", "antd": "^3.10.9", + "bizcharts": "^3.5.2", + "bizcharts-plugin-slider": "^2.1.1-beta.1", "dva": "^2.4.0", "numeral": "^2.0.6", - "react": "^16.6.3", + "react": "^16.8.6", + "react-fittext": "^1.0.0", "umi-request": "^1.0.0" }, "devDependencies": { + "@types/numeral": "^0.0.25", "mockjs": "^1.0.1-beta3", "umi": "^2.6.9", "umi-plugin-react": "^1.7.2", diff --git a/Monitor/src/_mock.js b/Monitor/src/_mock.ts similarity index 100% rename from Monitor/src/_mock.js rename to Monitor/src/_mock.ts diff --git a/Monitor/src/components/ActiveChart/index.js b/Monitor/src/components/ActiveChart/index.tsx similarity index 81% rename from Monitor/src/components/ActiveChart/index.js rename to Monitor/src/components/ActiveChart/index.tsx index c72c89d0..09fe772b 100644 --- a/Monitor/src/components/ActiveChart/index.js +++ b/Monitor/src/components/ActiveChart/index.tsx @@ -1,11 +1,11 @@ import React, { Component } from 'react'; -import { Charts, NumberInfo } from 'ant-design-pro'; - +import Charts from '../Charts'; +import { Statistic } from 'antd'; import styles from './index.less'; const { MiniArea } = Charts; -function fixedZero(val) { +function fixedZero(val: number) { return val * 1 < 10 ? `0${val}` : val; } @@ -28,15 +28,18 @@ export default class ActiveChart extends Component { componentDidMount() { this.loopData(); } - + timer: number | undefined; + requestRef: number | undefined; componentWillUnmount() { clearTimeout(this.timer); - cancelAnimationFrame(this.requestRef); + if (this.requestRef) { + cancelAnimationFrame(this.requestRef); + } } loopData = () => { this.requestRef = requestAnimationFrame(() => { - this.timer = setTimeout(() => { + this.timer = window.setTimeout(() => { this.setState( { activeData: getActiveData(), @@ -54,7 +57,7 @@ export default class ActiveChart extends Component { return (
- +
diff --git a/Monitor/src/components/Charts/Gauge/index.tsx b/Monitor/src/components/Charts/Gauge/index.tsx new file mode 100644 index 00000000..45e18614 --- /dev/null +++ b/Monitor/src/components/Charts/Gauge/index.tsx @@ -0,0 +1,177 @@ +import React from 'react'; +import { Chart, Geom, Axis, Coord, Guide, Shape } from 'bizcharts'; +import autoHeight from '../autoHeight'; + +const { Arc, Html, Line } = Guide; + +export interface IGaugeProps { + title: React.ReactNode; + color?: string; + height?: number; + bgColor?: number; + percent: number; + forceFit?: boolean; + style?: React.CSSProperties; + formatter?: (value: string) => string; +} + +const defaultFormatter = (val: string): string => { + switch (val) { + case '2': + return '差'; + case '4': + return '中'; + case '6': + return '良'; + case '8': + return '优'; + default: + return ''; + } +}; + +Shape.registerShape!('point', 'pointer', { + drawShape(cfg: any, group: any) { + let point = cfg.points[0]; + point = (this as any).parsePoint(point); + const center = (this as any).parsePoint({ + x: 0, + y: 0, + }); + group.addShape('line', { + attrs: { + x1: center.x, + y1: center.y, + x2: point.x, + y2: point.y, + stroke: cfg.color, + lineWidth: 2, + lineCap: 'round', + }, + }); + return group.addShape('circle', { + attrs: { + x: center.x, + y: center.y, + r: 6, + stroke: cfg.color, + lineWidth: 3, + fill: '#fff', + }, + }); + }, +}); + +class Gauge extends React.Component { + render() { + const { + title, + height = 1, + percent, + forceFit = true, + formatter = defaultFormatter, + color = '#2F9CFF', + bgColor = '#F0F2F5', + } = this.props; + const cols = { + value: { + type: 'linear', + min: 0, + max: 10, + tickCount: 6, + nice: true, + }, + }; + const renderHtml = () => ` +
+

${title}

+

+ ${(data[0].value * 10).toFixed(2)}% +

+
`; + const data = [{ value: percent / 10 }]; + const textStyle: { + fontSize: number; + fill: string; + textAlign: 'center'; + } = { + fontSize: 12, + fill: 'rgba(0, 0, 0, 0.65)', + textAlign: 'center', + }; + return ( + + + + + + + + + + + + + + + ); + } +} + +export default autoHeight()(Gauge); diff --git a/Monitor/src/components/Charts/MiniArea/index.tsx b/Monitor/src/components/Charts/MiniArea/index.tsx new file mode 100644 index 00000000..3eb588d6 --- /dev/null +++ b/Monitor/src/components/Charts/MiniArea/index.tsx @@ -0,0 +1,134 @@ +import React from 'react'; +import { Chart, Axis, Tooltip, Geom } from 'bizcharts'; +import autoHeight from '../autoHeight'; +import styles from '../index.less'; + +export interface IAxis { + title: any; + line: any; + gridAlign: any; + labels: any; + tickLine: any; + grid: any; + label: any; +} + +export interface IMiniAreaProps { + color?: string; + height?: number; + borderColor?: string; + line?: boolean; + animate?: boolean; + xAxis?: IAxis; + forceFit?: boolean; + scale?: { x?: any; y?: any }; + yAxis?: Partial; + borderWidth?: number; + data: Array<{ + x: number | string; + y: number; + }>; +} + +class MiniArea extends React.Component { + render() { + const { + height = 1, + data = [], + forceFit = true, + color = 'rgba(24, 144, 255, 0.2)', + borderColor = '#1089ff', + scale = { x: {}, y: {} }, + borderWidth = 2, + line, + xAxis, + yAxis, + animate = true, + } = this.props; + + const padding: [number, number, number, number] = [36, 5, 30, 5]; + + const scaleProps = { + x: { + type: 'cat', + range: [0, 1], + ...scale!.x, + }, + y: { + min: 0, + ...scale!.y, + }, + }; + + const tooltip: [string, (...args: any[]) => { name?: string; value: string }] = [ + 'x*y', + (x: string, y: string) => ({ + name: x, + value: y, + }), + ]; + + const chartHeight = height + 54; + + return ( +
+
+ {height > 0 && ( + + + + + + {line ? ( + + ) : ( + + )} + + )} +
+
+ ); + } +} + +export default autoHeight()(MiniArea); diff --git a/Monitor/src/components/Charts/Pie/index.less b/Monitor/src/components/Charts/Pie/index.less new file mode 100644 index 00000000..fc961b41 --- /dev/null +++ b/Monitor/src/components/Charts/Pie/index.less @@ -0,0 +1,94 @@ +@import '~antd/lib/style/themes/default.less'; + +.pie { + position: relative; + .chart { + position: relative; + } + &.hasLegend .chart { + width: ~'calc(100% - 240px)'; + } + .legend { + position: absolute; + top: 50%; + right: 0; + min-width: 200px; + margin: 0 20px; + padding: 0; + list-style: none; + transform: translateY(-50%); + li { + height: 22px; + margin-bottom: 16px; + line-height: 22px; + cursor: pointer; + &:last-child { + margin-bottom: 0; + } + } + } + .dot { + position: relative; + top: -1px; + display: inline-block; + width: 8px; + height: 8px; + margin-right: 8px; + border-radius: 8px; + } + .line { + display: inline-block; + width: 1px; + height: 16px; + margin-right: 8px; + background-color: @border-color-split; + } + .legendTitle { + color: @text-color; + } + .percent { + color: @text-color-secondary; + } + .value { + position: absolute; + right: 0; + } + .title { + margin-bottom: 8px; + } + .total { + position: absolute; + top: 50%; + left: 50%; + max-height: 62px; + text-align: center; + transform: translate(-50%, -50%); + & > h4 { + height: 22px; + margin-bottom: 8px; + color: @text-color-secondary; + font-weight: normal; + font-size: 14px; + line-height: 22px; + } + & > p { + display: block; + height: 32px; + color: @heading-color; + font-size: 1.2em; + line-height: 32px; + white-space: nowrap; + } + } +} + +.legendBlock { + &.hasLegend .chart { + width: 100%; + margin: 0 0 32px 0; + } + .legend { + position: relative; + transform: none; + } +} diff --git a/Monitor/src/components/Charts/Pie/index.tsx b/Monitor/src/components/Charts/Pie/index.tsx new file mode 100644 index 00000000..47c83663 --- /dev/null +++ b/Monitor/src/components/Charts/Pie/index.tsx @@ -0,0 +1,308 @@ +import React, { Component } from 'react'; +import { Chart, Tooltip, Geom, Coord } from 'bizcharts'; +import { DataView } from '@antv/data-set'; +import { Divider } from 'antd'; +import classNames from 'classnames'; +import ReactFitText from 'react-fittext'; +import Debounce from 'lodash-decorators/debounce'; +import Bind from 'lodash-decorators/bind'; +import autoHeight from '../autoHeight'; + +import styles from './index.less'; +export interface IPieProps { + animate?: boolean; + color?: string; + colors?: string[]; + selected?: boolean; + height?: number; + margin?: [number, number, number, number]; + hasLegend?: boolean; + padding?: [number, number, number, number]; + percent?: number; + data?: Array<{ + x: string | string; + y: number; + }>; + inner?: number; + lineWidth?: number; + forceFit?: boolean; + style?: React.CSSProperties; + className?: string; + total?: React.ReactNode | number | (() => React.ReactNode | number); + title?: React.ReactNode; + tooltip?: boolean; + valueFormat?: (value: string) => string | React.ReactNode; + subTitle?: React.ReactNode; +} +interface IPieState { + legendData: Array<{ checked: boolean; x: string; color: string; percent: number; y: string }>; + legendBlock: boolean; +} +class Pie extends Component { + state: IPieState = { + legendData: [], + legendBlock: false, + }; + + requestRef: number | undefined; + + componentDidMount() { + window.addEventListener( + 'resize', + () => { + this.requestRef = requestAnimationFrame(() => this.resize()); + }, + { passive: true } + ); + } + + componentDidUpdate(preProps: IPieProps) { + const { data } = this.props; + if (data !== preProps.data) { + // because of charts data create when rendered + // so there is a trick for get rendered time + this.getLegendData(); + } + } + + componentWillUnmount() { + if (this.requestRef) { + window.cancelAnimationFrame(this.requestRef); + } + window.removeEventListener('resize', this.resize); + if (this.resize) { + (this.resize as any).cancel(); + } + } + + getG2Instance = (chart: G2.Chart) => { + this.chart = chart; + requestAnimationFrame(() => { + this.getLegendData(); + this.resize(); + }); + }; + + // for custom lengend view + getLegendData = () => { + if (!this.chart) return; + const geom = this.chart.getAllGeoms()[0]; // 获取所有的图形 + if (!geom) return; + const items = geom.get('dataArray') || []; // 获取图形对应的 + + const legendData = items.map((item: { color: any; _origin: any }[]) => { + /* eslint no-underscore-dangle:0 */ + const origin = item[0]._origin; + origin.color = item[0].color; + origin.checked = true; + return origin; + }); + + this.setState({ + legendData, + }); + }; + handleRoot = (n: HTMLDivElement) => { + this.root = n; + }; + + handleLegendClick = (item: any, i: string | number) => { + const newItem = item; + newItem.checked = !newItem.checked; + + const { legendData } = this.state; + legendData[i] = newItem; + + const filteredLegendData = legendData.filter(l => l.checked).map(l => l.x); + + if (this.chart) { + this.chart.filter('x', val => filteredLegendData.indexOf(val + '') > -1); + } + + this.setState({ + legendData, + }); + }; + root!: HTMLDivElement; + chart: G2.Chart | undefined; + + // for window resize auto responsive legend + @Bind() + @Debounce(300) + resize() { + const { hasLegend } = this.props; + const { legendBlock } = this.state; + if (!hasLegend || !this.root) { + window.removeEventListener('resize', this.resize); + return; + } + if ( + this.root && + this.root.parentNode && + (this.root.parentNode as HTMLElement).clientWidth <= 380 + ) { + if (!legendBlock) { + this.setState({ + legendBlock: true, + }); + } + } else if (legendBlock) { + this.setState({ + legendBlock: false, + }); + } + } + + render() { + const { + valueFormat, + subTitle, + total, + hasLegend = false, + className, + style, + height = 0, + forceFit = true, + percent, + color, + inner = 0.75, + animate = true, + colors, + lineWidth = 1, + } = this.props; + + const { legendData, legendBlock } = this.state; + const pieClassName = classNames(styles.pie, className, { + [styles.hasLegend]: !!hasLegend, + [styles.legendBlock]: legendBlock, + }); + + const { + data: propsData, + selected: propsSelected = true, + tooltip: propsTooltip = true, + } = this.props; + + let data = propsData || []; + let selected = propsSelected; + let tooltip = propsTooltip; + + const defaultColors = colors; + data = data || []; + selected = selected || true; + tooltip = tooltip || true; + let formatColor; + + const scale = { + x: { + type: 'cat', + range: [0, 1], + }, + y: { + min: 0, + }, + }; + + if (percent || percent === 0) { + selected = false; + tooltip = false; + formatColor = (value: string) => { + if (value === '占比') { + return color || 'rgba(24, 144, 255, 0.85)'; + } + return '#F0F2F5'; + }; + + data = [ + { + x: '占比', + y: parseFloat(percent + ''), + }, + { + x: '反比', + y: 100 - parseFloat(percent + ''), + }, + ]; + } + + const tooltipFormat: [string, (...args: any[]) => { name?: string; value: string }] = [ + 'x*percent', + (x: string, p: number) => ({ + name: x, + value: `${(p * 100).toFixed(2)}%`, + }), + ]; + + const padding = [12, 0, 12, 0] as [number, number, number, number]; + + const dv = new DataView(); + dv.source(data).transform({ + type: 'percent', + field: 'y', + dimension: 'x', + as: 'percent', + }); + + return ( +
+ +
+ + {!!tooltip && } + + + + + {(subTitle || total) && ( +
+ {subTitle &&

{subTitle}

} + {/* eslint-disable-next-line */} + {total && ( +
{typeof total === 'function' ? total() : total}
+ )} +
+ )} +
+
+ + {hasLegend && ( +
    + {legendData.map((item, i) => ( +
  • this.handleLegendClick(item, i)}> + + {item.x} + + + {`${(Number.isNaN(item.percent) ? 0 : item.percent * 100).toFixed(2)}%`} + + {valueFormat ? valueFormat(item.y) : item.y} +
  • + ))} +
+ )} +
+ ); + } +} + +export default autoHeight()(Pie); diff --git a/Monitor/src/components/Charts/TagCloud/index.less b/Monitor/src/components/Charts/TagCloud/index.less new file mode 100644 index 00000000..db8e4dab --- /dev/null +++ b/Monitor/src/components/Charts/TagCloud/index.less @@ -0,0 +1,6 @@ +.tagCloud { + overflow: hidden; + canvas { + transform-origin: 0 0; + } +} diff --git a/Monitor/src/components/Charts/TagCloud/index.tsx b/Monitor/src/components/Charts/TagCloud/index.tsx new file mode 100644 index 00000000..d8070c3f --- /dev/null +++ b/Monitor/src/components/Charts/TagCloud/index.tsx @@ -0,0 +1,205 @@ +import React, { Component } from 'react'; +import { Chart, Geom, Coord, Shape, Tooltip } from 'bizcharts'; +import DataSet from '@antv/data-set'; +import Debounce from 'lodash-decorators/debounce'; +import Bind from 'lodash-decorators/bind'; +import classNames from 'classnames'; +import autoHeight from '../autoHeight'; +import styles from './index.less'; + +/* eslint no-underscore-dangle: 0 */ +/* eslint no-param-reassign: 0 */ + +const imgUrl = 'https://gw.alipayobjects.com/zos/rmsportal/gWyeGLCdFFRavBGIDzWk.png'; + +export interface ITagCloudProps { + data: Array<{ + name: string; + value: string; + }>; + height?: number; + className?: string; + style?: React.CSSProperties; +} + +interface ITagCloudState { + dv: any; + height?: number; + width: number; +} + +class TagCloud extends Component { + state = { + dv: null, + height: 0, + width: 0, + }; + + componentDidMount() { + requestAnimationFrame(() => { + this.initTagCloud(); + this.renderChart(this.props); + }); + window.addEventListener('resize', this.resize, { passive: true }); + } + + componentDidUpdate(preProps?: ITagCloudProps) { + const { data } = this.props; + if (preProps && JSON.stringify(preProps.data) !== JSON.stringify(data)) { + this.renderChart(this.props); + } + } + isUnmount!: boolean; + componentWillUnmount() { + this.isUnmount = true; + window.cancelAnimationFrame(this.requestRef); + window.removeEventListener('resize', this.resize); + } + requestRef!: number; + resize = () => { + this.requestRef = requestAnimationFrame(() => { + this.renderChart(this.props); + }); + }; + + root: HTMLDivElement | undefined; + saveRootRef = (node: HTMLDivElement) => { + this.root = node; + }; + + initTagCloud = () => { + function getTextAttrs(cfg: { + x?: any; + y?: any; + style?: any; + opacity?: any; + origin?: any; + color?: any; + }) { + return Object.assign({}, cfg.style, { + fillOpacity: cfg.opacity, + fontSize: cfg.origin._origin.size, + rotate: cfg.origin._origin.rotate, + text: cfg.origin._origin.text, + textAlign: 'center', + fontFamily: cfg.origin._origin.font, + fill: cfg.color, + textBaseline: 'Alphabetic', + }); + } + + (Shape as any).registerShape('point', 'cloud', { + drawShape( + cfg: { x: any; y: any }, + container: { addShape: (arg0: string, arg1: { attrs: any }) => void } + ) { + const attrs = getTextAttrs(cfg); + return container.addShape('text', { + attrs: Object.assign(attrs, { + x: cfg.x, + y: cfg.y, + }), + }); + }, + }); + }; + imageMask: HTMLImageElement | undefined; + + @Bind() + @Debounce(500) + renderChart(nextProps: ITagCloudProps) { + // const colors = ['#1890FF', '#41D9C7', '#2FC25B', '#FACC14', '#9AE65C']; + const { data, height } = nextProps || this.props; + if (data.length < 1 || !this.root) { + return; + } + + const h = height; + const w = this.root.offsetWidth; + + const onload = () => { + const dv = new DataSet.View().source(data); + const range = dv.range('value'); + const [min, max] = range; + dv.transform({ + type: 'tag-cloud', + fields: ['name', 'value'], + imageMask: this.imageMask, + font: 'Verdana', + size: [w, h], // 宽高设置最好根据 imageMask 做调整 + padding: 0, + timeInterval: 5000, // max execute time + rotate() { + return 0; + }, + fontSize(d: { value: number }) { + // eslint-disable-next-line + return Math.pow((d.value - min) / (max - min), 2) * (17.5 - 5) + 5; + }, + }); + + if (this.isUnmount) { + return; + } + + this.setState({ + dv, + width: w, + height: h, + }); + }; + + if (!this.imageMask) { + this.imageMask = new Image(); + this.imageMask.crossOrigin = ''; + this.imageMask.src = imgUrl; + + this.imageMask.onload = onload; + } else { + onload(); + } + } + + render() { + const { className, height } = this.props; + const { dv, width, height: stateHeight } = this.state; + + return ( +
+ {dv && ( + + + + + + )} +
+ ); + } +} + +export default autoHeight()(TagCloud); diff --git a/Monitor/src/components/Charts/WaterWave/index.less b/Monitor/src/components/Charts/WaterWave/index.less new file mode 100644 index 00000000..2e75f214 --- /dev/null +++ b/Monitor/src/components/Charts/WaterWave/index.less @@ -0,0 +1,28 @@ +@import '~antd/lib/style/themes/default.less'; + +.waterWave { + position: relative; + display: inline-block; + transform-origin: left; + .text { + position: absolute; + top: 32px; + left: 0; + width: 100%; + text-align: center; + span { + color: @text-color-secondary; + font-size: 14px; + line-height: 22px; + } + h4 { + color: @heading-color; + font-size: 24px; + line-height: 32px; + } + } + .waterWaveCanvasWrapper { + transform: scale(0.5); + transform-origin: 0 0; + } +} diff --git a/Monitor/src/components/Charts/WaterWave/index.tsx b/Monitor/src/components/Charts/WaterWave/index.tsx new file mode 100644 index 00000000..64ff6735 --- /dev/null +++ b/Monitor/src/components/Charts/WaterWave/index.tsx @@ -0,0 +1,230 @@ +import React, { Component } from 'react'; +import autoHeight from '../autoHeight'; +import styles from './index.less'; + +/* eslint no-return-assign: 0 */ +/* eslint no-mixed-operators: 0 */ +// riddle: https://riddle.alibaba-inc.com/riddles/2d9a4b90 + +export interface IWaterWaveProps { + title: React.ReactNode; + color?: string; + height?: number; + percent: number; + style?: React.CSSProperties; +} + +class WaterWave extends Component { + state = { + radio: 1, + }; + + componentDidMount() { + this.renderChart(); + this.resize(); + window.addEventListener( + 'resize', + () => { + requestAnimationFrame(() => this.resize()); + }, + { passive: true } + ); + } + + componentDidUpdate(props: IWaterWaveProps) { + const { percent } = this.props; + if (props.percent !== percent) { + // 不加这个会造成绘制缓慢 + this.renderChart('update'); + } + } + + componentWillUnmount() { + cancelAnimationFrame(this.timer); + if (this.node) { + this.node.innerHTML = ''; + } + window.removeEventListener('resize', this.resize); + } + + resize = () => { + if (this.root) { + const { height = 1 } = this.props; + const { offsetWidth } = this.root.parentNode as HTMLElement; + this.setState({ + radio: offsetWidth < height ? offsetWidth / height : 1, + }); + } + }; + timer: number = 0; + renderChart(type?: string) { + const { percent, color = '#1890FF' } = this.props; + const data = percent / 100; + const self = this; + cancelAnimationFrame(this.timer); + + if (!this.node || (data !== 0 && !data)) { + return; + } + + const canvas = this.node; + const ctx = canvas.getContext('2d'); + if (!ctx) { + return; + } + const canvasWidth = canvas.width; + const canvasHeight = canvas.height; + const radius = canvasWidth / 2; + const lineWidth = 2; + const cR = radius - lineWidth; + + ctx.beginPath(); + ctx.lineWidth = lineWidth * 2; + + const axisLength = canvasWidth - lineWidth; + const unit = axisLength / 8; + const range = 0.2; // 振幅 + let currRange = range; + const xOffset = lineWidth; + let sp = 0; // 周期偏移量 + let currData = 0; + const waveupsp = 0.005; // 水波上涨速度 + + let arcStack: number[][] = []; + const bR = radius - lineWidth; + const circleOffset = -(Math.PI / 2); + let circleLock = true; + + for (let i = circleOffset; i < circleOffset + 2 * Math.PI; i += 1 / (8 * Math.PI)) { + arcStack.push([radius + bR * Math.cos(i), radius + bR * Math.sin(i)]); + } + + const cStartPoint = arcStack.shift() as number[]; + ctx.strokeStyle = color; + ctx.moveTo(cStartPoint[0], cStartPoint[1]); + + function drawSin() { + if (!ctx) { + return; + } + ctx.beginPath(); + ctx.save(); + + const sinStack = []; + for (let i = xOffset; i <= xOffset + axisLength; i += 20 / axisLength) { + const x = sp + (xOffset + i) / unit; + const y = Math.sin(x) * currRange; + const dx = i; + const dy = 2 * cR * (1 - currData) + (radius - cR) - unit * y; + + ctx.lineTo(dx, dy); + sinStack.push([dx, dy]); + } + + const startPoint = sinStack.shift() as number[]; + + ctx.lineTo(xOffset + axisLength, canvasHeight); + ctx.lineTo(xOffset, canvasHeight); + ctx.lineTo(startPoint[0], startPoint[1]); + + const gradient = ctx.createLinearGradient(0, 0, 0, canvasHeight); + gradient.addColorStop(0, '#ffffff'); + gradient.addColorStop(1, color); + ctx.fillStyle = gradient; + ctx.fill(); + ctx.restore(); + } + + function render() { + if (!ctx) { + return; + } + ctx.clearRect(0, 0, canvasWidth, canvasHeight); + if (circleLock && type !== 'update') { + if (arcStack.length) { + const temp = arcStack.shift() as number[]; + ctx.lineTo(temp[0], temp[1]); + ctx.stroke(); + } else { + circleLock = false; + ctx.lineTo(cStartPoint[0], cStartPoint[1]); + ctx.stroke(); + arcStack = []; + + ctx.globalCompositeOperation = 'destination-over'; + ctx.beginPath(); + ctx.lineWidth = lineWidth; + ctx.arc(radius, radius, bR, 0, 2 * Math.PI, true); + + ctx.beginPath(); + ctx.save(); + ctx.arc(radius, radius, radius - 3 * lineWidth, 0, 2 * Math.PI, true); + + ctx.restore(); + ctx.clip(); + ctx.fillStyle = color; + } + } else { + if (data >= 0.85) { + if (currRange > range / 4) { + const t = range * 0.01; + currRange -= t; + } + } else if (data <= 0.1) { + if (currRange < range * 1.5) { + const t = range * 0.01; + currRange += t; + } + } else { + if (currRange <= range) { + const t = range * 0.01; + currRange += t; + } + if (currRange >= range) { + const t = range * 0.01; + currRange -= t; + } + } + if (data - currData > 0) { + currData += waveupsp; + } + if (data - currData < 0) { + currData -= waveupsp; + } + + sp += 0.07; + drawSin(); + } + self.timer = requestAnimationFrame(render); + } + render(); + } + root: HTMLDivElement | undefined | null; + node: HTMLCanvasElement | undefined | null; + render() { + const { radio } = this.state; + const { percent, title, height = 1 } = this.props; + return ( +
(this.root = n)} + style={{ transform: `scale(${radio})` }} + > +
+ (this.node = n)} + width={height * 2} + height={height * 2} + /> +
+
+ {title && {title}} +

{percent}%

+
+
+ ); + } +} + +export default autoHeight()(WaterWave); diff --git a/Monitor/src/components/Charts/autoHeight.tsx b/Monitor/src/components/Charts/autoHeight.tsx new file mode 100644 index 00000000..aa255000 --- /dev/null +++ b/Monitor/src/components/Charts/autoHeight.tsx @@ -0,0 +1,75 @@ +import React from 'react'; + +export type IReactComponent

= + | React.StatelessComponent

+ | React.ComponentClass

+ | React.ClassicComponentClass

; + +function computeHeight(node: HTMLDivElement) { + node.style.height = '100%'; + const totalHeight = parseInt(getComputedStyle(node).height + '', 10); + const padding = + parseInt(getComputedStyle(node).paddingTop + '', 10) + + parseInt(getComputedStyle(node).paddingBottom + '', 10); + return totalHeight - padding; +} + +function getAutoHeight(n: HTMLDivElement) { + if (!n) { + return 0; + } + + let node = n; + + let height = computeHeight(node); + const parentNode = node.parentNode as HTMLDivElement; + if (parentNode) { + height = computeHeight(parentNode); + } + + return height; +} + +interface IAutoHeightProps { + height?: number; +} + +function autoHeight() { + return function

( + WrappedComponent: React.ComponentClass

| React.SFC

+ ): React.ComponentClass

{ + class AutoHeightComponent extends React.Component

{ + state = { + computedHeight: 0, + }; + root!: HTMLDivElement; + componentDidMount() { + const { height } = this.props; + if (!height) { + const h = getAutoHeight(this.root); + // eslint-disable-next-line + this.setState({ computedHeight: h }); + if (h < 1) { + const h = getAutoHeight(this.root); + this.setState({ computedHeight: h }); + } + } + } + handleRoot = (node: HTMLDivElement) => { + this.root = node; + }; + render() { + const { height } = this.props; + const { computedHeight } = this.state; + const h = height || computedHeight; + return ( +

+ {h > 0 && } +
+ ); + } + } + return AutoHeightComponent; + }; +} +export default autoHeight; diff --git a/Monitor/src/components/Charts/index.less b/Monitor/src/components/Charts/index.less new file mode 100644 index 00000000..190428bc --- /dev/null +++ b/Monitor/src/components/Charts/index.less @@ -0,0 +1,19 @@ +.miniChart { + position: relative; + width: 100%; + .chartContent { + position: absolute; + bottom: -28px; + width: 100%; + > div { + margin: 0 -5px; + overflow: hidden; + } + } + .chartLoading { + position: absolute; + top: 16px; + left: 50%; + margin-left: -7px; + } +} diff --git a/Monitor/src/components/Charts/index.tsx b/Monitor/src/components/Charts/index.tsx new file mode 100644 index 00000000..0df3ba59 --- /dev/null +++ b/Monitor/src/components/Charts/index.tsx @@ -0,0 +1,14 @@ +import Pie from './Pie'; +import Gauge from './Gauge'; +import WaterWave from './WaterWave'; +import TagCloud from './TagCloud'; +import MiniArea from './MiniArea'; +const Charts = { + Pie, + WaterWave, + Gauge, + MiniArea, + TagCloud, +}; + +export { Charts as default, Pie, WaterWave, Gauge, TagCloud, MiniArea }; diff --git a/Monitor/src/data.d.ts b/Monitor/src/data.d.ts new file mode 100644 index 00000000..939988dd --- /dev/null +++ b/Monitor/src/data.d.ts @@ -0,0 +1,5 @@ +export interface ITag { + name: string; + value: string; + type: string; +} diff --git a/Monitor/src/index.js b/Monitor/src/index.js deleted file mode 100644 index 35b18fb4..00000000 --- a/Monitor/src/index.js +++ /dev/null @@ -1,243 +0,0 @@ -import React, { PureComponent } from 'react'; -import { connect } from 'dva'; -import { formatMessage, FormattedMessage } from 'umi-plugin-react/locale'; -import { Row, Col, Card, Tooltip } from 'antd'; -import { NumberInfo, Charts } from 'ant-design-pro'; -import CountDown from 'ant-design-pro/lib/CountDown'; -import numeral from 'numeral'; - -import ActiveChart from './components/ActiveChart'; -import styles from './style.less'; - -const { Pie, WaterWave, Gauge, TagCloud } = Charts; - -const targetTime = new Date().getTime() + 3900000; - -@connect(({ BLOCK_NAME_CAMEL_CASE, loading }) => ({ - BLOCK_NAME_CAMEL_CASE, - loading: loading.models.monitor, -})) -class PAGE_NAME_UPPER_CAMEL_CASE extends PureComponent { - componentDidMount() { - const { dispatch } = this.props; - dispatch({ - type: 'BLOCK_NAME_CAMEL_CASE/fetchTags', - }); - } - - render() { - const { BLOCK_NAME_CAMEL_CASE, loading } = this.props; - const { tags } = BLOCK_NAME_CAMEL_CASE; - - return ( - - - - - } - bordered={false} - > - - - - } - suffix="元" - total={numeral(124543233).format('0,0')} - /> - - - - } - total="92%" - /> - - - - } - total={} - /> - - - - } - suffix="元" - total={numeral(234).format('0,0')} - /> - - -
- - } - > - map - -
-
- - - - } - style={{ marginBottom: 24 }} - bordered={false} - > - - - - } - style={{ marginBottom: 24 }} - bodyStyle={{ textAlign: 'center' }} - bordered={false} - > - - - -
- - - - } - bordered={false} - className={styles.pieCard} - > - - - - } - total="28%" - height={128} - lineWidth={2} - /> - - - - } - total="22%" - height={128} - lineWidth={2} - /> - - - - } - total="32%" - height={128} - lineWidth={2} - /> - - - - - - - } - loading={loading} - bordered={false} - bodyStyle={{ overflow: 'hidden' }} - > - - - - - - } - bodyStyle={{ textAlign: 'center', fontSize: 0 }} - bordered={false} - > - - } - percent={34} - /> - - - -
- ); - } -} - -export default PAGE_NAME_UPPER_CAMEL_CASE; diff --git a/Monitor/src/index.tsx b/Monitor/src/index.tsx new file mode 100644 index 00000000..37d22c54 --- /dev/null +++ b/Monitor/src/index.tsx @@ -0,0 +1,273 @@ +import React, { Component } from 'react'; +import { connect } from 'dva'; +import { formatMessage, FormattedMessage } from 'umi-plugin-react/locale'; +import { Row, Col, Card, Statistic, Tooltip } from 'antd'; +import numeral from 'numeral'; +import { Dispatch } from 'redux'; +import { IStateType } from './model'; +import ActiveChart from './components/ActiveChart'; +import styles from './style.less'; +import Charts from './components/Charts'; +import { GridContent } from '@ant-design/pro-layout'; + +const { Countdown } = Statistic; + +const { Pie, WaterWave, Gauge, TagCloud } = Charts; + +const targetTime = new Date().getTime() + 3900000; + +interface PAGE_NAME_UPPER_CAMEL_CASEProps { + BLOCK_NAME_CAMEL_CASE: IStateType; + dispatch: Dispatch; + loading: boolean; +} + +@connect( + ({ + BLOCK_NAME_CAMEL_CASE, + loading, + }: { + BLOCK_NAME_CAMEL_CASE: IStateType; + loading: { + models: { [key: string]: boolean }; + }; + }) => ({ + BLOCK_NAME_CAMEL_CASE, + loading: loading.models.monitor, + }) +) +class PAGE_NAME_UPPER_CAMEL_CASE extends Component { + componentDidMount() { + const { dispatch } = this.props; + dispatch({ + type: 'BLOCK_NAME_CAMEL_CASE/fetchTags', + }); + } + + render() { + const { BLOCK_NAME_CAMEL_CASE, loading } = this.props; + const { tags } = BLOCK_NAME_CAMEL_CASE; + return ( + + + + + + } + bordered={false} + > + + + + } + suffix="元" + value={numeral(124543233).format('0,0')} + /> + + + + } + value="92%" + /> + + + + } + > + + + + + + } + suffix="元" + value={numeral(234).format('0,0')} + /> + + +
+ + } + > + map + +
+
+ + + + } + style={{ marginBottom: 24 }} + bordered={false} + > + + + + } + style={{ marginBottom: 24 }} + bodyStyle={{ textAlign: 'center' }} + bordered={false} + > + + + +
+ + + + } + bordered={false} + className={styles.pieCard} + > + + + + } + total="28%" + height={128} + lineWidth={2} + /> + + + + } + total="22%" + height={128} + lineWidth={2} + /> + + + + } + total="32%" + height={128} + lineWidth={2} + /> + + + + + + + } + loading={loading} + bordered={false} + bodyStyle={{ overflow: 'hidden' }} + > + + + + + + } + bodyStyle={{ textAlign: 'center', fontSize: 0 }} + bordered={false} + > + + } + percent={34} + /> + + + +
+
+ ); + } +} + +export default PAGE_NAME_UPPER_CAMEL_CASE; diff --git a/Monitor/src/locales/en-US.js b/Monitor/src/locales/en-US.ts similarity index 100% rename from Monitor/src/locales/en-US.js rename to Monitor/src/locales/en-US.ts diff --git a/Monitor/src/locales/pt-BR.js b/Monitor/src/locales/pt-BR.ts similarity index 100% rename from Monitor/src/locales/pt-BR.js rename to Monitor/src/locales/pt-BR.ts diff --git a/Monitor/src/locales/zh-CN.js b/Monitor/src/locales/zh-CN.ts similarity index 100% rename from Monitor/src/locales/zh-CN.js rename to Monitor/src/locales/zh-CN.ts diff --git a/Monitor/src/locales/zh-TW.js b/Monitor/src/locales/zh-TW.ts similarity index 100% rename from Monitor/src/locales/zh-TW.js rename to Monitor/src/locales/zh-TW.ts diff --git a/Monitor/src/model.js b/Monitor/src/model.js deleted file mode 100644 index 82d3b694..00000000 --- a/Monitor/src/model.js +++ /dev/null @@ -1,28 +0,0 @@ -import { queryTags } from './service'; - -export default { - namespace: 'BLOCK_NAME_CAMEL_CASE', - - state: { - tags: [], - }, - - effects: { - *fetchTags(_, { call, put }) { - const response = yield call(queryTags); - yield put({ - type: 'saveTags', - payload: response.list, - }); - }, - }, - - reducers: { - saveTags(state, action) { - return { - ...state, - tags: action.payload, - }; - }, - }, -}; diff --git a/Monitor/src/model.ts b/Monitor/src/model.ts new file mode 100644 index 00000000..8b70fe23 --- /dev/null +++ b/Monitor/src/model.ts @@ -0,0 +1,54 @@ +import { queryTags } from './service'; +import { ITag } from './data'; +import { Reducer } from 'redux'; +import { EffectsCommandMap } from 'dva'; +import { AnyAction } from 'redux'; + +export interface IStateType { + tags: ITag[]; +} + +export type Effect = ( + action: AnyAction, + effects: EffectsCommandMap & { select: (func: (state: IStateType) => T) => T } +) => void; + +export interface ModelType { + namespace: string; + state: IStateType; + effects: { + fetchTags: Effect; + }; + reducers: { + saveTags: Reducer; + }; +} + +const Model: ModelType = { + namespace: 'BLOCK_NAME_CAMEL_CASE', + + state: { + tags: [], + }, + + effects: { + *fetchTags(_, { call, put }) { + const response = yield call(queryTags); + yield put({ + type: 'saveTags', + payload: response.list, + }); + }, + }, + + reducers: { + saveTags(state, action) { + return { + ...state, + tags: action.payload, + }; + }, + }, +}; + +export default Model; diff --git a/Monitor/src/service.js b/Monitor/src/service.tsx similarity index 100% rename from Monitor/src/service.js rename to Monitor/src/service.tsx diff --git a/ResultFail/package.json b/ResultFail/package.json index a524f7bd..7c448286 100644 --- a/ResultFail/package.json +++ b/ResultFail/package.json @@ -11,7 +11,6 @@ "url": "https://github.com/umijs/umi-blocks/ant-design-pro/resultfail" }, "dependencies": { - "@ant-design/pro-layout": "^4.0.5", "react": "^16.6.3", "antd": "^3.10.9", "ant-design-pro": "^2.1.1" diff --git a/package.json b/package.json index 74ad06bc..23519829 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "scripts": { - "dev": "cross-env PAGES_PATH='CardList/src' umi dev", + "dev": "cross-env PAGES_PATH='Monitor/src' umi dev", "lint:style": "stylelint \"src/**/*.less\" --syntax less", "lint": "eslint --ext .js src mock tests && npm run lint:style", "lint:fix": "eslint --fix --ext .js src mock tests && npm run lint:style", diff --git a/typings.d.ts b/typings.d.ts index 802d980b..6d2c56e7 100644 --- a/typings.d.ts +++ b/typings.d.ts @@ -14,3 +14,4 @@ declare var APP_TYPE: string; declare module 'react-fittext'; declare module '@antv/data-set'; +declare module 'mockjs'; -- GitLab