浏览代码

feat:创建项目基础文件

baicha 5 年之前
当前提交
6c34b20beb
共有 97 个文件被更改,包括 3250 次插入0 次删除
  1. 16 0
      .editorconfig
  2. 1 0
      .env
  3. 3 0
      .eslintrc
  4. 18 0
      .gitignore
  5. 7 0
      .prettierignore
  6. 11 0
      .prettierrc
  7. 64 0
      .umirc.js
  8. 0 0
      mock/.gitkeep
  9. 43 0
      package.json
  10. 二进制
      public/cover01.png
  11. 二进制
      public/favicon.png
  12. 0 0
      public/qiniu-web.js
  13. 9 0
      src/app.js
  14. 二进制
      src/assets/banner1.png
  15. 二进制
      src/assets/banner2.png
  16. 二进制
      src/assets/banner3.png
  17. 二进制
      src/assets/banner4.png
  18. 二进制
      src/assets/banner5.png
  19. 二进制
      src/assets/banner6.png
  20. 二进制
      src/assets/banner7.png
  21. 二进制
      src/assets/banner8.png
  22. 二进制
      src/assets/bg.png
  23. 二进制
      src/assets/icon1.png
  24. 二进制
      src/assets/icon2.png
  25. 二进制
      src/assets/icon3.png
  26. 二进制
      src/assets/icon4.png
  27. 二进制
      src/assets/icon5.png
  28. 二进制
      src/assets/icon6.png
  29. 二进制
      src/assets/icon7.png
  30. 二进制
      src/assets/icon_android.png
  31. 二进制
      src/assets/icon_android01.png
  32. 二进制
      src/assets/icon_beian.png
  33. 二进制
      src/assets/icon_code.png
  34. 二进制
      src/assets/icon_ios.png
  35. 二进制
      src/assets/icon_ios01.png
  36. 二进制
      src/assets/img1.png
  37. 二进制
      src/assets/img10.png
  38. 二进制
      src/assets/img2.png
  39. 二进制
      src/assets/img3.png
  40. 二进制
      src/assets/img4.png
  41. 二进制
      src/assets/img5.png
  42. 二进制
      src/assets/img6.png
  43. 二进制
      src/assets/img7.png
  44. 二进制
      src/assets/img8.png
  45. 二进制
      src/assets/img9.png
  46. 二进制
      src/assets/img_phone.png
  47. 二进制
      src/assets/img_words_01.png
  48. 二进制
      src/assets/live_bg.png
  49. 二进制
      src/assets/logo copy.png
  50. 二进制
      src/assets/logo.png
  51. 二进制
      src/assets/logo2.png
  52. 二进制
      src/assets/logo_words.png
  53. 二进制
      src/assets/logo_words_02.png
  54. 二进制
      src/assets/qrcode.png
  55. 二进制
      src/assets/words01.png
  56. 二进制
      src/assets/wx.png
  57. 64 0
      src/components/PageHeader/PageHeader.js
  58. 112 0
      src/components/PageHeader/PageHeader.less
  59. 1 0
      src/components/index.js
  60. 22 0
      src/global.css
  61. 24 0
      src/layouts/BlankLayout.js
  62. 3 0
      src/layouts/BlankLayout.less
  63. 14 0
      src/layouts/__tests__/index.test.js
  64. 14 0
      src/layouts/index.css
  65. 29 0
      src/layouts/index.js
  66. 0 0
      src/models/.gitkeep
  67. 206 0
      src/pages/Home.jsx
  68. 196 0
      src/pages/Home.less
  69. 201 0
      src/pages/HotLives.js
  70. 209 0
      src/pages/HotLives.less
  71. 14 0
      src/pages/__tests__/index.test.js
  72. 35 0
      src/pages/components/CopyRight.js
  73. 64 0
      src/pages/components/CopyRight.less
  74. 1 0
      src/pages/components/index.js
  75. 34 0
      src/pages/document.ejs
  76. 93 0
      src/pages/index.js
  77. 97 0
      src/pages/index.less
  78. 26 0
      src/services/common.js
  79. 19 0
      src/utils/Authorized.js
  80. 175 0
      src/utils/Cookie.js
  81. 88 0
      src/utils/Format.js
  82. 33 0
      src/utils/Help.js
  83. 51 0
      src/utils/MyDate.js
  84. 229 0
      src/utils/Security.js
  85. 9 0
      src/utils/Sign.js
  86. 37 0
      src/utils/Url.js
  87. 33 0
      src/utils/authority.js
  88. 15 0
      src/utils/authority.test.js
  89. 508 0
      src/utils/base.js
  90. 28 0
      src/utils/enums.js
  91. 16 0
      src/utils/notice.js
  92. 0 0
      src/utils/qiniu-web.js
  93. 146 0
      src/utils/request.js
  94. 55 0
      src/utils/utils.js
  95. 50 0
      src/utils/utils.less
  96. 115 0
      src/utils/utils.test.js
  97. 12 0
      webpack.config.js

+ 16 - 0
.editorconfig

@@ -0,0 +1,16 @@
+# http://editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
+
+[Makefile]
+indent_style = tab

+ 1 - 0
.env

@@ -0,0 +1 @@
+

+ 3 - 0
.eslintrc

@@ -0,0 +1,3 @@
+{
+  "extends": "eslint-config-umi"
+}

+ 18 - 0
.gitignore

@@ -0,0 +1,18 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/npm-debug.log*
+/yarn-error.log
+/yarn.lock
+/package-lock.json
+
+# production
+/dist
+
+# misc
+.DS_Store
+
+# umi
+.umi
+.umi-production

+ 7 - 0
.prettierignore

@@ -0,0 +1,7 @@
+**/*.md
+**/*.svg
+**/*.ejs
+**/*.html
+package.json
+.umi
+.umi-production

+ 11 - 0
.prettierrc

@@ -0,0 +1,11 @@
+{
+  "singleQuote": true,
+  "trailingComma": "es5",
+  "printWidth": 100,
+  "overrides": [
+    {
+      "files": ".prettierrc",
+      "options": { "parser": "json" }
+    }
+  ]
+}

+ 64 - 0
.umirc.js

@@ -0,0 +1,64 @@
+
+// ref: https://umijs.org/config/
+export default {
+  treeShaking: true,
+  hash: true,
+  routes: [
+    {
+      path: '/explain',
+      component: '../pages/Home'
+    },
+    {
+      path: '/',
+      component: '../layouts/index',
+      routes: [
+        { path: '/', component: '../pages/index' },
+        // { path: '/explain', component: '../pages/Home' },
+        { path: '/hotlives', component: '../pages/HotLives' },
+      ]
+    }
+  ],
+  plugins: [
+    // ref: https://umijs.org/plugin/umi-plugin-react.html
+    ['umi-plugin-react', {
+      antd: true,
+      dva: false,
+      dynamicImport: false, // { webpackChunkName: true },
+      title: 'xk-umi',
+      dll: true,
+      
+      routes: {
+        exclude: [
+          /models\//,
+          /services\//,
+          /model\.(t|j)sx?$/,
+          /service\.(t|j)sx?$/,
+          /components\//,
+        ],
+      },
+      lessLoaderOptions: {
+        javascriptEnabled: true,
+      }
+    }],
+  ],
+  proxy: {
+    '/api-manage': {
+      target: 'http://api.starbuds.laylib.com',
+      changeOrigin: true,
+      //pathRewrite: { '^/server': '' },
+    },
+    '/api-common': {
+      target: 'http://api.starbuds.laylib.com',
+      changeOrigin: true,
+      //pathRewrite: { '^/server': '' },
+    },
+    '/api-app': {
+      target: 'http://api.starbuds.laylib.com',
+      changeOrigin: true,
+      //pathRewrite: { '^/server': '' },
+    }
+  },
+  targets: {
+    ie: 9,
+  }
+}

+ 0 - 0
mock/.gitkeep


+ 43 - 0
package.json

@@ -0,0 +1,43 @@
+{
+  "private": true,
+  "scripts": {
+    "start": "umi dev",
+    "build": "umi build",
+    "test": "umi test",
+    "lint": "eslint --ext .js src mock tests"
+  },
+  "dependencies": {
+    "antd": "^3.15.0",
+    "dva": "^2.5.0-beta.2",
+    "enquire-js": "^0.2.1",
+    "js-base64": "^2.5.2",
+    "rc-tween-one": "^2.4.1",
+    "react": "^16.7.0",
+    "react-dom": "^16.7.0",
+    "react-transition-group": "^4.2.1",
+    "umi-request": "^1.2.19"
+  },
+  "devDependencies": {
+    "babel-eslint": "^9.0.0",
+    "eslint": "^5.4.0",
+    "eslint-config-umi": "^1.4.0",
+    "eslint-plugin-flowtype": "^2.50.0",
+    "eslint-plugin-import": "^2.14.0",
+    "eslint-plugin-jsx-a11y": "^5.1.1",
+    "eslint-plugin-react": "^7.11.1",
+    "husky": "^0.14.3",
+    "lint-staged": "^7.2.2",
+    "react-test-renderer": "^16.7.0",
+    "umi": "^2.6.3",
+    "umi-plugin-react": "^1.6.0"
+  },
+  "lint-staged": {
+    "*.{js,jsx}": [
+      "eslint --fix",
+      "git add"
+    ]
+  },
+  "engines": {
+    "node": ">=8.0.0"
+  }
+}

二进制
public/cover01.png


二进制
public/favicon.png


文件差异内容过多而无法显示
+ 0 - 0
public/qiniu-web.js


+ 9 - 0
src/app.js

@@ -0,0 +1,9 @@
+
+// export const dva = {
+//   config: {
+//     onError(err) {
+//       err.preventDefault();
+//       console.error(err.message);
+//     },
+//   },
+// };

二进制
src/assets/banner1.png


二进制
src/assets/banner2.png


二进制
src/assets/banner3.png


二进制
src/assets/banner4.png


二进制
src/assets/banner5.png


二进制
src/assets/banner6.png


二进制
src/assets/banner7.png


二进制
src/assets/banner8.png


二进制
src/assets/bg.png


二进制
src/assets/icon1.png


二进制
src/assets/icon2.png


二进制
src/assets/icon3.png


二进制
src/assets/icon4.png


二进制
src/assets/icon5.png


二进制
src/assets/icon6.png


二进制
src/assets/icon7.png


二进制
src/assets/icon_android.png


二进制
src/assets/icon_android01.png


二进制
src/assets/icon_beian.png


二进制
src/assets/icon_code.png


二进制
src/assets/icon_ios.png


二进制
src/assets/icon_ios01.png


二进制
src/assets/img1.png


二进制
src/assets/img10.png


二进制
src/assets/img2.png


二进制
src/assets/img3.png


二进制
src/assets/img4.png


二进制
src/assets/img5.png


二进制
src/assets/img6.png


二进制
src/assets/img7.png


二进制
src/assets/img8.png


二进制
src/assets/img9.png


二进制
src/assets/img_phone.png


二进制
src/assets/img_words_01.png


二进制
src/assets/live_bg.png


二进制
src/assets/logo copy.png


二进制
src/assets/logo.png


二进制
src/assets/logo2.png


二进制
src/assets/logo_words.png


二进制
src/assets/logo_words_02.png


二进制
src/assets/qrcode.png


二进制
src/assets/words01.png


二进制
src/assets/wx.png


+ 64 - 0
src/components/PageHeader/PageHeader.js

@@ -0,0 +1,64 @@
+import React, { useState } from 'react'
+import styles from './PageHeader.less'
+import logoWords from '@/assets/logo_words.png'
+import logoWords02 from '@/assets/logo_words_02.png'
+import qrcode from '@/assets/qrcode.png'
+import router from 'umi/router'
+
+class PageHeader extends React.Component {
+  /**
+   * @param {string} pathname 当前页面路由路径
+   * @param {string} cPathname 当前导航路由路径
+   * @return {string}
+   */
+  getClassName = (pathname, cPathname) => {
+    if (pathname === cPathname) {
+      return styles['nav-active']
+    }
+  }
+  /**
+   * @param {string} pathname 当前页面路由路径
+   * @param {string} cPathname 当前导航路由路径
+   * @return {void}
+   */
+  goPage = (pathname, cPathname) => e => {
+    if (pathname !== cPathname) {
+      router.push(cPathname)
+    }
+  }
+  /**
+   * 
+   */
+  getWrapClassName = pathname => {
+    let _wrapClassName = styles['header-wrap']
+    if (pathname === '/hotlives') {
+      _wrapClassName += ' ' + styles['header-white']
+    }
+    return _wrapClassName
+  }
+  render () {
+    const { location } = this.props
+    const { pathname } = location
+    return (
+      <div className={this.getWrapClassName(pathname)}>
+        <div className={styles['logo-wrap']}>
+          {/* <img src={logoWords} className={styles['logo-words']} alt=""/> */}
+        </div>
+        <ul className={styles['header-nav']}>
+          <li className={this.getClassName(pathname, '/')} onClick={this.goPage(pathname, '/')}>首页</li>
+          <li className={styles['qrcode-li']}>
+            <span>APP下载</span>
+            <div className={styles['qrcode-wrap']}>
+              <div className={styles['qrcode-img-wrap']}>
+                <img className={styles['qrcode-img']} src={qrcode} alt=""/>
+              </div>
+            </div>
+          </li>
+          <li className={this.getClassName(pathname, '/hotlives')} onClick={this.goPage(pathname, '/hotlives')}>热门直播</li>
+          <li className={this.getClassName(pathname, '/explain')} onClick={this.goPage(pathname, '/explain')}>产品说明</li>
+        </ul>
+      </div>
+    )
+  }
+}
+export default PageHeader

+ 112 - 0
src/components/PageHeader/PageHeader.less

@@ -0,0 +1,112 @@
+.header-wrap {
+  height: 80px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  z-index: 999;
+  padding: 0 40px;
+}
+
+.logo-wrap {
+  width: 120px;
+  height: 40px;
+  display: block;
+  background: url(../../assets/logo_words.png) no-repeat center;
+  background-size: 100% 100%;
+}
+.header-nav {
+  display: flex;
+  text-align: center;
+  & > li {
+    width: 80px;
+    height: 24px;
+    line-height: 24px;
+    color: rgba(255, 255, 255, .5);
+    font-size: 12px;
+    cursor: pointer;
+    border-right: 1px solid rgba(255, 255, 255, .5);
+    transition: all .3s ease 0s;
+    position: relative;
+  }
+  & > li:last-child {
+    border-right: 0;
+  }
+
+  & > li:hover {
+    color: #fff;
+    transition: all .3s ease 0s;
+  }
+
+  .qrcode-wrap {
+    display: none;
+    position: absolute;
+    top: 100%;
+    left: 50%;
+    transform: translate(-50%, 0);
+    padding-top: 20px;
+  }
+  .qrcode-img-wrap {
+    background: #fff;
+    border-radius: 10px;
+    padding: 10px;
+  }
+  .qrcode-wrap .qrcode-img {
+    width: 106px;
+    height: 106px;
+    display: block;
+    margin: 0;
+  }
+  & > .qrcode-li:hover {
+    // color: rgba(255,255,255,1);
+    transition: all .3s ease 0s;
+    .qrcode-wrap {
+      display: block;
+    }
+  }
+
+  li.nav-active {
+    color: #fff;
+    &:after {
+      content: '';
+      position: absolute;
+      left: 50%;
+      bottom: -1px;
+      transform: translate(-50%, 0);
+      height: 2px;
+      width: 12px;
+      background: #FF4D79;
+      border-radius: 1px;
+    }
+  }
+}
+.header-white {
+  box-shadow: 0px 4px 10px rgba(0,0,0,0.04);
+  .logo-wrap {
+    background: url(../../assets/logo_words_02.png) no-repeat center;
+    background-size: 100% 100%;
+  }
+
+  .header-nav { 
+    & > li {
+      color: rgba(0, 0, 0, .5);
+      border-right: 1px solid rgba(0,0,0,0.25);
+    }
+    & > li:last-child {
+      border-right: 0;
+    }
+    & > li:hover {
+      color: #000000;
+    }
+  }
+  li.nav-active { 
+    color: #000000;
+  }
+  .qrcode-img-wrap {
+    box-shadow: 0px 0px 4px rgba(0,0,0,0.04);
+    border: 1px solid rgba(0,0,0, 0.1);
+  }
+}

+ 1 - 0
src/components/index.js

@@ -0,0 +1 @@
+export { default as PageHeader } from './PageHeader/PageHeader'

+ 22 - 0
src/global.css

@@ -0,0 +1,22 @@
+p, h1, h2, h3, h4, h5, h6, p, ul, ol, li, dl, dd, dt {
+  margin: 0; padding: 0;
+}
+html, body, #root {
+  height: 100%;
+}
+ul, li, ol {
+  list-style: none;
+}
+
+body {
+  margin: 0;
+  font-family: PingFangSC-Regular,PingFangSC-Medium,Helvetica,HelveticaNeue,Arial,Verdana,Sans-serif;
+  word-break: break-all;
+}
+a {
+  text-decoration: none;
+}
+.ant-popover-inner {
+  border-radius: 20px !important;
+  box-shadow:0px 10px 20px rgba(0,0,0,0.16) !important;
+}

+ 24 - 0
src/layouts/BlankLayout.js

@@ -0,0 +1,24 @@
+import styles from './BlankLayout.css'
+import React from 'react'
+import { PageHeader } from '@/components'
+
+class BlankLayout extends React.Component {
+  constructor(props) {
+    super(props)
+    this.state = {}
+  }
+
+  componentDidMount () {}
+  
+
+  render() {
+    const props = this.props
+    return (
+      <div className={styles.normal}>
+        {props.children}
+      </div>
+    );
+  }
+}
+
+export default BlankLayout

+ 3 - 0
src/layouts/BlankLayout.less

@@ -0,0 +1,3 @@
+.normal {
+  height: 100%;
+}

+ 14 - 0
src/layouts/__tests__/index.test.js

@@ -0,0 +1,14 @@
+import BasicLayout from '..';
+import renderer from 'react-test-renderer';
+
+describe('Layout: BasicLayout', () => {
+  it('Render correctly', () => {
+    const wrapper = renderer.create(<BasicLayout />);
+    expect(wrapper.root.children.length).toBe(1);
+    const outerLayer = wrapper.root.children[0];
+    expect(outerLayer.type).toBe('div');
+    const title = outerLayer.children[0];
+    expect(title.type).toBe('h1');
+    expect(title.children[0]).toBe('Yay! Welcome to umi!');
+  });
+});

+ 14 - 0
src/layouts/index.css

@@ -0,0 +1,14 @@
+
+.normal {
+  height: 100%;
+}
+
+.title {
+  font-size: 2.5rem;
+  font-weight: normal;
+  letter-spacing: -1px;
+  background: darkslateblue;
+  padding: .6em 0;
+  color: white;
+  margin: 0;
+}

+ 29 - 0
src/layouts/index.js

@@ -0,0 +1,29 @@
+import styles from './index.css'
+// import { enquireScreen } from 'enquire-js';
+import React from 'react'
+import { PageHeader } from '@/components'
+
+class BasicLayout extends React.Component {
+  constructor(props) {
+    super(props)
+    this.state = {}
+  }
+
+  componentDidMount () {}
+  
+
+  render() {
+    const props = this.props
+    return (
+      <div className={styles.normal}>
+        <PageHeader 
+          location={props.location}
+        />
+        <div>
+          {props.children}
+        </div>
+      </div>
+    )
+  }
+}
+export default BasicLayout

+ 0 - 0
src/models/.gitkeep


+ 206 - 0
src/pages/Home.jsx

@@ -0,0 +1,206 @@
+import React from 'react';
+import styles from './Home.less'
+import { Row, Col, Divider } from 'antd'
+
+import { CopyRight } from './components'
+class Home extends React.Component {
+  constructor(props) {
+    super(props)
+    this.state = {
+      buttonList: ['娱乐', '游戏', '社交', '电商', 'O2O', '旅游', '在线教育', '众筹路演', '新闻/咨询', '物联网金融' ],
+      tabName: '娱乐'
+    }
+  }
+  componentDidMount () {
+    document.title = '一起互娱'
+  }
+
+  handleTab(tabName) {
+    this.setState({ tabName })
+  }
+
+  renderTabContent(tabName) {
+    const detail = {}
+    if (tabName === '娱乐') {
+      detail.title = '直播+娱乐解决方案'
+      detail.text1 = '借助直播优势,帮助传统娱乐平台向直播+娱乐模式转型'
+      detail.text2 = '利用直播特点,对娱乐平台流量利用率不足进行完美补充,增加平台粉丝粘性,使客户的互动效率最大化,最终实现消费 / 购买转化或投放平台广告资源。'
+      detail.url = require('@/assets/img1.png')
+    }
+    if (tabName === '游戏') {
+      detail.title = '直播+游戏解决方案'
+      detail.text1 = '借助直播优势,帮助传统游戏平台向直播+游戏模式转型'
+      detail.text2 = '含答题、幸运礼物、砸宝箱、钓鱼等传统直播游戏项目,也有更多IP的结合,如f1赛车、欧洲杯球赛、棋牌、抢盲盒等新直播游戏项目'
+      detail.url = require('@/assets/img2.png')
+    }
+    if (tabName === '社交') {
+      detail.title = '直播+社交解决方案'
+      detail.text1 = '借助直播优势,帮助传统社交平台向直播+社交模式转型'
+      detail.text2 = '直播用更直观的方式自我展示,大大降低用户之间社交门槛。重叠观看的直播内容让社交平台更易于深入挖掘用户习惯,扩大平台社交属性的同'
+      detail.url = require('@/assets/img3.png')
+    }
+    if (tabName === '电商') {
+      detail.title = '直播+电商解决方案'
+      detail.text1 = '借助直播优势,帮助传统电商平台向直播+电商模式转型'
+      detail.text2 = '用户不退出直播情况下,就能直接下单主播推荐产品,“边看边买”的直播模式,在丰富了电商的产品展示内容,更好的将直播内容转化为用户'
+      detail.url = require('@/assets/img4.png')
+    }
+    if (tabName === 'O2O') {
+      detail.title = '直播+O2O解决方案'
+      detail.text1 = '借助直播优势,帮助传统O2O平台向直播+O2O模式转型'
+      detail.text2 = '商家利用线上直播,真实还原线下场景,不论美食/休闲/购物/娱乐/酒店/生活服务,均能通过直播打造业内优势,促进线上到线下导流,'
+      detail.url = require('@/assets/img5.png')
+    }
+    if (tabName === '旅游') {
+      detail.title = '直播+旅游解决方案'
+      detail.text1 = '借助直播优势,帮助传统旅游平台向直播+旅游模式转型'
+      detail.text2 = '打破原有旅游平台紧靠图片、文字对旅游这种个人体验项目描述上的单一感,加入直播那种身临其境、所见即所得的当下体验感,优化用户体验'
+      detail.url = require('@/assets/img6.png')
+    }
+    if (tabName === '在线教育') {
+      detail.title = '直播+在线教育解决方案'
+      detail.text1 = '借助直播优势,帮助传统在线教育平台向直播+在线教育模式转型'
+      detail.text2 = '直播,将传统线下老师督促学生完成学习任务,检验学习质量、学生反馈学习情况提出疑问等双向互动很好地在线上进行统一,形成线上教育互'
+      detail.url = require('@/assets/img7.png')
+    }
+    if (tabName === '众筹路演') {
+      detail.title = '直播+众筹路演解决方案'
+      detail.text1 = '借助直播优势,帮助传统众筹平台向直播+在线教育模式转型'
+      detail.text2 = '直播赋予了众筹路演新含义,让原本仅依靠文字/图文/视频等单向展示项目的方式成为实时性、直观性、互动性更强的交互模式。实时互动观'
+      detail.url = require('@/assets/img8.png')
+    }
+    if (tabName === '新闻/咨询') {
+      detail.title = '直播+新闻/资讯解决方案'
+      detail.text1 = '借助直播优势,帮助传统新闻/资讯平台向直播+新闻/资讯模式转型'
+      detail.text2 = '新闻事件都有一定突发性,传统的新闻播报形式难免不及时,借用直播技术,让所有人都会机会成为“云记者”。第一时间记录、传递最新最快'
+      detail.url = require('@/assets/img9.png')
+    }
+    if (tabName === '物联网金融') {
+      detail.title = '直播+互联网金融解决方案'
+      detail.text1 = '借助直播优势,帮助传统互联网金融平台向直播+互联网模式转型'
+      detail.text2 = '直播让互联网金融平台用户实时掌握最新时代动态,站在经济最前沿的金融战场。在线喊单、讲盘等功能,范围几乎能全面覆盖所有金融业务,'
+      detail.url = require('@/assets/img10.png')
+    }
+    return(
+      <div className={styles.tabPane}>
+        <div className={styles['item-title']}>{detail.title}</div>
+        <div className={styles['item-text1']}>{detail.text1}</div>
+        <div className={styles['item-text2']}>{detail.text2}</div>
+        <img src={detail.url}/>
+      </div>
+    )
+  }
+
+  render() {
+    const { buttonList, tabName } = this.state
+    return(
+      <div className={styles.home}>
+        <div>
+          <img className={styles.banner} src={require('@/assets/banner1.png')}/>
+          <img className={styles.banner} src={require('@/assets/banner2.png')} />
+          <div className={styles.content3}>
+            <div className={styles.layout}>
+              <div className={styles.title}>一起互娱云直播 <span style={{ color: '#FCFF00' }}>独立直播平台优势</span></div>
+              <Row type="flex">
+                <Col span={8}>
+                  <div className={styles.content}>
+                    <img className={styles.icon} src={require('@/assets/icon1.png')} />
+                    <div className={`${styles['item-title']} color-yellow`}>直播创业扶持</div>
+                    <div className={`${styles['item-text']} color-yellow`}>直播行业成熟运营团队带队,加盟即免费提供1000+主播进驻联运端,0技术开发成本、0行业摸索成本、0初创时间成本</div>
+                  </div>
+                </Col>
+                <Col span={8}>
+                  <div className={styles.content}>
+                    <img className={styles.icon} src={require('@/assets/icon2.png')} />
+                    <div className={`${styles['item-title']}`}>数字资产支付</div>
+                    <div className={`${styles['item-text']}`}>数字资产支付:用户在直播中收益和消费可以用设定的数字资产抵扣全部或者部分金额,打通区块链钱包和直播场景的价值对接</div>
+                  </div>
+                </Col>
+                <Col span={8}>
+                  <div className={styles.content}>
+                    <img className={styles.icon} src={require('@/assets/icon3.png')} />
+                    <div className={`${styles['item-title']}`}>变现形式多样</div>
+                    <div className={`${styles['item-text']}`}>“直播+”营销是2020年线上最火爆的变现模式,除直播打赏外,产品销售、游戏消费、时间充值、广告收益等均可快速变现</div>
+                  </div>
+                </Col>
+                <Col span={8}>
+                  <div className={styles.content}>
+                    <img className={styles.icon} src={require('@/assets/icon4.png')} />
+                    <div className={`${styles['item-title']} color-yellow`}>流量优先原则</div>
+                    <div className={`${styles['item-text']} color-yellow`}>凡联运平台邀请他人注册后,该用户在一起互联的任一直播平台消费,邀请方均可获得收益,该政策三年内均有效</div>
+                  </div>
+                </Col>
+                <Col span={8}>
+                  <div className={styles.content}>
+                    <img className={styles.icon} src={require('@/assets/icon5.png')} />
+                    <div className={`${styles['item-title']}`}>场景可自定义</div>
+                    <div className={`${styles['item-text']}`}>联运端名称和定位由加盟方决定,直播+购物、直播+秀场、直播+赛事、直播+教育、直播+旅游、直播+游戏…给区块链金融提供更多创新型娱乐互动场景</div>
+                  </div>
+                </Col>
+                <Col span={8}>
+                  <div className={styles.content}>
+                    <img className={styles.icon} src={require('@/assets/icon6.png')} />
+                    <div className={`${styles['item-title']}`}>资产多重安全保障</div>
+                    <div className={`${styles['item-text']}`}>通过专业的安全系统、风控系统、冷热钱包系统等,多维度全方位保障平台与用户数字货币资产安全</div>
+                  </div>
+                </Col>
+              </Row>
+            </div>
+          </div>
+          <div className={styles.content4}>
+            <div className={styles.layout}>
+              <div className={styles.title}>10大直播解决方案 助您快速进入“直播+”时代</div>
+              <div className={styles.text}>不管您处在哪个行业,我们均能为您定制专属解决方案,快速升级您的平台</div>
+              <Row type="flex" align="middle" justify="space-between"  style={{ marginTop: 45, flexWrap: "nowrap" }}>
+                <div className={styles.buttonList}>
+                  {buttonList.map((item, i)=>{
+                    return(
+                      <div key={i} className={item === tabName ? styles['button-active'] : styles.button} onClick={this.handleTab.bind(this, item) }>{item}</div>
+                    )
+                  }) }
+                </div>
+                <div style={{ height: 600, width: 1, background: '#fff' }}/>
+                <div style={{ width: 980 }}>{this.renderTabContent(tabName)}</div>
+              </Row>
+            </div>
+          </div>
+          <img className={styles.banner} src={require('@/assets/banner4.png')} />
+          <img className={styles.banner} src={require('@/assets/banner5.png')} />
+          <img className={styles.banner} src={require('@/assets/banner6.png')} />
+          <img className={styles.banner} src={require('@/assets/banner7.png')} />
+          <img className={styles.banner} src={require('@/assets/banner8.png')} />
+          <div className={styles.content5}>
+            <div className={styles.layout}>
+              <Row type="flex" align="top" justify="space-between">
+                <div>
+                  <img className={styles.logo2} src={require('@/assets/logo2.png')}/>
+                  <div className={styles['partner-1']}>直播平台联合运营开创者</div>
+                  <div className={styles.partner}>遇见星光灿烂的你</div>
+                </div>
+                <div>
+                  <div className={styles['title-phone']}>服务热线</div>
+                  <div className={styles.phone}>13777566724</div>
+                  <div>售前咨询(09:00-18:00)</div>
+                </div>
+                <div>
+                  <div className={styles['title-phone']}>联系我们</div>
+                  <p>杭州市西湖区蒋村街道西溪银泰2号写字楼1005</p>
+                  <p>企业QQ:474678784</p>
+                </div>
+                <div>
+                  <div className={styles['title-phone']}>服务咨询</div>
+                  <img className={styles['img-wx']} src={require('@/assets/wx.png')}/>
+                </div>
+              </Row>
+            </div>
+          </div>
+          <CopyRight 
+            type='black'
+          />
+        </div>
+      </div>  
+    )
+  }
+}
+
+export default Home

+ 196 - 0
src/pages/Home.less

@@ -0,0 +1,196 @@
+.home {
+  font-family: Arial, Helvetica, sans-serif;
+  .layout {
+    width: 1200px;
+    margin: auto;
+  }
+  .banner {
+    width  : 100%;
+    height : auto;
+    display: block;
+  }
+
+  .content3 {
+    padding-top     : 88px;
+    background-color: #FE5F5C;
+
+    .title {
+      text-align   : center;
+      font-size    : 36px;
+      font-family  : Microsoft YaHei;
+      font-weight  : 400;
+      color        : rgba(255, 255, 255, 1);
+      line-height  : 45px;
+      margin-bottom: 83px;
+    }
+
+    .content {
+      width        : 204px;
+      margin       : auto;
+      text-align   : center;
+      margin-bottom: 68px;
+
+      .icon {
+        width : 80px;
+        height: 80px;
+      }
+
+      .item-title {
+        font-size  : 18px;
+        font-family: Microsoft YaHei;
+        font-weight: bold;
+        color      : #fff;
+        margin     : 23px 0px 28px 0px;
+      }
+
+      .item-text {
+        font-size  : 12px;
+        font-family: Microsoft YaHei;
+        font-weight: 400;
+        color      : #fff;
+        line-height: 25px;
+      }
+    }
+  }
+
+  .content4 {
+    height: 848px;
+    background-image: url('@/assets/banner3.png');
+    background-repeat: no-repeat;
+    background-size: cover;
+    padding-top: 60px;
+    box-sizing: content-box;
+
+    .title {
+      font-size  :38px;
+      font-family:Microsoft YaHei;
+      font-weight:bold;
+      color      :rgba(242, 242, 242, 1);
+      margin: 0px 0px 24px 0px;
+    }
+
+    .text {
+      font-size  :16px;
+      font-family:Microsoft YaHei;
+      font-weight:400;
+      color      :rgba(242, 242, 242, 1);
+    }
+
+    .buttonList {
+      .button {
+        width     :142px;
+        height    :39px;
+        border-radius:20px;
+        line-height: 39px;
+        text-align: center;
+        font-size: 16px;
+        color: #fff;
+        font-weight: 400;
+        margin-bottom: 20px;
+        cursor: pointer;
+      }
+
+      .button-active {
+        .button();
+        background:rgba(254, 93, 91, 1);
+      }
+    }
+    .tabPane {
+      text-align: left;
+      .item-title {
+        font-size  : 36px;
+        font-family: Microsoft YaHei;
+        font-weight: 400;
+        color      : rgba(199, 242, 22, 1);
+        line-height: 45px;
+        text-align: left;
+      }
+
+      .item-text1 {
+        background    : rgba(0, 0, 0, 0.3);
+        border-radius : 15px;
+        padding: 0px 14px;
+        height        : 30px;
+        line-height   : 30px;
+        font-size     : 14px;
+        font-family   : Microsoft YaHei;
+        font-weight   : 400;
+        color         : rgba(255, 255, 255, 1);
+        text-align    : center;
+        width         : fit-content;
+        margin        : 22px 0px;
+      }
+
+      .item-text2 {
+        font-size  : 14px;
+        font-family: Microsoft YaHei;
+        font-weight: 400;
+        color      : rgba(255, 255, 255, 1);
+        line-height: 26px;
+        text-align: left;
+      }
+
+      img {
+        width : auto;
+        height: 434px;
+        margin-top: 49px;
+      }
+    }
+  }
+
+  .content5 {
+    background    : #535353;
+    padding-top   : 47px;
+    padding-bottom: 20px;
+    position      : relative;
+    font-size: 13px;
+    color    : #c2c2c2;
+    line-height: 22px;
+
+    .logo2 {
+      width: 248px;
+    }
+
+    .partner-1 {
+      color: #C7C7C7;
+      font-size: 14px;
+      padding: 20px 0 10px 0;
+    }
+    .partner {
+      margin-top      : 15px;
+      display         : block;
+      width           : 65%;
+      height          : 30px;
+      background-color: #454545;
+      line-height     : 30px;
+      text-align      : center;
+      color           : #b5b5b5;
+      border-radius   : 3px;
+    }
+    .phone {
+      margin-bottom: 15px;
+      font-size    : 26px;
+      line-height  : 26px;
+      color        : #63a7e4;
+      font-family: Arial, Helvetica, sans-serif;
+    }
+
+    .title-phone {
+        margin-bottom: 36px;
+        position     : relative;
+        font-size    : 16px;
+        color        : #fff;
+        line-height  : 16px;
+    }
+    .img-wx {
+      width: 100px;
+    }
+  }
+  // .footer {
+  //   background: #454545;
+  //   text-align: center;
+  //   font-size : 12px;
+  //   color     : #a4a4a4;
+  //   padding: 10px 0px;
+  // }
+}

+ 201 - 0
src/pages/HotLives.js

@@ -0,0 +1,201 @@
+/**
+ * 热门推荐
+ */
+import React, { Component } from 'react'
+import styles from './HotLives.less'
+import CopyRight from './components/CopyRight'
+import bg from '@/assets/live_bg.png'
+import { getTopSuggest, getLiveInfoLite } from '@/services/common' 
+
+class HotLives extends Component {
+  constructor (props) {
+    super(props)
+    this.state = {
+      // 推荐列表
+      list: [],
+      // 推流解析失败
+      isClose: false,
+      // 当前tab
+      currentTab: {},
+      // 加载 loading
+      loading: true,
+    }
+    this.liveInfoMap = new Map()
+  }
+  // 切换直播
+  onTab = item => async (e) => {
+    const { userId } = item
+    const _state = {
+      isClose: false,
+      currentTab: item
+    }
+    try {
+      if (!this.liveInfoMap.get(userId)) {
+        const { code, data } = await getLiveInfoLite({ userId })
+        if (code === 'OK') {
+          this.liveInfoMap.set(userId, data)
+        }
+      } 
+    } catch (e) {
+      console.log(e)
+    }
+    this.setState(_state, () => {
+      this.createPlayer()
+    })
+  }
+  componentDidMount () {
+    this.getLives()
+  }
+  getLives = async () => {
+    const _state = {}
+    try {
+      const { code, data } = await getTopSuggest()
+      if (code === 'OK') {
+        const list = data.list
+        _state.list = list
+        if (list && list[0]) {
+          this.onTab(list[0])()
+        }
+      }
+    } catch (e) {
+      console.log(e)
+    }
+    this.setState(_state)
+  }
+  createPlayer = () => {
+    if (this.player) {
+      this.player.destroy()
+      this.player = null
+    } 
+    const { currentTab } = this.state
+    const currentAnchor = this.liveInfoMap.get(currentTab.userId)
+    if (!currentAnchor || !currentAnchor.anchorCard) {
+      return
+    }
+    const { pullUrl } = currentAnchor
+    this.player =  new QPlayer({
+      url: pullUrl.hls,
+      container: this.videoRef,
+      loggerLevel: 3,
+      isLive: true,
+      autoplay: true
+    })
+    
+    // this.player.play()
+    // 错误监听
+    this.player.once('error', (error) => {
+      // 10006 浏览器不允许自动播放时的错误
+      if (error.code !== 10006) {
+        this.player.destroy()
+        this.player = null
+        this.setState({ isClose: true })
+      }
+    })
+  }
+
+  formatNumber = (num) => {
+    if (!num || num === '0') {
+      return num
+    }
+    num = +num
+    // 大于 1千万
+    if (num > 10000000) {
+      let str = (num / 10000000).toFixed(1) + 'kw'
+      return str
+    }
+    // 大于 1万
+    if (num > 10000) {
+      let str = (num / 10000).toFixed(1) + 'w'
+      return str
+    }
+    return num
+  }
+
+  componentWillUnmount () {
+    if (this.player) {
+      this.player.destroy()
+    }
+  }
+  
+  render () {
+    const {  isClose, list, currentTab = {} } = this.state
+    const currentAnchor = this.liveInfoMap.get(currentTab.userId) || {}
+    const { anchorCard = {}, anchorInfo = {}, liveHeat } = currentAnchor
+    const { userName, userAvatar } = anchorCard
+    const { liveCover } = anchorInfo
+    // 是否有主播
+    const isNoAnchor = !list || list.length === 0
+    return (
+      <div className={styles['hot-lives-wrap']}>
+        <div className={styles['hot-lives-body']}>
+          <div className={styles['hot-lives-content']} hidden={!isNoAnchor}> 
+
+          </div>
+          <div className={styles['hot-lives-content']} hidden={isNoAnchor}>
+            <div className={styles['content-left']}>
+              <div className={styles['video-wrap']}>
+                {
+                  isClose
+                  ? <React.Fragment>
+                    <div className={styles['live-close-cover']}><img src={bg} alt=""/></div>
+                    <div className={styles['close-user-info']}>
+                      <img src={userAvatar} alt="" className={styles['close-avatar']}/>
+                      <h3 className={styles['close-username']}>{userName}</h3>
+                      <p className={styles['close-message']}>直播已结束</p>
+                    </div>
+                  </React.Fragment>
+                  : <React.Fragment>
+                    <div 
+                      className={styles['live-cover-wrap']} 
+                      ref={node => this.videoRef = node}
+                    >
+                      <img src={liveCover} alt="" />
+                    </div>
+                    <div className={styles['user-info-wrap']}>
+                      <div className={styles['user-avatar']}> 
+                        <img src={userAvatar} alt="" className={styles['img']}/>
+                      </div>
+                      
+                      <div className={styles['user-info']}>
+                        <h3 className={styles['user-name']}>{userName}</h3>
+                        <div className={styles['live-heat']}>{this.formatNumber(liveHeat)} 观看</div>
+                      </div>
+                    </div>
+                  </React.Fragment>
+                }
+              </div>
+            </div>
+            <div className={styles['content-right']}>
+              {
+                list && list.map((item, i) => (
+                  <div 
+                    key = {i} 
+                    className={
+                      currentTab.userId === item.userId
+                      ? `${styles['list-item']} ${styles['active']}`
+                      : styles['list-item']
+                    }
+                    onClick={this.onTab(item)}
+                  >
+                    <div className={styles['item-user-avatar']}>
+                      <img src={item.userAvatar} alt=""/>
+                    </div>
+                    <div className={styles['item-username']}>
+                      {item.userName || '生活很苦 清水很甜'}
+                    </div>
+                  </div>
+                ))
+              }
+            </div>
+          </div>
+          <div className={styles['copy-right-wrap']}>
+            <CopyRight 
+              type='white'
+            />
+          </div>  
+        </div>
+      </div>
+    )
+  }
+}
+export default HotLives

+ 209 - 0
src/pages/HotLives.less

@@ -0,0 +1,209 @@
+.hot-lives-wrap {
+  padding: 120px 0 0 0;
+  box-sizing: border-box;
+  height: 100vh;
+}
+
+.hot-lives-body {
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  flex-wrap: nowrap;
+  height: 100%;
+}
+.hot-lives-content {
+  width: 1200px;
+  height: 700px;
+  margin: 0 auto;
+  display: flex;
+  position: relative;
+  z-index: 10;
+
+  .content-left {
+    flex: auto;
+    margin-right: 20px;
+    background: #000000;
+    padding-top: 16px;
+  }
+  .content-right {
+    flex: 0 0 160px;
+  }
+
+  .video-wrap {
+    width: 375px;
+    height: 668px;
+    margin: 0 auto;
+    position: relative;
+  }
+
+  .live-cover-wrap {
+    height: 668px;
+    width: 375px;
+    position: relative;
+    overflow: hidden;
+  }
+  .live-cover-wrap img {
+    width: 100%;
+    position: absolute;
+    top: 50%;
+    left: 0;
+    right: 0;
+    transform: translate(0, -50%);
+  }
+  .user-info-wrap {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    width: 112px;
+    height: 40px;
+    background: rgba(0,0,0,0.25);
+    border-radius: 22px;
+    position: absolute;
+    top: 30px;
+    left: 10px;
+    padding: 0 4px;
+    box-sizing: border-box;
+    z-index: 999;
+  }
+  .user-avatar {
+    width: 32px;
+    height: 32px;
+    margin-right: 4px;
+    flex: 0 0 32px;
+    img {
+      width: 100%;
+      height: 100%;
+      display: block;
+      border-radius: 50%;
+    }
+  }
+  .user-info {
+    flex: auto;
+    overflow: hidden;
+    color: #fff;
+    font-size: 12px;
+    font-weight: 500;
+  }
+  .user-name {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    width: 100%;
+    color: #fff;
+    line-height: 17px;
+  }
+  .live-heat {
+    line-height: 14px;
+    transform: scale(.85);
+    transform-origin: left;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    width: 100%;
+  }
+
+  .list-item {
+    position: relative;
+    width: 160px;
+    height: 100px;
+    background: #eee;
+    border: 2px solid #fff;
+    margin-bottom: 19px;
+    box-sizing: border-box;
+    cursor: pointer;
+  }
+  .item-user-avatar {
+    height: 100%;
+    position: relative;
+    overflow: hidden;
+  }
+  .list-item img {
+    width: 100%;
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    min-height: 100%;
+  }
+  .list-item.active {
+    border-color: #FF4D79;
+    position: relative;
+    z-index: 2;
+  }
+  .list-item.active:after {
+    content: '';
+    border: 14px solid #FF4D79;
+    border-top: 10px solid transparent;
+    border-bottom: 10px solid transparent;
+    border-left: 14px solid transparent;
+    position: absolute;
+    top: 50%;
+    left: -28px;
+    transform: translate(0, -50%);
+    z-index: 1;
+  }
+  .item-username {
+    height: 24px;
+    font-size:12px;
+    font-weight: 400;
+    line-height: 24px;
+    color:rgba(255,255,255,1);
+    background:rgba(0,0,0,0.5);
+    position: absolute;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    box-sizing: border-box;
+    padding: 0 10px;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+}
+
+.live-close-cover {
+  height: 668px;
+  width: 375px;
+  position: relative;
+  img {
+    display: block;
+    width: 100%;
+    height: 100%;
+  }
+}
+.close-user-info {
+  position: absolute;
+  top: 40%;
+  left: 0;
+  right: 0;
+  transform-origin: center;
+  transform: translate(0, -50%);
+  color: #fff;
+  text-align: center;
+
+  .close-avatar {
+    width: 72px;
+    height: 72px;
+    border-radius: 50%;
+    margin: 0 auto 20px;
+  }
+  .close-username {
+    font-size: 16px;
+    margin-bottom: 40px;
+    color: #fff;
+    padding: 0 20px;
+    box-sizing: border-box;
+    width: 100%;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+  .close-message {
+    font-size: 24px;
+  }
+}
+.copy-right-wrap {
+  position: relative;
+  height: 80px;
+  z-index: 1;
+}

+ 14 - 0
src/pages/__tests__/index.test.js

@@ -0,0 +1,14 @@
+import Index from '..';
+import renderer from 'react-test-renderer';
+
+
+describe('Page: index', () => {
+  it('Render correctly', () => {
+    const wrapper = renderer.create(<Index />);
+    expect(wrapper.root.children.length).toBe(1);
+    const outerLayer = wrapper.root.children[0];
+    expect(outerLayer.type).toBe('div');
+    expect(outerLayer.children.length).toBe(2);
+    
+  });
+});

+ 35 - 0
src/pages/components/CopyRight.js

@@ -0,0 +1,35 @@
+import React from 'react'
+import styles from './CopyRight.less'
+import beian from '@/assets/icon_beian.png'
+
+function getClassName(type) {
+  let _className = styles['copy-right']
+  if (type === 'black') {
+    _className += ' ' + styles['copy-right-black']
+  }
+  if (type === 'white') {
+    _className += ' ' + styles['copy-right-white']
+  }
+  return _className
+}
+export default ({ type }) => (
+  <div className={getClassName(type)}>
+    <div>
+      <span>浙江帕趣网络科技有限公司</span>
+      <span style={{ margin: '0 6px' }}>
+        &copy; 2019
+      </span>
+      <a href="http://www.beian.miit.gov.cn" target='_blank'>浙ICP备19043479号</a>
+      <img className={styles.icon7} src={require('@/assets/icon7.png')} onClick={() => window.open('http://sq.ccm.gov.cn:80/ccnt/sczr/service/business/emark/toDetail/7E04E0CA03D5DEB6E053010A14AC07CE', '_blank')} />
+    </div>
+    <div>
+      <a
+        className={styles['beian-wrap']}
+        target="_blank" href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=33010602010938"
+      >
+        <img src={beian} />
+        <span>浙公网安备 33010602010938号</span>
+      </a>
+    </div>
+  </div>
+)

+ 64 - 0
src/pages/components/CopyRight.less

@@ -0,0 +1,64 @@
+.copy-right {
+  position   : absolute;
+  left       : 50%;
+  transform  : translate(-50%, 0);
+  bottom     : 20px;
+  color      : rgba(255, 255, 255, .8);
+  font-size  : 12px;
+  font-weight: 400;
+  min-width  : 1000px;
+  text-align : center;
+}
+
+.copy-right a {
+  color: rgba(255, 255, 255, .8);
+}
+
+.beian-wrap {
+  padding        : 10px 0;
+  display        : flex;
+  align-items    : center;
+  justify-content: center;
+}
+
+.beian-wrap img {
+  margin-right: 4px;
+  display     : block;
+  width       : 14px;
+}
+
+.copy-right-black {
+  position   : relative;
+  transform  : translate(0, 0);
+  top        : unset;
+  left       : unset;
+  right      : unset;
+  bottom     : unset;
+  background : #454545;
+  color      : #a4a4a4;
+  font-size  : 12px;
+  height     : 82px;
+  line-height: 82px;
+
+  a {
+    color    : inherit;
+    font-size: inherit;
+  }
+}
+
+.copy-right-white {
+  color: rgba(0, 0, 0, 0.25);
+
+  a {
+    color    : inherit;
+    font-size: inherit;
+  }
+}
+
+.icon7 {
+  background-color: #fff;
+  border-radius   : 50%;
+  height          : 50px;
+  cursor          : pointer;
+  margin-left     : 4px;
+}

+ 1 - 0
src/pages/components/index.js

@@ -0,0 +1 @@
+export { default as CopyRight } from './CopyRight'

+ 34 - 0
src/pages/document.ejs

@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
+  <link rel="icon" href="/favicon.png" type="image/x-icon" />
+  <title>星芽</title>
+  <script src="/qiniu-web.js"></script>
+  <script>
+    var url = '//h5.paqukeji.com/download';
+    var isPc = () => { 
+      var userAgentInfo = navigator.userAgent; 
+      var Agents = new Array("Android","iPhone","SymbianOS","Windows Phone","iPad","iPod"); 
+      var flag = true; 
+      for(var v = 0; v < Agents.length; v++) { 
+        if(userAgentInfo.indexOf(Agents[v]) > 0) { 
+          flag = false; 
+          break; 
+        } 
+      } 
+      // pc true phone false
+      return flag; 
+    }
+    if (!isPc()) {
+      window.location.href = url;
+    }
+  </script>
+</head>
+<body>
+  <noscript>Sorry, we need js to run correctly!</noscript>
+  <div id="root"></div>
+</body>
+</html>

+ 93 - 0
src/pages/index.js

@@ -0,0 +1,93 @@
+import React, { Component } from 'react'
+import styles from './index.less'
+
+import words01 from '@/assets/words01.png'
+import iconIos from '@/assets/icon_ios01.png'
+import iconAndroid from '@/assets/icon_android01.png'
+import iconCode from '@/assets/icon_code.png'
+
+import qrcode from '@/assets/qrcode.png'
+import router from 'umi/router'
+import { Icon } from 'antd'
+import CopyRight from './components/CopyRight'
+
+class Home extends Component {
+  constructor(props) {
+    super(props)
+  }
+  // 跳转到产品说明
+  goExplain = (e) => {
+    e.preventDefault()
+    router.push('/explain')
+  }
+  onDownIos = () => {
+    const iosUrl = 'https://fir.im/starbuds'
+    window.open(iosUrl, '_blank')
+  }
+  onDownAndroid = () => {
+    const androidUrl = 'http://file.qn.paqukeji.com/xingya-1.0.0-3.apk'
+    window.location.href = androidUrl
+  }
+  render() {
+    return (
+      <div className={styles['home-wrap']}>
+        <div className={styles['video-wrap']}>
+          <video
+            src="//sp.qn.paqukeji.com/videos/office_web_video.mp4"
+            poster='/cover01.png'
+            controls={false}
+            className={styles['video-flag']}
+            autoPlay
+            muted
+            loop
+          />
+        </div>
+        <div className={styles['mask-wrap']}>
+          <div className={styles['content-wrap']}>
+            {/* <div className={styles['logo-bottom-height']}/> */}
+
+            <img src={words01} alt="" className={styles['words-img']} />
+            {/* <div className={styles['height-word-bot']} /> */}
+
+            {/* <div className={styles['height-btns-bot']}>
+              <a 
+                href="#"
+                onClick={this.goExplain}
+              >
+                <Icon type="appstore" />
+                <span>产品说明</span>
+              </a>
+            </div> */}
+            {/* <ul className={styles['btns-wrap']}>
+              <li onClick={this.goExplain}>
+                <Icon type="appstore" />
+                <span>产品说明</span>
+              </li>
+              <li onClick={this.onDownIos}>
+                <img src={iconIos} alt=""/>
+                <span>iOS下载</span>
+              </li>
+              <li onClick={this.onDownAndroid}>
+                <img src={iconAndroid} alt=""/>
+                <span>Android下载</span>
+              </li>
+              <li>
+                <img src={iconCode} alt=""/>
+                <span>扫描下载</span>
+                <div className={styles['qrcode-wrap']}>
+                  <div className={styles['qrcode-img-wrap']}>
+                    <img className={styles['qrcode-img']} src={qrcode} alt=""/>
+                  </div>
+                </div>
+              </li>
+            </ul> */}
+            {/* <div className={styles['height-btns-bot']} /> */}
+            <div className={styles['copy-right-height']} />
+            <CopyRight />
+          </div>
+        </div>
+      </div>
+    )
+  }
+}
+export default Home

+ 97 - 0
src/pages/index.less

@@ -0,0 +1,97 @@
+@minWidth: 1000px;
+@minHeight: 760px;
+
+.home-wrap {
+  position: relative;
+  overflow: hidden;
+}
+.video-wrap {
+  height: 100vh;
+}
+.video-flag {
+  width: 100%;
+  height: 100%;
+  display: block;
+  object-fit: cover;
+}
+
+.mask-wrap {
+  background: rgba(0, 0, 0, .5);
+  position: absolute;
+  top: 0;
+  left: 0;
+  bottom: 0;
+  right: 0;
+  overflow-y: hidden;
+}
+.content-wrap {
+  box-sizing: border-box;
+  padding: 40px 40px 6px;
+  margin: 0 auto;
+  height: 100%;
+}
+
+// .logo-bottom-height {
+//   height: 14%;
+// }
+// .height-word-bot {
+//   height: 6.5%;
+// }
+// .height-btns-bot {
+//   height: 12%;
+// }
+// .height-10 {
+//   height: 10%;
+// }
+// .height-5 {
+//   height: 5%;
+// }
+
+.words-img {
+  // margin: 0 auto;
+  width: auto;
+  height: 40%;
+  display: block;
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+}
+
+// .btns-wrap {
+//   display: flex;
+//   justify-content: center;
+//   align-items: center;
+//   color: #000;
+//   font-size: 14px;
+
+//   & > li {
+//     width: 160px;
+//     height: 48px;
+//     background: rgba(255,255,255,0.5);
+//     border-radius: 24px;
+//     margin: 0 16px;
+//     display: flex;
+//     align-items: center;
+//     justify-content: center;
+//     cursor: pointer;
+//     transition: all .3s ease 0s;
+//     position: relative;
+//   }
+//   & > li img {
+//     width: 16px;
+//     height: 16px;
+//     margin-right: 4px;
+//     display: block;
+//   }
+  
+
+//   :global(.anticon) {
+//     font-weight: 600;
+//     font-size: 18px;
+//     margin-right: 4px;
+//   }
+// }
+.copy-right-height {
+  height: 54px;
+}

+ 26 - 0
src/services/common.js

@@ -0,0 +1,26 @@
+import { stringify } from 'qs';
+import request from '@/utils/request';
+
+// 获取服务器时间
+export async function getCurrentTime() {
+  return request('/api-common/v1/data/getCurrentTime')
+}
+
+//获取用户头像上传配置
+export async function getUserAvatarUploadConfig(params) {
+  return request(`/api-common/v1/data/getUserAvatarUploadConfig?${stringify(params)}`)
+}
+
+// 获取菜单列表
+export const getAdminRouterPageList = (params) => {
+  return request(`/api-manage/v1/adminRouter/getAdminRouterPageList?${stringify(params)}`)
+}
+
+// 获取推荐直播列表
+export async function getTopSuggest(params) {
+  return request(`/api-app/v1/live/getTopSuggest?${stringify(params)}`)
+}
+// 获取直播详情
+export async function getLiveInfoLite(params) {
+  return request(`/api-app/v1/live/getLiveInfoLite?${stringify(params)}`)
+}

+ 19 - 0
src/utils/Authorized.js

@@ -0,0 +1,19 @@
+import RenderAuthorize from '@/components/Authorized';
+import { getAuthority } from './authority';
+/* eslint-disable eslint-comments/disable-enable-pair */
+
+/* eslint-disable import/no-mutable-exports */
+
+let Authorized = RenderAuthorize(getAuthority()); // Reload the rights component
+
+const reloadAuthorized = () => {
+  Authorized = RenderAuthorize(getAuthority());
+};
+/**
+ * hard code
+ * block need it。
+ */
+
+window.reloadAuthorized = reloadAuthorized;
+export { reloadAuthorized };
+export default Authorized;

+ 175 - 0
src/utils/Cookie.js

@@ -0,0 +1,175 @@
+// Cookie
+// -------------
+// Thanks to:
+//  - http://www.nczonline.net/blog/2009/05/05/http-cookies-explained/
+//  - http://developer.yahoo.com/yui/3/cookie/
+
+class Cookie {}
+
+var decode = decodeURIComponent;
+var encode = encodeURIComponent;
+
+/**
+ * Returns the cookie value for the given name.
+ *
+ * @param {String} name The name of the cookie to retrieve.
+ *
+ * @param {Function|Object} options (Optional) An object containing one or
+ *     more cookie options: raw (true/false) and converter (a function).
+ *     The converter function is run on the value before returning it. The
+ *     function is not used if the cookie doesn't exist. The function can be
+ *     passed instead of the options object for conveniently. When raw is
+ *     set to true, the cookie value is not URI decoded.
+ *
+ * @return {*} If no converter is specified, returns a string or undefined
+ *     if the cookie doesn't exist. If the converter is specified, returns
+ *     the value returned from the converter.
+ */
+Cookie.get = function(name, options) {
+  validateCookieName(name);
+
+  if (typeof options === 'function') {
+    options = { converter: options };
+  } else {
+    options = options || {};
+  }
+
+  var cookies = parseCookieString(document.cookie, !options['raw']);
+  return (options.converter || same)(cookies[name]);
+};
+
+/**
+ * Sets a cookie with a given name and value.
+ *
+ * @param {string} name The name of the cookie to set.
+ *
+ * @param {*} value The value to set for the cookie.
+ *
+ * @param {Object} options (Optional) An object containing one or more
+ *     cookie options: path (a string), domain (a string),
+ *     expires (number or a Date object), secure (true/false),
+ *     and raw (true/false). Setting raw to true indicates that the cookie
+ *     should not be URI encoded before being set.
+ *
+ * @return {string} The created cookie string.
+ */
+Cookie.set = function(name, value, options) {
+  validateCookieName(name);
+
+  options = options || {};
+  var expires = options['expires'];
+  var domain = options['domain'];
+  var path = options['path'];
+
+  if (!options['raw']) {
+    value = encode(String(value));
+  }
+
+  var text = name + '=' + value;
+
+  // expires
+  var date = expires;
+  if (typeof date === 'number') {
+    date = new Date();
+    date.setDate(date.getDate() + expires);
+  }
+  if (date instanceof Date) {
+    text += '; expires=' + date.toUTCString();
+  }
+
+  // domain
+  if (isNonEmptyString(domain)) {
+    text += '; domain=' + domain;
+  }
+
+  // path
+  if (isNonEmptyString(path)) {
+    text += '; path=' + path;
+  }
+
+  // secure
+  if (options['secure']) {
+    text += '; secure';
+  }
+
+  document.cookie = text;
+  return text;
+};
+
+/**
+ * Removes a cookie from the machine by setting its expiration date to
+ * sometime in the past.
+ *
+ * @param {string} name The name of the cookie to remove.
+ *
+ * @param {Object} options (Optional) An object containing one or more
+ *     cookie options: path (a string), domain (a string),
+ *     and secure (true/false). The expires option will be overwritten
+ *     by the method.
+ *
+ * @return {string} The created cookie string.
+ */
+Cookie.remove = function(name, options) {
+  options = options || {};
+  options['expires'] = new Date(0);
+  return this.set(name, '', options);
+};
+
+function parseCookieString(text, shouldDecode) {
+  var cookies = {};
+
+  if (isString(text) && text.length > 0) {
+    var decodeValue = shouldDecode ? decode : same;
+    var cookieParts = text.split(/;\s/g);
+    var cookieName;
+    var cookieValue;
+    var cookieNameValue;
+
+    for (var i = 0, len = cookieParts.length; i < len; i++) {
+      // Check for normally-formatted cookie (name-value)
+      cookieNameValue = cookieParts[i].match(/([^=]+)=/i);
+      if (cookieNameValue instanceof Array) {
+        try {
+          cookieName = decode(cookieNameValue[1]);
+          cookieValue = decodeValue(cookieParts[i].substring(cookieNameValue[1].length + 1));
+        } catch (ex) {
+          // Intentionally ignore the cookie -
+          // the encoding is wrong
+        }
+      } else {
+        // Means the cookie does not have an "=", so treat it as
+        // a boolean flag
+        cookieName = decode(cookieParts[i]);
+        cookieValue = '';
+      }
+
+      if (cookieName) {
+        cookies[cookieName] = cookieValue;
+      }
+    }
+  }
+
+  return cookies;
+}
+
+// Helpers
+
+function isString(o) {
+  return typeof o === 'string';
+}
+
+function isNonEmptyString(s) {
+  return isString(s) && s !== '';
+}
+
+function validateCookieName(name) {
+  if (!isNonEmptyString(name)) {
+    throw new TypeError('Cookie name must be a non-empty string');
+  }
+}
+
+function same(s) {
+  return s;
+}
+
+export default Cookie;

+ 88 - 0
src/utils/Format.js

@@ -0,0 +1,88 @@
+export function fixed(num, places, halfUp) {
+  num = num || 0;
+  places = !isNaN((places = Math.abs(places))) ? places : 2;
+
+  if (!halfUp) {
+    num = num.toFixed(places + 1);
+    // 丢掉最后一位
+    num = num.toString();
+    num = num.substr(0, num.length - 1);
+  } else num = num.toFixed(places);
+  return num;
+}
+
+export function money(number, places, symbol, thousand, decimal) {
+  number = number || 0;
+  places = !isNaN((places = Math.abs(places))) ? places : 2;
+  symbol = symbol !== undefined ? symbol : '$';
+  thousand = thousand || ',';
+  decimal = decimal || '.';
+  var negative = number < 0 ? '-' : '',
+    i = parseInt((number = Math.abs(+number || 0).toFixed(places)), 10) + '',
+    j = (j = i.length) > 3 ? j % 3 : 0;
+  return (
+    symbol +
+    negative +
+    (j ? i.substr(0, j) + thousand : '') +
+    i.substr(j).replace(/(\d{3})(?=\d)/g, '$1' + thousand) +
+    (places
+      ? decimal +
+        Math.abs(number - i)
+          .toFixed(places)
+          .slice(2)
+      : '')
+  );
+}
+
+/**
+ * 将 Date 转化为指定格式的String * 月(M)、日(d)、12小时(h)、24小时(H)、分(m)、秒(s)、周(E)、季度(q)
+ * 可以用 1-2 个占位符 * 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字)
+ * @param date
+ * @param fmt
+ * @returns {*}
+ */
+export function date(date, fmt) {
+  if (!date) {
+    return '未知';
+  }
+
+  date = new Date(parseInt(date));
+  var o = {
+    'M+': date.getMonth() + 1, //月份
+    'd+': date.getDate(), //日
+    'h+': date.getHours() % 12 == 0 ? 12 : date.getHours() % 12, //小时
+    'H+': date.getHours(), //小时
+    'm+': date.getMinutes(), //分
+    's+': date.getSeconds(), //秒
+    'q+': Math.floor((date.getMonth() + 3) / 3), //季度
+    S: date.getMilliseconds(), //毫秒
+  };
+  var week = {
+    '0': '/u65e5',
+    '1': '/u4e00',
+    '2': '/u4e8c',
+    '3': '/u4e09',
+    '4': '/u56db',
+    '5': '/u4e94',
+    '6': '/u516d',
+  };
+  if (/(y+)/.test(fmt)) {
+    fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
+  }
+  if (/(E+)/.test(fmt)) {
+    fmt = fmt.replace(
+      RegExp.$1,
+      (RegExp.$1.length > 1 ? (RegExp.$1.length > 2 ? '/u661f/u671f' : '/u5468') : '') +
+        week[date.getDay() + ''],
+    );
+  }
+  for (var k in o) {
+    if (new RegExp('(' + k + ')').test(fmt)) {
+      fmt = fmt.replace(
+        RegExp.$1,
+        RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length),
+      );
+    }
+  }
+  return fmt;
+}

+ 33 - 0
src/utils/Help.js

@@ -0,0 +1,33 @@
+
+function deepClone(obj) {
+  let objClone = Array.isArray(obj) ? [] : {};
+  if (obj && typeof obj === 'object') {
+    for (key in obj) {
+      if (obj.hasOwnProperty(key)) {
+        //判断ojb子元素是否为对象,如果是,递归复制
+        if (obj[key] && typeof obj[key] === 'object') {
+          objClone[key] = deepClone(obj[key]);
+        } else {
+          //如果不是,简单复制
+          objClone[key] = obj[key];
+        }
+      }
+    }
+  }
+  return objClone;
+}
+
+//删除对象内空属性
+function objFilter(obj) {
+  const keys = Object.keys(obj)
+  for (let key of keys) {
+    if (!obj[key] && obj[key] !== 0) {
+      delete obj[key]
+    }
+  }
+}
+
+module.exports = {
+  deepClone,
+  objFilter
+};

+ 51 - 0
src/utils/MyDate.js

@@ -0,0 +1,51 @@
+import moment from 'moment'
+// 补 0
+function fixedZero(val) {
+  return val * 1 < 10 ? `0${val}` : val;
+}
+// 获取本日、本周、本月、本年
+function getTimeDistance (type) {
+  const now = new Date();
+  const oneDay = 1000 * 60 * 60 * 24;
+
+  if (type === 'today') {
+    now.setHours(0);
+    now.setMinutes(0);
+    now.setSeconds(0);
+    return [moment(now), moment(now.getTime() + (oneDay - 1000))];
+  }
+
+  if (type === 'week') {
+    let day = now.getDay();
+    now.setHours(0);
+    now.setMinutes(0);
+    now.setSeconds(0);
+
+    if (day === 0) {
+      day = 6;
+    } else {
+      day -= 1;
+    }
+    const beginTime = now.getTime() - day * oneDay;
+    return [moment(beginTime), moment(beginTime + (7 * oneDay - 1000))];
+  }
+
+  if (type === 'month') {
+    const year = now.getFullYear();
+    const month = now.getMonth();
+    const nextDate = moment(now).add(1, 'months');
+    const nextYear = nextDate.year();
+    const nextMonth = nextDate.month();
+
+    return [
+      moment(`${year}-${fixedZero(month + 1)}-01 00:00:00`),
+      moment(moment(`${nextYear}-${fixedZero(nextMonth + 1)}-01 00:00:00`).valueOf() - 1000),
+    ];
+  }
+
+  const year = now.getFullYear();
+  return [moment(`${year}`)]// [moment(`${year}-01-01 00:00:00`), moment(`${year}-12-31 23:59:59`)];
+}
+export default {
+  getTimeDistance
+}

+ 229 - 0
src/utils/Security.js

@@ -0,0 +1,229 @@
+function Md5(string) {
+  function RotateLeft(lValue, iShiftBits) {
+    return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
+  }
+  function AddUnsigned(lX, lY) {
+    var lX4, lY4, lX8, lY8, lResult;
+    lX8 = lX & 0x80000000;
+    lY8 = lY & 0x80000000;
+    lX4 = lX & 0x40000000;
+    lY4 = lY & 0x40000000;
+    lResult = (lX & 0x3fffffff) + (lY & 0x3fffffff);
+    if (lX4 & lY4) {
+      return lResult ^ 0x80000000 ^ lX8 ^ lY8;
+    }
+    if (lX4 | lY4) {
+      if (lResult & 0x40000000) {
+        return lResult ^ 0xc0000000 ^ lX8 ^ lY8;
+      } else {
+        return lResult ^ 0x40000000 ^ lX8 ^ lY8;
+      }
+    } else {
+      return lResult ^ lX8 ^ lY8;
+    }
+  }
+
+  function F(x, y, z) {
+    return (x & y) | (~x & z);
+  }
+  function G(x, y, z) {
+    return (x & z) | (y & ~z);
+  }
+  function H(x, y, z) {
+    return x ^ y ^ z;
+  }
+  function I(x, y, z) {
+    return y ^ (x | ~z);
+  }
+
+  function FF(a, b, c, d, x, s, ac) {
+    a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac));
+    return AddUnsigned(RotateLeft(a, s), b);
+  }
+
+  function GG(a, b, c, d, x, s, ac) {
+    a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac));
+    return AddUnsigned(RotateLeft(a, s), b);
+  }
+
+  function HH(a, b, c, d, x, s, ac) {
+    a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac));
+    return AddUnsigned(RotateLeft(a, s), b);
+  }
+
+  function II(a, b, c, d, x, s, ac) {
+    a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac));
+    return AddUnsigned(RotateLeft(a, s), b);
+  }
+
+  function ConvertToWordArray(string) {
+    var lWordCount;
+    var lMessageLength = string.length;
+    var lNumberOfWords_temp1 = lMessageLength + 8;
+    var lNumberOfWords_temp2 = (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64;
+    var lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16;
+    var lWordArray = Array(lNumberOfWords - 1);
+    var lBytePosition = 0;
+    var lByteCount = 0;
+    while (lByteCount < lMessageLength) {
+      lWordCount = (lByteCount - (lByteCount % 4)) / 4;
+      lBytePosition = (lByteCount % 4) * 8;
+      lWordArray[lWordCount] =
+        lWordArray[lWordCount] | (string.charCodeAt(lByteCount) << lBytePosition);
+      lByteCount++;
+    }
+    lWordCount = (lByteCount - (lByteCount % 4)) / 4;
+    lBytePosition = (lByteCount % 4) * 8;
+    lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
+    lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
+    lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
+    return lWordArray;
+  }
+
+  function WordToHex(lValue) {
+    var WordToHexValue = '',
+      WordToHexValue_temp = '',
+      lByte,
+      lCount;
+    for (lCount = 0; lCount <= 3; lCount++) {
+      lByte = (lValue >>> (lCount * 8)) & 255;
+      WordToHexValue_temp = '0' + lByte.toString(16);
+      WordToHexValue =
+        WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length - 2, 2);
+    }
+    return WordToHexValue;
+  }
+
+  function Utf8Encode(string) {
+    string = string.replace(/\r\n/g, '\n');
+    var utftext = '';
+
+    for (var n = 0; n < string.length; n++) {
+      var c = string.charCodeAt(n);
+
+      if (c < 128) {
+        utftext += String.fromCharCode(c);
+      } else if (c > 127 && c < 2048) {
+        utftext += String.fromCharCode((c >> 6) | 192);
+        utftext += String.fromCharCode((c & 63) | 128);
+      } else {
+        utftext += String.fromCharCode((c >> 12) | 224);
+        utftext += String.fromCharCode(((c >> 6) & 63) | 128);
+        utftext += String.fromCharCode((c & 63) | 128);
+      }
+    }
+
+    return utftext;
+  }
+
+  var x = Array();
+  var k, AA, BB, CC, DD, a, b, c, d;
+  var S11 = 7,
+    S12 = 12,
+    S13 = 17,
+    S14 = 22;
+  var S21 = 5,
+    S22 = 9,
+    S23 = 14,
+    S24 = 20;
+  var S31 = 4,
+    S32 = 11,
+    S33 = 16,
+    S34 = 23;
+  var S41 = 6,
+    S42 = 10,
+    S43 = 15,
+    S44 = 21;
+
+  string = Utf8Encode(string);
+
+  x = ConvertToWordArray(string);
+
+  a = 0x67452301;
+  b = 0xefcdab89;
+  c = 0x98badcfe;
+  d = 0x10325476;
+
+  for (k = 0; k < x.length; k += 16) {
+    AA = a;
+    BB = b;
+    CC = c;
+    DD = d;
+    a = FF(a, b, c, d, x[k + 0], S11, 0xd76aa478);
+    d = FF(d, a, b, c, x[k + 1], S12, 0xe8c7b756);
+    c = FF(c, d, a, b, x[k + 2], S13, 0x242070db);
+    b = FF(b, c, d, a, x[k + 3], S14, 0xc1bdceee);
+    a = FF(a, b, c, d, x[k + 4], S11, 0xf57c0faf);
+    d = FF(d, a, b, c, x[k + 5], S12, 0x4787c62a);
+    c = FF(c, d, a, b, x[k + 6], S13, 0xa8304613);
+    b = FF(b, c, d, a, x[k + 7], S14, 0xfd469501);
+    a = FF(a, b, c, d, x[k + 8], S11, 0x698098d8);
+    d = FF(d, a, b, c, x[k + 9], S12, 0x8b44f7af);
+    c = FF(c, d, a, b, x[k + 10], S13, 0xffff5bb1);
+    b = FF(b, c, d, a, x[k + 11], S14, 0x895cd7be);
+    a = FF(a, b, c, d, x[k + 12], S11, 0x6b901122);
+    d = FF(d, a, b, c, x[k + 13], S12, 0xfd987193);
+    c = FF(c, d, a, b, x[k + 14], S13, 0xa679438e);
+    b = FF(b, c, d, a, x[k + 15], S14, 0x49b40821);
+    a = GG(a, b, c, d, x[k + 1], S21, 0xf61e2562);
+    d = GG(d, a, b, c, x[k + 6], S22, 0xc040b340);
+    c = GG(c, d, a, b, x[k + 11], S23, 0x265e5a51);
+    b = GG(b, c, d, a, x[k + 0], S24, 0xe9b6c7aa);
+    a = GG(a, b, c, d, x[k + 5], S21, 0xd62f105d);
+    d = GG(d, a, b, c, x[k + 10], S22, 0x2441453);
+    c = GG(c, d, a, b, x[k + 15], S23, 0xd8a1e681);
+    b = GG(b, c, d, a, x[k + 4], S24, 0xe7d3fbc8);
+    a = GG(a, b, c, d, x[k + 9], S21, 0x21e1cde6);
+    d = GG(d, a, b, c, x[k + 14], S22, 0xc33707d6);
+    c = GG(c, d, a, b, x[k + 3], S23, 0xf4d50d87);
+    b = GG(b, c, d, a, x[k + 8], S24, 0x455a14ed);
+    a = GG(a, b, c, d, x[k + 13], S21, 0xa9e3e905);
+    d = GG(d, a, b, c, x[k + 2], S22, 0xfcefa3f8);
+    c = GG(c, d, a, b, x[k + 7], S23, 0x676f02d9);
+    b = GG(b, c, d, a, x[k + 12], S24, 0x8d2a4c8a);
+    a = HH(a, b, c, d, x[k + 5], S31, 0xfffa3942);
+    d = HH(d, a, b, c, x[k + 8], S32, 0x8771f681);
+    c = HH(c, d, a, b, x[k + 11], S33, 0x6d9d6122);
+    b = HH(b, c, d, a, x[k + 14], S34, 0xfde5380c);
+    a = HH(a, b, c, d, x[k + 1], S31, 0xa4beea44);
+    d = HH(d, a, b, c, x[k + 4], S32, 0x4bdecfa9);
+    c = HH(c, d, a, b, x[k + 7], S33, 0xf6bb4b60);
+    b = HH(b, c, d, a, x[k + 10], S34, 0xbebfbc70);
+    a = HH(a, b, c, d, x[k + 13], S31, 0x289b7ec6);
+    d = HH(d, a, b, c, x[k + 0], S32, 0xeaa127fa);
+    c = HH(c, d, a, b, x[k + 3], S33, 0xd4ef3085);
+    b = HH(b, c, d, a, x[k + 6], S34, 0x4881d05);
+    a = HH(a, b, c, d, x[k + 9], S31, 0xd9d4d039);
+    d = HH(d, a, b, c, x[k + 12], S32, 0xe6db99e5);
+    c = HH(c, d, a, b, x[k + 15], S33, 0x1fa27cf8);
+    b = HH(b, c, d, a, x[k + 2], S34, 0xc4ac5665);
+    a = II(a, b, c, d, x[k + 0], S41, 0xf4292244);
+    d = II(d, a, b, c, x[k + 7], S42, 0x432aff97);
+    c = II(c, d, a, b, x[k + 14], S43, 0xab9423a7);
+    b = II(b, c, d, a, x[k + 5], S44, 0xfc93a039);
+    a = II(a, b, c, d, x[k + 12], S41, 0x655b59c3);
+    d = II(d, a, b, c, x[k + 3], S42, 0x8f0ccc92);
+    c = II(c, d, a, b, x[k + 10], S43, 0xffeff47d);
+    b = II(b, c, d, a, x[k + 1], S44, 0x85845dd1);
+    a = II(a, b, c, d, x[k + 8], S41, 0x6fa87e4f);
+    d = II(d, a, b, c, x[k + 15], S42, 0xfe2ce6e0);
+    c = II(c, d, a, b, x[k + 6], S43, 0xa3014314);
+    b = II(b, c, d, a, x[k + 13], S44, 0x4e0811a1);
+    a = II(a, b, c, d, x[k + 4], S41, 0xf7537e82);
+    d = II(d, a, b, c, x[k + 11], S42, 0xbd3af235);
+    c = II(c, d, a, b, x[k + 2], S43, 0x2ad7d2bb);
+    b = II(b, c, d, a, x[k + 9], S44, 0xeb86d391);
+    a = AddUnsigned(a, AA);
+    b = AddUnsigned(b, BB);
+    c = AddUnsigned(c, CC);
+    d = AddUnsigned(d, DD);
+  }
+
+  var temp = WordToHex(a) + WordToHex(b) + WordToHex(c) + WordToHex(d);
+
+  return temp.toLowerCase();
+}
+
+export default {
+  encryptMd5: Md5,
+};

+ 9 - 0
src/utils/Sign.js

@@ -0,0 +1,9 @@
+import Security from './Security';
+
+function signMd5() {
+  const time = new Date().getTime();
+  return { time: time, sign: Security.encryptMd5(time + 'security.marto.com') };
+}
+export default {
+  signMd5: signMd5,
+};

+ 37 - 0
src/utils/Url.js

@@ -0,0 +1,37 @@
+const Format = require('./Format');
+
+function getQueryString(name) {
+  const reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
+  const r = window.location.search.substr(1).match(reg);
+  if (r != null) return unescape(r[2]);
+  return null;
+}
+
+//导出文档-数据流jsonp
+function parseBlob(res) {
+  const blob = new Blob([res], {
+    type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+  });
+  const objectUrl = URL.createObjectURL(blob);
+  const filename =
+    Format.date(new Date().getTime(), 'yyyy_MM_dd_HH_mm_ss') +
+    '_' +
+    Math.floor(Math.random() * 20) +
+    '.xls';
+  //window.open(objectUrl);
+  const a = document.createElement('a');
+  // safari doesn't support this yet
+  if (typeof a.download === 'undefined') {
+    window.location = objectUrl;
+  } else {
+    a.href = objectUrl;
+    a.download = filename;
+    document.body.appendChild(a);
+    a.click();
+  }
+}
+
+module.exports = {
+  getQueryString,
+  parseBlob,
+};

+ 33 - 0
src/utils/authority.js

@@ -0,0 +1,33 @@
+import { reloadAuthorized } from './Authorized'; // use localStorage to store the authority info, which might be sent from server in actual project.
+
+export function getAuthority(str) {
+  const authorityString =
+    typeof str === 'undefined' && localStorage ? localStorage.getItem('antd-pro-authority') : str; // authorityString could be admin, "admin", ["admin"]
+  
+  let authority;
+
+  try {
+    if (authorityString) {
+      authority = JSON.parse(authorityString);
+    }
+  } catch (e) {
+    authority = authorityString;
+  }
+
+  if (typeof authority === 'string') {
+    return [authority];
+  } // preview.pro.ant.design only do not use in your production.
+  // preview.pro.ant.design 专用环境变量,请不要在你的项目中使用它。
+
+  if (!authority && ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site') {
+    return ['admin'];
+  }
+
+  return authority;
+}
+export function setAuthority(authority) {
+  const proAuthority = typeof authority === 'string' ? [authority] : authority;
+  localStorage.setItem('antd-pro-authority', JSON.stringify(proAuthority)); // auto reload
+
+  reloadAuthorized();
+}

+ 15 - 0
src/utils/authority.test.js

@@ -0,0 +1,15 @@
+import { getAuthority } from './authority';
+describe('getAuthority should be strong', () => {
+  it('string', () => {
+    expect(getAuthority('admin')).toEqual(['admin']);
+  });
+  it('array with double quotes', () => {
+    expect(getAuthority('"admin"')).toEqual(['admin']);
+  });
+  it('array with single item', () => {
+    expect(getAuthority('["admin"]')).toEqual(['admin']);
+  });
+  it('array with multiple items', () => {
+    expect(getAuthority('["admin", "guest"]')).toEqual(['admin', 'guest']);
+  });
+});

+ 508 - 0
src/utils/base.js

@@ -0,0 +1,508 @@
+import MD5 from './Security'
+import { Base64 as Base64Npm } from 'js-base64'
+export const Base64 = Base64Npm
+export { default as MyDate } from './MyDate'
+
+// 获取时间
+export const getDate = (nDate = (new Date()), fmt = 'yyyy-MM-dd hh:mm:ss') => {
+  const sDate = new Date(nDate)
+  const dateObj = {
+    'M+': sDate.getMonth() + 1,
+    'd+': sDate.getDate(),
+    'h+': sDate.getHours(),
+    'm+': sDate.getMinutes(),
+    's+': sDate.getSeconds(),
+    'q+': Math.floor((sDate.getMonth() + 3) / 3),
+    'S': sDate.getMilliseconds()
+  }
+  if (/(y+)/.test(fmt)) {
+    fmt = fmt.replace(RegExp.$1, (sDate.getFullYear() + '')
+      .substr(4 - RegExp.$1.length))
+  }
+  for (const s in dateObj) {
+    if (new RegExp('(' + s + ')').test(fmt)) {
+      fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1)
+        ? (dateObj[s])
+        : (('00' + dateObj[s]).substr(('' + dateObj[s]).length)))
+    }
+  }
+  return fmt
+}
+
+export const regIntNumber = /^[1-9]\d*$/
+export const regNumber = /^[1-9]\d*(\.?\d{1,})?$/
+export { nobleArray, getNobleLevelStr, walletTypeArray, getWalletTypeStr} from './enums'
+//贵族登记
+export const getNobility = (nobleLevelLimit) => {
+  let result = ''
+  switch (nobleLevelLimit) {
+    case 0:
+      result = '不限'
+      break;
+    case 1:
+      result = '男爵'
+      break;
+    case 2:
+      result = '子爵'
+      break;
+    case 3:
+      result = '伯爵'
+      break;
+    case 4:
+      result = '侯爵'
+      break;
+    case 5:
+      result = '公爵'
+      break;
+    case 6:
+      result = '国王'
+      break;
+    case 7:
+      result = '帝皇'
+      break;
+  
+    default:
+      break;
+  }
+  return result
+}
+
+//礼物类型
+export const getGiftType = (giftType) => {
+  let result = ''
+  switch (giftType) {
+    case 1:
+      result = '普通礼物'
+      break;
+    case 2:
+      result = '幸运礼物'
+      break;
+    case 3:
+      result = '红包礼物'
+      break;
+  
+    default:
+      break;
+  }
+  return result
+}
+
+//礼物分类
+export const getGiftCategory = (giftCategory) => {
+  let result = ''
+  switch (giftCategory) {
+    case 1:
+      result = '经典'
+      break;
+    case 2:
+      result = '活动'
+      break;
+    case 9:
+      result = '虚拟币'
+      break;
+
+    default:
+      break;
+  }
+  return result
+}
+
+//礼物价格类型
+export const getGiftPriceType = (giftPriceType) => {
+  let result = ''
+  switch (giftPriceType) {
+    case 11:
+      result = '星钻'
+      break;
+    case 12:
+      result = '星元'
+      break;
+    case 21:
+      result = '星币'
+      break;
+
+    default:
+      result = ''
+      break;
+  }
+  return result
+}
+
+//动效类型
+export const getAnimationType = (animationType) => {
+  let result = ''
+  switch (animationType) {
+    case 0:
+      result = '通用动效'
+      break;
+    case 1:
+      result = '礼物动效'
+      break;
+    case 2:
+      result = '坐骑动效'
+      break;
+
+    default:
+      break;
+  }
+  return result
+}
+
+//勋章类型
+export const getMedalType = (medalType) => {
+  let result = ''
+  switch (medalType) {
+    case 1:
+      result = '贵族勋章'
+      break;
+    case 2:
+      result = '会员勋章'
+      break;
+    case 3:
+      result = '成就勋章'
+      break;
+
+    default:
+      break;
+  }
+  return result
+}
+
+//道具类型
+export const getPropsType = (propsType) => {
+  let result = ''
+  switch (propsType) {
+    case 0:
+      result = '通用'
+      break;
+    case 101:
+      result = '改名卡'
+      break;
+    case 102:
+      result = '靓号卡'
+      break;
+    case 103:
+      result = '会员卡'
+      break;
+    case 104:
+      result = '贵族卡'
+      break;
+    case 105:
+      result = '坐骑卡'
+      break;
+
+    default:
+      break;
+  }
+  return result
+}
+
+//货币类型
+export const getWalletType = (walletType) => {
+  let result = ''
+  switch (walletType) {
+    case 11:
+      result = '星钻'
+      break;
+    case 12:
+      result = '星元'
+      break;
+    case 21:
+      result = '星币'
+      break;
+
+    default:
+      break;
+  }
+  return result
+}
+
+//触发器动作类型
+export const getTriggerActionType = (actionType) => {
+  let result = ''
+  switch (actionType) {
+    case 101:
+      result = '赠送物品'
+      break;
+    case 201:
+      result = '赠送货币'
+      break;
+    case 301:
+      result = '赠送积分'
+      break;
+
+    default:
+      break;
+  }
+  return result
+}
+
+//积分类型
+export const getScoreType = (scoreType) => {
+  let result = ''
+  switch (scoreType) {
+    case 11:
+      result = '财富值'
+      break;
+    case 21:
+      result = '魅力值'
+      break;
+
+    default:
+      break;
+  }
+  return result
+}
+
+//反馈类型
+export const getFeedType = (feedType) => {
+  let result = ''
+  switch (feedType) {
+    case 101:
+      result = '登录问题'
+      break;
+    case 201:
+      result = '使用咨询'
+      break;
+    case 301:
+      result = '充值问题'
+      break;
+    case 401:
+      result = '提现问题'
+      break;
+    case 501:
+      result = '功能建议'
+      break;
+    case 601:
+      result = '其他'
+      break;
+
+    default:
+      break;
+  }
+  return result
+}
+
+// 靓号等级
+export const niceNoLevelArray = [
+  { key: 1, value: '经典' },
+  { key: 2, value: '史诗' },
+  { key: 3, value: '传奇' },
+]
+export const getNiceNoLevel = (key) => {
+  const obj = niceNoLevelArray.find(item => item.key === key)
+  return obj ? obj.value : '-'
+}
+
+export { showSuccess, showError, showInfo } from './notice'
+export const encryptMd5 = MD5.encryptMd5
+
+// 平台统计类型
+export const statPlatformTypeArray = [
+  { key: 10100, value: '下载次数' },
+  { key: 10102, value: 'iOS下载次数' },
+  { key: 10103, value: 'Android下载次数' },
+  { key: 10200, value: '安装数' },
+  { key: 10202, value: 'iOS安装数' },
+  { key: 10203, value: 'Android安装数' },
+  { key: 10300, value: '注册用户数' },
+  { key: 10302, value: 'iOS注册用户数' },
+  { key: 10303, value: 'Android注册用户数' },
+  { key: 10400, value: '安装当日注册用户数' },
+  { key: 10402, value: 'iOS安装当日注册用户数' },
+  { key: 10403, value: 'Android安装当日注册用户数' },
+  { key: 10500, value: '活跃用户数' },
+  { key: 10502, value: 'iOS活跃用户数' },
+  { key: 10503, value: 'Android活跃用户数' },
+  { key: 10600, value: '进房用户数' },
+  { key: 10700, value: '认证主播数' },
+  { key: 10800, value: '开播主播数' },
+  { key: 10900, value: '认证当日开播主播数' },
+  { key: 11000, value: '直播分享次数' },
+  { key: 11100, value: '直播观看时长' },
+  { key: 20100, value: '充值次数' },
+  { key: 20200, value: '充值用户数' },
+  { key: 20300, value: '首充用户数' },
+  { key: 20400, value: '注册当日首充用户数' },
+  { key: 21100, value: '充值金额' },
+  { key: 21200, value: '首充金额' },
+  { key: 21300, value: '注册当日首充金额' },
+  { key: 21400, value: '礼物消耗金币' },
+  { key: 21500, value: '幸运礼物消耗金币' },
+  { key: 21600, value: '幸运礼物中奖金币' },
+  { key: 21700, value: '抽奖活动消耗金币'},
+  { key: 21800, value: '抽奖活动中奖价值'},
+  { key: 22101, value: '增加星钻'},
+  { key: 22102, value: '消耗星钻'},
+  { key: 22201, value: '增加星元'},
+  { key: 22202, value: '消耗星元'},
+  { key: 22301, value: '增加星币'},
+  { key: 22302, value: '消耗星币'},
+  { key: 30100, value: '开通贵族'},
+  { key: 30101, value: '开通男爵'},
+  { key: 30102, value: '开通子爵'},
+  { key: 30103, value: '开通伯爵'},
+  { key: 30104, value: '开通候爵'},
+  { key: 30105, value: '开通公爵'},
+  { key: 30106, value: '开通国王'},
+  { key: 30107, value: '开通帝皇'},
+  { key: 30200, value: '续费贵族'},
+  { key: 30201, value: '续费男爵'},
+  { key: 30202, value: '续费子爵'},
+  { key: 30203, value: '续费伯爵'},
+  { key: 30204, value: '续费候爵'},
+  { key: 30205, value: '续费公爵'},
+  { key: 30206, value: '续费国王'},
+  { key: 30207, value: '续费帝皇'},
+  { key: 30300, value: '升级贵族'},
+  { key: 30301, value: '升级男爵'},
+  { key: 30302, value: '升级子爵'},
+  { key: 30303, value: '升级伯爵'},
+  { key: 30304, value: '升级候爵'},
+  { key: 30305, value: '升级公爵'},
+  { key: 30306, value: '升级国王'},
+  { key: 30307, value: '升级帝皇'},
+]
+export const getStatPlatformType = (key) => {
+  const obj = statPlatformTypeArray.find(item => item.key === key)
+  return obj ? obj.value : '-'
+}
+
+// 推流状态
+export const streamStatusArray = [
+  { key: 101, value: '推流中' },
+  { key: 201, value: '已关闭' },
+]
+export const getStreamStatusStr = (key) => {
+  const obj = streamStatusArray.find(item => item.key === key)
+  return obj ? obj.value : '-'
+}
+// 统计时间类型
+export const stateTimeTypeArray = [
+  { key: 4, value: '年' },
+  { key: 6, value: '月' },
+  { key: 8, value: '日' },
+  { key: 10, value: '时' },
+]
+export const getStateTimeTypeArray = (key) => {
+  const obj = stateTimeTypeArray.find(item => item.key === key)
+  return obj ? obj.value : '-'
+}
+
+// 时间格式化
+export const formatTimeLabel = (time) => {
+  let timeStr = time + ''
+  let timeStrLen = timeStr.length
+   
+  // 年
+  if (timeStrLen === 4) {
+    return timeStr + '年'
+  }
+
+  // 月
+  if (timeStrLen === 6) {
+    return `${parseInt(timeStr.substr(0, 4))}年${parseInt(timeStr.substr(4))}月`
+  }
+
+  // 日
+  if (timeStrLen === 8) {
+    let timeStr2 = timeStr.substr(4)
+    return `${parseInt(timeStr.substr(0, 4))}年${parseInt(timeStr2.substr(0, 2))}月${parseInt(timeStr2.substr(2, 2))}日`
+  }
+
+  // 时
+  if (timeStrLen === 10) {
+    let timeStr2 = timeStr.substr(4)
+    let year = timeStr.substr(0, 4)
+    let month = parseInt(timeStr2.substr(0, 2))
+    let day = parseInt(timeStr2.substr(2, 2))
+    let hours = parseInt(timeStr2.substr(4))
+    
+    return `${year}年${month}月${day}日${hours}时`
+  }
+
+  return '-'
+}
+
+// 失效时间 格式化
+export const formatExpireTime = (timeType, timeStamp) => {
+  // timeType 0 永久; 1 输入 x天 xx小时
+  if (!timeType) {
+    return {
+      timeType: 0
+    }
+  }
+  const _result = {
+    timeType: 1
+  }
+  if (!timeStamp) {
+    return _result
+  }
+  const baseStamp = parseInt(timeStamp / 1000) 
+  _result.days = parseInt(baseStamp / (3600 * 24))
+  const baseHour = baseStamp % (3600 * 24)
+  _result.hours = parseInt(baseHour / 3600)
+  return _result
+}
+
+//道具动作类型
+export const getPropsAction = (propsAction) => {
+  let result = ''
+  switch (propsAction) {
+    case 0:
+      result = '无'
+      break;
+    case 101:
+      result = '直接使用'
+      break;
+    case 102:
+      result = '跳转URL'
+      break;
+    case 103:
+      result = '跳转本地'
+      break;
+  }
+  return result
+}
+
+//触发事件
+export const getTriggerEvent = (triggerEvent) => {
+  let result = ''
+  switch (triggerEvent) {
+    case 1001:
+      result = '用户注册'
+      break;
+    case 1002:
+      result = '用户注册'
+      break;
+    case 2001:
+      result = '用户充值'
+      break;
+    case 3001:
+      result = '开通贵族'
+      break;
+    case 3002:
+      result = '续费贵族'
+      break;
+    case 3003:
+      result = '升级贵族'
+      break;
+    case 4001:
+      result = '积分升级'
+      break;
+  }
+  return result
+}
+
+//state存值
+export const setValue = function (name, value ) {
+  const _state = this.state
+  _state[name] = value
+  this.setState(_state)
+}
+
+export const setEventValue = function (e, value) {
+  const _state = this.state
+  _state[name] = e.target.value
+  this.setState(_state)
+}

+ 28 - 0
src/utils/enums.js

@@ -0,0 +1,28 @@
+
+// 贵族等级
+export const nobleArray = [
+  { key: 1, value: '男爵' },
+  { key: 2, value: '子爵' },
+  { key: 3, value: '伯爵' },
+  { key: 4, value: '侯爵' },
+  { key: 5, value: '公爵' },
+  { key: 6, value: '国王' },
+  { key: 7, value: '帝皇' },
+]
+
+export const getNobleLevelStr = (key) => {
+  const obj = nobleArray.find(item => item.key === key)
+  return obj ? obj.value : '-'
+}
+
+// 钱包类型
+export const walletTypeArray = [
+  { key: 11, value: '星钻' },
+  { key: 12, value: '星元' },
+  { key: 21, value: '星币' },
+]
+
+export const getWalletTypeStr = (key) => {
+  const obj = walletTypeArray.find(item => item.key === key)
+  return obj ? obj.value : '-'
+}

+ 16 - 0
src/utils/notice.js

@@ -0,0 +1,16 @@
+import { notification } from 'antd'
+export const showSuccess = (message, options = {}) => {
+  notification.success({
+    message, ...options
+  })
+}
+export const showError = (message, options = {}) => {
+  notification.error({
+    message, ...options
+  })
+}
+export const showInfo = (message, options = {}) => {
+  notification.info({
+    message, ...options
+  })
+}

文件差异内容过多而无法显示
+ 0 - 0
src/utils/qiniu-web.js


+ 146 - 0
src/utils/request.js

@@ -0,0 +1,146 @@
+/**
+ * request 网络请求工具
+ * 更详细的 api 文档: https://github.com/umijs/umi-request
+ */
+import { extend } from 'umi-request';
+import { stringify } from 'querystring';
+import Cookie from './Cookie';
+import Security from './Security'
+import { Base64 } from './base'
+
+const codeMessage = {
+  200: '服务器成功返回请求的数据。',
+  201: '新建或修改数据成功。',
+  202: '一个请求已经进入后台排队(异步任务)。',
+  204: '删除数据成功。',
+  400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
+  401: '用户没有权限(令牌、用户名、密码错误)。',
+  403: '用户得到授权,但是访问是被禁止的。',
+  404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
+  406: '请求的格式不可得。',
+  410: '请求的资源被永久删除,且不会再得到的。',
+  422: '当创建一个对象时,发生一个验证错误。',
+  500: '服务器发生错误,请检查服务器。',
+  502: '网关错误。',
+  503: '服务不可用,服务器暂时过载或维护。',
+  504: '网关超时。',
+};
+
+/**
+ * 异常处理程序
+ */
+const errorHandler = error => {
+  const { response } = error;
+
+  if (response && response.status) {
+    const errorText = codeMessage[response.status] || response.statusText;
+    const { status, url } = response;
+    // notification.error({
+    //   message: `请求错误 ${status}: ${url}`,
+    //   description: errorText,
+    // });
+  } else if (!response) {
+    // notification.error({
+    //   description: '您的网络发生异常,无法连接服务器',
+    //   message: '网络异常',
+    // });
+  }
+
+  return response;
+};
+
+/**
+ * 配置request请求时的默认参数
+ */
+const request = extend({
+  errorHandler,
+  // 默认错误处理
+  credentials: 'include', // 默认请求是否带上cookie
+});
+
+/**
+ * 请求头处理
+ */
+const getHeader = () => {
+
+  // 通用参数
+  const commonParams = {
+    a: 11,
+    av: '1.0.0',
+    c: 4,
+    ci: '0',
+    di: '1a00bf0e978c458f7620b390714b26cc',
+    lat: '',
+    lng: '',
+    pm: '',
+    pn: 'com.starbuds.h5',
+    st: 1,
+    sv: '',
+    t: Cookie.get('_xy_tk_h5', { path: '/' }) || '',
+    ts: new Date().getTime()
+  };
+
+  let toSign = '';
+  Object.keys(commonParams).map((key)=> {
+    toSign += '&' + key + '=' + commonParams[key];
+  });
+  toSign = toSign.substr(1) + commonParams.pn;
+  var sign = Security.encryptMd5(toSign + ".security");
+  commonParams.s = sign;
+  var pkg = Base64.encode(JSON.stringify(commonParams));
+
+  return { pkg };
+};
+
+/**
+ * 请求拦截器 处理请求头
+ */
+request.interceptors.request.use(async (url, options) => {
+  let headers = getHeader();
+  const method = options.method.toUpperCase();
+  if (method === 'POST' || method === 'PUT' || method === 'DELETE') {
+    if (!(options.body instanceof FormData)) {
+      headers = {
+        Accept: 'application/json',
+        'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
+        ...headers,
+      };
+      options.body = stringify(options.body);
+    } else {
+      headers = {
+        Accept: 'application/json',
+        ...headers,
+      };
+    }
+  }
+
+  return {
+    url,
+    options: { ...options, headers },
+  };
+});
+
+/**
+ * 请求结果拦截器
+ */
+request.interceptors.response.use(async (response, options) => {
+  const res = await response.json();
+  if (!res.success) {
+    // notification.error({
+    //   message: '请求错误',
+    //   description: res.msg,
+    // });
+
+    // 令牌登录失败
+    if (res.code === 'TOKEN_INVALID') {
+      Cookie.remove('_xy_tk_h5', { path: '/' });
+      window.g_app._store.dispatch({
+        type: 'login/logout',
+      });
+      return res;
+    }
+  }
+  return res;
+});
+
+export default request;

+ 55 - 0
src/utils/utils.js

@@ -0,0 +1,55 @@
+import { parse } from 'querystring';
+import pathRegexp from 'path-to-regexp';
+
+/* eslint no-useless-escape:0 import/prefer-default-export:0 */
+const reg = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/;
+export const isUrl = path => reg.test(path);
+export const isAntDesignPro = () => {
+  if (ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site') {
+    return true;
+  }
+
+  return window.location.hostname === 'preview.pro.ant.design';
+}; // 给官方演示站点用,用于关闭真实开发环境不需要使用的特性
+
+export const isAntDesignProOrDev = () => {
+  const { NODE_ENV } = process.env;
+
+  if (NODE_ENV === 'development') {
+    return true;
+  }
+
+  return isAntDesignPro();
+};
+export const getPageQuery = () => parse(window.location.href.split('?')[1]);
+/**
+ * props.route.routes
+ * @param router [{}]
+ * @param pathname string
+ */
+
+export const getAuthorityFromRouter = (router = [], pathname) => {
+  const authority = router.find(({ path }) => path && pathRegexp(path).exec(pathname));
+  if (authority) return authority;
+  return undefined;
+};
+export const getRouteAuthority = (path, routeData) => {
+  let authorities;
+  routeData.forEach(route => {
+    // match prefix
+    if (pathRegexp(`${route.path}/(.*)`).test(`${path}/`)) {
+      if (route.authority) {
+        authorities = route.authority;
+      } // exact match
+
+      if (route.path === path) {
+        authorities = route.authority || authorities;
+      } // get children authority recursively
+
+      if (route.routes) {
+        authorities = getRouteAuthority(path, route.routes) || authorities;
+      }
+    }
+  });
+  return authorities;
+};

+ 50 - 0
src/utils/utils.less

@@ -0,0 +1,50 @@
+.textOverflow() {
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  word-break: break-all;
+}
+
+.textOverflowMulti(@line: 3, @bg: #fff) {
+  position: relative;
+  max-height: @line * 1.5em;
+  margin-right: -1em;
+  padding-right: 1em;
+  overflow: hidden;
+  line-height: 1.5em;
+  text-align: justify;
+  &::before {
+    position: absolute;
+    right: 14px;
+    bottom: 0;
+    padding: 0 1px;
+    background: @bg;
+    content: '...';
+  }
+  &::after {
+    position: absolute;
+    right: 14px;
+    width: 1em;
+    height: 1em;
+    margin-top: 0.2em;
+    background: white;
+    content: '';
+  }
+}
+
+// mixins for clearfix
+// ------------------------
+.clearfix() {
+  zoom: 1;
+  &::before,
+  &::after {
+    display: table;
+    content: ' ';
+  }
+  &::after {
+    clear: both;
+    height: 0;
+    font-size: 0;
+    visibility: hidden;
+  }
+}

+ 115 - 0
src/utils/utils.test.js

@@ -0,0 +1,115 @@
+import { isUrl, getRouteAuthority } from './utils';
+describe('isUrl tests', () => {
+  it('should return false for invalid and corner case inputs', () => {
+    expect(isUrl([])).toBeFalsy();
+    expect(isUrl({})).toBeFalsy();
+    expect(isUrl(false)).toBeFalsy();
+    expect(isUrl(true)).toBeFalsy();
+    expect(isUrl(NaN)).toBeFalsy();
+    expect(isUrl(null)).toBeFalsy();
+    expect(isUrl(undefined)).toBeFalsy();
+    expect(isUrl('')).toBeFalsy();
+  });
+  it('should return false for invalid URLs', () => {
+    expect(isUrl('foo')).toBeFalsy();
+    expect(isUrl('bar')).toBeFalsy();
+    expect(isUrl('bar/test')).toBeFalsy();
+    expect(isUrl('http:/example.com/')).toBeFalsy();
+    expect(isUrl('ttp://example.com/')).toBeFalsy();
+  });
+  it('should return true for valid URLs', () => {
+    expect(isUrl('http://example.com/')).toBeTruthy();
+    expect(isUrl('https://example.com/')).toBeTruthy();
+    expect(isUrl('http://example.com/test/123')).toBeTruthy();
+    expect(isUrl('https://example.com/test/123')).toBeTruthy();
+    expect(isUrl('http://example.com/test/123?foo=bar')).toBeTruthy();
+    expect(isUrl('https://example.com/test/123?foo=bar')).toBeTruthy();
+    expect(isUrl('http://www.example.com/')).toBeTruthy();
+    expect(isUrl('https://www.example.com/')).toBeTruthy();
+    expect(isUrl('http://www.example.com/test/123')).toBeTruthy();
+    expect(isUrl('https://www.example.com/test/123')).toBeTruthy();
+    expect(isUrl('http://www.example.com/test/123?foo=bar')).toBeTruthy();
+    expect(isUrl('https://www.example.com/test/123?foo=bar')).toBeTruthy();
+  });
+});
+describe('getRouteAuthority tests', () => {
+  it('should return authority for each route', () => {
+    const routes = [
+      {
+        path: '/user',
+        name: 'user',
+        authority: ['user'],
+        exact: true,
+      },
+      {
+        path: '/admin',
+        name: 'admin',
+        authority: ['admin'],
+        exact: true,
+      },
+    ];
+    expect(getRouteAuthority('/user', routes)).toEqual(['user']);
+    expect(getRouteAuthority('/admin', routes)).toEqual(['admin']);
+  });
+  it('should return inherited authority for unconfigured route', () => {
+    const routes = [
+      {
+        path: '/nested',
+        authority: ['admin', 'user'],
+        exact: true,
+      },
+      {
+        path: '/nested/user',
+        name: 'user',
+        exact: true,
+      },
+    ];
+    expect(getRouteAuthority('/nested/user', routes)).toEqual(['admin', 'user']);
+  });
+  it('should return authority for configured route', () => {
+    const routes = [
+      {
+        path: '/nested',
+        authority: ['admin', 'user'],
+        exact: true,
+      },
+      {
+        path: '/nested/user',
+        name: 'user',
+        authority: ['user'],
+        exact: true,
+      },
+      {
+        path: '/nested/admin',
+        name: 'admin',
+        authority: ['admin'],
+        exact: true,
+      },
+    ];
+    expect(getRouteAuthority('/nested/user', routes)).toEqual(['user']);
+    expect(getRouteAuthority('/nested/admin', routes)).toEqual(['admin']);
+  });
+  it('should return authority for substring route', () => {
+    const routes = [
+      {
+        path: '/nested',
+        authority: ['user', 'users'],
+        exact: true,
+      },
+      {
+        path: '/nested/users',
+        name: 'users',
+        authority: ['users'],
+        exact: true,
+      },
+      {
+        path: '/nested/user',
+        name: 'user',
+        authority: ['user'],
+        exact: true,
+      },
+    ];
+    expect(getRouteAuthority('/nested/user', routes)).toEqual(['user']);
+    expect(getRouteAuthority('/nested/users', routes)).toEqual(['users']);
+  });
+});

+ 12 - 0
webpack.config.js

@@ -0,0 +1,12 @@
+/**
+ * 不是真实的 webpack 配置,仅为兼容 webstorm 和 intellij idea 代码跳转
+ * ref: https://github.com/umijs/umi/issues/1109#issuecomment-423380125
+ */
+
+module.exports = {
+  resolve: {
+    alias: {
+      '@': require('path').resolve(__dirname, 'src'),
+    },
+  },
+};

部分文件因为文件数量过多而无法显示