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 && (
+
+ )}
+ {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})` }}
+ >
+
+
+
+ {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",