Browse Source

add charts

KaysonCui 5 years ago
parent
commit
60b11d4ae5

+ 2 - 1
front/.eslintrc

@@ -28,7 +28,8 @@
     "replaceLink": false,
     "goBack": false,
     "toLink": false,
-    "openLink": false
+    "openLink": false,
+    "WxLogin": false
   },
   "rules": {
     "camelcase": "off",

+ 34 - 0
front/package-lock.json

@@ -3644,6 +3644,30 @@
         "jsbn": "0.1.1"
       }
     },
+    "echarts": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npm.taobao.org/echarts/download/echarts-4.2.1.tgz",
+      "integrity": "sha1-mo6jsDNU+G+CTZdiXDNM8Wll7wM=",
+      "requires": {
+        "zrender": "4.0.7"
+      }
+    },
+    "echarts-for-react": {
+      "version": "2.0.15-beta.0",
+      "resolved": "https://registry.npm.taobao.org/echarts-for-react/download/echarts-for-react-2.0.15-beta.0.tgz",
+      "integrity": "sha1-2/MLZr/Mixy5+InrPsWaixNjw9Y=",
+      "requires": {
+        "fast-deep-equal": "2.0.1",
+        "size-sensor": "0.2.5"
+      },
+      "dependencies": {
+        "fast-deep-equal": {
+          "version": "2.0.1",
+          "resolved": "http://registry.npm.taobao.org/fast-deep-equal/download/fast-deep-equal-2.0.1.tgz",
+          "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
+        }
+      }
+    },
     "ee-first": {
       "version": "1.1.1",
       "resolved": "http://registry.npm.taobao.org/ee-first/download/ee-first-1.1.1.tgz",
@@ -10579,6 +10603,11 @@
       "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
       "dev": true
     },
+    "size-sensor": {
+      "version": "0.2.5",
+      "resolved": "https://registry.npm.taobao.org/size-sensor/download/size-sensor-0.2.5.tgz",
+      "integrity": "sha1-6P+3zFJYiTRyv9pPVzTWZYMkd1c="
+    },
     "slash": {
       "version": "1.0.0",
       "resolved": "http://registry.npm.taobao.org/slash/download/slash-1.0.0.tgz",
@@ -12418,6 +12447,11 @@
         }
       }
     },
+    "zrender": {
+      "version": "4.0.7",
+      "resolved": "https://registry.npm.taobao.org/zrender/download/zrender-4.0.7.tgz",
+      "integrity": "sha1-Fa6WCCL17+1BCZXTflEH/j3hDm0="
+    },
     "zscroller": {
       "version": "0.4.8",
       "resolved": "http://registry.npm.taobao.org/zscroller/download/zscroller-0.4.8.tgz",

+ 2 - 0
front/package.json

@@ -72,6 +72,8 @@
   "dependencies": {
     "antd": "^3.11.6",
     "antd-mobile": "^2.2.7",
+    "echarts": "^4.2.1",
+    "echarts-for-react": "^2.0.15-beta.0",
     "fastclick": "^1.0.6",
     "history": "^4.7.2",
     "moment": "^2.22.2",

+ 4 - 0
front/project/www/app.less

@@ -31,6 +31,10 @@
   color: @night-blue;
 }
 
+.link {
+  border-bottom: 1px dashed @theme_color;
+}
+
 .f-s-16 {
   font-size: 16px;
 }

+ 1 - 1
front/project/www/components/Button/index.less

@@ -25,7 +25,7 @@
   font-size: 18px;
   font-weight: 600;
   line-height: 28px;
-  padding: 5px 16px;
+  padding: 8px 16px;
 }
 
 .button.disabled {

+ 16 - 0
front/project/www/components/Input/index.js

@@ -0,0 +1,16 @@
+import React from 'react';
+import './index.less';
+
+function Input(props) {
+  const { className = '', size = 'basic', placeholder, disabled, radius, width, onChange } = props;
+  return (
+    <div
+      style={{ width: width || '' }}
+      className={`input ${className} ${size} ${disabled ? 'disabled' : ''}  ${radius ? 'radius' : ''}`}
+    >
+      <input placeholder={placeholder} disabled={disabled} onChange={() => onChange && onChange()} />
+    </div>
+  );
+}
+Input.propTypes = {};
+export default Input;

+ 76 - 0
front/project/www/components/Input/index.less

@@ -0,0 +1,76 @@
+@import '../../app.less';
+
+.input {
+  display: inline-block;
+  vertical-align: middle;
+  border-radius: 2px;
+
+  input {
+    width: 100%;
+    border: solid 1px rgba(0, 0, 0, 0.1);
+    background: @theme_bg_color;
+  }
+
+  input:hover {
+    border-color: @theme_color;
+  }
+
+  input:focus {
+    border-color: @theme_color;
+    background: darken(@theme_bg_color, 2);
+  }
+}
+
+.input.basic {
+
+  input {
+    padding: 4px 15px;
+    line-height: 30px;
+    font-size: 20px;
+  }
+}
+
+.input.small {
+
+  input {
+    line-height: 18px;
+    font-size: 12px;
+    padding: 2px 8px;
+  }
+}
+
+.input.lager {
+
+  input {
+    font-size: 18px;
+    line-height: 28px;
+    padding: 7px 20px;
+  }
+}
+
+.input.disabled {
+  cursor: no-drop;
+}
+
+.input.basic.radius {
+  border-radius: 17px;
+}
+
+.input.small.radius {
+  border-radius: 14px;
+}
+
+.input.lager.radius {
+  border-radius: 36px;
+}
+
+.input.default {
+  background: #fff;
+  color: @holder_color;
+  border: 1px solid @holder_color;
+}
+
+.button.default:hover {
+  color: @theme_color;
+  border-color: @theme_color;
+}

+ 1 - 1
front/project/www/index.js

@@ -8,7 +8,7 @@ export default {
   tabs: [
     { key: 'main', name: '首页', path: '/' },
     { key: 'ready', name: 'GetReady', path: '/' },
-    { key: 'exercise', name: '练习', path: '/' },
+    { key: 'practise', name: '练习', path: '/' },
     { key: 'cat', name: 'CAT模考', path: '/' },
     { key: 'item', name: '题库', path: '/' },
     { key: 'machine', name: '换库&机经', path: '/' },

+ 8 - 4
front/project/www/local.json

@@ -1,6 +1,6 @@
 {
   "development": {
-    "scripts": [],
+    "scripts": ["http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"],
     "proxy": [
       {
         "target": "http://127.0.0.1:8080",
@@ -9,6 +9,10 @@
       }
     ]
   },
-  "test": {},
-  "production": {}
-}
+  "test": {
+    "scripts": ["http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"]
+  },
+  "production": {
+    "scripts": ["http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"]
+  }
+}

+ 4 - 1
front/project/www/routes/page/index.js

@@ -1,3 +1,6 @@
 import home from './home';
+import practise from './practise';
+import report from './report';
+import login from './login';
 
-export default [home];
+export default [home, practise, report, login];

+ 10 - 0
front/project/www/routes/page/login/index.js

@@ -0,0 +1,10 @@
+export default {
+  path: '/login',
+  key: 'login',
+  title: '登录',
+  needLogin: false,
+  tab: 'main',
+  component() {
+    return import('./page');
+  },
+};

+ 3 - 0
front/project/www/routes/page/login/index.less

@@ -0,0 +1,3 @@
+@charset "utf-8";
+
+#login {}

+ 21 - 0
front/project/www/routes/page/login/page.js

@@ -0,0 +1,21 @@
+import React from 'react';
+import './index.less';
+import Page from '@src/containers/Page';
+
+export default class extends Page {
+  showWx() {
+    setTimeout(() => {
+      const wx = new WxLogin({
+        id: 'qrCode',
+        appid: '',
+        scope: 'snsapi_login',
+        redirect_uri: encodeURIComponent('/login'),
+      });
+      return wx;
+    }, 100);
+  }
+
+  renderView() {
+    return <div />;
+  }
+}

+ 10 - 0
front/project/www/routes/page/practise/index.js

@@ -0,0 +1,10 @@
+export default {
+  path: '/practise',
+  key: 'practise',
+  title: '练习',
+  needLogin: true,
+  tab: 'practise',
+  component() {
+    return import('./page');
+  },
+};

+ 42 - 0
front/project/www/routes/page/practise/index.less

@@ -0,0 +1,42 @@
+@charset "utf-8";
+
+#practise {
+  .code-module {
+    padding: 80px 250px;
+    text-align: center;
+
+    .title {
+      font-size: 18px;
+      margin-bottom: 24px;
+    }
+
+    .input-block {
+      margin-bottom: 24px;
+
+      .input {
+        width: 350px;
+
+        input {
+          border-top-left-radius: 22px;
+          border-bottom-left-radius: 22px;
+        }
+      }
+
+      .button {
+        width: 150px;
+        border-top-right-radius: 22px;
+        border-bottom-right-radius: 22px;
+      }
+    }
+
+    .tip {
+      .left {
+        float: left;
+      }
+
+      .right {
+        float: right;
+      }
+    }
+  }
+}

+ 52 - 0
front/project/www/routes/page/practise/page.js

@@ -0,0 +1,52 @@
+import React from 'react';
+import './index.less';
+import { Link } from 'react-router-dom';
+import Page from '@src/containers/Page';
+import Tabs from '../../../components/Tabs';
+import Module from '../../../components/Module';
+import Input from '../../../components/Input';
+import Button from '../../../components/Button';
+
+export default class extends Page {
+  renderView() {
+    return (
+      <div>
+        <div className="content">
+          <Module className="m-t-2">
+            <Tabs
+              type="card"
+              active="main"
+              tabs={[
+                { key: 'main', name: '首页', path: '/' },
+                { key: 'ready', name: 'GetReady', path: '/' },
+                { key: 'exercise', name: '练习', path: '/' },
+                { key: 'cat', name: 'CAT模考', path: '/' },
+                { key: 'item', name: '题库', path: '/' },
+                { key: 'machine', name: '换库&机经', path: '/' },
+              ]}
+            />
+          </Module>
+          <Module className="code-module">
+            <div className="title">输入《千行GMAT长难句》专属 Code,解锁在线练习功能。</div>
+            <div className="input-block">
+              <Input size="lager" placeholder="请输入CODE" />
+              <Button size="lager">解锁</Button>
+            </div>
+            <div className="tip">
+              <Link to="/" className="left link">
+                什么是CODE?
+              </Link>
+              <span>没有 CODE?</span>
+              <Link to="/" className="link">
+                去获取 >>
+              </Link>
+              <Link to="/" className="right link">
+                试用 >>
+              </Link>
+            </div>
+          </Module>
+        </div>
+      </div>
+    );
+  }
+}

+ 9 - 0
front/project/www/routes/page/report/index.js

@@ -0,0 +1,9 @@
+export default {
+  path: '/report',
+  key: 'report',
+  title: '报告',
+  needLogin: true,
+  component() {
+    return import('./page');
+  },
+};

+ 3 - 0
front/project/www/routes/page/report/index.less

@@ -0,0 +1,3 @@
+@charset "utf-8";
+
+#report {}

+ 226 - 0
front/project/www/routes/page/report/page.js

@@ -0,0 +1,226 @@
+import React from 'react';
+import './index.less';
+import LineChart from '@src/components/LineChart';
+import BarChart from '@src/components/BarChart';
+import PieChart from '@src/components/PieChart';
+import Page from '@src/containers/Page';
+
+const lineOption = {
+  title: {
+    text: '每题用时情况',
+    textStyle: { fontSize: 24, fontWeight: 'normal', color: '#5e677b' },
+  },
+  tooltip: {
+    trigger: 'axis',
+  },
+  legend: {
+    data: ['我的', '全站'],
+    right: 20,
+    orient: 'vertical',
+  },
+  xAxis: {
+    type: 'category',
+  },
+  yAxis: {
+    type: 'value',
+    min: 0,
+  },
+  dataset: {
+    source: [['type', '我的', '全站'], ['1', 43.3, 85.8], ['2', 43.1, 73.4], ['3', 56.4, 65.2]],
+  },
+  series: [
+    {
+      type: 'line',
+      smooth: true,
+      color: '#8684df',
+    },
+    {
+      type: 'line',
+      smooth: true,
+      color: '#5195e5',
+    },
+  ],
+};
+
+const barOption = {
+  title: {
+    text: '正确率',
+    textStyle: { fontSize: 24, fontWeight: 'normal', color: '#5e677b' },
+  },
+  tooltip: {
+    trigger: 'axis',
+  },
+  legend: {
+    data: ['我的', '全站'],
+    right: 20,
+    orient: 'vertical',
+  },
+  dataset: {
+    source: [['type', '我的', '全站'], ['Easy', 43.3, 85.8], ['Medium', 43.1, 73.4], ['Hard', 56.4, 65.2]],
+  },
+  xAxis: { type: 'category', boundaryGap: true },
+  yAxis: {
+    min: 0,
+    max: 100,
+  },
+  series: [{ type: 'bar', barWidth: 40, color: '#8684df' }, { type: 'bar', barWidth: 40, color: '#5195e5' }],
+};
+
+const bar1Option = {
+  title: {
+    text: '用时',
+    textStyle: { fontSize: 24, fontWeight: 'normal', color: '#5e677b' },
+  },
+  dataset: {
+    source: [['type', 'text'], ['Avg Time Total', 43.3], ['Avg Time Correct', 43.1], ['Avg Time Incorrect', 56.4]],
+  },
+  xAxis: { type: 'category', axisTick: { show: false }, axisLine: { show: false }, splitLine: { show: false } },
+  yAxis: {
+    show: false,
+    min: 0,
+    max: 100,
+    axisTick: { show: false },
+    axisLine: { show: false },
+    splitLine: { show: false },
+  },
+  series: [{ type: 'bar', barWidth: 40, color: '#8684df' }],
+};
+
+const bar2Option = {
+  title: {
+    text: '正确率',
+    textStyle: { fontSize: 18, fontWeight: 'normal', color: '#5e677b' },
+    left: 240,
+  },
+  dataset: {
+    source: [['type', '我的'], ['Avg Time Total', 43.3], ['Avg Time Correct', 43.1], ['Avg Time Incorrect', 56.4]],
+  },
+  grid: { left: 240 },
+  xAxis: {
+    show: false,
+    axisTick: { show: false },
+    axisLine: { show: false },
+    splitLine: { show: false },
+  },
+  yAxis: {
+    type: 'category',
+    show: true,
+    axisTick: { show: false },
+    axisLine: { show: false },
+    splitLine: { show: false },
+    axisLabel: { fontSize: 18, fontWeight: 'normal', color: '#5e677b' },
+    offset: 36,
+  },
+  series: [
+    {
+      type: 'bar',
+      barWidth: 40,
+      color: '#8684df',
+      label: {
+        normal: {
+          show: true,
+          position: 'insideLeft',
+          distance: 15,
+        },
+      },
+    },
+  ],
+};
+const bar3Option = {
+  title: {
+    text: '平均用时',
+    textStyle: { fontSize: 18, fontWeight: 'normal', color: '#5e677b' },
+    left: '10%',
+  },
+  dataset: {
+    source: [['type', '我的'], ['Avg Time Total', 43.3], ['Avg Time Correct', 43.1], ['Avg Time Incorrect', 56.4]],
+  },
+  xAxis: {
+    show: false,
+    axisTick: { show: false },
+    axisLine: { show: false },
+    splitLine: { show: false },
+  },
+  yAxis: {
+    type: 'category',
+    show: true,
+    axisTick: { show: false },
+    axisLine: { show: false },
+    splitLine: { show: false },
+    axisLabel: { show: false },
+  },
+  series: [
+    {
+      type: 'bar',
+      barWidth: 40,
+      color: '#8684df',
+      label: {
+        normal: {
+          show: true,
+          position: 'insideLeft',
+          distance: 15,
+        },
+      },
+    },
+  ],
+};
+const pieOption = {
+  visualMap: {
+    show: false,
+    min: 0,
+    max: 800,
+    inRange: {
+      colorLightness: [1, 0],
+    },
+  },
+  series: [
+    {
+      name: '访问来源',
+      type: 'pie',
+      radius: '55%',
+      center: ['50%', '50%'],
+      roseType: 'radius',
+      label: {
+        normal: {
+          fontSize: 18,
+          position: 'inside',
+        },
+      },
+      itemStyle: {
+        normal: {
+          color: '#5195e5',
+          shadowBlur: 40,
+          shadowColor: 'rgba(0, 0, 0, 0.2)',
+        },
+      },
+
+      animationType: 'scale',
+      animationEasing: 'elasticOut',
+      animationDelay: () => {
+        return Math.random() * 200;
+      },
+      data: [{ value: 335, name: '直接访问' }, { value: 310, name: '邮件营销' }, { value: 274, name: '联盟广告' }].sort(
+        (a, b) => {
+          return a.value - b.value;
+        },
+      ),
+    },
+  ],
+};
+
+export default class extends Page {
+  renderView() {
+    return (
+      <div>
+        <div className="content">
+          <LineChart option={lineOption} />
+          <BarChart option={bar1Option} />
+          <BarChart option={bar2Option} />
+          <BarChart option={bar3Option} />
+          <BarChart option={barOption} />
+          <PieChart option={pieOption} />
+        </div>
+      </div>
+    );
+  }
+}

+ 37 - 0
front/src/components/BarChart/index.js

@@ -0,0 +1,37 @@
+import React from 'react';
+import './index.less';
+import echarts from 'echarts/lib/echarts';
+import ReactEchartsCore from 'echarts-for-react/lib/core';
+import 'echarts/lib/chart/pie';
+import 'echarts/lib/component/tooltip';
+import 'echarts/lib/component/title';
+import 'echarts/lib/component/legend';
+
+function BarChart(props) {
+  const { className = '', theme = 'shine', option = {}, data = [] } = props;
+  let defaultOption = {
+    series: [
+      {
+        data: [],
+        type: 'bar',
+      },
+    ],
+    xAxis: {
+      type: 'key',
+    },
+    yAxis: {
+      type: 'value',
+    },
+  };
+  if (data.length > 0) {
+    defaultOption.series.data = data;
+  } else {
+    defaultOption = option;
+  }
+  return (
+    <div className={`bar-chart ${className}`}>
+      <ReactEchartsCore echarts={echarts} option={defaultOption} notMerge lazyUpdate theme={theme} />
+    </div>
+  );
+}
+export default BarChart;

+ 0 - 0
front/src/components/BarChart/index.less


+ 39 - 0
front/src/components/LineChart/index.js

@@ -0,0 +1,39 @@
+import React from 'react';
+import './index.less';
+import echarts from 'echarts/lib/echarts';
+import ReactEchartsCore from 'echarts-for-react/lib/core';
+import 'echarts/lib/chart/line';
+import 'echarts/lib/component/tooltip';
+import 'echarts/lib/component/title';
+import 'echarts/lib/component/legend';
+
+function LineChart(props) {
+  const { className = '', theme = 'shine', option = {}, data = [] } = props;
+
+  let defaultOption = {
+    series: [
+      {
+        data: [],
+        type: 'line',
+        smooth: true,
+      },
+    ],
+    xAxis: {
+      type: 'key',
+    },
+    yAxis: {
+      type: 'value',
+    },
+  };
+  if (data.length > 0) {
+    defaultOption.series.data = data;
+  } else {
+    defaultOption = option;
+  }
+  return (
+    <div className={`line-chart ${className}`}>
+      <ReactEchartsCore echarts={echarts} option={defaultOption} notMerge lazyUpdate theme={theme} />
+    </div>
+  );
+}
+export default LineChart;

+ 0 - 0
front/src/components/LineChart/index.less


+ 37 - 0
front/src/components/PieChart/index.js

@@ -0,0 +1,37 @@
+import React from 'react';
+import './index.less';
+import echarts from 'echarts/lib/echarts';
+import ReactEchartsCore from 'echarts-for-react/lib/core';
+import 'echarts/lib/chart/bar';
+import 'echarts/lib/component/tooltip';
+import 'echarts/lib/component/title';
+import 'echarts/lib/component/legend';
+
+function PieChart(props) {
+  const { className = '', theme = 'shine', option = {}, data = [] } = props;
+  let defaultOption = {
+    series: [
+      {
+        data: [],
+        type: 'pie',
+      },
+    ],
+    xAxis: {
+      type: 'key',
+    },
+    yAxis: {
+      type: 'value',
+    },
+  };
+  if (data.length > 0) {
+    defaultOption.series.data = data;
+  } else {
+    defaultOption = option;
+  }
+  return (
+    <div className={`pie-chart ${className}`}>
+      <ReactEchartsCore echarts={echarts} option={defaultOption} notMerge lazyUpdate theme={theme} />
+    </div>
+  );
+}
+export default PieChart;

+ 0 - 0
front/src/components/PieChart/index.less


+ 34 - 0
front/src/stores/wechat.js

@@ -0,0 +1,34 @@
+import BaseStore from './base';
+
+const wwwAppId = '';
+const h5AppId = '';
+
+export default class WeChatStore extends BaseStore {
+  initState() {
+    return {};
+  }
+
+  wwwQrCode() {
+    const url = encodeURIComponent(window.location.href);
+    setTimeout(() => {
+      const wx = new WxLogin({
+        id: 'qrCode',
+        appid: wwwAppId,
+        scope: 'snsapi_login',
+        redirect_uri: url,
+      });
+      return wx;
+    }, 100);
+  }
+
+  wwwLogin() {}
+
+  h5Auth() {
+    const url = encodeURIComponent(window.location.href);
+    window.location = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${h5AppId}&redirect_uri=${url}&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect`;
+  }
+
+  h5Login() {}
+}
+
+export const User = new WeChatStore({ key: 'wx', local: true });