Browse Source

提交

master
keke 4 years ago
parent
commit
1f1b14665c
100 changed files with 3368 additions and 0 deletions
  1. 5
    0
      .babelrc
  2. 9
    0
      .editorconfig
  3. 0
    0
      .eslintignore
  4. 21
    0
      .eslintrc.js
  5. 28
    0
      .gitignore
  6. 5
    0
      .postcssrc.js
  7. 5
    0
      .travis.yml
  8. 21
    0
      LICENSE
  9. 3
    0
      cypress.json
  10. 70
    0
      package.json
  11. BIN
      public/favicon.ico
  12. 17
    0
      public/index.html
  13. 27
    0
      src/App.vue
  14. 51
    0
      src/api/data.js
  15. 11
    0
      src/api/routers.js
  16. 84
    0
      src/api/user.js
  17. 37
    0
      src/assets/icons/iconfont.css
  18. BIN
      src/assets/icons/iconfont.eot
  19. 56
    0
      src/assets/icons/iconfont.svg
  20. BIN
      src/assets/icons/iconfont.ttf
  21. BIN
      src/assets/icons/iconfont.woff
  22. 1
    0
      src/assets/images/error-page/error-401.svg
  23. 1
    0
      src/assets/images/error-page/error-404.svg
  24. 1
    0
      src/assets/images/error-page/error-500.svg
  25. BIN
      src/assets/images/icon-qr-qq-wechat.png
  26. 1
    0
      src/assets/images/icon-social-bilibili.svg
  27. 1
    0
      src/assets/images/icon-social-juejin.svg
  28. 1
    0
      src/assets/images/icon-social-twitter.svg
  29. 1
    0
      src/assets/images/icon-social-zhihu.svg
  30. BIN
      src/assets/images/login-bg.jpg
  31. BIN
      src/assets/images/logo-min.jpg
  32. BIN
      src/assets/images/logo.jpg
  33. BIN
      src/assets/images/talkingdata.png
  34. BIN
      src/assets/img/bg.png
  35. BIN
      src/assets/img/gou.png
  36. BIN
      src/assets/img/key.png
  37. BIN
      src/assets/img/kuang.png
  38. BIN
      src/assets/img/logo.png
  39. BIN
      src/assets/img/user.png
  40. 58
    0
      src/components/charts/bar.vue
  41. 3
    0
      src/components/charts/index.js
  42. 70
    0
      src/components/charts/pie.vue
  43. 491
    0
      src/components/charts/theme.json
  44. 42
    0
      src/components/common-icon/common-icon.vue
  45. 2
    0
      src/components/common-icon/index.js
  46. 8
    0
      src/components/common/common.less
  47. 3
    0
      src/components/common/util.js
  48. 174
    0
      src/components/count-to/count-to.vue
  49. 2
    0
      src/components/count-to/index.js
  50. 10
    0
      src/components/count-to/index.less
  51. 2
    0
      src/components/cropper/index.js
  52. 35
    0
      src/components/cropper/index.less
  53. 139
    0
      src/components/cropper/index.vue
  54. 18
    0
      src/components/drag-drawer/drag-drawer-trigger.vue
  55. 156
    0
      src/components/drag-drawer/drag-drawer.vue
  56. 2
    0
      src/components/drag-drawer/index.js
  57. 70
    0
      src/components/drag-drawer/index.less
  58. 7
    0
      src/components/drag-drawer/mixin.js
  59. 92
    0
      src/components/drag-list/drag-list.vue
  60. 2
    0
      src/components/drag-list/index.js
  61. 75
    0
      src/components/editor/editor.vue
  62. 2
    0
      src/components/editor/index.js
  63. 35
    0
      src/components/icons/icons.vue
  64. 2
    0
      src/components/icons/index.js
  65. 2
    0
      src/components/info-card/index.js
  66. 94
    0
      src/components/info-card/infor-card.vue
  67. 2
    0
      src/components/login-form/index.js
  68. 99
    0
      src/components/login-form/login-form.vue
  69. 2
    0
      src/components/main/components/a-back-top/index.js
  70. 90
    0
      src/components/main/components/a-back-top/index.vue
  71. 49
    0
      src/components/main/components/error-store/error-store.vue
  72. 2
    0
      src/components/main/components/error-store/index.js
  73. 84
    0
      src/components/main/components/fullscreen/fullscreen.vue
  74. 2
    0
      src/components/main/components/fullscreen/index.js
  75. 4
    0
      src/components/main/components/header-bar/custom-bread-crumb/custom-bread-crumb.less
  76. 47
    0
      src/components/main/components/header-bar/custom-bread-crumb/custom-bread-crumb.vue
  77. 2
    0
      src/components/main/components/header-bar/custom-bread-crumb/index.js
  78. 18
    0
      src/components/main/components/header-bar/header-bar.less
  79. 34
    0
      src/components/main/components/header-bar/header-bar.vue
  80. 2
    0
      src/components/main/components/header-bar/index.js
  81. 2
    0
      src/components/main/components/header-bar/sider-trigger/index.js
  82. 21
    0
      src/components/main/components/header-bar/sider-trigger/sider-trigger.less
  83. 27
    0
      src/components/main/components/header-bar/sider-trigger/sider-trigger.vue
  84. 2
    0
      src/components/main/components/language/index.js
  85. 51
    0
      src/components/main/components/language/language.vue
  86. 119
    0
      src/components/main/components/navbar/index.vue
  87. 51
    0
      src/components/main/components/side-menu/collapsed-menu.vue
  88. 2
    0
      src/components/main/components/side-menu/index.js
  89. 21
    0
      src/components/main/components/side-menu/item-mixin.js
  90. 18
    0
      src/components/main/components/side-menu/mixin.js
  91. 26
    0
      src/components/main/components/side-menu/side-menu-item.vue
  92. 50
    0
      src/components/main/components/side-menu/side-menu.less
  93. 117
    0
      src/components/main/components/side-menu/side-menu.vue
  94. 2
    0
      src/components/main/components/tags-nav/index.js
  95. 87
    0
      src/components/main/components/tags-nav/tags-nav.less
  96. 209
    0
      src/components/main/components/tags-nav/tags-nav.vue
  97. 2
    0
      src/components/main/components/user/index.js
  98. 12
    0
      src/components/main/components/user/user.less
  99. 59
    0
      src/components/main/components/user/user.vue
  100. 0
    0
      src/components/main/index.js

+ 5
- 0
.babelrc View File

@@ -0,0 +1,5 @@
1
+{
2
+  "presets": [
3
+    "@vue/app"
4
+  ]
5
+}

+ 9
- 0
.editorconfig View File

@@ -0,0 +1,9 @@
1
+root = true
2
+
3
+[*]
4
+charset = utf-8
5
+indent_style = space
6
+indent_size = 2
7
+end_of_line = lf
8
+insert_final_newline = true
9
+trim_trailing_whitespace = true

+ 0
- 0
.eslintignore View File


+ 21
- 0
.eslintrc.js View File

@@ -0,0 +1,21 @@
1
+module.exports = {
2
+  root: true,
3
+  'extends': [
4
+    'plugin:vue/essential',
5
+    '@vue/standard'
6
+  ],
7
+  rules: {
8
+    // allow async-await
9
+    'generator-star-spacing': 'off',
10
+    // allow debugger during development
11
+    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
12
+    'vue/no-parsing-error': [2, {
13
+      'x-invalid-end-tag': false
14
+    }],
15
+    'no-undef': 'off',
16
+    'camelcase': 'off'
17
+  },
18
+  parserOptions: {
19
+    parser: 'babel-eslint'
20
+  }
21
+}

+ 28
- 0
.gitignore View File

@@ -0,0 +1,28 @@
1
+.DS_Store
2
+node_modules
3
+/dist
4
+
5
+package-lock.json
6
+
7
+/tests/e2e/videos/
8
+/tests/e2e/screenshots/
9
+
10
+# local env files
11
+.env.local
12
+.env.*.local
13
+
14
+# Log files
15
+npm-debug.log*
16
+yarn-debug.log*
17
+yarn-error.log*
18
+
19
+# Editor directories and files
20
+.idea
21
+.vscode
22
+*.suo
23
+*.ntvs*
24
+*.njsproj
25
+*.sln
26
+*.sw*
27
+
28
+build/env.js

+ 5
- 0
.postcssrc.js View File

@@ -0,0 +1,5 @@
1
+module.exports = {
2
+  plugins: {
3
+    autoprefixer: {}
4
+  }
5
+}

+ 5
- 0
.travis.yml View File

@@ -0,0 +1,5 @@
1
+language: node_js
2
+node_js: stable
3
+script: npm run lint
4
+notifications:
5
+  email: false

+ 21
- 0
LICENSE View File

@@ -0,0 +1,21 @@
1
+MIT License
2
+
3
+Copyright (c) 2017 iView
4
+
5
+Permission is hereby granted, free of charge, to any person obtaining a copy
6
+of this software and associated documentation files (the "Software"), to deal
7
+in the Software without restriction, including without limitation the rights
8
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+copies of the Software, and to permit persons to whom the Software is
10
+furnished to do so, subject to the following conditions:
11
+
12
+The above copyright notice and this permission notice shall be included in all
13
+copies or substantial portions of the Software.
14
+
15
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+SOFTWARE.

+ 3
- 0
cypress.json View File

@@ -0,0 +1,3 @@
1
+{
2
+  "pluginsFile": "tests/e2e/plugins/index.js"
3
+}

+ 70
- 0
package.json View File

@@ -0,0 +1,70 @@
1
+{
2
+  "name": "iview-admin",
3
+  "version": "2.0.0",
4
+  "author": "Lison<lison16new@163.com>",
5
+  "private": false,
6
+  "scripts": {
7
+    "dev": "vue-cli-service serve --open",
8
+    "build": "vue-cli-service build",
9
+    "lint": "vue-cli-service lint",
10
+    "test:unit": "vue-cli-service test:unit",
11
+    "test:e2e": "vue-cli-service test:e2e"
12
+  },
13
+  "dependencies": {
14
+    "axios": "^0.18.0",
15
+    "clipboard": "^2.0.0",
16
+    "codemirror": "^5.38.0",
17
+    "countup": "^1.8.2",
18
+    "cropperjs": "^1.2.2",
19
+    "dayjs": "^1.7.7",
20
+    "echarts": "^4.7.0",
21
+    "html2canvas": "^1.0.0-alpha.12",
22
+    "iview": "^3.2.2",
23
+    "iview-area": "^1.5.17",
24
+    "js-cookie": "^2.2.0",
25
+    "simplemde": "^1.11.2",
26
+    "sortablejs": "^1.7.0",
27
+    "tree-table-vue": "^1.1.0",
28
+    "v-org-tree": "^1.0.6",
29
+    "vue": "^2.5.10",
30
+    "vue-i18n": "^7.8.0",
31
+    "vue-router": "^3.0.1",
32
+    "vuedraggable": "^2.16.0",
33
+    "vuex": "^3.0.1",
34
+    "wangeditor": "^3.1.1",
35
+    "xlsx": "^0.13.3"
36
+  },
37
+  "devDependencies": {
38
+    "@vue/cli-plugin-babel": "^3.0.1",
39
+    "@vue/cli-plugin-eslint": "^3.0.1",
40
+    "@vue/cli-plugin-unit-mocha": "^3.0.1",
41
+    "@vue/cli-service": "^3.0.1",
42
+    "@vue/eslint-config-standard": "^3.0.0-beta.10",
43
+    "@vue/test-utils": "^1.0.0-beta.10",
44
+    "chai": "^4.1.2",
45
+    "eslint-plugin-cypress": "^2.0.1",
46
+    "less": "^2.7.3",
47
+    "less-loader": "^4.0.5",
48
+    "lint-staged": "^6.0.0",
49
+    "mockjs": "^1.0.1-beta3",
50
+    "vue-template-compiler": "^2.5.13"
51
+  },
52
+  "browserslist": [
53
+    "> 1%",
54
+    "last 2 versions",
55
+    "not ie <= 8"
56
+  ],
57
+  "gitHooks": {
58
+    "pre-commit": "lint-staged"
59
+  },
60
+  "lint-staged": {
61
+    "*.js": [
62
+      "vue-cli-service lint",
63
+      "git add"
64
+    ],
65
+    "*.vue": [
66
+      "vue-cli-service lint",
67
+      "git add"
68
+    ]
69
+  }
70
+}

BIN
public/favicon.ico View File


+ 17
- 0
public/index.html View File

@@ -0,0 +1,17 @@
1
+<!DOCTYPE html>
2
+<html>
3
+  <head>
4
+    <meta charset="utf-8">
5
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
+    <meta name="viewport" content="width=device-width,initial-scale=1.0">
7
+    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
8
+    <title></title>
9
+  </head>
10
+  <body>
11
+    <noscript>
12
+      <strong>We're sorry but iview-admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
13
+    </noscript>
14
+    <div id="app"></div>
15
+    <!-- built files will be auto injected -->
16
+  </body>
17
+</html>

+ 27
- 0
src/App.vue View File

@@ -0,0 +1,27 @@
1
+<template>
2
+  <div id="app">
3
+    <router-view/>
4
+  </div>
5
+</template>
6
+
7
+<script>
8
+export default {
9
+  name: 'App'
10
+}
11
+</script>
12
+
13
+<style lang="less">
14
+.size{
15
+  width: 100%;
16
+  height: 100%;
17
+}
18
+html,body{
19
+  .size;
20
+  overflow: hidden;
21
+  margin: 0;
22
+  padding: 0;
23
+}
24
+#app {
25
+  .size;
26
+}
27
+</style>

+ 51
- 0
src/api/data.js View File

@@ -0,0 +1,51 @@
1
+import axios from '@/libs/api.request'
2
+
3
+export const getTableData = () => {
4
+  return axios.request({
5
+    url: 'get_table_data',
6
+    method: 'get'
7
+  })
8
+}
9
+
10
+export const getDragList = () => {
11
+  return axios.request({
12
+    url: 'get_drag_list',
13
+    method: 'get'
14
+  })
15
+}
16
+
17
+export const errorReq = () => {
18
+  return axios.request({
19
+    url: 'error_url',
20
+    method: 'post'
21
+  })
22
+}
23
+
24
+export const saveErrorLogger = info => {
25
+  return axios.request({
26
+    url: 'save_error_logger',
27
+    data: info,
28
+    method: 'post'
29
+  })
30
+}
31
+
32
+export const uploadImg = formData => {
33
+  return axios.request({
34
+    url: 'image/upload',
35
+    data: formData
36
+  })
37
+}
38
+
39
+export const getOrgData = () => {
40
+  return axios.request({
41
+    url: 'get_org_data',
42
+    method: 'get'
43
+  })
44
+}
45
+
46
+export const getTreeSelectData = () => {
47
+  return axios.request({
48
+    url: 'get_tree_select_data',
49
+    method: 'get'
50
+  })
51
+}

+ 11
- 0
src/api/routers.js View File

@@ -0,0 +1,11 @@
1
+import axios from '@/libs/api.request'
2
+
3
+export const getRouterReq = (access) => {
4
+  return axios.request({
5
+    url: 'get_router',
6
+    params: {
7
+      access
8
+    },
9
+    method: 'get'
10
+  })
11
+}

+ 84
- 0
src/api/user.js View File

@@ -0,0 +1,84 @@
1
+import axios from '@/libs/api.request'
2
+
3
+export const login = ({ userName, password }) => {
4
+  const data = {
5
+    userName,
6
+    password
7
+  }
8
+  return axios.request({
9
+    url: 'login',
10
+    data,
11
+    method: 'post'
12
+  })
13
+}
14
+
15
+export const getUserInfo = (token) => {
16
+  return axios.request({
17
+    url: 'get_info',
18
+    params: {
19
+      token
20
+    },
21
+    method: 'get'
22
+  })
23
+}
24
+
25
+export const logout = (token) => {
26
+  return axios.request({
27
+    url: 'logout',
28
+    method: 'post'
29
+  })
30
+}
31
+
32
+export const getUnreadCount = () => {
33
+  return axios.request({
34
+    url: 'message/count',
35
+    method: 'get'
36
+  })
37
+}
38
+
39
+export const getMessage = () => {
40
+  return axios.request({
41
+    url: 'message/init',
42
+    method: 'get'
43
+  })
44
+}
45
+
46
+export const getContentByMsgId = msg_id => {
47
+  return axios.request({
48
+    url: 'message/content',
49
+    method: 'get',
50
+    params: {
51
+      msg_id
52
+    }
53
+  })
54
+}
55
+
56
+export const hasRead = msg_id => {
57
+  return axios.request({
58
+    url: 'message/has_read',
59
+    method: 'post',
60
+    data: {
61
+      msg_id
62
+    }
63
+  })
64
+}
65
+
66
+export const removeReaded = msg_id => {
67
+  return axios.request({
68
+    url: 'message/remove_readed',
69
+    method: 'post',
70
+    data: {
71
+      msg_id
72
+    }
73
+  })
74
+}
75
+
76
+export const restoreTrash = msg_id => {
77
+  return axios.request({
78
+    url: 'message/restore',
79
+    method: 'post',
80
+    data: {
81
+      msg_id
82
+    }
83
+  })
84
+}

+ 37
- 0
src/assets/icons/iconfont.css View File

@@ -0,0 +1,37 @@
1
+
2
+@font-face {font-family: "iconfont";
3
+  src: url('iconfont.eot?t=1541579316141'); /* IE9*/
4
+  src: url('iconfont.eot?t=1541579316141#iefix') format('embedded-opentype'), /* IE6-IE8 */
5
+  url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAiEAAsAAAAADmgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFY8eUnXY21hcAAAAYAAAACjAAACLi+YJuBnbHlmAAACJAAABAgAAAcg4dRWHmhlYWQAAAYsAAAAMQAAADYTL8piaGhlYQAABmAAAAAgAAAAJAfdA4xobXR4AAAGgAAAABQAAAAsLAD//2xvY2EAAAaUAAAAGAAAABgImgpGbWF4cAAABqwAAAAfAAAAIAEcAG5uYW1lAAAGzAAAAUUAAAJtPlT+fXBvc3QAAAgUAAAAbgAAAI54roygeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2BkYWCcwMDKwMHUyXSGgYGhH0IzvmYwYuRgYGBiYGVmwAoC0lxTGByeMTx/ytzwv4EhhrmBoRkozAiSAwDuUwzMeJzlkUEKwkAMRd/YabXFhQvxFF6qPYPrUujGY7jyIr1JoZNjtMnEhag3MOEN5MMk8D9QAoVyVSKEJwGrh6oh6wVN1iM3nc+cVImJVKdOehlklElmWdYVstp+ql8VdIv15a1NLW0zFXsO7Kjz3erH/3+rY37vr6kxnx1LKNWOJZlaxxJNnWOpSu+ot8jgqMvI6KjfyOSo88jsaAbI4tBsig89rQB4nLVUTWwbRRSeNzO767i2g7N/FP9s7MRrE5ON4/V6rSZyU0PiINSSNImES4IUoapWz6hEiqiMBDQqEojkAkiFStyKRC+9VSoFCeUEyqESVUAqEkcu3OAQb3hrJxAXwSGI3X0/szPz5vvm2x0i7O/vf8IJe5VkSJnUyUtklRBQJE1VIjRtUafkmk6pSu2ipleh4+xikkKxSksWTUeo8m8NoagpYtoslTmxrLl37z64e33esuJjU8P5Wd262LxoPVnPZ06Pxfe+C0YjkhSJygPhQCA8ABPOykwuN7NyuRvgUnAgLEnhATkaCQQiUe/7XKUyV6nQz+t2o7l66+rs7NVbq82GXTdrdjxjRGU5amTids2bUDMFtzCsqsMYMqr3IDY6OT05GjsI8Exv/6CSkOWEQigh+y3clxY5QVTcEZFIGtHLxDUJs6WsHR1y9SFKdr1HggCp3V1ICYL36OOpVmvKN9bC1u6R3vZ0qwWtVovgJfqOfUvfIYxIWL+fyETHNVJqSkIT1JTjW8ZWh3yDJDz0ctvsyt51etvrg9/QHhqGlzMM+vbmizPnDWPLMNbW19e7tffvsBzL99aWEfBRY46t+tbe3PypXv/IMDYN43WsQBe9HL2NC33RuxABrPsG+xH3o4bVRE2KgCRqulbWNf8W/UYVHM129aKra24VshZkq+CWD/Oy6Xt8cGYEthgHVlVliCfynAlqjo6oysTKlYUAD4docMI5/1ZioN+GwZNBcTwWUmTdBUqhTwX29QebXzF4An4JJMzwfMl+WQ01+IlQZVR4yhie53ycA16pOI/ODiYNGK4MChdCgXNnX5gIJXPCSYnf2OF850aQ+zJIyOs+u8+mMO8jQdwtg1TIWVRjKAnFcslMi8KfGUPoSUCergUyUk77dMyS69Ms6tijKZKYwUGKbpfdzu+iYeZYAHMFiOVi+MD7h9mb99qC0L7X8c+XatMfTj97KZ5IxJt/pd43tYYQKEjAnXMOB6kQEBrwg+LPjindAPOHNdC3q3ait0I3/ZIunZEARLNYNEUA6czSP3N/7j9wz6ZESdX0VNl1zGNS/szbQaQSIGk4DtVPcZf8AgXpf9A2OyTit5s2syZmand46bhEe2WtodLHkvaoqtTXuXN2/c42WADP9HGfbUcUW7JgqHss4xHtlMys679FqUomdP9VJBQBdnlPABBubpuNwqnmQj6/0HwNQzKxDUJFgKiXurBG6dqFjmeBzsvtRPJgGIZThYa5fdOvsReOticPh6JHHXxsv7ItJpOniYPYsmZ/x0QD/o5P105DeQwF6MH33ogoLi+KQp7zpY3HQV5bFMURzheXeds7gpP+jKNXljjHuYvXHke7cdCxLLZf6YX7B63UcCV4nGNgZGBgAOKAN2ZR8fw2Xxm4WRhA4AbHYRMY/f///1oWBuYGIJeDgQkkCgAvWgs2AAAAeJxjYGRgYG7438AQw8Lw/z8DAwsDA1AEBXADAHXiBHJ4nGNhYGBgYfj/nwVM48cATwECKwAAAAAAjAC6AOgBFAGAAf4CbgLqAzgDkHicY2BkYGDgZkhiYGcAASYg5gJCBob/YD4DABOmAYsAeJxlj01OwzAQhV/6B6QSqqhgh+QFYgEo/RGrblhUavdddN+mTpsqiSPHrdQDcB6OwAk4AtyAO/BIJ5s2lsffvHljTwDc4Acejt8t95E9XDI7cg0XuBeuU38QbpBfhJto41W4Rf1N2MczpsJtdGF5g9e4YvaEd2EPHXwI13CNT+E69S/hBvlbuIk7/Aq30PHqwj7mXle4jUcv9sdWL5xeqeVBxaHJIpM5v4KZXu+Sha3S6pxrW8QmU4OgX0lTnWlb3VPs10PnIhVZk6oJqzpJjMqt2erQBRvn8lGvF4kehCblWGP+tsYCjnEFhSUOjDFCGGSIyujoO1Vm9K+xQ8Jee1Y9zed0WxTU/3OFAQL0z1xTurLSeTpPgT1fG1J1dCtuy56UNJFezUkSskJe1rZUQuoBNmVXjhF6XNGJPyhnSP8ACVpuyAAAAHicbYhdDoIwEAb3a6k/YIIX8VArWewmdJFWJOnpJTG+OQ+TzJCjLy39p4ODR4OAA4444YwWHS7U3IVzn6Voldtb8ksHnvohrlqjjmw1rmzXsvdT7fEbblnCmOfNfJIYStJJfGIL27yb6AOCGR89AAA=') format('woff'),
6
+  url('iconfont.ttf?t=1541579316141') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
7
+  url('iconfont.svg?t=1541579316141#iconfont') format('svg'); /* iOS 4.1- */
8
+}
9
+
10
+.iconfont {
11
+  font-family:"iconfont" !important;
12
+  font-size:16px;
13
+  font-style:normal;
14
+  -webkit-font-smoothing: antialiased;
15
+  -moz-osx-font-smoothing: grayscale;
16
+}
17
+
18
+.icon-bear:before { content: "\e600"; }
19
+
20
+.icon-resize-vertical:before { content: "\e7c3"; }
21
+
22
+.icon-chuizhifanzhuan:before { content: "\e661"; }
23
+
24
+.icon-shuipingfanzhuan:before { content: "\e662"; }
25
+
26
+.icon-qq:before { content: "\e609"; }
27
+
28
+.icon-frown:before { content: "\e77e"; }
29
+
30
+.icon-meh:before { content: "\e780"; }
31
+
32
+.icon-smile:before { content: "\e783"; }
33
+
34
+.icon-man:before { content: "\e7e2"; }
35
+
36
+.icon-woman:before { content: "\e7e5"; }
37
+

BIN
src/assets/icons/iconfont.eot View File


+ 56
- 0
src/assets/icons/iconfont.svg View File

@@ -0,0 +1,56 @@
1
+<?xml version="1.0" standalone="no"?>
2
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
3
+<!--
4
+2013-9-30: Created.
5
+-->
6
+<svg>
7
+<metadata>
8
+Created by iconfont
9
+</metadata>
10
+<defs>
11
+
12
+<font id="iconfont" horiz-adv-x="1024" >
13
+  <font-face
14
+    font-family="iconfont"
15
+    font-weight="500"
16
+    font-stretch="normal"
17
+    units-per-em="1024"
18
+    ascent="896"
19
+    descent="-128"
20
+  />
21
+    <missing-glyph />
22
+    
23
+    <glyph glyph-name="bear" unicode="&#58880;" d="M1024 683.008q0-70.656-46.08-121.856 46.08-89.088 46.08-193.536 0-96.256-39.936-181.248t-109.568-147.968-162.816-99.328-199.68-36.352-199.68 36.352-162.304 99.328-109.568 147.968-40.448 181.248q0 104.448 46.08 193.536-46.08 51.2-46.08 121.856 0 37.888 13.824 71.168t37.376 58.368 55.808 39.424 68.096 14.336q43.008 0 78.848-18.432t59.392-50.176q46.08 17.408 96.256 26.624t102.4 9.216 102.4-9.216 96.256-26.624q24.576 31.744 59.904 50.176t78.336 18.432q36.864 0 68.608-14.336t55.296-39.424 37.376-58.368 13.824-71.168zM205.824 268.288q10.24 0 18.944 10.24t15.36 28.672 10.24 42.496 3.584 51.712-3.584 51.712-10.24 41.984-15.36 28.16-18.944 10.24q-9.216 0-17.92-10.24t-15.36-28.16-10.752-41.984-4.096-51.712 4.096-51.712 10.752-42.496 15.36-28.672 17.92-10.24zM512-31.744000000000028q53.248 0 99.84 13.312t81.408 35.84 54.784 52.736 19.968 65.024q0 33.792-19.968 64t-54.784 52.736-81.408 35.84-99.84 13.312-99.84-13.312-81.408-35.84-54.784-52.736-19.968-64q0-34.816 19.968-65.024t54.784-52.736 81.408-35.84 99.84-13.312zM818.176 268.288q10.24 0 18.944 10.24t15.36 28.672 10.24 42.496 3.584 51.712-3.584 51.712-10.24 41.984-15.36 28.16-18.944 10.24q-9.216 0-17.92-10.24t-15.36-28.16-10.752-41.984-4.096-51.712 4.096-51.712 10.752-42.496 15.36-28.672 17.92-10.24zM512 235.51999999999998q39.936 0 68.096-9.728t28.16-24.064-28.16-24.064-68.096-9.728-68.096 9.728-28.16 24.064 28.16 24.064 68.096 9.728z"  horiz-adv-x="1024" />
24
+
25
+    
26
+    <glyph glyph-name="resize-vertical" unicode="&#59331;" d="M512 896C229.248 896 0 666.752 0 384s229.248-512 512-512 512 229.248 512 512S794.752 896 512 896zM576 192l64 0-128-128-128 128 64 0L448 576l-64 0 128 128 128-128-64 0L576 192z"  horiz-adv-x="1024" />
27
+
28
+    
29
+    <glyph glyph-name="chuizhifanzhuan" unicode="&#58977;" d="M286.01856 645.08416l472.4224 0 0-146.2784-472.4224 0 0 146.2784ZM87.19872 420.37248l885.80096 0 0-70.87104-885.80096 0 0 70.87104ZM773.55008 268.05248l0-31.0016L270.6688 237.05088l0 31.0016L773.55008 268.05248zM773.55008 121.4208l0-31.0016L270.6688 90.4192l0 31.0016L773.55008 121.4208zM742.54848 240.75776l31.0016 0 0-123.04896-31.0016 0L742.54848 240.75776zM270.70464 240.57856l31.0016 0 0-123.04896-31.0016 0L270.70464 240.57856z"  horiz-adv-x="1024" />
30
+
31
+    
32
+    <glyph glyph-name="shuipingfanzhuan" unicode="&#58978;" d="M252.76928 596.096l146.2784 0 0-472.42752-146.2784 0 0 472.42752ZM477.48096 810.65472l70.87104 0 0-885.80608-70.87104 0 0 885.80608ZM629.80096 611.2l31.0016 0 0-502.88128-31.0016 0L629.80096 611.2zM776.42752 611.2l31.0016 0 0-502.88128-31.0016 0L776.42752 611.2zM657.09056 580.1984l0 31.0016 123.04896 0 0-31.0016L657.09056 580.1984zM657.27488 108.35456l0 31.0016 123.04896 0 0-31.0016L657.27488 108.35456z"  horiz-adv-x="1024" />
33
+
34
+    
35
+    <glyph glyph-name="qq" unicode="&#58889;" d="M147.372058 491.394284c-5.28997-13.909921 2.431986-22.698872 0-75.732573-0.682996-14.25092-62.165649-78.762555-86.569511-145.791177-24.192863-66.517625-27.519845-135.978232 9.811944-163.285078 37.419789-27.305846 72.191593 90.879487 76.757567 73.685584 1.961989-7.509958 4.436975-15.317914 7.423958-23.338868a331.945126 331.945126 0 0 1 61.140655-101.162429c5.929967-6.783962-36.009797-19.199892-61.140655-61.99365-25.173858-42.751759 7.209959-120.49032 132.223254-120.49032 161.27909 0 197.288886 56.70368 200.574868 56.447681 12.031932-0.895995 12.841928 0 25.599855 0 15.572912 0 9.129948-1.279993 23.593867 0 7.807956 0.682996 86.186514-67.839617 194.686901-56.447681 184.873956 19.45589 156.586116 81.40754 142.079198 120.48932-15.103915 40.83277-68.692612 59.946662-66.303626 62.549647 44.28775 48.938724 51.285711 79.018554 66.346626 123.9463 6.143965 18.473896 49.066723-101.674426 82.089537-73.685584 13.781922 11.690934 41.301767 60.24566 13.781922 163.285078-27.519845 102.996419-80.767544 126.505286-79.615551 145.791177 2.389987 40.191773 1.023994 68.436614-1.023994 75.732573-9.812945 35.4128-30.378829 27.604844-30.378829 35.4128C858.450044 730.752933 705.10691 896 515.966978 896s-342.398067-165.289067-342.398068-369.192916c0-16.169909-14.378919-4.223976-26.154852-35.4128z"  horiz-adv-x="1024" />
36
+
37
+    
38
+    <glyph glyph-name="frown" unicode="&#59262;" d="M336 475m-48 0a48 48 0 1 1 96 0 48 48 0 1 1-96 0ZM688 475m-48 0a48 48 0 1 1 96 0 48 48 0 1 1-96 0ZM512 832C264.6 832 64 631.4 64 384s200.6-448 448-448 448 200.6 448 448S759.4 832 512 832z m263-711c-34.2-34.2-74-61-118.3-79.8C611 21.8 562.3 12 512 12c-50.3 0-99 9.8-144.8 29.2-44.3 18.7-84.1 45.6-118.3 79.8-34.2 34.2-61 74-79.8 118.3C149.8 285 140 333.7 140 384s9.8 99 29.2 144.8c18.7 44.3 45.6 84.1 79.8 118.3 34.2 34.2 74 61 118.3 79.8C413 746.2 461.7 756 512 756c50.3 0 99-9.8 144.8-29.2 44.3-18.7 84.1-45.6 118.3-79.8 34.2-34.2 61-74 79.8-118.3C874.2 483 884 434.3 884 384s-9.8-99-29.2-144.8c-18.7-44.3-45.6-84.1-79.8-118.2zM512 363c-85.5 0-155.6-67.3-160-151.6-0.2-4.6 3.4-8.4 8-8.4h48.1c4.2 0 7.8 3.2 8.1 7.4C420 259.9 461.5 299 512 299s92.1-39.1 95.8-88.6c0.3-4.2 3.9-7.4 8.1-7.4H664c4.6 0 8.2 3.8 8 8.4-4.4 84.3-74.5 151.6-160 151.6z"  horiz-adv-x="1024" />
39
+
40
+    
41
+    <glyph glyph-name="meh" unicode="&#59264;" d="M336 475m-48 0a48 48 0 1 1 96 0 48 48 0 1 1-96 0ZM688 475m-48 0a48 48 0 1 1 96 0 48 48 0 1 1-96 0ZM512 832C264.6 832 64 631.4 64 384s200.6-448 448-448 448 200.6 448 448S759.4 832 512 832z m263-711c-34.2-34.2-74-61-118.3-79.8C611 21.8 562.3 12 512 12c-50.3 0-99 9.8-144.8 29.2-44.3 18.7-84.1 45.6-118.3 79.8-34.2 34.2-61 74-79.8 118.3C149.8 285 140 333.7 140 384s9.8 99 29.2 144.8c18.7 44.3 45.6 84.1 79.8 118.3 34.2 34.2 74 61 118.3 79.8C413 746.2 461.7 756 512 756c50.3 0 99-9.8 144.8-29.2 44.3-18.7 84.1-45.6 118.3-79.8 34.2-34.2 61-74 79.8-118.3C874.2 483 884 434.3 884 384s-9.8-99-29.2-144.8c-18.7-44.3-45.6-84.1-79.8-118.2zM664 331H360c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h304c4.4 0 8 3.6 8 8v48c0 4.4-3.6 8-8 8z"  horiz-adv-x="1024" />
42
+
43
+    
44
+    <glyph glyph-name="smile" unicode="&#59267;" d="M336 475m-48 0a48 48 0 1 1 96 0 48 48 0 1 1-96 0ZM688 475m-48 0a48 48 0 1 1 96 0 48 48 0 1 1-96 0ZM512 832C264.6 832 64 631.4 64 384s200.6-448 448-448 448 200.6 448 448S759.4 832 512 832z m263-711c-34.2-34.2-74-61-118.3-79.8C611 21.8 562.3 12 512 12c-50.3 0-99 9.8-144.8 29.2-44.3 18.7-84.1 45.6-118.3 79.8-34.2 34.2-61 74-79.8 118.3C149.8 285 140 333.7 140 384s9.8 99 29.2 144.8c18.7 44.3 45.6 84.1 79.8 118.3 34.2 34.2 74 61 118.3 79.8C413 746.2 461.7 756 512 756c50.3 0 99-9.8 144.8-29.2 44.3-18.7 84.1-45.6 118.3-79.8 34.2-34.2 61-74 79.8-118.3C874.2 483 884 434.3 884 384s-9.8-99-29.2-144.8c-18.7-44.3-45.6-84.1-79.8-118.2zM664 363h-48.1c-4.2 0-7.8-3.2-8.1-7.4C604 306.1 562.5 267 512 267s-92.1 39.1-95.8 88.6c-0.3 4.2-3.9 7.4-8.1 7.4H360c-4.6 0-8.2-3.8-8-8.4 4.4-84.3 74.5-151.6 160-151.6s155.6 67.3 160 151.6c0.2 4.6-3.4 8.4-8 8.4z"  horiz-adv-x="1024" />
45
+
46
+    
47
+    <glyph glyph-name="man" unicode="&#59362;" d="M874 776H622c-3.3 0-6-2.7-6-6v-56c0-3.3 2.7-6 6-6h160.4L583.1 508.7c-50 38.5-111 59.3-175.1 59.3-76.9 0-149.3-30-203.6-84.4S120 356.9 120 280s30-149.3 84.4-203.6C258.7 22 331.1-8 408-8s149.3 30 203.6 84.4C666 130.7 696 203.1 696 280c0 64.1-20.8 124.9-59.2 174.9L836 654.1V494c0-3.3 2.7-6 6-6h56c3.3 0 6 2.7 6 6V746c0 16.5-13.5 30-30 30zM408 68c-116.9 0-212 95.1-212 212s95.1 212 212 212 212-95.1 212-212-95.1-212-212-212z"  horiz-adv-x="1024" />
48
+
49
+    
50
+    <glyph glyph-name="woman" unicode="&#59365;" d="M909.7 739.4l-42.2 42.2c-3.1 3.1-8.2 3.1-11.3 0L764 689.4l-84.2 84.2c-3.1 3.1-8.2 3.1-11.3 0l-42.1-42.1c-3.1-3.1-3.1-8.1 0-11.3l84.2-84.2-135.5-135.3c-50 38.5-111 59.3-175.1 59.3-76.9 0-149.3-30-203.6-84.4S112 348.9 112 272s30-149.3 84.4-203.6C250.7 14 323.1-16 400-16s149.3 30 203.6 84.4C658 122.7 688 195.1 688 272c0 64.2-20.9 125.1-59.3 175.1l135.4 135.4 84.2-84.2c3.1-3.1 8.2-3.1 11.3 0l42.1 42.1c3.1 3.1 3.1 8.1 0 11.3l-84.2 84.2 92.2 92.2c3.1 3.1 3.1 8.2 0 11.3zM400 60c-116.9 0-212 95.1-212 212s95.1 212 212 212 212-95.1 212-212-95.1-212-212-212z"  horiz-adv-x="1024" />
51
+
52
+    
53
+
54
+
55
+  </font>
56
+</defs></svg>

BIN
src/assets/icons/iconfont.ttf View File


BIN
src/assets/icons/iconfont.woff View File


+ 1
- 0
src/assets/images/error-page/error-401.svg
File diff suppressed because it is too large
View File


+ 1
- 0
src/assets/images/error-page/error-404.svg
File diff suppressed because it is too large
View File


+ 1
- 0
src/assets/images/error-page/error-500.svg
File diff suppressed because it is too large
View File


BIN
src/assets/images/icon-qr-qq-wechat.png View File


+ 1
- 0
src/assets/images/icon-social-bilibili.svg
File diff suppressed because it is too large
View File


+ 1
- 0
src/assets/images/icon-social-juejin.svg View File

@@ -0,0 +1 @@
1
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1547543883733" class="icon" style="" viewBox="0 0 1272 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16831" xmlns:xlink="http://www.w3.org/1999/xlink" width="496.875" height="400"><defs><style type="text/css"></style></defs><path d="M729.64116345 165.27693991L634.32650881 90.125l-99.5625 78.52693991-5.17887981 4.16056009 104.74137981 83.50215546 105.09051682-83.50215546-9.77586218-7.53556009z m361.21228445 291.47198236l-456.78879245 360.19396555-456.49784537-359.99030128L110.125 511.12715547l523.93965546 413.11745671 524.23060335-413.35021555-67.44181091-54.14547436z m-456.78879245 29.21120673L385.4784479 290.00646554 318.06573237 344.12284454l315.96982771 249.16810336 316.28987101-249.40086136-67.41271555-54.14547436-248.84806008 196.21551682z" fill="#006cff" p-id="16832"></path></svg>

+ 1
- 0
src/assets/images/icon-social-twitter.svg View File

@@ -0,0 +1 @@
1
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1547543874130" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16498" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400"><defs><style type="text/css"></style></defs><path d="M511.8 0.6C229.2 0.6 0.1 229.7 0.1 512.3S229.2 1024 511.8 1024s511.7-229.1 511.7-511.7S794.4 0.6 511.8 0.6z m264.9 375.3c-0.1 0.1-0.1 0.2-0.2 0.3h0.2c-9 14.3-21.2 28.8-34.2 39.2-5.2 4.2-10.5 8.3-15.7 12.5v0.5c0.3 22.9-0.3 44.9-4.7 64.2-25.2 113.1-91.9 189.9-197.4 222.8-37.9 11.8-99.1 16.7-142.6 5.9-21.5-5.4-41-11.4-59.3-19.3-10.2-4.5-19.6-9.3-28.5-14.7l-8.8-5.3h0.5l-0.5-0.3c9.8 0.2 21.3 2.9 32.2 1.2 9.8-1.6 19.6-1.2 28.7-3.2 22.7-5 43-11.6 60.4-21.8 8.3-4.8 20.9-10.6 27-17.6-11.2 0.2-21.4-2.4-29.7-5.4-32.6-11.5-51.6-32.7-63.9-64.4h0.1c0-0.1-0.1-0.2-0.1-0.3 9.7 1.1 37.3 3.5 44.6-1.7-12.3-0.8-24.1-7.9-32.5-13.2-26.2-16.4-47.6-43.9-47.4-86.2v-0.3c3.5 1.7 6.9 3.3 10.4 4.9 6.6 2.8 13.3 4.3 21.1 5.9 3.1 0.7 9.3 2.4 13.2 1.4-5.1-5.8-13.2-9.7-18.4-16-14.7-18.3-28.7-45.3-25.2-77.4 0.5-4.7 1.3-9.4 2.6-14.3 2.5-9.7 6.5-18.3 10.8-26.2 0.2 0.1 0.4 0.2 0.5 0.3 2 4.2 6.4 7.2 9.1 10.6 8.6 10.7 19.2 20.3 30 28.7 36.7 28.7 69.9 46.4 123.1 59.5 13.5 3.3 29 5.9 45.1 5.9-1.9-5.8-2.7-13-2.7-20.5 0-9.7 1.3-19.6 3.3-26.8 9-32.1 28.5-55.2 57-67.6 6.9-3 14.4-5.2 22.4-6.9 4.1-0.6 8.2-1.1 12.3-1.6 39-0.7 59.8 13.5 79.6 31.6 16.8-1.4 38.7-10.8 51.6-17.4l12.6-6.9c0 0.1-0.1 0.3-0.1 0.4l0.1-0.1c-7.3 19.9-17.4 35.5-32.7 47.3-3.1 2.4-6.3 5.6-10 7.4 21.5-0.4 39.3-10 56.1-15.4v0.3z" fill="#2EB1EB" p-id="16499"></path><path d="M719.7 391.1s0.1 0 0.1-0.1c0 0-0.1 0-0.1 0.1zM726.8 428.4v-0.5 0.5zM336.4 479.9c3.3 0.7 9.9 2.7 13.8 1.2h-0.5l-0.1-0.1c-3.9 1-10.1-0.7-13.2-1.4-7.8-1.6-14.5-3.1-21.1-5.9-3.5-1.6-6.9-3.2-10.4-4.9v0.3c3.4 1.6 6.9 3.2 10.4 4.9 6.6 2.8 13.3 4.3 21.1 5.9zM719.6 391.4v0.2c21.9-0.2 39.8-10.1 56.9-15.4 0.1-0.1 0.1-0.2 0.2-0.3v-0.3c-16.9 5.3-34.6 14.9-56.1 15.4-0.4 0.1-0.7 0.3-1 0.4zM584.8 337.5c6.9-3 14.4-5.1 22.4-6.9 4.1-0.6 8.2-1 12.3-1.6 39-0.7 59.8 13.5 79.6 31.6 16.8-1.4 38.7-10.8 51.6-17.4 4.2-2.3 8.3-4.5 12.5-6.8 0-0.1 0.1-0.3 0.1-0.4l-12.6 6.9c-12.9 6.6-34.8 16-51.6 17.4-19.8-18.1-40.6-32.3-79.6-31.6-4.1 0.5-8.2 1-12.3 1.6-8 1.7-15.5 3.9-22.4 6.9-28.5 12.4-48 35.5-57 67.6-2 7.2-3.4 17.2-3.3 26.8 0-9.6 1.3-19.4 3.3-26.5 9-32.1 28.5-55.2 57-67.6zM385.2 568.5h-0.4c-7.3 5.2-34.9 2.8-44.6 1.7 0 0.1 0.1 0.2 0.1 0.3 10 1.1 38.2 3.6 44.9-2zM319.4 347.4c0.1 0.1 0.3 0.1 0.5 0.3 2 4.1 6.3 7.1 9.1 10.6 8.6 10.6 19.2 20.2 30 28.7 36.8 28.7 69.9 46.4 123.1 59.5 13.5 3.3 29.1 5.9 45.2 5.9 0-0.1-0.1-0.2-0.1-0.3-16.1 0-31.6-2.6-45.1-5.9-53.2-13.1-86.4-30.8-123.1-59.5-10.8-8.4-21.4-18-30-28.7-2.7-3.4-7.1-6.4-9.1-10.6-0.1-0.1-0.3-0.2-0.5-0.3-4.3 7.9-8.3 16.5-10.8 26.2-1.3 4.9-2.1 9.6-2.6 14.3 0.5-4.6 1.3-9.2 2.6-14 2.5-9.7 6.5-18.3 10.8-26.2zM317.7 683.2c9.9-1.6 19.6-1.2 28.7-3.2 22.8-5 43-11.6 60.4-21.8 8.4-4.9 21.3-10.8 27.3-17.9h-0.3c-6.1 7-18.7 12.8-27 17.6-17.4 10.2-37.7 16.8-60.4 21.8-9.1 2-18.9 1.6-28.7 3.2-10.9 1.7-22.4-1-32.2-1.2l0.5 0.3c9.7 0.4 21 2.9 31.7 1.2z" fill="#FFFFFF" p-id="16500"></path></svg>

+ 1
- 0
src/assets/images/icon-social-zhihu.svg View File

@@ -0,0 +1 @@
1
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1547543863835" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16165" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400"><defs><style type="text/css"></style></defs><path d="M512 1024C229.236364 1024 0 794.763636 0 512S229.236364 0 512 0s512 229.236364 512 512-229.236364 512-512 512z m-129.861818-756.48s-36.212364 2.094545-48.989091 24.482909c-12.8 22.365091-54.318545 137.378909-54.318546 137.378909s13.847273 6.376727 37.28291-10.658909c23.435636-17.035636 30.882909-46.848 30.882909-46.848l42.589091-2.117818 1.070545 121.390545s-73.495273-1.070545-88.413091 0c-14.894545 1.047273-23.412364 40.448-23.412364 40.448h111.825455s-9.588364 67.095273-38.353455 116.084364c-28.741818 48.989091-83.060364 87.319273-83.060363 87.319273s39.424 15.965091 77.730909-6.4c38.353455-22.341818 66.629818-120.692364 66.629818-120.692364l89.925818 110.056727s8.192-52.386909-1.466182-67.188363c-9.658182-14.778182-62.208-74.286545-62.208-74.286546l-22.946909 20.247273 16.337455-65.117091h97.954909s0-38.353455-19.153455-40.494545c-19.176727-2.094545-78.801455 0-78.801454 0V371.898182h88.389818s-1.070545-39.400727-18.106182-39.400727h-143.755636l22.341818-64.954182z m169.984 61.184v358.562909h36.002909l13.102545 45.009455 63.348364-45.009455h89.064727V328.704h-201.518545z" fill="#0f84fd" p-id="16166"></path><path d="M594.781091 368.64h117.899636v277.876364h-41.890909l-53.364363 40.261818-11.636364-40.261818h-11.008V368.64z" fill="#0f84fd" p-id="16167"></path></svg>

BIN
src/assets/images/login-bg.jpg View File


BIN
src/assets/images/logo-min.jpg View File


BIN
src/assets/images/logo.jpg View File


BIN
src/assets/images/talkingdata.png View File


BIN
src/assets/img/bg.png View File


BIN
src/assets/img/gou.png View File


BIN
src/assets/img/key.png View File


BIN
src/assets/img/kuang.png View File


BIN
src/assets/img/logo.png View File


BIN
src/assets/img/user.png View File


+ 58
- 0
src/components/charts/bar.vue View File

@@ -0,0 +1,58 @@
1
+<template>
2
+  <div ref="dom" class="charts chart-bar"></div>
3
+</template>
4
+
5
+<script>
6
+import echarts from 'echarts'
7
+import tdTheme from './theme.json'
8
+import { on, off } from '@/libs/tools'
9
+echarts.registerTheme('tdTheme', tdTheme)
10
+export default {
11
+  name: 'ChartBar',
12
+  props: {
13
+    value: Object,
14
+    text: String,
15
+    subtext: String
16
+  },
17
+  data () {
18
+    return {
19
+      dom: null
20
+    }
21
+  },
22
+  methods: {
23
+    resize () {
24
+      this.dom.resize()
25
+    }
26
+  },
27
+  mounted () {
28
+    this.$nextTick(() => {
29
+      let xAxisData = Object.keys(this.value)
30
+      let seriesData = Object.values(this.value)
31
+      let option = {
32
+        title: {
33
+          text: this.text,
34
+          subtext: this.subtext,
35
+          x: 'center'
36
+        },
37
+        xAxis: {
38
+          type: 'category',
39
+          data: xAxisData
40
+        },
41
+        yAxis: {
42
+          type: 'value'
43
+        },
44
+        series: [{
45
+          data: seriesData,
46
+          type: 'bar'
47
+        }]
48
+      }
49
+      this.dom = echarts.init(this.$refs.dom, 'tdTheme')
50
+      this.dom.setOption(option)
51
+      on(window, 'resize', this.resize)
52
+    })
53
+  },
54
+  beforeDestroy () {
55
+    off(window, 'resize', this.resize)
56
+  }
57
+}
58
+</script>

+ 3
- 0
src/components/charts/index.js View File

@@ -0,0 +1,3 @@
1
+import ChartPie from './pie.vue'
2
+import ChartBar from './bar.vue'
3
+export { ChartPie, ChartBar }

+ 70
- 0
src/components/charts/pie.vue View File

@@ -0,0 +1,70 @@
1
+<template>
2
+  <div ref="dom" class="charts chart-pie"></div>
3
+</template>
4
+
5
+<script>
6
+import echarts from 'echarts'
7
+import tdTheme from './theme.json'
8
+import { on, off } from '@/libs/tools'
9
+echarts.registerTheme('tdTheme', tdTheme)
10
+export default {
11
+  name: 'ChartPie',
12
+  props: {
13
+    value: Array,
14
+    text: String,
15
+    subtext: String
16
+  },
17
+  data () {
18
+    return {
19
+      dom: null
20
+    }
21
+  },
22
+  methods: {
23
+    resize () {
24
+      this.dom.resize()
25
+    }
26
+  },
27
+  mounted () {
28
+    this.$nextTick(() => {
29
+      let legend = this.value.map(_ => _.name)
30
+      let option = {
31
+        title: {
32
+          text: this.text,
33
+          subtext: this.subtext,
34
+          x: 'center'
35
+        },
36
+        tooltip: {
37
+          trigger: 'item',
38
+          formatter: '{a} <br/>{b} : {c} ({d}%)'
39
+        },
40
+        legend: {
41
+          orient: 'vertical',
42
+          left: 'left',
43
+          data: legend
44
+        },
45
+        series: [
46
+          {
47
+            type: 'pie',
48
+            radius: '55%',
49
+            center: ['50%', '60%'],
50
+            data: this.value,
51
+            itemStyle: {
52
+              emphasis: {
53
+                shadowBlur: 10,
54
+                shadowOffsetX: 0,
55
+                shadowColor: 'rgba(0, 0, 0, 0.5)'
56
+              }
57
+            }
58
+          }
59
+        ]
60
+      }
61
+      this.dom = echarts.init(this.$refs.dom, 'tdTheme')
62
+      this.dom.setOption(option)
63
+      on(window, 'resize', this.resize)
64
+    })
65
+  },
66
+  beforeDestroy () {
67
+    off(window, 'resize', this.resize)
68
+  }
69
+}
70
+</script>

+ 491
- 0
src/components/charts/theme.json View File

@@ -0,0 +1,491 @@
1
+
2
+{
3
+    "color": [
4
+        "#2d8cf0",
5
+        "#19be6b",
6
+        "#ff9900",
7
+        "#E46CBB",
8
+        "#9A66E4",
9
+        "#ed3f14"
10
+    ],
11
+    "backgroundColor": "rgba(0,0,0,0)",
12
+    "textStyle": {},
13
+    "title": {
14
+        "textStyle": {
15
+            "color": "#516b91"
16
+        },
17
+        "subtextStyle": {
18
+            "color": "#93b7e3"
19
+        }
20
+    },
21
+    "line": {
22
+        "itemStyle": {
23
+            "normal": {
24
+                "borderWidth": "2"
25
+            }
26
+        },
27
+        "lineStyle": {
28
+            "normal": {
29
+                "width": "2"
30
+            }
31
+        },
32
+        "symbolSize": "6",
33
+        "symbol": "emptyCircle",
34
+        "smooth": true
35
+    },
36
+    "radar": {
37
+        "itemStyle": {
38
+            "normal": {
39
+                "borderWidth": "2"
40
+            }
41
+        },
42
+        "lineStyle": {
43
+            "normal": {
44
+                "width": "2"
45
+            }
46
+        },
47
+        "symbolSize": "6",
48
+        "symbol": "emptyCircle",
49
+        "smooth": true
50
+    },
51
+    "bar": {
52
+        "itemStyle": {
53
+            "normal": {
54
+                "barBorderWidth": 0,
55
+                "barBorderColor": "#ccc"
56
+            },
57
+            "emphasis": {
58
+                "barBorderWidth": 0,
59
+                "barBorderColor": "#ccc"
60
+            }
61
+        }
62
+    },
63
+    "pie": {
64
+        "itemStyle": {
65
+            "normal": {
66
+                "borderWidth": 0,
67
+                "borderColor": "#ccc"
68
+            },
69
+            "emphasis": {
70
+                "borderWidth": 0,
71
+                "borderColor": "#ccc"
72
+            }
73
+        }
74
+    },
75
+    "scatter": {
76
+        "itemStyle": {
77
+            "normal": {
78
+                "borderWidth": 0,
79
+                "borderColor": "#ccc"
80
+            },
81
+            "emphasis": {
82
+                "borderWidth": 0,
83
+                "borderColor": "#ccc"
84
+            }
85
+        }
86
+    },
87
+    "boxplot": {
88
+        "itemStyle": {
89
+            "normal": {
90
+                "borderWidth": 0,
91
+                "borderColor": "#ccc"
92
+            },
93
+            "emphasis": {
94
+                "borderWidth": 0,
95
+                "borderColor": "#ccc"
96
+            }
97
+        }
98
+    },
99
+    "parallel": {
100
+        "itemStyle": {
101
+            "normal": {
102
+                "borderWidth": 0,
103
+                "borderColor": "#ccc"
104
+            },
105
+            "emphasis": {
106
+                "borderWidth": 0,
107
+                "borderColor": "#ccc"
108
+            }
109
+        }
110
+    },
111
+    "sankey": {
112
+        "itemStyle": {
113
+            "normal": {
114
+                "borderWidth": 0,
115
+                "borderColor": "#ccc"
116
+            },
117
+            "emphasis": {
118
+                "borderWidth": 0,
119
+                "borderColor": "#ccc"
120
+            }
121
+        }
122
+    },
123
+    "funnel": {
124
+        "itemStyle": {
125
+            "normal": {
126
+                "borderWidth": 0,
127
+                "borderColor": "#ccc"
128
+            },
129
+            "emphasis": {
130
+                "borderWidth": 0,
131
+                "borderColor": "#ccc"
132
+            }
133
+        }
134
+    },
135
+    "gauge": {
136
+        "itemStyle": {
137
+            "normal": {
138
+                "borderWidth": 0,
139
+                "borderColor": "#ccc"
140
+            },
141
+            "emphasis": {
142
+                "borderWidth": 0,
143
+                "borderColor": "#ccc"
144
+            }
145
+        }
146
+    },
147
+    "candlestick": {
148
+        "itemStyle": {
149
+            "normal": {
150
+                "color": "#edafda",
151
+                "color0": "transparent",
152
+                "borderColor": "#d680bc",
153
+                "borderColor0": "#8fd3e8",
154
+                "borderWidth": "2"
155
+            }
156
+        }
157
+    },
158
+    "graph": {
159
+        "itemStyle": {
160
+            "normal": {
161
+                "borderWidth": 0,
162
+                "borderColor": "#ccc"
163
+            }
164
+        },
165
+        "lineStyle": {
166
+            "normal": {
167
+                "width": 1,
168
+                "color": "#aaa"
169
+            }
170
+        },
171
+        "symbolSize": "6",
172
+        "symbol": "emptyCircle",
173
+        "smooth": true,
174
+        "color": [
175
+            "#2d8cf0",
176
+            "#19be6b",
177
+            "#f5ae4a",
178
+            "#9189d5",
179
+            "#56cae2",
180
+            "#cbb0e3"
181
+        ],
182
+        "label": {
183
+            "normal": {
184
+                "textStyle": {
185
+                    "color": "#eee"
186
+                }
187
+            }
188
+        }
189
+    },
190
+    "map": {
191
+        "itemStyle": {
192
+            "normal": {
193
+                "areaColor": "#f3f3f3",
194
+                "borderColor": "#516b91",
195
+                "borderWidth": 0.5
196
+            },
197
+            "emphasis": {
198
+                "areaColor": "rgba(165,231,240,1)",
199
+                "borderColor": "#516b91",
200
+                "borderWidth": 1
201
+            }
202
+        },
203
+        "label": {
204
+            "normal": {
205
+                "textStyle": {
206
+                    "color": "#000"
207
+                }
208
+            },
209
+            "emphasis": {
210
+                "textStyle": {
211
+                    "color": "rgb(81,107,145)"
212
+                }
213
+            }
214
+        }
215
+    },
216
+    "geo": {
217
+        "itemStyle": {
218
+            "normal": {
219
+                "areaColor": "#f3f3f3",
220
+                "borderColor": "#516b91",
221
+                "borderWidth": 0.5
222
+            },
223
+            "emphasis": {
224
+                "areaColor": "rgba(165,231,240,1)",
225
+                "borderColor": "#516b91",
226
+                "borderWidth": 1
227
+            }
228
+        },
229
+        "label": {
230
+            "normal": {
231
+                "textStyle": {
232
+                    "color": "#000"
233
+                }
234
+            },
235
+            "emphasis": {
236
+                "textStyle": {
237
+                    "color": "rgb(81,107,145)"
238
+                }
239
+            }
240
+        }
241
+    },
242
+    "categoryAxis": {
243
+        "axisLine": {
244
+            "show": true,
245
+            "lineStyle": {
246
+                "color": "#cccccc"
247
+            }
248
+        },
249
+        "axisTick": {
250
+            "show": false,
251
+            "lineStyle": {
252
+                "color": "#333"
253
+            }
254
+        },
255
+        "axisLabel": {
256
+            "show": true,
257
+            "textStyle": {
258
+                "color": "#999999"
259
+            }
260
+        },
261
+        "splitLine": {
262
+            "show": true,
263
+            "lineStyle": {
264
+                "color": [
265
+                    "#eeeeee"
266
+                ]
267
+            }
268
+        },
269
+        "splitArea": {
270
+            "show": false,
271
+            "areaStyle": {
272
+                "color": [
273
+                    "rgba(250,250,250,0.05)",
274
+                    "rgba(200,200,200,0.02)"
275
+                ]
276
+            }
277
+        }
278
+    },
279
+    "valueAxis": {
280
+        "axisLine": {
281
+            "show": true,
282
+            "lineStyle": {
283
+                "color": "#cccccc"
284
+            }
285
+        },
286
+        "axisTick": {
287
+            "show": false,
288
+            "lineStyle": {
289
+                "color": "#333"
290
+            }
291
+        },
292
+        "axisLabel": {
293
+            "show": true,
294
+            "textStyle": {
295
+                "color": "#999999"
296
+            }
297
+        },
298
+        "splitLine": {
299
+            "show": true,
300
+            "lineStyle": {
301
+                "color": [
302
+                    "#eeeeee"
303
+                ]
304
+            }
305
+        },
306
+        "splitArea": {
307
+            "show": false,
308
+            "areaStyle": {
309
+                "color": [
310
+                    "rgba(250,250,250,0.05)",
311
+                    "rgba(200,200,200,0.02)"
312
+                ]
313
+            }
314
+        }
315
+    },
316
+    "logAxis": {
317
+        "axisLine": {
318
+            "show": true,
319
+            "lineStyle": {
320
+                "color": "#cccccc"
321
+            }
322
+        },
323
+        "axisTick": {
324
+            "show": false,
325
+            "lineStyle": {
326
+                "color": "#333"
327
+            }
328
+        },
329
+        "axisLabel": {
330
+            "show": true,
331
+            "textStyle": {
332
+                "color": "#999999"
333
+            }
334
+        },
335
+        "splitLine": {
336
+            "show": true,
337
+            "lineStyle": {
338
+                "color": [
339
+                    "#eeeeee"
340
+                ]
341
+            }
342
+        },
343
+        "splitArea": {
344
+            "show": false,
345
+            "areaStyle": {
346
+                "color": [
347
+                    "rgba(250,250,250,0.05)",
348
+                    "rgba(200,200,200,0.02)"
349
+                ]
350
+            }
351
+        }
352
+    },
353
+    "timeAxis": {
354
+        "axisLine": {
355
+            "show": true,
356
+            "lineStyle": {
357
+                "color": "#cccccc"
358
+            }
359
+        },
360
+        "axisTick": {
361
+            "show": false,
362
+            "lineStyle": {
363
+                "color": "#333"
364
+            }
365
+        },
366
+        "axisLabel": {
367
+            "show": true,
368
+            "textStyle": {
369
+                "color": "#999999"
370
+            }
371
+        },
372
+        "splitLine": {
373
+            "show": true,
374
+            "lineStyle": {
375
+                "color": [
376
+                    "#eeeeee"
377
+                ]
378
+            }
379
+        },
380
+        "splitArea": {
381
+            "show": false,
382
+            "areaStyle": {
383
+                "color": [
384
+                    "rgba(250,250,250,0.05)",
385
+                    "rgba(200,200,200,0.02)"
386
+                ]
387
+            }
388
+        }
389
+    },
390
+    "toolbox": {
391
+        "iconStyle": {
392
+            "normal": {
393
+                "borderColor": "#999"
394
+            },
395
+            "emphasis": {
396
+                "borderColor": "#666"
397
+            }
398
+        }
399
+    },
400
+    "legend": {
401
+        "textStyle": {
402
+            "color": "#999999"
403
+        }
404
+    },
405
+    "tooltip": {
406
+        "axisPointer": {
407
+            "lineStyle": {
408
+                "color": "#ccc",
409
+                "width": 1
410
+            },
411
+            "crossStyle": {
412
+                "color": "#ccc",
413
+                "width": 1
414
+            }
415
+        }
416
+    },
417
+    "timeline": {
418
+        "lineStyle": {
419
+            "color": "#8fd3e8",
420
+            "width": 1
421
+        },
422
+        "itemStyle": {
423
+            "normal": {
424
+                "color": "#8fd3e8",
425
+                "borderWidth": 1
426
+            },
427
+            "emphasis": {
428
+                "color": "#8fd3e8"
429
+            }
430
+        },
431
+        "controlStyle": {
432
+            "normal": {
433
+                "color": "#8fd3e8",
434
+                "borderColor": "#8fd3e8",
435
+                "borderWidth": 0.5
436
+            },
437
+            "emphasis": {
438
+                "color": "#8fd3e8",
439
+                "borderColor": "#8fd3e8",
440
+                "borderWidth": 0.5
441
+            }
442
+        },
443
+        "checkpointStyle": {
444
+            "color": "#8fd3e8",
445
+            "borderColor": "rgba(138,124,168,0.37)"
446
+        },
447
+        "label": {
448
+            "normal": {
449
+                "textStyle": {
450
+                    "color": "#8fd3e8"
451
+                }
452
+            },
453
+            "emphasis": {
454
+                "textStyle": {
455
+                    "color": "#8fd3e8"
456
+                }
457
+            }
458
+        }
459
+    },
460
+    "visualMap": {
461
+        "color": [
462
+            "#516b91",
463
+            "#59c4e6",
464
+            "#a5e7f0"
465
+        ]
466
+    },
467
+    "dataZoom": {
468
+        "backgroundColor": "rgba(0,0,0,0)",
469
+        "dataBackgroundColor": "rgba(255,255,255,0.3)",
470
+        "fillerColor": "rgba(167,183,204,0.4)",
471
+        "handleColor": "#a7b7cc",
472
+        "handleSize": "100%",
473
+        "textStyle": {
474
+            "color": "#333"
475
+        }
476
+    },
477
+    "markPoint": {
478
+        "label": {
479
+            "normal": {
480
+                "textStyle": {
481
+                    "color": "#eee"
482
+                }
483
+            },
484
+            "emphasis": {
485
+                "textStyle": {
486
+                    "color": "#eee"
487
+                }
488
+            }
489
+        }
490
+    }
491
+}

+ 42
- 0
src/components/common-icon/common-icon.vue View File

@@ -0,0 +1,42 @@
1
+<template>
2
+  <component :is="iconType" :type="iconName" :color="iconColor" :size="iconSize"/>
3
+</template>
4
+
5
+<script>
6
+import Icons from '_c/icons'
7
+export default {
8
+  name: 'CommonIcon',
9
+  components: { Icons },
10
+  props: {
11
+    type: {
12
+      type: String,
13
+      required: true
14
+    },
15
+    color: String,
16
+    size: Number
17
+  },
18
+  computed: {
19
+    iconType () {
20
+      return this.type.indexOf('_') === 0 ? 'Icons' : 'Icon'
21
+    },
22
+    iconName () {
23
+      return this.iconType === 'Icons' ? this.getCustomIconName(this.type) : this.type
24
+    },
25
+    iconSize () {
26
+      return this.size || (this.iconType === 'Icons' ? 12 : undefined)
27
+    },
28
+    iconColor () {
29
+      return this.color || ''
30
+    }
31
+  },
32
+  methods: {
33
+    getCustomIconName (iconName) {
34
+      return iconName.slice(1)
35
+    }
36
+  }
37
+}
38
+</script>
39
+
40
+<style>
41
+
42
+</style>

+ 2
- 0
src/components/common-icon/index.js View File

@@ -0,0 +1,2 @@
1
+import CommonIcon from './common-icon.vue'
2
+export default CommonIcon

+ 8
- 0
src/components/common/common.less View File

@@ -0,0 +1,8 @@
1
+.no-select{
2
+  -webkit-touch-callout: none;
3
+  -webkit-user-select: none;
4
+  -khtml-user-select: none;
5
+  -moz-user-select: none;
6
+  -ms-user-select: none;
7
+  user-select: none;
8
+}

+ 3
- 0
src/components/common/util.js View File

@@ -0,0 +1,3 @@
1
+export const showTitle = (item, vm) => {
2
+  return vm.$config.useI18n ? vm.$t(item.name) : ((item.meta && item.meta.title) || item.name)
3
+}

+ 174
- 0
src/components/count-to/count-to.vue View File

@@ -0,0 +1,174 @@
1
+<template>
2
+  <div class="count-to-wrapper">
3
+    <slot name="left"/>
4
+    <p class="content-outer"><span :class="['count-to-count-text', countClass]" :id="counterId">{{ init }}</span><i :class="['count-to-unit-text', unitClass]">{{ unitText }}</i></p>
5
+    <slot name="right"/>
6
+  </div>
7
+</template>
8
+
9
+<script>
10
+import CountUp from 'countup'
11
+import './index.less'
12
+export default {
13
+  name: 'CountTo',
14
+  props: {
15
+    init: {
16
+      type: Number,
17
+      default: 0
18
+    },
19
+    /**
20
+     * @description 起始值,即动画开始前显示的数值
21
+     */
22
+    startVal: {
23
+      type: Number,
24
+      default: 0
25
+    },
26
+    /**
27
+     * @description 结束值,即动画结束后显示的数值
28
+     */
29
+    end: {
30
+      type: Number,
31
+      required: true
32
+    },
33
+    /**
34
+     * @description 保留几位小数
35
+     */
36
+    decimals: {
37
+      type: Number,
38
+      default: 0
39
+    },
40
+    /**
41
+     * @description 分隔整数和小数的符号,默认是小数点
42
+     */
43
+    decimal: {
44
+      type: String,
45
+      default: '.'
46
+    },
47
+    /**
48
+     * @description 动画持续的时间,单位是秒
49
+     */
50
+    duration: {
51
+      type: Number,
52
+      default: 2
53
+    },
54
+    /**
55
+     * @description 动画延迟开始的时间,单位是秒
56
+     */
57
+    delay: {
58
+      type: Number,
59
+      default: 0
60
+    },
61
+    /**
62
+     * @description 是否禁用easing动画效果
63
+     */
64
+    uneasing: {
65
+      type: Boolean,
66
+      default: false
67
+    },
68
+    /**
69
+     * @description 是否使用分组,分组后每三位会用一个符号分隔
70
+     */
71
+    usegroup: {
72
+      type: Boolean,
73
+      default: false
74
+    },
75
+    /**
76
+     * @description 用于分组(usegroup)的符号
77
+     */
78
+    separator: {
79
+      type: String,
80
+      default: ','
81
+    },
82
+    /**
83
+     * @description 是否简化显示,设为true后会使用unit单位来做相关省略
84
+     */
85
+    simplify: {
86
+      type: Boolean,
87
+      default: false
88
+    },
89
+    /**
90
+     * @description 自定义单位,如[3, 'K+'], [6, 'M+']即大于3位数小于6位数的用k+来做省略
91
+     *              1000即显示为1K+
92
+     */
93
+    unit: {
94
+      type: Array,
95
+      default () {
96
+        return [[3, 'K+'], [6, 'M+'], [9, 'B+']]
97
+      }
98
+    },
99
+    countClass: {
100
+      type: String,
101
+      default: ''
102
+    },
103
+    unitClass: {
104
+      type: String,
105
+      default: ''
106
+    }
107
+  },
108
+  data () {
109
+    return {
110
+      counter: null,
111
+      unitText: ''
112
+    }
113
+  },
114
+  computed: {
115
+    counterId () {
116
+      return `count_to_${this._uid}`
117
+    }
118
+  },
119
+  methods: {
120
+    getHandleVal (val, len) {
121
+      return {
122
+        endVal: parseInt(val / Math.pow(10, this.unit[len - 1][0])),
123
+        unitText: this.unit[len - 1][1]
124
+      }
125
+    },
126
+    transformValue (val) {
127
+      let len = this.unit.length
128
+      let res = {
129
+        endVal: 0,
130
+        unitText: ''
131
+      }
132
+      if (val < Math.pow(10, this.unit[0][0])) res.endVal = val
133
+      else {
134
+        for (let i = 1; i < len; i++) {
135
+          if (val >= Math.pow(10, this.unit[i - 1][0]) && val < Math.pow(10, this.unit[i][0])) res = this.getHandleVal(val, i)
136
+        }
137
+      }
138
+      if (val > Math.pow(10, this.unit[len - 1][0])) res = this.getHandleVal(val, len)
139
+      return res
140
+    },
141
+    getValue (val) {
142
+      let res = 0
143
+      if (this.simplify) {
144
+        let { endVal, unitText } = this.transformValue(val)
145
+        this.unitText = unitText
146
+        res = endVal
147
+      } else {
148
+        res = val
149
+      }
150
+      return res
151
+    }
152
+  },
153
+  mounted () {
154
+    this.$nextTick(() => {
155
+      let endVal = this.getValue(this.end)
156
+      this.counter = new CountUp(this.counterId, this.startVal, endVal, this.decimals, this.duration, {
157
+        useEasing: !this.uneasing,
158
+        useGrouping: this.useGroup,
159
+        separator: this.separator,
160
+        decimal: this.decimal
161
+      })
162
+      setTimeout(() => {
163
+        if (!this.counter.error) this.counter.start()
164
+      }, this.delay)
165
+    })
166
+  },
167
+  watch: {
168
+    end (newVal) {
169
+      let endVal = this.getValue(newVal)
170
+      this.counter.update(endVal)
171
+    }
172
+  }
173
+}
174
+</script>

+ 2
- 0
src/components/count-to/index.js View File

@@ -0,0 +1,2 @@
1
+import countTo from './count-to.vue'
2
+export default countTo

+ 10
- 0
src/components/count-to/index.less View File

@@ -0,0 +1,10 @@
1
+@prefix: ~"count-to";
2
+
3
+.@{prefix}-wrapper{
4
+  .content-outer{
5
+    display: inline-block;
6
+    .@{prefix}-unit-text{
7
+      font-style: normal;
8
+    }
9
+  }
10
+}

+ 2
- 0
src/components/cropper/index.js View File

@@ -0,0 +1,2 @@
1
+import Cropper from './index.vue'
2
+export default Cropper

+ 35
- 0
src/components/cropper/index.less View File

@@ -0,0 +1,35 @@
1
+.bg{
2
+  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC")
3
+}
4
+.cropper-wrapper{
5
+  width: 600px;
6
+  height: 340px;
7
+  .img-box{
8
+    height: 340px;
9
+    width: 430px;
10
+    border: 1px solid #ebebeb;
11
+    display: inline-block;
12
+    .bg;
13
+    img{
14
+      max-width: 100%;
15
+      display: block;
16
+    }
17
+  }
18
+  .right-con{
19
+    display: inline-block;
20
+    width: 170px;
21
+    vertical-align: top;
22
+    box-sizing: border-box;
23
+    padding: 0 10px;
24
+    .preview-box{
25
+      height: 150px !important;
26
+      width: 100% !important;
27
+      overflow: hidden;
28
+      border: 1px solid #ebebeb;
29
+      .bg;
30
+    }
31
+    .button-box{
32
+      padding: 10px 0 0;
33
+    }
34
+  }
35
+}

+ 139
- 0
src/components/cropper/index.vue View File

@@ -0,0 +1,139 @@
1
+<template>
2
+  <div class="cropper-wrapper">
3
+    <div class="img-box">
4
+      <img class="cropper-image" :id="imgId" alt="">
5
+    </div>
6
+    <div class="right-con">
7
+      <div v-if="preview" class="preview-box" :id="previewId"></div>
8
+      <div class="button-box">
9
+        <slot>
10
+          <Upload action="image/upload" :before-upload="beforeUpload">
11
+            <Button style="width: 150px;" type="primary">上传图片</Button>
12
+          </Upload>
13
+        </slot>
14
+        <div v-show="insideSrc">
15
+          <Button type="primary" @click="rotate">
16
+            <Icon type="md-refresh" :size="18"/>
17
+          </Button>
18
+          <Button type="primary" @click="shrink">
19
+            <Icon type="md-remove" :size="18"/>
20
+          </Button>
21
+          <Button type="primary" @click="magnify">
22
+            <Icon type="md-add" :size="18"/>
23
+          </Button>
24
+          <Button type="primary" @click="scale('X')">
25
+            <Icon custom="iconfont icon-shuipingfanzhuan" :size="18"/>
26
+          </Button>
27
+          <Button type="primary" @click="scale('Y')">
28
+            <Icon custom="iconfont icon-chuizhifanzhuan" :size="18"/>
29
+          </Button>
30
+          <Button type="primary" @click="move(0, -moveStep)">
31
+            <Icon type="md-arrow-round-up" :size="18"/>
32
+          </Button>
33
+          <Button type="primary" @click="move(-moveStep, 0)">
34
+            <Icon type="md-arrow-round-back" :size="18"/>
35
+          </Button>
36
+          <Button type="primary" @click="move(0, moveStep)">
37
+            <Icon type="md-arrow-round-down" :size="18"/>
38
+          </Button>
39
+          <Button type="primary" @click="move(moveStep, 0)">
40
+            <Icon type="md-arrow-round-forward" :size="18"/>
41
+          </Button>
42
+          <Button style="width: 150px;margin-top: 10px;" type="primary" @click="crop">{{ cropButtonText }}</Button>
43
+        </div>
44
+      </div>
45
+    </div>
46
+  </div>
47
+</template>
48
+
49
+<script>
50
+import Cropper from 'cropperjs'
51
+import './index.less'
52
+import 'cropperjs/dist/cropper.min.css'
53
+export default {
54
+  name: 'Cropper',
55
+  props: {
56
+    src: {
57
+      type: String,
58
+      default: ''
59
+    },
60
+    preview: {
61
+      type: Boolean,
62
+      default: true
63
+    },
64
+    moveStep: {
65
+      type: Number,
66
+      default: 4
67
+    },
68
+    cropButtonText: {
69
+      type: String,
70
+      default: '裁剪'
71
+    }
72
+  },
73
+  data () {
74
+    return {
75
+      cropper: null,
76
+      insideSrc: ''
77
+    }
78
+  },
79
+  computed: {
80
+    imgId () {
81
+      return `cropper${this._uid}`
82
+    },
83
+    previewId () {
84
+      return `cropper_preview${this._uid}`
85
+    }
86
+  },
87
+  watch: {
88
+    src (src) {
89
+      this.replace(src)
90
+    },
91
+    insideSrc (src) {
92
+      this.replace(src)
93
+    }
94
+  },
95
+  methods: {
96
+    beforeUpload (file) {
97
+      const reader = new FileReader()
98
+      reader.readAsDataURL(file)
99
+      reader.onload = (event) => {
100
+        this.insideSrc = event.srcElement.result
101
+      }
102
+      return false
103
+    },
104
+    replace (src) {
105
+      this.cropper.replace(src)
106
+      this.insideSrc = src
107
+    },
108
+    rotate () {
109
+      this.cropper.rotate(90)
110
+    },
111
+    shrink () {
112
+      this.cropper.zoom(-0.1)
113
+    },
114
+    magnify () {
115
+      this.cropper.zoom(0.1)
116
+    },
117
+    scale (d) {
118
+      this.cropper[`scale${d}`](-this.cropper.getData()[`scale${d}`])
119
+    },
120
+    move (...argu) {
121
+      this.cropper.move(...argu)
122
+    },
123
+    crop () {
124
+      this.cropper.getCroppedCanvas().toBlob(blob => {
125
+        this.$emit('on-crop', blob)
126
+      })
127
+    }
128
+  },
129
+  mounted () {
130
+    this.$nextTick(() => {
131
+      let dom = document.getElementById(this.imgId)
132
+      this.cropper = new Cropper(dom, {
133
+        preview: `#${this.previewId}`,
134
+        checkCrossOrigin: true
135
+      })
136
+    })
137
+  }
138
+}
139
+</script>

+ 18
- 0
src/components/drag-drawer/drag-drawer-trigger.vue View File

@@ -0,0 +1,18 @@
1
+<template>
2
+  <div :class="`${prefix}-move-trigger`">
3
+    <div :class="`${prefix}-move-trigger-point`">
4
+      <i></i><i></i><i></i><i></i><i></i>
5
+    </div>
6
+  </div>
7
+</template>
8
+
9
+<script>
10
+import Mixin from './mixin'
11
+export default {
12
+  name: 'DragDrawerTrigger',
13
+  mixins: [Mixin]
14
+}
15
+</script>
16
+
17
+<style>
18
+</style>

+ 156
- 0
src/components/drag-drawer/drag-drawer.vue View File

@@ -0,0 +1,156 @@
1
+<template>
2
+  <Drawer ref="drawerWrapper"
3
+          :value="value"
4
+          @input="handleInput"
5
+          :width="width"
6
+          :class-name="outerClasses"
7
+          v-bind="$attrs"
8
+          v-on="$listeners">
9
+    <!-- 所有插槽内容显示在这里 ↓ -->
10
+
11
+    <template v-for="(slots, slotsName) in $slots">
12
+      <template v-if="slotsName !== 'default'">
13
+        <render-dom v-for="(render, index) in slots"
14
+                    :key="`b_drawer_${slotsName}_${index}`"
15
+                    :render="() => render"
16
+                    :slot="slotsName">
17
+        </render-dom>
18
+      </template>
19
+      <template v-else>
20
+        <div :class="`${prefix}-body-wrapper`"
21
+             :key="`b_drawer_${slotsName}`">
22
+          <render-dom v-for="(render, index) in slots"
23
+                      :key="`b_drawer_${slotsName}_${index}`"
24
+                      :render="() => render"
25
+                      :slot="slotsName">
26
+          </render-dom>
27
+        </div>
28
+      </template>
29
+    </template>
30
+    <!-- 所有插槽内容显示在这里 ↑ -->
31
+    <div v-if="draggable"
32
+         :style="triggerStyle"
33
+         :class="`${prefix}-trigger-wrapper`"
34
+         @mousedown="handleTriggerMousedown">
35
+      <slot name="trigger">
36
+        <drag-drawer-trigger></drag-drawer-trigger>
37
+      </slot>
38
+    </div>
39
+    <div v-if="$slots.footer"
40
+         :class="`${prefix}-footer`">
41
+      <slot name="footer"></slot>
42
+    </div>
43
+  </Drawer>
44
+</template>
45
+
46
+<script>
47
+import RenderDom from '@/libs/render-dom'
48
+import DragDrawerTrigger from './drag-drawer-trigger.vue'
49
+import Mixin from './mixin'
50
+import { on, off } from '@/libs/tools'
51
+import './index.less'
52
+export default {
53
+  name: 'BDrawer',
54
+  components: {
55
+    RenderDom,
56
+    DragDrawerTrigger
57
+  },
58
+  mixins: [Mixin],
59
+  props: {
60
+    value: {
61
+      type: Boolean,
62
+      default: false
63
+    },
64
+    width: {
65
+      type: [String, Number],
66
+      default: 256
67
+    },
68
+    // 是否可拖动修改宽度
69
+    draggable: {
70
+      type: Boolean,
71
+      default: false
72
+    },
73
+    // 最小拖动宽度
74
+    minWidth: {
75
+      type: [String, Number],
76
+      default: 256
77
+    }
78
+  },
79
+  data () {
80
+    return {
81
+      canMove: false,
82
+      wrapperWidth: 0,
83
+      wrapperLeft: 0
84
+    }
85
+  },
86
+  computed: {
87
+    outerClasses () {
88
+      const classesArray = [
89
+        `${this.prefix}-wrapper`,
90
+        this.canMove ? 'no-select pointer-events-none' : ''
91
+      ]
92
+      return classesArray.join(' ')
93
+    },
94
+    placement () {
95
+      return this.$attrs.placement
96
+    },
97
+    innerWidth () {
98
+      const width = this.width
99
+      return width <= 100 ? (this.wrapperWidth * width) / 100 : width
100
+    },
101
+    triggerStyle () {
102
+      return {
103
+        [this.placement]: `${this.innerWidth}px`,
104
+        position: this.$attrs.inner ? 'absolute' : 'fixed'
105
+      }
106
+    }
107
+  },
108
+  methods: {
109
+    handleInput (status) {
110
+      this.$emit('input', status)
111
+    },
112
+    handleTriggerMousedown (event) {
113
+      this.canMove = true
114
+      this.$emit('on-resize-start')
115
+      // 防止鼠标选中抽屉中文字,造成拖动trigger触发浏览器原生拖动行为
116
+      window.getSelection().removeAllRanges()
117
+    },
118
+    handleMousemove (event) {
119
+      if (!this.canMove) return
120
+      // 更新容器宽度和距离左侧页面距离,如果是window则距左侧距离为0
121
+      this.setWrapperWidth()
122
+      const left = event.pageX - this.wrapperLeft
123
+      // 如果抽屉方向为右边,宽度计算需用容器宽度减去left
124
+      let width = this.placement === 'right' ? this.wrapperWidth - left : left
125
+      // 限定做小宽度
126
+      width = Math.max(width, parseFloat(this.minWidth))
127
+      event.atMin = width === parseFloat(this.minWidth)
128
+      // 如果当前width不大于100,视为百分比
129
+      if (width <= 100) width = (width / this.wrapperWidth) * 100
130
+      this.$emit('update:width', parseInt(width))
131
+      this.$emit('on-resize', event)
132
+    },
133
+    handleMouseup (event) {
134
+      this.canMove = false
135
+      this.$emit('on-resize-end')
136
+    },
137
+    setWrapperWidth () {
138
+      const {
139
+        width,
140
+        left
141
+      } = this.$refs.drawerWrapper.$el.getBoundingClientRect()
142
+      this.wrapperWidth = width
143
+      this.wrapperLeft = left
144
+    }
145
+  },
146
+  mounted () {
147
+    on(document, 'mousemove', this.handleMousemove)
148
+    on(document, 'mouseup', this.handleMouseup)
149
+    this.setWrapperWidth()
150
+  },
151
+  beforeDestroy () {
152
+    off(document, 'mousemove', this.handleMousemove)
153
+    off(document, 'mouseup', this.handleMouseup)
154
+  }
155
+}
156
+</script>

+ 2
- 0
src/components/drag-drawer/index.js View File

@@ -0,0 +1,2 @@
1
+import DragDrawer from './drag-drawer.vue'
2
+export default DragDrawer

+ 70
- 0
src/components/drag-drawer/index.less View File

@@ -0,0 +1,70 @@
1
+@prefix: ~"drag-drawer";
2
+@drag-drawer-trigger-height: 100px;
3
+@drag-drawer-trigger-width: 8px;
4
+
5
+.@{prefix}-wrapper{
6
+  &.no-select{
7
+    user-select: none;
8
+  }
9
+  &.pointer-events-none{
10
+    pointer-events: none;
11
+    & .@{prefix}-trigger-wrapper{
12
+      pointer-events: all;
13
+    }
14
+  }
15
+  .ivu-drawer{
16
+    &-header{
17
+      overflow: hidden !important;
18
+      box-sizing: border-box;
19
+    }
20
+    &-body{
21
+      padding: 0;
22
+      overflow: visible;
23
+      position: static;
24
+      display: flex;
25
+      flex-direction: column;
26
+    }
27
+  }
28
+  .@{prefix}-body-wrapper{
29
+    width: 100%;
30
+    height: 100%;
31
+    padding: 16px;
32
+    overflow: auto;
33
+  }
34
+  .@{prefix}-trigger-wrapper{
35
+    top: 0;
36
+    height: 100%;
37
+    width: 0;
38
+    .@{prefix}-move-trigger{
39
+      position: absolute;
40
+      top: 50%;
41
+      height: @drag-drawer-trigger-height;
42
+      width: @drag-drawer-trigger-width;
43
+      background: rgb(243, 243, 243);
44
+      transform: translate(-50%, -50%);
45
+      border-radius: ~"4px / 6px";
46
+      box-shadow: 0 0 1px 1px rgba(0, 0, 0, .2);
47
+      line-height: @drag-drawer-trigger-height;
48
+      cursor: col-resize;
49
+      &-point{
50
+        display: inline-block;
51
+        width: 50%;
52
+        transform: translateX(50%);
53
+        i{
54
+          display: block;
55
+          border-bottom: 1px solid rgb(192, 192, 192);
56
+          padding-bottom: 2px;
57
+        }
58
+      }
59
+    }
60
+  }
61
+  .@{prefix}-footer{
62
+    flex-grow: 1;
63
+    width: 100%;
64
+    bottom: 0;
65
+    left: 0;
66
+    border-top: 1px solid #e8e8e8;
67
+    padding: 10px 16px;
68
+    background: #fff;
69
+  }
70
+}

+ 7
- 0
src/components/drag-drawer/mixin.js View File

@@ -0,0 +1,7 @@
1
+export default {
2
+  data () {
3
+    return {
4
+      prefix: 'drag-drawer'
5
+    }
6
+  }
7
+}

+ 92
- 0
src/components/drag-list/drag-list.vue View File

@@ -0,0 +1,92 @@
1
+<template>
2
+  <div class="drag-list-wrapper">
3
+    <div class="drag-list-con con1">
4
+      <slot name="left-title"></slot>
5
+      <draggable class="drop-box1" :class="dropConClass.left" :options="options" :value="list1" @input="handleListChange($event, 'left')" @end="handleEnd($event, 'left')">
6
+        <div class="drag-list-item" v-for="(itemLeft, index) in list1" :key="`drag_li1_${index}`">
7
+          <slot name="left" :itemLeft="itemLeft">{{ itemLeft }}</slot>
8
+        </div>
9
+      </draggable>
10
+    </div>
11
+    <div class="drag-list-con con2">
12
+      <slot name="right-title"></slot>
13
+      <draggable class="drop-box2" :class="dropConClass.right" :options="options" :value="list2" @input="handleListChange($event, 'right')" @end="handleEnd($event, 'right')">
14
+        <div class="drag-list-item" v-for="(itemRight, index) in list2" :key="`drag_li2_${index}`">
15
+          <slot name="right" :itemRight="itemRight">{{ itemRight }}</slot>
16
+        </div>
17
+      </draggable>
18
+    </div>
19
+  </div>
20
+</template>
21
+<script>
22
+import draggable from 'vuedraggable'
23
+export default {
24
+  name: 'DragList',
25
+  components: {
26
+    draggable
27
+  },
28
+  props: {
29
+    list1: {
30
+      type: Array,
31
+      required: true
32
+    },
33
+    list2: {
34
+      type: Array,
35
+      default: () => []
36
+    },
37
+    dropConClass: {
38
+      type: Object,
39
+      default: () => ({})
40
+    }
41
+  },
42
+  data () {
43
+    return {
44
+      options: { group: 'drag_list' }
45
+    }
46
+  },
47
+  methods: {
48
+    handleListChange (value, type) {
49
+      if (type === 'left') this.$emit('update:list1', value)
50
+      else this.$emit('update:list2', value)
51
+    },
52
+    handleEnd (event, type) {
53
+      const srcClassName = (event.srcElement || event.target).classList[0]
54
+      const targetClassName = event.to.classList[0]
55
+      let src = ''
56
+      let target = ''
57
+      if (srcClassName === targetClassName) {
58
+        if (type === 'left') {
59
+          src = 'left'
60
+          target = 'left'
61
+        } else {
62
+          src = 'right'
63
+          target = 'right'
64
+        }
65
+      } else {
66
+        if (type === 'left') {
67
+          src = 'left'
68
+          target = 'right'
69
+        } else {
70
+          src = 'right'
71
+          target = 'left'
72
+        }
73
+      }
74
+      this.$emit('on-change', {
75
+        src: src,
76
+        target: target,
77
+        oldIndex: event.oldIndex,
78
+        newIndex: event.newIndex
79
+      })
80
+    }
81
+  }
82
+}
83
+</script>
84
+<style lang="less">
85
+.drag-list-wrapper{
86
+  height: 100%;
87
+  .drag-list-con{
88
+    width: 50%;
89
+    float: left;
90
+  }
91
+}
92
+</style>

+ 2
- 0
src/components/drag-list/index.js View File

@@ -0,0 +1,2 @@
1
+import DragList from './drag-list.vue'
2
+export default DragList

+ 75
- 0
src/components/editor/editor.vue View File

@@ -0,0 +1,75 @@
1
+<template>
2
+  <div class="editor-wrapper">
3
+    <div :id="editorId"></div>
4
+  </div>
5
+</template>
6
+
7
+<script>
8
+import Editor from 'wangeditor'
9
+import 'wangeditor/release/wangEditor.min.css'
10
+import { oneOf } from '@/libs/tools'
11
+export default {
12
+  name: 'Editor',
13
+  props: {
14
+    value: {
15
+      type: String,
16
+      default: ''
17
+    },
18
+    /**
19
+     * 绑定的值的类型, enum: ['html', 'text']
20
+     */
21
+    valueType: {
22
+      type: String,
23
+      default: 'html',
24
+      validator: (val) => {
25
+        return oneOf(val, ['html', 'text'])
26
+      }
27
+    },
28
+    /**
29
+     * @description 设置change事件触发时间间隔
30
+     */
31
+    changeInterval: {
32
+      type: Number,
33
+      default: 200
34
+    },
35
+    /**
36
+     * @description 是否开启本地存储
37
+     */
38
+    cache: {
39
+      type: Boolean,
40
+      default: true
41
+    }
42
+  },
43
+  computed: {
44
+    editorId () {
45
+      return `editor${this._uid}`
46
+    }
47
+  },
48
+  methods: {
49
+    setHtml (val) {
50
+      this.editor.txt.html(val)
51
+    }
52
+  },
53
+  mounted () {
54
+    this.editor = new Editor(`#${this.editorId}`)
55
+    this.editor.customConfig.onchange = (html) => {
56
+      let text = this.editor.txt.text()
57
+      if (this.cache) localStorage.editorCache = html
58
+      this.$emit('input', this.valueType === 'html' ? html : text)
59
+      this.$emit('on-change', html, text)
60
+    }
61
+    this.editor.customConfig.onchangeTimeout = this.changeInterval
62
+    // create这个方法一定要在所有配置项之后调用
63
+    this.editor.create()
64
+    // 如果本地有存储加载本地存储内容
65
+    let html = this.value || localStorage.editorCache
66
+    if (html) this.editor.txt.html(html)
67
+  }
68
+}
69
+</script>
70
+
71
+<style lang="less">
72
+.editor-wrapper *{
73
+  z-index: 100 !important;
74
+}
75
+</style>

+ 2
- 0
src/components/editor/index.js View File

@@ -0,0 +1,2 @@
1
+import Editor from './editor.vue'
2
+export default Editor

+ 35
- 0
src/components/icons/icons.vue View File

@@ -0,0 +1,35 @@
1
+<template>
2
+  <i :class="`iconfont icon-${type}`" :style="styles"></i>
3
+</template>
4
+
5
+<script>
6
+export default {
7
+  name: 'Icons',
8
+  props: {
9
+    type: {
10
+      type: String,
11
+      required: true
12
+    },
13
+    color: {
14
+      type: String,
15
+      default: '#5c6b77'
16
+    },
17
+    size: {
18
+      type: Number,
19
+      default: 16
20
+    }
21
+  },
22
+  computed: {
23
+    styles () {
24
+      return {
25
+        fontSize: `${this.size}px`,
26
+        color: this.color
27
+      }
28
+    }
29
+  }
30
+}
31
+</script>
32
+
33
+<style>
34
+
35
+</style>

+ 2
- 0
src/components/icons/index.js View File

@@ -0,0 +1,2 @@
1
+import Icons from './icons.vue'
2
+export default Icons

+ 2
- 0
src/components/info-card/index.js View File

@@ -0,0 +1,2 @@
1
+import InforCard from './infor-card.vue'
2
+export default InforCard

+ 94
- 0
src/components/info-card/infor-card.vue View File

@@ -0,0 +1,94 @@
1
+<template>
2
+  <Card :shadow="shadow" class="info-card-wrapper" :padding="0">
3
+    <div class="content-con">
4
+      <div class="left-area" :style="{background: color, width: leftWidth}">
5
+        <common-icon class="icon" :type="icon" :size="iconSize" color="#fff"/>
6
+      </div>
7
+      <div class="right-area" :style="{width: rightWidth}">
8
+        <div>
9
+          <slot></slot>
10
+        </div>
11
+      </div>
12
+    </div>
13
+  </Card>
14
+</template>
15
+
16
+<script>
17
+import CommonIcon from '_c/common-icon'
18
+export default {
19
+  name: 'InforCard',
20
+  components: {
21
+    CommonIcon
22
+  },
23
+  props: {
24
+    left: {
25
+      type: Number,
26
+      default: 36
27
+    },
28
+    color: {
29
+      type: String,
30
+      default: '#2d8cf0'
31
+    },
32
+    icon: {
33
+      type: String,
34
+      default: ''
35
+    },
36
+    iconSize: {
37
+      type: Number,
38
+      default: 20
39
+    },
40
+    shadow: {
41
+      type: Boolean,
42
+      default: false
43
+    }
44
+  },
45
+  computed: {
46
+    leftWidth () {
47
+      return `${this.left}%`
48
+    },
49
+    rightWidth () {
50
+      return `${100 - this.left}%`
51
+    }
52
+  }
53
+}
54
+</script>
55
+
56
+<style lang="less">
57
+.common{
58
+  float: left;
59
+  height: 100%;
60
+  display: table;
61
+  text-align: center;
62
+}
63
+.size{
64
+  width: 100%;
65
+  height: 100%;
66
+}
67
+.middle-center{
68
+  display: table-cell;
69
+  vertical-align: middle;
70
+}
71
+.info-card-wrapper{
72
+  .size;
73
+  overflow: hidden;
74
+  .ivu-card-body{
75
+    .size;
76
+  }
77
+  .content-con{
78
+    .size;
79
+    position: relative;
80
+    .left-area{
81
+      .common;
82
+      & > .icon{
83
+        .middle-center;
84
+      }
85
+    }
86
+    .right-area{
87
+      .common;
88
+      & > div{
89
+        .middle-center;
90
+      }
91
+    }
92
+  }
93
+}
94
+</style>

+ 2
- 0
src/components/login-form/index.js View File

@@ -0,0 +1,2 @@
1
+import LoginForm from './login-form.vue'
2
+export default LoginForm

+ 99
- 0
src/components/login-form/login-form.vue View File

@@ -0,0 +1,99 @@
1
+<template>
2
+<div>
3
+  <Form ref="loginForm" :model="form" :rules="rules" @keydown.enter.native="handleSubmit">
4
+    <FormItem prop="userName">
5
+      <Input v-model="form.userName" placeholder="用户名">
6
+        <span slot="prepend">
7
+          <Icon :size="16" type="ios-person"></Icon>
8
+        </span>
9
+      </Input>
10
+    </FormItem>
11
+    <FormItem prop="password">
12
+      <Input type="password" v-model="form.password" placeholder="密码">
13
+        <span slot="prepend">
14
+          <Icon :size="14" type="md-lock"></Icon>
15
+        </span>
16
+      </Input>
17
+    </FormItem>
18
+    <FormItem prop="password">
19
+      <Checkbox :checked.sync="single">记住密码</Checkbox>
20
+    </FormItem>
21
+    <FormItem>
22
+      <Button @click="handleSubmit" type="primary" long>登录</Button>
23
+    </FormItem>
24
+  </Form>
25
+   <div class="bottom">
26
+            <a>注册</a>
27
+            <a>忘记密码</a>
28
+    </div>
29
+  </div>
30
+</template>
31
+<script>
32
+export default {
33
+  name: 'LoginForm',
34
+  props: {
35
+    userNameRules: {
36
+      type: Array,
37
+      default: () => {
38
+        return [
39
+          { required: true, message: '账号不能为空', trigger: 'blur' }
40
+        ]
41
+      }
42
+    },
43
+    passwordRules: {
44
+      type: Array,
45
+      default: () => {
46
+        return [
47
+          { required: true, message: '密码不能为空', trigger: 'blur' }
48
+        ]
49
+      }
50
+    }
51
+  },
52
+  data () {
53
+    return {
54
+      single: false,
55
+      form: {
56
+        userName: 'super_admin',
57
+        password: ''
58
+      }
59
+    }
60
+  },
61
+  computed: {
62
+    rules () {
63
+      return {
64
+        userName: this.userNameRules,
65
+        password: this.passwordRules
66
+      }
67
+    }
68
+  },
69
+  methods: {
70
+    handleSubmit () {
71
+      this.$refs.loginForm.validate((valid) => {
72
+        if (valid) {
73
+          this.$emit('on-success-valid', {
74
+            userName: this.form.userName,
75
+            password: this.form.password
76
+          })
77
+        }
78
+      })
79
+    }
80
+  }
81
+}
82
+</script>
83
+<style lang='less' scoped>
84
+// .ivu-input{
85
+//   height: 40px;
86
+//   width: 300px;
87
+// }
88
+.ivu-form-item-content{
89
+  width: 270px;
90
+  height: 35px;
91
+}
92
+.ivu-form-item{
93
+  margin-bottom: 20px;
94
+}
95
+.bottom {
96
+          display: flex;
97
+          justify-content: space-between;
98
+        }
99
+</style>>

+ 2
- 0
src/components/main/components/a-back-top/index.js View File

@@ -0,0 +1,2 @@
1
+import ABackTop from './index.vue'
2
+export default ABackTop

+ 90
- 0
src/components/main/components/a-back-top/index.vue View File

@@ -0,0 +1,90 @@
1
+<template>
2
+    <div :class="classes" :style="styles" @click="back">
3
+        <slot>
4
+            <div :class="innerClasses">
5
+                <i class="ivu-icon ivu-icon-ios-arrow-up"></i>
6
+            </div>
7
+        </slot>
8
+    </div>
9
+</template>
10
+<script>
11
+import { scrollTop } from '@/libs/util'
12
+import { on, off } from '@/libs/tools'
13
+const prefixCls = 'ivu-back-top'
14
+
15
+export default {
16
+  name: 'ABackTop',
17
+  props: {
18
+    height: {
19
+      type: Number,
20
+      default: 400
21
+    },
22
+    bottom: {
23
+      type: Number,
24
+      default: 30
25
+    },
26
+    right: {
27
+      type: Number,
28
+      default: 30
29
+    },
30
+    duration: {
31
+      type: Number,
32
+      default: 1000
33
+    },
34
+    container: {
35
+      type: null,
36
+      default: window
37
+    }
38
+  },
39
+  data () {
40
+    return {
41
+      backTop: false
42
+    }
43
+  },
44
+  mounted () {
45
+    // window.addEventListener('scroll', this.handleScroll, false)
46
+    // window.addEventListener('resize', this.handleScroll, false)
47
+    on(this.containerEle, 'scroll', this.handleScroll)
48
+    on(this.containerEle, 'resize', this.handleScroll)
49
+  },
50
+  beforeDestroy () {
51
+    // window.removeEventListener('scroll', this.handleScroll, false)
52
+    // window.removeEventListener('resize', this.handleScroll, false)
53
+    off(this.containerEle, 'scroll', this.handleScroll)
54
+    off(this.containerEle, 'resize', this.handleScroll)
55
+  },
56
+  computed: {
57
+    classes () {
58
+      return [
59
+        `${prefixCls}`,
60
+        {
61
+          [`${prefixCls}-show`]: this.backTop
62
+        }
63
+      ]
64
+    },
65
+    styles () {
66
+      return {
67
+        bottom: `${this.bottom}px`,
68
+        right: `${this.right}px`
69
+      }
70
+    },
71
+    innerClasses () {
72
+      return `${prefixCls}-inner`
73
+    },
74
+    containerEle () {
75
+      return this.container === window ? window : document.querySelector(this.container)
76
+    }
77
+  },
78
+  methods: {
79
+    handleScroll () {
80
+      this.backTop = this.containerEle.scrollTop >= this.height
81
+    },
82
+    back () {
83
+      let target = typeof this.container === 'string' ? this.containerEle : (document.documentElement || document.body)
84
+      const sTop = target.scrollTop
85
+      scrollTop(this.containerEle, sTop, 0, this.duration)
86
+      this.$emit('on-click')
87
+    }
88
+  }
89
+}
90
+</script>

+ 49
- 0
src/components/main/components/error-store/error-store.vue View File

@@ -0,0 +1,49 @@
1
+<template>
2
+  <div class="error-store">
3
+    <Badge dot :count="countComputed">
4
+      <Button type="text" @click="openErrorLoggerPage">
5
+        <Icon :size="20" type="ios-bug"/>
6
+      </Button>
7
+    </Badge>
8
+  </div>
9
+</template>
10
+
11
+<script>
12
+export default {
13
+  name: 'ErrorStore',
14
+  props: {
15
+    count: {
16
+      type: Number,
17
+      default: 0
18
+    },
19
+    hasRead: {
20
+      type: Boolean,
21
+      default: false
22
+    }
23
+  },
24
+  computed: {
25
+    countComputed () {
26
+      return this.hasRead ? 0 : this.count
27
+    }
28
+  },
29
+  methods: {
30
+    openErrorLoggerPage () {
31
+      this.$router.push({
32
+        name: 'error_logger_page'
33
+      })
34
+    }
35
+  }
36
+}
37
+</script>
38
+
39
+<style lang="less">
40
+.error-store{
41
+  margin-right: 12px;
42
+  .ivu-badge-dot{
43
+    top: 20px;
44
+  }
45
+  .ivu-btn.ivu-btn-text{
46
+    padding: 5px 1px 6px;
47
+  }
48
+}
49
+</style>

+ 2
- 0
src/components/main/components/error-store/index.js View File

@@ -0,0 +1,2 @@
1
+import ErrorStore from './error-store.vue'
2
+export default ErrorStore

+ 84
- 0
src/components/main/components/fullscreen/fullscreen.vue View File

@@ -0,0 +1,84 @@
1
+<template>
2
+  <div v-if="showFullScreenBtn" class="full-screen-btn-con">
3
+    <Tooltip :content="value ? '退出全屏' : '全屏'" placement="bottom">
4
+      <Icon @click.native="handleChange" :type="value ? 'md-contract' : 'md-expand'" :size="23"></Icon>
5
+    </Tooltip>
6
+  </div>
7
+</template>
8
+
9
+<script>
10
+export default {
11
+  name: 'Fullscreen',
12
+  computed: {
13
+    showFullScreenBtn () {
14
+      return window.navigator.userAgent.indexOf('MSIE') < 0
15
+    }
16
+  },
17
+  props: {
18
+    value: {
19
+      type: Boolean,
20
+      default: false
21
+    }
22
+  },
23
+  methods: {
24
+    handleFullscreen () {
25
+      let main = document.body
26
+      if (this.value) {
27
+        if (document.exitFullscreen) {
28
+          document.exitFullscreen()
29
+        } else if (document.mozCancelFullScreen) {
30
+          document.mozCancelFullScreen()
31
+        } else if (document.webkitCancelFullScreen) {
32
+          document.webkitCancelFullScreen()
33
+        } else if (document.msExitFullscreen) {
34
+          document.msExitFullscreen()
35
+        }
36
+      } else {
37
+        if (main.requestFullscreen) {
38
+          main.requestFullscreen()
39
+        } else if (main.mozRequestFullScreen) {
40
+          main.mozRequestFullScreen()
41
+        } else if (main.webkitRequestFullScreen) {
42
+          main.webkitRequestFullScreen()
43
+        } else if (main.msRequestFullscreen) {
44
+          main.msRequestFullscreen()
45
+        }
46
+      }
47
+    },
48
+    handleChange () {
49
+      this.handleFullscreen()
50
+    }
51
+  },
52
+  mounted () {
53
+    let isFullscreen = document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen
54
+    isFullscreen = !!isFullscreen
55
+    document.addEventListener('fullscreenchange', () => {
56
+      this.$emit('input', !this.value)
57
+      this.$emit('on-change', !this.value)
58
+    })
59
+    document.addEventListener('mozfullscreenchange', () => {
60
+      this.$emit('input', !this.value)
61
+      this.$emit('on-change', !this.value)
62
+    })
63
+    document.addEventListener('webkitfullscreenchange', () => {
64
+      this.$emit('input', !this.value)
65
+      this.$emit('on-change', !this.value)
66
+    })
67
+    document.addEventListener('msfullscreenchange', () => {
68
+      this.$emit('input', !this.value)
69
+      this.$emit('on-change', !this.value)
70
+    })
71
+    this.$emit('input', isFullscreen)
72
+  }
73
+}
74
+</script>
75
+
76
+<style lang="less">
77
+.full-screen-btn-con .ivu-tooltip-rel{
78
+  height: 64px;
79
+  line-height: 56px;
80
+  i{
81
+    cursor: pointer;
82
+  }
83
+}
84
+</style>

+ 2
- 0
src/components/main/components/fullscreen/index.js View File

@@ -0,0 +1,2 @@
1
+import Fullscreen from './fullscreen.vue'
2
+export default Fullscreen

+ 4
- 0
src/components/main/components/header-bar/custom-bread-crumb/custom-bread-crumb.less View File

@@ -0,0 +1,4 @@
1
+.custom-bread-crumb{
2
+  display: inline-block;
3
+  vertical-align: top;
4
+}

+ 47
- 0
src/components/main/components/header-bar/custom-bread-crumb/custom-bread-crumb.vue View File

@@ -0,0 +1,47 @@
1
+<template>
2
+  <div class="custom-bread-crumb">
3
+    <Breadcrumb :style="{fontSize: `${fontSize}px`}" separator='>'>
4
+     <span style="margin-right: 4px;">青鸽管家</span><span style="margin-right: 4px;">></span>
5
+      <BreadcrumbItem v-for="item in list" :to="item.to" :key="`bread-crumb-${item.name}`">
6
+       <common-icon style="margin-right: 4px;"  type='' />
7
+        {{ showTitle(item) }}
8
+      </BreadcrumbItem>
9
+    </Breadcrumb>
10
+  </div>
11
+</template>
12
+<script>
13
+import { showTitle } from '@/libs/util'
14
+import CommonIcon from '_c/common-icon'
15
+import './custom-bread-crumb.less'
16
+export default {
17
+  name: 'customBreadCrumb',
18
+  components: {
19
+    CommonIcon
20
+  },
21
+  props: {
22
+    list: {
23
+      type: Array,
24
+      default: () => []
25
+    },
26
+    fontSize: {
27
+      type: Number,
28
+      default: 14
29
+    },
30
+    showIcon: {
31
+      type: Boolean,
32
+      default: false
33
+    }
34
+  },
35
+  methods: {
36
+    showTitle (item) {
37
+      return showTitle(item, this)
38
+    },
39
+    isCustomIcon (iconName) {
40
+      return iconName.indexOf('_') === 0
41
+    },
42
+    getCustomIconName (iconName) {
43
+      return iconName.slice(1)
44
+    }
45
+  }
46
+}
47
+</script>

+ 2
- 0
src/components/main/components/header-bar/custom-bread-crumb/index.js View File

@@ -0,0 +1,2 @@
1
+import customBreadCrumb from './custom-bread-crumb.vue'
2
+export default customBreadCrumb

+ 18
- 0
src/components/main/components/header-bar/header-bar.less View File

@@ -0,0 +1,18 @@
1
+.header-bar{
2
+  width: 100%;
3
+  height: 100%;
4
+  position: relative;
5
+  .ivu-breadcrumb{
6
+    height: 50px;
7
+    line-height: 50px;
8
+  }
9
+  .custom-content-con{
10
+    float: right;
11
+    height: 50px;
12
+    padding-right: 20px;
13
+    line-height: 50px;
14
+    & > *{
15
+      float: right;
16
+    }
17
+  }
18
+}

+ 34
- 0
src/components/main/components/header-bar/header-bar.vue View File

@@ -0,0 +1,34 @@
1
+<template>
2
+  <div class="header-bar">
3
+    <!-- <sider-trigger :collapsed="collapsed" icon="md-menu" @on-change="handleCollpasedChange"></sider-trigger> -->
4
+    <custom-bread-crumb show-icon  :list="breadCrumbList"></custom-bread-crumb>
5
+    <div class="custom-content-con">
6
+      <slot></slot>
7
+    </div>
8
+  </div>
9
+</template>
10
+<script>
11
+import siderTrigger from './sider-trigger'
12
+import customBreadCrumb from './custom-bread-crumb'
13
+import './header-bar.less'
14
+export default {
15
+  name: 'HeaderBar',
16
+  components: {
17
+    siderTrigger,
18
+    customBreadCrumb
19
+  },
20
+  props: {
21
+    collapsed: Boolean
22
+  },
23
+  computed: {
24
+    breadCrumbList () {
25
+      return this.$store.state.app.breadCrumbList
26
+    }
27
+  },
28
+  methods: {
29
+    handleCollpasedChange (state) {
30
+      this.$emit('on-coll-change', state)
31
+    }
32
+  }
33
+}
34
+</script>

+ 2
- 0
src/components/main/components/header-bar/index.js View File

@@ -0,0 +1,2 @@
1
+import HeaderBar from './header-bar'
2
+export default HeaderBar

+ 2
- 0
src/components/main/components/header-bar/sider-trigger/index.js View File

@@ -0,0 +1,2 @@
1
+import siderTrigger from './sider-trigger.vue'
2
+export default siderTrigger

+ 21
- 0
src/components/main/components/header-bar/sider-trigger/sider-trigger.less View File

@@ -0,0 +1,21 @@
1
+.trans{
2
+  transition: transform .2s ease;
3
+}
4
+@size: 40px;
5
+.sider-trigger-a{
6
+  padding: 6px;
7
+  width: @size;
8
+  height: @size;
9
+  display: inline-block;
10
+  text-align: center;
11
+  color: #5c6b77;
12
+  margin-top: 12px;
13
+  i{
14
+    .trans;
15
+    vertical-align: top;
16
+  }
17
+  &.collapsed i{
18
+    transform: rotateZ(90deg);
19
+    .trans;
20
+  }
21
+}

+ 27
- 0
src/components/main/components/header-bar/sider-trigger/sider-trigger.vue View File

@@ -0,0 +1,27 @@
1
+<template>
2
+  <a @click="handleChange" type="text" :class="['sider-trigger-a', collapsed ? 'collapsed' : '']"><Icon :type="icon" :size="size" /></a>
3
+</template>
4
+<script>
5
+export default {
6
+  name: 'siderTrigger',
7
+  props: {
8
+    collapsed: Boolean,
9
+    icon: {
10
+      type: String,
11
+      default: 'navicon-round'
12
+    },
13
+    size: {
14
+      type: Number,
15
+      default: 26
16
+    }
17
+  },
18
+  methods: {
19
+    handleChange () {
20
+      this.$emit('on-change', !this.collapsed)
21
+    }
22
+  }
23
+}
24
+</script>
25
+<style lang="less">
26
+@import './sider-trigger.less';
27
+</style>

+ 2
- 0
src/components/main/components/language/index.js View File

@@ -0,0 +1,2 @@
1
+import Language from './language.vue'
2
+export default Language

+ 51
- 0
src/components/main/components/language/language.vue View File

@@ -0,0 +1,51 @@
1
+<template>
2
+  <div>
3
+    <Dropdown trigger="click" @on-click="selectLang">
4
+      <a href="javascript:void(0)">
5
+        {{ title }}
6
+        <Icon :size="18" type="md-arrow-dropdown" />
7
+      </a>
8
+      <DropdownMenu slot="list">
9
+        <DropdownItem v-for="(value, key) in localList" :name="key" :key="`lang-${key}`">{{ value }}</DropdownItem>
10
+      </DropdownMenu>
11
+    </Dropdown>
12
+  </div>
13
+</template>
14
+
15
+<script>
16
+export default {
17
+  name: 'Language',
18
+  props: {
19
+    lang: String
20
+  },
21
+  data () {
22
+    return {
23
+      langList: {
24
+        'zh-CN': '语言',
25
+        'zh-TW': '語言',
26
+        'en-US': 'Lang'
27
+      },
28
+      localList: {
29
+        'zh-CN': '中文简体',
30
+        'zh-TW': '中文繁体',
31
+        'en-US': 'English'
32
+      }
33
+    }
34
+  },
35
+  watch: {
36
+    lang (lang) {
37
+      this.$i18n.locale = lang
38
+    }
39
+  },
40
+  computed: {
41
+    title () {
42
+      return this.langList[this.lang]
43
+    }
44
+  },
45
+  methods: {
46
+    selectLang (name) {
47
+      this.$emit('on-lang-change', name)
48
+    }
49
+  }
50
+}
51
+</script>

+ 119
- 0
src/components/main/components/navbar/index.vue View File

@@ -0,0 +1,119 @@
1
+<template>
2
+  <div class="navbar">
3
+    <div class="navbarInner">
4
+      <!-- 导航左边 -->
5
+      <div class="left">
6
+        <img src="../../../../assets/img/logo.png" alt="">
7
+      </div>
8
+      <!-- 导航中间 -->
9
+      <div class="center">
10
+        显现英语
11
+      </div>
12
+      <!-- 导航右边 -->
13
+      <div class="right">
14
+        <!-- <img src="../../../../assets/img/user.png" alt=""> -->
15
+         <Badge >
16
+        <Avatar :src="userAvatar"/>
17
+      </Badge>
18
+        <div class="drop">
19
+           <Dropdown  @on-click="handleClick">
20
+            <a href="javascript:void(0)">
21
+                SHAW-小岩
22
+            </a>
23
+            <Dropdown-menu slot="list">
24
+                <Dropdown-item>修改密码</Dropdown-item>
25
+                <Dropdown-item>修改信息</Dropdown-item>
26
+                <Dropdown-item>机构信息</Dropdown-item>
27
+                <Dropdown-item name="logout">退出登录</Dropdown-item>
28
+
29
+                </Dropdown-menu>
30
+          </Dropdown>
31
+        </div>
32
+        <a href="#">退出</a>
33
+      </div>
34
+    </div>
35
+  </div>
36
+</template>
37
+<script>
38
+import { mapActions } from 'vuex'
39
+
40
+export default {
41
+  props: {
42
+    userAvatar: {
43
+      type: String,
44
+      default: ''
45
+    }
46
+  },
47
+  methods: {
48
+    ...mapActions([
49
+      'handleLogOut'
50
+    ]),
51
+    logout () {
52
+      this.handleLogOut().then(() => {
53
+        this.$router.push({
54
+          name: 'login'
55
+        })
56
+      })
57
+    },
58
+    handleClick (name) {
59
+      switch (name) {
60
+        case 'logout': this.logout()
61
+          break
62
+        case 'message': this.message()
63
+          break
64
+      }
65
+    }
66
+  }
67
+}
68
+</script>>
69
+<style lang="less" scoped>
70
+.navbar {
71
+  background: #3d8efe;
72
+  width: 100%;
73
+  height: 50px;
74
+  // padding: 0 20px;
75
+  .navbarInner{
76
+    width: 100%;
77
+    display: flex;
78
+     .left {
79
+    flex:1;
80
+    img{
81
+      width: 200px;
82
+    height: 50px;
83
+    }
84
+  }
85
+  .center{
86
+    flex:5;
87
+    color: white;
88
+    font-size: 16px;
89
+    line-height: 50px;
90
+    margin-left: 20px;
91
+  }
92
+  .right{
93
+    flex:1;
94
+    display: flex;
95
+    // justify-content:center;
96
+    align-items: center;
97
+    img{
98
+      width: 20px;
99
+      height: 20px;
100
+      border-radius: 50%;
101
+    }
102
+    .drop{
103
+      margin-left:  5px;
104
+      padding-right: 10px;
105
+      border-right: 2px solid white;
106
+      .ivu-dropdown{
107
+        a{
108
+          color: white;
109
+        }
110
+      }
111
+    }
112
+    a{
113
+      color: white;
114
+      margin-left: 10px;
115
+    }
116
+  }
117
+  }
118
+}
119
+</style>

+ 51
- 0
src/components/main/components/side-menu/collapsed-menu.vue View File

@@ -0,0 +1,51 @@
1
+<template>
2
+  <Dropdown ref="dropdown" @on-click="handleClick" :class="hideTitle ? '' : 'collased-menu-dropdown'" :transfer="hideTitle" :placement="placement">
3
+    <a class="drop-menu-a" type="text" @mouseover="handleMousemove($event, children)" :style="{textAlign: !hideTitle ? 'left' : ''}"><common-icon :size="rootIconSize" :color="textColor" :type="parentItem.icon"/><span class="menu-title" v-if="!hideTitle">{{ showTitle(parentItem) }}</span><Icon style="float: right;" v-if="!hideTitle" type="ios-arrow-forward" :size="16"/></a>
4
+    <DropdownMenu ref="dropdown" slot="list">
5
+      <template v-for="child in children">
6
+        <collapsed-menu v-if="showChildren(child)" :icon-size="iconSize" :parent-item="child" :key="`drop-${child.name}`"></collapsed-menu>
7
+        <DropdownItem v-else :key="`drop-${child.name}`" :name="child.name"><common-icon :size="iconSize" :type="child.icon"/><span class="menu-title">{{ showTitle(child) }}</span></DropdownItem>
8
+      </template>
9
+    </DropdownMenu>
10
+  </Dropdown>
11
+</template>
12
+<script>
13
+import mixin from './mixin'
14
+import itemMixin from './item-mixin'
15
+import { findNodeUpperByClasses } from '@/libs/util'
16
+
17
+export default {
18
+  name: 'CollapsedMenu',
19
+  mixins: [ mixin, itemMixin ],
20
+  props: {
21
+    hideTitle: {
22
+      type: Boolean,
23
+      default: false
24
+    },
25
+    rootIconSize: {
26
+      type: Number,
27
+      default: 16
28
+    }
29
+  },
30
+  data () {
31
+    return {
32
+      placement: 'right-end'
33
+    }
34
+  },
35
+  methods: {
36
+    handleClick (name) {
37
+      this.$emit('on-click', name)
38
+    },
39
+    handleMousemove (event, children) {
40
+      const { pageY } = event
41
+      const height = children.length * 38
42
+      const isOverflow = pageY + height < window.innerHeight
43
+      this.placement = isOverflow ? 'right-start' : 'right-end'
44
+    }
45
+  },
46
+  mounted () {
47
+    let dropdown = findNodeUpperByClasses(this.$refs.dropdown.$el, ['ivu-select-dropdown', 'ivu-dropdown-transfer'])
48
+    if (dropdown) dropdown.style.overflow = 'visible'
49
+  }
50
+}
51
+</script>

+ 2
- 0
src/components/main/components/side-menu/index.js View File

@@ -0,0 +1,2 @@
1
+import SideMenu from './side-menu.vue'
2
+export default SideMenu

+ 21
- 0
src/components/main/components/side-menu/item-mixin.js View File

@@ -0,0 +1,21 @@
1
+export default {
2
+  props: {
3
+    parentItem: {
4
+      type: Object,
5
+      default: () => {}
6
+    },
7
+    theme: String,
8
+    iconSize: Number
9
+  },
10
+  computed: {
11
+    parentName () {
12
+      return this.parentItem.name
13
+    },
14
+    children () {
15
+      return this.parentItem.children
16
+    },
17
+    textColor () {
18
+      return this.theme === 'dark' ? '#fff' : '#495060'
19
+    }
20
+  }
21
+}

+ 18
- 0
src/components/main/components/side-menu/mixin.js View File

@@ -0,0 +1,18 @@
1
+import CommonIcon from '_c/common-icon'
2
+import { showTitle } from '@/libs/util'
3
+export default {
4
+  components: {
5
+    CommonIcon
6
+  },
7
+  methods: {
8
+    showTitle (item) {
9
+      return showTitle(item, this)
10
+    },
11
+    showChildren (item) {
12
+      return item.children && (item.children.length > 1 || (item.meta && item.meta.showAlways))
13
+    },
14
+    getNameOrHref (item, children0) {
15
+      return item.href ? `isTurnByHref_${item.href}` : (children0 ? item.children[0].name : item.name)
16
+    }
17
+  }
18
+}

+ 26
- 0
src/components/main/components/side-menu/side-menu-item.vue View File

@@ -0,0 +1,26 @@
1
+<template>
2
+  <Submenu :name="`${parentName}`">
3
+    <template slot="title">
4
+      <common-icon :type="parentItem.icon || ''"/>
5
+      <span>{{ showTitle(parentItem) }}</span>
6
+    </template>
7
+    <template v-for="item in children">
8
+      <template v-if="item.children && item.children.length === 1">
9
+        <side-menu-item v-if="showChildren(item)" :key="`menu-${item.name}`" :parent-item="item"></side-menu-item>
10
+        <menu-item v-else :name="getNameOrHref(item, true)" :key="`menu-${item.children[0].name}`"><common-icon :type="item.children[0].icon || ''"/><span>{{ showTitle(item.children[0]) }}</span></menu-item>
11
+      </template>
12
+      <template v-else>
13
+        <side-menu-item v-if="showChildren(item)" :key="`menu-${item.name}`" :parent-item="item"></side-menu-item>
14
+        <menu-item v-else :name="getNameOrHref(item)" :key="`menu-${item.name}`"><common-icon :type="item.icon || ''"/><span>{{ showTitle(item) }}</span></menu-item>
15
+      </template>
16
+    </template>
17
+  </Submenu>
18
+</template>
19
+<script>
20
+import mixin from './mixin'
21
+import itemMixin from './item-mixin'
22
+export default {
23
+  name: 'SideMenuItem',
24
+  mixins: [ mixin, itemMixin ]
25
+}
26
+</script>

+ 50
- 0
src/components/main/components/side-menu/side-menu.less View File

@@ -0,0 +1,50 @@
1
+.side-menu-wrapper{
2
+  user-select: none;
3
+  // width: 170px;
4
+  // margin-top: 50px;
5
+
6
+  .ivu-menu-dark{
7
+    background-color: white;
8
+    color: black;
9
+  }
10
+  .ivu-menu-dark.ivu-menu-vertical .ivu-menu-item, .ivu-menu-dark.ivu-menu-vertical .ivu-menu-submenu-title {
11
+    color: black;
12
+}
13
+  .menu-collapsed{
14
+    padding-top: 10px;
15
+    
16
+    .ivu-dropdown{
17
+      width: 100%;
18
+      .ivu-dropdown-rel a{
19
+        width: 100%;
20
+      }
21
+    }
22
+    .ivu-tooltip{
23
+      width: 100%;
24
+      .ivu-tooltip-rel{
25
+        width: 100%;
26
+      }
27
+      .ivu-tooltip-popper .ivu-tooltip-content{
28
+        .ivu-tooltip-arrow{
29
+          border-right-color: #fff;
30
+        }
31
+        .ivu-tooltip-inner{
32
+          background: #fff;
33
+          color: #495060;
34
+        }
35
+      }
36
+    }
37
+
38
+
39
+  }
40
+  a.drop-menu-a{
41
+    display: inline-block;
42
+    padding: 6px 15px;
43
+    width: 100%;
44
+    text-align: center;
45
+    color: #495060;
46
+  }
47
+}
48
+.menu-title{
49
+  padding-left: 6px;
50
+}

+ 117
- 0
src/components/main/components/side-menu/side-menu.vue View File

@@ -0,0 +1,117 @@
1
+<template>
2
+  <div class="side-menu-wrapper">
3
+    <!-- <navbar></navbar> -->
4
+    <slot></slot>
5
+    <Menu ref="menu" v-show="!collapsed" :active-name="activeName" :open-names="openedNames" :accordion="accordion" :theme="theme" width="auto" @on-select="handleSelect">
6
+      <template v-for="item in menuList">
7
+        <template v-if="item.children && item.children.length === 1">
8
+          <side-menu-item v-if="showChildren(item)" :key="`menu-${item.name}`" :parent-item="item"></side-menu-item>
9
+          <menu-item v-else :name="getNameOrHref(item, true)" :key="`menu-${item.children[0].name}`"><common-icon :type="item.children[0].icon || ''"/><span>{{ showTitle(item.children[0]) }}</span></menu-item>
10
+        </template>
11
+        <template v-else>
12
+          <side-menu-item v-if="showChildren(item)" :key="`menu-${item.name}`" :parent-item="item"></side-menu-item>
13
+          <menu-item v-else :name="getNameOrHref(item)" :key="`menu-${item.name}`"><common-icon :type="item.icon || ''"/><span>{{ showTitle(item) }}</span></menu-item>
14
+        </template>
15
+      </template>
16
+    </Menu>
17
+    <div class="menu-collapsed" v-show="collapsed" :list="menuList">
18
+      <template v-for="item in menuList">
19
+        <collapsed-menu v-if="item.children && item.children.length > 1" @on-click="handleSelect" hide-title :root-icon-size="rootIconSize" :icon-size="iconSize" :theme="theme" :parent-item="item" :key="`drop-menu-${item.name}`"></collapsed-menu>
20
+        <Tooltip transfer v-else :content="showTitle(item.children && item.children[0] ? item.children[0] : item)" placement="right" :key="`drop-menu-${item.name}`">
21
+          <a @click="handleSelect(getNameOrHref(item, true))" class="drop-menu-a" :style="{textAlign: 'center'}"><common-icon :size="rootIconSize" :color="textColor" :type="item.icon || (item.children && item.children[0].icon)"/></a>
22
+        </Tooltip>
23
+      </template>
24
+    </div>
25
+  </div>
26
+</template>
27
+<script>
28
+import SideMenuItem from './side-menu-item.vue'
29
+import CollapsedMenu from './collapsed-menu.vue'
30
+import { getUnion } from '@/libs/tools'
31
+import mixin from './mixin'
32
+// import navbar from '../navbar/index'
33
+
34
+export default {
35
+  name: 'SideMenu',
36
+  mixins: [ mixin ],
37
+  components: {
38
+    SideMenuItem,
39
+    CollapsedMenu
40
+    // navbar
41
+  },
42
+  props: {
43
+    menuList: {
44
+      type: Array,
45
+      default () {
46
+        return []
47
+      }
48
+    },
49
+    collapsed: {
50
+      type: Boolean
51
+    },
52
+    theme: {
53
+      type: String,
54
+      default: 'dark'
55
+    },
56
+    rootIconSize: {
57
+      type: Number,
58
+      default: 20
59
+    },
60
+    iconSize: {
61
+      type: Number,
62
+      default: 16
63
+    },
64
+    accordion: Boolean,
65
+    activeName: {
66
+      type: String,
67
+      default: ''
68
+    },
69
+    openNames: {
70
+      type: Array,
71
+      default: () => []
72
+    }
73
+  },
74
+  data () {
75
+    return {
76
+      openedNames: []
77
+    }
78
+  },
79
+  methods: {
80
+    handleSelect (name) {
81
+      this.$emit('on-select', name)
82
+    },
83
+    getOpenedNamesByActiveName (name) {
84
+      return this.$route.matched.map(item => item.name).filter(item => item !== name)
85
+    },
86
+    updateOpenName (name) {
87
+      if (name === this.$config.homeName) this.openedNames = []
88
+      else this.openedNames = this.getOpenedNamesByActiveName(name)
89
+    }
90
+  },
91
+  computed: {
92
+    textColor () {
93
+      return this.theme === 'dark' ? '#fff' : '#495060'
94
+    }
95
+  },
96
+  watch: {
97
+    activeName (name) {
98
+      if (this.accordion) this.openedNames = this.getOpenedNamesByActiveName(name)
99
+      else this.openedNames = getUnion(this.openedNames, this.getOpenedNamesByActiveName(name))
100
+    },
101
+    openNames (newNames) {
102
+      this.openedNames = newNames
103
+    },
104
+    openedNames () {
105
+      this.$nextTick(() => {
106
+        this.$refs.menu.updateOpened()
107
+      })
108
+    }
109
+  },
110
+  mounted () {
111
+    this.openedNames = getUnion(this.openedNames, this.getOpenedNamesByActiveName(name))
112
+  }
113
+}
114
+</script>
115
+<style lang="less">
116
+@import './side-menu.less';
117
+</style>

+ 2
- 0
src/components/main/components/tags-nav/index.js View File

@@ -0,0 +1,2 @@
1
+import TagsNav from './tags-nav.vue'
2
+export default TagsNav

+ 87
- 0
src/components/main/components/tags-nav/tags-nav.less View File

@@ -0,0 +1,87 @@
1
+.no-select{
2
+  -webkit-touch-callout: none;
3
+  -webkit-user-select: none;
4
+  -khtml-user-select: none;
5
+  -moz-user-select: none;
6
+  -ms-user-select: none;
7
+  user-select: none;
8
+}
9
+.size{
10
+  width: 100%;
11
+  height: 100%;
12
+}
13
+.tags-nav{
14
+  position: relative;
15
+  border-top: 1px solid #F0F0F0;
16
+  border-bottom: 1px solid #F0F0F0;
17
+  .no-select;
18
+  .size;
19
+  .close-con{
20
+    position: absolute;
21
+    right: 0;
22
+    top: 0;
23
+    height: 100%;
24
+    width: 32px;
25
+    background: #fff;
26
+    text-align: center;
27
+    z-index: 10;
28
+  }
29
+  .btn-con{
30
+    position: absolute;
31
+    top: 0px;
32
+    height: 100%;
33
+    background: #fff;
34
+    padding-top: 3px;
35
+    z-index: 10;
36
+    button{
37
+      padding: 6px 4px;
38
+      line-height: 14px;
39
+      text-align: center;
40
+    }
41
+    &.left-btn{
42
+      left: 0px;
43
+    }
44
+    &.right-btn{
45
+      right: 32px;
46
+      border-right: 1px solid #F0F0F0;
47
+    }
48
+  }
49
+  .scroll-outer{
50
+    position: absolute;
51
+    left: 28px;
52
+    right: 61px;
53
+    top: 0;
54
+    bottom: 0;
55
+    box-shadow: 0px 0 3px 2px rgba(100,100,100,.1) inset;
56
+    .scroll-body{
57
+      height: ~"calc(100% - 1px)";
58
+      display: inline-block;
59
+      padding: 1px 4px 0;
60
+      position: absolute;
61
+      overflow: visible;
62
+      white-space: nowrap;
63
+      transition: left .3s ease;
64
+      .ivu-tag-dot-inner{
65
+        transition: background .2s ease;
66
+      }
67
+    }
68
+  }
69
+  .contextmenu {
70
+    position: absolute;
71
+    margin: 0;
72
+    padding: 5px 0;
73
+    background: #fff;
74
+    z-index: 1000;
75
+    list-style-type: none;
76
+    border-radius: 4px;
77
+    box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .1);
78
+    li {
79
+      margin: 0;
80
+      padding: 5px 15px;
81
+      cursor: pointer;
82
+      &:hover {
83
+        background: #eee;
84
+      }
85
+    }
86
+  }
87
+}

+ 209
- 0
src/components/main/components/tags-nav/tags-nav.vue View File

@@ -0,0 +1,209 @@
1
+<template>
2
+  <div class="tags-nav">
3
+    <div class="close-con">
4
+      <Dropdown transfer @on-click="handleTagsOption" style="margin-top:7px;">
5
+        <Button size="small" type="text">
6
+          <Icon :size="18" type="ios-close-circle-outline" />
7
+        </Button>
8
+        <DropdownMenu slot="list">
9
+          <DropdownItem name="close-all">关闭所有</DropdownItem>
10
+          <DropdownItem name="close-others">关闭其他</DropdownItem>
11
+        </DropdownMenu>
12
+      </Dropdown>
13
+    </div>
14
+    <ul v-show="visible" :style="{left: contextMenuLeft + 'px', top: contextMenuTop + 'px'}" class="contextmenu">
15
+      <li v-for="(item, key) of menuList" @click="handleTagsOption(key)" :key="key">{{item}}</li>
16
+    </ul>
17
+    <div class="btn-con left-btn">
18
+      <Button type="text" @click="handleScroll(240)">
19
+        <Icon :size="18" type="ios-arrow-back" />
20
+      </Button>
21
+    </div>
22
+    <div class="btn-con right-btn">
23
+      <Button type="text" @click="handleScroll(-240)">
24
+        <Icon :size="18" type="ios-arrow-forward" />
25
+      </Button>
26
+    </div>
27
+    <div class="scroll-outer" ref="scrollOuter" @DOMMouseScroll="handlescroll" @mousewheel="handlescroll">
28
+      <div ref="scrollBody" class="scroll-body" :style="{left: tagBodyLeft + 'px'}">
29
+        <transition-group name="taglist-moving-animation">
30
+          <Tag
31
+            type="dot"
32
+            v-for="(item, index) in list"
33
+            ref="tagsPageOpened"
34
+            :key="`tag-nav-${index}`"
35
+            :name="item.name"
36
+            :data-route-item="item"
37
+            @on-close="handleClose(item)"
38
+            @click.native="handleClick(item)"
39
+            :closable="item.name !== $config.homeName"
40
+            :color="isCurrentTag(item) ? 'primary' : 'default'"
41
+            @contextmenu.prevent.native="contextMenu(item, $event)"
42
+          >{{ showTitleInside(item) }}</Tag>
43
+        </transition-group>
44
+      </div>
45
+    </div>
46
+  </div>
47
+</template>
48
+
49
+<script>
50
+import { showTitle, routeEqual } from '@/libs/util'
51
+import beforeClose from '@/router/before-close'
52
+export default {
53
+  name: 'TagsNav',
54
+  props: {
55
+    value: Object,
56
+    list: {
57
+      type: Array,
58
+      default () {
59
+        return []
60
+      }
61
+    }
62
+  },
63
+  data () {
64
+    return {
65
+      tagBodyLeft: 0,
66
+      rightOffset: 40,
67
+      outerPadding: 4,
68
+      contextMenuLeft: 0,
69
+      contextMenuTop: 0,
70
+      visible: false,
71
+      menuList: {
72
+        others: '关闭其他',
73
+        all: '关闭所有'
74
+      }
75
+    }
76
+  },
77
+  computed: {
78
+    currentRouteObj () {
79
+      const { name, params, query } = this.value
80
+      return { name, params, query }
81
+    }
82
+  },
83
+  methods: {
84
+    handlescroll (e) {
85
+      var type = e.type
86
+      let delta = 0
87
+      if (type === 'DOMMouseScroll' || type === 'mousewheel') {
88
+        delta = (e.wheelDelta) ? e.wheelDelta : -(e.detail || 0) * 40
89
+      }
90
+      this.handleScroll(delta)
91
+    },
92
+    handleScroll (offset) {
93
+      const outerWidth = this.$refs.scrollOuter.offsetWidth
94
+      const bodyWidth = this.$refs.scrollBody.offsetWidth
95
+      if (offset > 0) {
96
+        this.tagBodyLeft = Math.min(0, this.tagBodyLeft + offset)
97
+      } else {
98
+        if (outerWidth < bodyWidth) {
99
+          if (this.tagBodyLeft < -(bodyWidth - outerWidth)) {
100
+            this.tagBodyLeft = this.tagBodyLeft
101
+          } else {
102
+            this.tagBodyLeft = Math.max(this.tagBodyLeft + offset, outerWidth - bodyWidth)
103
+          }
104
+        } else {
105
+          this.tagBodyLeft = 0
106
+        }
107
+      }
108
+    },
109
+    handleTagsOption (type) {
110
+      if (type.includes('all')) {
111
+        // 关闭所有,除了home
112
+        let res = this.list.filter(item => item.name === this.$config.homeName)
113
+        this.$emit('on-close', res, 'all')
114
+      } else if (type.includes('others')) {
115
+        // 关闭除当前页和home页的其他页
116
+        let res = this.list.filter(item => routeEqual(this.currentRouteObj, item) || item.name === this.$config.homeName)
117
+        this.$emit('on-close', res, 'others', this.currentRouteObj)
118
+        setTimeout(() => {
119
+          this.getTagElementByRoute(this.currentRouteObj)
120
+        }, 100)
121
+      }
122
+    },
123
+    handleClose (current) {
124
+      if (current.meta && current.meta.beforeCloseName && current.meta.beforeCloseName in beforeClose) {
125
+        new Promise(beforeClose[current.meta.beforeCloseName]).then(close => {
126
+          if (close) {
127
+            this.close(current)
128
+          }
129
+        })
130
+      } else {
131
+        this.close(current)
132
+      }
133
+    },
134
+    close (route) {
135
+      let res = this.list.filter(item => !routeEqual(route, item))
136
+      this.$emit('on-close', res, undefined, route)
137
+    },
138
+    handleClick (item) {
139
+      this.$emit('input', item)
140
+    },
141
+    showTitleInside (item) {
142
+      return showTitle(item, this)
143
+    },
144
+    isCurrentTag (item) {
145
+      return routeEqual(this.currentRouteObj, item)
146
+    },
147
+    moveToView (tag) {
148
+      const outerWidth = this.$refs.scrollOuter.offsetWidth
149
+      const bodyWidth = this.$refs.scrollBody.offsetWidth
150
+      if (bodyWidth < outerWidth) {
151
+        this.tagBodyLeft = 0
152
+      } else if (tag.offsetLeft < -this.tagBodyLeft) {
153
+        // 标签在可视区域左侧
154
+        this.tagBodyLeft = -tag.offsetLeft + this.outerPadding
155
+      } else if (tag.offsetLeft > -this.tagBodyLeft && tag.offsetLeft + tag.offsetWidth < -this.tagBodyLeft + outerWidth) {
156
+        // 标签在可视区域
157
+        this.tagBodyLeft = Math.min(0, outerWidth - tag.offsetWidth - tag.offsetLeft - this.outerPadding)
158
+      } else {
159
+        // 标签在可视区域右侧
160
+        this.tagBodyLeft = -(tag.offsetLeft - (outerWidth - this.outerPadding - tag.offsetWidth))
161
+      }
162
+    },
163
+    getTagElementByRoute (route) {
164
+      this.$nextTick(() => {
165
+        this.refsTag = this.$refs.tagsPageOpened
166
+        this.refsTag.forEach((item, index) => {
167
+          if (routeEqual(route, item.$attrs['data-route-item'])) {
168
+            let tag = this.refsTag[index].$el
169
+            this.moveToView(tag)
170
+          }
171
+        })
172
+      })
173
+    },
174
+    contextMenu (item, e) {
175
+      if (item.name === this.$config.homeName) {
176
+        return
177
+      }
178
+      this.visible = true
179
+      const offsetLeft = this.$el.getBoundingClientRect().left
180
+      this.contextMenuLeft = e.clientX - offsetLeft + 10
181
+      this.contextMenuTop = e.clientY - 64
182
+    },
183
+    closeMenu () {
184
+      this.visible = false
185
+    }
186
+  },
187
+  watch: {
188
+    '$route' (to) {
189
+      this.getTagElementByRoute(to)
190
+    },
191
+    visible (value) {
192
+      if (value) {
193
+        document.body.addEventListener('click', this.closeMenu)
194
+      } else {
195
+        document.body.removeEventListener('click', this.closeMenu)
196
+      }
197
+    }
198
+  },
199
+  mounted () {
200
+    setTimeout(() => {
201
+      this.getTagElementByRoute(this.$route)
202
+    }, 200)
203
+  }
204
+}
205
+</script>
206
+
207
+<style lang="less">
208
+@import './tags-nav.less';
209
+</style>

+ 2
- 0
src/components/main/components/user/index.js View File

@@ -0,0 +1,2 @@
1
+import User from './user.vue'
2
+export default User

+ 12
- 0
src/components/main/components/user/user.less View File

@@ -0,0 +1,12 @@
1
+.user{
2
+  &-avatar-dropdown{
3
+    cursor: pointer;
4
+    display: inline-block;
5
+    // height: 64px;
6
+    vertical-align: middle;
7
+    // line-height: 64px;
8
+    .ivu-badge-dot{
9
+      top: 16px;
10
+    }
11
+  }
12
+}

+ 59
- 0
src/components/main/components/user/user.vue View File

@@ -0,0 +1,59 @@
1
+<template>
2
+  <div class="user-avatar-dropdown">
3
+    <Dropdown @on-click="handleClick">
4
+      <Badge >
5
+        <Avatar :src="userAvatar"/>
6
+      </Badge>
7
+      <Icon :size="18" type="md-arrow-dropdown"></Icon>
8
+      <DropdownMenu slot="list">
9
+        <DropdownItem name="message">
10
+          消息中心<Badge style="margin-left: 10px" :count="messageUnreadCount"></Badge>
11
+        </DropdownItem>
12
+        <DropdownItem name="logout">退出登录</DropdownItem>
13
+      </DropdownMenu>
14
+    </Dropdown>
15
+  </div>
16
+</template>
17
+
18
+<script>
19
+import './user.less'
20
+import { mapActions } from 'vuex'
21
+export default {
22
+  name: 'User',
23
+  props: {
24
+    userAvatar: {
25
+      type: String,
26
+      default: ''
27
+    },
28
+    messageUnreadCount: {
29
+      type: Number,
30
+      default: 0
31
+    }
32
+  },
33
+  methods: {
34
+    ...mapActions([
35
+      'handleLogOut'
36
+    ]),
37
+    logout () {
38
+      this.handleLogOut().then(() => {
39
+        this.$router.push({
40
+          name: 'login'
41
+        })
42
+      })
43
+    },
44
+    message () {
45
+      this.$router.push({
46
+        name: 'message_page'
47
+      })
48
+    },
49
+    handleClick (name) {
50
+      switch (name) {
51
+        case 'logout': this.logout()
52
+          break
53
+        case 'message': this.message()
54
+          break
55
+      }
56
+    }
57
+  }
58
+}
59
+</script>

+ 0
- 0
src/components/main/index.js View File


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

Loading…
Cancel
Save