WuJunFeng 4 years ago
commit
71ba795556
78 changed files with 2143 additions and 0 deletions
  1. 16 0
      .editorconfig
  2. 1 0
      .env
  3. 3 0
      .eslintrc
  4. 20 0
      .gitignore
  5. 7 0
      .prettierignore
  6. 11 0
      .prettierrc
  7. 89 0
      .umirc.js
  8. 23 0
      .umirc.test.js
  9. 11 0
      .vscode/sftp.json
  10. 28 0
      .vscode/tasks.json
  11. 0 0
      mock/.gitkeep
  12. 46 0
      package.json
  13. 0 0
      public/living-bg.json
  14. 0 0
      public/qplayer-web-player.js
  15. 8 0
      src/app.js
  16. BIN
      src/assets/android.png
  17. BIN
      src/assets/apple.png
  18. BIN
      src/assets/bg.png
  19. BIN
      src/assets/bg_header_recharge.png
  20. BIN
      src/assets/download_left.png
  21. BIN
      src/assets/download_right.png
  22. BIN
      src/assets/download_title.png
  23. BIN
      src/assets/fans_first_bg.png
  24. BIN
      src/assets/icon-err.png
  25. BIN
      src/assets/icon-onPlay copy.png
  26. BIN
      src/assets/icon-onPlay.png
  27. BIN
      src/assets/icon-warning.png
  28. BIN
      src/assets/icon_add_red.png
  29. BIN
      src/assets/icon_box.png
  30. BIN
      src/assets/icon_close_dark.png
  31. BIN
      src/assets/icon_coin.png
  32. BIN
      src/assets/icon_download.png
  33. BIN
      src/assets/icon_gift.png
  34. BIN
      src/assets/icon_grade.png
  35. BIN
      src/assets/icon_logout.png
  36. BIN
      src/assets/icon_manager.png
  37. BIN
      src/assets/icon_message.png
  38. BIN
      src/assets/icon_share.png
  39. BIN
      src/assets/img_recharge.png
  40. BIN
      src/assets/img_sexy_button.png
  41. BIN
      src/assets/img_sexy_button2.png
  42. BIN
      src/assets/logo.png
  43. BIN
      src/assets/sexy_bg.png
  44. BIN
      src/assets/tag_1.png
  45. BIN
      src/assets/wx_tips_android.png
  46. 199 0
      src/common.less
  47. 61 0
      src/components/MyLottie.js
  48. 17 0
      src/components/UserRole/UserRole.js
  49. 23 0
      src/components/UserRole/UserRole.less
  50. BIN
      src/components/UserRole/assets/role1.png
  51. BIN
      src/components/UserRole/assets/role2.png
  52. BIN
      src/components/UserRole/assets/role3.png
  53. BIN
      src/components/UserRole/assets/role4.png
  54. BIN
      src/components/UserRole/assets/role5.png
  55. 46 0
      src/components/Wrap/Wrap.jsx
  56. 34 0
      src/components/Wrap/Wrap.less
  57. BIN
      src/components/Wrap/assets/icon_empty.png
  58. 1 0
      src/components/index.js
  59. 35 0
      src/global.less
  60. 14 0
      src/layouts/__tests__/index.test.js
  61. 7 0
      src/layouts/index.css
  62. 20 0
      src/layouts/index.js
  63. 0 0
      src/models/.gitkeep
  64. 145 0
      src/pages/Download/Download.js
  65. 118 0
      src/pages/Download/Download.less
  66. 14 0
      src/pages/__tests__/index.test.js
  67. 16 0
      src/pages/document.ejs
  68. 61 0
      src/services/api.js
  69. 175 0
      src/utils/Cookie.js
  70. 88 0
      src/utils/Format.js
  71. 33 0
      src/utils/Help.js
  72. 229 0
      src/utils/Security.js
  73. 11 0
      src/utils/Url.js
  74. 26 0
      src/utils/UserAgent.js
  75. 242 0
      src/utils/base.js
  76. 103 0
      src/utils/longSock.js
  77. 150 0
      src/utils/request.js
  78. 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 @@
+BROWSER=none

+ 3 - 0
.eslintrc

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

+ 20 - 0
.gitignore

@@ -0,0 +1,20 @@
+# 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
+/live-download-h5
+live-download-h5.zip

+ 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": "all",
+  "printWidth": 100,
+  "overrides": [
+    {
+      "files": ".prettierrc",
+      "options": { "parser": "json" }
+    }
+  ]
+}

+ 89 - 0
.umirc.js

@@ -0,0 +1,89 @@
+
+// ref: https://umijs.org/config/
+export default {
+  treeShaking: true,
+  routes: [
+    {
+      path: '/',
+      component: '../layouts/index',
+      routes: [
+        { path: '/', component: '../pages/Download/Download' },
+        { path: '/:ci', component: '../pages/Download/Download' },
+      ]
+    }
+  ],
+  plugins: [
+    // ref: https://umijs.org/plugin/umi-plugin-react.html
+    ['umi-plugin-react', {
+      antd: false,
+      dva: true,
+      dynamicImport: false,
+      title: '',
+      dll: false,
+      hd: true,
+      routes: {
+        exclude: [
+          /models\//,
+          /services\//,
+          /model\.(t|j)sx?$/,
+          /service\.(t|j)sx?$/,
+          /components\//,
+        ],
+      },
+      headScripts: [
+        //react
+        '//sp.qn.paqukeji.com/static/react/16.9.0/react.production.min.js',
+        //react-dom
+        '//sp.qn.paqukeji.com/static/react/16.9.0/react-dom.production.min.js',
+        //antd-mobile
+        '//sp.qn.paqukeji.com/static/antd-mobile/2.3.1/antd-mobile.min.js',
+        // //lottie动画
+        // '//sp.qn.paqukeji.com/static/bodymovin/5.6.8/lottie.min.js'
+      ],
+      //CDN引入
+      links: [
+        { href: '//sp.qn.paqukeji.com/static/antd-mobile/2.3.1/antd-mobile.min.css', rel: 'stylesheet' }
+      ],
+    }],
+  ],
+  lessLoaderOptions: {
+    javascriptEnabled: true,
+  },
+  extraBabelPlugins: [
+    ["import", { libraryName: "antd-mobile", style: true }] // `style: true` 会加载 less 文件
+  ],
+  proxy: {
+    '/api-app': {
+      //target: 'http://api.starbuds.laylib.com',
+      target: 'http://h5.paqukeji.com',
+      changeOrigin: true,
+      //pathRewrite: { '^/server': '' },
+    },
+    '/api-common': {
+      //target: 'http://api.starbuds.laylib.com',
+      target: 'http://h5.paqukeji.com',
+      changeOrigin: true,
+      //pathRewrite: { '^/server': '' },
+    },
+    '/wx': {
+      target: 'https://api.weixin.qq.com',
+      //target: 'http://h5.paqukeji.com',
+      changeOrigin: true,
+      pathRewrite: { '^/wx': '' },
+    },
+  },
+  hash: true,
+  outputPath: './live-download-h5',
+  publicPath: '/', // '//sp.qn.paqukeji.com/live-h5/',
+  externals: {
+    'react': 'React',
+    'react-dom': 'ReactDOM',
+    'antd-mobile': 'antd-mobile',
+    'lottie-web': 'lottie'
+  },
+  theme: {
+    "brand-primary": "#ff4d5cff",
+    "color-text-base": "#333",
+    "brand-primary-tap": "rgba(255, 77, 92, 0.8)"
+  },
+}

+ 23 - 0
.umirc.test.js

@@ -0,0 +1,23 @@
+export default {
+  publicPath: '/',
+  proxy: {
+    '/api-app': {
+      target: 'http://api.starbuds.laylib.com',
+      //target: 'http://h5.paqukeji.com',
+      changeOrigin: true,
+      //pathRewrite: { '^/server': '' },
+    },
+    '/api-common': {
+      target: 'http://api.starbuds.laylib.com',
+      //target: 'http://h5.paqukeji.com',
+      changeOrigin: true,
+      //pathRewrite: { '^/server': '' },
+    },
+    '/wx': {
+      target: 'https://api.weixin.qq.com',
+      //target: 'http://h5.paqukeji.com',
+      changeOrigin: true,
+      pathRewrite: { '^/wx': '' },
+    },
+  },
+}

+ 11 - 0
.vscode/sftp.json

@@ -0,0 +1,11 @@
+{
+  "name": "好艺拍-h5",
+  "host": "121.40.145.19",
+  "protocol": "sftp",
+  "port": 22,
+  "username": "root",
+  "context": "/Users/wuzongwen/Git/Paqu/h5/live-h5/live-h5",
+  "remotePath": "/data/webroot/h5/live",
+  "uploadOnSave": true,
+  "password": "iPaibao2019"
+}

+ 28 - 0
.vscode/tasks.json

@@ -0,0 +1,28 @@
+{
+// 有关 tasks.json 格式的文档,请参见
+  // https://go.microsoft.com/fwlink/?LinkId=733558
+  "version": "2.0.0",
+  "tasks": [
+    {
+      "type": "npm",
+      "script": "build",
+      "group": "build",
+      "problemMatcher": []
+    },
+    {
+      "type": "npm",
+      "script": "buildTest",
+      "group": "build",
+      "problemMatcher": [],
+      "label": "npm: buildTest",
+      "detail": "UMI_ENV=buildTest umi build"
+    },
+    {
+      "type": "npm",
+      "script": "startTest",
+      "problemMatcher": [],
+      "label": "npm: startTest",
+      "detail": "UMI_ENV=buildTest umi build"
+    }
+  ]
+}

+ 0 - 0
mock/.gitkeep


+ 46 - 0
package.json

@@ -0,0 +1,46 @@
+{
+  "private": true,
+  "scripts": {
+    "start": "umi dev",
+    "startTest": "UMI_ENV=test umi dev",
+    "build": "umi build",
+    "buildTest": "UMI_ENV=test umi build",
+    "test": "umi test",
+    "lint": "eslint --ext .js src mock tests"
+  },
+  "dependencies": {
+    "dva": "^2.6.0-beta.6",
+    "react": "^16.8.6",
+    "react-dom": "^16.8.6",
+    "react-player": "^2.1.1",
+    "ua-parser-js": "^0.7.21",
+    "umi-request": "^1.2.17"
+  },
+  "devDependencies": {
+    "antd-mobile": "^2.3.1",
+    "babel-eslint": "^9.0.0",
+    "babel-plugin-import": "^1.13.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",
+    "lottie-web": "^5.6.2",
+    "react-test-renderer": "^16.7.0",
+    "umi": "^2.7.7",
+    "umi-plugin-react": "^1.8.4",
+    "vconsole": "^3.3.4"
+  },
+  "lint-staged": {
+    "*.{js,jsx}": [
+      "eslint --fix",
+      "git add"
+    ]
+  },
+  "engines": {
+    "node": ">=8.0.0"
+  }
+}

File diff suppressed because it is too large
+ 0 - 0
public/living-bg.json


File diff suppressed because it is too large
+ 0 - 0
public/qplayer-web-player.js


+ 8 - 0
src/app.js

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

BIN
src/assets/android.png


BIN
src/assets/apple.png


BIN
src/assets/bg.png


BIN
src/assets/bg_header_recharge.png


BIN
src/assets/download_left.png


BIN
src/assets/download_right.png


BIN
src/assets/download_title.png


BIN
src/assets/fans_first_bg.png


BIN
src/assets/icon-err.png


BIN
src/assets/icon-onPlay copy.png


BIN
src/assets/icon-onPlay.png


BIN
src/assets/icon-warning.png


BIN
src/assets/icon_add_red.png


BIN
src/assets/icon_box.png


BIN
src/assets/icon_close_dark.png


BIN
src/assets/icon_coin.png


BIN
src/assets/icon_download.png


BIN
src/assets/icon_gift.png


BIN
src/assets/icon_grade.png


BIN
src/assets/icon_logout.png


BIN
src/assets/icon_manager.png


BIN
src/assets/icon_message.png


BIN
src/assets/icon_share.png


BIN
src/assets/img_recharge.png


BIN
src/assets/img_sexy_button.png


BIN
src/assets/img_sexy_button2.png


BIN
src/assets/logo.png


BIN
src/assets/sexy_bg.png


BIN
src/assets/tag_1.png


BIN
src/assets/wx_tips_android.png


+ 199 - 0
src/common.less

@@ -0,0 +1,199 @@
+
+html, body, #root {
+  height: 100%;
+}
+
+body {
+  margin: 0;
+  height: 100%;
+  font-family: PingFangSC-Regular,PingFangSC-Medium,Helvetica,HelveticaNeue,Arial,Verdana,Sans-serif;
+  word-break: break-all;
+}
+
+dl, dd, dt, p, h1, h2, h3, h4, h5, h6, ul, li, ol {
+  padding: 0; margin: 0;
+}
+ul, ol, li {
+  list-style: none;
+}
+h1, h2, h3, h4, h5, h6 {
+  font-weight: 500;
+}
+
+@color-333: #333;
+
+.hidden {
+  display: none !important;
+}
+
+.color-fff {
+  color: #fff !important;
+}
+
+.color-white {
+  color: #fff !important;
+}
+
+.color-primary {
+  color: #ff4d5b;
+}
+
+.color-333 {
+  color: #333;
+}
+
+.color-999 {
+  color: #999;
+}
+
+.color-ccc {
+  color: #ccc;
+}
+
+.color-green {
+  color: #1fb922;
+}
+
+.font-20 {
+  font-size: 20px !important;
+}
+
+.font-24 {
+  font-size: 24px !important;
+}
+
+.font-26 {
+  font-size: 26px !important;
+}
+
+.font-28 {
+  font-size: 28px !important;
+}
+
+.font-30 {
+  font-size: 30px !important;
+}
+
+.font-44 {
+  font-size: 44px !important;
+}
+
+.font-48 {
+  font-size: 48px !important;
+}
+
+.border-bottom {
+  border-bottom: 1px solid #f2f2ff;
+}
+
+.font-weight-500 {
+  font-weight: 500;
+}
+
+.line-h-1 {
+  line-height: 1;
+}
+
+.base-line-height {
+  line-height: 1.5;
+}
+
+//灰色遮罩
+.mask {
+  position: fixed;
+  top: 0px;
+  right: 0px;
+  bottom: 0px;
+  left: 0px;
+  z-index: 1000;
+  background-color: rgba(0, 0, 0, 0.4);
+}
+
+.no-height {
+  height: 0px !important;
+}
+
+.padding-30 {
+  padding: 30px;
+}
+
+.border-bottom {
+  border-bottom: 1px solid #f5f5f5;
+}
+
+.word-break-all {
+  word-break: break-all;
+}
+
+.text-overflow-hidden {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.vertical-align-middle {
+  vertical-align: middle;
+}
+
+.line-2 {
+  overflow: hidden;
+  display: -webkit-box;
+  line-height: 1.5;
+  -webkit-line-clamp: 2;
+  /*! autoprefixer: off */
+ -webkit-box-orient: vertical;
+/* autoprefixer: on */
+}
+
+.line-3 {
+  overflow: hidden;
+  display: -webkit-box;
+  -webkit-line-clamp: 3;
+  /*! autoprefixer: off */
+ -webkit-box-orient: vertical;
+/* autoprefixer: on */
+}
+
+.line-12 {
+  overflow: hidden;
+  display: -webkit-box;
+  -webkit-line-clamp: 12;
+  /*! autoprefixer: off */
+ -webkit-box-orient: vertical;
+/* autoprefixer: on */
+}
+
+.position-relative {
+  position: relative;
+}
+
+.text-align-left {
+  text-align: left;
+}
+
+.text-align-right {
+  text-align: right;
+}
+.text-align-center {
+  text-align: center;
+}
+
+.text-line-through {
+  text-decoration: line-through;
+}
+
+.bg-fff {
+  background-color: #fff;
+}
+
+.border-radius-10 {
+  border-radius: 10px;
+}
+
+.margin-top-20 {
+  margin-top: 20px;
+}
+
+.margin-top-30 {
+  margin-top: 30px;
+}

+ 61 - 0
src/components/MyLottie.js

@@ -0,0 +1,61 @@
+
+//<MyLottie path='./living-bg.json' style={{ width: '100%', height: '100%', position: 'absolute', top: 0, left: 0, zIndex: 2 }}/>
+import React from 'react';
+import lottie from 'lottie-web';
+
+class MyLottie extends React.Component {
+
+  componentDidMount() {
+    this.renderLottie()
+  }
+
+  renderLottie() {
+    const { path } = this.props;
+    if (path) {
+      this.timer = setTimeout(() => {
+        this.lottie = lottie.loadAnimation({
+          container: document.getElementById('lottie'), //this.lottieRef, // the dom element that will contain the animation
+          renderer: 'svg',
+          loop: true,
+          autoplay: true,
+          path, // the path to the animation json
+        });
+      }, 300);
+    }
+  }
+
+  componentWillUnmount() {
+    if (this.lottie) {
+      this.lottie.destroy();
+    }
+    if (this.timer) {
+      clearTimeout(this.timer);
+    }
+  }
+
+  closeLottie() {
+    if (this.lottie) {
+      this.lottie.destroy();
+    }
+  }
+
+  testLog() {
+    console.log(this.state);
+  }
+
+  render() {
+    return (
+      <div
+        ref={ref => (this.lottieRef = ref)}
+        id="lottie"
+        style={this.props.style || {}}
+      ></div>
+    );
+  }
+}
+
+export default MyLottie;
+
+MyLottie.defaultProps = {
+  path: ''
+}

+ 17 - 0
src/components/UserRole/UserRole.js

@@ -0,0 +1,17 @@
+import styles from './UserRole.less'
+
+function UserRole(props) {
+  const { userRole } = props
+  return(
+    <div className={styles.userRole}>
+      {userRole < 11 && <img src={require('./assets/role1.png')} />}
+      {userRole > 10 && userRole < 21 && <img src={require('./assets/role2.png')} />}
+      {userRole > 20 && userRole < 31 && <img src={require('./assets/role3.png')} />}
+      {userRole > 30 && userRole < 41 && <img src={require('./assets/role4.png')} />}
+      {userRole > 40 && userRole < 51 && <img src={require('./assets/role5.png')} />}
+      <div className={styles.text}>{userRole}</div>
+    </div>
+  )
+}
+
+export default UserRole

+ 23 - 0
src/components/UserRole/UserRole.less

@@ -0,0 +1,23 @@
+.userRole {
+  display: inline-block;
+  width: 60px;
+  height: 32px;
+  line-height: 32px;
+  text-align: right;
+  position: relative;
+  color: #fff;
+  font-size: 20px;
+  vertical-align: sub;
+  margin-right: 8px;
+  img {
+    width: 60px;
+    height: 32px;
+    position: absolute;
+    top: 0px;
+    left: 0px;
+  }
+  .text {
+    position: absolute;
+    right: 8px;
+  }
+}

BIN
src/components/UserRole/assets/role1.png


BIN
src/components/UserRole/assets/role2.png


BIN
src/components/UserRole/assets/role3.png


BIN
src/components/UserRole/assets/role4.png


BIN
src/components/UserRole/assets/role5.png


+ 46 - 0
src/components/Wrap/Wrap.jsx

@@ -0,0 +1,46 @@
+import React from 'react'
+import { ActivityIndicator, Result, Icon, Flex } from 'antd-mobile'
+import styles from './Wrap.less'
+
+export default (props) => {
+
+  const { loading, err, errMsg, empty } = props
+
+  //渲染主体
+  const renderContent = () => {
+    //加载中
+    if (loading) {
+      return <ActivityIndicator toast text="加载中" />
+    }
+    //接口错误
+    if (err) {
+      return (
+        <div className={styles.err}>
+          <Result
+            img={<Icon type="cross-circle" className={styles['icon-err']} />}
+            title="信息错误"
+            message={errMsg}
+          />
+        </div>
+      )
+    }
+    //空页面
+    if (empty) {
+      return (
+        <Flex className={styles.empty} align="center" justify="center">
+          <div>
+            <img className={styles['icon-empty']} src={require('./assets/icon_empty.png')} />
+            <div className={styles.message}>~空数据~</div>
+          </div>
+        </Flex>
+      )
+    }
+    //正常
+    return props.children
+  }
+  return (
+    <div className={styles.wrap}>
+      {renderContent()}
+    </div>
+  )
+}

+ 34 - 0
src/components/Wrap/Wrap.less

@@ -0,0 +1,34 @@
+.wrap {
+  position: relative;
+  height  : 100%;
+
+  .icon-err {
+    width : 100px;
+    height: 100px;
+    color : #f53131;
+  }
+
+  .empty {
+    height    : 100%;
+    text-align: center;
+    margin-top: -100px;
+
+    .icon-empty {
+      width  : 400px;
+      height : 400px;
+      display: block;
+    }
+  }
+
+
+
+  .message {
+    font-size: 32px;
+    font-family: PingFangSC-Semibold,
+      PingFang SC;
+    color      : rgba(154, 154, 154, 1);
+    line-height: 1.5;
+    margin-top : 10px;
+    font-weight: 400;
+  }
+}

BIN
src/components/Wrap/assets/icon_empty.png


+ 1 - 0
src/components/index.js

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

+ 35 - 0
src/global.less

@@ -0,0 +1,35 @@
+@import './common.less';
+@import "~antd-mobile/dist/antd-mobile.less"; // 引入官方提供的 less 样式入口文件
+
+html,
+body,
+#root {
+  height: 100%;
+}
+
+body {
+  margin     : 0;
+  height     : 100%;
+  font-family: DIN Alternate, PingFangSC-Regular, PingFangSC-Medium, Helvetica, HelveticaNeue, Arial, Verdana, Sans-serif;
+  word-break : break-all;
+}
+
+@media only screen and (device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) {
+  body {
+    top   : constant(safe-area-inset-top);
+    bottom: constant(safe-area-inset-bottom);
+    left  : constant(safe-area-inset-left);
+    right : constant(safe-area-inset-right);
+  }
+}
+
+//灰色遮罩
+.mask {
+  position        : fixed;
+  top             : 0px;
+  right           : 0px;
+  bottom          : 0px;
+  left            : 0px;
+  z-index         : 1000;
+  background-color: rgba(0, 0, 0, 0.4);
+}

+ 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!');
+  });
+});

+ 7 - 0
src/layouts/index.css

@@ -0,0 +1,7 @@
+.normal {
+  font-family: PingFang SC, Georgia, sans-serif;
+  text-align : center;
+  width      : 100%;
+  height     : 100%;
+  word-break : break-all;
+}

+ 20 - 0
src/layouts/index.js

@@ -0,0 +1,20 @@
+import styles from './index.css';
+import 'antd-mobile/dist/antd-mobile.css';  // or 'antd-mobile/dist/antd-mobile.less'
+import { useEffect } from 'react';
+
+function BasicLayout(props) {
+  
+
+  useEffect(()=>{
+    //console.log(UA)
+    
+  })
+
+  return (
+    <div className={styles.normal}>
+      {props.children}
+    </div>
+  );
+}
+
+export default BasicLayout;

+ 0 - 0
src/models/.gitkeep


+ 145 - 0
src/pages/Download/Download.js

@@ -0,0 +1,145 @@
+import React, { Fragment } from 'react'
+import styles from './Download.less'
+import { Flex, Icon, Toast, Result } from 'antd-mobile'
+import { getAppLatest } from '@/services/api'
+import UserAgent from '@/utils/UserAgent'
+import { _appDownloadUrl, isIos, getChannelId } from '../../utils/base'
+import Wrap from '@/components/Wrap/Wrap'
+import iosImg from '../../assets/apple.png';
+import androidImg from '../../assets/android.png';
+import leftImg from '../../assets/download_left.png';
+import rightImg from '../../assets/download_right.png';
+
+class Download extends React.Component {
+  constructor(props) {
+    super(props)
+    this.state = {
+      showMask: false,
+      loading: false,
+      versionRemarks: '',
+      info: {},
+      channelId: getChannelId(),
+    }
+    document.title = '下载APP'
+  }
+
+  componentDidMount() {
+    if (!this.state.channelId) {
+      return
+    }
+    this.getAppLatest()
+  }
+
+  componentWillUnmount() {
+    if (this.timer) {
+      clearTimeout(this.timer)
+    }
+  }
+
+  //获取最新版本应用
+  async getAppLatest() {
+    
+    try {
+      this.setState({ loading: true })
+      const res = await getAppLatest({ clientType: UserAgent.iOS() ? 2 : 3, appType: 11 })
+      if (res.success && res.data) {
+        const { versionDownloadUrl, versionRemarks } = res.data
+        const info = res.data
+        if (versionDownloadUrl) {
+          this.setState({ loading: false, versionDownloadUrl, versionRemarks, info })
+        } else {
+          Toast.info('版本正在准备中', 1)
+          this.setState({ loading: false, noVersion: true })
+        }
+      } else if (!res.data) {
+        this.setState({ noVersion: true, loading: false })
+      } else {
+        if (!res.data && !res.msg) {
+          res.msg = '获取下载版本失败'
+        }
+        Toast.info(res.msg, 1)
+        this.setState({ loading: false, err: true, errMsg: res.msg })
+      }
+    } catch (error) {
+      this.timer = setTimeout(() => {
+        this.setState({ loading: false, noVersion: true })
+      }, 600);
+    }
+  }
+
+  onDownload = () => {
+    if (this.state.showTrust) {
+      //下载证书
+      window.location.href = 'https://starbuds.paqukeji.com/embedded.mobileprovision'
+    } else {
+      // 判断是否在微信中
+      if (UserAgent.wechat() || UserAgent.QQ()) {
+        // 微信中需要右上角浏览器打开
+        this.setState({ showMask: true })
+      } else {
+        if (UserAgent.iOS()) {
+          //统计下载
+          const script = document.createElement('script')
+          script.src = _appDownloadUrl
+          script.type = 'text/javascript'
+          document.body.appendChild(script)
+
+          this.timer = setTimeout(() => {
+            alert('您可以切换到桌面查看安装进度')
+            this.setState({ showTrust: true })
+          }, 2000)
+        } else {
+          window.location.href = _appDownloadUrl
+        }
+      }
+    }
+  }
+  render() {
+    const { showMask, loading, err, errMsg, noVersion, showTrust, info, channelId } = this.state;
+    return (
+      <Wrap err={err} errMsg={errMsg} loading={loading} empty={!channelId || noVersion}>
+        <div className={styles.download}>
+          <img className={styles.logo} src={info.appIcon || require('@/assets/logo.png')} />
+          {info.appName && (
+            <div className={styles.appName_less}>
+              <img className={styles.appName_icon} src={isIos() ? iosImg : androidImg} />
+              {info.appName}
+            </div>
+          )}
+          <Flex align="center" justify="center" className={noVersion ? 'hidden' : styles.button}>
+            {loading ? <Icon type="loading" /> : (
+              <Fragment>
+                <img className={styles.icon} src={require('@/assets/icon_download.png')} />
+                <div className={styles.text} onClick={this.onDownload.bind(this)}>
+                  <span>{showTrust ? '下载证书' : '立即下载'}</span>
+                </div>
+              </Fragment>
+            )}
+          </Flex>
+          {noVersion && <div className="color-999 font-28">版本正在准备中</div>}
+          {/* 版本说明 */}
+          <div className={styles.notice}>
+            <div className="color-999 font-24">{!showTrust ? `版本号:${info.versionCode} 包大小:${info.versionFileSize}` : '安装完成后记得回来下载证书哦'}</div>
+            <Flex justify="center" style={{ marginTop: '0.3rem' }}>
+              <div
+                className={`color-999 font-24 ${styles.remark}`}
+                dangerouslySetInnerHTML={{ __html: this.state.versionRemarks.replace(/\n/g, "<br />") }}
+              />
+            </Flex>
+          </div>
+          <div className={styles.mask} style={{ display: showMask ? '' : 'none' }}>
+            <img src={require('../../assets/wx_tips_android.png')} />
+          </div>
+          {/* <div className={styles.footer}>
+            <div className="color-999 font-24">浙江帕趣网络科技有限公司&nbsp;&copy;&nbsp;2019<br />浙ICP备19043479号</div>
+          </div> */}
+          <img className={styles.download_left} src={leftImg} />
+          <img className={styles.download_right} src={rightImg} />
+        </div>
+      </Wrap>
+
+    )
+  }
+}
+
+export default Download

+ 118 - 0
src/pages/Download/Download.less

@@ -0,0 +1,118 @@
+.download {
+  background-color: #fff;
+  padding-top     : 240px;
+  height          : 100%;
+  box-sizing      : border-box;
+  position        : relative;
+
+  .download_left {
+    position: absolute;
+    left    : 0;
+    top     : 0;
+    display : inline-block;
+    width   : 150px;
+    height  : 540px;
+  }
+
+  .download_right {
+    position: absolute;
+    right   : 0;
+    top     : 0;
+    display : inline-block;
+    width   : 150px;
+    height  : 540px;
+  }
+
+  .logo {
+    width  : 200px;
+    height : 200px;
+    display: block;
+    margin : 0px auto 64px auto;
+  }
+
+  .appName_less {
+    width          : 280px;
+    display        : flex;
+    justify-content: center;
+    align-items    : center;
+    margin         : 0 auto;
+    margin-bottom  : 40px;
+
+    .appName_icon {
+      display     : inline-block;
+      width       : 52px;
+      height      : 52px;
+      margin-right: 20px;
+    }
+
+    font-size: 48px;
+  }
+
+  .title {
+    width  : 400px;
+    height : 96px;
+    display: block;
+    margin : 0px auto 80px auto;
+  }
+
+  .button {
+    width        : 280px;
+    height       : 80px;
+    background   : linear-gradient(180deg, rgba(255, 117, 89, 1) 0%, rgba(255, 77, 121, 1) 100%);
+    box-shadow   : 0px 8px 20px rgba(255, 77, 121, 0.25);
+    border-radius: 48px;
+    margin       : auto;
+
+    .icon {
+      width       : 28px;
+      height      : 28px;
+      display     : block;
+      margin-right: 16px;
+    }
+
+    .text {
+      font-size  : 32px;
+      font-family: PingFang SC;
+      font-weight: 400;
+      color      : rgba(255, 255, 255, 1);
+    }
+  }
+
+  .mask {
+    position        : fixed;
+    left            : 0;
+    top             : 0;
+    bottom          : 0;
+    width           : 100%;
+    background-color: rgba(0, 0, 0, 0.8);
+    z-index         : 2;
+
+    & img {
+      width: 100%;
+    }
+  }
+
+  .footer {
+    width     : 100%;
+    padding   : 20px 0px;
+    position  : absolute;
+    left      : 0px;
+    bottom    : 0px;
+    color     : #999;
+    text-align: center;
+    font-size : 24px;
+  }
+}
+
+.notice {
+  margin-top: 60px;
+
+  div {
+    line-height: 2em;
+  }
+
+  .remark {
+    padding   : 30px 130px;
+    text-align: left;
+  }
+}

+ 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);
+    
+  });
+});

+ 16 - 0
src/pages/document.ejs

@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <!-- <script src="/qplayer-web-player.js"></script> -->
+  <title></title>
+</head>
+
+<body>
+  <div id="root"></div>
+</body>
+
+</html>

+ 61 - 0
src/services/api.js

@@ -0,0 +1,61 @@
+import { stringify } from 'qs';
+import request from '@/utils/request';
+
+//获取直播详情
+export async function getLiveInfo(params) {
+  return request(`/api-app/v1/live/getLiveInfo?${stringify(params)}`);
+}
+
+//获取直播详情(游客)
+export async function getLiveInfoLite(params) {
+  return request(`/api-app/v1/live/getLiveInfoLite?${stringify(params)}`);
+}
+
+//禁播
+export async function disableAnchor(params) {
+  return request('/api-manage/v1/anchor/disableAnchor', {
+    method: 'POST',
+    body: params,
+  });
+}
+
+//获取最新版本应用
+export async function getAppLatest(params) {
+  return request(`/api-common/v1/data/getAppLatest?${stringify(params)}`);
+}
+
+//获取内容详情(游客)
+export async function getFeedDetailGuest(params) {
+  return request(`/api-app/v1/feed/getFeedDetailGuest?${stringify(params)}`);
+}
+
+//获取金币充值项目
+export async function getCoinRechargeItemsGuest(params) {
+  return request(`/api-app/v1/recharge/getCoinRechargeItemsGuest?${stringify(params)}`);
+}
+
+//查询用户
+export async function getUserByUserNoOrNiceNo(params) {
+  return request(`/api-app/v1/user/getUserByUserNoOrNiceNo?${stringify(params)}`);
+}
+
+//创建金币充值订单
+export async function addCoinRechargeOrderGuest(params) {
+  return request('/api-app/v1/recharge/addCoinRechargeOrderGuest', {
+    method: 'POST',
+    body: params,
+  });
+}
+
+//创建支付订单
+export async function addPayOrderGuest(params) {
+  return request('/api-app/v1/pay/addPayOrderGuest', {
+    method: 'POST',
+    body: params,
+  });
+}
+
+//获取服务号openId
+export async function getOpenId(params) {
+  return request(`/api-app/v1/user/getOpenId?${stringify(params)}`);
+}

+ 175 - 0
src/utils/Cookie.js

@@ -0,0 +1,175 @@
+// Cookie
+// -------------
+// Thanks to:
+//  - //www.nczonline.net/blog/2009/05/05/http-cookies-explained/
+//  - //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 (let 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
+};

+ 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,
+};

+ 11 - 0
src/utils/Url.js

@@ -0,0 +1,11 @@
+
+function getQueryString(name) {
+  const reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
+  const r = window.location.search.substr(1).match(reg);
+  if (r != null) return decodeURIComponent(r[2]);
+  return null;
+}
+
+module.exports = {
+  getQueryString
+};

+ 26 - 0
src/utils/UserAgent.js

@@ -0,0 +1,26 @@
+import UAParser from 'ua-parser-js'
+
+const UA = new UAParser().getResult()
+
+function iOS() {
+  return UA.os.name == 'iOS';
+}
+
+function android() {
+  return UA.os.name == 'Android'
+}
+
+function wechat() {
+  return UA.browser.name == 'WeChat'
+}
+
+function QQ() {
+  return UA.browser.name == 'QQ'
+}
+
+export default {
+  iOS,
+  android,
+  wechat,
+  QQ
+}

+ 242 - 0
src/utils/base.js

@@ -0,0 +1,242 @@
+import MD5 from './Security'
+import { getQueryString } from './Url'
+
+// 62进制转成10进制
+function string62to10(number_code) {
+  var chars = '0123456789abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ',
+    radix = chars.length,
+    number_code = String(number_code),
+    len = number_code.length,
+    i = 0,
+    origin_number = 0;
+  while (i < len) {
+    origin_number += Math.pow(radix, i++) * chars.indexOf(number_code.charAt(len - i) || 0);
+  }
+  return origin_number;
+}
+
+// 截取渠道id
+export const getChannelId = () => {
+  const pathname = window.location.pathname
+  // 62进制的渠道id
+  const _channelId62 = pathname.substr(1)
+  return string62to10(_channelId62)
+}
+
+export const Base64 = {
+  _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
+  encode: function (e) {
+    var t = "";
+    var n, r, i, s, o, u, a;
+    var f = 0;
+    e = Base64._utf8_encode(e);
+    while (f < e.length) {
+      n = e.charCodeAt(f++);
+      r = e.charCodeAt(f++);
+      i = e.charCodeAt(f++);
+      s = n >> 2;
+      o = (n & 3) << 4 | r >> 4;
+      u = (r & 15) << 2 | i >> 6;
+      a = i & 63;
+      if (isNaN(r)) {
+        u = a = 64
+      } else if (isNaN(i)) {
+        a = 64
+      }
+      t = t + this._keyStr.charAt(s) + this._keyStr.charAt(o) + this._keyStr.charAt(u) + this._keyStr.charAt(a)
+    }
+    return t
+  },
+  decode: function (e) {
+    var t = "";
+    var n, r, i;
+    var s, o, u, a;
+    var f = 0;
+    e = e.replace(/[^A-Za-z0-9+/=]/g, "");
+    while (f < e.length) {
+      s = this._keyStr.indexOf(e.charAt(f++));
+      o = this._keyStr.indexOf(e.charAt(f++));
+      u = this._keyStr.indexOf(e.charAt(f++));
+      a = this._keyStr.indexOf(e.charAt(f++));
+      n = s << 2 | o >> 4;
+      r = (o & 15) << 4 | u >> 2;
+      i = (u & 3) << 6 | a;
+      t = t + String.fromCharCode(n);
+      if (u != 64) {
+        t = t + String.fromCharCode(r)
+      }
+      if (a != 64) {
+        t = t + String.fromCharCode(i)
+      }
+    }
+    t = Base64._utf8_decode(t);
+    return t
+  },
+  _utf8_encode: function (e) {
+    e = e.replace(/rn/g, "n");
+    var t = "";
+    for (var n = 0; n < e.length; n++) {
+      var r = e.charCodeAt(n);
+      if (r < 128) {
+        t += String.fromCharCode(r)
+      } else if (r > 127 && r < 2048) {
+        t += String.fromCharCode(r >> 6 | 192);
+        t += String.fromCharCode(r & 63 | 128)
+      } else {
+        t += String.fromCharCode(r >> 12 | 224);
+        t += String.fromCharCode(r >> 6 & 63 | 128);
+        t += String.fromCharCode(r & 63 | 128)
+      }
+    }
+    return t
+  },
+  _utf8_decode: function (e) {
+    var t = "";
+    var n = 0;
+    var c1, c2, c3;
+    var r = c1 = c2 = 0;
+    while (n < e.length) {
+      r = e.charCodeAt(n);
+      if (r < 128) {
+        t += String.fromCharCode(r);
+        n++
+      } else if (r > 191 && r < 224) {
+        c2 = e.charCodeAt(n + 1);
+        t += String.fromCharCode((r & 31) << 6 | c2 & 63);
+        n += 2
+      } else {
+        c2 = e.charCodeAt(n + 1);
+        c3 = e.charCodeAt(n + 2);
+        t += String.fromCharCode((r & 15) << 12 | (c2 & 63) << 6 | c3 & 63);
+        n += 3
+      }
+    }
+    return t
+  }
+}
+
+// 获取时间
+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 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
+}
+
+// -----------
+// ----------- 环境判断类 ----------
+// -----------
+const USERAGENT = window.navigator.userAgent
+//判断微信
+export const isWeixin = () => {
+  const ua = USERAGENT.toLowerCase();
+  return ua.match(/MicroMessenger/i) == 'micromessenger'
+}
+// 判断安卓
+export const isAndroid = () => {
+  return USERAGENT.indexOf('Android') > -1 || USERAGENT.indexOf('Linux') > -1; //g
+}
+// 判断ios
+export const isIos = () => {
+  return !!USERAGENT.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端
+}
+// 判断是否是在 App 中
+export const isApp = () => {
+  return USERAGENT.indexOf('com.xike.app') > -1
+}
+
+export const 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;
+}
+
+export const DOWN_MS = 500000
+
+// body 禁止滚动
+const preventDefaultFunc = function (e) {
+  e.preventDefault()
+}
+export const setBodyScroll = (isAllow) => {
+  const _body = document.body
+  if (isAllow) {
+    // _body.style.overflow = 'unset' 
+    _body.removeEventListener('touchmove', preventDefaultFunc)
+    return
+  }
+  // _body.style.overflow = 'hidden' 
+  _body.addEventListener('touchmove', preventDefaultFunc, { passive: false })
+}
+
+
+export const encryptMd5 = MD5.encryptMd5
+
+//默认头像
+export const _defaultAvatar = 'http://sp.qn.paqukeji.com/images/default_avatar.png'
+
+//app下载地址统一
+let c = getChannelId() // getQueryString('c') || getQueryString('ci') || '0'
+// c = (parseInt(c) < 1000 && c !== '4') ? 0 : c
+export const _appDownloadUrl = `//starbuds.paqukeji.com/download/${c}` + '?s=1'  //增加渠道号 //starbuds.paqukeji.com/download/渠道号, 主包是0

+ 103 - 0
src/utils/longSock.js

@@ -0,0 +1,103 @@
+function longSock(url, fn, intro = '') {
+  let lockReconnect = false //避免重复连接
+  let timeoutFlag = true
+  let timeoutSet = null
+  let reconectNum = 0
+  const timeout = 30000 //超时重连间隔
+  let ws
+
+  //重连
+  function reconnect() {
+    if (lockReconnect) return
+    lockReconnect = true
+    //没连接上会一直重连,设置延迟避免请求过多
+    if (reconectNum < 3) {
+      setTimeout(function () {
+        timeoutFlag = true
+        createWebSocket()
+        console.info(`${intro}正在重连第${reconectNum + 1}次`)
+        reconectNum++
+        lockReconnect = false
+      }, 5000) //这里设置重连间隔(ms)
+    }
+  }
+
+  //心跳检测
+  const heartCheck = {
+    timeout: 5000, //毫秒
+    timeoutObj: null,
+    serverTimeoutObj: null,
+    reset: function () {
+      clearInterval(this.timeoutObj)
+      clearTimeout(this.serverTimeoutObj)
+      return this
+    },
+    start: function () {
+      const self = this
+      let count = 0
+      this.timeoutObj = setInterval(() => {
+        if (count < 3) {
+          if (ws.readyState === 1) {
+            ws.send('HeartBeat')
+            console.info(`${intro}HeartBeat第${count + 1}次`)
+          }
+          count++
+        } else {
+          clearInterval(this.timeoutObj)
+          count = 0
+          if (ws.readyState === 0 && ws.readyState === 1) {
+            ws.close()
+          }
+        }
+      }, self.timeout)
+    }
+  }
+
+  //建立连接
+  const createWebSocket = () => {
+    console.info(`${intro}创建11`)
+    timeoutSet = setTimeout(() => {
+      if (timeoutFlag && reconectNum < 3) {
+        console.info(`${intro}重连22`)
+        reconectNum++
+        createWebSocket()
+      }
+    }, timeout)
+    ws = new WebSocket(url)
+
+    ws.onopen = () => {
+      reconectNum = 0
+      timeoutFlag = false
+      clearTimeout(timeoutSet)
+      heartCheck.reset().start()
+    }
+
+    ws.onmessage = evt => {
+      heartCheck.reset().start()
+      // console.info(evt);
+      if (evt.data === 'HeartBeat') return
+      fn(evt, ws)
+    }
+    
+    ws.onclose = e => {
+      console.info(`${intro}关闭11`, e.code)
+      if (e.code !== 1000) {
+        timeoutFlag = false
+        clearTimeout(timeoutSet)
+        reconnect()
+      } else {
+        clearInterval(heartCheck.timeoutObj)
+        clearTimeout(heartCheck.serverTimeoutObj)
+      }
+    }
+    ws.onerror = function () {
+      console.info(`${intro}错误11`)
+      reconnect() //重连
+    }
+  }
+
+  createWebSocket()
+  return ws
+}
+
+export default longSock

+ 150 - 0
src/utils/request.js

@@ -0,0 +1,150 @@
+/**
+ * request 网络请求工具
+ * 更详细的 api 文档: //://github.com/umijs/umi-request
+ */
+import { extend } from 'umi-request';
+import { stringify } from 'querystring';
+import Cookie from './Cookie';
+import Security from './Security'
+import { Base64, getChannelId } from './base'
+import { getQueryString } from './Url'
+
+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 = () => {
+  let ci = getChannelId()
+  if (!ci) {
+    ci = ''
+  }
+  // 通用参数
+  const commonParams = {
+    a: 11,
+    av: '1.0.0',
+    c: 4,
+    ci: ci,
+    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;

+ 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'),
+    },
+  },
+};

Some files were not shown because too many files changed in this diff