diff --git a/Analysis/package.json b/Analysis/package.json index 83afadcccb7ea8f558f95f41da48fcf1d5ebeb1a..ff98c471a699020fbafb3a1c0b17cf9d43c4fc49 100644 --- a/Analysis/package.json +++ b/Analysis/package.json @@ -2,27 +2,31 @@ "name": "@umi-block/analysis", "version": "0.0.1", "description": "Analysis", - "main": "src/index.js", - "scripts": { - "dev": "umi dev" - }, "repository": { "type": "git", "url": "https://github.com/umijs/umi-blocks/ant-design-pro/analysis" }, + "license": "ISC", + "main": "src/index.js", + "scripts": { + "dev": "umi dev" + }, "dependencies": { - "react": "^16.6.3", - "dva": "^2.4.0", + "@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", + "dva": "^2.4.0", "moment": "^2.22.2", - "umi-request": "^1.0.0", - "ant-design-pro": "^2.1.1", - "numeral": "^2.0.6" + "numeral": "^2.0.6", + "react": "^16.6.3", + "react-fittext": "^1.0.0", + "umi-request": "^1.0.0" }, "devDependencies": { "umi": "^2.6.9", - "umi-plugin-react": "^1.7.2", - "umi-plugin-block-dev": "^1.1.0" - }, - "license": "ISC" + "umi-plugin-block-dev": "^1.1.0", + "umi-plugin-react": "^1.7.2" + } } diff --git a/Analysis/src/_mock.js b/Analysis/src/_mock.ts similarity index 94% rename from Analysis/src/_mock.js rename to Analysis/src/_mock.ts index ed3d7e2b372a501ca95d69f00a74d0cc630426b2..4ad3addd5b30045eb9dbcfab78495595f4fb0dbf 100644 --- a/Analysis/src/_mock.js +++ b/Analysis/src/_mock.ts @@ -1,7 +1,8 @@ import moment from 'moment'; +import { IVisitData, IRadarData, IAnalysisData } from './data'; // mock data -const visitData = []; +const visitData: IVisitData[] = []; const beginDay = new Date().getTime(); const fakeY = [7, 5, 4, 2, 4, 7, 5, 6, 5, 9, 6, 3, 1, 5, 3, 6, 5]; @@ -158,7 +159,7 @@ const radarOriginData = [ }, ]; -const radarData = []; +const radarData: IRadarData[] = []; const radarTitleMap = { ref: '引用', koubei: '口碑', @@ -178,7 +179,7 @@ radarOriginData.forEach(item => { }); }); -const getFakeChartData = { +const getFakeChartData: IAnalysisData = { visitData, visitData2, salesData, diff --git a/Analysis/src/components/Charts/Bar/index.tsx b/Analysis/src/components/Charts/Bar/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7cf4b3debb25688c1a03910f986449c543469130 --- /dev/null +++ b/Analysis/src/components/Charts/Bar/index.tsx @@ -0,0 +1,131 @@ +import React, { Component } from 'react'; +import { Chart, Axis, Tooltip, Geom } from 'bizcharts'; +import Debounce from 'lodash-decorators/debounce'; +import Bind from 'lodash-decorators/bind'; +import autoHeight from '../autoHeight'; +import styles from '../index.less'; + +export interface IBarProps { + title: React.ReactNode; + color?: string; + padding?: [number, number, number, number]; + height?: number; + data: Array<{ + x: string; + y: number; + }>; + forceFit?: boolean; + autoLabel?: boolean; + style?: React.CSSProperties; +} + +class Bar extends Component< + IBarProps, + { + autoHideXLabels: boolean; + } +> { + state = { + autoHideXLabels: false, + }; + + componentDidMount() { + window.addEventListener('resize', this.resize, { passive: true }); + } + + componentWillUnmount() { + window.removeEventListener('resize', this.resize); + } + root: HTMLDivElement | undefined; + handleRoot = (n: HTMLDivElement) => { + this.root = n; + }; + node: HTMLDivElement | undefined; + handleRef = (n: HTMLDivElement) => { + this.node = n; + }; + + @Bind() + @Debounce(400) + resize() { + if (!this.node || !this.node.parentNode) { + return; + } + const canvasWidth = (this.node.parentNode as HTMLDivElement).clientWidth; + const { data = [], autoLabel = true } = this.props; + if (!autoLabel) { + return; + } + const minWidth = data.length * 30; + const { autoHideXLabels } = this.state; + + if (canvasWidth <= minWidth) { + if (!autoHideXLabels) { + this.setState({ + autoHideXLabels: true, + }); + } + } else if (autoHideXLabels) { + this.setState({ + autoHideXLabels: false, + }); + } + } + + render() { + const { + height = 1, + title, + forceFit = true, + data, + color = 'rgba(24, 144, 255, 0.85)', + padding, + } = this.props; + + const { autoHideXLabels } = this.state; + + const scale = { + x: { + type: 'cat', + }, + y: { + min: 0, + }, + }; + + const tooltip: [string, (...args: any[]) => { name?: string; value: string }] = [ + 'x*y', + (x: string, y: string) => ({ + name: x, + value: y, + }), + ]; + + return ( +
+
+ {title &&

{title}

} + + + + + + +
+
+ ); + } +} + +export default autoHeight()(Bar); diff --git a/Analysis/src/components/Charts/ChartCard/index.less b/Analysis/src/components/Charts/ChartCard/index.less new file mode 100644 index 0000000000000000000000000000000000000000..282f17d9cf32af486e1c13d8d55bec1a9e5076f1 --- /dev/null +++ b/Analysis/src/components/Charts/ChartCard/index.less @@ -0,0 +1,75 @@ +@import '~antd/lib/style/themes/default.less'; + +.chartCard { + position: relative; + .chartTop { + position: relative; + width: 100%; + overflow: hidden; + } + .chartTopMargin { + margin-bottom: 12px; + } + .chartTopHasMargin { + margin-bottom: 20px; + } + .metaWrap { + float: left; + } + .avatar { + position: relative; + top: 4px; + float: left; + margin-right: 20px; + img { + border-radius: 100%; + } + } + .meta { + height: 22px; + color: @text-color-secondary; + font-size: @font-size-base; + line-height: 22px; + } + .action { + position: absolute; + top: 4px; + right: 0; + line-height: 1; + cursor: pointer; + } + .total { + height: 38px; + margin-top: 4px; + margin-bottom: 0; + overflow: hidden; + color: @heading-color; + font-size: 30px; + line-height: 38px; + white-space: nowrap; + text-overflow: ellipsis; + word-break: break-all; + } + .content { + position: relative; + width: 100%; + margin-bottom: 12px; + } + .contentFixed { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + } + .footer { + margin-top: 8px; + padding-top: 9px; + border-top: 1px solid @border-color-split; + & > * { + position: relative; + } + } + .footerMargin { + margin-top: 20px; + } +} diff --git a/Analysis/src/components/Charts/ChartCard/index.tsx b/Analysis/src/components/Charts/ChartCard/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..11f5f2052c176f10269fb29efd312714c5957c3a --- /dev/null +++ b/Analysis/src/components/Charts/ChartCard/index.tsx @@ -0,0 +1,98 @@ +import React from 'react'; +import { Card } from 'antd'; +import classNames from 'classnames'; +import { CardProps } from 'antd/lib/card'; + +import styles from './index.less'; + +type totalType = () => React.ReactNode; + +const renderTotal = (total?: number | totalType | React.ReactNode) => { + if (!total) { + return; + } + let totalDom; + switch (typeof total) { + case 'undefined': + totalDom = null; + break; + case 'function': + totalDom =
{total()}
; + break; + default: + totalDom =
{total}
; + } + return totalDom; +}; + +export interface IChartCardProps extends CardProps { + title: React.ReactNode; + action?: React.ReactNode; + total?: React.ReactNode | number | (() => React.ReactNode | number); + footer?: React.ReactNode; + contentHeight?: number; + avatar?: React.ReactNode; + style?: React.CSSProperties; +} + +class ChartCard extends React.Component { + renderContent = () => { + const { contentHeight, title, avatar, action, total, footer, children, loading } = this.props; + if (loading) { + return false; + } + return ( +
+
+
{avatar}
+
+
+ {title} + {action} +
+ {renderTotal(total)} +
+
+ {children && ( +
+
{children}
+
+ )} + {footer && ( +
+ {footer} +
+ )} +
+ ); + }; + + render() { + const { + loading = false, + contentHeight, + title, + avatar, + action, + total, + footer, + children, + ...rest + } = this.props; + return ( + + {this.renderContent()} + + ); + } +} + +export default ChartCard; diff --git a/Analysis/src/components/Charts/Field/index.less b/Analysis/src/components/Charts/Field/index.less new file mode 100644 index 0000000000000000000000000000000000000000..4124471cb522bf18fb7963675ddeeb3dc217b9e7 --- /dev/null +++ b/Analysis/src/components/Charts/Field/index.less @@ -0,0 +1,17 @@ +@import '~antd/lib/style/themes/default.less'; + +.field { + margin: 0; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + .label, + .number { + font-size: @font-size-base; + line-height: 22px; + } + .number { + margin-left: 8px; + color: @heading-color; + } +} diff --git a/Analysis/src/components/Charts/Field/index.tsx b/Analysis/src/components/Charts/Field/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ee3c12454ead9e275a7ed71dba1a20c79b083cf8 --- /dev/null +++ b/Analysis/src/components/Charts/Field/index.tsx @@ -0,0 +1,18 @@ +import React from 'react'; + +import styles from './index.less'; + +export interface IFieldProps { + label: React.ReactNode; + value: React.ReactNode; + style?: React.CSSProperties; +} + +const Field: React.SFC = ({ label, value, ...rest }) => ( +
+ {label} + {value} +
+); + +export default Field; diff --git a/Analysis/src/components/Charts/Gauge/index.tsx b/Analysis/src/components/Charts/Gauge/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b5d33c60421e690358310fa418b909dbe2e30aca --- /dev/null +++ b/Analysis/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/Analysis/src/components/Charts/MiniArea/index.tsx b/Analysis/src/components/Charts/MiniArea/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5684aa51fb6c8cf3bbe5457c38a2dbe0168ef3c0 --- /dev/null +++ b/Analysis/src/components/Charts/MiniArea/index.tsx @@ -0,0 +1,133 @@ +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; +} + +export interface IMiniAreaProps { + color?: string; + height?: number; + borderColor?: string; + line?: boolean; + animate?: boolean; + xAxis?: IAxis; + forceFit?: boolean; + scale?: { x: any; y: any }; + yAxis?: IAxis; + 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/Analysis/src/components/Charts/MiniBar/index.tsx b/Analysis/src/components/Charts/MiniBar/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d0fe9d4c160ddac885cf1246d455f675ba1822be --- /dev/null +++ b/Analysis/src/components/Charts/MiniBar/index.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { Chart, Tooltip, Geom } from 'bizcharts'; +import autoHeight from '../autoHeight'; +import styles from '../index.less'; + +export interface IMiniBarProps { + color?: string; + height?: number; + data: Array<{ + x: number | string; + y: number; + }>; + forceFit?: boolean; + style?: React.CSSProperties; +} + +class MiniBar extends React.Component { + render() { + const { height = 0, forceFit = true, color = '#1890FF', data = [] } = this.props; + + const scale = { + x: { + type: 'cat', + }, + y: { + min: 0, + }, + }; + + const padding: [number, number, number, number] = [36, 5, 30, 5]; + + const tooltip: [string, (...args: any[]) => { name?: string; value: string }] = [ + 'x*y', + (x: string, y: string) => ({ + name: x, + value: y, + }), + ]; + + // for tooltip not to be hide + const chartHeight = height + 54; + + return ( +
+
+ + + + +
+
+ ); + } +} +export default autoHeight()(MiniBar); diff --git a/Analysis/src/components/Charts/MiniProgress/index.less b/Analysis/src/components/Charts/MiniProgress/index.less new file mode 100644 index 0000000000000000000000000000000000000000..e1e0b4fc5169615814efe60821f39dc3e1bc58b9 --- /dev/null +++ b/Analysis/src/components/Charts/MiniProgress/index.less @@ -0,0 +1,37 @@ +@import '~antd/lib/style/themes/default.less'; + +.miniProgress { + position: relative; + width: 100%; + padding: 5px 0; + .progressWrap { + position: relative; + background-color: @background-color-base; + } + .progress { + width: 0; + height: 100%; + background-color: @primary-color; + border-radius: 1px 0 0 1px; + transition: all 0.4s cubic-bezier(0.08, 0.82, 0.17, 1) 0s; + } + .target { + position: absolute; + top: 0; + bottom: 0; + z-index: 9; + width: 20px; + span { + position: absolute; + top: 0; + left: 0; + width: 2px; + height: 4px; + border-radius: 100px; + } + span:last-child { + top: auto; + bottom: 0; + } + } +} diff --git a/Analysis/src/components/Charts/MiniProgress/index.tsx b/Analysis/src/components/Charts/MiniProgress/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c0d1507dd4c5ff0d6d36f40897a910910df7a737 --- /dev/null +++ b/Analysis/src/components/Charts/MiniProgress/index.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { Tooltip } from 'antd'; +import styles from './index.less'; + +export interface IMiniProgressProps { + target: number; + targetLabel?: string; + color?: string; + strokeWidth?: number; + percent?: number; + style?: React.CSSProperties; +} + +const MiniProgress: React.SFC = ({ + targetLabel, + target, + color = 'rgb(19, 194, 194)', + strokeWidth, + percent, +}) => { + return ( +
+ +
+ + +
+
+
+
+
+
+ ); +}; + +export default MiniProgress; diff --git a/Analysis/src/components/Charts/Pie/index.less b/Analysis/src/components/Charts/Pie/index.less new file mode 100644 index 0000000000000000000000000000000000000000..fc961b41df8831d2da6e7cf987b89e3624133fbc --- /dev/null +++ b/Analysis/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/Analysis/src/components/Charts/Pie/index.tsx b/Analysis/src/components/Charts/Pie/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..cbf95909e82c7067896956f163681b1a3cb85f24 --- /dev/null +++ b/Analysis/src/components/Charts/Pie/index.tsx @@ -0,0 +1,295 @@ +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; +} + +/* eslint react/no-danger:0 */ + +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); + (this.resize as any).cancel(); + } + + getG2Instance = (chart: G2.Chart) => { + this.chart = chart; + requestAnimationFrame(() => { + this.getLegendData(); + this.resize(); + }); + }; + + root!: HTMLDivElement; + chart: G2.Chart | undefined; + // 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: Array) => { + /* 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, + }); + }; + + // 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!.parentNode! as HTMLDivElement).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 = 1, + forceFit = true, + percent, + inner = 0.75, + animate = true, + 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; + data = data || []; + selected = selected || true; + tooltip = tooltip || true; + + const scale = { + x: { + type: 'cat', + range: [0, 1], + }, + y: { + min: 0, + }, + }; + + if (percent || percent === 0) { + selected = false; + tooltip = false; + + 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: [number, number, number, number] = [12, 0, 12, 0]; + + 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/Analysis/src/components/Charts/Radar/index.less b/Analysis/src/components/Charts/Radar/index.less new file mode 100644 index 0000000000000000000000000000000000000000..437a71297e1bb83c6c9af788bfeab2dde89f25be --- /dev/null +++ b/Analysis/src/components/Charts/Radar/index.less @@ -0,0 +1,46 @@ +@import '~antd/lib/style/themes/default.less'; + +.radar { + .legend { + margin-top: 16px; + .legendItem { + position: relative; + color: @text-color-secondary; + line-height: 22px; + text-align: center; + cursor: pointer; + p { + margin: 0; + } + h6 { + margin-top: 4px; + margin-bottom: 0; + padding-left: 16px; + color: @heading-color; + font-size: 24px; + line-height: 32px; + } + &::after { + position: absolute; + top: 8px; + right: 0; + width: 1px; + height: 40px; + background-color: @border-color-split; + content: ''; + } + } + > :last-child .legendItem::after { + display: none; + } + .dot { + position: relative; + top: -1px; + display: inline-block; + width: 6px; + height: 6px; + margin-right: 6px; + border-radius: 6px; + } + } +} diff --git a/Analysis/src/components/Charts/Radar/index.tsx b/Analysis/src/components/Charts/Radar/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ab79250db217fc03c9f21a6b694c1a15edaf9813 --- /dev/null +++ b/Analysis/src/components/Charts/Radar/index.tsx @@ -0,0 +1,216 @@ +import React, { Component } from 'react'; +import { Chart, G2, Tooltip, Geom, Coord, Axis } from 'bizcharts'; +import { Row, Col } from 'antd'; +import autoHeight from '../autoHeight'; +import styles from './index.less'; + +export interface IRadarProps { + title?: React.ReactNode; + height?: number; + padding?: [number, number, number, number]; + hasLegend?: boolean; + data: Array<{ + name: string; + label: string; + value: string; + }>; + colors?: string[]; + animate?: boolean; + forceFit?: boolean; + tickCount?: number; + style?: React.CSSProperties; +} +interface IRadarState { + legendData: Array<{ + checked: boolean; + name: string; + color: string; + percent: number; + value: string; + }>; +} +/* eslint react/no-danger:0 */ +class Radar extends Component { + state: IRadarState = { + legendData: [], + }; + + componentDidMount() { + this.getLegendData(); + } + + componentDidUpdate(preProps: IRadarProps) { + const { data } = this.props; + if (data !== preProps.data) { + this.getLegendData(); + } + } + chart: G2.Chart | undefined; + + getG2Instance = (chart: G2.Chart) => { + this.chart = chart; + }; + + // 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-disable-next-line + const origins = item.map(t => t._origin); + const result = { + name: origins[0].name, + color: item[0].color, + checked: true, + value: origins.reduce((p, n) => p + n.value, 0), + }; + + return result; + }); + + this.setState({ + legendData, + }); + }; + node: HTMLDivElement | undefined; + + handleRef = (n: HTMLDivElement) => { + this.node = n; + }; + + handleLegendClick = ( + item: { + checked: boolean; + name: string; + }, + 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.name); + + if (this.chart) { + this.chart.filter('name', val => filteredLegendData.indexOf(val + '') > -1); + this.chart.repaint(); + } + + this.setState({ + legendData, + }); + }; + + render() { + const defaultColors = [ + '#1890FF', + '#FACC14', + '#2FC25B', + '#8543E0', + '#F04864', + '#13C2C2', + '#fa8c16', + '#a0d911', + ]; + + const { + data = [], + height = 0, + title, + hasLegend = false, + forceFit = true, + tickCount = 5, + padding = [35, 30, 16, 30] as [number, number, number, number], + animate = true, + colors = defaultColors, + } = this.props; + + const { legendData } = this.state; + + const scale = { + value: { + min: 0, + tickCount, + }, + }; + + const chartHeight = height - (hasLegend ? 80 : 22); + + return ( +
+ {title &&

{title}

} + + + + + + + + + {hasLegend && ( + + {legendData.map((item, i) => ( + this.handleLegendClick(item, i)} + > +
+

+ + {item.name} +

+
{item.value}
+
+ + ))} +
+ )} +
+ ); + } +} + +export default autoHeight()(Radar); diff --git a/Analysis/src/components/Charts/TagCloud/index.less b/Analysis/src/components/Charts/TagCloud/index.less new file mode 100644 index 0000000000000000000000000000000000000000..db8e4dabfdd3f1fd4566ff22f55962648c369c49 --- /dev/null +++ b/Analysis/src/components/Charts/TagCloud/index.less @@ -0,0 +1,6 @@ +.tagCloud { + overflow: hidden; + canvas { + transform-origin: 0 0; + } +} diff --git a/Analysis/src/components/Charts/TagCloud/index.tsx b/Analysis/src/components/Charts/TagCloud/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..cc2fc394c13a808db13f742f9f7e15c02acd6835 --- /dev/null +++ b/Analysis/src/components/Charts/TagCloud/index.tsx @@ -0,0 +1,206 @@ +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: number; + }>; + 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/Analysis/src/components/Charts/TimelineChart/index.less b/Analysis/src/components/Charts/TimelineChart/index.less new file mode 100644 index 0000000000000000000000000000000000000000..1751975692135769eebdcaf89ffafcf6b3037cb8 --- /dev/null +++ b/Analysis/src/components/Charts/TimelineChart/index.less @@ -0,0 +1,3 @@ +.timelineChart { + background: #fff; +} diff --git a/Analysis/src/components/Charts/TimelineChart/index.tsx b/Analysis/src/components/Charts/TimelineChart/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..89eb9fc892ee0964586019fd8c43e98145749de6 --- /dev/null +++ b/Analysis/src/components/Charts/TimelineChart/index.tsx @@ -0,0 +1,133 @@ +import React from 'react'; +import { Chart, Tooltip, Geom, Legend, Axis } from 'bizcharts'; +import DataSet from '@antv/data-set'; +import Slider from 'bizcharts-plugin-slider'; +import autoHeight from '../autoHeight'; +import styles from './index.less'; + +export interface ITimelineChartProps { + data: Array<{ + x: number; + y1: number; + y2: number; + }>; + title?: string; + titleMap: { y1: string; y2: string }; + padding?: [number, number, number, number]; + height?: number; + style?: React.CSSProperties; + borderWidth?: number; +} + +class TimelineChart extends React.Component { + render() { + const { + title, + height = 400, + padding = [60, 20, 40, 40] as [number, number, number, number], + titleMap = { + y1: 'y1', + y2: 'y2', + }, + borderWidth = 2, + data: sourceData, + } = this.props; + + const data = Array.isArray(sourceData) ? sourceData : [{ x: 0, y1: 0, y2: 0 }]; + + data.sort((a, b) => a.x - b.x); + + let max; + if (data[0] && data[0].y1 && data[0].y2) { + max = Math.max( + [...data].sort((a, b) => b.y1 - a.y1)[0].y1, + [...data].sort((a, b) => b.y2 - a.y2)[0].y2 + ); + } + + const ds = new DataSet({ + state: { + start: data[0].x, + end: data[data.length - 1].x, + }, + }); + + const dv = ds.createView(); + dv.source(data) + .transform({ + type: 'filter', + callback: (obj: { x: string }) => { + const date = obj.x; + return date <= ds.state.end && date >= ds.state.start; + }, + }) + .transform({ + type: 'map', + callback(row: { y1: string; y2: string }) { + const newRow = { ...row }; + newRow[titleMap.y1] = row.y1; + newRow[titleMap.y2] = row.y2; + return newRow; + }, + }) + .transform({ + type: 'fold', + fields: [titleMap.y1, titleMap.y2], // 展开字段集 + key: 'key', // key字段 + value: 'value', // value字段 + }); + + const timeScale = { + type: 'time', + tickInterval: 60 * 60 * 1000, + mask: 'HH:mm', + range: [0, 1], + }; + + const cols = { + x: timeScale, + value: { + max, + min: 0, + }, + }; + + const SliderGen = () => ( + { + ds.setState('start', startValue); + ds.setState('end', endValue); + }} + /> + ); + + return ( +
+
+ {title &&

{title}

} + + + + + + +
+ +
+
+
+ ); + } +} + +export default autoHeight()(TimelineChart); diff --git a/Analysis/src/components/Charts/WaterWave/index.less b/Analysis/src/components/Charts/WaterWave/index.less new file mode 100644 index 0000000000000000000000000000000000000000..2e75f21464300dd1d443329943b363b16fee1e97 --- /dev/null +++ b/Analysis/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/Analysis/src/components/Charts/WaterWave/index.tsx b/Analysis/src/components/Charts/WaterWave/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..64ff67359fd737e2279fdf7b5d32a662a50cd817 --- /dev/null +++ b/Analysis/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/Analysis/src/components/Charts/autoHeight.tsx b/Analysis/src/components/Charts/autoHeight.tsx new file mode 100644 index 0000000000000000000000000000000000000000..310dac7ccbcbd7457585fdbd5d8680938f6e7f4b --- /dev/null +++ b/Analysis/src/components/Charts/autoHeight.tsx @@ -0,0 +1,80 @@ +/* eslint eqeqeq: 0 */ +import React from 'react'; + +export type IReactComponent

= + | React.StatelessComponent

+ | React.ComponentClass

+ | React.ClassicComponentClass

; + +function computeHeight(node: HTMLDivElement) { + 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); + + while (!height) { + const parentNode = node.parentNode as HTMLDivElement; + if (parentNode) { + height = computeHeight(parentNode); + } else { + break; + } + } + + 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/Analysis/src/components/Charts/bizcharts.d.ts b/Analysis/src/components/Charts/bizcharts.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..0815ffeeffcacd0ac9710977ab3d4419d078491c --- /dev/null +++ b/Analysis/src/components/Charts/bizcharts.d.ts @@ -0,0 +1,3 @@ +import * as BizChart from 'bizcharts'; + +export = BizChart; diff --git a/Analysis/src/components/Charts/bizcharts.tsx b/Analysis/src/components/Charts/bizcharts.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e08db8d6d2dca240451bdf6ab8a30be077a3fd9d --- /dev/null +++ b/Analysis/src/components/Charts/bizcharts.tsx @@ -0,0 +1,3 @@ +import * as BizChart from 'bizcharts'; + +export default BizChart; diff --git a/Analysis/src/components/Charts/index.less b/Analysis/src/components/Charts/index.less new file mode 100644 index 0000000000000000000000000000000000000000..190428bc80d7cd7f6f22d51fd48fa37b2d44eb10 --- /dev/null +++ b/Analysis/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/Analysis/src/components/Charts/index.tsx b/Analysis/src/components/Charts/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f025299399ac03e4e1d2dfabc4b2fb4ab2de89e1 --- /dev/null +++ b/Analysis/src/components/Charts/index.tsx @@ -0,0 +1,48 @@ +import numeral from 'numeral'; +import ChartCard from './ChartCard'; +import Field from './Field'; +import Bar from './Bar'; +import Pie from './Pie'; +import Radar from './Radar'; +import Gauge from './Gauge'; +import MiniArea from './MiniArea'; +import MiniBar from './MiniBar'; +import MiniProgress from './MiniProgress'; +import WaterWave from './WaterWave'; +import TagCloud from './TagCloud'; +import TimelineChart from './TimelineChart'; + +const yuan = (val: number | string) => `¥ ${numeral(val).format('0,0')}`; + +const Charts = { + yuan, + Bar, + Pie, + Gauge, + Radar, + MiniBar, + MiniArea, + MiniProgress, + ChartCard, + Field, + WaterWave, + TagCloud, + TimelineChart, +}; + +export { + Charts as default, + yuan, + Bar, + Pie, + Gauge, + Radar, + MiniBar, + MiniArea, + MiniProgress, + ChartCard, + Field, + WaterWave, + TagCloud, + TimelineChart, +}; diff --git a/Analysis/src/components/IntroduceRow.js b/Analysis/src/components/IntroduceRow.js deleted file mode 100755 index 720c40f89c48326ac96e06c7be4ac8398db6de10..0000000000000000000000000000000000000000 --- a/Analysis/src/components/IntroduceRow.js +++ /dev/null @@ -1,159 +0,0 @@ -import React, { memo } from 'react'; -import { Row, Col, Icon, Tooltip } from 'antd'; -import { FormattedMessage } from 'umi-plugin-react/locale'; -import { Charts, Trend } from 'ant-design-pro'; -import numeral from 'numeral'; -import styles from '../style.less'; -import Yuan from '../utils/Yuan'; - -const { ChartCard, MiniArea, MiniBar, MiniProgress, Field } = Charts; - -const topColResponsiveProps = { - xs: 24, - sm: 12, - md: 12, - lg: 12, - xl: 6, - style: { marginBottom: 24 }, -}; - -const IntroduceRow = memo(({ loading, visitData }) => ( - - - - } - action={ - - } - > - - - } - loading={loading} - total={() => 126560} - footer={ - - } - value={`¥${numeral(12423).format('0,0')}`} - /> - } - contentHeight={46} - > - - - 12% - - - - 11% - - - - - - } - action={ - - } - > - - - } - total={numeral(8846).format('0,0')} - footer={ - - } - value={numeral(1234).format('0,0')} - /> - } - contentHeight={46} - > - - - - - } - action={ - - } - > - - - } - total={numeral(6560).format('0,0')} - footer={ - - } - value="60%" - /> - } - contentHeight={46} - > - - - - - - } - action={ - - } - > - - - } - total="78%" - footer={ -
- - - 12% - - - - 11% - -
- } - contentHeight={46} - > - -
- -
-)); - -export default IntroduceRow; diff --git a/Analysis/src/components/IntroduceRow.tsx b/Analysis/src/components/IntroduceRow.tsx new file mode 100755 index 0000000000000000000000000000000000000000..61a181f9a1c842158c1eadfe58b4dd5f46fb6572 --- /dev/null +++ b/Analysis/src/components/IntroduceRow.tsx @@ -0,0 +1,163 @@ +import React from 'react'; +import { Row, Col, Icon, Tooltip } from 'antd'; +import { FormattedMessage } from 'umi-plugin-react/locale'; +import Charts from './Charts'; +import numeral from 'numeral'; +import styles from '../style.less'; +import Yuan from '../utils/Yuan'; +import Trend from './Trend'; +import { IVisitData } from '../data.d'; +const { ChartCard, MiniArea, MiniBar, MiniProgress, Field } = Charts; + +const topColResponsiveProps = { + xs: 24, + sm: 12, + md: 12, + lg: 12, + xl: 6, + style: { marginBottom: 24 }, +}; + +const IntroduceRow = ({ loading, visitData }: { loading: boolean; visitData: IVisitData[] }) => { + return ( + + + + } + action={ + + } + > + + + } + loading={loading} + total={() => 126560} + footer={ + + } + value={`¥${numeral(12423).format('0,0')}`} + /> + } + contentHeight={46} + > + + + 12% + + + + 11% + + + + + {/* + } + action={ + + } + > + + + } + total={numeral(8846).format('0,0')} + footer={ + + } + value={numeral(1234).format('0,0')} + /> + } + contentHeight={46} + > + + + */} + + {/* + } + action={ + + } + > + + + } + total={numeral(6560).format('0,0')} + footer={ + + } + value="60%" + /> + } + contentHeight={46} + > + + + */} + + + } + action={ + + } + > + + + } + total="78%" + footer={ +
+ + + 12% + + + + 11% + +
+ } + contentHeight={46} + > + +
+ +
+ ); +}; + +export default IntroduceRow; diff --git a/Analysis/src/components/NumberInfo/index.less b/Analysis/src/components/NumberInfo/index.less new file mode 100644 index 0000000000000000000000000000000000000000..4a77288cc29d4bc24aa9ee660461bd44cb049897 --- /dev/null +++ b/Analysis/src/components/NumberInfo/index.less @@ -0,0 +1,68 @@ +@import '~antd/lib/style/themes/default.less'; + +.numberInfo { + .suffix { + margin-left: 4px; + color: @text-color; + font-size: 16px; + font-style: normal; + } + .numberInfoTitle { + margin-bottom: 16px; + color: @text-color; + font-size: @font-size-lg; + transition: all 0.3s; + } + .numberInfoSubTitle { + height: 22px; + overflow: hidden; + color: @text-color-secondary; + font-size: @font-size-base; + line-height: 22px; + white-space: nowrap; + text-overflow: ellipsis; + word-break: break-all; + } + .numberInfoValue { + margin-top: 4px; + overflow: hidden; + font-size: 0; + white-space: nowrap; + text-overflow: ellipsis; + word-break: break-all; + & > span { + display: inline-block; + height: 32px; + margin-right: 32px; + color: @heading-color; + font-size: 24px; + line-height: 32px; + } + .subTotal { + margin-right: 0; + color: @text-color-secondary; + font-size: @font-size-lg; + vertical-align: top; + i { + margin-left: 4px; + font-size: 12px; + transform: scale(0.82); + } + :global { + .anticon-caret-up { + color: @red-6; + } + .anticon-caret-down { + color: @green-6; + } + } + } + } +} +.numberInfolight { + .numberInfoValue { + & > span { + color: @text-color; + } + } +} diff --git a/Analysis/src/components/NumberInfo/index.tsx b/Analysis/src/components/NumberInfo/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a8df6e6d94efbd2dc579d315b28ba64cb4d20859 --- /dev/null +++ b/Analysis/src/components/NumberInfo/index.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { Icon } from 'antd'; +import classNames from 'classnames'; +import styles from './index.less'; +export interface NumberInfoProps { + title?: React.ReactNode | string; + subTitle?: React.ReactNode | string; + total?: React.ReactNode | string; + status?: 'up' | 'down'; + theme?: string; + gap?: number; + subTotal?: number; + suffix?: string; + style?: React.CSSProperties; +} +const NumberInfo: React.SFC = ({ + theme, + title, + subTitle, + total, + subTotal, + status, + suffix, + gap, + ...rest +}) => ( +
+ {title && ( +
+ {title} +
+ )} + {subTitle && ( +
+ {subTitle} +
+ )} +
+ + {total} + {suffix && {suffix}} + + {(status || subTotal) && ( + + {subTotal} + {status && } + + )} +
+
+); + +export default NumberInfo; diff --git a/Analysis/src/components/OfflineData.js b/Analysis/src/components/OfflineData.js deleted file mode 100755 index bac3d62d050aea7d3ea9f985ac079d8b2947ac19..0000000000000000000000000000000000000000 --- a/Analysis/src/components/OfflineData.js +++ /dev/null @@ -1,69 +0,0 @@ -import React, { memo } from 'react'; -import { Card, Tabs, Row, Col } from 'antd'; -import { formatMessage, FormattedMessage } from 'umi-plugin-react/locale'; -import { Charts, NumberInfo } from 'ant-design-pro'; -import styles from '../style.less'; - -const { TimelineChart, Pie } = Charts; - -const CustomTab = ({ data, currentTabKey: currentKey }) => ( - - - - } - gap={2} - total={`${data.cvr * 100}%`} - theme={currentKey !== data.name && 'light'} - /> - - - - - -); - -const { TabPane } = Tabs; - -const OfflineData = memo( - ({ activeKey, loading, offlineData, offlineChartData, handleTabChange }) => ( - - - {offlineData.map(shop => ( - } key={shop.name}> -
- -
-
- ))} -
-
- ) -); - -export default OfflineData; diff --git a/Analysis/src/components/OfflineData.tsx b/Analysis/src/components/OfflineData.tsx new file mode 100755 index 0000000000000000000000000000000000000000..a2f5c4755b6a0adb4cccdf23edf76ac42175631b --- /dev/null +++ b/Analysis/src/components/OfflineData.tsx @@ -0,0 +1,80 @@ +import React from 'react'; +import { Card, Tabs, Row, Col } from 'antd'; +import { formatMessage, FormattedMessage } from 'umi-plugin-react/locale'; +import Charts from './Charts'; +import styles from '../style.less'; +import NumberInfo from './NumberInfo'; +import { IOfflineData, IOfflineChartData } from '../data'; +const { TimelineChart, Pie } = Charts; + +const CustomTab = ({ + data, + currentTabKey: currentKey, +}: { + data: IOfflineData; + currentTabKey: string; +}) => ( + + + + } + gap={2} + total={`${data.cvr * 100}%`} + theme={currentKey !== data.name ? 'light' : undefined} + /> + + + + + +); + +const { TabPane } = Tabs; + +const OfflineData = ({ + activeKey, + loading, + offlineData, + offlineChartData, + handleTabChange, +}: { + activeKey: string; + loading: boolean; + offlineData: IOfflineData[]; + offlineChartData: IOfflineChartData[]; + handleTabChange: (activeKey: string) => void; +}) => ( + + + {offlineData.map(shop => ( + } key={shop.name}> +
+ +
+
+ ))} +
+
+); + +export default OfflineData; diff --git a/Analysis/src/components/PageLoading/index.js b/Analysis/src/components/PageLoading/index.tsx similarity index 100% rename from Analysis/src/components/PageLoading/index.js rename to Analysis/src/components/PageLoading/index.tsx diff --git a/Analysis/src/components/ProportionSales.js b/Analysis/src/components/ProportionSales.js deleted file mode 100755 index 6c5a89f69afd3d5e79d3526024b1c9a10acc4170..0000000000000000000000000000000000000000 --- a/Analysis/src/components/ProportionSales.js +++ /dev/null @@ -1,65 +0,0 @@ -import React, { memo } from 'react'; -import { Card, Radio } from 'antd'; -import { Charts } from 'ant-design-pro'; -import { FormattedMessage } from 'umi-plugin-react/locale'; -import styles from '../style.less'; -import Yuan from '../utils/Yuan'; - -const { Pie } = Charts; - -const ProportionSales = memo( - ({ dropdownGroup, salesType, loading, salesPieData, handleChangeSalesType }) => ( - - } - bodyStyle={{ padding: 24 }} - extra={ -
- {dropdownGroup} -
- - - - - - - - - - - -
-
- } - style={{ marginTop: 24 }} - > -
-

- -

- } - total={() => {salesPieData.reduce((pre, now) => now.y + pre, 0)}} - data={salesPieData} - valueFormat={value => {value}} - height={248} - lineWidth={4} - /> -
-
- ) -); - -export default ProportionSales; diff --git a/Analysis/src/components/ProportionSales.tsx b/Analysis/src/components/ProportionSales.tsx new file mode 100755 index 0000000000000000000000000000000000000000..29c7cf45e7ebb0351cb70137c74e2f970fc24e42 --- /dev/null +++ b/Analysis/src/components/ProportionSales.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { Card, Radio } from 'antd'; +import Charts from './Charts'; +import { FormattedMessage } from 'umi-plugin-react/locale'; +import styles from '../style.less'; +import Yuan from '../utils/Yuan'; +import { RadioChangeEvent } from 'antd/lib/radio'; +import { ISalesData } from '../data'; + +const { Pie } = Charts; + +const ProportionSales = ({ + dropdownGroup, + salesType, + loading, + salesPieData, + handleChangeSalesType, +}: { + loading: boolean; + dropdownGroup: React.ReactNode; + salesType: 'all' | 'online' | 'stores'; + salesPieData: ISalesData[]; + handleChangeSalesType?: (e: RadioChangeEvent) => void; +}) => ( + + } + bodyStyle={{ padding: 24 }} + extra={ +
+ {dropdownGroup} +
+ + + + + + + + + + + +
+
+ } + style={{ marginTop: 24 }} + > +
+

+ +

+ } + total={() => {salesPieData.reduce((pre, now) => now.y + pre, 0)}} + data={salesPieData} + valueFormat={value => {value}} + height={248} + lineWidth={4} + /> +
+
+); + +export default ProportionSales; diff --git a/Analysis/src/components/SalesCard.js b/Analysis/src/components/SalesCard.js deleted file mode 100755 index 0121e794b3a3aff855fec5dcc5a98dd04587302f..0000000000000000000000000000000000000000 --- a/Analysis/src/components/SalesCard.js +++ /dev/null @@ -1,152 +0,0 @@ -import React, { memo } from 'react'; -import { Row, Col, Card, Tabs, DatePicker } from 'antd'; -import { FormattedMessage, formatMessage } from 'umi-plugin-react/locale'; -import numeral from 'numeral'; -import { Charts } from 'ant-design-pro'; -import styles from '../style.less'; - -const { Bar } = Charts; - -const { RangePicker } = DatePicker; -const { TabPane } = Tabs; - -const rankingListData = []; -for (let i = 0; i < 7; i += 1) { - rankingListData.push({ - title: formatMessage({ id: 'BLOCK_NAME.analysis.test' }, { no: i }), - total: 323234, - }); -} - -const SalesCard = memo( - ({ rangePickerValue, salesData, isActive, handleRangePickerChange, loading, selectDate }) => ( - - - } - size="large" - tabBarStyle={{ marginBottom: 24 }} - > - } - key="sales" - > - - -
- - } - data={salesData} - /> -
- - -
-

- -

-
    - {rankingListData.map((item, i) => ( -
  • - - {i + 1} - - - {item.title} - - - {numeral(item.total).format('0,0')} - -
  • - ))} -
-
- -
-
- } - key="views" - > - - -
- - } - data={salesData} - /> -
- - -
-

- -

-
    - {rankingListData.map((item, i) => ( -
  • - - {i + 1} - - - {item.title} - - {numeral(item.total).format('0,0')} -
  • - ))} -
-
- -
-
- -
- - ) -); - -export default SalesCard; diff --git a/Analysis/src/components/SalesCard.tsx b/Analysis/src/components/SalesCard.tsx new file mode 100755 index 0000000000000000000000000000000000000000..20520f7f51888ca327c559820c6427dbe5776401 --- /dev/null +++ b/Analysis/src/components/SalesCard.tsx @@ -0,0 +1,162 @@ +import React from 'react'; +import { Row, Col, Card, Tabs, DatePicker } from 'antd'; +import { FormattedMessage, formatMessage } from 'umi-plugin-react/locale'; +import numeral from 'numeral'; +import Charts from './Charts'; +import { RangePickerValue } from 'antd/lib/date-picker/interface'; +import { ISalesData } from '../data'; +import styles from '../style.less'; + +const { Bar } = Charts; + +const { RangePicker } = DatePicker; +const { TabPane } = Tabs; + +const rankingListData: { title: string; total: number }[] = []; +for (let i = 0; i < 7; i += 1) { + rankingListData.push({ + title: formatMessage({ id: 'BLOCK_NAME.analysis.test' }, { no: i }), + total: 323234, + }); +} + +const SalesCard = ({ + rangePickerValue, + salesData, + isActive, + handleRangePickerChange, + loading, + selectDate, +}: { + rangePickerValue: RangePickerValue; + isActive: (key: 'today' | 'week' | 'month' | 'year') => string; + salesData: ISalesData[]; + loading: boolean; + handleRangePickerChange: (dates: RangePickerValue, dateStrings: [string, string]) => void; + selectDate: (key: 'today' | 'week' | 'month' | 'year') => void; +}) => ( + + + } + size="large" + tabBarStyle={{ marginBottom: 24 }} + > + } + key="sales" + > + + +
+ + } + data={salesData} + /> +
+ + +
+

+ +

+
    + {rankingListData.map((item, i) => ( +
  • + + {i + 1} + + + {item.title} + + + {numeral(item.total).format('0,0')} + +
  • + ))} +
+
+ +
+
+ } + key="views" + > + + +
+ + } + data={salesData} + /> +
+ + +
+

+ +

+
    + {rankingListData.map((item, i) => ( +
  • + + {i + 1} + + + {item.title} + + {numeral(item.total).format('0,0')} +
  • + ))} +
+
+ +
+
+ + +
+); + +export default SalesCard; diff --git a/Analysis/src/components/TopSearch.js b/Analysis/src/components/TopSearch.tsx similarity index 81% rename from Analysis/src/components/TopSearch.js rename to Analysis/src/components/TopSearch.tsx index 4e19176cb9103b6e19271952ed37efe7bf08bf8f..aab57b0fbbbced0b6c19963bd71877bd6c39b66c 100755 --- a/Analysis/src/components/TopSearch.js +++ b/Analysis/src/components/TopSearch.tsx @@ -1,9 +1,12 @@ -import React, { memo } from 'react'; +import React from 'react'; import { Row, Col, Table, Tooltip, Card, Icon } from 'antd'; import { FormattedMessage } from 'umi-plugin-react/locale'; -import { Trend, NumberInfo, Charts } from 'ant-design-pro'; +import Charts from './Charts'; +import Trend from './Trend'; +import NumberInfo from './NumberInfo'; import numeral from 'numeral'; import styles from '../style.less'; +import { ISearchData, IVisitData2 } from '../data'; const { MiniArea } = Charts; @@ -19,30 +22,39 @@ const columns = [ ), dataIndex: 'keyword', key: 'keyword', - render: text => {text}, + render: (text: React.ReactNode) => {text}, }, { title: , dataIndex: 'count', key: 'count', - sorter: (a, b) => a.count - b.count, + sorter: (a: { count: number }, b: { count: number }) => a.count - b.count, className: styles.alignRight, }, { title: , dataIndex: 'range', key: 'range', - sorter: (a, b) => a.range - b.range, - render: (text, record) => ( + sorter: (a: { range: number }, b: { range: number }) => a.range - b.range, + render: (text: React.ReactNode, record: { status: number }) => ( {text}% ), - align: 'right', }, ]; -const TopSearch = memo(({ loading, visitData2, searchData, dropdownGroup }) => ( +const TopSearch = ({ + loading, + visitData2, + searchData, + dropdownGroup, +}: { + loading: boolean; + visitData2: IVisitData2[]; + dropdownGroup: React.ReactNode; + searchData: ISearchData[]; +}) => ( ( - rowKey={record => record.index} size="small" columns={columns} @@ -116,6 +128,6 @@ const TopSearch = memo(({ loading, visitData2, searchData, dropdownGroup }) => ( }} /> -)); +); export default TopSearch; diff --git a/Analysis/src/components/Trend/index.less b/Analysis/src/components/Trend/index.less new file mode 100644 index 0000000000000000000000000000000000000000..13618838afcd46f1fc0e724097a0af938ca6f7b3 --- /dev/null +++ b/Analysis/src/components/Trend/index.less @@ -0,0 +1,37 @@ +@import '~antd/lib/style/themes/default.less'; + +.trendItem { + display: inline-block; + font-size: @font-size-base; + line-height: 22px; + + .up, + .down { + position: relative; + top: 1px; + margin-left: 4px; + i { + font-size: 12px; + transform: scale(0.83); + } + } + .up { + color: @red-6; + } + .down { + top: -1px; + color: @green-6; + } + + &.trendItemGrey .up, + &.trendItemGrey .down { + color: @text-color; + } + + &.reverseColor .up { + color: @green-6; + } + &.reverseColor .down { + color: @red-6; + } +} diff --git a/Analysis/src/components/Trend/index.tsx b/Analysis/src/components/Trend/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..69b9c5017b5047e4b959a01d864e9752738829d2 --- /dev/null +++ b/Analysis/src/components/Trend/index.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { Icon } from 'antd'; +import classNames from 'classnames'; +import styles from './index.less'; + +export interface ITrendProps { + colorful?: boolean; + flag: 'up' | 'down'; + style?: React.CSSProperties; + reverseColor?: boolean; + className?: string; +} + +const Trend: React.SFC = ({ + colorful = true, + reverseColor = false, + flag, + children, + className, + ...rest +}) => { + const classString = classNames( + styles.trendItem, + { + [styles.trendItemGrey]: !colorful, + [styles.reverseColor]: reverseColor && colorful, + }, + className + ); + return ( +
+ {children} + {flag && ( + + + + )} +
+ ); +}; + +export default Trend; diff --git a/Analysis/src/data.d.ts b/Analysis/src/data.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..20df2e20dc6eeeff67414b3f6f50d8bfaeb7d62e --- /dev/null +++ b/Analysis/src/data.d.ts @@ -0,0 +1,67 @@ +export interface IVisitData { + x: string; + y: number; +} + +export interface IVisitData2 { + x: string; + y: number; +} + +export interface ISalesData { + x: string; + y: number; +} + +export interface ISearchData { + index: number; + keyword: string; + count: number; + range: number; + status: number; +} + +export interface IOfflineData { + name: string; + cvr: number; +} + +export interface IOfflineChartData { + x: any; + y1: number; + y2: number; +} + +export interface ISalesTypeData { + x: string; + y: number; +} + +export interface ISalesTypeDataOnline { + x: string; + y: number; +} + +export interface ISalesTypeDataOffline { + x: string; + y: number; +} + +export interface IRadarData { + name: string; + label: string; + value: number; +} + +export interface IAnalysisData { + visitData: IVisitData[]; + visitData2: IVisitData2[]; + salesData: ISalesData[]; + searchData: ISearchData[]; + offlineData: IOfflineData[]; + offlineChartData: IOfflineChartData[]; + salesTypeData: ISalesTypeData[]; + salesTypeDataOnline: ISalesTypeDataOnline[]; + salesTypeDataOffline: ISalesTypeDataOffline[]; + radarData: IRadarData[]; +} diff --git a/Analysis/src/index.js b/Analysis/src/index.tsx similarity index 79% rename from Analysis/src/index.js rename to Analysis/src/index.tsx index d175e701cf2f48ce9bb371d6eccfaebb7d83c974..201009c693cec55b0cbd869eac02c915cc1c3355 100644 --- a/Analysis/src/index.js +++ b/Analysis/src/index.tsx @@ -1,11 +1,13 @@ import React, { Component, Suspense } from 'react'; import { connect } from 'dva'; import { Row, Col, Icon, Menu, Dropdown } from 'antd'; - +import { RangePickerValue } from 'antd/lib/date-picker/interface'; import { getTimeDistance } from './utils/utils'; - import styles from './style.less'; import PageLoading from './components/PageLoading'; +import { Dispatch } from 'redux'; +import { IAnalysisData } from './data.d'; +import { RadioChangeEvent } from 'antd/lib/radio'; const IntroduceRow = React.lazy(() => import('./components/IntroduceRow')); const SalesCard = React.lazy(() => import('./components/SalesCard')); @@ -13,17 +15,43 @@ const TopSearch = React.lazy(() => import('./components/TopSearch')); const ProportionSales = React.lazy(() => import('./components/ProportionSales')); const OfflineData = React.lazy(() => import('./components/OfflineData')); -@connect(({ BLOCK_NAME_CAMEL_CASE, loading }) => ({ - BLOCK_NAME_CAMEL_CASE, - loading: loading.effects['BLOCK_NAME_CAMEL_CASE/fetch'], -})) -class PAGE_NAME_UPPER_CAMEL_CASE extends Component { - state = { +interface BLOCK_NAME_CAMEL_CASEProps { + BLOCK_NAME_CAMEL_CASE: IAnalysisData; + dispatch: Dispatch; + loading: boolean; +} + +interface BLOCK_NAME_CAMEL_CASEState { + salesType: 'all' | 'online' | 'stores'; + currentTabKey: string; + rangePickerValue: RangePickerValue; +} + +@connect( + ({ + BLOCK_NAME_CAMEL_CASE, + loading, + }: { + BLOCK_NAME_CAMEL_CASE: any; + loading: { + effects: { [key: string]: boolean }; + }; + }) => ({ + BLOCK_NAME_CAMEL_CASE, + loading: loading.effects['BLOCK_NAME_CAMEL_CASE/fetch'], + }) +) +class PAGE_NAME_UPPER_CAMEL_CASE extends Component< + BLOCK_NAME_CAMEL_CASEProps, + BLOCK_NAME_CAMEL_CASEState +> { + state: BLOCK_NAME_CAMEL_CASEState = { salesType: 'all', currentTabKey: '', rangePickerValue: getTimeDistance('year'), }; - + reqRef!: number; + timeoutId!: number; componentDidMount() { const { dispatch } = this.props; this.reqRef = requestAnimationFrame(() => { @@ -42,19 +70,19 @@ class PAGE_NAME_UPPER_CAMEL_CASE extends Component { clearTimeout(this.timeoutId); } - handleChangeSalesType = e => { + handleChangeSalesType = (e: RadioChangeEvent) => { this.setState({ salesType: e.target.value, }); }; - handleTabChange = key => { + handleTabChange = (key: string) => { this.setState({ currentTabKey: key, }); }; - handleRangePickerChange = rangePickerValue => { + handleRangePickerChange = (rangePickerValue: RangePickerValue) => { const { dispatch } = this.props; this.setState({ rangePickerValue, @@ -65,7 +93,7 @@ class PAGE_NAME_UPPER_CAMEL_CASE extends Component { }); }; - selectDate = type => { + selectDate = (type: 'today' | 'week' | 'month' | 'year') => { const { dispatch } = this.props; this.setState({ rangePickerValue: getTimeDistance(type), @@ -76,7 +104,7 @@ class PAGE_NAME_UPPER_CAMEL_CASE extends Component { }); }; - isActive = type => { + isActive = (type: 'today' | 'week' | 'month' | 'year') => { const { rangePickerValue } = this.state; const value = getTimeDistance(type); if (!rangePickerValue[0] || !rangePickerValue[1]) { @@ -127,7 +155,6 @@ class PAGE_NAME_UPPER_CAMEL_CASE extends Component { ); const activeKey = currentTabKey || (offlineData[0] && offlineData[0].name); - return ( }> @@ -149,7 +176,6 @@ class PAGE_NAME_UPPER_CAMEL_CASE extends Component { diff --git a/Analysis/src/locales/en-US.js b/Analysis/src/locales/en-US.ts similarity index 100% rename from Analysis/src/locales/en-US.js rename to Analysis/src/locales/en-US.ts diff --git a/Analysis/src/locales/pt-BR.js b/Analysis/src/locales/pt-BR.ts similarity index 100% rename from Analysis/src/locales/pt-BR.js rename to Analysis/src/locales/pt-BR.ts diff --git a/Analysis/src/locales/zh-CN.js b/Analysis/src/locales/zh-CN.ts similarity index 100% rename from Analysis/src/locales/zh-CN.js rename to Analysis/src/locales/zh-CN.ts diff --git a/Analysis/src/locales/zh-TW.js b/Analysis/src/locales/zh-TW.ts similarity index 100% rename from Analysis/src/locales/zh-TW.js rename to Analysis/src/locales/zh-TW.ts diff --git a/Analysis/src/model.js b/Analysis/src/model.tsx similarity index 100% rename from Analysis/src/model.js rename to Analysis/src/model.tsx diff --git a/Analysis/src/service.js b/Analysis/src/service.tsx similarity index 100% rename from Analysis/src/service.js rename to Analysis/src/service.tsx diff --git a/Analysis/src/utils/Yuan.js b/Analysis/src/utils/Yuan.tsx similarity index 100% rename from Analysis/src/utils/Yuan.js rename to Analysis/src/utils/Yuan.tsx diff --git a/Analysis/src/utils/utils.js b/Analysis/src/utils/utils.ts similarity index 84% rename from Analysis/src/utils/utils.js rename to Analysis/src/utils/utils.ts index 6e77bd481b045873e3b5853bca1fb361e02cd115..20c070d40aacd8f0251c2594982c94fdad1063e9 100644 --- a/Analysis/src/utils/utils.js +++ b/Analysis/src/utils/utils.ts @@ -1,10 +1,11 @@ import moment from 'moment'; +import { RangePickerValue } from 'antd/lib/date-picker/interface'; -export function fixedZero(val) { +export function fixedZero(val: number) { return val * 1 < 10 ? `0${val}` : val; } -export function getTimeDistance(type) { +export function getTimeDistance(type: 'today' | 'week' | 'month' | 'year'): RangePickerValue { const now = new Date(); const oneDay = 1000 * 60 * 60 * 24; diff --git a/package.json b/package.json index 33d8bf36b2a50607542d30e6a39ce4f2cbe86610..a70bd2ee9fd407cb0081bd8ee3a9757f9649ebc1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "scripts": { - "dev": "cross-env PAGES_PATH='AdvancedProfile/src' umi dev", + "dev": "cross-env PAGES_PATH='Analysis/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",