浏览代码

增加权限路由

user 4 年之前
父节点
当前提交
e68916392b
共有 100 个文件被更改,包括 5977 次插入0 次删除
  1. 14 0
      .editorconfig
  2. 15 0
      .env.development
  3. 6 0
      .env.production
  4. 8 0
      .env.staging
  5. 4 0
      .eslintignore
  6. 198 0
      .eslintrc.js
  7. 16 0
      .gitignore
  8. 8 0
      .postcssrc.js
  9. 5 0
      .travis.yml
  10. 5 0
      Dockerfile
  11. 10 0
      Dockerfile.builder
  12. 5 0
      Dockerfile.dev
  13. 21 0
      LICENSE
  14. 5 0
      babel.config.js
  15. 35 0
      build/index.js
  16. 7 0
      docker-auto-push.sh
  17. 3 0
      docker-entrypoint.sh
  18. 11 0
      docker-init.sh
  19. 24 0
      jest.config.js
  20. 66 0
      mock/index.js
  21. 64 0
      mock/mock-server.js
  22. 29 0
      mock/table.js
  23. 84 0
      mock/user.js
  24. 81 0
      package.json
  25. 二进制
      public/favicon.ico
  26. 17 0
      public/index.html
  27. 0 0
      pushTest.txt
  28. 95 0
      src/App.vue
  29. 111 0
      src/api/AxiosApi.js
  30. 17 0
      src/api/permissions/index.js
  31. 9 0
      src/api/permissions/modulepermission.js
  32. 47 0
      src/api/user/index.js
  33. 二进制
      src/assets/404_images/404.png
  34. 二进制
      src/assets/404_images/404_cloud.png
  35. 二进制
      src/assets/500_images/500.png
  36. 二进制
      src/assets/login/amtxts.png
  37. 二进制
      src/assets/login/bg.png
  38. 二进制
      src/assets/login/bg1.png
  39. 二进制
      src/assets/login/logo.gif
  40. 二进制
      src/assets/login/logo.png
  41. 二进制
      src/assets/login/logo_full.jpg
  42. 二进制
      src/assets/login/logo_full1.gif
  43. 111 0
      src/components/BackToTop/index.vue
  44. 78 0
      src/components/Breadcrumb/index.vue
  45. 239 0
      src/components/Charts/blendChart.vue
  46. 184 0
      src/components/Charts/brokenLineChart.vue
  47. 217 0
      src/components/Charts/doubleYAxisChart.vue
  48. 272 0
      src/components/Charts/mixChart.vue
  49. 34 0
      src/components/Charts/mixins/resize.js
  50. 94 0
      src/components/DishesCard/index.vue
  51. 68 0
      src/components/ErrorLog/index.vue
  52. 54 0
      src/components/GithubCorner/index.vue
  53. 44 0
      src/components/Hamburger/index.vue
  54. 1421 0
      src/components/ImageCropper/index.vue
  55. 19 0
      src/components/ImageCropper/utils/data2blob.js
  56. 39 0
      src/components/ImageCropper/utils/effectRipple.js
  57. 232 0
      src/components/ImageCropper/utils/language.js
  58. 7 0
      src/components/ImageCropper/utils/mimes.js
  59. 84 0
      src/components/Layout/LeftMptt.vue
  60. 140 0
      src/components/PanThumb/index.vue
  61. 59 0
      src/components/Screenfull/index.vue
  62. 91 0
      src/components/Sticky/index.vue
  63. 43 0
      src/components/SvgIcon/index.vue
  64. 113 0
      src/components/TextHoverEffect/Mallki.vue
  65. 91 0
      src/components/index.vue
  66. 653 0
      src/data/map.js
  67. 49 0
      src/directive/clipboard/clipboard.js
  68. 13 0
      src/directive/clipboard/index.js
  69. 77 0
      src/directive/el-drag-dialog/drag.js
  70. 13 0
      src/directive/el-drag-dialog/index.js
  71. 41 0
      src/directive/el-table/adaptive.js
  72. 13 0
      src/directive/el-table/index.js
  73. 13 0
      src/directive/permission/index.js
  74. 23 0
      src/directive/permission/permission.js
  75. 13 0
      src/directive/special_input_code/index.js
  76. 11 0
      src/directive/special_input_code/special_input_code.js
  77. 91 0
      src/directive/sticky.js
  78. 13 0
      src/directive/waves/index.js
  79. 26 0
      src/directive/waves/waves.css
  80. 72 0
      src/directive/waves/waves.js
  81. 82 0
      src/filters/index.js
  82. 9 0
      src/icons/index.js
  83. 二进制
      src/icons/order.png
  84. 二进制
      src/icons/person.jpg
  85. 1 0
      src/icons/svg/404.svg
  86. 1 0
      src/icons/svg/add_up.svg
  87. 0 0
      src/icons/svg/alipay.svg
  88. 1 0
      src/icons/svg/apply.svg
  89. 1 0
      src/icons/svg/bug.svg
  90. 0 0
      src/icons/svg/cash.svg
  91. 0 0
      src/icons/svg/cashier.svg
  92. 1 0
      src/icons/svg/chart.svg
  93. 1 0
      src/icons/svg/clipboard.svg
  94. 1 0
      src/icons/svg/component.svg
  95. 1 0
      src/icons/svg/compstaff.svg
  96. 0 0
      src/icons/svg/dashboard.svg
  97. 0 0
      src/icons/svg/dishes.svg
  98. 1 0
      src/icons/svg/documentation.svg
  99. 1 0
      src/icons/svg/drag.svg
  100. 1 0
      src/icons/svg/edit.svg

+ 14 - 0
.editorconfig

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

+ 15 - 0
.env.development

@@ -0,0 +1,15 @@
+# just a flag
+ENV = 'development'
+
+# base api
+VUE_APP_BASE_API = '/report'
+#VUE_APP_BASE_API = '/mock'
+
+# vue-cli uses the VUE_CLI_BABEL_TRANSPILE_MODULES environment variable,
+# to control whether the babel-plugin-dynamic-import-node plugin is enabled.
+# It only does one thing by converting all import() to require().
+# This configuration can significantly increase the speed of hot updates,
+# when you have a large number of pages.
+# Detail:  https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/babel-preset-app/index.js
+
+VUE_CLI_BABEL_TRANSPILE_MODULES = true

+ 6 - 0
.env.production

@@ -0,0 +1,6 @@
+# just a flag
+ENV = 'production'
+
+# base api
+VUE_APP_BASE_API = '/srv/api'
+

+ 8 - 0
.env.staging

@@ -0,0 +1,8 @@
+NODE_ENV = production
+
+# just a flag
+ENV = 'staging'
+
+# base api
+VUE_APP_BASE_API = '/srv/api'
+

+ 4 - 0
.eslintignore

@@ -0,0 +1,4 @@
+build/*.js
+src/assets
+public
+dist

+ 198 - 0
.eslintrc.js

@@ -0,0 +1,198 @@
+module.exports = {
+  root: true,
+  parserOptions: {
+    parser: 'babel-eslint',
+    sourceType: 'module'
+  },
+  env: {
+    browser: true,
+    node: true,
+    es6: true,
+  },
+  extends: ['plugin:vue/recommended', 'eslint:recommended'],
+
+  // add your custom rules here
+  //it is base on https://github.com/vuejs/eslint-config-vue
+  rules: {
+    "vue/max-attributes-per-line": [2, {
+      "singleline": 10,
+      "multiline": {
+        "max": 1,
+        "allowFirstLine": false
+      }
+    }],
+    "vue/singleline-html-element-content-newline": "off",
+    "vue/multiline-html-element-content-newline":"off",
+    "vue/name-property-casing": ["error", "PascalCase"],
+    "vue/no-v-html": "off",
+    'accessor-pairs': 2,
+    'arrow-spacing': [2, {
+      'before': true,
+      'after': true
+    }],
+    'block-spacing': [2, 'always'],
+    'brace-style': [2, '1tbs', {
+      'allowSingleLine': true
+    }],
+    'camelcase': [0, {
+      'properties': 'always'
+    }],
+    'comma-dangle': [2, 'never'],
+    'comma-spacing': [2, {
+      'before': false,
+      'after': true
+    }],
+    'comma-style': [2, 'last'],
+    'constructor-super': 2,
+    'curly': [2, 'multi-line'],
+    'dot-location': [2, 'property'],
+    'eol-last': 2,
+    'eqeqeq': ["error", "always", {"null": "ignore"}],
+    'generator-star-spacing': [2, {
+      'before': true,
+      'after': true
+    }],
+    'handle-callback-err': [2, '^(err|error)$'],
+    'indent': [2, 2, {
+      'SwitchCase': 1
+    }],
+    'jsx-quotes': [2, 'prefer-single'],
+    'key-spacing': [2, {
+      'beforeColon': false,
+      'afterColon': true
+    }],
+    'keyword-spacing': [2, {
+      'before': true,
+      'after': true
+    }],
+    'new-cap': [2, {
+      'newIsCap': true,
+      'capIsNew': false
+    }],
+    'new-parens': 2,
+    'no-array-constructor': 2,
+    'no-caller': 2,
+    'no-console': 'off',
+    'no-class-assign': 2,
+    'no-cond-assign': 2,
+    'no-const-assign': 2,
+    'no-control-regex': 0,
+    'no-delete-var': 2,
+    'no-dupe-args': 2,
+    'no-dupe-class-members': 2,
+    'no-dupe-keys': 2,
+    'no-duplicate-case': 2,
+    'no-empty-character-class': 2,
+    'no-empty-pattern': 2,
+    'no-eval': 2,
+    'no-ex-assign': 2,
+    'no-extend-native': 2,
+    'no-extra-bind': 2,
+    'no-extra-boolean-cast': 2,
+    'no-extra-parens': [2, 'functions'],
+    'no-fallthrough': 2,
+    'no-floating-decimal': 2,
+    'no-func-assign': 2,
+    'no-implied-eval': 2,
+    'no-inner-declarations': [2, 'functions'],
+    'no-invalid-regexp': 2,
+    'no-irregular-whitespace': 2,
+    'no-iterator': 2,
+    'no-label-var': 2,
+    'no-labels': [2, {
+      'allowLoop': false,
+      'allowSwitch': false
+    }],
+    'no-lone-blocks': 2,
+    'no-mixed-spaces-and-tabs': 2,
+    'no-multi-spaces': 2,
+    'no-multi-str': 2,
+    'no-multiple-empty-lines': [2, {
+      'max': 1
+    }],
+    'no-native-reassign': 2,
+    'no-negated-in-lhs': 2,
+    'no-new-object': 2,
+    'no-new-require': 2,
+    'no-new-symbol': 2,
+    'no-new-wrappers': 2,
+    'no-obj-calls': 2,
+    'no-octal': 2,
+    'no-octal-escape': 2,
+    'no-path-concat': 2,
+    'no-proto': 2,
+    'no-redeclare': 2,
+    'no-regex-spaces': 2,
+    'no-return-assign': [2, 'except-parens'],
+    'no-self-assign': 2,
+    'no-self-compare': 2,
+    'no-sequences': 2,
+    'no-shadow-restricted-names': 2,
+    'no-spaced-func': 2,
+    'no-sparse-arrays': 2,
+    'no-this-before-super': 2,
+    'no-throw-literal': 2,
+    'no-trailing-spaces': 2,
+    'no-undef': 2,
+    'no-undef-init': 2,
+    'no-unexpected-multiline': 2,
+    'no-unmodified-loop-condition': 2,
+    'no-unneeded-ternary': [2, {
+      'defaultAssignment': false
+    }],
+    'no-unreachable': 2,
+    'no-unsafe-finally': 2,
+    'no-unused-vars': [2, {
+      'vars': 'all',
+      'args': 'none'
+    }],
+    'no-useless-call': 2,
+    'no-useless-computed-key': 2,
+    'no-useless-constructor': 2,
+    'no-useless-escape': 0,
+    'no-whitespace-before-property': 2,
+    'no-with': 2,
+    'one-var': [2, {
+      'initialized': 'never'
+    }],
+    'operator-linebreak': [2, 'after', {
+      'overrides': {
+        '?': 'before',
+        ':': 'before'
+      }
+    }],
+    'padded-blocks': [2, 'never'],
+    'quotes': [2, 'single', {
+      'avoidEscape': true,
+      'allowTemplateLiterals': true
+    }],
+    'semi': [2, 'never'],
+    'semi-spacing': [2, {
+      'before': false,
+      'after': true
+    }],
+    'space-before-blocks': [2, 'always'],
+    'space-before-function-paren': [2, 'never'],
+    'space-in-parens': [2, 'never'],
+    'space-infix-ops': 2,
+    'space-unary-ops': [2, {
+      'words': true,
+      'nonwords': false
+    }],
+    'spaced-comment': [2, 'always', {
+      'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
+    }],
+    'template-curly-spacing': [2, 'never'],
+    'use-isnan': 2,
+    'valid-typeof': 2,
+    'wrap-iife': [2, 'any'],
+    'yield-star-spacing': [2, 'both'],
+    'yoda': [2, 'never'],
+    'prefer-const': 2,
+    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
+    'object-curly-spacing': [2, 'always', {
+      objectsInObjects: false
+    }],
+    'array-bracket-spacing': [2, 'never']
+  }
+}

+ 16 - 0
.gitignore

@@ -0,0 +1,16 @@
+.DS_Store
+node_modules/
+dist/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+package-lock.json
+tests/**/coverage/
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln

+ 8 - 0
.postcssrc.js

@@ -0,0 +1,8 @@
+// https://github.com/michael-ciniawsky/postcss-load-config
+
+module.exports = {
+  'plugins': {
+    // to edit target browsers: use "browserslist" field in package.json
+    'autoprefixer': {}
+  }
+}

+ 5 - 0
.travis.yml

@@ -0,0 +1,5 @@
+language: node_js
+node_js: stable
+script: npm run test
+notifications:
+  email: false

+ 5 - 0
Dockerfile

@@ -0,0 +1,5 @@
+FROM nginx:stable-alpine
+RUN mkdir -p /usr/share/nginx/html
+COPY ./dist /usr/share/nginx/html
+COPY ./docker-init.sh /bin/
+CMD ["/bin/docker-init.sh"]

+ 10 - 0
Dockerfile.builder

@@ -0,0 +1,10 @@
+FROM node:alpine
+
+RUN cp /etc/apk/repositories /etc/apk/repositories.bak
+
+RUN echo "http://mirrors.aliyun.com/alpine/latest-stable/main/" > /etc/apk/repositories 
+RUN apk --no-cache add make g++ python
+
+WORKDIR /opt/web
+
+CMD ["node"]

+ 5 - 0
Dockerfile.dev

@@ -0,0 +1,5 @@
+FROM nginx:stable-alpine
+RUN mkdir -p /usr/share/nginx/html
+COPY ./dist /usr/share/nginx/html
+COPY ./docker-init.sh /bin/
+CMD ["/bin/docker-init.sh"]

+ 21 - 0
LICENSE

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

+ 5 - 0
babel.config.js

@@ -0,0 +1,5 @@
+module.exports = {
+  presets: [
+    '@vue/app'
+  ]
+}

+ 35 - 0
build/index.js

@@ -0,0 +1,35 @@
+const { run } = require('runjs')
+const chalk = require('chalk')
+const config = require('../vue.config.js')
+const rawArgv = process.argv.slice(2)
+const args = rawArgv.join(' ')
+
+if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
+  const report = rawArgv.includes('--report')
+
+  run(`vue-cli-service build ${args}`)
+
+  const port = 9526
+  const publicPath = config.publicPath
+
+  var connect = require('connect')
+  var serveStatic = require('serve-static')
+  const app = connect()
+
+  app.use(
+    publicPath,
+    serveStatic('./dist', {
+      index: ['index.html', '/']
+    })
+  )
+
+  app.listen(port, function () {
+    console.log(chalk.green(`> Preview at  http://localhost:${port}${publicPath}`))
+    if (report) {
+      console.log(chalk.green(`> Report at  http://localhost:${port}${publicPath}report.html`))
+    }
+
+  })
+} else {
+  run(`vue-cli-service build ${args}`)
+}

+ 7 - 0
docker-auto-push.sh

@@ -0,0 +1,7 @@
+#!/bin/sh
+git pull
+docker build -t registry-platform.linkerplus.com/huzhou/web-builder:1.0.0 -f Dockerfile.builder . && docker push registry-platform.linkerplus.com/huzhou/web-builder:1.0.0
+rm -rf ./dist
+docker run --rm -i --user $(id -u) -v $(pwd):/opt/web registry-platform.linkerplus.com/huzhou/web-builder:1.0.0 /bin/sh -c "npm --registry https://registry.npm.taobao.org install && npm rebuild node-sass && npm run build"
+docker build -t registry-platform.linkerplus.com/huzhou/web-dev:1.0.0 -f Dockerfile.dev . && docker push registry-platform.linkerplus.com/huzhou/web-dev:1.0.0
+docker build -t registry-platform.linkerplus.com/huzhou/web:1.0.0 . && docker push registry-platform.linkerplus.com/huzhou/web:1.0.0

+ 3 - 0
docker-entrypoint.sh

@@ -0,0 +1,3 @@
+#!/bin/sh
+
+npm run staging

+ 11 - 0
docker-init.sh

@@ -0,0 +1,11 @@
+#!/bin/sh
+
+if ! [[ -z "${BASE_API}" ]]; then
+    sed -i -e "s@https://apple.linkerplus.com/api@$BASE_API@g" /usr/share/nginx/html/static/js/app*.js
+fi
+
+if ! [[ -z "${LOGIN_API}" ]]; then
+    sed -i -e "s@https://linker-im.linkerplus.com/user/login@$LOGIN_API@g" /usr/share/nginx/html/static/js/app*.js
+fi
+
+nginx -g "daemon off;"

+ 24 - 0
jest.config.js

@@ -0,0 +1,24 @@
+module.exports = {
+  moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
+  transform: {
+    '^.+\\.vue$': 'vue-jest',
+    '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$':
+      'jest-transform-stub',
+    '^.+\\.jsx?$': 'babel-jest'
+  },
+  moduleNameMapper: {
+    '^@/(.*)$': '<rootDir>/src/$1'
+  },
+  snapshotSerializers: ['jest-serializer-vue'],
+  testMatch: [
+    '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
+  ],
+  collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'],
+  coverageDirectory: '<rootDir>/tests/unit/coverage',
+  // 'collectCoverage': true,
+  'coverageReporters': [
+    'lcov',
+    'text-summary'
+  ],
+  testURL: 'http://localhost/'
+}

+ 66 - 0
mock/index.js

@@ -0,0 +1,66 @@
+import Mock from 'mockjs'
+import { param2Obj } from '../src/utils'
+
+import user from './user'
+import table from './table'
+
+const mocks = [
+  ...user,
+  ...table
+]
+
+// for front mock
+// please use it cautiously, it will redefine XMLHttpRequest,
+// which will cause many of your third-party libraries to be invalidated(like progress event).
+export function mockXHR() {
+  // mock patch
+  // https://github.com/nuysoft/Mock/issues/300
+  Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
+  Mock.XHR.prototype.send = function() {
+    if (this.custom.xhr) {
+      this.custom.xhr.withCredentials = this.withCredentials || false
+
+      if (this.responseType) {
+        this.custom.xhr.responseType = this.responseType
+      }
+    }
+    this.proxy_send(...arguments)
+  }
+
+  function XHR2ExpressReqWrap(respond) {
+    return function(options) {
+      let result = null
+      if (respond instanceof Function) {
+        const { body, type, url } = options
+        // https://expressjs.com/en/4x/api.html#req
+        result = respond({
+          method: type,
+          body: JSON.parse(body),
+          query: param2Obj(url)
+        })
+      } else {
+        result = respond
+      }
+      return Mock.mock(result)
+    }
+  }
+
+  for (const i of mocks) {
+    Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response))
+  }
+}
+
+// for mock server
+const responseFake = (url, type, respond) => {
+  return {
+    url: new RegExp(`/mock${url}`),
+    type: type || 'get',
+    response(req, res) {
+      res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond))
+    }
+  }
+}
+
+export default mocks.map(route => {
+  return responseFake(route.url, route.type, route.response)
+})

+ 64 - 0
mock/mock-server.js

@@ -0,0 +1,64 @@
+const chokidar = require('chokidar')
+const bodyParser = require('body-parser')
+const chalk = require('chalk')
+const path = require('path')
+
+const mockDir = path.join(process.cwd(), 'mock')
+
+function registerRoutes(app) {
+  let mockLastIndex
+  const { default: mocks } = require('./index.js')
+  for (const mock of mocks) {
+    app[mock.type](mock.url, mock.response)
+    mockLastIndex = app._router.stack.length
+  }
+  const mockRoutesLength = Object.keys(mocks).length
+  return {
+    mockRoutesLength: mockRoutesLength,
+    mockStartIndex: mockLastIndex - mockRoutesLength
+  }
+}
+
+function unregisterRoutes() {
+  Object.keys(require.cache).forEach(i => {
+    if (i.includes(mockDir)) {
+      delete require.cache[require.resolve(i)]
+    }
+  })
+}
+
+module.exports = app => {
+  // es6 polyfill
+  require('@babel/register')
+
+  // parse app.body
+  // https://expressjs.com/en/4x/api.html#req.body
+  app.use(bodyParser.json())
+  app.use(bodyParser.urlencoded({
+    extended: true
+  }))
+
+  const mockRoutes = registerRoutes(app)
+  var mockRoutesLength = mockRoutes.mockRoutesLength
+  var mockStartIndex = mockRoutes.mockStartIndex
+
+  // watch files, hot reload mock server
+  chokidar.watch(mockDir, {
+    ignored: /mock-server/,
+    ignoreInitial: true
+  }).on('all', (event, path) => {
+    if (event === 'change' || event === 'add') {
+      // remove mock routes stack
+      app._router.stack.splice(mockStartIndex, mockRoutesLength)
+
+      // clear routes cache
+      unregisterRoutes()
+
+      const mockRoutes = registerRoutes(app)
+      mockRoutesLength = mockRoutes.mockRoutesLength
+      mockStartIndex = mockRoutes.mockStartIndex
+
+      console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed  ${path}`))
+    }
+  })
+}

+ 29 - 0
mock/table.js

@@ -0,0 +1,29 @@
+import Mock from 'mockjs'
+
+const data = Mock.mock({
+  'items|30': [{
+    id: '@id',
+    title: '@sentence(10, 20)',
+    'status|1': ['published', 'draft', 'deleted'],
+    author: 'name',
+    display_time: '@datetime',
+    pageviews: '@integer(300, 5000)'
+  }]
+})
+
+export default [
+  {
+    url: '/table/list',
+    type: 'get',
+    response: config => {
+      const items = data.items
+      return {
+        code: 20000,
+        data: {
+          total: items.length,
+          items: items
+        }
+      }
+    }
+  }
+]

+ 84 - 0
mock/user.js

@@ -0,0 +1,84 @@
+
+const tokens = {
+  admin: {
+    token: 'admin-token'
+  },
+  editor: {
+    token: 'editor-token'
+  }
+}
+
+const users = {
+  'admin-token': {
+    roles: ['admin'],
+    introduction: 'I am a super administrator',
+    avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
+    name: 'Super Admin'
+  },
+  'editor-token': {
+    roles: ['editor'],
+    introduction: 'I am an editor',
+    avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
+    name: 'Normal Editor'
+  }
+}
+
+export default [
+  // user login
+  {
+    url: '/user/login',
+    type: 'post',
+    response: config => {
+      const { username } = config.body
+      const token = tokens[username]
+
+      // mock error
+      if (!token) {
+        return {
+          code: 60204,
+          message: 'Account and password are incorrect.'
+        }
+      }
+
+      return {
+        code: 20000,
+        data: token
+      }
+    }
+  },
+
+  // get user info
+  {
+    url: '/user/info\.*',
+    type: 'get',
+    response: config => {
+      const { token } = config.query
+      const info = users[token]
+
+      // mock error
+      if (!info) {
+        return {
+          code: 50008,
+          message: 'Login failed, unable to get user details.'
+        }
+      }
+
+      return {
+        code: 20000,
+        data: info
+      }
+    }
+  },
+
+  // user logout
+  {
+    url: '/user/logout',
+    type: 'post',
+    response: _ => {
+      return {
+        code: 20000,
+        data: 'success'
+      }
+    }
+  }
+]

+ 81 - 0
package.json

@@ -0,0 +1,81 @@
+{
+  "name": "vue-admin-template",
+  "version": "4.1.0",
+  "description": "A vue admin template with Element UI & axios & iconfont & permission control & lint",
+  "author": "Pan <panfree23@gmail.com>",
+  "license": "MIT",
+  "scripts": {
+    "dev": "vue-cli-service serve",
+    "build": "vue-cli-service build",
+    "staging": "vue-cli-service build --mode staging",
+    "preview": "node build/index.js --preview",
+    "lint": "eslint --ext .js,.vue src",
+    "test:unit": "jest --clearCache && vue-cli-service test:unit",
+    "test:ci": "npm run lint && npm run test:unit",
+    "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
+  },
+  "dependencies": {
+    "@types/echarts": "^4.4.2",
+    "axios": "0.18.0",
+    "babel-polyfill": "^6.26.0",
+    "c3": "^0.7.17",
+    "echarts": "^4.5.0",
+    "element-ui": "2.13.0",
+    "jquery": "^3.5.1",
+    "jquery-ui": "^1.12.1",
+    "js-cookie": "2.2.0",
+    "lodash": "^4.17.15",
+    "normalize.css": "7.0.0",
+    "nprogress": "0.2.0",
+    "path-to-regexp": "2.4.0",
+    "pivottable": "^2.23.0",
+    "screenfull": "^5.0.0",
+    "sortablejs": "^1.10.1",
+    "vue": "2.6.10",
+    "vue-count-to": "^1.0.13",
+    "vue-moment": "^4.1.0",
+    "vue-router": "3.0.6",
+    "vuex": "3.1.0",
+    "webpack-jquery-ui": "^2.0.1"
+  },
+  "devDependencies": {
+    "@babel/core": "7.0.0",
+    "@babel/register": "7.0.0",
+    "@vue/cli-plugin-babel": "3.6.0",
+    "@vue/cli-plugin-eslint": "3.6.0",
+    "@vue/cli-plugin-unit-jest": "3.6.3",
+    "@vue/cli-service": "3.6.0",
+    "@vue/test-utils": "1.0.0-beta.29",
+    "babel-core": "7.0.0-bridge.0",
+    "babel-eslint": "10.0.1",
+    "babel-jest": "23.6.0",
+    "chalk": "2.4.2",
+    "connect": "3.6.6",
+    "eslint": "5.15.3",
+    "eslint-friendly-formatter": "4.0.1",
+    "eslint-loader": "2.1.2",
+    "eslint-plugin-html": "^5.0.3",
+    "eslint-plugin-vue": "^5.2.2",
+    "html-webpack-plugin": "3.2.0",
+    "mockjs": "1.0.1-beta3",
+    "node-sass": "^4.9.0",
+    "runjs": "^4.3.2",
+    "sass-loader": "^7.1.0",
+    "script-ext-html-webpack-plugin": "2.1.3",
+    "script-loader": "0.7.2",
+    "serve-static": "^1.13.2",
+    "svg-inline-loader": "^0.8.0",
+    "svg-sprite-loader": "4.1.3",
+    "svgo": "1.2.2",
+    "vue-template-compiler": "2.6.10"
+  },
+  "engines": {
+    "node": ">=8.9",
+    "npm": ">= 3.0.0"
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions",
+    "not ie <= 8"
+  ]
+}

二进制
public/favicon.ico


+ 17 - 0
public/index.html

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
+    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+    <title><%= webpackConfig.name %></title>
+  </head>
+  <body>
+    <noscript>
+      <strong>We're sorry but <%= webpackConfig.name %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+    </noscript>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+  </body>
+</html>

+ 0 - 0
pushTest.txt


+ 95 - 0
src/App.vue

@@ -0,0 +1,95 @@
+<template>
+    <div id="app">
+        <router-view />
+    </div>
+</template>
+
+<script>
+import request from "@/utils/request";
+import store from "@/store";
+import router from "@/router";
+import axios from "./api/AxiosApi";
+import Layout from "@/layout";
+export default {
+    name: "App",
+    data() {
+        return {
+            routerData: [
+                {
+                    path: "test1",
+                    component: () => import("@/views/power/test1"),
+                    meta: {
+                        title: "第一个路由",
+                        icon: "home",
+                        affix: false,
+                    },
+                },
+                {
+                    path: "test2",
+                    name: "test2",
+                    component: () => import("@/views/power/test2"),
+                    meta: {
+                        title: "第二个路由",
+                        icon: "home",
+                        affix: false,
+                    },
+                },
+                {
+                    path: "test3",
+                    name: "test3",
+                    component: () => import("@/views/power/test3"),
+                    meta: {
+                        title: "第三个路由",
+                        icon: "home",
+                        affix: false,
+                    },
+                },
+                {
+                    path: "test4",
+                    name: "test4",
+                    component: () => import("@/views/power/test4"),
+                    meta: {
+                        title: "第四个路由",
+                        icon: "home",
+                        affix: false,
+                    },
+                },
+            ],
+        };
+    },
+    created() {
+        this.routesHandle()
+    },
+    methods: {
+        routesHandle() {
+            axios.power.get().then((rem) => {
+                console.log("路由查询", rem);
+                
+                this.asyncRoute(rem);
+            });
+        },
+        asyncRoute(data) {
+            console.log(11111,data);
+            let pathArray = data.map(v=>{
+                return v.path
+            })
+            let routes = [
+                {
+                    path: "/power",
+                    component: () => import("@/layout"),
+                    meta: { title: "权限路由", icon: "home", affix: false },
+                    children: this.routerData.filter(val => pathArray.indexOf(val.path)),
+                },
+            ];
+            // console.log('routes', routes);
+            // routes[0].children = data.children
+            const ps = [
+                store.dispatch("permission/asnycAddRouter", { routes: routes }),
+            ];
+            Promise.all(ps).then((res) => {
+                router.addRoutes(routes);
+            });
+        },
+    },
+};
+</script>

+ 111 - 0
src/api/AxiosApi.js

@@ -0,0 +1,111 @@
+import axios from '@/utils/request';
+
+const URL = {
+    target: "http://127.0.0.1:8000/",
+    report: "report/",
+    pivottable: "pivottable/payment/",
+    menu: "pivottable/menu/",
+    relation: {
+        dapt: 'relation/dapt/',
+        daptuser: 'relation/daptuser/'
+    },
+    detail: 'mptt/detail/',
+    payStat: 'pivottable/paystat/',
+    power: 'power/userroute/'
+}
+
+export default {
+    payment: {
+        list: (id = '') => {
+            return axios({
+                method: "get",
+                url: URL.pivottable + id,
+                params: {}
+            });
+        },
+        save: data => {
+            return axios({
+                method: "post",
+                url: URL.pivottable,
+                data: data
+            })
+        },
+        delete: id => {
+            return axios({
+                method: "delete",
+                url: URL.pivottable + id + '/'
+            })
+        }
+
+    },
+    payStat: {
+        get: (order_by = 0) => {
+            return axios({
+                method: "get",
+                url: URL.payStat + order_by,
+                params: { order_by: order_by }
+            });
+        },
+    },
+    menu: {
+        list: (id = '') => {
+            return axios({
+                method: "get",
+                url: URL.menu + id,
+            });
+        },
+        save: data => {
+            return axios({
+                method: "post",
+                url: URL.menu,
+                data: data
+            })
+        },
+        delete: (id) => {
+            return axios({
+                method: "delete",
+                url: URL.menu + id + '/'
+            })
+        }
+    },
+    dailyMptt: {
+        save: data => {
+            return axios({
+                method: "post",
+                url: URL.detail,
+                data: data
+            });
+        },
+        get: (params = {}) => {
+            return axios({
+                method: "get",
+                url: URL.detail,
+                params: params
+            });
+        },
+        delete: id => {
+            return axios({
+                method: "delete",
+                url: URL.detail,
+                data: { id: id }
+            })
+        },
+    },
+    user: {
+        list: () => {
+            return axios({
+                method: "get",
+                url: 'relation/user/',
+                params: {}
+            });
+        }
+    },
+    power: {
+        get: () => {
+            return axios({
+                method: "get",
+                url: URL.power,
+            });
+        }
+    }
+}

+ 17 - 0
src/api/permissions/index.js

@@ -0,0 +1,17 @@
+import request from '@/utils/request'
+
+export function fetchUserPermissions(params) {
+  return request({
+    url: '/basic/userpermission/',
+    method: 'get',
+    params
+  })
+}
+
+export function fetchUserRouters(params) {
+  return request({
+    url: '/basic/usermenu/',
+    method: 'get',
+    params
+  })
+}

+ 9 - 0
src/api/permissions/modulepermission.js

@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+
+export function fetchmodulepermissionList(params) {
+  return request({
+    url: '/permissions/modulepermission/',
+    method: 'get',
+    params
+  })
+}

+ 47 - 0
src/api/user/index.js

@@ -0,0 +1,47 @@
+import request from '@/utils/request'
+
+export function login(data) {
+  return request({
+    url: 'login/',
+    method: 'post',
+    data
+  })
+}
+
+export function getInfo() {
+  return request({
+    url: '/shop/user/',
+    method: 'get'
+  })
+}
+
+export function logout() {
+  return request({
+    url: 'logout/',
+    method: 'post'
+  })
+}
+
+export function register(data) {
+  return request({
+    url: '/basic/register/',
+    method: 'post',
+    data
+  })
+}
+
+export function updateUser(data) {
+  return request({
+    url: `/shop/userinfo/${data.id}/`,
+    method: 'put',
+    data
+  })
+}
+
+export function updatePassword(data) {
+  return request({
+    url: '/basic/renewpassword/',
+    method: 'post',
+    data
+  })
+}

二进制
src/assets/404_images/404.png


二进制
src/assets/404_images/404_cloud.png


二进制
src/assets/500_images/500.png


二进制
src/assets/login/amtxts.png


二进制
src/assets/login/bg.png


二进制
src/assets/login/bg1.png


二进制
src/assets/login/logo.gif


二进制
src/assets/login/logo.png


二进制
src/assets/login/logo_full.jpg


二进制
src/assets/login/logo_full1.gif


+ 111 - 0
src/components/BackToTop/index.vue

@@ -0,0 +1,111 @@
+<template>
+  <transition :name="transitionName">
+    <div v-show="visible" :style="customStyle" class="back-to-ceiling" @click="backToTop">
+      <svg width="16" height="16" viewBox="0 0 17 17" xmlns="http://www.w3.org/2000/svg" class="Icon Icon--backToTopArrow" aria-hidden="true" style="height:16px;width:16px"><path d="M12.036 15.59a1 1 0 0 1-.997.995H5.032a.996.996 0 0 1-.997-.996V8.584H1.03c-1.1 0-1.36-.633-.578-1.416L7.33.29a1.003 1.003 0 0 1 1.412 0l6.878 6.88c.782.78.523 1.415-.58 1.415h-3.004v7.004z" /></svg>
+    </div>
+  </transition>
+</template>
+
+<script>
+export default {
+  name: 'BackToTop',
+  props: {
+    visibilityHeight: {
+      type: Number,
+      default: 400
+    },
+    backPosition: {
+      type: Number,
+      default: 0
+    },
+    customStyle: {
+      type: Object,
+      default: function() {
+        return {
+          right: '50px',
+          bottom: '50px',
+          width: '40px',
+          height: '40px',
+          'border-radius': '4px',
+          'line-height': '45px',
+          background: '#e7eaf1'
+        }
+      }
+    },
+    transitionName: {
+      type: String,
+      default: 'fade'
+    }
+  },
+  data() {
+    return {
+      visible: false,
+      interval: null,
+      isMoving: false
+    }
+  },
+  mounted() {
+    window.addEventListener('scroll', this.handleScroll)
+  },
+  beforeDestroy() {
+    window.removeEventListener('scroll', this.handleScroll)
+    if (this.interval) {
+      clearInterval(this.interval)
+    }
+  },
+  methods: {
+    handleScroll() {
+      this.visible = window.pageYOffset > this.visibilityHeight
+    },
+    backToTop() {
+      if (this.isMoving) return
+      const start = window.pageYOffset
+      let i = 0
+      this.isMoving = true
+      this.interval = setInterval(() => {
+        const next = Math.floor(this.easeInOutQuad(10 * i, start, -start, 500))
+        if (next <= this.backPosition) {
+          window.scrollTo(0, this.backPosition)
+          clearInterval(this.interval)
+          this.isMoving = false
+        } else {
+          window.scrollTo(0, next)
+        }
+        i++
+      }, 16.7)
+    },
+    easeInOutQuad(t, b, c, d) {
+      if ((t /= d / 2) < 1) return c / 2 * t * t + b
+      return -c / 2 * (--t * (t - 2) - 1) + b
+    }
+  }
+}
+</script>
+
+<style scoped>
+.back-to-ceiling {
+  position: fixed;
+  display: inline-block;
+  text-align: center;
+  cursor: pointer;
+}
+
+.back-to-ceiling:hover {
+  background: #d5dbe7;
+}
+
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity .5s;
+}
+
+.fade-enter,
+.fade-leave-to {
+  opacity: 0
+}
+
+.back-to-ceiling .Icon {
+  fill: #9aaabf;
+  background: none;
+}
+</style>

+ 78 - 0
src/components/Breadcrumb/index.vue

@@ -0,0 +1,78 @@
+<template>
+  <el-breadcrumb class="app-breadcrumb" separator="/">
+    <transition-group name="breadcrumb">
+      <el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
+        <span v-if="item.redirect==='noRedirect'||index==levelList.length-1" class="no-redirect">{{ item.meta.title }}</span>
+        <a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
+      </el-breadcrumb-item>
+    </transition-group>
+  </el-breadcrumb>
+</template>
+
+<script>
+import pathToRegexp from 'path-to-regexp'
+
+export default {
+  data() {
+    return {
+      levelList: null
+    }
+  },
+  watch: {
+    $route() {
+      this.getBreadcrumb()
+    }
+  },
+  created() {
+    this.getBreadcrumb()
+  },
+  methods: {
+    getBreadcrumb() {
+      // only show routes with meta.title
+      let matched = this.$route.matched.filter(item => item.meta && item.meta.title)
+      const first = matched[0]
+
+      if (!this.isDashboard(first)) {
+        matched = [{ path: '/dashboard', meta: { title: '首页' }}].concat(matched)
+      }
+
+      this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
+    },
+    isDashboard(route) {
+      const name = route && route.name
+      if (!name) {
+        return false
+      }
+      return name.trim().toLocaleLowerCase() === 'Dashboard'.toLocaleLowerCase()
+    },
+    pathCompile(path) {
+      // To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
+      const { params } = this.$route
+      var toPath = pathToRegexp.compile(path)
+      return toPath(params)
+    },
+    handleLink(item) {
+      const { redirect, path } = item
+      if (redirect) {
+        this.$router.push(redirect)
+        return
+      }
+      this.$router.push(this.pathCompile(path))
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.app-breadcrumb.el-breadcrumb {
+  display: inline-block;
+  font-size: 14px;
+  line-height: 50px;
+  margin-left: 8px;
+
+  .no-redirect {
+    color: #97a8be;
+    cursor: text;
+  }
+}
+</style>

+ 239 - 0
src/components/Charts/blendChart.vue

@@ -0,0 +1,239 @@
+<template>
+  <div :id="id" :class="className" :style="{height:height,width:width}" />
+</template>
+
+<script>
+import echarts from 'echarts'
+require('echarts/theme/macarons') // echarts theme
+import resize from './mixins/resize'
+
+export default {
+  name: 'BlendChart',
+  mixins: [resize],
+  props: {
+    chartTitle: {
+      type: String,
+      default: 'TITLE'
+    },
+    className: {
+      type: String,
+      default: 'blend-chart'
+    },
+    id: {
+      type: String,
+      default: 'blend-chart'
+    },
+    width: {
+      type: String,
+      default: '100%'
+    },
+    height: {
+      type: String,
+      default: '500px'
+    },
+    xData: {
+      type: Array,
+      default: Array[1]
+    },
+    yOneData: {
+      type: Array,
+      default: Array[10]
+    },
+    yTwoData: {
+      type: Array,
+      default: Array[12]
+    },
+    yThreeData: {
+      type: Array,
+      default: Array[14]
+    }
+  },
+  data() {
+    return {
+      chart: null
+    }
+  },
+  mounted() {
+    this.initChart()
+  },
+  beforeDestroy() {
+    if (!this.chart) {
+      return
+    }
+    this.chart.dispose()
+    this.chart = null
+  },
+  methods: {
+    initChart() {
+      this.chart = echarts.init(document.getElementById(this.id), 'macarons')
+      // const xData = (function() {
+      //   const data = []
+      //   for (let i = 1; i < 13; i++) {
+      //     data.push(i + '月')
+      //   }
+      //   return data
+      // }())
+      this.chart.setOption({
+        backgroundColor: '#FFFFFF',
+        title: {
+          text: this.chartTitle
+        },
+        tooltip: {
+          trigger: 'axis',
+          axisPointer: { // 坐标轴指示器,坐标轴触发有效
+            type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
+          }
+        },
+        toolbox: {
+          feature: {
+            dataView: {
+              show: true,
+              readOnly: false
+            },
+            // magicType: {show: true, type: ['stack', 'tiled']},
+            restore: {
+              show: true
+            },
+            saveAsImage: {
+              show: true
+            }
+          }
+        },
+        legend: {
+          top: 24,
+          data: ['支付宝', '微信', '现金']
+        },
+        grid: {
+          left: '3%',
+          right: '8%',
+          borderWidth: 0,
+          top: '13%',
+          bottom: '10%',
+          containLabel: true
+        },
+        xAxis: [{
+          type: 'category',
+          data: []
+        }],
+        yAxis: [{
+          name: '元(¥)',
+          type: 'value',
+          // name: '投诉举报数',
+          axisLabel: {
+            formatter: '{value}'
+          }
+        }],
+        dataZoom: [{
+          show: true,
+          height: 20,
+          xAxisIndex: [
+            0
+          ],
+          bottom: 30,
+          start: 0,
+          end: 100,
+          handleIcon: 'path://M306.1,413c0,2.2-1.8,4-4,4h-59.8c-2.2,0-4-1.8-4-4V200.8c0-2.2,1.8-4,4-4h59.8c2.2,0,4,1.8,4,4V413z',
+          handleSize: '110%',
+          handleStyle: {
+            color: '#d3dee5'
+
+          },
+          textStyle: {
+            color: '#000'
+          },
+          borderColor: '#90979c'
+
+        }, {
+          type: 'inside',
+          show: true,
+          height: 15,
+          start: 1,
+          end: 35
+        }],
+        series: [{
+          name: '支付宝',
+          type: 'bar',
+          itemStyle: {
+            normal: {
+              // color: '#01949B'
+            }
+          },
+          markPoint: {
+            data: [{
+              type: 'max',
+              name: '最大值'
+            },
+            {
+              type: 'min',
+              name: '最小值'
+            }
+            ]
+          },
+          markLine: {
+            data: [{
+              type: 'average',
+              name: '平均值'
+            }]
+          },
+          data: []
+          // data: [2031, 1793, 3640, 2593, 4377, 3201, 2275, 3289, 3356, 2859, 4244, 3945]
+        }, {
+          name: '微信',
+          type: 'bar',
+          itemStyle: {
+            normal: {
+              // color: '#EBA954'
+            }
+          },
+          markPoint: {
+            data: [{
+              type: 'max',
+              name: '最大值'
+            },
+            {
+              type: 'min',
+              name: '最小值'
+            }
+            ]
+          },
+          markLine: {
+            data: [{
+              type: 'average',
+              name: '平均值'
+            }]
+          },
+          data: []
+          // data: [1043, 1456, 1900, 1200, 2100, 1870, 980, 1569, 1130, 1490, 2300, 2210]
+        }, {
+          name: '现金',
+          type: 'bar',
+          itemStyle: {
+            normal: {
+              // color: '#C23531'
+            }
+          },
+          markPoint: {
+            data: [{
+              type: 'max',
+              name: '最大值'
+            },
+            {
+              type: 'min',
+              name: '最小值'
+            }
+            ]
+          },
+          markLine: {
+            data: [{
+              type: 'average',
+              name: '平均值'
+            }]
+          },
+          data: []
+          // data: [787, 571, 999, 341, 231, 812, 735, 231, 322, 712, 1230, 870]
+        }]
+      })
+    }
+  }
+}
+</script>

+ 184 - 0
src/components/Charts/brokenLineChart.vue

@@ -0,0 +1,184 @@
+<template>
+  <div :id="id" :class="className" :style="{height:height,width:width}" />
+</template>
+
+<script>
+import echarts from 'echarts'
+require('echarts/theme/macarons') // echarts theme
+import resize from './mixins/resize'
+
+export default {
+  name: 'BrokenLineChart',
+  mixins: [resize],
+  props: {
+    chartTitle: {
+      type: String,
+      default: 'TITLE'
+    },
+    className: {
+      type: String,
+      default: 'broken-line-chart'
+    },
+    id: {
+      type: String,
+      default: 'broken-line--chart'
+    },
+    width: {
+      type: String,
+      default: '100%'
+    },
+    height: {
+      type: String,
+      default: '500px'
+    },
+    xData: {
+      type: Array,
+      default: Array[1]
+    },
+    yOneData: {
+      type: Array,
+      default: Array[10]
+    },
+    yTwoData: {
+      type: Array,
+      default: Array[12]
+    },
+    yThreeData: {
+      type: Array,
+      default: Array[14]
+    }
+  },
+  data() {
+    return {
+      chart: null
+    }
+  },
+  mounted() {
+    this.initChart()
+  },
+  beforeDestroy() {
+    if (!this.chart) {
+      return
+    }
+    this.chart.dispose()
+    this.chart = null
+  },
+  methods: {
+    initChart() {
+      this.chart = echarts.init(document.getElementById(this.id), 'macarons')
+      // const xData = (function() {
+      //   const data = []
+      //   for (let i = 1; i < 13; i++) {
+      //     data.push(i + '月')
+      //   }
+      //   return data
+      // }())
+      this.chart.setOption({
+        backgroundColor: '#FFFFFF', // 背景色
+        title: {
+          text: this.chartTitle
+        },
+        tooltip: {
+          trigger: 'axis',
+          axisPointer: { // 坐标轴指示器,坐标轴触发有效
+            type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
+          }
+        },
+        toolbox: {
+          feature: {
+            dataView: {
+              show: true,
+              readOnly: false
+            },
+            restore: {
+              show: true
+            },
+            saveAsImage: {
+              show: true
+            }
+          }
+        },
+        legend: {
+          top: 24,
+          data: ['支付宝', '微信', '现金']
+        },
+        grid: {
+          left: '3%',
+          right: '8%',
+          borderWidth: 0,
+          top: '13%',
+          bottom: '10%',
+          containLabel: true
+        },
+
+        xAxis: {
+          type: 'category',
+          boundaryGap: false, // 坐标轴两边留白策略
+          splitLine: { // 网格线 x轴对应的是否显示
+            show: false
+          },
+          data: []
+        },
+
+        yAxis: {
+          type: 'value',
+          name: '支付次数(次)',
+          // min: 70,
+          // max: 100,
+          // interval: 10,
+          splitLine: { // 网格线 y轴对应的是否显示
+            show: false
+          }
+        },
+
+        dataZoom: [{
+          show: true,
+          height: 20,
+          xAxisIndex: [
+            0
+          ],
+          bottom: 30,
+          start: 0,
+          end: 100,
+          handleIcon: 'path://M306.1,413c0,2.2-1.8,4-4,4h-59.8c-2.2,0-4-1.8-4-4V200.8c0-2.2,1.8-4,4-4h59.8c2.2,0,4,1.8,4,4V413z',
+          handleSize: '110%',
+          handleStyle: {
+            color: '#d3dee5'
+
+          },
+          textStyle: {
+            color: '#000' },
+          borderColor: '#90979c'
+
+        }, {
+          type: 'inside',
+          show: true,
+          height: 15,
+          start: 1,
+          end: 35
+        }],
+
+        series: [{
+          name: '支付宝',
+          type: 'line',
+          data: []
+          // data: [88.29, 83.68, 89.64, 90.47, 90.21, 93.63, 94.07, 90.85, 90.32, 90.56, 86.69, 81.77]
+        },
+        {
+          name: '微信',
+          type: 'line',
+          data: []
+          // data: [90.36, 86.21, 92.04, 89.91, 90.15, 90.38, 88.03, 88.99, 88.35, 87.18, 86.29, 81.23]
+        },
+        {
+          name: '现金',
+          type: 'line',
+          data: []
+          // data: [91.36, 76.21, 79.04, 99.91, 94.15, 97.38, 87.03, 98.99, 89.35, 77.18, 76.29, 91.23]
+        }
+        ]
+      })
+    }
+  }
+}
+</script>

+ 217 - 0
src/components/Charts/doubleYAxisChart.vue

@@ -0,0 +1,217 @@
+<template>
+  <div :id="id" :class="className" :style="{height:height,width:width}" />
+</template>
+
+<script>
+import echarts from 'echarts'
+require('echarts/theme/macarons') // echarts theme
+import resize from './mixins/resize'
+
+export default {
+  name: 'DoubleYAxisChart',
+  mixins: [resize],
+  props: {
+    chartTitle: {
+      type: String,
+      default: 'TITLE'
+    },
+    className: {
+      type: String,
+      default: 'double-y-axis'
+    },
+    id: {
+      type: String,
+      default: 'double-y-axis'
+    },
+    width: {
+      type: String,
+      default: '100%'
+    },
+    height: {
+      type: String,
+      default: '500px'
+    },
+    xData: {
+      type: Array,
+      default: Array[1]
+    },
+    yOneData: {
+      type: Array,
+      default: Array[10]
+    },
+    yTwoData: {
+      type: Array,
+      default: Array[12]
+    }
+  },
+  data() {
+    return {
+      chart: null
+    }
+  },
+  mounted() {
+    this.initChart()
+  },
+  beforeDestroy() {
+    if (!this.chart) {
+      return
+    }
+    this.chart.dispose()
+    this.chart = null
+  },
+  methods: {
+    initChart() {
+      this.chart = echarts.init(document.getElementById(this.id), 'macarons')
+      // const xData = (function() {
+      //   const data = []
+      //   for (let i = 1; i < 13; i++) {
+      //     data.push(i + '月')
+      //   }
+      //   return data
+      // }())
+      this.chart.setOption({
+        backgroundColor: '#FFFFFF',
+        title: {
+          text: this.chartTitle
+        },
+        tooltip: {
+          trigger: 'axis',
+          axisPointer: { // 坐标轴指示器,坐标轴触发有效
+            type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
+          }
+        },
+        toolbox: {
+          feature: {
+            dataView: {
+              show: true,
+              readOnly: false
+            },
+            restore: {
+              show: true
+            },
+            saveAsImage: {
+              show: true
+            }
+          }
+        },
+        legend: {
+          top: 24,
+          data: ['订单金额', '订单数']
+        },
+        grid: {
+          left: '3%',
+          right: '8%',
+          borderWidth: 0,
+          top: '13%',
+          bottom: '10%',
+          containLabel: true
+        },
+        xAxis: [{
+          type: 'category',
+          data: []
+        }],
+        yAxis: [{
+          name: '元(¥)',
+          type: 'value',
+          // name: '投诉举报数',
+          axisLabel: {
+            formatter: '{value}'
+          }
+        },
+        {
+          name: '数量(个)',
+          type: 'value',
+          // min: 0,
+          // max: 25,
+          // interval: 5,
+          axisLabel: {
+            formatter: '{value}'
+          }
+        }],
+        dataZoom: [{
+          show: true,
+          height: 20,
+          xAxisIndex: [
+            0
+          ],
+          bottom: 30,
+          start: 0,
+          end: 100,
+          handleIcon: 'path://M306.1,413c0,2.2-1.8,4-4,4h-59.8c-2.2,0-4-1.8-4-4V200.8c0-2.2,1.8-4,4-4h59.8c2.2,0,4,1.8,4,4V413z',
+          handleSize: '110%',
+          handleStyle: {
+            color: '#d3dee5'
+
+          },
+          textStyle: {
+            color: '#000' },
+          borderColor: '#90979c'
+
+        }, {
+          type: 'inside',
+          show: true,
+          height: 15,
+          start: 1,
+          end: 35
+        }],
+        series: [{
+          name: '订单金额',
+          type: 'bar',
+          itemStyle: {
+            normal: {
+              // color: '#01949B'
+            }
+          },
+          markPoint: {
+            data: [{
+              type: 'max',
+              name: '最大值'
+            },
+            {
+              type: 'min',
+              name: '最小值'
+            }
+            ]
+          },
+          markLine: {
+            data: [{
+              type: 'average',
+              name: '平均值'
+            }]
+          },
+          data: []
+          // data: [2031, 1793, 3640, 2593, 4377, 3201, 2275, 3289, 3356, 2859, 4244, 3945]
+        }, {
+          name: '订单数',
+          type: 'bar',
+          yAxisIndex: 1,
+          itemStyle: {
+            normal: {
+              // color: '#EBA954'
+            }
+          },
+          markPoint: {
+            data: [{
+              type: 'max',
+              name: '最大值'
+            },
+            {
+              type: 'min',
+              name: '最小值'
+            }
+            ]
+          },
+          markLine: {
+            data: [{
+              type: 'average',
+              name: '平均值'
+            }]
+          },
+          data: []
+          // data: [10, 14, 19, 13, 21, 18, 9, 15, 11, 14, 23, 22]
+        }]
+      })
+    }
+  }
+}
+</script>

+ 272 - 0
src/components/Charts/mixChart.vue

@@ -0,0 +1,272 @@
+<template>
+  <div :id="id" :class="className" :style="{height:height,width:width}" />
+</template>
+
+<script>
+import echarts from 'echarts'
+import resize from './mixins/resize'
+
+export default {
+  name: 'MixChart',
+  mixins: [resize],
+  props: {
+    className: {
+      type: String,
+      default: 'mix-chart'
+    },
+    id: {
+      type: String,
+      default: 'mix-chart'
+    },
+    width: {
+      type: String,
+      default: '100%'
+    },
+    height: {
+      type: String,
+      default: '500px'
+    }
+  },
+  data() {
+    return {
+      chart: null
+    }
+  },
+  mounted() {
+    this.initChart()
+  },
+  beforeDestroy() {
+    if (!this.chart) {
+      return
+    }
+    this.chart.dispose()
+    this.chart = null
+  },
+  methods: {
+    initChart() {
+      this.chart = echarts.init(document.getElementById(this.id))
+      const xData = (function() {
+        const data = []
+        for (let i = 1; i < 13; i++) {
+          data.push(i + 'month')
+        }
+        return data
+      }())
+      this.chart.setOption({
+        backgroundColor: '#344b58',
+        title: {
+          text: '营收统计',
+          x: '20',
+          top: '20',
+          textStyle: {
+            color: '#fff',
+            fontSize: '22'
+          },
+          subtextStyle: {
+            color: '#90979c',
+            fontSize: '16'
+          }
+        },
+        tooltip: {
+          trigger: 'axis',
+          axisPointer: {
+            textStyle: {
+              color: '#fff'
+            }
+          }
+        },
+        grid: {
+          left: '5%',
+          right: '5%',
+          borderWidth: 0,
+          top: 150,
+          bottom: 95,
+          textStyle: {
+            color: '#fff'
+          }
+        },
+        legend: {
+          x: '5%',
+          top: '10%',
+          textStyle: {
+            color: '#90979c'
+          },
+          data: ['female', 'male', 'average']
+        },
+        calculable: true,
+        xAxis: [{
+          type: 'category',
+          axisLine: {
+            lineStyle: {
+              color: '#90979c'
+            }
+          },
+          splitLine: {
+            show: false
+          },
+          axisTick: {
+            show: false
+          },
+          splitArea: {
+            show: false
+          },
+          axisLabel: {
+            interval: 0
+
+          },
+          data: xData
+        }],
+        yAxis: [{
+          type: 'value',
+          splitLine: {
+            show: false
+          },
+          axisLine: {
+            lineStyle: {
+              color: '#90979c'
+            }
+          },
+          axisTick: {
+            show: false
+          },
+          axisLabel: {
+            interval: 0
+          },
+          splitArea: {
+            show: false
+          }
+        }],
+        dataZoom: [{
+          show: true,
+          height: 30,
+          xAxisIndex: [
+            0
+          ],
+          bottom: 30,
+          start: 10,
+          end: 80,
+          handleIcon: 'path://M306.1,413c0,2.2-1.8,4-4,4h-59.8c-2.2,0-4-1.8-4-4V200.8c0-2.2,1.8-4,4-4h59.8c2.2,0,4,1.8,4,4V413z',
+          handleSize: '110%',
+          handleStyle: {
+            color: '#d3dee5'
+
+          },
+          textStyle: {
+            color: '#fff' },
+          borderColor: '#90979c'
+
+        }, {
+          type: 'inside',
+          show: true,
+          height: 15,
+          start: 1,
+          end: 35
+        }],
+        series: [{
+          name: 'female',
+          type: 'bar',
+          stack: 'total',
+          barMaxWidth: 35,
+          barGap: '10%',
+          itemStyle: {
+            normal: {
+              color: 'rgba(255,144,128,1)',
+              label: {
+                show: true,
+                textStyle: {
+                  color: '#fff'
+                },
+                position: 'insideTop',
+                formatter(p) {
+                  return p.value > 0 ? p.value : ''
+                }
+              }
+            }
+          },
+          data: [
+            709,
+            1917,
+            2455,
+            2610,
+            1719,
+            1433,
+            1544,
+            3285,
+            5208,
+            3372,
+            2484,
+            4078
+          ]
+        },
+
+        {
+          name: 'male',
+          type: 'bar',
+          stack: 'total',
+          itemStyle: {
+            normal: {
+              color: 'rgba(0,191,183,1)',
+              barBorderRadius: 0,
+              label: {
+                show: true,
+                position: 'top',
+                formatter(p) {
+                  return p.value > 0 ? p.value : ''
+                }
+              }
+            }
+          },
+          data: [
+            327,
+            1776,
+            507,
+            1200,
+            800,
+            482,
+            204,
+            1390,
+            1001,
+            951,
+            381,
+            220
+          ]
+        }, {
+          name: 'average',
+          type: 'line',
+          stack: 'total',
+          symbolSize: 10,
+          symbol: 'circle',
+          itemStyle: {
+            normal: {
+              color: 'rgba(252,230,48,1)',
+              barBorderRadius: 0,
+              label: {
+                show: true,
+                position: 'top',
+                formatter(p) {
+                  return p.value > 0 ? p.value : ''
+                }
+              }
+            }
+          },
+          data: [
+            1036,
+            3693,
+            2962,
+            3810,
+            2519,
+            1915,
+            1748,
+            4675,
+            6209,
+            4323,
+            2865,
+            4298
+          ]
+        }
+        ]
+      })
+    }
+  }
+}
+</script>

+ 34 - 0
src/components/Charts/mixins/resize.js

@@ -0,0 +1,34 @@
+import { debounce } from '@/utils'
+
+export default {
+  data() {
+    return {
+      $_sidebarElm: null
+    }
+  },
+  mounted() {
+    this.__resizeHandler = debounce(() => {
+      if (this.chart) {
+        this.chart.resize()
+      }
+    }, 100)
+    window.addEventListener('resize', this.__resizeHandler)
+
+    this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0]
+    this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)
+  },
+  beforeDestroy() {
+    window.removeEventListener('resize', this.__resizeHandler)
+
+    this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)
+  },
+  methods: {
+    // use $_ for mixins properties
+    // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
+    $_sidebarResizeHandler(e) {
+      if (e.propertyName === 'width') {
+        this.__resizeHandler()
+      }
+    }
+  }
+}

+ 94 - 0
src/components/DishesCard/index.vue

@@ -0,0 +1,94 @@
+<template>
+  <div>
+    <div v-for="(item, index) in list" :key="index" class="block nowrap" style="width:160px;">
+      <el-card :body-style="{ padding: '0px' }" style="width:150px; height:230px; margin-top:10px;">
+        <!-- <img v-if="item.image" :src="item.image" class="image">
+        <img v-else src="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png" class="image"> -->
+        <el-image v-if="item.image" style="width:150px; height:150px" :src="item.image">
+          <div slot="error" class="image-slot image-display">
+            <i class="el-icon-picture-outline" />
+          </div>
+        </el-image>
+        <el-image v-else style="width:150px; height:150px" src="http://hbimg.huabanimg.com/6d84060bdff92fad071278ec9aa72b52aac49ac2b3ff-Ba8AUJ_fw658" />
+        <div style="padding: 14px;">
+          <span>{{ item.name }}</span>
+          <div class="bottom clearfix">
+            <time class="time">¥{{ item.price | exactDecimalPoint(2) }}/{{ item.unit_name }}</time>
+            <el-button v-permission="['income_change_dishes']" type="text" class="button" @click="editDishes(item)">编辑</el-button>
+          </div>
+        </div>
+      </el-card>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'DishesCard',
+  filters: {
+    exactDecimalPoint: function(value, num) {
+      return Number(value).toFixed(num)
+    }
+  },
+  props: {
+    list: {
+      type: Array,
+      default: null
+    }
+  },
+  data() {
+    return {
+    }
+  },
+  methods: {
+    editDishes(val) {
+      this.$emit('editDishes', val)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  .image-display {
+    font-size: 30px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    background: #f5f7fa;
+    color: #909399;
+  }
+
+  .nowrap {
+    display: inline-block;
+  }
+
+  .time {
+    font-size: 13px;
+    color: #999;
+  }
+
+  .bottom {
+    margin-top: 13px;
+    line-height: 12px;
+  }
+
+  .button {
+    padding: 0;
+    float: right;
+  }
+
+  .image {
+    width: 100%;
+    display: block;
+  }
+
+  .clearfix:before,
+  .clearfix:after {
+      display: table;
+      content: "";
+  }
+
+  .clearfix:after {
+      clear: both
+  }
+</style>

+ 68 - 0
src/components/ErrorLog/index.vue

@@ -0,0 +1,68 @@
+<template>
+  <div v-if="errorLogs.length>0">
+    <el-badge :is-dot="true" style="line-height: 25px;margin-top: -5px;" @click.native="dialogTableVisible=true">
+      <el-button style="padding: 8px 10px;" size="small" type="danger">
+        <svg-icon icon-class="bug" />
+      </el-button>
+    </el-badge>
+
+    <el-dialog :visible.sync="dialogTableVisible" title="Error Log" width="80%" append-to-body>
+      <el-table :data="errorLogs" border>
+        <el-table-column label="Message">
+          <template slot-scope="{row}">
+            <div>
+              <span class="message-title">Msg:</span>
+              <el-tag type="danger">
+                {{ row.err.message }}
+              </el-tag>
+            </div>
+            <br>
+            <div>
+              <span class="message-title" style="padding-right: 10px;">Info: </span>
+              <el-tag type="warning">
+                {{ row.vm.$vnode.tag }} error in {{ row.info }}
+              </el-tag>
+            </div>
+            <br>
+            <div>
+              <span class="message-title" style="padding-right: 16px;">Url: </span>
+              <el-tag type="success">
+                {{ row.url }}
+              </el-tag>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="Stack">
+          <template slot-scope="scope">
+            {{ scope.row.err.stack }}
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'ErrorLog',
+  data() {
+    return {
+      dialogTableVisible: false
+    }
+  },
+  computed: {
+    errorLogs() {
+      return this.$store.getters.errorLogs
+    }
+  }
+}
+</script>
+
+<style scoped>
+.message-title {
+  font-size: 16px;
+  color: #333;
+  font-weight: bold;
+  padding-right: 8px;
+}
+</style>

+ 54 - 0
src/components/GithubCorner/index.vue

@@ -0,0 +1,54 @@
+<template>
+  <a href="https://github.com/PanJiaChen/vue-element-admin" target="_blank" class="github-corner" aria-label="View source on Github">
+    <svg
+      width="80"
+      height="80"
+      viewBox="0 0 250 250"
+      style="fill:#40c9c6; color:#fff;"
+      aria-hidden="true"
+    >
+      <path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z" />
+      <path
+        d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
+        fill="currentColor"
+        style="transform-origin: 130px 106px;"
+        class="octo-arm"
+      />
+      <path
+        d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
+        fill="currentColor"
+        class="octo-body"
+      />
+    </svg>
+  </a>
+</template>
+
+<style scoped>
+.github-corner:hover .octo-arm {
+  animation: octocat-wave 560ms ease-in-out
+}
+
+@keyframes octocat-wave {
+  0%,
+  100% {
+    transform: rotate(0)
+  }
+  20%,
+  60% {
+    transform: rotate(-25deg)
+  }
+  40%,
+  80% {
+    transform: rotate(10deg)
+  }
+}
+
+@media (max-width:500px) {
+  .github-corner:hover .octo-arm {
+    animation: none
+  }
+  .github-corner .octo-arm {
+    animation: octocat-wave 560ms ease-in-out
+  }
+}
+</style>

+ 44 - 0
src/components/Hamburger/index.vue

@@ -0,0 +1,44 @@
+<template>
+  <div style="padding: 0 15px;" @click="toggleClick">
+    <svg
+      :class="{'is-active':isActive}"
+      class="hamburger"
+      viewBox="0 0 1024 1024"
+      xmlns="http://www.w3.org/2000/svg"
+      width="64"
+      height="64"
+    >
+      <path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
+    </svg>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'Hamburger',
+  props: {
+    isActive: {
+      type: Boolean,
+      default: false
+    }
+  },
+  methods: {
+    toggleClick() {
+      this.$emit('toggleClick')
+    }
+  }
+}
+</script>
+
+<style scoped>
+.hamburger {
+  display: inline-block;
+  vertical-align: middle;
+  width: 20px;
+  height: 20px;
+}
+
+.hamburger.is-active {
+  transform: rotate(180deg);
+}
+</style>

+ 1421 - 0
src/components/ImageCropper/index.vue

@@ -0,0 +1,1421 @@
+<template>
+  <div v-show="value" class="vue-image-crop-upload">
+    <div class="vicp-wrap">
+      <div class="vicp-close" @click="off">
+        <i class="vicp-icon4" />
+      </div>
+
+      <div v-show="step == 1" class="vicp-step1">
+        <div class="vicp-drop-area" @dragleave="preventDefault" @dragover="preventDefault" @dragenter="preventDefault" @click="handleClick" @drop="handleChange">
+          <i v-show="loading != 1" class="vicp-icon1">
+            <i class="vicp-icon1-arrow" />
+            <i class="vicp-icon1-body" />
+            <i class="vicp-icon1-bottom" />
+          </i>
+          <span v-show="loading !== 1" class="vicp-hint">{{ lang.hint }}</span>
+          <span v-show="!isSupported" class="vicp-no-supported-hint">{{ lang.noSupported }}</span>
+          <input v-show="false" v-if="step == 1" ref="fileinput" type="file" @change="handleChange">
+        </div>
+        <div v-show="hasError" class="vicp-error">
+          <i class="vicp-icon2" /> {{ errorMsg }}
+        </div>
+        <div class="vicp-operate">
+          <a @click="off" @mousedown="ripple">{{ lang.btn.off }}</a>
+        </div>
+      </div>
+
+      <div v-if="step == 2" class="vicp-step2">
+        <div class="vicp-crop">
+          <div v-show="true" class="vicp-crop-left">
+            <div class="vicp-img-container">
+              <img
+                ref="img"
+                :src="sourceImgUrl"
+                :style="sourceImgStyle"
+                class="vicp-img"
+                draggable="false"
+                @drag="preventDefault"
+                @dragstart="preventDefault"
+                @dragend="preventDefault"
+                @dragleave="preventDefault"
+                @dragover="preventDefault"
+                @dragenter="preventDefault"
+                @drop="preventDefault"
+                @touchstart="imgStartMove"
+                @touchmove="imgMove"
+                @touchend="createImg"
+                @touchcancel="createImg"
+                @mousedown="imgStartMove"
+                @mousemove="imgMove"
+                @mouseup="createImg"
+                @mouseout="createImg"
+              >
+              <div :style="sourceImgShadeStyle" class="vicp-img-shade vicp-img-shade-1" />
+              <div :style="sourceImgShadeStyle" class="vicp-img-shade vicp-img-shade-2" />
+            </div>
+
+            <div class="vicp-range">
+              <input :value="scale.range" type="range" step="1" min="0" max="100" @input="zoomChange">
+              <i class="vicp-icon5" @mousedown="startZoomSub" @mouseout="endZoomSub" @mouseup="endZoomSub" />
+              <i class="vicp-icon6" @mousedown="startZoomAdd" @mouseout="endZoomAdd" @mouseup="endZoomAdd" />
+            </div>
+
+            <div v-if="!noRotate" class="vicp-rotate">
+              <i @mousedown="startRotateLeft" @mouseout="endRotate" @mouseup="endRotate">↺</i>
+              <i @mousedown="startRotateRight" @mouseout="endRotate" @mouseup="endRotate">↻</i>
+            </div>
+          </div>
+          <div v-show="true" class="vicp-crop-right">
+            <div class="vicp-preview">
+              <div v-if="!noSquare" class="vicp-preview-item">
+                <img :src="createImgUrl" :style="previewStyle">
+                <span>{{ lang.preview }}</span>
+              </div>
+              <div v-if="!noCircle" class="vicp-preview-item vicp-preview-item-circle">
+                <img :src="createImgUrl" :style="previewStyle">
+                <span>{{ lang.preview }}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="vicp-operate">
+          <a @click="setStep(1)" @mousedown="ripple">{{ lang.btn.back }}</a>
+          <a class="vicp-operate-btn" @click="prepareUpload" @mousedown="ripple">{{ lang.btn.save }}</a>
+        </div>
+      </div>
+
+      <div v-if="step == 3" class="vicp-step3">
+        <div class="vicp-upload">
+          <span v-show="loading === 1" class="vicp-loading">{{ lang.loading }}</span>
+          <div class="vicp-progress-wrap">
+            <span v-show="loading === 1" :style="progressStyle" class="vicp-progress" />
+          </div>
+          <div v-show="hasError" class="vicp-error">
+            <i class="vicp-icon2" /> {{ errorMsg }}
+          </div>
+          <div v-show="loading === 2" class="vicp-success">
+            <i class="vicp-icon3" /> {{ lang.success }}
+          </div>
+        </div>
+        <div class="vicp-operate">
+          <a @click="setStep(2)" @mousedown="ripple">{{ lang.btn.back }}</a>
+          <a @click="off" @mousedown="ripple">{{ lang.btn.close }}</a>
+        </div>
+      </div>
+      <canvas v-show="false" ref="canvas" :width="width" :height="height" />
+    </div>
+  </div>
+</template>
+
+<script>
+/* eslint-disable */
+'use strict'
+import request from '@/utils/request'
+import language from './utils/language.js'
+import mimes from './utils/mimes.js'
+import data2blob from './utils/data2blob.js'
+import effectRipple from './utils/effectRipple.js'
+export default {
+  props: {
+    // 域,上传文件name,触发事件会带上(如果一个页面多个图片上传控件,可以做区分
+    field: {
+      type: String,
+      'default': 'avatar'
+    },
+    // 原名key,类似于id,触发事件会带上(如果一个页面多个图片上传控件,可以做区分
+    ki: {
+      'default': 0
+    },
+    // 显示该控件与否
+    value: {
+      'default': true
+    },
+    // 上传地址
+    url: {
+      type: String,
+      'default': ''
+    },
+    // 其他要上传文件附带的数据,对象格式
+    params: {
+      type: Object,
+      'default': null
+    },
+    // Add custom headers
+    headers: {
+      type: Object,
+      'default': null
+    },
+    // 剪裁图片的宽
+    width: {
+      type: Number,
+      default: 200
+    },
+    // 剪裁图片的高
+    height: {
+      type: Number,
+      default: 200
+    },
+    // 不显示旋转功能
+    noRotate: {
+      type: Boolean,
+      default: true
+    },
+    // 不预览圆形图片
+    noCircle: {
+      type: Boolean,
+      default: false
+    },
+    // 不预览方形图片
+    noSquare: {
+      type: Boolean,
+      default: false
+    },
+    // 单文件大小限制
+    maxSize: {
+      type: Number,
+      'default': 10240
+    },
+    // 语言类型
+    langType: {
+      type: String,
+      'default': 'zh'
+    },
+    // 语言包
+    langExt: {
+      type: Object,
+      'default': null
+    },
+    // 图片上传格式
+    imgFormat: {
+      type: String,
+      'default': 'png'
+    },
+    // 是否支持跨域
+    withCredentials: {
+      type: Boolean,
+      'default': false
+    }
+  },
+  data() {
+    const that = this
+    const {
+      imgFormat,
+      langType,
+      langExt,
+      width,
+      height
+    } = that
+    let isSupported = true
+    const allowImgFormat = [
+      'jpg',
+      'png'
+    ]
+    const tempImgFormat = allowImgFormat.indexOf(imgFormat) === -1 ? 'jpg' : imgFormat
+    const lang = language[langType] ? language[langType] : language['en']
+    const mime = mimes[tempImgFormat]
+    // 规范图片格式
+    that.imgFormat = tempImgFormat
+    if (langExt) {
+      Object.assign(lang, langExt)
+    }
+    if (typeof FormData !== 'function') {
+      isSupported = false
+    }
+    return {
+      // 图片的mime
+      mime,
+      // 语言包
+      lang,
+      // 浏览器是否支持该控件
+      isSupported,
+      // 浏览器是否支持触屏事件
+      isSupportTouch: document.hasOwnProperty('ontouchstart'),
+      // 步骤
+      step: 1, // 1选择文件 2剪裁 3上传
+      // 上传状态及进度
+      loading: 0, // 0未开始 1正在 2成功 3错误
+      progress: 0,
+      // 是否有错误及错误信息
+      hasError: false,
+      errorMsg: '',
+      // 需求图宽高比
+      ratio: width / height,
+      // 原图地址、生成图片地址
+      sourceImg: null,
+      sourceImgUrl: '',
+      createImgUrl: '',
+      // 原图片拖动事件初始值
+      sourceImgMouseDown: {
+        on: false,
+        mX: 0, // 鼠标按下的坐标
+        mY: 0,
+        x: 0, // scale原图坐标
+        y: 0
+      },
+      // 生成图片预览的容器大小
+      previewContainer: {
+        width: 100,
+        height: 100
+      },
+      // 原图容器宽高
+      sourceImgContainer: { // sic
+        width: 240,
+        height: 184 // 如果生成图比例与此一致会出现bug,先改成特殊的格式吧,哈哈哈
+      },
+      // 原图展示属性
+      scale: {
+        zoomAddOn: false, // 按钮缩放事件开启
+        zoomSubOn: false, // 按钮缩放事件开启
+        range: 1, // 最大100
+        rotateLeft: false, // 按钮向左旋转事件开启
+        rotateRight: false, // 按钮向右旋转事件开启
+        degree: 0, // 旋转度数
+        x: 0,
+        y: 0,
+        width: 0,
+        height: 0,
+        maxWidth: 0,
+        maxHeight: 0,
+        minWidth: 0, // 最宽
+        minHeight: 0,
+        naturalWidth: 0, // 原宽
+        naturalHeight: 0
+      }
+    }
+  },
+  computed: {
+    // 进度条样式
+    progressStyle() {
+      const {
+        progress
+      } = this
+      return {
+        width: progress + '%'
+      }
+    },
+    // 原图样式
+    sourceImgStyle() {
+      const {
+        scale,
+        sourceImgMasking
+      } = this
+      const top = scale.y + sourceImgMasking.y + 'px'
+      const left = scale.x + sourceImgMasking.x + 'px'
+      return {
+        top,
+        left,
+        width: scale.width + 'px',
+        height: scale.height + 'px',
+        transform: 'rotate(' + scale.degree + 'deg)', // 旋转时 左侧原始图旋转样式
+        '-ms-transform': 'rotate(' + scale.degree + 'deg)', // 兼容IE9
+        '-moz-transform': 'rotate(' + scale.degree + 'deg)', // 兼容FireFox
+        '-webkit-transform': 'rotate(' + scale.degree + 'deg)', // 兼容Safari 和 chrome
+        '-o-transform': 'rotate(' + scale.degree + 'deg)' // 兼容 Opera
+      }
+    },
+    // 原图蒙版属性
+    sourceImgMasking() {
+      const {
+        width,
+        height,
+        ratio,
+        sourceImgContainer
+      } = this
+      const sic = sourceImgContainer
+      const sicRatio = sic.width / sic.height // 原图容器宽高比
+      let x = 0
+      let y = 0
+      let w = sic.width
+      let h = sic.height
+      let scale = 1
+      if (ratio < sicRatio) {
+        scale = sic.height / height
+        w = sic.height * ratio
+        x = (sic.width - w) / 2
+      }
+      if (ratio > sicRatio) {
+        scale = sic.width / width
+        h = sic.width / ratio
+        y = (sic.height - h) / 2
+      }
+      return {
+        scale, // 蒙版相对需求宽高的缩放
+        x,
+        y,
+        width: w,
+        height: h
+      }
+    },
+    // 原图遮罩样式
+    sourceImgShadeStyle() {
+      const {
+        sourceImgMasking,
+        sourceImgContainer
+      } = this
+      const sic = sourceImgContainer
+      const sim = sourceImgMasking
+      const w = sim.width == sic.width ? sim.width : (sic.width - sim.width) / 2
+      const h = sim.height == sic.height ? sim.height : (sic.height - sim.height) / 2
+      return {
+        width: w + 'px',
+        height: h + 'px'
+      }
+    },
+    previewStyle() {
+      const {
+        width,
+        height,
+        ratio,
+        previewContainer
+      } = this
+      const pc = previewContainer
+      let w = pc.width
+      let h = pc.height
+      const pcRatio = w / h
+      if (ratio < pcRatio) {
+        w = pc.height * ratio
+      }
+      if (ratio > pcRatio) {
+        h = pc.width / ratio
+      }
+      return {
+        width: w + 'px',
+        height: h + 'px'
+      }
+    }
+  },
+  watch: {
+    value(newValue) {
+      if (newValue && this.loading != 1) {
+        this.reset()
+      }
+    }
+  },
+  methods: {
+    // 点击波纹效果
+    ripple(e) {
+      effectRipple(e)
+    },
+    // 关闭控件
+    off() {
+      setTimeout(() => {
+        this.$emit('input', false)
+        this.$emit('close')
+        if (this.step == 3 && this.loading == 2) {
+          this.setStep(1)
+        }
+      }, 200)
+    },
+    // 设置步骤
+    setStep(no) {
+      // 延时是为了显示动画效果呢,哈哈哈
+      setTimeout(() => {
+        this.step = no
+      }, 200)
+    },
+    /* 图片选择区域函数绑定
+     ---------------------------------------------------------------*/
+    preventDefault(e) {
+      e.preventDefault()
+      return false
+    },
+    handleClick(e) {
+      if (this.loading !== 1) {
+        if (e.target !== this.$refs.fileinput) {
+          e.preventDefault()
+          if (document.activeElement !== this.$refs) {
+            this.$refs.fileinput.click()
+          }
+        }
+      }
+    },
+    handleChange(e) {
+      e.preventDefault()
+      if (this.loading !== 1) {
+        const files = e.target.files || e.dataTransfer.files
+        this.reset()
+        if (this.checkFile(files[0])) {
+          this.setSourceImg(files[0])
+        }
+      }
+    },
+    /* ---------------------------------------------------------------*/
+    // 检测选择的文件是否合适
+    checkFile(file) {
+      let that = this,
+        {
+          lang,
+          maxSize
+        } = that
+      // 仅限图片
+      if (file.type.indexOf('image') === -1) {
+        that.hasError = true
+        that.errorMsg = lang.error.onlyImg
+        return false
+      }
+      // 超出大小
+      if (file.size / 1024 > maxSize) {
+        that.hasError = true
+        that.errorMsg = lang.error.outOfSize + maxSize + 'kb'
+        return false
+      }
+      return true
+    },
+    // 重置控件
+    reset() {
+      const that = this
+      that.loading = 0
+      that.hasError = false
+      that.errorMsg = ''
+      that.progress = 0
+    },
+    // 设置图片源
+    setSourceImg(file) {
+      let that = this,
+        fr = new FileReader()
+      fr.onload = function(e) {
+        that.sourceImgUrl = fr.result
+        that.startCrop()
+      }
+      fr.readAsDataURL(file)
+    },
+    // 剪裁前准备工作
+    startCrop() {
+      let that = this,
+        {
+          width,
+          height,
+          ratio,
+          scale,
+          sourceImgUrl,
+          sourceImgMasking,
+          lang
+        } = that,
+        sim = sourceImgMasking,
+        img = new Image()
+      img.src = sourceImgUrl
+      img.onload = function() {
+        let nWidth = img.naturalWidth,
+          nHeight = img.naturalHeight,
+          nRatio = nWidth / nHeight,
+          w = sim.width,
+          h = sim.height,
+          x = 0,
+          y = 0
+        // 图片像素不达标
+        if (nWidth < width || nHeight < height) {
+          that.hasError = true
+          that.errorMsg = lang.error.lowestPx + width + '*' + height
+          return false
+        }
+        if (ratio > nRatio) {
+          h = w / nRatio
+          y = (sim.height - h) / 2
+        }
+        if (ratio < nRatio) {
+          w = h * nRatio
+          x = (sim.width - w) / 2
+        }
+        scale.range = 0
+        scale.x = x
+        scale.y = y
+        scale.width = w
+        scale.height = h
+        scale.degree = 0
+        scale.minWidth = w
+        scale.minHeight = h
+        scale.maxWidth = nWidth * sim.scale
+        scale.maxHeight = nHeight * sim.scale
+        scale.naturalWidth = nWidth
+        scale.naturalHeight = nHeight
+        that.sourceImg = img
+        that.createImg()
+        that.setStep(2)
+      }
+    },
+    // 鼠标按下图片准备移动
+    imgStartMove(e) {
+      e.preventDefault()
+      // 支持触摸事件,则鼠标事件无效
+      if (this.isSupportTouch && !e.targetTouches) {
+        return false
+      }
+      let et = e.targetTouches ? e.targetTouches[0] : e,
+        {
+          sourceImgMouseDown,
+          scale
+        } = this,
+        simd = sourceImgMouseDown
+      simd.mX = et.screenX
+      simd.mY = et.screenY
+      simd.x = scale.x
+      simd.y = scale.y
+      simd.on = true
+    },
+    // 鼠标按下状态下移动,图片移动
+    imgMove(e) {
+      e.preventDefault()
+      // 支持触摸事件,则鼠标事件无效
+      if (this.isSupportTouch && !e.targetTouches) {
+        return false
+      }
+      let et = e.targetTouches ? e.targetTouches[0] : e,
+        {
+          sourceImgMouseDown: {
+            on,
+            mX,
+            mY,
+            x,
+            y
+          },
+          scale,
+          sourceImgMasking
+        } = this,
+        sim = sourceImgMasking,
+        nX = et.screenX,
+        nY = et.screenY,
+        dX = nX - mX,
+        dY = nY - mY,
+        rX = x + dX,
+        rY = y + dY
+      if (!on) return
+      if (rX > 0) {
+        rX = 0
+      }
+      if (rY > 0) {
+        rY = 0
+      }
+      if (rX < sim.width - scale.width) {
+        rX = sim.width - scale.width
+      }
+      if (rY < sim.height - scale.height) {
+        rY = sim.height - scale.height
+      }
+      scale.x = rX
+      scale.y = rY
+    },
+     // 按钮按下开始向右旋转
+    startRotateRight(e) {
+      let that = this,
+        {
+          scale
+        } = that
+      scale.rotateRight = true
+      function rotate() {
+        if (scale.rotateRight) {
+          const degree = ++scale.degree
+          that.createImg(degree)
+          setTimeout(function() {
+            rotate()
+          }, 60)
+        }
+      }
+      rotate()
+    },
+    // 按钮按下开始向右旋转
+    startRotateLeft(e) {
+      let that = this,
+        {
+          scale
+        } = that
+      scale.rotateLeft = true
+      function rotate() {
+        if (scale.rotateLeft) {
+          const degree = --scale.degree
+          that.createImg(degree)
+          setTimeout(function() {
+            rotate()
+          }, 60)
+        }
+      }
+      rotate()
+    },
+    // 停止旋转
+    endRotate() {
+      const {
+        scale
+      } = this
+      scale.rotateLeft = false
+      scale.rotateRight = false
+    },
+    // 按钮按下开始放大
+    startZoomAdd(e) {
+      let that = this,
+        {
+          scale
+        } = that
+      scale.zoomAddOn = true
+      function zoom() {
+        if (scale.zoomAddOn) {
+          const range = scale.range >= 100 ? 100 : ++scale.range
+          that.zoomImg(range)
+          setTimeout(function() {
+            zoom()
+          }, 60)
+        }
+      }
+      zoom()
+    },
+    // 按钮松开或移开取消放大
+    endZoomAdd(e) {
+      this.scale.zoomAddOn = false
+    },
+    // 按钮按下开始缩小
+    startZoomSub(e) {
+      let that = this,
+        {
+          scale
+        } = that
+      scale.zoomSubOn = true
+      function zoom() {
+        if (scale.zoomSubOn) {
+          const range = scale.range <= 0 ? 0 : --scale.range
+          that.zoomImg(range)
+          setTimeout(function() {
+            zoom()
+          }, 60)
+        }
+      }
+      zoom()
+    },
+    // 按钮松开或移开取消缩小
+    endZoomSub(e) {
+      const {
+        scale
+      } = this
+      scale.zoomSubOn = false
+    },
+    zoomChange(e) {
+      this.zoomImg(e.target.value)
+    },
+    // 缩放原图
+    zoomImg(newRange) {
+      const that = this
+      const {
+        sourceImgMasking,
+        sourceImgMouseDown,
+        scale
+      } = this
+      const {
+        maxWidth,
+        maxHeight,
+        minWidth,
+        minHeight,
+        width,
+        height,
+        x,
+        y,
+        range
+      } = scale
+      const sim = sourceImgMasking
+      // 蒙版宽高
+      const sWidth = sim.width
+      const sHeight = sim.height
+      // 新宽高
+      const nWidth = minWidth + (maxWidth - minWidth) * newRange / 100
+      const nHeight = minHeight + (maxHeight - minHeight) * newRange / 100
+      // 新坐标(根据蒙版中心点缩放)
+      let nX = sWidth / 2 - (nWidth / width) * (sWidth / 2 - x)
+      let nY = sHeight / 2 - (nHeight / height) * (sHeight / 2 - y)
+      // 判断新坐标是否超过蒙版限制
+      if (nX > 0) {
+        nX = 0
+      }
+      if (nY > 0) {
+        nY = 0
+      }
+      if (nX < sWidth - nWidth) {
+        nX = sWidth - nWidth
+      }
+      if (nY < sHeight - nHeight) {
+        nY = sHeight - nHeight
+      }
+      // 赋值处理
+      scale.x = nX
+      scale.y = nY
+      scale.width = nWidth
+      scale.height = nHeight
+      scale.range = newRange
+      setTimeout(function() {
+        if (scale.range == newRange) {
+          that.createImg()
+        }
+      }, 300)
+    },
+     // 生成需求图片
+    createImg(e) {
+      let that = this,
+        {
+          mime,
+          sourceImg,
+          scale: {
+            x,
+            y,
+            width,
+            height,
+            degree
+          },
+          sourceImgMasking: {
+            scale
+          }
+        } = that,
+        canvas = that.$refs.canvas,
+        ctx = canvas.getContext('2d')
+      if (e) {
+        // 取消鼠标按下移动状态
+        that.sourceImgMouseDown.on = false
+      }
+      canvas.width = that.width
+      canvas.height = that.height
+      ctx.clearRect(0, 0, that.width, that.height)
+      // 将透明区域设置为白色底边
+      ctx.fillStyle = '#fff'
+      ctx.fillRect(0, 0, that.width, that.height)
+      ctx.translate(that.width * 0.5, that.height * 0.5)
+      ctx.rotate(Math.PI * degree / 180)
+      ctx.translate(-that.width * 0.5, -that.height * 0.5)
+      ctx.drawImage(sourceImg, x / scale, y / scale, width / scale, height / scale)
+      that.createImgUrl = canvas.toDataURL(mime)
+    },
+    prepareUpload() {
+      const {
+        url,
+        createImgUrl,
+        field,
+        ki
+      } = this
+      this.$emit('crop-success', createImgUrl, field, ki)
+      if (typeof url === 'string' && url) {
+        this.upload()
+      } else {
+        this.off()
+      }
+    },
+    // 上传图片
+    upload() {
+      let that = this,
+        {
+          lang,
+          imgFormat,
+          mime,
+          url,
+          params,
+          headers,
+          field,
+          ki,
+          createImgUrl,
+          withCredentials
+        } = this,
+        fmData = new FormData()
+      fmData.append(field, data2blob(createImgUrl, mime), field + '.' + imgFormat)
+      // 添加其他参数
+      if (typeof params === 'object' && params) {
+        Object.keys(params).forEach((k) => {
+          fmData.append(k, params[k])
+        })
+      }
+      // 监听进度回调
+      const uploadProgress = function(event) {
+        if (event.lengthComputable) {
+          that.progress = 100 * Math.round(event.loaded) / event.total
+        }
+      }
+      // 上传文件
+      that.reset()
+      that.loading = 1
+      that.setStep(3)
+      request({
+        url,
+        method: 'post',
+        data: fmData
+      }).then(resData => {
+        that.loading = 2
+        that.$emit('crop-upload-success', resData.data)
+      }).catch(err => {
+        if (that.value) {
+          that.loading = 3
+          that.hasError = true
+          that.errorMsg = lang.fail
+          that.$emit('crop-upload-fail', err, field, ki)
+        }
+      })
+    }
+  },
+  created() {
+    // 绑定按键esc隐藏此插件事件
+    document.addEventListener('keyup', (e) => {
+      if (this.value && (e.key == 'Escape' || e.keyCode == 27)) {
+        this.off()
+      }
+    })
+  }
+}
+</script>
+
+<!--
+<style lang='sass' src="./scss/upload.scss">
+</style> -->
+
+<style>
+@charset "UTF-8";
+@-webkit-keyframes vicp_progress {
+  0% {
+    background-position-y: 0; }
+  100% {
+    background-position-y: 40px; } }
+@keyframes vicp_progress {
+  0% {
+    background-position-y: 0; }
+  100% {
+    background-position-y: 40px; } }
+@-webkit-keyframes vicp {
+  0% {
+    opacity: 0;
+    -webkit-transform: scale(0) translatey(-60px);
+            transform: scale(0) translatey(-60px); }
+  100% {
+    opacity: 1;
+    -webkit-transform: scale(1) translatey(0);
+            transform: scale(1) translatey(0); } }
+@keyframes vicp {
+  0% {
+    opacity: 0;
+    -webkit-transform: scale(0) translatey(-60px);
+            transform: scale(0) translatey(-60px); }
+  100% {
+    opacity: 1;
+    -webkit-transform: scale(1) translatey(0);
+            transform: scale(1) translatey(0); } }
+.vue-image-crop-upload {
+  position: fixed;
+  display: block;
+  -webkit-box-sizing: border-box;
+          box-sizing: border-box;
+  z-index: 10000;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.65);
+  -webkit-tap-highlight-color: transparent;
+  -moz-tap-highlight-color: transparent; }
+  .vue-image-crop-upload .vicp-wrap {
+    -webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
+            box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
+    position: fixed;
+    display: block;
+    -webkit-box-sizing: border-box;
+            box-sizing: border-box;
+    z-index: 10000;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    margin: auto;
+    width: 600px;
+    height: 330px;
+    padding: 25px;
+    background-color: #fff;
+    border-radius: 2px;
+    -webkit-animation: vicp 0.12s ease-in;
+            animation: vicp 0.12s ease-in; }
+    .vue-image-crop-upload .vicp-wrap .vicp-close {
+      position: absolute;
+      right: -30px;
+      top: -30px; }
+      .vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4 {
+        position: relative;
+        display: block;
+        width: 30px;
+        height: 30px;
+        cursor: pointer;
+        -webkit-transition: -webkit-transform 0.18s;
+        transition: -webkit-transform 0.18s;
+        transition: transform 0.18s;
+        transition: transform 0.18s, -webkit-transform 0.18s;
+        -webkit-transform: rotate(0);
+            -ms-transform: rotate(0);
+                transform: rotate(0); }
+        .vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4::after, .vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4::before {
+          -webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
+                  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
+          content: '';
+          position: absolute;
+          top: 12px;
+          left: 4px;
+          width: 20px;
+          height: 3px;
+          -webkit-transform: rotate(45deg);
+              -ms-transform: rotate(45deg);
+                  transform: rotate(45deg);
+          background-color: #fff; }
+        .vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4::after {
+          -webkit-transform: rotate(-45deg);
+              -ms-transform: rotate(-45deg);
+                  transform: rotate(-45deg); }
+        .vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4:hover {
+          -webkit-transform: rotate(90deg);
+              -ms-transform: rotate(90deg);
+                  transform: rotate(90deg); }
+    .vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area {
+      position: relative;
+      -webkit-box-sizing: border-box;
+              box-sizing: border-box;
+      padding: 35px;
+      height: 170px;
+      background-color: rgba(0, 0, 0, 0.03);
+      text-align: center;
+      border: 1px dashed rgba(0, 0, 0, 0.08);
+      overflow: hidden; }
+      .vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-icon1 {
+        display: block;
+        margin: 0 auto 6px;
+        width: 42px;
+        height: 42px;
+        overflow: hidden; }
+        .vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-icon1 .vicp-icon1-arrow {
+          display: block;
+          margin: 0 auto;
+          width: 0;
+          height: 0;
+          border-bottom: 14.7px solid rgba(0, 0, 0, 0.3);
+          border-left: 14.7px solid transparent;
+          border-right: 14.7px solid transparent; }
+        .vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-icon1 .vicp-icon1-body {
+          display: block;
+          width: 12.6px;
+          height: 14.7px;
+          margin: 0 auto;
+          background-color: rgba(0, 0, 0, 0.3); }
+        .vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-icon1 .vicp-icon1-bottom {
+          -webkit-box-sizing: border-box;
+                  box-sizing: border-box;
+          display: block;
+          height: 12.6px;
+          border: 6px solid rgba(0, 0, 0, 0.3);
+          border-top: none; }
+      .vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-hint {
+        display: block;
+        padding: 15px;
+        font-size: 14px;
+        color: #666;
+        line-height: 30px; }
+      .vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-no-supported-hint {
+        display: block;
+        position: absolute;
+        top: 0;
+        left: 0;
+        padding: 30px;
+        width: 100%;
+        height: 60px;
+        line-height: 30px;
+        background-color: #eee;
+        text-align: center;
+        color: #666;
+        font-size: 14px; }
+      .vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area:hover {
+        cursor: pointer;
+        border-color: rgba(0, 0, 0, 0.1);
+        background-color: rgba(0, 0, 0, 0.05); }
+    .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop {
+      overflow: hidden; }
+      .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left {
+        float: left; }
+        .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-img-container {
+          position: relative;
+          display: block;
+          width: 240px;
+          height: 180px;
+          background-color: #e5e5e0;
+          overflow: hidden; }
+          .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-img-container .vicp-img {
+            position: absolute;
+            display: block;
+            cursor: move;
+            -webkit-user-select: none;
+               -moz-user-select: none;
+                -ms-user-select: none;
+                    user-select: none; }
+          .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-img-container .vicp-img-shade {
+            -webkit-box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
+                    box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
+            position: absolute;
+            background-color: rgba(241, 242, 243, 0.8); }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-img-container .vicp-img-shade.vicp-img-shade-1 {
+              top: 0;
+              left: 0; }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-img-container .vicp-img-shade.vicp-img-shade-2 {
+              bottom: 0;
+              right: 0; }
+        .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-rotate {
+          position: relative;
+          width: 240px;
+          height: 18px; }
+          .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-rotate i {
+            display: block;
+            width: 18px;
+            height: 18px;
+            border-radius: 100%;
+            line-height: 18px;
+            text-align: center;
+            font-size: 12px;
+            font-weight: bold;
+            background-color: rgba(0, 0, 0, 0.08);
+            color: #fff;
+            overflow: hidden; }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-rotate i:hover {
+              -webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
+                      box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
+              cursor: pointer;
+              background-color: rgba(0, 0, 0, 0.14); }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-rotate i:first-child {
+              float: left; }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-rotate i:last-child {
+              float: right; }
+        .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range {
+          position: relative;
+          margin: 30px 0 10px 0;
+          width: 240px;
+          height: 18px; }
+          .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon5,
+          .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon6 {
+            position: absolute;
+            top: 0;
+            width: 18px;
+            height: 18px;
+            border-radius: 100%;
+            background-color: rgba(0, 0, 0, 0.08); }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon5:hover,
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon6:hover {
+              -webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
+                      box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
+              cursor: pointer;
+              background-color: rgba(0, 0, 0, 0.14); }
+          .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon5 {
+            left: 0; }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon5::before {
+              position: absolute;
+              content: '';
+              display: block;
+              left: 3px;
+              top: 8px;
+              width: 12px;
+              height: 2px;
+              background-color: #fff; }
+          .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon6 {
+            right: 0; }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon6::before {
+              position: absolute;
+              content: '';
+              display: block;
+              left: 3px;
+              top: 8px;
+              width: 12px;
+              height: 2px;
+              background-color: #fff; }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon6::after {
+              position: absolute;
+              content: '';
+              display: block;
+              top: 3px;
+              left: 8px;
+              width: 2px;
+              height: 12px;
+              background-color: #fff; }
+          .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range] {
+            display: block;
+            padding-top: 5px;
+            margin: 0 auto;
+            width: 180px;
+            height: 8px;
+            vertical-align: top;
+            background: transparent;
+            -webkit-appearance: none;
+               -moz-appearance: none;
+                    appearance: none;
+            cursor: pointer;
+            /* 滑块
+               ---------------------------------------------------------------*/
+            /* 轨道
+               ---------------------------------------------------------------*/ }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]:focus {
+              outline: none; }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]::-webkit-slider-thumb {
+              -webkit-box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
+                      box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
+              -webkit-appearance: none;
+                      appearance: none;
+              margin-top: -3px;
+              width: 12px;
+              height: 12px;
+              background-color: #61c091;
+              border-radius: 100%;
+              border: none;
+              -webkit-transition: 0.2s;
+              transition: 0.2s; }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]::-moz-range-thumb {
+              box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
+              -moz-appearance: none;
+                   appearance: none;
+              width: 12px;
+              height: 12px;
+              background-color: #61c091;
+              border-radius: 100%;
+              border: none;
+              -webkit-transition: 0.2s;
+              transition: 0.2s; }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]::-ms-thumb {
+              box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
+              appearance: none;
+              width: 12px;
+              height: 12px;
+              background-color: #61c091;
+              border: none;
+              border-radius: 100%;
+              -webkit-transition: 0.2s;
+              transition: 0.2s; }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]:active::-moz-range-thumb {
+              box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
+              width: 14px;
+              height: 14px; }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]:active::-ms-thumb {
+              box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
+              width: 14px;
+              height: 14px; }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]:active::-webkit-slider-thumb {
+              -webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
+                      box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
+              margin-top: -4px;
+              width: 14px;
+              height: 14px; }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]::-webkit-slider-runnable-track {
+              -webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
+                      box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
+              width: 100%;
+              height: 6px;
+              cursor: pointer;
+              border-radius: 2px;
+              border: none;
+              background-color: rgba(68, 170, 119, 0.3); }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]::-moz-range-track {
+              box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
+              width: 100%;
+              height: 6px;
+              cursor: pointer;
+              border-radius: 2px;
+              border: none;
+              background-color: rgba(68, 170, 119, 0.3); }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]::-ms-track {
+              box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
+              width: 100%;
+              cursor: pointer;
+              background: transparent;
+              border-color: transparent;
+              color: transparent;
+              height: 6px;
+              border-radius: 2px;
+              border: none; }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]::-ms-fill-lower {
+              background-color: rgba(68, 170, 119, 0.3); }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]::-ms-fill-upper {
+              background-color: rgba(68, 170, 119, 0.15); }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]:focus::-webkit-slider-runnable-track {
+              background-color: rgba(68, 170, 119, 0.5); }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]:focus::-moz-range-track {
+              background-color: rgba(68, 170, 119, 0.5); }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]:focus::-ms-fill-lower {
+              background-color: rgba(68, 170, 119, 0.45); }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]:focus::-ms-fill-upper {
+              background-color: rgba(68, 170, 119, 0.25); }
+      .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right {
+        float: right; }
+        .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right .vicp-preview {
+          height: 150px;
+          overflow: hidden; }
+          .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right .vicp-preview .vicp-preview-item {
+            position: relative;
+            padding: 5px;
+            width: 100px;
+            height: 100px;
+            float: left;
+            margin-right: 16px; }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right .vicp-preview .vicp-preview-item span {
+              position: absolute;
+              bottom: -30px;
+              width: 100%;
+              font-size: 14px;
+              color: #bbb;
+              display: block;
+              text-align: center; }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right .vicp-preview .vicp-preview-item img {
+              position: absolute;
+              display: block;
+              top: 0;
+              bottom: 0;
+              left: 0;
+              right: 0;
+              margin: auto;
+              padding: 3px;
+              background-color: #fff;
+              border: 1px solid rgba(0, 0, 0, 0.15);
+              overflow: hidden;
+              -webkit-user-select: none;
+                 -moz-user-select: none;
+                  -ms-user-select: none;
+                      user-select: none; }
+            .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right .vicp-preview .vicp-preview-item.vicp-preview-item-circle {
+              margin-right: 0; }
+              .vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right .vicp-preview .vicp-preview-item.vicp-preview-item-circle img {
+                border-radius: 100%; }
+    .vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload {
+      position: relative;
+      -webkit-box-sizing: border-box;
+              box-sizing: border-box;
+      padding: 35px;
+      height: 170px;
+      background-color: rgba(0, 0, 0, 0.03);
+      text-align: center;
+      border: 1px dashed #ddd; }
+      .vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-loading {
+        display: block;
+        padding: 15px;
+        font-size: 16px;
+        color: #999;
+        line-height: 30px; }
+      .vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-progress-wrap {
+        margin-top: 12px;
+        background-color: rgba(0, 0, 0, 0.08);
+        border-radius: 3px; }
+        .vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-progress-wrap .vicp-progress {
+          position: relative;
+          display: block;
+          height: 5px;
+          border-radius: 3px;
+          background-color: #4a7;
+          -webkit-box-shadow: 0 2px 6px 0 rgba(68, 170, 119, 0.3);
+                  box-shadow: 0 2px 6px 0 rgba(68, 170, 119, 0.3);
+          -webkit-transition: width 0.15s linear;
+          transition: width 0.15s linear;
+          background-image: -webkit-linear-gradient(135deg, rgba(255, 255, 255, 0.2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.2) 75%, transparent 75%, transparent);
+          background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.2) 75%, transparent 75%, transparent);
+          background-size: 40px 40px;
+          -webkit-animation: vicp_progress 0.5s linear infinite;
+                  animation: vicp_progress 0.5s linear infinite; }
+          .vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-progress-wrap .vicp-progress::after {
+            content: '';
+            position: absolute;
+            display: block;
+            top: -3px;
+            right: -3px;
+            width: 9px;
+            height: 9px;
+            border: 1px solid rgba(245, 246, 247, 0.7);
+            -webkit-box-shadow: 0 1px 4px 0 rgba(68, 170, 119, 0.7);
+                    box-shadow: 0 1px 4px 0 rgba(68, 170, 119, 0.7);
+            border-radius: 100%;
+            background-color: #4a7; }
+      .vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-error,
+      .vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-success {
+        height: 100px;
+        line-height: 100px; }
+    .vue-image-crop-upload .vicp-wrap .vicp-operate {
+      position: absolute;
+      right: 20px;
+      bottom: 20px; }
+      .vue-image-crop-upload .vicp-wrap .vicp-operate a {
+        position: relative;
+        float: left;
+        display: block;
+        margin-left: 10px;
+        width: 100px;
+        height: 36px;
+        line-height: 36px;
+        text-align: center;
+        cursor: pointer;
+        font-size: 14px;
+        color: #4a7;
+        border-radius: 2px;
+        overflow: hidden;
+        -webkit-user-select: none;
+           -moz-user-select: none;
+            -ms-user-select: none;
+                user-select: none; }
+        .vue-image-crop-upload .vicp-wrap .vicp-operate a:hover {
+          background-color: rgba(0, 0, 0, 0.03); }
+    .vue-image-crop-upload .vicp-wrap .vicp-error,
+    .vue-image-crop-upload .vicp-wrap .vicp-success {
+      display: block;
+      font-size: 14px;
+      line-height: 24px;
+      height: 24px;
+      color: #d10;
+      text-align: center;
+      vertical-align: top; }
+    .vue-image-crop-upload .vicp-wrap .vicp-success {
+      color: #4a7; }
+    .vue-image-crop-upload .vicp-wrap .vicp-icon3 {
+      position: relative;
+      display: inline-block;
+      width: 20px;
+      height: 20px;
+      top: 4px; }
+      .vue-image-crop-upload .vicp-wrap .vicp-icon3::after {
+        position: absolute;
+        top: 3px;
+        left: 6px;
+        width: 6px;
+        height: 10px;
+        border-width: 0 2px 2px 0;
+        border-color: #4a7;
+        border-style: solid;
+        -webkit-transform: rotate(45deg);
+            -ms-transform: rotate(45deg);
+                transform: rotate(45deg);
+        content: ''; }
+    .vue-image-crop-upload .vicp-wrap .vicp-icon2 {
+      position: relative;
+      display: inline-block;
+      width: 20px;
+      height: 20px;
+      top: 4px; }
+      .vue-image-crop-upload .vicp-wrap .vicp-icon2::after, .vue-image-crop-upload .vicp-wrap .vicp-icon2::before {
+        content: '';
+        position: absolute;
+        top: 9px;
+        left: 4px;
+        width: 13px;
+        height: 2px;
+        background-color: #d10;
+        -webkit-transform: rotate(45deg);
+            -ms-transform: rotate(45deg);
+                transform: rotate(45deg); }
+      .vue-image-crop-upload .vicp-wrap .vicp-icon2::after {
+        -webkit-transform: rotate(-45deg);
+            -ms-transform: rotate(-45deg);
+                transform: rotate(-45deg); }
+.e-ripple {
+  position: absolute;
+  border-radius: 100%;
+  background-color: rgba(0, 0, 0, 0.15);
+  background-clip: padding-box;
+  pointer-events: none;
+  -webkit-user-select: none;
+     -moz-user-select: none;
+      -ms-user-select: none;
+          user-select: none;
+  -webkit-transform: scale(0);
+      -ms-transform: scale(0);
+          transform: scale(0);
+  opacity: 1; }
+  .e-ripple.z-active {
+    opacity: 0;
+    -webkit-transform: scale(2);
+        -ms-transform: scale(2);
+            transform: scale(2);
+    -webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
+    transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
+    transition: opacity 1.2s ease-out, transform 0.6s ease-out;
+    transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out; }
+</style>

+ 19 - 0
src/components/ImageCropper/utils/data2blob.js

@@ -0,0 +1,19 @@
+/**
+ * database64文件格式转换为2进制
+ *
+ * @param  {[String]} data dataURL 的格式为 “data:image/png;base64,****”,逗号之前都是一些说明性的文字,我们只需要逗号之后的就行了
+ * @param  {[String]} mime [description]
+ * @return {[blob]}      [description]
+ */
+export default function(data, mime) {
+  data = data.split(',')[1]
+  data = window.atob(data)
+  var ia = new Uint8Array(data.length)
+  for (var i = 0; i < data.length; i++) {
+    ia[i] = data.charCodeAt(i)
+  }
+  // canvas.toDataURL 返回的默认格式就是 image/png
+  return new Blob([ia], {
+    type: mime
+  })
+}

+ 39 - 0
src/components/ImageCropper/utils/effectRipple.js

@@ -0,0 +1,39 @@
+/**
+ * 点击波纹效果
+ *
+ * @param  {[event]} e        [description]
+ * @param  {[Object]} arg_opts [description]
+ * @return {[bollean]}          [description]
+ */
+export default function(e, arg_opts) {
+  var opts = Object.assign({
+    ele: e.target, // 波纹作用元素
+    type: 'hit', // hit点击位置扩散center中心点扩展
+    bgc: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
+  }, arg_opts)
+  var target = opts.ele
+  if (target) {
+    var rect = target.getBoundingClientRect()
+    var ripple = target.querySelector('.e-ripple')
+    if (!ripple) {
+      ripple = document.createElement('span')
+      ripple.className = 'e-ripple'
+      ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'
+      target.appendChild(ripple)
+    } else {
+      ripple.className = 'e-ripple'
+    }
+    switch (opts.type) {
+      case 'center':
+        ripple.style.top = (rect.height / 2 - ripple.offsetHeight / 2) + 'px'
+        ripple.style.left = (rect.width / 2 - ripple.offsetWidth / 2) + 'px'
+        break
+      default:
+        ripple.style.top = (e.pageY - rect.top - ripple.offsetHeight / 2 - document.body.scrollTop) + 'px'
+        ripple.style.left = (e.pageX - rect.left - ripple.offsetWidth / 2 - document.body.scrollLeft) + 'px'
+    }
+    ripple.style.backgroundColor = opts.bgc
+    ripple.className = 'e-ripple z-active'
+    return false
+  }
+}

+ 232 - 0
src/components/ImageCropper/utils/language.js

@@ -0,0 +1,232 @@
+export default {
+  zh: {
+    hint: '点击,或拖动图片至此处',
+    loading: '正在上传……',
+    noSupported: '浏览器不支持该功能,请使用IE10以上或其他现在浏览器!',
+    success: '上传成功',
+    fail: '图片上传失败',
+    preview: '头像预览',
+    btn: {
+      off: '取消',
+      close: '关闭',
+      back: '上一步',
+      save: '保存'
+    },
+    error: {
+      onlyImg: '仅限图片格式',
+      outOfSize: '单文件大小不能超过 ',
+      lowestPx: '图片最低像素为(宽*高):'
+    }
+  },
+  'zh-tw': {
+    hint: '點擊,或拖動圖片至此處',
+    loading: '正在上傳……',
+    noSupported: '瀏覽器不支持該功能,請使用IE10以上或其他現代瀏覽器!',
+    success: '上傳成功',
+    fail: '圖片上傳失敗',
+    preview: '頭像預覽',
+    btn: {
+      off: '取消',
+      close: '關閉',
+      back: '上一步',
+      save: '保存'
+    },
+    error: {
+      onlyImg: '僅限圖片格式',
+      outOfSize: '單文件大小不能超過 ',
+      lowestPx: '圖片最低像素為(寬*高):'
+    }
+  },
+  en: {
+    hint: 'Click or drag the file here to upload',
+    loading: 'Uploading…',
+    noSupported: 'Browser is not supported, please use IE10+ or other browsers',
+    success: 'Upload success',
+    fail: 'Upload failed',
+    preview: 'Preview',
+    btn: {
+      off: 'Cancel',
+      close: 'Close',
+      back: 'Back',
+      save: 'Save'
+    },
+    error: {
+      onlyImg: 'Image only',
+      outOfSize: 'Image exceeds size limit: ',
+      lowestPx: 'Image\'s size is too low. Expected at least: '
+    }
+  },
+  ro: {
+    hint: 'Atinge sau trage fișierul aici',
+    loading: 'Se încarcă',
+    noSupported: 'Browser-ul tău nu suportă acest feature. Te rugăm încearcă cu alt browser.',
+    success: 'S-a încărcat cu succes',
+    fail: 'A apărut o problemă la încărcare',
+    preview: 'Previzualizează',
+
+    btn: {
+      off: 'Anulează',
+      close: 'Închide',
+      back: 'Înapoi',
+      save: 'Salvează'
+    },
+
+    error: {
+      onlyImg: 'Doar imagini',
+      outOfSize: 'Imaginea depășește limita de: ',
+      loewstPx: 'Imaginea este prea mică; Minim: '
+    }
+  },
+  ru: {
+    hint: 'Нажмите, или перетащите файл в это окно',
+    loading: 'Загружаю……',
+    noSupported: 'Ваш браузер не поддерживается, пожалуйста, используйте IE10 + или другие браузеры',
+    success: 'Загрузка выполнена успешно',
+    fail: 'Ошибка загрузки',
+    preview: 'Предпросмотр',
+    btn: {
+      off: 'Отменить',
+      close: 'Закрыть',
+      back: 'Назад',
+      save: 'Сохранить'
+    },
+    error: {
+      onlyImg: 'Только изображения',
+      outOfSize: 'Изображение превышает предельный размер: ',
+      lowestPx: 'Минимальный размер изображения: '
+    }
+  },
+  'pt-br': {
+    hint: 'Clique ou arraste o arquivo aqui para carregar',
+    loading: 'Carregando…',
+    noSupported: 'Browser não suportado, use o IE10+ ou outro browser',
+    success: 'Sucesso ao carregar imagem',
+    fail: 'Falha ao carregar imagem',
+    preview: 'Pré-visualizar',
+    btn: {
+      off: 'Cancelar',
+      close: 'Fechar',
+      back: 'Voltar',
+      save: 'Salvar'
+    },
+    error: {
+      onlyImg: 'Apenas imagens',
+      outOfSize: 'A imagem excede o limite de tamanho: ',
+      lowestPx: 'O tamanho da imagem é muito pequeno. Tamanho mínimo: '
+    }
+  },
+  fr: {
+    hint: 'Cliquez ou glissez le fichier ici.',
+    loading: 'Téléchargement…',
+    noSupported: 'Votre navigateur n\'est pas supporté. Utilisez IE10 + ou un autre navigateur s\'il vous plaît.',
+    success: 'Téléchargement réussit',
+    fail: 'Téléchargement echoué',
+    preview: 'Aperçu',
+    btn: {
+      off: 'Annuler',
+      close: 'Fermer',
+      back: 'Retour',
+      save: 'Enregistrer'
+    },
+    error: {
+      onlyImg: 'Image uniquement',
+      outOfSize: 'L\'image sélectionnée dépasse la taille maximum: ',
+      lowestPx: 'L\'image sélectionnée est trop petite. Dimensions attendues: '
+    }
+  },
+  nl: {
+    hint: 'Klik hier of sleep een afbeelding in dit vlak',
+    loading: 'Uploaden…',
+    noSupported: 'Je browser wordt helaas niet ondersteund. Gebruik IE10+ of een andere browser.',
+    success: 'Upload succesvol',
+    fail: 'Upload mislukt',
+    preview: 'Voorbeeld',
+    btn: {
+      off: 'Annuleren',
+      close: 'Sluiten',
+      back: 'Terug',
+      save: 'Opslaan'
+    },
+    error: {
+      onlyImg: 'Alleen afbeeldingen',
+      outOfSize: 'De afbeelding is groter dan: ',
+      lowestPx: 'De afbeelding is te klein! Minimale afmetingen: '
+    }
+  },
+  tr: {
+    hint: 'Tıkla veya yüklemek istediğini buraya sürükle',
+    loading: 'Yükleniyor…',
+    noSupported: 'Tarayıcı desteklenmiyor, lütfen IE10+ veya farklı tarayıcı kullanın',
+    success: 'Yükleme başarılı',
+    fail: 'Yüklemede hata oluştu',
+    preview: 'Önizle',
+    btn: {
+      off: 'İptal',
+      close: 'Kapat',
+      back: 'Geri',
+      save: 'Kaydet'
+    },
+    error: {
+      onlyImg: 'Sadece resim',
+      outOfSize: 'Resim yükleme limitini aşıyor: ',
+      lowestPx: 'Resmin boyutu çok küçük. En az olması gereken: '
+    }
+  },
+  'es-MX': {
+    hint: 'Selecciona o arrastra una imagen',
+    loading: 'Subiendo...',
+    noSupported: 'Tu navegador no es soportado, porfavor usa IE10+ u otros navegadores mas recientes',
+    success: 'Subido exitosamente',
+    fail: 'Sucedió un error',
+    preview: 'Vista previa',
+    btn: {
+      off: 'Cancelar',
+      close: 'Cerrar',
+      back: 'Atras',
+      save: 'Guardar'
+    },
+    error: {
+      onlyImg: 'Unicamente imagenes',
+      outOfSize: 'La imagen excede el tamaño maximo:',
+      lowestPx: 'La imagen es demasiado pequeño. Se espera por lo menos:'
+    }
+  },
+  de: {
+    hint: 'Klick hier oder zieh eine Datei hier rein zum Hochladen',
+    loading: 'Hochladen…',
+    noSupported: 'Browser wird nicht unterstützt, bitte verwende IE10+ oder andere Browser',
+    success: 'Upload erfolgreich',
+    fail: 'Upload fehlgeschlagen',
+    preview: 'Vorschau',
+    btn: {
+      off: 'Abbrechen',
+      close: 'Schließen',
+      back: 'Zurück',
+      save: 'Speichern'
+    },
+    error: {
+      onlyImg: 'Nur Bilder',
+      outOfSize: 'Das Bild ist zu groß: ',
+      lowestPx: 'Das Bild ist zu klein. Mindestens: '
+    }
+  },
+  ja: {
+    hint: 'クリック・ドラッグしてファイルをアップロード',
+    loading: 'アップロード中...',
+    noSupported: 'このブラウザは対応されていません。IE10+かその他の主要ブラウザをお使いください。',
+    success: 'アップロード成功',
+    fail: 'アップロード失敗',
+    preview: 'プレビュー',
+    btn: {
+      off: 'キャンセル',
+      close: '閉じる',
+      back: '戻る',
+      save: '保存'
+    },
+    error: {
+      onlyImg: '画像のみ',
+      outOfSize: '画像サイズが上限を超えています。上限: ',
+      lowestPx: '画像が小さすぎます。最小サイズ: '
+    }
+  }
+}

+ 7 - 0
src/components/ImageCropper/utils/mimes.js

@@ -0,0 +1,7 @@
+export default {
+  'jpg': 'image/jpeg',
+  'png': 'image/png',
+  'gif': 'image/gif',
+  'svg': 'image/svg+xml',
+  'psd': 'image/photoshop'
+}

+ 84 - 0
src/components/Layout/LeftMptt.vue

@@ -0,0 +1,84 @@
+<template>
+    <div>
+        <el-tree
+            :data="data"
+            node-key="id"
+            :props="props"
+            lazy
+            :load="load"
+            @node-drag-end="handleDragEnd"
+            @node-drop="handleDrop"
+            :current-node-key="currentNode"
+            draggable
+            
+        ></el-tree>
+    </div>
+</template>
+
+<script>
+import axios from "../../api/AxiosApi.js";
+export default {
+    name: "LeftMptt",
+    data() {
+        return {
+            dataList: this.data,
+            dataModify: this.dataModel,
+            currentNode: null
+        };
+    },
+    props: {
+        props: {
+            type: Object,
+            default() {
+                return {
+                    children: "children",
+                    label: "label",
+                };
+            },
+        },
+        data: {
+            type: Array,
+        },
+        load: {
+            type: Function,
+        },
+    },
+
+    created() {},
+    watch: {
+        // dataModify: {
+        //     deep: true,
+        //     handler(newVal, oldVal) {
+        //         this.$emit("callback", newVal);
+        //     },
+        // },
+        data: {
+            deep: true,
+            handler(newVal, oldVal) {
+                this.$emit("callback", newVal);
+            },
+        },
+    },
+    methods: {
+        handleDragEnd(data) {
+            console.log("tree drag end: ", data);
+        },
+        handleDrop() {
+            console.log("tree drop: ");
+        },
+        sonDeptJudge(data, node) {
+            if (data.id != -1) {
+                return parseInt((data.rght - data.lft - 1) / 2) == 0;
+            }
+            return false;
+        }
+    },
+};
+</script>
+
+<style>
+.el-tree {
+    /* opacity: 0.5; */
+    background-color: rgba(0, 0, 0, 0);
+}
+</style>

+ 140 - 0
src/components/PanThumb/index.vue

@@ -0,0 +1,140 @@
+<template>
+  <div :style="{zIndex:zIndex,height:height,width:width}" class="pan-item">
+    <div class="pan-info">
+      <div class="pan-info-roles-container">
+        <slot />
+      </div>
+    </div>
+    <img :src="image" class="pan-thumb">
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'PanThumb',
+  props: {
+    image: {
+      type: String,
+      required: true
+    },
+    zIndex: {
+      type: Number,
+      default: 1
+    },
+    width: {
+      type: String,
+      default: '150px'
+    },
+    height: {
+      type: String,
+      default: '150px'
+    }
+  }
+}
+</script>
+
+<style scoped>
+.pan-item {
+  width: 200px;
+  height: 200px;
+  border-radius: 50%;
+  display: inline-block;
+  position: relative;
+  cursor: default;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
+}
+
+.pan-info-roles-container {
+  padding: 20px;
+  text-align: center;
+}
+
+.pan-thumb {
+  width: 100%;
+  height: 100%;
+  background-size: 100%;
+  border-radius: 50%;
+  overflow: hidden;
+  position: absolute;
+  transform-origin: 95% 40%;
+  transition: all 0.3s ease-in-out;
+}
+
+.pan-thumb:after {
+  content: '';
+  width: 8px;
+  height: 8px;
+  position: absolute;
+  border-radius: 50%;
+  top: 40%;
+  left: 95%;
+  margin: -4px 0 0 -4px;
+  background: radial-gradient(ellipse at center, rgba(14, 14, 14, 1) 0%, rgba(125, 126, 125, 1) 100%);
+  box-shadow: 0 0 1px rgba(255, 255, 255, 0.9);
+}
+
+.pan-info {
+  position: absolute;
+  width: inherit;
+  height: inherit;
+  border-radius: 50%;
+  overflow: hidden;
+  box-shadow: inset 0 0 0 5px rgba(0, 0, 0, 0.05);
+}
+
+.pan-info h3 {
+  color: #fff;
+  text-transform: uppercase;
+  position: relative;
+  letter-spacing: 2px;
+  font-size: 18px;
+  margin: 0 60px;
+  padding: 22px 0 0 0;
+  height: 85px;
+  font-family: 'Open Sans', Arial, sans-serif;
+  text-shadow: 0 0 1px #fff, 0 1px 2px rgba(0, 0, 0, 0.3);
+}
+
+.pan-info p {
+  color: #fff;
+  padding: 10px 5px;
+  font-style: italic;
+  margin: 0 30px;
+  font-size: 12px;
+  border-top: 1px solid rgba(255, 255, 255, 0.5);
+}
+
+.pan-info p a {
+  display: block;
+  color: #333;
+  width: 80px;
+  height: 80px;
+  background: rgba(255, 255, 255, 0.3);
+  border-radius: 50%;
+  color: #fff;
+  font-style: normal;
+  font-weight: 700;
+  text-transform: uppercase;
+  font-size: 9px;
+  letter-spacing: 1px;
+  padding-top: 24px;
+  margin: 7px auto 0;
+  font-family: 'Open Sans', Arial, sans-serif;
+  opacity: 0;
+  transition: transform 0.3s ease-in-out 0.2s, opacity 0.3s ease-in-out 0.2s, background 0.2s linear 0s;
+  transform: translateX(60px) rotate(90deg);
+}
+
+.pan-info p a:hover {
+  background: rgba(255, 255, 255, 0.5);
+}
+
+.pan-item:hover .pan-thumb {
+  transform: rotate(-110deg);
+}
+
+.pan-item:hover .pan-info p a {
+  opacity: 1;
+  transform: translateX(0px) rotate(0deg);
+}
+</style>

+ 59 - 0
src/components/Screenfull/index.vue

@@ -0,0 +1,59 @@
+<template>
+  <div>
+    <svg-icon :icon-class="isFullscreen?'exit-fullscreen':'fullscreen'" @click="click" />
+  </div>
+</template>
+
+<script>
+import screenfull from 'screenfull'
+export default {
+  name: 'Screenfull',
+  data() {
+    return {
+      isFullscreen: false
+    }
+  },
+  mounted() {
+    this.init()
+  },
+  beforeDestroy() {
+    this.destroy()
+  },
+  methods: {
+    click() {
+      if (!screenfull.isEnabled) {
+        this.$message({
+          message: 'you browser can not work',
+          type: 'warning'
+        })
+        return false
+      }
+      screenfull.toggle()
+    },
+    change() {
+      this.isFullscreen = screenfull.isFullscreen
+    },
+    init() {
+      if (screenfull.isEnabled) {
+        screenfull.on('change', this.change)
+      }
+    },
+    destroy() {
+      if (screenfull.isEnabled) {
+        screenfull.off('change', this.change)
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.screenfull-svg {
+  display: inline-block;
+  cursor: pointer;
+  fill: #5a5e66;;
+  width: 20px;
+  height: 20px;
+  vertical-align: 10px;
+}
+</style>

+ 91 - 0
src/components/Sticky/index.vue

@@ -0,0 +1,91 @@
+<template>
+  <div :style="{height:height+'px',zIndex:zIndex}">
+    <div
+      :class="className"
+      :style="{top:(isSticky ? stickyTop +'px' : ''),zIndex:zIndex,position:position,width:width,height:height+'px'}"
+    >
+      <slot>
+        <div>sticky</div>
+      </slot>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'Sticky',
+  props: {
+    stickyTop: {
+      type: Number,
+      default: 0
+    },
+    zIndex: {
+      type: Number,
+      default: 1
+    },
+    className: {
+      type: String,
+      default: ''
+    }
+  },
+  data() {
+    return {
+      active: false,
+      position: '',
+      width: undefined,
+      height: undefined,
+      isSticky: false
+    }
+  },
+  mounted() {
+    this.height = this.$el.getBoundingClientRect().height
+    window.addEventListener('scroll', this.handleScroll)
+    window.addEventListener('resize', this.handleResize)
+  },
+  activated() {
+    this.handleScroll()
+  },
+  destroyed() {
+    window.removeEventListener('scroll', this.handleScroll)
+    window.removeEventListener('resize', this.handleResize)
+  },
+  methods: {
+    sticky() {
+      if (this.active) {
+        return
+      }
+      this.position = 'fixed'
+      this.active = true
+      this.width = this.width + 'px'
+      this.isSticky = true
+    },
+    handleReset() {
+      if (!this.active) {
+        return
+      }
+      this.reset()
+    },
+    reset() {
+      this.position = ''
+      this.width = 'auto'
+      this.active = false
+      this.isSticky = false
+    },
+    handleScroll() {
+      const width = this.$el.getBoundingClientRect().width
+      this.width = width || 'auto'
+      const offsetTop = this.$el.getBoundingClientRect().top
+      if (offsetTop < this.stickyTop) {
+        this.sticky()
+        return
+      }
+      this.handleReset()
+    },
+    handleResize() {
+      if (this.isSticky) {
+        this.width = this.$el.getBoundingClientRect().width + 'px'
+      }
+    }
+  }
+}
+</script>

+ 43 - 0
src/components/SvgIcon/index.vue

@@ -0,0 +1,43 @@
+<template>
+  <svg :class="svgClass" aria-hidden="true" v-on="$listeners">
+    <use :xlink:href="iconName" />
+  </svg>
+</template>
+
+<script>
+export default {
+  name: 'SvgIcon',
+  props: {
+    iconClass: {
+      type: String,
+      required: true
+    },
+    className: {
+      type: String,
+      default: ''
+    }
+  },
+  computed: {
+    iconName() {
+      return `#icon-${this.iconClass}`
+    },
+    svgClass() {
+      if (this.className) {
+        return 'svg-icon ' + this.className
+      } else {
+        return 'svg-icon'
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.svg-icon {
+  width: 1em;
+  height: 1em;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+}
+</style>

+ 113 - 0
src/components/TextHoverEffect/Mallki.vue

@@ -0,0 +1,113 @@
+<template>
+  <a :class="className" class="link--mallki" href="#">
+    {{ text }}
+    <span :data-letters="text" />
+    <span :data-letters="text" />
+  </a>
+</template>
+
+<script>
+export default {
+  props: {
+    className: {
+      type: String,
+      default: ''
+    },
+    text: {
+      type: String,
+      default: 'vue-element-admin'
+    }
+  }
+}
+</script>
+
+<style>
+/* Mallki */
+
+.link--mallki {
+  font-weight: 800;
+  color: #4dd9d5;
+  font-family: 'Dosis', sans-serif;
+  -webkit-transition: color 0.5s 0.25s;
+  transition: color 0.5s 0.25s;
+  overflow: hidden;
+  position: relative;
+  display: inline-block;
+  line-height: 1;
+  outline: none;
+  text-decoration: none;
+}
+
+.link--mallki:hover {
+  -webkit-transition: none;
+  transition: none;
+  color: transparent;
+}
+
+.link--mallki::before {
+  content: '';
+  width: 100%;
+  height: 6px;
+  margin: -3px 0 0 0;
+  background: #3888fa;
+  position: absolute;
+  left: 0;
+  top: 50%;
+  -webkit-transform: translate3d(-100%, 0, 0);
+  transform: translate3d(-100%, 0, 0);
+  -webkit-transition: -webkit-transform 0.4s;
+  transition: transform 0.4s;
+  -webkit-transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
+  transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
+}
+
+.link--mallki:hover::before {
+  -webkit-transform: translate3d(100%, 0, 0);
+  transform: translate3d(100%, 0, 0);
+}
+
+.link--mallki span {
+  position: absolute;
+  height: 50%;
+  width: 100%;
+  left: 0;
+  top: 0;
+  overflow: hidden;
+}
+
+.link--mallki span::before {
+  content: attr(data-letters);
+  color: red;
+  position: absolute;
+  left: 0;
+  width: 100%;
+  color: #3888fa;
+  -webkit-transition: -webkit-transform 0.5s;
+  transition: transform 0.5s;
+}
+
+.link--mallki span:nth-child(2) {
+  top: 50%;
+}
+
+.link--mallki span:first-child::before {
+  top: 0;
+  -webkit-transform: translate3d(0, 100%, 0);
+  transform: translate3d(0, 100%, 0);
+}
+
+.link--mallki span:nth-child(2)::before {
+  bottom: 0;
+  -webkit-transform: translate3d(0, -100%, 0);
+  transform: translate3d(0, -100%, 0);
+}
+
+.link--mallki:hover span::before {
+  -webkit-transition-delay: 0.3s;
+  transition-delay: 0.3s;
+  -webkit-transform: translate3d(0, 0, 0);
+  transform: translate3d(0, 0, 0);
+  -webkit-transition-timing-function: cubic-bezier(0.2, 1, 0.3, 1);
+  transition-timing-function: cubic-bezier(0.2, 1, 0.3, 1);
+}
+</style>

+ 91 - 0
src/components/index.vue

@@ -0,0 +1,91 @@
+<template>
+  <div :style="{height:height+'px',zIndex:zIndex}">
+    <div
+      :class="className"
+      :style="{top:(isSticky ? stickyTop +'px' : ''),zIndex:zIndex,position:position,width:width,height:height+'px'}"
+    >
+      <slot>
+        <div>sticky</div>
+      </slot>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'Sticky',
+  props: {
+    stickyTop: {
+      type: Number,
+      default: 0
+    },
+    zIndex: {
+      type: Number,
+      default: 1
+    },
+    className: {
+      type: String,
+      default: ''
+    }
+  },
+  data() {
+    return {
+      active: false,
+      position: '',
+      width: undefined,
+      height: undefined,
+      isSticky: false
+    }
+  },
+  mounted() {
+    this.height = this.$el.getBoundingClientRect().height
+    window.addEventListener('scroll', this.handleScroll)
+    window.addEventListener('resize', this.handleResize)
+  },
+  activated() {
+    this.handleScroll()
+  },
+  destroyed() {
+    window.removeEventListener('scroll', this.handleScroll)
+    window.removeEventListener('resize', this.handleResize)
+  },
+  methods: {
+    sticky() {
+      if (this.active) {
+        return
+      }
+      this.position = 'fixed'
+      this.active = true
+      this.width = this.width + 'px'
+      this.isSticky = true
+    },
+    handleReset() {
+      if (!this.active) {
+        return
+      }
+      this.reset()
+    },
+    reset() {
+      this.position = ''
+      this.width = 'auto'
+      this.active = false
+      this.isSticky = false
+    },
+    handleScroll() {
+      const width = this.$el.getBoundingClientRect().width
+      this.width = width === 0 ? '100%' : width // fix resize problem
+      const offsetTop = this.$el.getBoundingClientRect().top
+      if (offsetTop < this.stickyTop) {
+        this.sticky()
+        return
+      }
+      this.handleReset()
+    },
+    handleResize() {
+      if (this.isSticky) {
+        this.width = this.$el.getBoundingClientRect().width + 'px'
+      }
+    }
+  }
+}
+</script>

+ 653 - 0
src/data/map.js

@@ -0,0 +1,653 @@
+export default {
+  huzhou: {
+    'features': [{
+      'type': 'Feature',
+      'geometry': {
+        'type': 'Polygon',
+        'coordinates': [
+          [
+            [120.358401, 30.89202661819244],
+            [120.35966599999999, 30.93323249134752],
+            [120.369933, 30.948341444799137],
+            [120.34333800000002, 30.940276469648623],
+            [120.30807800000001, 30.932564493405067],
+            [120.25075500000001, 30.92652551200415],
+            [120.22178, 30.92716051004861],
+            [120.19463539123534, 30.931678807582227],
+            [120.18115997314453, 30.925862341013612],
+            [120.169358253479, 30.91573790043986],
+            [120.15498161315917, 30.91905147168173],
+            [120.13970375061035, 30.88764156978525],
+            [120.12309551239014, 30.894712458250417],
+            [120.1019811630249, 30.899241971805164],
+            [120.07700443267822, 30.882706369566026],
+            [120.04992485046385, 30.871877502119922],
+            [120.0640869140625, 30.8139185961498],
+            [120.06271362304688, 30.80120201357747],
+            [120.07013797760008, 30.77476825532388],
+            [120.05687713623047, 30.768167899212475],
+            [120.04494667053223, 30.744491390358732],
+            [120.04537582397462, 30.72353909620633],
+            [120.03164291381836, 30.7329829476104],
+            [119.97675418853758, 30.71051539306333],
+            [119.9662399291992, 30.719665373960456],
+            [119.96658325195312, 30.755998458321667],
+            [119.98100280761719, 30.815282303625203],
+            [119.962420463562, 30.818120227383083],
+            [119.95658397674562, 30.819152178869253],
+            [119.95361, 30.80826487556828],
+            [119.955401, 30.800663898892708],
+            [119.946999, 30.7899689317026],
+            [119.945396, 30.772184986237264],
+            [119.94805, 30.749620055391922],
+            [119.94021199999999, 30.73472210102456],
+            [119.91832499999998, 30.723738134655825],
+            [119.893282, 30.722392138776318],
+            [119.873886, 30.705901189246767],
+            [119.86576000000001, 30.692310230823438],
+            [119.877806, 30.67308128961909],
+            [119.91029900000001, 30.65304135085914],
+            [119.91476899999999, 30.639257392960577],
+            [119.89957999999999, 30.622954442733853],
+            [119.90585299999998, 30.613668471073485],
+            [119.917974, 30.613522471518973],
+            [119.938196, 30.6318604155467],
+            [119.95649, 30.63077941884702],
+            [119.96717100000001, 30.61750845935527],
+            [119.99137400000001, 30.621397447486164],
+            [120.00081600000001, 30.614426468760453],
+            [120.01638, 30.633578410301354],
+            [120.03030300000002, 30.637749397565525],
+            [120.04624300000002, 30.636902400151886],
+            [120.066778, 30.645306374486793],
+            [120.07836000000002, 30.65467034588244],
+            [120.087589, 30.65562234297391],
+            [120.093261, 30.64216938406768],
+            [120.11702600000001, 30.642225383896637],
+            [120.12040699999999, 30.64770236716845],
+            [120.137925, 30.652670351992548],
+            [120.166223, 30.65021035950745],
+            [120.17130700000001, 30.664872314709275],
+            [120.146326, 30.67583828119119],
+            [120.138288, 30.697179215930415],
+            [120.11998100000001, 30.69825121265118],
+            [120.12015700000002, 30.705079191761854],
+            [120.074341, 30.710850174103005],
+            [120.072663, 30.717145154837347],
+            [120.08516, 30.733594104478815],
+            [120.09036799999998, 30.747284062548427],
+            [120.12164700000001, 30.791547926859234],
+            [120.12870899999999, 30.808595874552424],
+            [120.13560799999999, 30.814384856784663],
+            [120.150784, 30.80509288530251],
+            [120.164082, 30.809488871811823],
+            [120.189888, 30.79692691035818],
+            [120.20145799999999, 30.80598688255909],
+            [120.21901300000002, 30.80382088920577],
+            [120.225199, 30.813224860345198],
+            [120.21818700000001, 30.821308835529347],
+            [120.22965600000002, 30.834012796519364],
+            [120.250417, 30.83269180057642],
+            [120.27102700000002, 30.83803678415989],
+            [120.29022200000001, 30.838093783984803],
+            [120.31372500000002, 30.8500467472633],
+            [120.331217, 30.8550207319786],
+            [120.326271, 30.87132468186218],
+            [120.344202, 30.87032668493057],
+            [120.341773, 30.892999615199006],
+            [120.358401, 30.89202661819244]
+          ]
+        ]
+      },
+      'properties': {
+        'adcode': 330502,
+        'name': '吴兴区',
+        'center': [120.107385, 30.783065],
+        'centroid': [120.084733, 30.803374],
+        'childrenNum': 0,
+        'level': 'district',
+        'subFeatureIndex': 0,
+        'acroutes': [100000, 330000, 330500]
+      }
+    }, {
+      'type': 'Feature',
+      'geometry': {
+        'type': 'Polygon',
+        'coordinates': [
+          [
+            [120.358401, 30.891732],
+            [120.341773, 30.892705],
+            [120.344202, 30.870032],
+            [120.326271, 30.87103],
+            [120.331217, 30.854726],
+            [120.313725, 30.849752],
+            [120.290222, 30.837799],
+            [120.271027, 30.837742],
+            [120.250417, 30.832397],
+            [120.229656, 30.833718],
+            [120.218187, 30.821014],
+            [120.225199, 30.81293],
+            [120.219013, 30.803526],
+            [120.201458, 30.805692],
+            [120.189888, 30.796632],
+            [120.164082, 30.809194],
+            [120.150784, 30.804798],
+            [120.135608, 30.81409],
+            [120.128709, 30.808301],
+            [120.121647, 30.791253],
+            [120.090368, 30.746989],
+            [120.08516, 30.733299],
+            [120.072663, 30.71685],
+            [120.074341, 30.710555],
+            [120.120157, 30.704784],
+            [120.119981, 30.697956],
+            [120.138288, 30.696884],
+            [120.146326, 30.675543],
+            [120.171307, 30.664577],
+            [120.18235, 30.679067],
+            [120.221567, 30.673665],
+            [120.214242, 30.662891],
+            [120.210711, 30.642947],
+            [120.230658, 30.635727],
+            [120.246923, 30.633912],
+            [120.26697, 30.626854],
+            [120.27278, 30.636607],
+            [120.27263, 30.649222],
+            [120.292288, 30.669834],
+            [120.307727, 30.673302],
+            [120.30973, 30.655343],
+            [120.304834, 30.647818],
+            [120.318621, 30.639607],
+            [120.323604, 30.651585],
+            [120.343626, 30.645528],
+            [120.344991, 30.662738],
+            [120.353993, 30.662795],
+            [120.364624, 30.654553],
+            [120.377897, 30.65723],
+            [120.387926, 30.670697],
+            [120.406082, 30.670746],
+            [120.407961, 30.675672],
+            [120.455028, 30.685541],
+            [120.441155, 30.695578],
+            [120.444485, 30.701753],
+            [120.462892, 30.701407],
+            [120.475275, 30.710708],
+            [120.486257, 30.727996],
+            [120.471907, 30.746691],
+            [120.469703, 30.76493],
+            [120.487935, 30.764447],
+            [120.477003, 30.785591],
+            [120.477229, 30.800015],
+            [120.455279, 30.816851],
+            [120.461014, 30.828268],
+            [120.4604, 30.839859],
+            [120.454452, 30.849631],
+            [120.441305, 30.856295],
+            [120.452361, 30.8679],
+            [120.43527, 30.886478],
+            [120.43835, 30.906646],
+            [120.435007, 30.920818],
+            [120.418015, 30.925555],
+            [120.423487, 30.903067],
+            [120.3947, 30.890775],
+            [120.379412, 30.890775],
+            [120.364662, 30.880468],
+            [120.358401, 30.891732]
+          ]
+        ]
+      },
+      'properties': {
+        'adcode': 330503,
+        'name': '南浔区',
+        'center': [120.417195, 30.872742],
+        'centroid': [120.305513, 30.757826],
+        'childrenNum': 0,
+        'level': 'district',
+        'subFeatureIndex': 1,
+        'acroutes': [100000, 330000, 330500]
+      }
+    }, {
+      'type': 'Feature',
+      'geometry': {
+        'type': 'Polygon',
+        'coordinates': [
+          [
+            [119.86576, 30.692015],
+            [119.845813, 30.694659],
+            [119.843259, 30.68388],
+            [119.834757, 30.67618],
+            [119.839565, 30.666053],
+            [119.829598, 30.657472],
+            [119.827369, 30.645423],
+            [119.812356, 30.645124],
+            [119.793186, 30.610331],
+            [119.805006, 30.591134],
+            [119.801939, 30.5709],
+            [119.808212, 30.55473],
+            [119.772025, 30.546431],
+            [119.76639, 30.514073],
+            [119.784058, 30.51102],
+            [119.794188, 30.505624],
+            [119.815875, 30.510665],
+            [119.836034, 30.496594],
+            [119.85111, 30.502022],
+            [119.867112, 30.477778],
+            [119.877806, 30.477932],
+            [119.872597, 30.46249],
+            [119.891016, 30.45613],
+            [119.909322, 30.456324],
+            [119.921555, 30.462539],
+            [119.93141, 30.456462],
+            [119.934978, 30.44678],
+            [119.95073, 30.444234],
+            [119.955876, 30.433661],
+            [119.965029, 30.431568],
+            [119.982973, 30.445276],
+            [120.003695, 30.444282],
+            [120.013813, 30.435593],
+            [120.041548, 30.433378],
+            [120.046105, 30.427437],
+            [120.061932, 30.429321],
+            [120.068105, 30.445939],
+            [120.059265, 30.459015],
+            [120.059478, 30.473431],
+            [120.068281, 30.49661],
+            [120.099246, 30.495794],
+            [120.096754, 30.486762],
+            [120.111229, 30.476194],
+            [120.146013, 30.482698],
+            [120.14982, 30.467508],
+            [120.17505, 30.476825],
+            [120.184504, 30.493774],
+            [120.185093, 30.50262],
+            [120.212527, 30.509776],
+            [120.239285, 30.505237],
+            [120.27745, 30.504841],
+            [120.299813, 30.517812],
+            [120.30198, 30.529999],
+            [120.299513, 30.557071],
+            [120.319121, 30.579068],
+            [120.302581, 30.595621],
+            [120.303169, 30.609032],
+            [120.30938, 30.612638],
+            [120.332707, 30.615059],
+            [120.343626, 30.645528],
+            [120.323604, 30.651585],
+            [120.318621, 30.639607],
+            [120.304834, 30.647818],
+            [120.30973, 30.655343],
+            [120.307727, 30.673302],
+            [120.292288, 30.669834],
+            [120.27263, 30.649222],
+            [120.27278, 30.636607],
+            [120.26697, 30.626854],
+            [120.246923, 30.633912],
+            [120.230658, 30.635727],
+            [120.210711, 30.642947],
+            [120.214242, 30.662891],
+            [120.221567, 30.673665],
+            [120.18235, 30.679067],
+            [120.171307, 30.664577],
+            [120.166223, 30.649915],
+            [120.137925, 30.652375],
+            [120.120407, 30.647407],
+            [120.117026, 30.64193],
+            [120.093261, 30.641874],
+            [120.087589, 30.655327],
+            [120.07836, 30.654375],
+            [120.066778, 30.645011],
+            [120.046243, 30.636607],
+            [120.030303, 30.637454],
+            [120.01638, 30.633283],
+            [120.000816, 30.614131],
+            [119.991374, 30.621102],
+            [119.967171, 30.617213],
+            [119.95649, 30.630484],
+            [119.938196, 30.631565],
+            [119.917974, 30.613227],
+            [119.905853, 30.613373],
+            [119.89958, 30.622659],
+            [119.914769, 30.638962],
+            [119.910299, 30.652746],
+            [119.877806, 30.672786],
+            [119.86576, 30.692015]
+          ]
+        ]
+      },
+      'properties': {
+        'adcode': 330521,
+        'name': '德清县',
+        'center': [119.967662, 30.534927],
+        'centroid': [120.043755, 30.561662],
+        'childrenNum': 0,
+        'level': 'district',
+        'subFeatureIndex': 2,
+        'acroutes': [100000, 330000, 330500]
+      }
+    }, {
+      'type': 'Feature',
+      'geometry': {
+        'type': 'Polygon',
+        'coordinates': [
+          [
+            [119.893453661377, 30.72224456950103],
+            [119.91849666137699, 30.723590567440745],
+            [119.94038366137698, 30.7345745506252],
+            [119.94822166137699, 30.749472527808898],
+            [119.94556766137701, 30.772037493231625],
+            [119.947170661377, 30.789821465964312],
+            [119.955572661377, 30.800516449559417],
+            [119.953781661377, 30.808117437897216],
+            [119.95662689208984, 30.81926274444228],
+            [119.94598388671874, 30.8238326767625],
+            [119.90306854248045, 30.828697204445906],
+            [119.91680145263672, 30.870993458914445],
+            [119.94272232055663, 30.915627446088592],
+            [119.93379592895508, 30.92357983343157],
+            [119.95971679687499, 30.938010255039625],
+            [119.9578285217285, 30.953763432093723],
+            [119.96229171752928, 30.96642298146033],
+            [119.97619628906249, 30.971869015455276],
+            [119.99507904052734, 30.973929595952995],
+            [120.01070022583008, 30.968189296794247],
+            [120.06065368652344, 30.935948899578104],
+            [120.10562896728514, 30.962595852810715],
+            [120.094571661377, 30.97338118369163],
+            [120.052612661377, 31.005882133555186],
+            [120.017189661377, 31.017950114926915],
+            [120.00190166137699, 31.027270100535958],
+            [119.98869166137699, 31.05936705094543],
+            [119.969934661377, 31.074424027666183],
+            [119.94641866137701, 31.10637697823078],
+            [119.93692766137701, 31.146703915774424],
+            [119.92123866137699, 31.161352893068887],
+            [119.921125661377, 31.170530878838303],
+            [119.900377661377, 31.169326880705363],
+            [119.87852766137698, 31.16096789366577],
+            [119.86680766137701, 31.16846088204818],
+            [119.84231666137701, 31.1689018813644],
+            [119.826626661377, 31.173345874472865],
+            [119.823484661377, 31.15427690403769],
+            [119.810023661377, 31.148668912729345],
+            [119.80115866137702, 31.1564919006044],
+            [119.79144166137698, 31.156764900181194],
+            [119.79355766137701, 31.16820388244672],
+            [119.77949666137701, 31.178928865813806],
+            [119.755518661377, 31.170923878228834],
+            [119.74068066137701, 31.17356287413633],
+            [119.71328366137699, 31.167762883130496],
+            [119.704042661377, 31.15204690749405],
+            [119.68293166137701, 31.16064689416339],
+            [119.67821166137699, 31.1683328822467],
+            [119.66389966137702, 31.166102885704408],
+            [119.662684661377, 31.15999789516949],
+            [119.64172366137701, 31.14825991336315],
+            [119.63829266137698, 31.135677932858165],
+            [119.62395566137701, 31.130413941012346],
+            [119.624344661377, 31.11615296309692],
+            [119.649311661377, 31.105252979970476],
+            [119.63487466137701, 31.093917997511912],
+            [119.62972866137702, 31.08568901024299],
+            [119.63511266137701, 31.06204104681202],
+            [119.631506661377, 31.046468070880195],
+            [119.634198661377, 31.019621112346993],
+            [119.62419366137699, 31.005464134200306],
+            [119.588520661377, 30.979289174581506],
+            [119.58058166137701, 30.96727219311026],
+            [119.58232266137699, 30.94758322345439],
+            [119.580519661377, 30.92931325159596],
+            [119.564003661377, 30.919381266888134],
+            [119.55649066137698, 30.89685130156086],
+            [119.559132661377, 30.87134634078444],
+            [119.57343266137701, 30.854325366944504],
+            [119.59709766137699, 30.862188354861296],
+            [119.60158066137701, 30.853971367488455],
+            [119.645567661377, 30.866558348144626],
+            [119.658514661377, 30.86657434812006],
+            [119.679300661377, 30.853295368527096],
+            [119.69644266137699, 30.8527963692938],
+            [119.703980661377, 30.85669936329666],
+            [119.71724066137699, 30.854728366325254],
+            [119.722474661377, 30.86038535763219],
+            [119.74227066137698, 30.861955355219393],
+            [119.76279266137699, 30.843194384044978],
+            [119.786658661377, 30.8481843763796],
+            [119.79722666137701, 30.856361363815992],
+            [119.79835366137699, 30.86619634870104],
+            [119.816522661377, 30.862888353785422],
+            [119.826852661377, 30.83842939136373],
+            [119.797339661377, 30.836336394578144],
+            [119.79345766137699, 30.831546401933867],
+            [119.80045766137698, 30.818246422352246],
+            [119.80298666137699, 30.79393745965146],
+            [119.82946966137699, 30.786173471558808],
+            [119.84872766137701, 30.765005504009462],
+            [119.85032966137699, 30.752421523291346],
+            [119.861774661377, 30.74420353587952],
+            [119.88456366137702, 30.744872534854874],
+            [119.880681661377, 30.73567854893479],
+            [119.885865661377, 30.72467856577541],
+            [119.893453661377, 30.72224456950103]
+          ]
+        ]
+      },
+      'properties': {
+        'adcode': 330522,
+        'name': '长兴县',
+        'center': [119.910122, 31.00475],
+        'centroid': [119.813123, 30.977176],
+        'childrenNum': 0,
+        'level': 'district',
+        'subFeatureIndex': 3,
+        'acroutes': [100000, 330000, 330500]
+      }
+    }, {
+      'type': 'Feature',
+      'geometry': {
+        'type': 'Polygon',
+        'coordinates': [
+          [
+            [119.573261, 30.854178],
+            [119.575752, 30.829741],
+            [119.554529, 30.825627],
+            [119.524502, 30.776023],
+            [119.507123, 30.769627],
+            [119.491521, 30.777231],
+            [119.479576, 30.771246],
+            [119.478787, 30.75308],
+            [119.484597, 30.744604],
+            [119.479688, 30.736828],
+            [119.482744, 30.704462],
+            [119.466003, 30.683913],
+            [119.452918, 30.671625],
+            [119.448297, 30.655835],
+            [119.433635, 30.644471],
+            [119.417169, 30.642221],
+            [119.405161, 30.649326],
+            [119.391513, 30.685235],
+            [119.372368, 30.680373],
+            [119.35455, 30.666923],
+            [119.343143, 30.664198],
+            [119.333639, 30.652198],
+            [119.323134, 30.63029],
+            [119.31075, 30.620739],
+            [119.291229, 30.616027],
+            [119.278382, 30.617608],
+            [119.255005, 30.615139],
+            [119.245852, 30.618867],
+            [119.240079, 30.604949],
+            [119.253653, 30.596468],
+            [119.254128, 30.588656],
+            [119.264759, 30.582046],
+            [119.264759, 30.573442],
+            [119.245501, 30.561415],
+            [119.236937, 30.546729],
+            [119.242571, 30.530023],
+            [119.261904, 30.525541],
+            [119.261654, 30.517748],
+            [119.272134, 30.510511],
+            [119.287949, 30.512619],
+            [119.313643, 30.531194],
+            [119.32654, 30.533003],
+            [119.336344, 30.507143],
+            [119.330333, 30.488232],
+            [119.334478, 30.478093],
+            [119.336143, 30.445567],
+            [119.349692, 30.438826],
+            [119.352972, 30.42485],
+            [119.349516, 30.4086],
+            [119.363215, 30.409473],
+            [119.368937, 30.39973],
+            [119.371454, 30.383928],
+            [119.393817, 30.383023],
+            [119.403058, 30.374352],
+            [119.418146, 30.3761],
+            [119.435713, 30.391498],
+            [119.441047, 30.39246],
+            [119.450238, 30.410823],
+            [119.467004, 30.408139],
+            [119.490407, 30.408204],
+            [119.513146, 30.401679],
+            [119.533455, 30.409409],
+            [119.535296, 30.424058],
+            [119.551736, 30.439772],
+            [119.565923, 30.443377],
+            [119.5818, 30.423629],
+            [119.59789, 30.422449],
+            [119.618025, 30.427316],
+            [119.637095, 30.441833],
+            [119.645672, 30.429846],
+            [119.631047, 30.42329],
+            [119.635968, 30.403611],
+            [119.649779, 30.395452],
+            [119.667396, 30.399107],
+            [119.681095, 30.408721],
+            [119.696408, 30.426685],
+            [119.707277, 30.430024],
+            [119.704159, 30.443199],
+            [119.694931, 30.452736],
+            [119.708892, 30.487966],
+            [119.70918, 30.508823],
+            [119.704873, 30.525977],
+            [119.694993, 30.542345],
+            [119.692402, 30.556143],
+            [119.711735, 30.566516],
+            [119.729052, 30.551767],
+            [119.768957, 30.551356],
+            [119.772025, 30.546431],
+            [119.808212, 30.55473],
+            [119.801939, 30.5709],
+            [119.805006, 30.591134],
+            [119.793186, 30.610331],
+            [119.812356, 30.645124],
+            [119.827369, 30.645423],
+            [119.829598, 30.657472],
+            [119.839565, 30.666053],
+            [119.834757, 30.67618],
+            [119.843259, 30.68388],
+            [119.845813, 30.694659],
+            [119.86576, 30.692015],
+            [119.873886, 30.705606],
+            [119.893282, 30.722097],
+            [119.885694, 30.724531],
+            [119.88051, 30.735531],
+            [119.884392, 30.744725],
+            [119.861603, 30.744056],
+            [119.850158, 30.752274],
+            [119.848556, 30.764858],
+            [119.829298, 30.786026],
+            [119.802815, 30.79379],
+            [119.800286, 30.818099],
+            [119.793286, 30.831399],
+            [119.797168, 30.836189],
+            [119.826681, 30.838282],
+            [119.816351, 30.862741],
+            [119.798182, 30.866049],
+            [119.797055, 30.856214],
+            [119.786487, 30.848037],
+            [119.762621, 30.843047],
+            [119.742099, 30.861808],
+            [119.722303, 30.860238],
+            [119.717069, 30.854581],
+            [119.703809, 30.856552],
+            [119.696271, 30.852649],
+            [119.679129, 30.853148],
+            [119.658343, 30.866427],
+            [119.645396, 30.866411],
+            [119.601409, 30.853824],
+            [119.596926, 30.862041],
+            [119.573261, 30.854178]
+          ]
+        ]
+      },
+      'properties': {
+        'adcode': 330523,
+        'name': '安吉县',
+        'center': [119.687891, 30.631974],
+        'centroid': [119.577238, 30.620374],
+        'childrenNum': 0,
+        'level': 'district',
+        'subFeatureIndex': 4,
+        'acroutes': '100000,330000,330500'
+      }
+    }, {
+      'type': 'Feature',
+      'geometry': {
+        'type': 'Polygon',
+        'coordinates': [
+          [
+            [119.96194839477539, 30.96627578704089],
+            [119.9580430984498, 30.953616218162193],
+            [119.95975971221932, 30.938304730762635],
+            [119.93366718292246, 30.92328531235632],
+            [119.94293689727793, 30.915921990741882],
+            [119.91718769073498, 30.871140800014885],
+            [119.9031114578248, 30.82899201638426],
+            [119.946370124817, 30.823685262985343],
+            [119.9567127227784, 30.819152178869203],
+            [119.96250629425055, 30.818083371767727],
+            [119.98108863830574, 30.815171733469935],
+            [119.96662616729746, 30.755555904209483],
+            [119.96628284454354, 30.719554693895013],
+            [119.9767541885377, 30.71040470249633],
+            [120.0316858291627, 30.732835394548125],
+            [120.04541873931896, 30.723391528686655],
+            [120.04498958587654, 30.744417622667576],
+            [120.05696296691902, 30.768020400049586],
+            [120.07030963897714, 30.774731383083612],
+            [120.06275653839121, 30.801128289351826],
+            [120.06408691406257, 30.813955453363064],
+            [120.04988193511971, 30.871877502119823],
+            [120.07700443267834, 30.8827063695659],
+            [120.10206699371348, 30.89927879624042],
+            [120.12318134307871, 30.894712458250368],
+            [120.13966083526621, 30.887567911949667],
+            [120.15493869781504, 30.91908828849523],
+            [120.16940116882324, 30.915701082336962],
+            [120.18120288848887, 30.925935969383417],
+            [120.19484996795664, 30.931752431473733],
+            [120.16043186187753, 30.93653786281706],
+            [120.13562679290781, 30.943678753352515],
+            [120.10524272918713, 30.96266945288506],
+            [120.06078243255625, 30.93587527891804],
+            [120.01022815704356, 30.96833648826376],
+            [119.99495029449473, 30.97407677857212],
+            [119.97658252716074, 30.971869015455223],
+            [119.96194839477539, 30.96627578704089]
+          ]
+        ]
+      },
+      'properties': {
+        'adcode': 330552,
+        'name': '南太湖新区',
+        'center': '',
+        'childrenNum': 0,
+        'acroutes': '100000,330000,330500',
+        'subFeatureIndex': 5,
+        'level': 'district',
+        'centroid': [120.018821, 30.862818]
+      }
+    }]
+  }
+}
+

+ 49 - 0
src/directive/clipboard/clipboard.js

@@ -0,0 +1,49 @@
+// Inspired by https://github.com/Inndy/vue-clipboard2
+const Clipboard = require('clipboard')
+if (!Clipboard) {
+  throw new Error('you should npm install `clipboard` --save at first ')
+}
+
+export default {
+  bind(el, binding) {
+    if (binding.arg === 'success') {
+      el._v_clipboard_success = binding.value
+    } else if (binding.arg === 'error') {
+      el._v_clipboard_error = binding.value
+    } else {
+      const clipboard = new Clipboard(el, {
+        text() { return binding.value },
+        action() { return binding.arg === 'cut' ? 'cut' : 'copy' }
+      })
+      clipboard.on('success', e => {
+        const callback = el._v_clipboard_success
+        callback && callback(e) // eslint-disable-line
+      })
+      clipboard.on('error', e => {
+        const callback = el._v_clipboard_error
+        callback && callback(e) // eslint-disable-line
+      })
+      el._v_clipboard = clipboard
+    }
+  },
+  update(el, binding) {
+    if (binding.arg === 'success') {
+      el._v_clipboard_success = binding.value
+    } else if (binding.arg === 'error') {
+      el._v_clipboard_error = binding.value
+    } else {
+      el._v_clipboard.text = function() { return binding.value }
+      el._v_clipboard.action = function() { return binding.arg === 'cut' ? 'cut' : 'copy' }
+    }
+  },
+  unbind(el, binding) {
+    if (binding.arg === 'success') {
+      delete el._v_clipboard_success
+    } else if (binding.arg === 'error') {
+      delete el._v_clipboard_error
+    } else {
+      el._v_clipboard.destroy()
+      delete el._v_clipboard
+    }
+  }
+}

+ 13 - 0
src/directive/clipboard/index.js

@@ -0,0 +1,13 @@
+import Clipboard from './clipboard'
+
+const install = function(Vue) {
+  Vue.directive('Clipboard', Clipboard)
+}
+
+if (window.Vue) {
+  window.clipboard = Clipboard
+  Vue.use(install); // eslint-disable-line
+}
+
+Clipboard.install = install
+export default Clipboard

+ 77 - 0
src/directive/el-drag-dialog/drag.js

@@ -0,0 +1,77 @@
+export default {
+  bind(el, binding, vnode) {
+    const dialogHeaderEl = el.querySelector('.el-dialog__header')
+    const dragDom = el.querySelector('.el-dialog')
+    dialogHeaderEl.style.cssText += ';cursor:move;'
+    dragDom.style.cssText += ';top:0px;'
+
+    // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
+    const getStyle = (function() {
+      if (window.document.currentStyle) {
+        return (dom, attr) => dom.currentStyle[attr]
+      } else {
+        return (dom, attr) => getComputedStyle(dom, false)[attr]
+      }
+    })()
+
+    dialogHeaderEl.onmousedown = (e) => {
+      // 鼠标按下,计算当前元素距离可视区的距离
+      const disX = e.clientX - dialogHeaderEl.offsetLeft
+      const disY = e.clientY - dialogHeaderEl.offsetTop
+
+      const dragDomWidth = dragDom.offsetWidth
+      const dragDomHeight = dragDom.offsetHeight
+
+      const screenWidth = document.body.clientWidth
+      const screenHeight = document.body.clientHeight
+
+      const minDragDomLeft = dragDom.offsetLeft
+      const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth
+
+      const minDragDomTop = dragDom.offsetTop
+      const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomHeight
+
+      // 获取到的值带px 正则匹配替换
+      let styL = getStyle(dragDom, 'left')
+      let styT = getStyle(dragDom, 'top')
+
+      if (styL.includes('%')) {
+        styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100)
+        styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100)
+      } else {
+        styL = +styL.replace(/\px/g, '')
+        styT = +styT.replace(/\px/g, '')
+      }
+
+      document.onmousemove = function(e) {
+        // 通过事件委托,计算移动的距离
+        let left = e.clientX - disX
+        let top = e.clientY - disY
+
+        // 边界处理
+        if (-(left) > minDragDomLeft) {
+          left = -minDragDomLeft
+        } else if (left > maxDragDomLeft) {
+          left = maxDragDomLeft
+        }
+
+        if (-(top) > minDragDomTop) {
+          top = -minDragDomTop
+        } else if (top > maxDragDomTop) {
+          top = maxDragDomTop
+        }
+
+        // 移动当前元素
+        dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`
+
+        // emit onDrag event
+        vnode.child.$emit('dragDialog')
+      }
+
+      document.onmouseup = function(e) {
+        document.onmousemove = null
+        document.onmouseup = null
+      }
+    }
+  }
+}

+ 13 - 0
src/directive/el-drag-dialog/index.js

@@ -0,0 +1,13 @@
+import drag from './drag'
+
+const install = function(Vue) {
+  Vue.directive('el-drag-dialog', drag)
+}
+
+if (window.Vue) {
+  window['el-drag-dialog'] = drag
+  Vue.use(install); // eslint-disable-line
+}
+
+drag.install = install
+export default drag

+ 41 - 0
src/directive/el-table/adaptive.js

@@ -0,0 +1,41 @@
+import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event'
+
+/**
+ * How to use
+ * <el-table height="100px" v-el-height-adaptive-table="{bottomOffset: 30}">...</el-table>
+ * el-table height is must be set
+ * bottomOffset: 30(default)   // The height of the table from the bottom of the page.
+ */
+
+const doResize = (el, binding, vnode) => {
+  const { componentInstance: $table } = vnode
+
+  const { value } = binding
+
+  if (!$table.height) {
+    throw new Error(`el-$table must set the height. Such as height='100px'`)
+  }
+  const bottomOffset = (value && value.bottomOffset) || 30
+
+  if (!$table) return
+
+  const height = window.innerHeight - el.getBoundingClientRect().top - bottomOffset
+  $table.layout.setHeight(height)
+  $table.doLayout()
+}
+
+export default {
+  bind(el, binding, vnode) {
+    el.resizeListener = () => {
+      doResize(el, binding, vnode)
+    }
+    // parameter 1 is must be "Element" type
+    addResizeListener(window.document.body, el.resizeListener)
+  },
+  inserted(el, binding, vnode) {
+    doResize(el, binding, vnode)
+  },
+  unbind(el) {
+    removeResizeListener(window.document.body, el.resizeListener)
+  }
+}

+ 13 - 0
src/directive/el-table/index.js

@@ -0,0 +1,13 @@
+import adaptive from './adaptive'
+
+const install = function(Vue) {
+  Vue.directive('el-height-adaptive-table', adaptive)
+}
+
+if (window.Vue) {
+  window['el-height-adaptive-table'] = adaptive
+  Vue.use(install); // eslint-disable-line
+}
+
+adaptive.install = install
+export default adaptive

+ 13 - 0
src/directive/permission/index.js

@@ -0,0 +1,13 @@
+import permission from './permission'
+
+const install = function(Vue) {
+  Vue.directive('permission', permission)
+}
+
+if (window.Vue) {
+  window['permission'] = permission
+  Vue.use(install); // eslint-disable-line
+}
+
+permission.install = install
+export default permission

+ 23 - 0
src/directive/permission/permission.js

@@ -0,0 +1,23 @@
+import store from '@/store'
+
+export default {
+  inserted(el, binding, vnode) {
+    const { value } = binding
+    const roles = store.getters && store.getters.permissions
+
+    if (value && value instanceof Array && value.length > 0) {
+      const permissionRoles = value
+
+      const hasPermission = roles.some(role => {
+        return permissionRoles.includes(role)
+      })
+
+      if (!hasPermission) {
+        el.parentNode && el.parentNode.removeChild(el)
+      }
+    } else {
+      throw new Error(`need roles! Like v-permission="['admin','editor']"`)
+    }
+  }
+}
+

+ 13 - 0
src/directive/special_input_code/index.js

@@ -0,0 +1,13 @@
+import speinput from './special_input_code'
+
+const install = function(Vue) {
+  Vue.directive('speinput', speinput)
+}
+
+if (window.Vue) {
+  window.waves = speinput
+  Vue.use(install); // eslint-disable-line
+}
+
+speinput.install = install
+export default speinput

+ 11 - 0
src/directive/special_input_code/special_input_code.js

@@ -0,0 +1,11 @@
+export default {
+  inserted: function(el) {
+    var parent = el.parentNode
+    el.onfocus = function() {
+      parent.className = 'special_input_layout special_input_code'
+    }
+    el.onblur = function() {
+      parent.className = 'special_input_layout'
+    }
+  }
+}

+ 91 - 0
src/directive/sticky.js

@@ -0,0 +1,91 @@
+const vueSticky = {}
+let listenAction
+vueSticky.install = Vue => {
+  Vue.directive('sticky', {
+    inserted(el, binding) {
+      const params = binding.value || {}
+      const stickyTop = params.stickyTop || 0
+      const zIndex = params.zIndex || 1000
+      const elStyle = el.style
+
+      elStyle.position = '-webkit-sticky'
+      elStyle.position = 'sticky'
+      // if the browser support css sticky(Currently Safari, Firefox and Chrome Canary)
+      // if (~elStyle.position.indexOf('sticky')) {
+      //     elStyle.top = `${stickyTop}px`;
+      //     elStyle.zIndex = zIndex;
+      //     return
+      // }
+      const elHeight = el.getBoundingClientRect().height
+      const elWidth = el.getBoundingClientRect().width
+      elStyle.cssText = `top: ${stickyTop}px; z-index: ${zIndex}`
+
+      const parentElm = el.parentNode || document.documentElement
+      const placeholder = document.createElement('div')
+      placeholder.style.display = 'none'
+      placeholder.style.width = `${elWidth}px`
+      placeholder.style.height = `${elHeight}px`
+      parentElm.insertBefore(placeholder, el)
+
+      let active = false
+
+      const getScroll = (target, top) => {
+        const prop = top ? 'pageYOffset' : 'pageXOffset'
+        const method = top ? 'scrollTop' : 'scrollLeft'
+        let ret = target[prop]
+        if (typeof ret !== 'number') {
+          ret = window.document.documentElement[method]
+        }
+        return ret
+      }
+
+      const sticky = () => {
+        if (active) {
+          return
+        }
+        if (!elStyle.height) {
+          elStyle.height = `${el.offsetHeight}px`
+        }
+
+        elStyle.position = 'fixed'
+        elStyle.width = `${elWidth}px`
+        placeholder.style.display = 'inline-block'
+        active = true
+      }
+
+      const reset = () => {
+        if (!active) {
+          return
+        }
+
+        elStyle.position = ''
+        placeholder.style.display = 'none'
+        active = false
+      }
+
+      const check = () => {
+        const scrollTop = getScroll(window, true)
+        const offsetTop = el.getBoundingClientRect().top
+        if (offsetTop < stickyTop) {
+          sticky()
+        } else {
+          if (scrollTop < elHeight + stickyTop) {
+            reset()
+          }
+        }
+      }
+      listenAction = () => {
+        check()
+      }
+
+      window.addEventListener('scroll', listenAction)
+    },
+
+    unbind() {
+      window.removeEventListener('scroll', listenAction)
+    }
+  })
+}
+
+export default vueSticky
+

+ 13 - 0
src/directive/waves/index.js

@@ -0,0 +1,13 @@
+import waves from './waves'
+
+const install = function(Vue) {
+  Vue.directive('waves', waves)
+}
+
+if (window.Vue) {
+  window.waves = waves
+  Vue.use(install); // eslint-disable-line
+}
+
+waves.install = install
+export default waves

+ 26 - 0
src/directive/waves/waves.css

@@ -0,0 +1,26 @@
+.waves-ripple {
+    position: absolute;
+    border-radius: 100%;
+    background-color: rgba(0, 0, 0, 0.15);
+    background-clip: padding-box;
+    pointer-events: none;
+    -webkit-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+    -webkit-transform: scale(0);
+    -ms-transform: scale(0);
+    transform: scale(0);
+    opacity: 1;
+}
+
+.waves-ripple.z-active {
+    opacity: 0;
+    -webkit-transform: scale(2);
+    -ms-transform: scale(2);
+    transform: scale(2);
+    -webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
+    transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
+    transition: opacity 1.2s ease-out, transform 0.6s ease-out;
+    transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
+}

+ 72 - 0
src/directive/waves/waves.js

@@ -0,0 +1,72 @@
+import './waves.css'
+
+const context = '@@wavesContext'
+
+function handleClick(el, binding) {
+  function handle(e) {
+    const customOpts = Object.assign({}, binding.value)
+    const opts = Object.assign({
+      ele: el, // 波纹作用元素
+      type: 'hit', // hit 点击位置扩散 center中心点扩展
+      color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
+    },
+    customOpts
+    )
+    const target = opts.ele
+    if (target) {
+      target.style.position = 'relative'
+      target.style.overflow = 'hidden'
+      const rect = target.getBoundingClientRect()
+      let ripple = target.querySelector('.waves-ripple')
+      if (!ripple) {
+        ripple = document.createElement('span')
+        ripple.className = 'waves-ripple'
+        ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'
+        target.appendChild(ripple)
+      } else {
+        ripple.className = 'waves-ripple'
+      }
+      switch (opts.type) {
+        case 'center':
+          ripple.style.top = rect.height / 2 - ripple.offsetHeight / 2 + 'px'
+          ripple.style.left = rect.width / 2 - ripple.offsetWidth / 2 + 'px'
+          break
+        default:
+          ripple.style.top =
+            (e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop ||
+              document.body.scrollTop) + 'px'
+          ripple.style.left =
+            (e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft ||
+              document.body.scrollLeft) + 'px'
+      }
+      ripple.style.backgroundColor = opts.color
+      ripple.className = 'waves-ripple z-active'
+      return false
+    }
+  }
+
+  if (!el[context]) {
+    el[context] = {
+      removeHandle: handle
+    }
+  } else {
+    el[context].removeHandle = handle
+  }
+
+  return handle
+}
+
+export default {
+  bind(el, binding) {
+    el.addEventListener('click', handleClick(el, binding), false)
+  },
+  update(el, binding) {
+    el.removeEventListener('click', el[context].removeHandle, false)
+    el.addEventListener('click', handleClick(el, binding), false)
+  },
+  unbind(el) {
+    el.removeEventListener('click', el[context].removeHandle, false)
+    el[context] = null
+    delete el[context]
+  }
+}

+ 82 - 0
src/filters/index.js

@@ -0,0 +1,82 @@
+// import parseTime, formatTime and set to filter
+export { parseTime, formatTime } from '@/utils'
+
+/**
+ * Show plural label if time is plural number
+ * @param {number} time
+ * @param {string} label
+ * @return {string}
+ */
+function pluralize(time, label) {
+  if (time === 1) {
+    return time + label
+  }
+  return time + label + 's'
+}
+
+/**
+ * @param {number} time
+ */
+export function timeAgo(time) {
+  const between = Date.now() / 1000 - Number(time)
+  if (between < 3600) {
+    return pluralize(~~(between / 60), ' minute')
+  } else if (between < 86400) {
+    return pluralize(~~(between / 3600), ' hour')
+  } else {
+    return pluralize(~~(between / 86400), ' day')
+  }
+}
+
+/**
+ * Number formatting
+ * like 10000 => 10k
+ * @param {number} num
+ * @param {number} digits
+ */
+export function numberFormatter(num, digits) {
+  const si = [
+    { value: 1E18, symbol: 'E' },
+    { value: 1E15, symbol: 'P' },
+    { value: 1E12, symbol: 'T' },
+    { value: 1E9, symbol: 'G' },
+    { value: 1E6, symbol: 'M' },
+    { value: 1E3, symbol: 'k' }
+  ]
+  for (let i = 0; i < si.length; i++) {
+    if (num >= si[i].value) {
+      return (num / si[i].value + 0.1).toFixed(digits).replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + si[i].symbol
+    }
+  }
+  return num.toString()
+}
+
+/**
+ * 10000 => "10,000"
+ * @param {number} num
+ */
+export function toThousandFilter(num) {
+  return (+num || 0).toString().replace(/^-?\d+/g, m => m.replace(/(?=(?!\b)(\d{3})+$)/g, ','))
+}
+
+/**
+ * Upper case first char
+ * @param {String} string
+ */
+export function uppercaseFirst(string) {
+  return string.charAt(0).toUpperCase() + string.slice(1)
+}
+
+/**
+ * 1.5000000 => 1.50
+ */
+export function numFilter(value) {
+  let realVal = ''
+  if (value) {
+    // 截取当前数据到小数点后两位
+    realVal = parseFloat(value).toFixed(2)
+  } else {
+    realVal = '--'
+  }
+  return realVal
+}

+ 9 - 0
src/icons/index.js

@@ -0,0 +1,9 @@
+import Vue from 'vue'
+import SvgIcon from '@/components/SvgIcon'// svg component
+
+// register globally
+Vue.component('svg-icon', SvgIcon)
+
+const req = require.context('./svg', false, /\.svg$/)
+const requireAll = requireContext => requireContext.keys().map(requireContext)
+requireAll(req)

二进制
src/icons/order.png


二进制
src/icons/person.jpg


+ 1 - 0
src/icons/svg/404.svg

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M121.718 73.272v9.953c3.957-7.584 6.199-16.05 6.199-24.995C127.917 26.079 99.273 0 63.958 0 28.644 0 0 26.079 0 58.23c0 .403.028.806.028 1.21l22.97-25.953h13.34l-19.76 27.187h6.42V53.77l13.728-19.477v49.361H22.998V73.272H2.158c5.951 20.284 23.608 36.208 45.998 41.399-1.44 3.3-5.618 11.263-12.565 12.674-8.607 1.764 23.358.428 46.163-13.178 17.519-4.611 31.938-15.849 39.77-30.513h-13.506V73.272H85.02V59.464l22.998-25.977h13.008l-19.429 27.187h6.421v-7.433l13.727-19.402v39.433h-.027zm-78.24 2.822a10.516 10.516 0 0 1-.996-4.535V44.548c0-1.613.332-3.124.996-4.535a11.66 11.66 0 0 1 2.713-3.68c1.134-1.032 2.49-1.864 4.04-2.468 1.55-.605 3.21-.908 4.982-.908h11.292c1.77 0 3.431.303 4.981.908 1.522.604 2.85 1.41 3.986 2.418l-12.26 16.303v-2.898a1.96 1.96 0 0 0-.665-1.512c-.443-.403-.996-.604-1.66-.604-.665 0-1.218.201-1.661.604a1.96 1.96 0 0 0-.664 1.512v9.071L44.364 77.606a10.556 10.556 0 0 1-.886-1.512zm35.73-4.535c0 1.613-.332 3.124-.997 4.535a11.66 11.66 0 0 1-2.712 3.68c-1.134 1.032-2.49 1.864-4.04 2.469-1.55.604-3.21.907-4.982.907H55.185c-1.77 0-3.431-.303-4.981-.907-1.55-.605-2.906-1.437-4.041-2.47a12.49 12.49 0 0 1-1.384-1.512l13.727-18.217v6.375c0 .605.222 1.109.665 1.512.442.403.996.604 1.66.604.664 0 1.218-.201 1.66-.604a1.96 1.96 0 0 0 .665-1.512V53.87L75.97 36.838c.913.932 1.66 1.99 2.214 3.175.664 1.41.996 2.922.996 4.535v27.011h.028z"/></svg>

+ 1 - 0
src/icons/svg/add_up.svg

@@ -0,0 +1 @@
+<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><defs><style/></defs><path d="M180.282 571.574c-8.76 0-17.262-4.726-21.818-12.973-6.526-12.11-2.06-27.141 9.965-33.673l629.15-305.21-68.293-47.247c-10.997-8.246-13.145-23.794-4.98-34.793 8.334-10.91 23.878-13.14 34.786-4.895l99.306 70.526c6.787 5.068 10.48 13.313 9.878 21.732-.603 8.419-5.495 15.98-12.97 20.017l-663.17 323.51c-3.777 2.147-7.812 3.006-11.854 3.006zm661.37 324.282c-13.748 0-24.828-11.081-24.828-24.827V374.598c0-13.745 11.08-24.827 24.828-24.827 13.74 0 24.821 11.082 24.821 24.827v496.43c0 13.66-11.081 24.828-24.821 24.828zm-220.173 0c-13.743 0-24.821-11.081-24.821-24.827V473.902c0-13.745 11.079-24.826 24.821-24.826 13.746 0 24.828 11.082 24.828 24.826V871.03c0 13.66-11.166 24.827-24.828 24.827zm-220.252 0c-13.745 0-24.826-11.081-24.826-24.827V596.484c0-13.745 11.081-24.825 24.826-24.825 13.741 0 24.823 11.081 24.823 24.825v274.46c0 13.744-11.082 24.912-24.823 24.912zm-220.254 0c-13.747 0-24.827-11.081-24.827-24.827v-186.15c0-13.744 11.08-24.826 24.827-24.826 13.746 0 24.826 11.083 24.826 24.827v186.066c0 13.742-11.08 24.91-24.826 24.91zm0 0"/></svg>

文件差异内容过多而无法显示
+ 0 - 0
src/icons/svg/alipay.svg


+ 1 - 0
src/icons/svg/apply.svg

@@ -0,0 +1 @@
+<svg class="icon" viewBox="0 0 1216 1024" xmlns="http://www.w3.org/2000/svg" width="237.5" height="200"><defs><style/></defs><path d="M64 896v10.688C64 899.136 60.48 896 59.456 896H64zm0 0V64h704v126.784h64V53.312C832 23.872 805.376 0 772.544 0H59.52C26.624 0 0 23.872 0 53.312v853.376C0 936.128 26.624 960 59.456 960h323.2v-64H64zm0-832h-4.544C60.48 64 64 60.864 64 53.312V64zm708.544 0C771.52 64 768 60.864 768 53.312V64h4.544zM768 53.312C768 60.864 771.52 64 772.544 64H768V53.312zm0 137.472h64V53.312C832 23.872 805.376 0 772.544 0H59.52C26.624 0 0 23.872 0 53.312v853.376C0 936.128 26.624 960 59.456 960h323.2v-64H64V64h704v126.784zM64 64h-4.544C60.48 64 64 60.864 64 53.312V64zm0 832v10.688C64 899.136 60.48 896 59.456 896H64z"/><path d="M192 512v-64h320v64zm2.176 194.24v-64h192v64zm919.168 135.552h-156.48l-84.032-290.304 34.816-26.56c28.864-22.08 45.568-54.4 45.568-89.152C953.216 372.608 897.664 320 827.52 320c-70.208 0-125.76 52.608-125.76 115.776 0 34.688 16.704 67.072 45.568 89.152l34.88 26.56-84.096 290.304h-156.48a29.632 29.632 0 0 0 0 59.264h571.712a29.632 29.632 0 0 0 0-59.264zm-108.416-64h108.416a93.632 93.632 0 0 1 0 187.264H541.632a93.632 93.632 0 0 1 0-187.264h108.416l58.496-201.984c-43.2-32.96-70.784-83.456-70.784-140.032C637.76 336.512 722.688 256 827.52 256c104.768 0 189.696 80.512 189.696 179.776 0 56.576-27.584 107.072-70.72 140.032l58.432 201.984zM192 320v-64h384v64z"/></svg>

+ 1 - 0
src/icons/svg/bug.svg

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M127.88 73.143c0 1.412-.506 2.635-1.518 3.669-1.011 1.033-2.209 1.55-3.592 1.55h-17.887c0 9.296-1.783 17.178-5.35 23.645l16.609 17.044c1.011 1.034 1.517 2.257 1.517 3.67 0 1.412-.506 2.635-1.517 3.668-.958 1.033-2.155 1.55-3.593 1.55-1.438 0-2.635-.517-3.593-1.55l-15.811-16.063a15.49 15.49 0 0 1-1.196 1.06c-.532.434-1.65 1.208-3.353 2.322a50.104 50.104 0 0 1-5.192 2.974c-1.758.87-3.94 1.658-6.546 2.364-2.607.706-5.189 1.06-7.748 1.06V47.044H58.89v73.062c-2.716 0-5.417-.367-8.106-1.102-2.688-.734-5.003-1.631-6.945-2.692a66.769 66.769 0 0 1-5.268-3.179c-1.571-1.057-2.73-1.94-3.476-2.65L33.9 109.34l-14.611 16.877c-1.066 1.14-2.344 1.711-3.833 1.711-1.277 0-2.422-.434-3.434-1.304-1.012-.978-1.557-2.187-1.635-3.627-.079-1.44.333-2.705 1.236-3.794l16.129-18.51c-3.087-6.197-4.63-13.644-4.63-22.342H5.235c-1.383 0-2.58-.517-3.592-1.55S.125 74.545.125 73.132c0-1.412.506-2.635 1.518-3.668 1.012-1.034 2.21-1.55 3.592-1.55h17.887V43.939L9.308 29.833c-1.012-1.033-1.517-2.256-1.517-3.669 0-1.412.505-2.635 1.517-3.668 1.012-1.034 2.21-1.55 3.593-1.55s2.58.516 3.593 1.55l13.813 14.106h67.396l13.814-14.106c1.012-1.034 2.21-1.55 3.592-1.55 1.384 0 2.581.516 3.593 1.55 1.012 1.033 1.518 2.256 1.518 3.668 0 1.413-.506 2.636-1.518 3.67l-13.814 14.105v23.975h17.887c1.383 0 2.58.516 3.593 1.55 1.011 1.033 1.517 2.256 1.517 3.668l-.005.01zM89.552 26.175H38.448c0-7.23 2.489-13.386 7.466-18.469C50.892 2.623 56.92.082 64 .082c7.08 0 13.108 2.541 18.086 7.624 4.977 5.083 7.466 11.24 7.466 18.469z"/></svg>

文件差异内容过多而无法显示
+ 0 - 0
src/icons/svg/cash.svg


文件差异内容过多而无法显示
+ 0 - 0
src/icons/svg/cashier.svg


+ 1 - 0
src/icons/svg/chart.svg

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 54.857h36.571V128H0V54.857zM91.429 27.43H128V128H91.429V27.429zM45.714 0h36.572v128H45.714V0z"/></svg>

+ 1 - 0
src/icons/svg/clipboard.svg

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M54.857 118.857h64V73.143H89.143c-1.902 0-3.52-.668-4.855-2.002-1.335-1.335-2.002-2.954-2.002-4.855V36.57H54.857v82.286zM73.143 16v-4.571a2.2 2.2 0 0 0-.677-1.61 2.198 2.198 0 0 0-1.609-.676H20.571c-.621 0-1.158.225-1.609.676a2.198 2.198 0 0 0-.676 1.61V16a2.2 2.2 0 0 0 .676 1.61c.451.45.988.676 1.61.676h50.285c.622 0 1.158-.226 1.61-.677.45-.45.676-.987.676-1.609zm18.286 48h21.357L91.43 42.642V64zM128 73.143v48c0 1.902-.667 3.52-2.002 4.855-1.335 1.335-2.953 2.002-4.855 2.002H52.57c-1.901 0-3.52-.667-4.854-2.002-1.335-1.335-2.003-2.953-2.003-4.855v-11.429H6.857c-1.902 0-3.52-.667-4.855-2.002C.667 106.377 0 104.759 0 102.857v-96c0-1.902.667-3.52 2.002-4.855C3.337.667 4.955 0 6.857 0h77.714c1.902 0 3.52.667 4.855 2.002 1.335 1.335 2.003 2.953 2.003 4.855V30.29c1 .622 1.856 1.29 2.569 2.003l29.147 29.147c1.335 1.335 2.478 3.145 3.429 5.43.95 2.287 1.426 4.383 1.426 6.291v-.018z"/></svg>

+ 1 - 0
src/icons/svg/component.svg

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h54.857v54.857H0V0zm0 73.143h54.857V128H0V73.143zm73.143 0H128V128H73.143V73.143zm27.428-18.286C115.72 54.857 128 42.577 128 27.43 128 12.28 115.72 0 100.571 0 85.423 0 73.143 12.28 73.143 27.429c0 15.148 12.28 27.428 27.428 27.428z"/></svg>

+ 1 - 0
src/icons/svg/compstaff.svg

@@ -0,0 +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="1582021789386" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1934" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M938.6496 85.3504h-192V140.8c0 46.9504-38.4 85.3504-85.2992 85.3504H362.6496c-46.8992 0-85.2992-38.4-85.2992-85.3504V85.3504h-192C38.4 85.3504 0 123.7504 0 170.6496v716.8C0 934.4 38.4 972.8 85.3504 972.8h853.2992c46.9504 0 85.3504-38.4 85.3504-85.3504v-716.8c0-46.8992-38.4-85.2992-85.3504-85.2992z m-194.0992 318.72c68.2496 0 125.8496 55.4496 125.8496 125.8496s-55.4496 125.8496-125.8496 125.8496-125.9008-57.6-125.9008-125.8496 55.5008-125.8496 125.9008-125.8496z m-456.5504 321.28H149.3504c-17.1008 0-32-14.9504-32-32 0-17.1008 14.8992-32 32-32h138.6496c17.0496 0 32 14.8992 32 32 0 17.0496-14.9504 32-32 32zM384 554.6496H149.3504c-17.1008 0-32-14.8992-32-32 0-17.0496 14.8992-32 32-32H384c17.0496 0 32 14.9504 32 32 0 17.1008-14.9504 32-32 32z m518.4 252.6208h-320c-17.0496 0-32-19.2-27.7504-38.4 14.9504-51.2 57.6-87.5008 106.7008-87.5008h14.8992c21.3504 10.7008 44.8 14.9504 68.3008 14.9504 23.4496 0 44.8-4.2496 66.0992-14.9504h14.9504c49.0496 0 89.6 36.3008 104.5504 87.5008 6.4 19.2-8.5504 38.4-27.7504 38.4z" p-id="1935"></path><path d="M416 21.33504h192c40.52992 0 74.67008 34.13504 74.67008 74.66496s-34.14016 74.66496-74.67008 74.66496h-192c-40.53504 0-74.66496-34.13504-74.66496-74.66496S375.46496 21.33504 416 21.33504z" p-id="1936"></path></svg>

文件差异内容过多而无法显示
+ 0 - 0
src/icons/svg/dashboard.svg


文件差异内容过多而无法显示
+ 0 - 0
src/icons/svg/dishes.svg


+ 1 - 0
src/icons/svg/documentation.svg

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M71.984 44.815H115.9L71.984 9.642v35.173zM16.094.05h63.875l47.906 38.37v76.74c0 3.392-1.682 6.645-4.677 9.044-2.995 2.399-7.056 3.746-11.292 3.746H16.094c-4.236 0-8.297-1.347-11.292-3.746-2.995-2.399-4.677-5.652-4.677-9.044V12.84C.125 5.742 7.23.05 16.094.05zm71.86 102.32V89.58h-71.86v12.79h71.86zm23.952-25.58V64H16.094v12.79h95.812z"/></svg>

+ 1 - 0
src/icons/svg/drag.svg

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M73.137 29.08h-9.209 29.7L63.886.093 34.373 29.08h20.49v27.035H27.238v17.948h27.625v27.133h18.274V74.063h27.41V56.115h-27.41V29.08zm-9.245 98.827l27.518-26.711H36.59l27.302 26.71zM.042 64.982l27.196 27.029V38.167L.042 64.982zm100.505-26.815V92.01l27.41-27.029-27.41-26.815z"/></svg>

+ 1 - 0
src/icons/svg/edit.svg

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M106.133 67.2a4.797 4.797 0 0 0-4.8 4.8c0 .187.014.36.027.533h-.027V118.4H9.6V26.667h50.133c2.654 0 4.8-2.147 4.8-4.8 0-2.654-2.146-4.8-4.8-4.8H9.6a9.594 9.594 0 0 0-9.6 9.6V118.4c0 5.307 4.293 9.6 9.6 9.6h91.733c5.307 0 9.6-4.293 9.6-9.6V72.533h-.026c.013-.173.026-.346.026-.533 0-2.653-2.146-4.8-4.8-4.8z"/><path d="M125.16 13.373L114.587 2.8c-3.747-3.747-9.854-3.72-13.6.027l-52.96 52.96a4.264 4.264 0 0 0-.907 1.36L33.813 88.533c-.746 1.76-.226 3.534.907 4.68 1.133 1.147 2.92 1.667 4.693.92l31.4-13.293c.507-.213.96-.52 1.36-.907l52.96-52.96c3.747-3.746 3.774-9.853.027-13.6zM66.107 72.4l-18.32 7.76 7.76-18.32L92.72 24.667l10.56 10.56L66.107 72.4zm52.226-52.227l-8.266 8.267-10.56-10.56 8.266-8.267.027-.026 10.56 10.56-.027.026z"/></svg>

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