瀏覽代碼

初始化仓库

wuyunfeng 2 年之前
當前提交
561858c72f
共有 100 個文件被更改,包括 27366 次插入0 次删除
  1. 4 0
      .eslintignore
  2. 200 0
      .eslintrc.js
  3. 5 0
      .gitignore
  4. 14 0
      Dockerfile
  5. 21 0
      LICENSE
  6. 43 0
      README.md
  7. 14 0
      babel.config.js
  8. 36 0
      build/index.js
  9. 24 0
      jest.config.js
  10. 9 0
      jsconfig.json
  11. 103 0
      languages/en.js
  12. 636 0
      languages/zh-CN.js
  13. 51 0
      nginx.conf
  14. 18682 0
      package-lock.json
  15. 112 0
      package.json
  16. 26 0
      plop-templates/component/index.hbs
  17. 55 0
      plop-templates/component/prompt.js
  18. 16 0
      plop-templates/store/index.hbs
  19. 62 0
      plop-templates/store/prompt.js
  20. 2 0
      plop-templates/utils.js
  21. 26 0
      plop-templates/view/index.hbs
  22. 55 0
      plop-templates/view/prompt.js
  23. 9 0
      plopfile.js
  24. 5 0
      postcss.config.js
  25. 7 0
      public/domain.js
  26. 二進制
      public/favicon.ico
  27. 16 0
      public/index.html
  28. 15 0
      public/vital_sign_log.html
  29. 11 0
      src/App.vue
  30. 83 0
      src/api/calling-part.js
  31. 56 0
      src/api/pdm_category.js
  32. 55 0
      src/api/pdm_category_label_prefix.js
  33. 55 0
      src/api/pdm_chip.js
  34. 55 0
      src/api/pdm_label_language.js
  35. 11 0
      src/api/pdm_macdispatch.js
  36. 48 0
      src/api/pdm_pdm.js
  37. 55 0
      src/api/pdm_print_custom.js
  38. 55 0
      src/api/pdm_print_item.js
  39. 55 0
      src/api/pdm_print_solution.js
  40. 55 0
      src/api/pdm_print_template_param.js
  41. 55 0
      src/api/pdm_print_template_param_group.js
  42. 57 0
      src/api/pdm_produceFile.js
  43. 50 0
      src/api/pdm_produced_product.js
  44. 56 0
      src/api/pdm_themometer_category.js
  45. 56 0
      src/api/pdm_themometer_produced.js
  46. 56 0
      src/api/pdm_themometer_schedual.js
  47. 51 0
      src/api/pdm_toolversion.js
  48. 54 0
      src/api/pdm_user.js
  49. 8 0
      src/api/qiniu.js
  50. 62 0
      src/api/role.js
  51. 85 0
      src/api/user.js
  52. 二進制
      src/assets/401_images/401.gif
  53. 二進制
      src/assets/404_images/404.png
  54. 二進制
      src/assets/404_images/404_cloud.png
  55. 二進制
      src/assets/avatar.jpeg
  56. 二進制
      src/assets/custom-theme/fonts/element-icons.ttf
  57. 二進制
      src/assets/custom-theme/fonts/element-icons.woff
  58. 1 0
      src/assets/custom-theme/index.css
  59. 二進制
      src/assets/loginBg.jpg
  60. 二進制
      src/assets/logo.png
  61. 110 0
      src/assets/particlesjs-config.json
  62. 55 0
      src/components/AgGridCellRender/ButtonCellRender.vue
  63. 40 0
      src/components/AgGridCellRender/ButtonCellRenderList.vue
  64. 90 0
      src/components/AgGridCustomFilter/ContainsItemFilter.vue
  65. 108 0
      src/components/AgGridCustomFilter/ListFilter.vue
  66. 94 0
      src/components/AgGridCustomFilter/RadioFilter.vue
  67. 41 0
      src/components/AgGridImg/AgGridImg.vue
  68. 14 0
      src/components/AgGridLayout/index.js
  69. 135 0
      src/components/AgGridLayout/src/main.vue
  70. 111 0
      src/components/BackToTop/index.vue
  71. 82 0
      src/components/Breadcrumb/index.vue
  72. 155 0
      src/components/Charts/Keyboard.vue
  73. 227 0
      src/components/Charts/LineMarker.vue
  74. 271 0
      src/components/Charts/MixChart.vue
  75. 56 0
      src/components/Charts/mixins/resize.js
  76. 166 0
      src/components/DndList/index.vue
  77. 65 0
      src/components/DragSelect/index.vue
  78. 297 0
      src/components/Dropzone/index.vue
  79. 78 0
      src/components/ErrorLog/index.vue
  80. 54 0
      src/components/GithubCorner/index.vue
  81. 44 0
      src/components/Hamburger/index.vue
  82. 180 0
      src/components/HeaderSearch/index.vue
  83. 1779 0
      src/components/ImageCropper/index.vue
  84. 19 0
      src/components/ImageCropper/utils/data2blob.js
  85. 39 0
      src/components/ImageCropper/utils/effectRipple.js
  86. 232 0
      src/components/ImageCropper/utils/language.js
  87. 7 0
      src/components/ImageCropper/utils/mimes.js
  88. 77 0
      src/components/JsonEditor/index.vue
  89. 99 0
      src/components/Kanban/index.vue
  90. 360 0
      src/components/MDinput/index.vue
  91. 31 0
      src/components/MarkdownEditor/default-options.js
  92. 118 0
      src/components/MarkdownEditor/index.vue
  93. 101 0
      src/components/Pagination/index.vue
  94. 142 0
      src/components/PanThumb/index.vue
  95. 145 0
      src/components/RightPanel/index.vue
  96. 60 0
      src/components/Screenfull/index.vue
  97. 103 0
      src/components/Share/DropdownMenu.vue
  98. 57 0
      src/components/SizeSelect/index.vue
  99. 91 0
      src/components/Sticky/index.vue
  100. 0 0
      src/components/SvgIcon/index.vue

+ 4 - 0
.eslintignore

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

+ 200 - 0
.eslintrc.js

@@ -0,0 +1,200 @@
+module.exports = {
+    root: true,
+    parser: 'babel-eslint',
+    parserOptions: {
+        sourceType: 'module'
+    },
+    env: {
+        browser: true,
+        node: true,
+        es6: true,
+    },
+    extends: 'eslint:recommended',
+    // required to lint *.vue files
+    plugins: [
+        'html'
+    ],
+    // check if imports actually resolve
+    'settings': {
+        'import/resolver': {
+            'webpack': {
+                'config': 'build/webpack.base.conf.js'
+            }
+        }
+    },
+    // add your custom rules here
+    //it is base on https://github.com/vuejs/eslint-config-vue
+    'rules': {
+        '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': [2, 'allow-null'],
+        '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': 0,
+        'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
+        'object-curly-spacing': [2, 'always', {
+            objectsInObjects: false
+        }],
+        'array-bracket-spacing': [2, 'never'],
+        'no-unused-vars': 0
+    }
+}
+

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+node_modules
+.idea
+.env
+dist
+mock

+ 14 - 0
Dockerfile

@@ -0,0 +1,14 @@
+FROM nginx:alpine
+MAINTAINER wuyunfeng
+
+RUN mkdir -p /app/
+COPY ./dist /app/
+COPY ./nginx.conf /etc/nginx/nginx.conf
+
+EXPOSE 443 80
+
+ENV serverUrl=http://172.28.100.100:8005 DeviceUrl=http://172.28.100.100:8006 apiMode=dev uiVersion=1
+RUN echo 'USERNAME=' $serverUrl ',DeviceUrl=' $DeviceUrl ',apiMode=' $apiMode ',uiVersion=' $uiVersion
+#CMD sh -c "sed -i 's/^.*8006.*$/serverUrl=$serverUrl' domain.js"
+CMD sh -c  "sed -i -e \"s~^.*serverUrl.*$~serverUrl:'$serverUrl',~;s~^.*DeviceUrl.*$~DeviceUrl:'$DeviceUrl',~;s~^.*apiMode.*$~apiMode:'$apiMode',~;s~^.*uiVersion.*$~uiVersion:$uiVersion~\" /app/domain.js; exec nginx -g \"daemon off;\""
+#CMD sh -c "exec nginx -g 'daemon off;'"

+ 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.

+ 43 - 0
README.md

@@ -0,0 +1,43 @@
+## Getting started
+
+```bash
+# clone the project
+git clone http://git.wdklian.com/allen/ncs_ui.git
+
+# enter the project directory
+cd ncs_ui
+
+# install dependency
+npm install --registry=https://registry.npm.taobao.org
+
+# develop
+npm run dev
+```
+
+This will automatically open http://localhost:9527
+
+## Build
+
+```bash
+# build for test environment
+npm run build:stage
+
+# build for production environment
+npm run build:prod
+```
+
+## Advanced
+
+```bash
+# preview the release environment effect
+npm run preview
+
+# preview the release environment effect + static resource analysis
+npm run preview -- --report
+
+# code format check
+npm run lint
+
+# code format check and auto fix
+npm run lint -- --fix
+```

+ 14 - 0
babel.config.js

@@ -0,0 +1,14 @@
+module.exports = {
+  presets: [
+    // https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app
+    '@vue/cli-plugin-babel/preset'
+  ],
+  'env': {
+    'development': {
+      // babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require().
+      // This plugin can significantly increase the speed of hot updates, when you have a large number of pages.
+      // https://panjiachen.github.io/vue-element-admin-site/guide/advanced/lazy-loading.html
+      'plugins': ['dynamic-import-node']
+    }
+  }
+}

+ 36 - 0
build/index.js

@@ -0,0 +1,36 @@
+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', '/'],
+      vitalSignLog: ['vital_sign_log.html','/vital_sign_log']
+    })
+  )
+
+  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}`)
+}

+ 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/'
+}

+ 9 - 0
jsconfig.json

@@ -0,0 +1,9 @@
+{
+  "compilerOptions": {
+    "baseUrl": "./",
+    "paths": {
+        "@/*": ["src/*"]
+    }
+  },
+  "exclude": ["node_modules", "dist"]
+}

+ 103 - 0
languages/en.js

@@ -0,0 +1,103 @@
+module.exports = {
+  action: {
+    home: 'home',
+    add: 'add',
+    edit: 'edit',
+    more: 'more',
+    delete: 'delete',
+    logout: 'logout',
+    waring: 'waring',
+    yes: 'yes',
+    cancel: 'cancel',
+    deleted: 'deleted!',
+    editSuccess: 'edit success!',
+    addSuccess: 'add success!',
+    createSuccess: 'create success!',
+    saveSuccess: 'save success!',
+    notDevelopedYet: 'not developed yet',
+    keywords: 'Please enter a search keyword',
+    chooseLang: 'languages',
+    perpetualLicence: 'perpetual license',
+    licenseValidity: 'your license is valid until: ',
+    licenseRemainsValid: 'Your license remains valid: ',
+    getLicense: 'day, Please contact after sales for authorization'
+  },
+  home: {
+    todayTask: 'today task',
+    recentNote: 'recent note',
+    recentInteract: 'recent interact',
+    recentRemarks: 'recent remarks'
+  },
+  frameManage: {
+    frameManage: 'frame manage',
+    numberOfRooms: 'rooms',
+    numberOfBeds: 'beds',
+    occupiedBeds: 'occupied',
+    emptyBeds: 'empty',
+    keywordsFilter: 'Enter keywords to filter',
+    memberList: 'member list',
+    deviceList: 'device list',
+    clerkList: 'clerk list',
+    quickCreate: 'quick create',
+    quickCreateFrame: 'quick create frame',
+    startRoom: 'start room number',
+    endRoom: 'end room number',
+    bedQuantity: 'number of beds per room',
+    showRoomDigit: 'room number display digits',
+    showBedDigit: 'bed number display digit',
+    example: 'example',
+    inputFrameName: 'please enter frame name!',
+    inputFrameFullName: 'please enter full name!',
+    inputLong: 'the length is between 2 and 10 characters',
+    addBed: 'add bed',
+    addRoom: 'add room',
+    editFrame: 'edit frame',
+    sureDelete: 'are you sure you want to delete',
+    allBad: 'and all the beds?',
+    sureDeleteBed: 'are you sure to delete the bed',
+    room: 'room',
+    bed: 'bed',
+    newOrganization: 'new organization',
+    partName: 'the part name',
+    inputPartName: 'please enter the part name',
+    adminMember: 'administrator account',
+    inputAdminMember: 'please enter the administrator account',
+    adminPassword: 'administrator password',
+    inputAdminPassword: 'please enter the administrator password'
+  },
+  deviceManage: {
+    deviceManage: 'device manage',
+    deviceAdd: 'device add',
+    deviceType: 'device type',
+    deviceName: 'device name',
+    status: 'status',
+    statusTure: 'status ture',
+    statusFalse: 'status false',
+    connect: 'connect',
+    connectTrue: 'connect true',
+    connectFalse: 'connect false',
+    frameFullName: 'frame full name',
+    ethMac: 'eth mac',
+    ethIp: 'eth ip',
+    ethIpPort: 'eth ip port',
+    model: 'model',
+    code: 'code',
+    softVer: 'soft ver',
+    hardVer: 'hard ver',
+    wifiMac: 'wifi mac',
+    wifiIp: 'wifi ip',
+    sipId: 'sip id',
+    updateTime: 'update time'
+  },
+  tab: {
+    home: 'home',
+    frameManage: 'frame manage',
+    deviceManage: 'device manage',
+    clerkManage: 'clerk manage',
+    memberManage: 'member manage',
+    mobileDeviceManage: 'mobile device manage'
+  },
+  error: {
+    fromError: '表单填写有误,请检查!'
+  }
+}

+ 636 - 0
languages/zh-CN.js

@@ -0,0 +1,636 @@
+module.exports = {
+  action: {
+    home: '首页',
+    add: '新增',
+    edit: '编辑',
+    more: '更多',
+    delete: '删除',
+    logout: '退出登录',
+    waring: '警告',
+    yes: '确定',
+    cancel: '取消',
+    back: '返回',
+    handle: '操作',
+    details: '详情',
+    sort: '排序',
+    submit: '提交',
+    systemCreate: '系统创建',
+    cancelHandle: '取消操作!',
+    handleSuccess: '操作成功',
+    saveEdit: '保存修改',
+    saveSettings: '保存设置',
+    deleteSettings: '清空设置',
+    deleted: '已删除!',
+    deleteList: '批量删除',
+    sureDelete: '删除操作后数据不可复原,您确定要删除此数据?',
+    allDelete: '确定要清空吗?',
+    allDeleteSuccess: '清空成功!',
+    cancelDelete: '已取消删除',
+    pleaseChoiceDelete: '请先勾选需要删除的数据',
+    fromError: '表单填写有误,请检查!',
+    editSuccess: '修改成功!',
+    addSuccess: '新增成功!',
+    createSuccess: '创建成功!',
+    createTime: '创建时间',
+    updateTime: '更新时间',
+    save: '保存',
+    saveSuccess: '保存成功!',
+    notDevelopedYet: '暂未开发',
+    keywords: '请输入搜索关键字',
+    chooseLang: '选择语言',
+    perpetualLicence: '永久授权',
+    licenseValidity: '授权有效期至: ',
+    licenseRemainsValid: '授权剩余有效期:',
+    getLicense: '天,请联系售后获得授权',
+    name: '名称',
+    fullName: '全称',
+    type: '类型',
+    alias: '别名',
+    tips: '提示',
+    time: '小时',
+    minute: '分钟',
+    second: '秒',
+    Monday: '周一',
+    Tuesday: '周二',
+    Wednesday: '周三',
+    Thursday: '周四',
+    Friday: '周五',
+    Saturday: '周六',
+    Sunday: '周日',
+    every: '每',
+    times: '次',
+    choice: '请选择',
+    choiceAll: '全选',
+    colour: '颜色',
+    searchDateRange: '搜索时间段',
+    to: '至',
+    startDate: '开始日期',
+    endDate: '结束日期',
+    startTime: '开始时间',
+    endTime: '结束时间',
+    lastWeek: '最近一周',
+    lastMonth: '最近一个月',
+    lastThreeMonths: '最近三个月',
+    getMsg: '收到消息:',
+    sureInitialize: '您确定要初始化吗?',
+    dateNull: '暂无数据',
+    content: '内容',
+    inputContent: '请输入文本内容,长度300',
+    uploader: '附件',
+    uploaderDownload: '下载附件',
+    uploaderImg: '上传附件只能是txt,doc,docx,xls,xlsx,jpg,png,jpeg格式!',
+    uploaderImg2: '上传头像图片只能是 JPG、PNG、GIF 格式!',
+    uploaderSize: '上传附件大小不能超过 5MB!',
+    uploaderSize2: '上传头像图片大小不能超过 2MB!',
+    uploaderImgMsg: '图片宽度必须在100~500之间,宽高比为1:1!',
+    uploaderImgMsg2: '请上传宽高比为1:1的图片',
+    uploaderImgMsg3: '图片高度必须在100~500之间!',
+    uploadFile: '上传文件',
+    uploadFileMsg: '将文件拖到此处,或',
+    uploadFileMsg2: '点击上传',
+    uploadFileMsg3: '只能上传mp3文件',
+    uploadFileMsg4: '上传附件大小不能超过 5MB!',
+    uploadFileName: '文件名称',
+    uploadFileNameMsg: '文件名称不能为空',
+    uploadFileUrl: '文件路径',
+    uploadFileUrlMsg: '文件路径不能为空,请上传文件',
+    uploadFileAuthor: '上传者',
+    uploadFileDate: '上传日期'
+  },
+  member: {
+    face: '头像',
+    faceImg: '上传头像图片只能是 JPG、PNG、GIF 格式!',
+    faceSize: '上传头像图片大小不能超过 2MB!',
+    faceError1: '图片宽度必须在100~500之间,宽高比为1:1!',
+    faceError2: '请上传宽高比为1:1的图片',
+    faceError3: '图片高度必须在100~500之间!',
+    uname: '登录名',
+    inputUname: '请输入用户名',
+    unameInputMsg: '长度在 2 到 20 个字符',
+    password: '密码',
+    inputPassword: '请输入密码!',
+    passwordInputMsg: '密码格式有误,密码只能包含字母数字和!#$%^&*.~,字符,长度为6-20位',
+    nickname: '真实姓名',
+    inputNickname: '请输入真实姓名',
+    nicknameInputMsg: '长度在 2 到 20 个字符',
+    mobile: '手机号码',
+    inputMobile: '请输入手机号码!',
+    mobileInputMsg: '手机格式格式有误',
+    sex: '性别',
+    man: '男',
+    woman: '女',
+    unknown: '未知',
+    IDCard: '身份证号',
+    birthday: '生日',
+    choiceBirthday: '选择日期',
+    address: '详细地址',
+    addressInputMsg: '最长50个字符',
+    homeAddress: '家庭住址',
+    inputHomeAddress: '请输入家庭住址',
+    disableClerk: '你确定要禁用此用户?',
+    disabled: '已禁用!',
+    disableCancel: '已取消禁用',
+    deleteMember: '你确定要删除此用户信息?',
+    cannotDeleteAdmin: '不能删除机构负责人'
+  },
+  home: {
+    todayTask: '今日任务',
+    recentNote: '最近便签',
+    recentInteract: '最近交互',
+    recentRemarks: '最近用户备注'
+  },
+  frameManage: {
+    frameManage: '空间位置',
+    numberOfRooms: '客房数',
+    numberOfBeds: '床位数',
+    occupiedBeds: '在床数',
+    emptyBeds: '空床数',
+    keywordsFilter: '输入关键字进行过滤',
+    memberList: '用户列表',
+    deviceList: '设备列表',
+    clerkList: '机构成员',
+    quickCreate: '快速创建',
+    quickCreateFrame: '快速构建结构',
+    startRoom: '开始房间号',
+    endRoom: '结束房间号',
+    bedQuantity: '每房床位数',
+    showRoomDigit: '房号显示位数',
+    showBedDigit: '床号显示位数',
+    example: '示例',
+    inputFrameName: '请输入结构名称!',
+    inputFrameFullName: '请输入全称!',
+    inputLong: '长度在 2 到 10 个字符',
+    addBed: '添加床位',
+    addRoom: '添加房间',
+    editFrame: '编辑节点',
+    sureDelete: '确定要删除',
+    allBad: '及其中的所有床位吗?',
+    sureDeleteBed: '确定删除床位',
+    room: '房',
+    bed: '床',
+    newOrganization: '新建组织',
+    partName: '科室名称',
+    inputPartName: '请输入科室名称',
+    adminMember: '管理员账号',
+    inputAdminMember: '请输入管理员账号',
+    adminPassword: '管理员密码',
+    inputAdminPassword: '请输入管理员密码'
+  },
+  deviceManage: {
+    deviceManage: '设备管理',
+    deviceKeywords: '请输入设备别名或设备标识码',
+    deviceAdd: '新增设备',
+    deviceEdit: '修改设备',
+    deviceId: '设备id',
+    deviceType: '设备类型',
+    choiceDeviceType: '请选择设备类型',
+    deviceName: '设备别名',
+    inputDeviceName: '请输入设备别名',
+    status: '设备状态',
+    deviceStatus: '是否启用',
+    choiceDeviceStatus: '启用设备',
+    statusTure: '启用',
+    statusFalse: '未启用',
+    connect: '连接状态',
+    connectTrue: '在线',
+    connectFalse: '离线',
+    frameFullName: '部署位置',
+    frameName: '设备位置',
+    choiceFrameName: '请选择安装位置',
+    ethMac: '有线MAC地址',
+    inputEthMac: '请输入有线MAC地址',
+    inputTrueEthMac: '请输入正确的MAC地址',
+    ethIp: '有线IP地址',
+    inputEthIp: '请输入IP地址',
+    ethIpPort: '通讯端口',
+    model: '设备型号',
+    inputModel: '请输入设备型号',
+    code: '出厂编号',
+    inputCode: '请输入出厂编号',
+    softVer: '软件版本',
+    inputSoftVer: '请输入软件版本号',
+    hardVer: '硬件版本',
+    inputHardVer: '请输入硬件版本号',
+    wifiMac: '无线MAC地址',
+    inputWifiMac: '请输入无线MAC地址',
+    wifiIp: '无线IP地址',
+    inputWifiIp: '请输入无线IP地址',
+    wifiHostname: 'WIFI热点',
+    inputWifiHostname: '请输入WIFI热点名称',
+    wifiPassword: 'WIFI密码',
+    inputPassword: '请输入WIFI热点密码',
+    sipId: 'SIP账号',
+    updateTime: '更新时间',
+    phoneNumber: '设备电话号码',
+    inputPhoneNumber: '请输入设备电话号码',
+    priority: '设备优先级',
+    backupId: '后备设备ID',
+    roleName: '适用角色',
+    choiceRoleName: '请选择适用人',
+    transAudio: '上属总线转换盒',
+    choiceTransAudio: '请选择总线转换盒',
+    transRs485: '上属485转换盒',
+    re485SipId: '485地址',
+    inputRe485SipId: '请输入485地址',
+    sosDeviceSettingType: '报警模式',
+    sosDeviceSettingStatusFalse: '不开启',
+    sosDeviceSettingControlModel: '疫情防控模式',
+    sosDeviceSettingElderlyModel: '关爱老人模式',
+    sosDeviceSettingTime: '报警时间',
+    sosDeviceSettingUnit: '报警时间单位',
+    sosDeviceSettingEdit: '修改设备报警',
+    ledResolutionRatio: '点阵屏规格',
+    ledVoice: '语音播放',
+    ledVoiceStatus: '启用语音',
+    ledFontSize: '字体大小',
+    webSocketSuccess: 'WebSocket连接成功',
+    webSocketError: 'WebSocket连接发生错误'
+  },
+  clerkManage: {
+    clerkEdit: '编辑成员信息',
+    role: '角色',
+    choiceRole: '请选择角色',
+    clerkList: '小组成员',
+    admin: '机构负责人'
+  },
+  customerManage: {
+    customerAdd: '入住登记',
+    customerAdd2: '登记注册',
+    customerQrCode: '二维码',
+    customerInfo: '用户信息',
+    baseInfo: '基本信息',
+    named: '用户姓名',
+    inputNamed: '请输入姓名',
+    cardNo: '入住编号',
+    cardNo2: '登记号',
+    inputCardNo: '请输入病人编号',
+    inputCardNo2: '请输入登记号',
+    age: '年龄',
+    inputAge: '请输入年龄',
+    ageUnit: '年龄单位',
+    choiceAgeUnit: '请选择年龄单位',
+    year: '岁',
+    month: '月',
+    day: '天',
+    inDate: '入住日期',
+    outDate: '离开时间',
+    choiceDate: '选择日期',
+    to: '至',
+    today: '至今',
+    idType: '证件类型',
+    id: '证件',
+    idCard: '身份证',
+    noIdCard: '暂无证件',
+    null: '暂无',
+    passport: '护照',
+    servicemanCard: '军人证',
+    idNo: '证件号码',
+    inputIdNo: '请输入证件号',
+    nickname: '昵称',
+    inputNickname: '请输入用户其他称呼',
+    frame: '入住床位',
+    frame2: '登记位置',
+    choiceFrame: '请选择床位',
+    illnessDesc: '用户情况简述',
+    illnessDesc2: '用户简介',
+    patientIllnessDesc: '病况描述',
+    inputIllnessDesc: '请输入文本内容,长度2~50',
+    advice: '医嘱',
+    inputAdvice: '请输入文本内容,长度2~50',
+    doctor: '责任医生',
+    doctor2: '监护人',
+    choiceDoctor: '请选择医生',
+    choiceDoctor2: '请选择监护人',
+    nurse: '责任护士',
+    choiceNurse: '请选择护士',
+    worker: '责任护工',
+    choiceWorker: '请选择护工',
+    customerRelative: '用户亲属',
+    sureDeleteRelative: '你确定要删除此病人亲属信息?',
+    customerRelativeTrueName: '亲属姓名',
+    inputCustomerRelativeTrueName: '请输入亲属姓名',
+    relativeName: '关系',
+    inputRelativeName: '请输入亲属关系 如:朋友',
+    vital: '体征',
+    remark: '备注',
+    remarks: '备注内容:',
+    inputRemarks: '请输入备注内容',
+    remarkTime: '时间:',
+    remarkName: '备注人:',
+    addRemark: '添加备注',
+    outBed: '退床',
+    outBed2: '注销登记',
+    outBed3: '注销',
+    sureOutBed: '确定退床吗?',
+    outBedSuccess: '退床成功!',
+    changeBed: '换床',
+    changeBed2: '更换位置',
+    changeBed3: '更换',
+    changeBedSuccess: '',
+    oldBed: '当前床位',
+    newBed: '换到床位',
+    choiceNewBed: '请选择床位',
+    getEmptyBed: '获取空床位:',
+    message: '的信息',
+    customerStatus: '当前状态',
+    customerStatusTrue: '入住中',
+    customerStatusFalse: '已离开'
+  },
+  watch: {
+    deviceKeywords: '请输入设备别名或设备标识码',
+    dateKeywords: '选择日期进行搜索',
+    huanBan: '换班',
+    binding: '绑定',
+    noBinding: '未绑定 ',
+    ethMac: '设备标识码',
+    initializeDevice: '初始化设备',
+    watchFrame: '管理空间',
+    watchDeviceFrame: '移动设备管辖空间',
+    place: '位置',
+    placeInfo: '位置信息',
+    getNewPlace: '获取最新位置',
+    inputName: '请输入名称',
+    deviceMember: '绑定人',
+    choiceNurse: '请选择护士',
+    doNothing: '未做任何改变',
+    nurseManageFrame: '护士组长管理空间是其管辖护士的管理空间',
+    choiceFrame: '请选择床位!',
+    you: '您于',
+    in: '在',
+    nearby: '附近',
+    notCovered: '当前不在信标范围内'
+  },
+  channel: {
+    channelAdd: '新增频道',
+    channelName: '频道名称',
+    inputChannelName: '请输入频道名称',
+    subscribeManage: '订阅管理'
+  },
+  remark: {
+    remarkAdd: '新建便签',
+    remarkContent: '便签内容:',
+    remarkCreateTime: '创建时间:',
+    remarkCreateName: '创建人:'
+  },
+  task: {
+    all: '全部',
+    taskType: '任务状态',
+    notOperated: '未操作',
+    taskFail: '任务失败',
+    taskSuccess: '任务成功',
+    isTaskSuccess: '任务完成?',
+    success: '成功',
+    failed: '失败',
+    noticeAndTask: '通知/任务',
+    taskPlanTime: '计划任务执行时间',
+    taskPlanTime2: '计划执行时间:',
+    taskToTime: '实际任务执行时间',
+    taskToName: '任务操作人',
+    doTask: '操作任务',
+    creatName: '创建人',
+    choiceTaskPlanTime: '请选择计划任务执行时间',
+    inputTaskContent: '请输入任务内容',
+    choiceTaskToTime: '请选择实际任务时间'
+  },
+  interaction: {
+    interactionKeywords: '请输入发起方或接收方',
+    fromMemberName: '发起方',
+    toMemberName: '接收方',
+    actionType: '交互类型',
+    actionEnd: '交互结果',
+    success: '成功',
+    notOperated: '未响应',
+    data: '交互数据',
+    createDate: '交互时间',
+    fromDevice: '发起设备',
+    toDevice: '接收设备'
+  },
+  frameGroup: {
+    frameGroupAdd: '新建区域',
+    frameGroupName: '区域名称',
+    inputFrameGroupName: '请输入区域名称',
+    frameGroupNameMsg: '区域名称不能为空',
+    frameGroupManage: '区域管辖空间'
+  },
+  broadcast: {
+    broadcastAdd: '新建广播',
+    broadcastName: '广播名称',
+    inputBroadcastName: '请输入广播名称',
+    broadcastNameMsg: '广播名称不能为空',
+    broadcastMode: '广播模式',
+    broadcastModeMsg: '广播模式必须选择',
+    playMode: '播放模式',
+    manualMode: '手动模式',
+    autoMode: '自动模式',
+    playModeMsg: '播放模式必须选择',
+    timePlay: '定时播放',
+    scheduledPlay: '定次播放',
+    playStart: '自动播放开始',
+    playStartMsg: '自动模式开始时间必须选择',
+    anyTime: '任意时间点',
+    playEnd: '定时播放结束',
+    playEndMsg: '定时播放结束时间必须选择',
+    repeatTimes: '定次播放次数',
+    repeatTimesMsg: '播放次数必须填写',
+    repeatTime: '播放次数',
+    broadcastStatus: '是否启用',
+    broadcastStart: '开启此广播',
+    status: '开启状态',
+    statusTrue: '启用',
+    statusFalse: '关闭',
+    repeatDay: '开启日',
+    play: '播放',
+    stop: '停止',
+    playOrder: '播放顺序',
+    playOrderMsg: '播放顺序必填',
+    broadcastFrameGroup: '广播区域',
+    otherBroadcastFrameGroup: '其他广播位置',
+    broadcastInfo: '广播信息',
+    broadcastFile: '广播文件'
+  },
+  event: {
+    eventEdit: '编辑按钮事件',
+    name: '名称',
+    inputName: '请输入名称',
+    desc: '描述',
+    inputDesc: '请输入描述',
+    keyCode: '唯一标识码',
+    inputKeyCode: '请输入唯一标识码',
+    coordinateX: 'x坐标范围',
+    minX: '最小x轴',
+    manX: '最大x轴',
+    coordinateY: 'y坐标范围',
+    minY: '最小y轴',
+    manY: '最大y轴',
+    deviceType: '设备类型',
+    choiceDeviceType: '请选择类型',
+    remoteControl: '遥控器',
+    emergencyButton: 'SOS紧急按钮',
+    roleName: '适用角色',
+    choiceRoleName: '请选择适用人',
+    icon: '图标'
+  },
+  nurseConfig: {
+    configName: '护理名',
+    inputConfigName: '请输入护理名称',
+    configAdd: '新增护理参数',
+    configEdit: '修改护理',
+    optionAdd: '新增护理项',
+    optionName: '护理项名',
+    inputOptionName: '请输入护理项名称',
+    optionColor: '护理灯颜色',
+    colorRgb: '颜色标识',
+    basic: '基本护理信息'
+  },
+  partInfo: {
+    partInfo: '系统设置',
+    baseInfo: '基本信息',
+    shopName: '组织简称',
+    inputShopName: '输入组织简称',
+    shopFullName: '组织全称',
+    inputShopFullName: '输入组织全称',
+    hisCode: 'HIS系统标识',
+    inputHisCode: '请输入HIS系统标识',
+    shopType: '组织类型',
+    choiceShopType: '请选择组织类型',
+    hospital: '医院',
+    part: '科室',
+    organization: '组织',
+    shopMemberName: '管理员账号',
+    inputShopMemberName: '请输入管理员账号',
+    shopParentId: '所属组织',
+    choiceShopParentId: '请选择上级组织',
+    dayInfo: '白天参数设置',
+    dayStart: '白天开始时间',
+    nightInfo: '夜晚参数设置',
+    nightStart: '夜晚开始时间',
+    dayOrNightVol: '主机通话音量',
+    dayOrNightDoorVol: '门口机通话音量',
+    dayOrNightTransferBoxVol: '转换盒通话音量',
+    dayOrNightLight: '分机液晶屏亮度',
+    dayOrNightRingTimes: '呼叫响铃次数',
+    dayOrNightBedVol: '分机通话音量',
+    dayOrNightRingVol: '主机系统音量',
+    dayOrNightNurseLed: '分机护理灯亮度',
+    dayOrNightTransferBoxSystemVol: '转换盒系统音量',
+    otherInfo: '其它参数',
+    sleepSecondsBed: '分机息屏时间',
+    sleepSecondsDoor: '门口机息屏时间',
+    sleepSecondsNurse: '护士主机息屏时间',
+    sipOvertime: '语音呼叫超时',
+    transferDuration: '护士转接时长',
+    transferDurationLeader: '护士组长转接时长',
+    nurseKeyVal: '护士主机转接时长',
+    autoAccept: '分机开启自动接听',
+    openAutoAccept: '开启自动接听',
+    communicationModeBed: '分机通讯方式',
+    communicationModeNurse: '护士主机通讯方式',
+    communicationModeMobile: '移动设备通讯方式',
+    networkCall: '网络电话',
+    mobileCall: '移动电话',
+    fixedCall: '固定电话',
+    customizeRoleCallFirst: '自定义呼叫角色1',
+    customizeRoleCallSecond: '自定义呼叫角色2',
+    doctorTitle: '模拟分机医生标题',
+    nurseTitle: '模拟分机护士标题',
+    display: '显示',
+    onDisplay: '不显示',
+    upSeconds: '腕表上传定位间隔',
+    saveSettings: '保存设置',
+    initializeSystem: '初始化系统',
+    initialize485Device: '初始化485设备',
+    addTestMemberList: '批量添加测试用户',
+    devicesRef: '设备批量刷新',
+    sureAddTestMemberList: '确定需要批量增加测试用户吗?',
+    startAdd: '开始创建',
+    sureDevicesRef: '确定需要批量刷新设备吗?',
+    boardShowEmptyBed: '看板显示空床'
+  },
+  tab: {
+    home: '首页',
+    frameManage: '空间位置',
+    deviceManage: '所有设备',
+    clerkManage: '员工管理',
+    patientManage: '病人管理',
+    customerManage: '用户管理',
+    customerQrCode: '用户二维码',
+    mobileDeviceManage: '移动设备',
+    sosDeviceSettingManage: '报警设备',
+    channelManage: '对讲频道',
+    remarkManage: '便签管理',
+    taskManage: '任务管理',
+    interactionHistory: '交互历史',
+    frameGroupManage: '区域管理',
+    frameGroupEdit: '编辑区域信息',
+    watchFrameManage: '移动设备空间',
+    broadcastManage: '广播设置',
+    broadcastEdit: '编辑广播信息',
+    nurseConfig: '护理参数',
+    boardManage: '看板设置',
+    eventManage: '按钮事件管理',
+    interactionChars: '交互统计',
+    partSetting: '机构设置',
+    allClerk: '所有员工',
+    allCustomer: '所有用户',
+    allDevice: '所有设备',
+    errorLog: '错误日志',
+    systemConfig: '系统参数',
+    shopSetting: '系统设置',
+    menuManage: '菜单管理',
+    organization: '组织机构',
+    roleManage: '角色管理'
+  },
+  deviceType: {
+    NURSE_HOST: '护士主机',
+    DOCTOR_HOST: '医生主机',
+    DOOR_DEVICE: '门口机',
+    DIGIT_BED_DEVICE: '床位分机',
+    LCD_SCREEN: 'LCD走廊屏',
+    LED_SCREEN: 'LED点阵屏',
+    NURSE_WATCH: '护士移动',
+    WORKER_WATCH: '护工移动',
+    USER_WATCH: '用户移动',
+    CELL_PHONE: '手机App',
+    TRANSFER_DEVICE: '总线转换盒',
+    SIMULATE_BED_DEVICE: '模拟分机',
+    SIMULATE_EMERGENCY_BUTTON: '模拟紧急按钮',
+    SIMULATE_DOOR_LIGHT: '模拟门灯',
+    REMOTE_CONTROL: '遥控器',
+    BEACON: '信标',
+    INFORMATION_BOARD: '信息看版',
+    ENTRANCE_GUARD: '门禁设备',
+    VISITATION: '探视机',
+    RS485_TRANSFER: '485转换盒',
+    EMERGENCY_BUTTON: '紧急按钮',
+    RS485_DOOR: '485门口分机',
+    ALARM_BODY_INDUCTIVE: '红外报警器',
+    ALARM_WATER_OVERFLOW: '用水监控器',
+    ALARM_HOUSEHOLD_GAS: '家用燃起报警器',
+    ALARM_HOUSEHOLD_SMOKE: '家用烟雾报警器',
+    ALARM_BUTTON_SOS: '一键报警器',
+    DOOR_LOCK: '门磁传感器'
+  },
+  sosDeviceSettingType: {
+    NOT_START: '不启动',
+    EPIDEMIC_PATTERN: '疫情防控模式',
+    CARING_PATTERN: '关爱老人模式'
+  },
+  tcpType: {
+    CALLBACK: 'TCP反馈',
+    VOICE: '语音',
+    VIDEO: '视频',
+    SOS: '紧急呼叫',
+    REINFORCE: '增援',
+    IM: '留言',
+    DEVICE: '设备',
+    DATA: '数据',
+    EVENT: '事件',
+    SIDE: '边外信息',
+    BROADCAST: '广播',
+    TIME: '时间',
+    ENTRACEGUARD: '门禁',
+    CHANNELIM: '频道留言',
+    LOCATION: '定位',
+    UNKNOWN: '未知'
+  }
+}

+ 51 - 0
nginx.conf

@@ -0,0 +1,51 @@
+#这个文件给docker用的
+#user  nobody;
+worker_processes  1;
+
+#error_log  logs/error.log;
+#error_log  logs/error.log  notice;
+#error_log  logs/error.log  info;
+
+#pid        logs/nginx.pid;
+
+
+events {
+    worker_connections  1024;
+}
+
+
+http {
+    include       mime.types;
+    default_type  application/octet-stream;
+
+    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
+    #                  '$status $body_bytes_sent "$http_referer" '
+    #                  '"$http_user_agent" "$http_x_forwarded_for"';
+
+    #access_log  logs/access.log  main;
+
+    sendfile        on;
+    #tcp_nopush     on;
+
+    #keepalive_timeout  0;
+    keepalive_timeout  65;
+    client_max_body_size 10m;
+    gzip on;
+    gzip_min_length  5k;
+    gzip_buffers     4 16k;
+    gzip_http_version 1.0;
+    gzip_comp_level 4;
+    gzip_types       text/plain application/x-javascript application/javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
+    gzip_vary on;
+
+    server {
+        listen       80;
+        server_name  localhost;
+
+        location / {
+            root /app;
+            try_files $uri $uri/ /index.html $uri/ =404;
+            index  index.html index.htm;
+        }
+    }
+}

File diff suppressed because it is too large
+ 18682 - 0
package-lock.json


+ 112 - 0
package.json

@@ -0,0 +1,112 @@
+{
+  "name": "wdkl",
+  "version": "4.4.0",
+  "description": "维鼎康联",
+  "author": "Pan <panfree23@gmail.com>",
+  "scripts": {
+    "dev": "vue-cli-service serve",
+    "lint": "eslint --ext .js,.vue src",
+    "build:prod": "vue-cli-service build",
+    "build:stage": "vue-cli-service build --mode staging",
+    "preview": "node build/index.js --preview",
+    "new": "plop",
+    "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml",
+    "test:unit": "jest --clearCache && vue-cli-service test:unit",
+    "test:ci": "npm run lint && npm run test:unit"
+  },
+  "dependencies": {
+    "@moefe/vue-aplayer": "^2.0.0-beta.5",
+    "ag-grid-community": "^25.0.0",
+    "ag-grid-vue": "^25.0.0",
+    "axios": "0.18.1",
+    "clipboard": "2.0.4",
+    "codemirror": "5.45.0",
+    "core-js": "3.6.5",
+    "driver.js": "0.9.5",
+    "dropzone": "5.5.1",
+    "echarts": "^5.2.2",
+    "element-ui": "^2.15.6",
+    "eslint-plugin-html": "^6.2.0",
+    "file-saver": "2.0.1",
+    "fuse.js": "3.4.4",
+    "js-cookie": "2.2.0",
+    "js-md5": "^0.7.3",
+    "jsonlint": "1.6.3",
+    "jszip": "3.2.1",
+    "jwt-decode": "^3.1.2",
+    "normalize.css": "7.0.0",
+    "nprogress": "0.2.0",
+    "particles.js": "2.0.0",
+    "path-to-regexp": "2.4.0",
+    "pinyin": "^2.9.1",
+    "screenfull": "4.2.0",
+    "script-loader": "0.7.2",
+    "sortablejs": "1.8.4",
+    "tui-editor": "1.3.3",
+    "vcolorpicker": "^1.0.3",
+    "vue": "2.6.10",
+    "vue-class-component": "^7.2.6",
+    "vue-count-to": "^1.0.13",
+    "vue-draggable-resizable": "^2.3.0",
+    "vue-i18n": "^8.26.1",
+    "vue-property-decorator": "^9.1.2",
+    "vue-qr": "^3.2.4",
+    "vue-router": "3.0.2",
+    "vue-seamless-scroll": "^1.1.23",
+    "vue-splitpane": "1.0.4",
+    "vuedraggable": "2.20.0",
+    "vuex": "3.1.0",
+    "xlsx": "0.14.1"
+  },
+  "devDependencies": {
+    "@vue/cli-plugin-babel": "4.4.4",
+    "@vue/cli-plugin-eslint": "4.4.4",
+    "@vue/cli-plugin-unit-jest": "4.4.4",
+    "@vue/cli-service": "4.4.4",
+    "@vue/test-utils": "1.0.0-beta.29",
+    "autoprefixer": "9.5.1",
+    "babel-eslint": "10.1.0",
+    "babel-jest": "23.6.0",
+    "babel-plugin-dynamic-import-node": "2.3.3",
+    "chalk": "2.4.2",
+    "chokidar": "2.1.5",
+    "connect": "3.6.6",
+    "eslint": "^5.16.0",
+    "eslint-plugin-vue": "^7.20.0",
+    "html-webpack-plugin": "3.2.0",
+    "husky": "1.3.1",
+    "js-cookie": "2.2.0",
+    "js-md5": "^0.7.3",
+    "lint-staged": "8.1.5",
+    "mockjs": "1.0.1-beta3",
+    "plop": "2.3.0",
+    "runjs": "4.3.2",
+    "sass": "1.26.2",
+    "sass-loader": "8.0.2",
+    "script-ext-html-webpack-plugin": "2.1.3",
+    "serve-static": "1.13.2",
+    "svg-sprite-loader": "4.1.3",
+    "svgo": "1.2.0",
+    "vue-template-compiler": "2.6.10"
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions"
+  ],
+  "bugs": {
+    "url": "https://github.com/PanJiaChen/vue-element-admin/issues"
+  },
+  "engines": {
+    "node": ">=8.9",
+    "npm": ">= 3.0.0"
+  },
+  "keywords": [
+    "vue",
+    "admin",
+    "dashboard",
+    "element-ui",
+    "boilerplate",
+    "admin-template",
+    "management-system"
+  ]
+}

+ 26 - 0
plop-templates/component/index.hbs

@@ -0,0 +1,26 @@
+{{#if template}}
+<template>
+  <div />
+</template>
+{{/if}}
+
+{{#if script}}
+<script>
+export default {
+  name: '{{ properCase name }}',
+  props: {},
+  data() {
+    return {}
+  },
+  created() {},
+  mounted() {},
+  methods: {}
+}
+</script>
+{{/if}}
+
+{{#if style}}
+<style lang="scss" scoped>
+
+</style>
+{{/if}}

+ 55 - 0
plop-templates/component/prompt.js

@@ -0,0 +1,55 @@
+const { notEmpty } = require('../utils.js')
+
+module.exports = {
+  description: 'generate vue component',
+  prompts: [{
+    type: 'input',
+    name: 'name',
+    message: 'component name please',
+    validate: notEmpty('name')
+  },
+  {
+    type: 'checkbox',
+    name: 'blocks',
+    message: 'Blocks:',
+    choices: [{
+      name: '<template>',
+      value: 'template',
+      checked: true
+    },
+    {
+      name: '<script>',
+      value: 'script',
+      checked: true
+    },
+    {
+      name: 'style',
+      value: 'style',
+      checked: true
+    }
+    ],
+    validate(value) {
+      if (value.indexOf('script') === -1 && value.indexOf('template') === -1) {
+        return 'Components require at least a <script> or <template> tag.'
+      }
+      return true
+    }
+  }
+  ],
+  actions: data => {
+    const name = '{{properCase name}}'
+    const actions = [{
+      type: 'add',
+      path: `src/components/${name}/index.vue`,
+      templateFile: 'plop-templates/component/index.hbs',
+      data: {
+        name: name,
+        template: data.blocks.includes('template'),
+        script: data.blocks.includes('script'),
+        style: data.blocks.includes('style')
+      }
+    }]
+
+    return actions
+  }
+}

+ 16 - 0
plop-templates/store/index.hbs

@@ -0,0 +1,16 @@
+{{#if state}}
+const state = {}
+{{/if}}
+
+{{#if mutations}}
+const mutations = {}
+{{/if}}
+
+{{#if actions}}
+const actions = {}
+{{/if}}
+
+export default {
+  namespaced: true,
+  {{options}}
+}

+ 62 - 0
plop-templates/store/prompt.js

@@ -0,0 +1,62 @@
+const { notEmpty } = require('../utils.js')
+
+module.exports = {
+  description: 'generate store',
+  prompts: [{
+    type: 'input',
+    name: 'name',
+    message: 'store name please',
+    validate: notEmpty('name')
+  },
+  {
+    type: 'checkbox',
+    name: 'blocks',
+    message: 'Blocks:',
+    choices: [{
+      name: 'state',
+      value: 'state',
+      checked: true
+    },
+    {
+      name: 'mutations',
+      value: 'mutations',
+      checked: true
+    },
+    {
+      name: 'actions',
+      value: 'actions',
+      checked: true
+    }
+    ],
+    validate(value) {
+      if (!value.includes('state') || !value.includes('mutations')) {
+        return 'store require at least state and mutations'
+      }
+      return true
+    }
+  }
+  ],
+  actions(data) {
+    const name = '{{name}}'
+    const { blocks } = data
+    const options = ['state', 'mutations']
+    const joinFlag = `,
+  `
+    if (blocks.length === 3) {
+      options.push('actions')
+    }
+
+    const actions = [{
+      type: 'add',
+      path: `src/store/modules/${name}.js`,
+      templateFile: 'plop-templates/store/index.hbs',
+      data: {
+        options: options.join(joinFlag),
+        state: blocks.includes('state'),
+        mutations: blocks.includes('mutations'),
+        actions: blocks.includes('actions')
+      }
+    }]
+    return actions
+  }
+}

+ 2 - 0
plop-templates/utils.js

@@ -0,0 +1,2 @@
+exports.notEmpty = name => v =>
+  !v || v.trim() === '' ? `${name} is required` : true

+ 26 - 0
plop-templates/view/index.hbs

@@ -0,0 +1,26 @@
+{{#if template}}
+<template>
+  <div />
+</template>
+{{/if}}
+
+{{#if script}}
+<script>
+export default {
+  name: '{{ properCase name }}',
+  props: {},
+  data() {
+    return {}
+  },
+  created() {},
+  mounted() {},
+  methods: {}
+}
+</script>
+{{/if}}
+
+{{#if style}}
+<style lang="scss" scoped>
+
+</style>
+{{/if}}

+ 55 - 0
plop-templates/view/prompt.js

@@ -0,0 +1,55 @@
+const { notEmpty } = require('../utils.js')
+
+module.exports = {
+  description: 'generate a view',
+  prompts: [{
+    type: 'input',
+    name: 'name',
+    message: 'view name please',
+    validate: notEmpty('name')
+  },
+  {
+    type: 'checkbox',
+    name: 'blocks',
+    message: 'Blocks:',
+    choices: [{
+      name: '<template>',
+      value: 'template',
+      checked: true
+    },
+    {
+      name: '<script>',
+      value: 'script',
+      checked: true
+    },
+    {
+      name: 'style',
+      value: 'style',
+      checked: true
+    }
+    ],
+    validate(value) {
+      if (value.indexOf('script') === -1 && value.indexOf('template') === -1) {
+        return 'View require at least a <script> or <template> tag.'
+      }
+      return true
+    }
+  }
+  ],
+  actions: data => {
+    const name = '{{name}}'
+    const actions = [{
+      type: 'add',
+      path: `src/views/${name}/index.vue`,
+      templateFile: 'plop-templates/view/index.hbs',
+      data: {
+        name: name,
+        template: data.blocks.includes('template'),
+        script: data.blocks.includes('script'),
+        style: data.blocks.includes('style')
+      }
+    }]
+
+    return actions
+  }
+}

+ 9 - 0
plopfile.js

@@ -0,0 +1,9 @@
+const viewGenerator = require('./plop-templates/view/prompt')
+const componentGenerator = require('./plop-templates/component/prompt')
+const storeGenerator = require('./plop-templates/store/prompt.js')
+
+module.exports = function(plop) {
+  plop.setGenerator('view', viewGenerator)
+  plop.setGenerator('component', componentGenerator)
+  plop.setGenerator('store', storeGenerator)
+}

+ 5 - 0
postcss.config.js

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

+ 7 - 0
public/domain.js

@@ -0,0 +1,7 @@
+const domain = {
+  serverUrl: 'http://192.168.1.128:8989',
+  DeviceUrl: 'http://192.168.1.128:8006',
+  apiMode: 'dev',
+  uiVersion: 1 // 1 医院版,2 月子中心版,3养老院版
+}
+

二進制
public/favicon.ico


+ 16 - 0
public/index.html

@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="renderer" content="webkit">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
+    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+    <script src="/domain.js" ></script>
+    <title><%= webpackConfig.name %></title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+  </body>
+</html>

+ 15 - 0
public/vital_sign_log.html

@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="renderer" content="webkit">
+    <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>abc</title>
+</head>
+<body>
+<div id="app"></div>
+<!-- built files will be auto injected -->
+</body>
+</html>

+ 11 - 0
src/App.vue

@@ -0,0 +1,11 @@
+<template>
+  <div id="app">
+    <router-view />
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'App'
+}
+</script>

+ 83 - 0
src/api/calling-part.js

@@ -0,0 +1,83 @@
+/**
+ * 科室信息接口请求
+ * @param params
+ * @returns {Promise<any>}
+ */
+import request from '@/utils/request'
+
+export function getList(params) {
+  return request({
+    url: '/mgr/partinfo/page',
+    method: 'POST',
+    loading: true,
+    data: params,
+    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
+  })
+}
+
+/** 新增科室 */
+export function add(params) {
+  return request({
+    url: '/mgr/partinfo',
+    method: 'POST',
+    loading: true,
+    data: params
+  })
+}
+
+/** 删除科室 */
+export function remove(params) {
+  const ids = params.toString()
+  return request({
+    url: `/mgr/partinfo/${ids}`,
+    method: 'DELETE',
+    loading: true,
+    data: params
+  })
+}
+
+/** 更新科室 */
+export function update(id, params) {
+  return request({
+    url: `/mgr/partinfo/${id}`,
+    method: 'put',
+    data: params
+  })
+}
+
+/** 查询单个科室 */
+export function get(id, params) {
+  return request({
+    url: `/mgr/partinfo/${id}`,
+    method: 'get',
+    loading: false,
+    params
+  })
+}
+/** 获取科室的床位信息 */
+export function getBedInfos(id, params) {
+  return request({
+    url: `/mgr/getbeddata/${id}`,
+    method: 'get',
+    loading: false,
+    params
+  })
+}
+
+/** 获取授权期限 */
+export function getExpire() {
+  return request({
+    url: '/lc/expire',
+    method: 'get',
+    loading: false
+  })
+}
+
+/** 同步HIS */
+export function syncHis() {
+  return request({
+    url: '/mgr/partinfo/synchis',
+    method: 'get',
+    loading: false
+  })
+}

+ 56 - 0
src/api/pdm_category.js

@@ -0,0 +1,56 @@
+import request from '@/utils/request'
+export function getList(params) {
+  return request({
+    url: '/pdm/category/page',
+    method: 'POST',
+    loading: true,
+    data: params,
+    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
+  })
+}
+
+export function remove(params) {
+  const ids = params.toString()
+  return request({
+    url: `/pdm/category/${ids}`,
+    method: 'DELETE',
+    loading: true,
+    data: params
+  })
+}
+
+export function getModel(id) {
+  return request({
+    url: `/pdm/category/${id}`,
+    method: 'GET',
+    loading: true,
+    data: id
+  })
+}
+
+/** 新增 */
+export function add(params) {
+  return request({
+    url: '/pdm/category',
+    method: 'POST',
+    loading: true,
+    data: params
+  })
+}
+
+/** 更新 */
+export function update(id, params) {
+  return request({
+    url: `/pdm/category/${id}`,
+    method: 'put',
+    data: params
+  })
+}
+
+export function getAll() {
+  return request({
+    url: '/pdm/category/all',
+    method: 'get',
+    loading: false
+  })
+}

+ 55 - 0
src/api/pdm_category_label_prefix.js

@@ -0,0 +1,55 @@
+import request from '@/utils/request'
+export function getList(params) {
+  return request({
+    url: '/pdm/categoryprefix/page',
+    method: 'POST',
+    loading: true,
+    data: params,
+    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
+  })
+}
+
+export function remove(params) {
+  const ids = params.toString()
+  return request({
+    url: `/pdm/categoryprefix/${ids}`,
+    method: 'DELETE',
+    loading: true,
+    data: params
+  })
+}
+
+export function getModel(id) {
+  return request({
+    url: `/pdm/categoryprefix/${id}`,
+    method: 'GET',
+    loading: true,
+    data: id
+  })
+}
+
+/** 新增 */
+export function add(params) {
+  return request({
+    url: '/pdm/categoryprefix',
+    method: 'POST',
+    loading: true,
+    data: params
+  })
+}
+
+/** 更新 */
+export function update(id, params) {
+  return request({
+    url: `/pdm/categoryprefix/${id}`,
+    method: 'put',
+    data: params
+  })
+}
+export function getAll() {
+  return request({
+    url: '/pdm/categoryprefix/all',
+    method: 'get',
+    loading: false
+  })
+}

+ 55 - 0
src/api/pdm_chip.js

@@ -0,0 +1,55 @@
+import request from '@/utils/request'
+export function getList(params) {
+  return request({
+    url: '/pdm/chip/page',
+    method: 'POST',
+    loading: true,
+    data: params,
+    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
+  })
+}
+
+export function remove(params) {
+  const ids = params.toString()
+  return request({
+    url: `/pdm/chip/${ids}`,
+    method: 'DELETE',
+    loading: true,
+    data: params
+  })
+}
+
+export function getModel(id) {
+  return request({
+    url: `/pdm/chip/${id}`,
+    method: 'GET',
+    loading: true,
+    data: id
+  })
+}
+
+/** 新增 */
+export function add(params) {
+  return request({
+    url: '/pdm/chip',
+    method: 'POST',
+    loading: true,
+    data: params
+  })
+}
+
+/** 更新 */
+export function update(id, params) {
+  return request({
+    url: `/pdm/chip/${id}`,
+    method: 'put',
+    data: params
+  })
+}
+export function getAll() {
+  return request({
+    url: '/pdm/chip/all',
+    method: 'get',
+    loading: false
+  })
+}

+ 55 - 0
src/api/pdm_label_language.js

@@ -0,0 +1,55 @@
+import request from '@/utils/request'
+export function getList(params) {
+  return request({
+    url: '/pdm/labellanguage/page',
+    method: 'POST',
+    loading: true,
+    data: params,
+    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
+  })
+}
+
+export function remove(params) {
+  const ids = params.toString()
+  return request({
+    url: `/pdm/labellanguage/${ids}`,
+    method: 'DELETE',
+    loading: true,
+    data: params
+  })
+}
+
+export function getModel(id) {
+  return request({
+    url: `/pdm/labellanguage/${id}`,
+    method: 'GET',
+    loading: true,
+    data: id
+  })
+}
+
+/** 新增 */
+export function add(params) {
+  return request({
+    url: '/pdm/labellanguage',
+    method: 'POST',
+    loading: true,
+    data: params
+  })
+}
+
+/** 更新 */
+export function update(id, params) {
+  return request({
+    url: `/pdm/labellanguage/${id}`,
+    method: 'put',
+    data: params
+  })
+}
+export function getAll() {
+  return request({
+    url: '/pdm/labellanguage/all',
+    method: 'get',
+    loading: false
+  })
+}

+ 11 - 0
src/api/pdm_macdispatch.js

@@ -0,0 +1,11 @@
+import request from '@/utils/request'
+export function getList(params) {
+  return request({
+    url: '/pdm/macdispatch/page',
+    method: 'POST',
+    loading: true,
+    data: params,
+    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
+  })
+}
+

+ 48 - 0
src/api/pdm_pdm.js

@@ -0,0 +1,48 @@
+import request from '@/utils/request'
+export function getList(params) {
+  return request({
+    url: '/pdm/product/page',
+    method: 'POST',
+    loading: true,
+    data: params,
+    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
+  })
+}
+
+export function remove(params) {
+  const ids = params.toString()
+  return request({
+    url: `/pdm/product/${ids}`,
+    method: 'DELETE',
+    loading: true,
+    data: params
+  })
+}
+
+export function getModel(id) {
+  return request({
+    url: `/pdm/product/${id}`,
+    method: 'GET',
+    loading: true,
+    data: id
+  })
+}
+
+/** 新增 */
+export function add(params) {
+  return request({
+    url: '/pdm/product',
+    method: 'POST',
+    loading: true,
+    data: params
+  })
+}
+
+/** 更新 */
+export function update(id, params) {
+  return request({
+    url: `/pdm/product/${id}`,
+    method: 'put',
+    data: params
+  })
+}

+ 55 - 0
src/api/pdm_print_custom.js

@@ -0,0 +1,55 @@
+import request from '@/utils/request'
+export function getList(params) {
+  return request({
+    url: '/pdm/customprint/page',
+    method: 'POST',
+    loading: true,
+    data: params,
+    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
+  })
+}
+
+export function remove(params) {
+  const ids = params.toString()
+  return request({
+    url: `/pdm/customprint/${ids}`,
+    method: 'DELETE',
+    loading: true,
+    data: params
+  })
+}
+
+export function getModel(id) {
+  return request({
+    url: `/pdm/customprint/${id}`,
+    method: 'GET',
+    loading: true,
+    data: id
+  })
+}
+
+/** 新增 */
+export function add(params) {
+  return request({
+    url: '/pdm/customprint',
+    method: 'POST',
+    loading: true,
+    data: params
+  })
+}
+
+/** 更新 */
+export function update(id, params) {
+  return request({
+    url: `/pdm/customprint/${id}`,
+    method: 'put',
+    data: params
+  })
+}
+export function getAll() {
+  return request({
+    url: '/pdm/customprint/all',
+    method: 'get',
+    loading: false
+  })
+}

+ 55 - 0
src/api/pdm_print_item.js

@@ -0,0 +1,55 @@
+import request from '@/utils/request'
+export function getList(params) {
+  return request({
+    url: '/pdm/printitem/page',
+    method: 'POST',
+    loading: true,
+    data: params,
+    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
+  })
+}
+
+export function remove(params) {
+  const ids = params.toString()
+  return request({
+    url: `/pdm/printitem/${ids}`,
+    method: 'DELETE',
+    loading: true,
+    data: params
+  })
+}
+
+export function getModel(id) {
+  return request({
+    url: `/pdm/printitem/${id}`,
+    method: 'GET',
+    loading: true,
+    data: id
+  })
+}
+
+/** 新增 */
+export function add(params) {
+  return request({
+    url: '/pdm/printitem',
+    method: 'POST',
+    loading: true,
+    data: params
+  })
+}
+
+/** 更新 */
+export function update(id, params) {
+  return request({
+    url: `/pdm/printitem/${id}`,
+    method: 'put',
+    data: params
+  })
+}
+export function getAll() {
+  return request({
+    url: '/pdm/printitem/all',
+    method: 'get',
+    loading: false
+  })
+}

+ 55 - 0
src/api/pdm_print_solution.js

@@ -0,0 +1,55 @@
+import request from '@/utils/request'
+export function getList(params) {
+  return request({
+    url: '/pdm/printsolution/page',
+    method: 'POST',
+    loading: true,
+    data: params,
+    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
+  })
+}
+
+export function remove(params) {
+  const ids = params.toString()
+  return request({
+    url: `/pdm/printsolution/${ids}`,
+    method: 'DELETE',
+    loading: true,
+    data: params
+  })
+}
+
+export function getModel(id) {
+  return request({
+    url: `/pdm/printsolution/${id}`,
+    method: 'GET',
+    loading: true,
+    data: id
+  })
+}
+
+/** 新增 */
+export function add(params) {
+  return request({
+    url: '/pdm/printsolution',
+    method: 'POST',
+    loading: true,
+    data: params
+  })
+}
+
+/** 更新 */
+export function update(id, params) {
+  return request({
+    url: `/pdm/printsolution/${id}`,
+    method: 'put',
+    data: params
+  })
+}
+export function getAll() {
+  return request({
+    url: '/pdm/printsolution/all',
+    method: 'get',
+    loading: false
+  })
+}

+ 55 - 0
src/api/pdm_print_template_param.js

@@ -0,0 +1,55 @@
+import request from '@/utils/request'
+export function getList(params) {
+  return request({
+    url: '/pdm/templateparam/page',
+    method: 'POST',
+    loading: true,
+    data: params,
+    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
+  })
+}
+
+export function remove(params) {
+  const ids = params.toString()
+  return request({
+    url: `/pdm/templateparam/${ids}`,
+    method: 'DELETE',
+    loading: true,
+    data: params
+  })
+}
+
+export function getModel(id) {
+  return request({
+    url: `/pdm/templateparam/${id}`,
+    method: 'GET',
+    loading: true,
+    data: id
+  })
+}
+
+/** 新增 */
+export function add(params) {
+  return request({
+    url: '/pdm/templateparam',
+    method: 'POST',
+    loading: true,
+    data: params
+  })
+}
+
+/** 更新 */
+export function update(id, params) {
+  return request({
+    url: `/pdm/templateparam/${id}`,
+    method: 'put',
+    data: params
+  })
+}
+export function getAll() {
+  return request({
+    url: '/pdm/templateparam/all',
+    method: 'get',
+    loading: false
+  })
+}

+ 55 - 0
src/api/pdm_print_template_param_group.js

@@ -0,0 +1,55 @@
+import request from '@/utils/request'
+export function getList(params) {
+  return request({
+    url: '/pdm/templateparamgroup/page',
+    method: 'POST',
+    loading: true,
+    data: params,
+    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
+  })
+}
+
+export function remove(params) {
+  const ids = params.toString()
+  return request({
+    url: `/pdm/templateparamgroup/${ids}`,
+    method: 'DELETE',
+    loading: true,
+    data: params
+  })
+}
+
+export function getModel(id) {
+  return request({
+    url: `/pdm/templateparamgroup/${id}`,
+    method: 'GET',
+    loading: true,
+    data: id
+  })
+}
+
+/** 新增 */
+export function add(params) {
+  return request({
+    url: '/pdm/templateparamgroup',
+    method: 'POST',
+    loading: true,
+    data: params
+  })
+}
+
+/** 更新 */
+export function update(id, params) {
+  return request({
+    url: `/pdm/templateparamgroup/${id}`,
+    method: 'put',
+    data: params
+  })
+}
+export function getAll() {
+  return request({
+    url: '/pdm/templateparamgroup/all',
+    method: 'get',
+    loading: false
+  })
+}

+ 57 - 0
src/api/pdm_produceFile.js

@@ -0,0 +1,57 @@
+import request from '@/utils/request'
+export function getList(params) {
+  return request({
+    url: '/pdm/producefile/page',
+    method: 'POST',
+    loading: true,
+    data: params,
+    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
+  })
+}
+
+export function remove(params) {
+  const ids = params.toString()
+  return request({
+    url: `/pdm/producefile/${ids}`,
+    method: 'DELETE',
+    loading: true,
+    data: params
+  })
+}
+
+export function getModel(id) {
+  return request({
+    url: `/pdm/producefile/${id}`,
+    method: 'GET',
+    loading: true,
+    data: id
+  })
+}
+
+/** 新增 */
+export function add(params) {
+  return request({
+    url: '/pdm/producefile',
+    method: 'POST',
+    loading: true,
+    data: params
+  })
+}
+
+/** 更新 */
+export function update(id, params) {
+  return request({
+    url: `/pdm/producefile/${id}`,
+    method: 'put',
+    data: params
+  })
+}
+
+export function getAll() {
+  return request({
+    url: '/pdm/producefile/all',
+    method: 'get',
+    loading: false
+  })
+}
+

+ 50 - 0
src/api/pdm_produced_product.js

@@ -0,0 +1,50 @@
+import request from '@/utils/request'
+export function getList(params) {
+  return request({
+    url: '/pdm/producedproduct/page',
+    method: 'POST',
+    loading: true,
+    data: params,
+    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
+  })
+}
+
+export function remove(params) {
+  const ids = params.toString()
+  return request({
+    url: `/pdm/producedproduct/${ids}`,
+    method: 'DELETE',
+    loading: true,
+    data: params
+  })
+}
+
+export function getModel(id) {
+  return request({
+    url: `/pdm/producedproduct/${id}`,
+    method: 'GET',
+    loading: true,
+    data: id
+  })
+}
+
+/** 新增 */
+export function add(params) {
+  return request({
+    url: '/pdm/producedproduct',
+    method: 'POST',
+    loading: true,
+    data: params
+  })
+}
+
+/** 更新 */
+export function update(id, params) {
+  return request({
+    url: `/pdm/producedproduct/${id}`,
+    method: 'put',
+    data: params
+  })
+}
+
+

+ 56 - 0
src/api/pdm_themometer_category.js

@@ -0,0 +1,56 @@
+import request from '@/utils/request'
+export function getList(params) {
+  return request({
+    url: '/pdm/thermometercategory/page',
+    method: 'POST',
+    loading: true,
+    data: params,
+    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
+  })
+}
+
+export function remove(params) {
+  const ids = params.toString()
+  return request({
+    url: `/pdm/thermometercategory/${ids}`,
+    method: 'DELETE',
+    loading: true,
+    data: params
+  })
+}
+
+export function getModel(id) {
+  return request({
+    url: `/pdm/thermometercategory/${id}`,
+    method: 'GET',
+    loading: true,
+    data: id
+  })
+}
+
+/** 新增 */
+export function add(params) {
+  return request({
+    url: '/pdm/thermometercategory',
+    method: 'POST',
+    loading: true,
+    data: params
+  })
+}
+
+/** 更新 */
+export function update(id, params) {
+  return request({
+    url: `/pdm/thermometercategory/${id}`,
+    method: 'put',
+    data: params
+  })
+}
+
+export function getAll() {
+  return request({
+    url: '/pdm/thermometercategory/all',
+    method: 'get',
+    loading: false
+  })
+}

+ 56 - 0
src/api/pdm_themometer_produced.js

@@ -0,0 +1,56 @@
+import request from '@/utils/request'
+export function getList(params) {
+  return request({
+    url: '/pdm/thermometerproduced/page',
+    method: 'POST',
+    loading: true,
+    data: params,
+    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
+  })
+}
+
+export function remove(params) {
+  const ids = params.toString()
+  return request({
+    url: `/pdm/thermometerproduced/${ids}`,
+    method: 'DELETE',
+    loading: true,
+    data: params
+  })
+}
+
+export function getModel(id) {
+  return request({
+    url: `/pdm/thermometerproduced/${id}`,
+    method: 'GET',
+    loading: true,
+    data: id
+  })
+}
+
+/** 新增 */
+export function add(params) {
+  return request({
+    url: '/pdm/thermometerproduced',
+    method: 'POST',
+    loading: true,
+    data: params
+  })
+}
+
+/** 更新 */
+export function update(id, params) {
+  return request({
+    url: `/pdm/thermometerproduced/${id}`,
+    method: 'put',
+    data: params
+  })
+}
+
+export function getAll() {
+  return request({
+    url: '/pdm/thermometerproduced/all',
+    method: 'get',
+    loading: false
+  })
+}

+ 56 - 0
src/api/pdm_themometer_schedual.js

@@ -0,0 +1,56 @@
+import request from '@/utils/request'
+export function getList(params) {
+  return request({
+    url: '/pdm/thermometerschedual/page',
+    method: 'POST',
+    loading: true,
+    data: params,
+    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
+  })
+}
+
+export function remove(params) {
+  const ids = params.toString()
+  return request({
+    url: `/pdm/thermometerschedual/${ids}`,
+    method: 'DELETE',
+    loading: true,
+    data: params
+  })
+}
+
+export function getModel(id) {
+  return request({
+    url: `/pdm/thermometerschedual/${id}`,
+    method: 'GET',
+    loading: true,
+    data: id
+  })
+}
+
+/** 新增 */
+export function add(params) {
+  return request({
+    url: '/pdm/thermometerschedual',
+    method: 'POST',
+    loading: true,
+    data: params
+  })
+}
+
+/** 更新 */
+export function update(id, params) {
+  return request({
+    url: `/pdm/thermometerschedual/${id}`,
+    method: 'put',
+    data: params
+  })
+}
+
+export function getAll() {
+  return request({
+    url: '/pdm/thermometerschedual/all',
+    method: 'get',
+    loading: false
+  })
+}

+ 51 - 0
src/api/pdm_toolversion.js

@@ -0,0 +1,51 @@
+import request from '@/utils/request'
+export function getList(params) {
+  return request({
+    url: '/pdm/version/page',
+    method: 'POST',
+    loading: true,
+    data: params,
+    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
+  })
+}
+
+export function remove(params) {
+  const ids = params.toString()
+  return request({
+    url: `/pdm/version/${ids}`,
+    method: 'DELETE',
+    loading: true,
+    data: params
+  })
+}
+
+export function getModel(id) {
+  return request({
+    url: `/pdm/version/${id}`,
+    method: 'GET',
+    loading: true,
+    data: id
+  })
+}
+
+/** 新增 */
+export function add(params) {
+  return request({
+    url: '/pdm/version',
+    method: 'POST',
+    loading: true,
+    data: params
+  })
+}
+
+/** 更新 */
+export function update(id, params) {
+  return request({
+    url: `/pdm/version/${id}`,
+    method: 'put',
+    data: params
+  })
+}
+
+
+

+ 54 - 0
src/api/pdm_user.js

@@ -0,0 +1,54 @@
+import request from '@/utils/request'
+import md5 from 'js-md5'
+export function getList(params) {
+  return request({
+    url: '/pdm/user/page',
+    method: 'POST',
+    loading: true,
+    data: params,
+    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
+  })
+}
+
+export function remove(params) {
+  const ids = params.toString()
+  return request({
+    url: `/pdm/user/${ids}`,
+    method: 'DELETE',
+    loading: true,
+    data: params
+  })
+}
+
+export function getModel(id) {
+  return request({
+    url: `/pdm/user/${id}`,
+    method: 'GET',
+    loading: true,
+    data: id
+  })
+}
+
+/** 新增 */
+export function add(params) {
+  params = JSON.parse(JSON.stringify(params))
+  params.password = md5(params.password)
+  return request({
+    url: '/pdm/user',
+    method: 'POST',
+    loading: true,
+    data: params
+  })
+}
+
+/** 更新 */
+export function update(id, params) {
+  if (params.password) {
+    params.password = md5(params.password)
+  }
+  return request({
+    url: `/pdm/user/${id}`,
+    method: 'put',
+    data: params
+  })
+}

+ 8 - 0
src/api/qiniu.js

@@ -0,0 +1,8 @@
+import request from '@/utils/request'
+
+export function getToken() {
+  return request({
+    url: '/qiniu/upload/token', // 假地址 自行替换
+    method: 'get'
+  })
+}

+ 62 - 0
src/api/role.js

@@ -0,0 +1,62 @@
+import request from '@/utils/request'
+
+/**
+ * 获取角色列表
+ * @param params
+ */
+export function getRoleList(part_id) {
+  return request({
+    url: `/mgr/role/list/${part_id}`,
+    method: 'get',
+    loading: false
+  })
+}
+
+/**
+ * 删除角色
+ * @param id
+ */
+export function deleteRole(id) {
+  return request({
+    url: `/mgr/role/${id}`,
+    method: 'delete'
+  })
+}
+
+/**
+ * 获取角色具体权限
+ * @param id
+ */
+export function getRolePermission(id) {
+  return request({
+    url: `/mgr/role/${id}`,
+    method: 'get'
+  })
+}
+
+/**
+ * 添加角色
+ * @param params
+ */
+export function addRole(params) {
+  return request({
+    url: '/mgr/role',
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: params
+  })
+}
+
+/**
+ * 修改角色
+ * @param id
+ * @param params
+ */
+export function editRole(id, params) {
+  return request({
+    url: `/mgr/role/${id}`,
+    method: 'put',
+    headers: { 'Content-Type': 'application/json' },
+    data: params
+  })
+}

+ 85 - 0
src/api/user.js

@@ -0,0 +1,85 @@
+import request from '@/utils/request'
+import Storage from '@/utils/storage'
+import store from '@/store'
+import md5 from 'js-md5'
+export function login(data) {
+  data.password = md5(data.password)
+  return request({
+    url: '/pdm/authorize/login',
+    method: 'get',
+    params: data
+  })
+}
+
+export function logout(data) {
+  return request({
+    url: '/Mgr/logout',
+    method: 'post',
+    data: {
+      uid: store.getters.userInfo.uid
+    }
+  })
+}
+/**
+ * 刷新用户token
+ * @param data refresh_token=token
+ * @returns {AxiosPromise | * | Promise<unknown>}
+ */
+export function refreshToken(data) {
+  return request({
+    url: '/Mgr/refresh_token',
+    method: 'post',
+    loading: false,
+    data: {
+      refresh_token: Storage.getItem('calling_refresh_token')
+    }
+  })
+}
+
+export function getInfo(token) {
+  return request({
+    url: '/vue-element-admin/user/info',
+    method: 'get',
+    params: { token }
+  })
+}
+
+export function getList(data) {
+  return request({
+    url: '/Mgr/member/page',
+    method: 'POST',
+    params: data,
+    loading: false
+  })
+}
+
+export function add(params) {
+  return request({
+    url: '/Mgr/member/add',
+    method: 'POST',
+    data: params
+  })
+}
+
+export function remove(params) {
+  const ids = params.toString()
+  return request({
+    url: `/Mgr/member/${ids}`,
+    method: 'DELETE',
+    loading: false,
+    data: params
+  })
+}
+
+/**
+ * 获取用户角色权限
+ * @param role_id
+ * @returns {*}
+ */
+export function getUserRolesPermissions(role_id) {
+  return request({
+    url: `/mgr/role/${role_id}/checked`,
+    method: 'get'
+  })
+}
+

二進制
src/assets/401_images/401.gif


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


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


二進制
src/assets/avatar.jpeg


二進制
src/assets/custom-theme/fonts/element-icons.ttf


二進制
src/assets/custom-theme/fonts/element-icons.woff


File diff suppressed because it is too large
+ 1 - 0
src/assets/custom-theme/index.css


二進制
src/assets/loginBg.jpg


二進制
src/assets/logo.png


+ 110 - 0
src/assets/particlesjs-config.json

@@ -0,0 +1,110 @@
+{
+  "particles": {
+    "number": {
+      "value": 160,
+      "density": {
+        "enable": true,
+        "value_area": 800
+      }
+    },
+    "color": {
+      "value": "#409eff"
+    },
+    "shape": {
+      "type": "circle",
+      "stroke": {
+        "width": 0,
+        "color": "#000000"
+      },
+      "polygon": {
+        "nb_sides": 5
+      },
+      "image": {
+        "src": "img/github.svg",
+        "width": 100,
+        "height": 100
+      }
+    },
+    "opacity": {
+      "value": 1,
+      "random": true,
+      "anim": {
+        "enable": true,
+        "speed": 1,
+        "opacity_min": 0,
+        "sync": false
+      }
+    },
+    "size": {
+      "value": 3,
+      "random": true,
+      "anim": {
+        "enable": false,
+        "speed": 4,
+        "size_min": 0.3,
+        "sync": false
+      }
+    },
+    "line_linked": {
+      "enable": true,
+      "distance": 110.48066982851817,
+      "color": "#409eff",
+      "opacity": 0.1815039575754227,
+      "width": 0
+    },
+    "move": {
+      "enable": true,
+      "speed": 1,
+      "direction": "none",
+      "random": true,
+      "straight": false,
+      "out_mode": "bounce",
+      "bounce": false,
+      "attract": {
+        "enable": false,
+        "rotateX": 600,
+        "rotateY": 600
+      }
+    }
+  },
+  "interactivity": {
+    "detect_on": "canvas",
+    "events": {
+      "onhover": {
+        "enable": false,
+        "mode": "grab"
+      },
+      "onclick": {
+        "enable": true,
+        "mode": "push"
+      },
+      "resize": true
+    },
+    "modes": {
+      "grab": {
+        "distance": 60.90579092260088,
+        "line_linked": {
+          "opacity": 1
+        }
+      },
+      "bubble": {
+        "distance": 133.99274002972194,
+        "size": 0,
+        "duration": 2,
+        "opacity": 0,
+        "speed": 3
+      },
+      "repulse": {
+        "distance": 400,
+        "duration": 0.4
+      },
+      "push": {
+        "particles_nb": 4
+      },
+      "remove": {
+        "particles_nb": 2
+      }
+    }
+  },
+  "retina_detect": true
+}

+ 55 - 0
src/components/AgGridCellRender/ButtonCellRender.vue

@@ -0,0 +1,55 @@
+<template>
+  <div>
+    <el-button v-if="show" :type="buttonType" :size="buttonSize" :disabled="disabled" @click="buttonClick">{{ label }}</el-button>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'ButtonCellRender',
+  data() {
+    return {
+      buttonType: 'primary',
+      buttonSize: 'mini',
+      label: 'button',
+      disabled: false,
+      show: true
+    }
+  },
+  beforeMount() {
+  },
+  mounted() {
+    const { buttonType, buttonSize, label, disabled, show } = this.params
+    if (buttonType) {
+      this.buttonType = buttonType
+    }
+    if (buttonSize) {
+      this.buttonSize = buttonSize
+    }
+    if (label) {
+      this.label = label
+    }
+    if (disabled !== undefined) {
+      this.disabled = disabled
+    }
+    if (show !== undefined) {
+      this.show = show
+    }
+  },
+  methods: {
+    buttonClick() {
+      console.log('click', typeof this.params.onClick === 'function')
+
+      if (typeof this.params.onClick === 'function') {
+        console.log(this.params.node.data)
+        this.params.onClick(this.params.node.data)
+        // this.params.context.componentParent.showDetail(this.params.node.data)
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+
+</style>

+ 40 - 0
src/components/AgGridCellRender/ButtonCellRenderList.vue

@@ -0,0 +1,40 @@
+<template>
+  <div>
+    <el-button-group v-if="show">
+      <el-button v-for="(item, index) in buttonList" :key="index" :type="item.buttonType" :size="item.buttonSize" :disabled="item.disabled" @click="buttonClick(index)">{{ item.label }}</el-button>
+    </el-button-group>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'ButtonCellRender',
+  data() {
+    return {
+      buttonType: 'primary',
+      buttonSize: 'mini',
+      label: 'button',
+      disabled: false,
+      show: true,
+      buttonList: []
+    }
+  },
+  beforeMount() {
+  },
+  mounted() {
+    const _this = this
+    _this.buttonList = this.params.list
+  },
+  methods: {
+    buttonClick(index) {
+      if (typeof this.buttonList[index].onClick === 'function') {
+        this.buttonList[index].onClick(this.params.node.data)
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+
+</style>

+ 90 - 0
src/components/AgGridCustomFilter/ContainsItemFilter.vue

@@ -0,0 +1,90 @@
+<template>
+    <div class="el-transfer-panel">
+        <div class="el-transfer-panel__header">
+            <el-checkbox v-model="checkAll" :indeterminate="isIndeterminate" @change="handleCheckAllChange">全选
+            </el-checkbox>
+        </div>
+        <div class="el-transfer-panel__body">
+            <el-checkbox-group v-model="checkData" class="el-transfer-panel__list" @change="handleCheckChange">
+                <div v-for="(item,_index) in listData" :key="_index" class="text item">
+                    <el-checkbox :label="item.value" class="el-transfer-panel__item">{{ item.key }}</el-checkbox>
+                </div>
+            </el-checkbox-group>
+        </div>
+    </div>
+</template>
+
+<script>
+    export default {
+      name: 'ContainsItemFilter',
+      data() {
+        return {
+          isIndeterminate: false,
+          checkAll: false,
+                /** 可选择的项数组 {key:'护士主机',value:1} **/
+          listData: [],
+          checkData: []
+        }
+      },
+      mounted() {
+        const { listData } = this.params
+        if (listData) {
+          this.listData = listData
+        }
+      },
+      methods: {
+
+        handleCheckAllChange(val) {
+          this.checkData = val ? this.listData.map(p => p.value) : []
+          this.isIndeterminate = false
+          this.params.filterChangedCallback()
+        },
+        handleCheckChange() {
+          if (this.checkData.length === this.listData.length) { // 选中所有
+            this.isIndeterminate = false
+            this.checkAll = true
+          } else if (this.checkData.length === 0) { // 一个没选
+            this.isIndeterminate = false
+            this.checkAll = false
+          } else { // 部分选中
+            this.isIndeterminate = true
+            this.checkAll = false
+          }
+          this.params.filterChangedCallback()
+        },
+            /**
+             * 组装过滤对象,GridApi 可以通过getFilterModel 函数获取设置状态
+             **/
+        getModel() {
+          const model = {
+            'filterType': 'list',
+            'type': 'contains',
+            'filter': this.checkData.join(',')
+          }
+          if (this.checkData.length === 0) {
+            return null
+          } else {
+            return model
+          }
+        },
+
+        setModel() {
+        },
+
+        doesFilterPass(params) {
+          return this.checkData.indexOf(params.data.year)
+        },
+            /**
+             * filter 接口方法,返回true 列头显示过滤图标,返回false,不显示
+             * @returns {boolean}
+             */
+        isFilterActive() {
+          return this.checkData.length > 0
+        }
+      }
+    }
+</script>
+
+<style scoped>
+
+</style>

+ 108 - 0
src/components/AgGridCustomFilter/ListFilter.vue

@@ -0,0 +1,108 @@
+<template>
+  <div class="el-transfer-panel">
+    <div class="el-transfer-panel__header">
+      <el-checkbox v-model="checkAll" :indeterminate="isIndeterminate" @change="handleCheckAllChange">全选
+      </el-checkbox>
+    </div>
+    <div class="el-transfer-panel__body">
+      <el-checkbox-group v-model="checkData" class="el-transfer-panel__list" @change="handleCheckChange">
+        <div v-for="(item,_index) in listData" :key="_index" class="text item">
+          <el-checkbox :label="item.value" class="el-transfer-panel__item">{{ item.key }}</el-checkbox>
+        </div>
+      </el-checkbox-group>
+    </div>
+  </div>
+</template>
+
+<script>
+/**
+     * 可选择多项过滤的组件
+     * 通过参数传入可选择项的数组,每个项包含key value 属性 key为可选项的展示文字,value 表示真实要过滤的值
+     * 此过滤器用与数据库中的值与列表展示不一致时,并且列表项可穷尽的数据。
+     */
+export default {
+  name: 'ListFilter',
+  data() {
+    return {
+      isIndeterminate: false,
+      checkAll: false,
+      /** 可选择的项数组 {key:'护士主机',value:1} **/
+      listData: [],
+      checkData: []
+    }
+  },
+  beforeMount() {
+
+  },
+  mounted() {
+    const { listData } = this.params
+    if (listData) {
+      this.listData = listData
+    }
+  },
+  methods: {
+
+    handleCheckAllChange(val) {
+      this.checkData = val ? this.listData.map(p => p.value) : []
+      this.isIndeterminate = false
+      this.params.filterChangedCallback()
+    },
+    handleCheckChange() {
+      if (this.checkData.length === this.listData.length) { // 选中所有
+        this.isIndeterminate = false
+        this.checkAll = true
+      } else if (this.checkData.length === 0) { // 一个没选
+        this.isIndeterminate = false
+        this.checkAll = false
+      } else { // 部分选中
+        this.isIndeterminate = true
+        this.checkAll = false
+      }
+      this.params.filterChangedCallback()
+    },
+    /**
+             * 组装过滤对象,GridApi 可以通过getFilterModel 函数获取设置状态
+             **/
+    getModel() {
+      const model = {
+        'filterType': 'list',
+        'type': this.checkData.length === 1 ? 'equals' : 'in',
+        'filter': this.checkData.join(',')
+      }
+      if (this.checkData.length === 0) {
+        return null
+      } else {
+        return model
+      }
+    },
+
+    setModel() {
+    },
+
+    doesFilterPass(params) {
+      return this.checkData.indexOf(params.data.year)
+    },
+    /**
+             * filter 接口方法,返回true 列头显示过滤图标,返回false,不显示
+             * @returns {boolean}
+             */
+    isFilterActive() {
+      return this.checkData.length > 0
+    }
+  }
+}
+</script>
+
+<style scoped>
+    .el-transfer-panel {
+        width: 160px;
+    }
+
+    .el-transfer-panel__header {
+        background: #f8f8f8;
+    }
+
+    .el-transfer-panel__body {
+        min-height: 150px;
+    }
+</style>

+ 94 - 0
src/components/AgGridCustomFilter/RadioFilter.vue

@@ -0,0 +1,94 @@
+<template>
+  <div class="el-transfer-panel">
+    <div class="el-transfer-panel__body">
+      <el-radio-group v-model="checkData" class="el-transfer-panel__list" @change="handleCheckChange">
+        <div v-for="(item,_index) in listData" :key="_index" class="text item">
+          <el-radio :label="item.value" class="el-transfer-panel__item">{{ item.key }}</el-radio>
+        </div>
+      </el-radio-group>
+    </div>
+    <div style="text-align: center;margin-bottom: 10px">
+      <el-button size="mini" type="primary" @click="resetStatus">重置</el-button>
+    </div>
+
+  </div>
+</template>
+
+<script>
+/**
+     * 此过滤器用于快速过滤某一个类型数据,用法与ListFilter 相似
+     */
+export default {
+  name: 'RadioFilter',
+  data() {
+    return {
+      listData: [],
+      checkData: null
+    }
+  },
+  beforeMount() {
+
+  },
+  mounted() {
+    const { listData } = this.params
+    if (listData) {
+      this.listData = listData
+    }
+  },
+  methods: {
+    handleCheckChange() {
+      this.params.filterChangedCallback()
+    },
+    /**
+       * 组装过滤对象,GridApi 可以通过getFilterModel 函数获取设置状态
+       **/
+    getModel() {
+      const model = {
+        'filterType': 'list',
+        'type': 'equals',
+        'filter': this.checkData
+      }
+      if (this.checkData === null) {
+        return null
+      } else {
+        return model
+      }
+    },
+
+    setModel() {
+    },
+
+    doesFilterPass(params) {
+      return true
+    },
+    /**
+       * filter 接口方法,返回true 列头显示过滤图标,返回false,不显示
+       * @returns {boolean}
+       */
+    isFilterActive() {
+      return this.checkData !== null
+    },
+    /** 重置选择 radio选择后一定有一个被选中,需提供一个按钮把选中状态全置为空  */
+    resetStatus() {
+      this.checkData = null
+      this.params.filterChangedCallback()
+    }
+  }
+}
+</script>
+
+<style scoped>
+    .el-transfer-panel {
+        width: 160px;
+    }
+
+    .el-transfer-panel__header {
+        background: #f8f8f8;
+    }
+
+    .el-transfer-panel__body {
+        padding: 10px 0 0;
+        height: auto;
+    }
+    .el-transfer-panel__list{height: auto}
+</style>

+ 41 - 0
src/components/AgGridImg/AgGridImg.vue

@@ -0,0 +1,41 @@
+<template>
+  <div>
+    <slot v-if="!myFace" name="error">
+      <div slot="error" class="image-slot">
+        <i class="el-icon-picture-outline" />
+      </div>
+    </slot>
+    <img v-else :src="myFace" class="el-image__inner" style="width: 50px; height: 50px" @click="buttonClick">
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'AgGridImg',
+  data() {
+    return {
+      myFace: '',
+      srcList: ['1']
+    }
+  },
+  beforeMount() {
+  },
+  async mounted() {
+    const { face } = this.params
+    if (face) {
+      this.myFace = this.params.node.data[face]
+    }
+  },
+  methods: {
+    buttonClick() {
+      if (typeof this.params.onClick === 'function') {
+        this.params.onClick(this.params.node.data)
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+
+</style>

+ 14 - 0
src/components/AgGridLayout/index.js

@@ -0,0 +1,14 @@
+/**
+ * 表格组件
+ * 依赖于 Ag-Grid表格组件
+ * 包含分页
+ * 搜索栏可选
+ */
+import Vue from 'vue'
+import AgGrid from './src/main'
+
+AgGrid.install = () => {
+  Vue.component(AgGrid.name, AgGrid)
+}
+
+export default AgGrid

+ 135 - 0
src/components/AgGridLayout/src/main.vue

@@ -0,0 +1,135 @@
+<template>
+  <div class="container">
+    <div v-if="toolbar" class="toolbar">
+      <slot name="toolbar" />
+    </div>
+    <ag-grid-vue
+      :style="{'height':tableHeight+'px'}"
+      :class="theme"
+      :column-defs="columnDefs"
+      v-bind="$attrs"
+      :row-data="rowData"
+      :locale-text="localeText"
+      :grid-options="gridOptions"
+      :default-col-def="defaultColDef"
+      :animate-rows="true"
+      v-on="$listeners"
+    />
+    <div v-if="pagination" class="pagination">
+      <div class="pagination-toolbar">
+        <slot name="pagination-toolbar" />
+      </div>
+      <slot name="pagination" />
+    </div>
+  </div>
+</template>
+
+<script>
+import { AG_GRID_LOCALE_CN } from '@/utils/AgGridVueLocaleCn'
+export default {
+  name: 'AgGridLayout',
+  props: {
+    /** 皮肤,默认ag-theme-alpine **/
+    theme: {
+      type: String,
+      default: 'ag-theme-alpine'
+    },
+    /** 表格高度 **/
+    tableHeight: {
+      type: Number,
+      default: 500
+    },
+    /** 是否显示工具栏 */
+    toolbar: {
+      type: Boolean,
+      default: true
+    },
+    /** 是否显示分页 */
+    pagination: {
+      type: Boolean,
+      default: true
+    },
+    /** 表格数据 */
+    rowData: {
+      default: () => ([])
+    },
+    /** 默认列定义 **/
+    defaultColDef: {
+      type: Object,
+      default: () => ({
+        filter: 'text',
+        sortable: true,
+        resizable: true,
+        comparator: this.testComparator // 拦截前端排序
+      })
+    },
+    /** 表格options **/
+    gridOptions: {
+      type: Object,
+      default: () => ({})
+    },
+    /** 列定义 **/
+    columnDefs: {
+      type: Array,
+      default: () => ([])
+    }
+  },
+  data() {
+    return {
+      localeText: AG_GRID_LOCALE_CN
+    }
+  },
+  watch: {
+    rowData(value) {
+      // console.log('rows', value)
+    }
+  },
+  methods: {
+    testComparator(valueA, valueB, nodeA, nodeB, isInverted) {
+      return 0
+      // return console.log(valueA)
+    }
+  }
+}
+</script>
+
+<style type="text/scss" lang="scss" scoped>
+    .container {
+        width: 100%;
+        height: 100%;
+        position: relative;
+    }
+    /** 工具栏样式 */
+    .toolbar {
+        display: flex;
+        align-items: center;
+        height: 44px;
+        border-bottom: 1px solid #e6ebf5;
+        background-color: #fff;
+    }
+    .inner-toolbar {
+        display: flex;
+        width: 100%;
+        justify-content: space-between;
+        padding: 0 20px;
+    }
+
+    .toolbar-search {
+        margin-right: 10px;
+    }
+    /** 分页样式 */
+    .pagination {
+        display: flex;
+        justify-content: space-between;
+        padding: 5px 20px;
+        text-align: right;
+        background-color: #ffffff;
+    .pagination-toolbar {
+        display: flex;
+        align-items: center;
+    }
+    }
+    .el-table td:not(.is-left) {
+        text-align: center;
+    }
+</style>

+ 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>

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

@@ -0,0 +1,82 @@
+<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(route) {
+      // if you go to the redirect page, do not update the breadcrumbs
+      if (route.path.startsWith('/redirect/')) {
+        return
+      }
+      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: this.$t('action.home') }}].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>

+ 155 - 0
src/components/Charts/Keyboard.vue

@@ -0,0 +1,155 @@
+<template>
+  <div :id="id" :class="className" :style="{height:height,width:width}" />
+</template>
+
+<script>
+import echarts from 'echarts'
+import resize from './mixins/resize'
+
+export default {
+  mixins: [resize],
+  props: {
+    className: {
+      type: String,
+      default: 'chart'
+    },
+    id: {
+      type: String,
+      default: 'chart'
+    },
+    width: {
+      type: String,
+      default: '200px'
+    },
+    height: {
+      type: String,
+      default: '200px'
+    }
+  },
+  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 xAxisData = []
+      const data = []
+      const data2 = []
+      for (let i = 0; i < 50; i++) {
+        xAxisData.push(i)
+        data.push((Math.sin(i / 5) * (i / 5 - 10) + i / 6) * 5)
+        data2.push((Math.sin(i / 5) * (i / 5 + 10) + i / 6) * 3)
+      }
+      this.chart.setOption({
+        backgroundColor: '#08263a',
+        grid: {
+          left: '5%',
+          right: '5%'
+        },
+        xAxis: [{
+          show: false,
+          data: xAxisData
+        }, {
+          show: false,
+          data: xAxisData
+        }],
+        visualMap: {
+          show: false,
+          min: 0,
+          max: 50,
+          dimension: 0,
+          inRange: {
+            color: ['#4a657a', '#308e92', '#b1cfa5', '#f5d69f', '#f5898b', '#ef5055']
+          }
+        },
+        yAxis: {
+          axisLine: {
+            show: false
+          },
+          axisLabel: {
+            textStyle: {
+              color: '#4a657a'
+            }
+          },
+          splitLine: {
+            show: true,
+            lineStyle: {
+              color: '#08263f'
+            }
+          },
+          axisTick: {
+            show: false
+          }
+        },
+        series: [{
+          name: 'back',
+          type: 'bar',
+          data: data2,
+          z: 1,
+          itemStyle: {
+            normal: {
+              opacity: 0.4,
+              barBorderRadius: 5,
+              shadowBlur: 3,
+              shadowColor: '#111'
+            }
+          }
+        }, {
+          name: 'Simulate Shadow',
+          type: 'line',
+          data,
+          z: 2,
+          showSymbol: false,
+          animationDelay: 0,
+          animationEasing: 'linear',
+          animationDuration: 1200,
+          lineStyle: {
+            normal: {
+              color: 'transparent'
+            }
+          },
+          areaStyle: {
+            normal: {
+              color: '#08263a',
+              shadowBlur: 50,
+              shadowColor: '#000'
+            }
+          }
+        }, {
+          name: 'front',
+          type: 'bar',
+          data,
+          xAxisIndex: 1,
+          z: 3,
+          itemStyle: {
+            normal: {
+              barBorderRadius: 5
+            }
+          }
+        }],
+        animationEasing: 'elasticOut',
+        animationEasingUpdate: 'elasticOut',
+        animationDelay(idx) {
+          return idx * 20
+        },
+        animationDelayUpdate(idx) {
+          return idx * 20
+        }
+      })
+    }
+  }
+}
+</script>

+ 227 - 0
src/components/Charts/LineMarker.vue

@@ -0,0 +1,227 @@
+<template>
+  <div :id="id" :class="className" :style="{height:height,width:width}" />
+</template>
+
+<script>
+import echarts from 'echarts'
+import resize from './mixins/resize'
+
+export default {
+  mixins: [resize],
+  props: {
+    className: {
+      type: String,
+      default: 'chart'
+    },
+    id: {
+      type: String,
+      default: 'chart'
+    },
+    width: {
+      type: String,
+      default: '200px'
+    },
+    height: {
+      type: String,
+      default: '200px'
+    }
+  },
+  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))
+
+      this.chart.setOption({
+        backgroundColor: '#394056',
+        title: {
+          top: 20,
+          text: 'Requests',
+          textStyle: {
+            fontWeight: 'normal',
+            fontSize: 16,
+            color: '#F1F1F3'
+          },
+          left: '1%'
+        },
+        tooltip: {
+          trigger: 'axis',
+          axisPointer: {
+            lineStyle: {
+              color: '#57617B'
+            }
+          }
+        },
+        legend: {
+          top: 20,
+          icon: 'rect',
+          itemWidth: 14,
+          itemHeight: 5,
+          itemGap: 13,
+          data: ['CMCC', 'CTCC', 'CUCC'],
+          right: '4%',
+          textStyle: {
+            fontSize: 12,
+            color: '#F1F1F3'
+          }
+        },
+        grid: {
+          top: 100,
+          left: '2%',
+          right: '2%',
+          bottom: '2%',
+          containLabel: true
+        },
+        xAxis: [{
+          type: 'category',
+          boundaryGap: false,
+          axisLine: {
+            lineStyle: {
+              color: '#57617B'
+            }
+          },
+          data: ['13:00', '13:05', '13:10', '13:15', '13:20', '13:25', '13:30', '13:35', '13:40', '13:45', '13:50', '13:55']
+        }],
+        yAxis: [{
+          type: 'value',
+          name: '(%)',
+          axisTick: {
+            show: false
+          },
+          axisLine: {
+            lineStyle: {
+              color: '#57617B'
+            }
+          },
+          axisLabel: {
+            margin: 10,
+            textStyle: {
+              fontSize: 14
+            }
+          },
+          splitLine: {
+            lineStyle: {
+              color: '#57617B'
+            }
+          }
+        }],
+        series: [{
+          name: 'CMCC',
+          type: 'line',
+          smooth: true,
+          symbol: 'circle',
+          symbolSize: 5,
+          showSymbol: false,
+          lineStyle: {
+            normal: {
+              width: 1
+            }
+          },
+          areaStyle: {
+            normal: {
+              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+                offset: 0,
+                color: 'rgba(137, 189, 27, 0.3)'
+              }, {
+                offset: 0.8,
+                color: 'rgba(137, 189, 27, 0)'
+              }], false),
+              shadowColor: 'rgba(0, 0, 0, 0.1)',
+              shadowBlur: 10
+            }
+          },
+          itemStyle: {
+            normal: {
+              color: 'rgb(137,189,27)',
+              borderColor: 'rgba(137,189,2,0.27)',
+              borderWidth: 12
+
+            }
+          },
+          data: [220, 182, 191, 134, 150, 120, 110, 125, 145, 122, 165, 122]
+        }, {
+          name: 'CTCC',
+          type: 'line',
+          smooth: true,
+          symbol: 'circle',
+          symbolSize: 5,
+          showSymbol: false,
+          lineStyle: {
+            normal: {
+              width: 1
+            }
+          },
+          areaStyle: {
+            normal: {
+              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+                offset: 0,
+                color: 'rgba(0, 136, 212, 0.3)'
+              }, {
+                offset: 0.8,
+                color: 'rgba(0, 136, 212, 0)'
+              }], false),
+              shadowColor: 'rgba(0, 0, 0, 0.1)',
+              shadowBlur: 10
+            }
+          },
+          itemStyle: {
+            normal: {
+              color: 'rgb(0,136,212)',
+              borderColor: 'rgba(0,136,212,0.2)',
+              borderWidth: 12
+
+            }
+          },
+          data: [120, 110, 125, 145, 122, 165, 122, 220, 182, 191, 134, 150]
+        }, {
+          name: 'CUCC',
+          type: 'line',
+          smooth: true,
+          symbol: 'circle',
+          symbolSize: 5,
+          showSymbol: false,
+          lineStyle: {
+            normal: {
+              width: 1
+            }
+          },
+          areaStyle: {
+            normal: {
+              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+                offset: 0,
+                color: 'rgba(219, 50, 51, 0.3)'
+              }, {
+                offset: 0.8,
+                color: 'rgba(219, 50, 51, 0)'
+              }], false),
+              shadowColor: 'rgba(0, 0, 0, 0.1)',
+              shadowBlur: 10
+            }
+          },
+          itemStyle: {
+            normal: {
+              color: 'rgb(219,50,51)',
+              borderColor: 'rgba(219,50,51,0.2)',
+              borderWidth: 12
+            }
+          },
+          data: [220, 182, 125, 145, 122, 191, 134, 150, 120, 110, 165, 122]
+        }]
+      })
+    }
+  }
+}
+</script>

+ 271 - 0
src/components/Charts/MixChart.vue

@@ -0,0 +1,271 @@
+<template>
+  <div :id="id" :class="className" :style="{height:height,width:width}" />
+</template>
+
+<script>
+import echarts from 'echarts'
+import resize from './mixins/resize'
+
+export default {
+  mixins: [resize],
+  props: {
+    className: {
+      type: String,
+      default: 'chart'
+    },
+    id: {
+      type: String,
+      default: 'chart'
+    },
+    width: {
+      type: String,
+      default: '200px'
+    },
+    height: {
+      type: String,
+      default: '200px'
+    }
+  },
+  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: 'statistics',
+          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>

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

@@ -0,0 +1,56 @@
+import { debounce } from '@/utils'
+
+export default {
+  data() {
+    return {
+      $_sidebarElm: null,
+      $_resizeHandler: null
+    }
+  },
+  mounted() {
+    this.initListener()
+  },
+  activated() {
+    if (!this.$_resizeHandler) {
+      // avoid duplication init
+      this.initListener()
+    }
+
+    // when keep-alive chart activated, auto resize
+    this.resize()
+  },
+  beforeDestroy() {
+    this.destroyListener()
+  },
+  deactivated() {
+    this.destroyListener()
+  },
+  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()
+      }
+    },
+    initListener() {
+      this.$_resizeHandler = debounce(() => {
+        this.resize()
+      }, 100)
+      window.addEventListener('resize', this.$_resizeHandler)
+
+      this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0]
+      this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)
+    },
+    destroyListener() {
+      window.removeEventListener('resize', this.$_resizeHandler)
+      this.$_resizeHandler = null
+
+      this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)
+    },
+    resize() {
+      const { chart } = this
+      chart && chart.resize()
+    }
+  }
+}

+ 166 - 0
src/components/DndList/index.vue

@@ -0,0 +1,166 @@
+<template>
+  <div class="dndList">
+    <div :style="{width:width1}" class="dndList-list">
+      <h3>{{ list1Title }}</h3>
+      <draggable :set-data="setData" :list="list1" group="article" class="dragArea">
+        <div v-for="element in list1" :key="element.id" class="list-complete-item">
+          <div class="list-complete-item-handle">
+            {{ element.id }}[{{ element.author }}] {{ element.title }}
+          </div>
+          <div style="position:absolute;right:0px;">
+            <span style="float: right ;margin-top: -20px;margin-right:5px;" @click="deleteEle(element)">
+              <i style="color:#ff4949" class="el-icon-delete" />
+            </span>
+          </div>
+        </div>
+      </draggable>
+    </div>
+    <div :style="{width:width2}" class="dndList-list">
+      <h3>{{ list2Title }}</h3>
+      <draggable :list="list2" group="article" class="dragArea">
+        <div v-for="element in list2" :key="element.id" class="list-complete-item">
+          <div class="list-complete-item-handle2" @click="pushEle(element)">
+            {{ element.id }} [{{ element.author }}] {{ element.title }}
+          </div>
+        </div>
+      </draggable>
+    </div>
+  </div>
+</template>
+
+<script>
+import draggable from 'vuedraggable'
+
+export default {
+  name: 'DndList',
+  components: { draggable },
+  props: {
+    list1: {
+      type: Array,
+      default() {
+        return []
+      }
+    },
+    list2: {
+      type: Array,
+      default() {
+        return []
+      }
+    },
+    list1Title: {
+      type: String,
+      default: 'list1'
+    },
+    list2Title: {
+      type: String,
+      default: 'list2'
+    },
+    width1: {
+      type: String,
+      default: '48%'
+    },
+    width2: {
+      type: String,
+      default: '48%'
+    }
+  },
+  methods: {
+    isNotInList1(v) {
+      return this.list1.every(k => v.id !== k.id)
+    },
+    isNotInList2(v) {
+      return this.list2.every(k => v.id !== k.id)
+    },
+    deleteEle(ele) {
+      for (const item of this.list1) {
+        if (item.id === ele.id) {
+          const index = this.list1.indexOf(item)
+          this.list1.splice(index, 1)
+          break
+        }
+      }
+      if (this.isNotInList2(ele)) {
+        this.list2.unshift(ele)
+      }
+    },
+    pushEle(ele) {
+      for (const item of this.list2) {
+        if (item.id === ele.id) {
+          const index = this.list2.indexOf(item)
+          this.list2.splice(index, 1)
+          break
+        }
+      }
+      if (this.isNotInList1(ele)) {
+        this.list1.push(ele)
+      }
+    },
+    setData(dataTransfer) {
+      // to avoid Firefox bug
+      // Detail see : https://github.com/RubaXa/Sortable/issues/1012
+      dataTransfer.setData('Text', '')
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.dndList {
+  background: #fff;
+  padding-bottom: 40px;
+  &:after {
+    content: "";
+    display: table;
+    clear: both;
+  }
+  .dndList-list {
+    float: left;
+    padding-bottom: 30px;
+    &:first-of-type {
+      margin-right: 2%;
+    }
+    .dragArea {
+      margin-top: 15px;
+      min-height: 50px;
+      padding-bottom: 30px;
+    }
+  }
+}
+
+.list-complete-item {
+  cursor: pointer;
+  position: relative;
+  font-size: 14px;
+  padding: 5px 12px;
+  margin-top: 4px;
+  border: 1px solid #bfcbd9;
+  transition: all 1s;
+}
+
+.list-complete-item-handle {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  margin-right: 50px;
+}
+
+.list-complete-item-handle2 {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  margin-right: 20px;
+}
+
+.list-complete-item.sortable-chosen {
+  background: #4AB7BD;
+}
+
+.list-complete-item.sortable-ghost {
+  background: #30B08F;
+}
+
+.list-complete-enter,
+.list-complete-leave-active {
+  opacity: 0;
+}
+</style>

+ 65 - 0
src/components/DragSelect/index.vue

@@ -0,0 +1,65 @@
+<template>
+  <el-select ref="dragSelect" v-model="selectVal" v-bind="$attrs" class="drag-select" multiple v-on="$listeners">
+    <slot />
+  </el-select>
+</template>
+
+<script>
+import Sortable from 'sortablejs'
+
+export default {
+  name: 'DragSelect',
+  props: {
+    value: {
+      type: Array,
+      required: true
+    }
+  },
+  computed: {
+    selectVal: {
+      get() {
+        return [...this.value]
+      },
+      set(val) {
+        this.$emit('input', [...val])
+      }
+    }
+  },
+  mounted() {
+    this.setSort()
+  },
+  methods: {
+    setSort() {
+      const el = this.$refs.dragSelect.$el.querySelectorAll('.el-select__tags > span')[0]
+      this.sortable = Sortable.create(el, {
+        ghostClass: 'sortable-ghost', // Class name for the drop placeholder,
+        setData: function(dataTransfer) {
+          dataTransfer.setData('Text', '')
+          // to avoid Firefox bug
+          // Detail see : https://github.com/RubaXa/Sortable/issues/1012
+        },
+        onEnd: evt => {
+          const targetRow = this.value.splice(evt.oldIndex, 1)[0]
+          this.value.splice(evt.newIndex, 0, targetRow)
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.drag-select {
+  ::v-deep {
+    .sortable-ghost {
+      opacity: .8;
+      color: #fff !important;
+      background: #42b983 !important;
+    }
+
+    .el-tag {
+      cursor: pointer;
+    }
+  }
+}
+</style>

File diff suppressed because it is too large
+ 297 - 0
src/components/Dropzone/index.vue


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

@@ -0,0 +1,78 @@
+<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" width="80%" append-to-body>
+      <div slot="title">
+        <span style="padding-right: 10px;">Error Log</span>
+        <el-button size="mini" type="primary" icon="el-icon-delete" @click="clearAll">Clear All</el-button>
+      </div>
+      <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
+    }
+  },
+  methods: {
+    clearAll() {
+      this.dialogTableVisible = false
+      this.$store.dispatch('errorLog/clearErrorLog')
+    }
+  }
+}
+</script>
+
+<style scoped>
+.message-title {
+  font-size: 16px;
+  color: #333;
+  font-weight: bold;
+  padding-right: 8px;
+}
+</style>

File diff suppressed because it is too large
+ 54 - 0
src/components/GithubCorner/index.vue


+ 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>

+ 180 - 0
src/components/HeaderSearch/index.vue

@@ -0,0 +1,180 @@
+<template>
+  <div :class="{'show':show}" class="header-search">
+    <svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
+    <el-select
+      ref="headerSearchSelect"
+      v-model="search"
+      :remote-method="querySearch"
+      filterable
+      default-first-option
+      remote
+      placeholder="Search"
+      class="header-search-select"
+      @change="change"
+    >
+      <el-option v-for="item in options" :key="item.path" :value="item" :label="item.title.join(' > ')" />
+    </el-select>
+  </div>
+</template>
+
+<script>
+// fuse is a lightweight fuzzy-search module
+// make search results more in line with expectations
+import Fuse from 'fuse.js'
+import path from 'path'
+
+export default {
+  name: 'HeaderSearch',
+  data() {
+    return {
+      search: '',
+      options: [],
+      searchPool: [],
+      show: false,
+      fuse: undefined
+    }
+  },
+  computed: {
+    routes() {
+      return this.$store.getters.permission_routes
+    }
+  },
+  watch: {
+    routes() {
+      this.searchPool = this.generateRoutes(this.routes)
+    },
+    searchPool(list) {
+      this.initFuse(list)
+    },
+    show(value) {
+      if (value) {
+        document.body.addEventListener('click', this.close)
+      } else {
+        document.body.removeEventListener('click', this.close)
+      }
+    }
+  },
+  mounted() {
+    this.searchPool = this.generateRoutes(this.routes)
+  },
+  methods: {
+    click() {
+      this.show = !this.show
+      if (this.show) {
+        this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.focus()
+      }
+    },
+    close() {
+      this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.blur()
+      this.options = []
+      this.show = false
+    },
+    change(val) {
+      this.$router.push(val.path)
+      this.search = ''
+      this.options = []
+      this.$nextTick(() => {
+        this.show = false
+      })
+    },
+    initFuse(list) {
+      this.fuse = new Fuse(list, {
+        shouldSort: true,
+        threshold: 0.4,
+        location: 0,
+        distance: 100,
+        maxPatternLength: 32,
+        minMatchCharLength: 1,
+        keys: [{
+          name: 'title',
+          weight: 0.7
+        }, {
+          name: 'path',
+          weight: 0.3
+        }]
+      })
+    },
+    // Filter out the routes that can be displayed in the sidebar
+    // And generate the internationalized title
+    generateRoutes(routes, basePath = '/', prefixTitle = []) {
+      let res = []
+
+      for (const router of routes) {
+        // skip hidden router
+        if (router.hidden) { continue }
+
+        const data = {
+          path: path.resolve(basePath, router.path),
+          title: [...prefixTitle]
+        }
+
+        if (router.meta && router.meta.title) {
+          data.title = [...data.title, router.meta.title]
+
+          if (router.redirect !== 'noRedirect') {
+            // only push the routes with title
+            // special case: need to exclude parent router without redirect
+            res.push(data)
+          }
+        }
+
+        // recursive child routes
+        if (router.children) {
+          const tempRoutes = this.generateRoutes(router.children, data.path, data.title)
+          if (tempRoutes.length >= 1) {
+            res = [...res, ...tempRoutes]
+          }
+        }
+      }
+      return res
+    },
+    querySearch(query) {
+      if (query !== '') {
+        this.options = this.fuse.search(query)
+      } else {
+        this.options = []
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.header-search {
+  font-size: 0 !important;
+
+  .search-icon {
+    cursor: pointer;
+    font-size: 18px;
+    vertical-align: middle;
+  }
+
+  .header-search-select {
+    font-size: 18px;
+    transition: width 0.2s;
+    width: 0;
+    overflow: hidden;
+    background: transparent;
+    border-radius: 0;
+    display: inline-block;
+    vertical-align: middle;
+
+    ::v-deep .el-input__inner {
+      border-radius: 0;
+      border: 0;
+      padding-left: 0;
+      padding-right: 0;
+      box-shadow: none !important;
+      border-bottom: 1px solid #d9d9d9;
+      vertical-align: middle;
+    }
+  }
+
+  &.show {
+    .header-search-select {
+      width: 210px;
+      margin-left: 10px;
+    }
+  }
+}
+</style>

File diff suppressed because it is too large
+ 1779 - 0
src/components/ImageCropper/index.vue


+ 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'
+}

+ 77 - 0
src/components/JsonEditor/index.vue

@@ -0,0 +1,77 @@
+<template>
+  <div class="json-editor">
+    <textarea ref="textarea" />
+  </div>
+</template>
+
+<script>
+import CodeMirror from 'codemirror'
+import 'codemirror/addon/lint/lint.css'
+import 'codemirror/lib/codemirror.css'
+import 'codemirror/theme/rubyblue.css'
+require('script-loader!jsonlint')
+import 'codemirror/mode/javascript/javascript'
+import 'codemirror/addon/lint/lint'
+import 'codemirror/addon/lint/json-lint'
+
+export default {
+  name: 'JsonEditor',
+  /* eslint-disable vue/require-prop-types */
+  props: ['value'],
+  data() {
+    return {
+      jsonEditor: false
+    }
+  },
+  watch: {
+    value(value) {
+      const editorValue = this.jsonEditor.getValue()
+      if (value !== editorValue) {
+        this.jsonEditor.setValue(JSON.stringify(this.value, null, 2))
+      }
+    }
+  },
+  mounted() {
+    this.jsonEditor = CodeMirror.fromTextArea(this.$refs.textarea, {
+      lineNumbers: true,
+      mode: 'application/json',
+      gutters: ['CodeMirror-lint-markers'],
+      theme: 'rubyblue',
+      lint: true
+    })
+
+    this.jsonEditor.setValue(JSON.stringify(this.value, null, 2))
+    this.jsonEditor.on('change', cm => {
+      this.$emit('changed', cm.getValue())
+      this.$emit('input', cm.getValue())
+    })
+  },
+  methods: {
+    getValue() {
+      return this.jsonEditor.getValue()
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.json-editor {
+  height: 100%;
+  position: relative;
+
+  ::v-deep {
+    .CodeMirror {
+      height: auto;
+      min-height: 300px;
+    }
+
+    .CodeMirror-scroll {
+      min-height: 300px;
+    }
+
+    .cm-s-rubyblue span.cm-string {
+      color: #F08047;
+    }
+  }
+}
+</style>

+ 99 - 0
src/components/Kanban/index.vue

@@ -0,0 +1,99 @@
+<template>
+  <div class="board-column">
+    <div class="board-column-header">
+      {{ headerText }}
+    </div>
+    <draggable
+      :list="list"
+      v-bind="$attrs"
+      class="board-column-content"
+      :set-data="setData"
+    >
+      <div v-for="element in list" :key="element.id" class="board-item">
+        {{ element.name }} {{ element.id }}
+      </div>
+    </draggable>
+  </div>
+</template>
+
+<script>
+import draggable from 'vuedraggable'
+
+export default {
+  name: 'DragKanbanDemo',
+  components: {
+    draggable
+  },
+  props: {
+    headerText: {
+      type: String,
+      default: 'Header'
+    },
+    options: {
+      type: Object,
+      default() {
+        return {}
+      }
+    },
+    list: {
+      type: Array,
+      default() {
+        return []
+      }
+    }
+  },
+  methods: {
+    setData(dataTransfer) {
+      // to avoid Firefox bug
+      // Detail see : https://github.com/RubaXa/Sortable/issues/1012
+      dataTransfer.setData('Text', '')
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.board-column {
+  min-width: 300px;
+  min-height: 100px;
+  height: auto;
+  overflow: hidden;
+  background: #f0f0f0;
+  border-radius: 3px;
+
+  .board-column-header {
+    height: 50px;
+    line-height: 50px;
+    overflow: hidden;
+    padding: 0 20px;
+    text-align: center;
+    background: #333;
+    color: #fff;
+    border-radius: 3px 3px 0 0;
+  }
+
+  .board-column-content {
+    height: auto;
+    overflow: hidden;
+    border: 10px solid transparent;
+    min-height: 60px;
+    display: flex;
+    justify-content: flex-start;
+    flex-direction: column;
+    align-items: center;
+
+    .board-item {
+      cursor: pointer;
+      width: 100%;
+      height: 64px;
+      margin: 5px 0;
+      background-color: #fff;
+      text-align: left;
+      line-height: 54px;
+      padding: 5px 10px;
+      box-sizing: border-box;
+      box-shadow: 0px 1px 3px 0 rgba(0, 0, 0, 0.2);
+    }
+  }
+}
+</style>
+

+ 360 - 0
src/components/MDinput/index.vue

@@ -0,0 +1,360 @@
+<template>
+  <div :class="computedClasses" class="material-input__component">
+    <div :class="{iconClass:icon}">
+      <i v-if="icon" :class="['el-icon-' + icon]" class="el-input__icon material-input__icon" />
+      <input
+        v-if="type === 'email'"
+        v-model="currentValue"
+        :name="name"
+        :placeholder="fillPlaceHolder"
+        :readonly="readonly"
+        :disabled="disabled"
+        :autocomplete="autoComplete"
+        :required="required"
+        type="email"
+        class="material-input"
+        @focus="handleMdFocus"
+        @blur="handleMdBlur"
+        @input="handleModelInput"
+      >
+      <input
+        v-if="type === 'url'"
+        v-model="currentValue"
+        :name="name"
+        :placeholder="fillPlaceHolder"
+        :readonly="readonly"
+        :disabled="disabled"
+        :autocomplete="autoComplete"
+        :required="required"
+        type="url"
+        class="material-input"
+        @focus="handleMdFocus"
+        @blur="handleMdBlur"
+        @input="handleModelInput"
+      >
+      <input
+        v-if="type === 'number'"
+        v-model="currentValue"
+        :name="name"
+        :placeholder="fillPlaceHolder"
+        :step="step"
+        :readonly="readonly"
+        :disabled="disabled"
+        :autocomplete="autoComplete"
+        :max="max"
+        :min="min"
+        :minlength="minlength"
+        :maxlength="maxlength"
+        :required="required"
+        type="number"
+        class="material-input"
+        @focus="handleMdFocus"
+        @blur="handleMdBlur"
+        @input="handleModelInput"
+      >
+      <input
+        v-if="type === 'password'"
+        v-model="currentValue"
+        :name="name"
+        :placeholder="fillPlaceHolder"
+        :readonly="readonly"
+        :disabled="disabled"
+        :autocomplete="autoComplete"
+        :max="max"
+        :min="min"
+        :required="required"
+        type="password"
+        class="material-input"
+        @focus="handleMdFocus"
+        @blur="handleMdBlur"
+        @input="handleModelInput"
+      >
+      <input
+        v-if="type === 'tel'"
+        v-model="currentValue"
+        :name="name"
+        :placeholder="fillPlaceHolder"
+        :readonly="readonly"
+        :disabled="disabled"
+        :autocomplete="autoComplete"
+        :required="required"
+        type="tel"
+        class="material-input"
+        @focus="handleMdFocus"
+        @blur="handleMdBlur"
+        @input="handleModelInput"
+      >
+      <input
+        v-if="type === 'text'"
+        v-model="currentValue"
+        :name="name"
+        :placeholder="fillPlaceHolder"
+        :readonly="readonly"
+        :disabled="disabled"
+        :autocomplete="autoComplete"
+        :minlength="minlength"
+        :maxlength="maxlength"
+        :required="required"
+        type="text"
+        class="material-input"
+        @focus="handleMdFocus"
+        @blur="handleMdBlur"
+        @input="handleModelInput"
+      >
+      <span class="material-input-bar" />
+      <label class="material-label">
+        <slot />
+      </label>
+    </div>
+  </div>
+</template>
+
+<script>
+// source:https://github.com/wemake-services/vue-material-input/blob/master/src/components/MaterialInput.vue
+
+export default {
+  name: 'MdInput',
+  props: {
+    /* eslint-disable */
+    icon: String,
+    name: String,
+    type: {
+      type: String,
+      default: 'text'
+    },
+    value: [String, Number],
+    placeholder: String,
+    readonly: Boolean,
+    disabled: Boolean,
+    min: String,
+    max: String,
+    step: String,
+    minlength: Number,
+    maxlength: Number,
+    required: {
+      type: Boolean,
+      default: true
+    },
+    autoComplete: {
+      type: String,
+      default: 'off'
+    },
+    validateEvent: {
+      type: Boolean,
+      default: true
+    }
+  },
+  data() {
+    return {
+      currentValue: this.value,
+      focus: false,
+      fillPlaceHolder: null
+    }
+  },
+  computed: {
+    computedClasses() {
+      return {
+        'material--active': this.focus,
+        'material--disabled': this.disabled,
+        'material--raised': Boolean(this.focus || this.currentValue) // has value
+      }
+    }
+  },
+  watch: {
+    value(newValue) {
+      this.currentValue = newValue
+    }
+  },
+  methods: {
+    handleModelInput(event) {
+      const value = event.target.value
+      this.$emit('input', value)
+      if (this.$parent.$options.componentName === 'ElFormItem') {
+        if (this.validateEvent) {
+          this.$parent.$emit('el.form.change', [value])
+        }
+      }
+      this.$emit('change', value)
+    },
+    handleMdFocus(event) {
+      this.focus = true
+      this.$emit('focus', event)
+      if (this.placeholder && this.placeholder !== '') {
+        this.fillPlaceHolder = this.placeholder
+      }
+    },
+    handleMdBlur(event) {
+      this.focus = false
+      this.$emit('blur', event)
+      this.fillPlaceHolder = null
+      if (this.$parent.$options.componentName === 'ElFormItem') {
+        if (this.validateEvent) {
+          this.$parent.$emit('el.form.blur', [this.currentValue])
+        }
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  // Fonts:
+  $font-size-base: 16px;
+  $font-size-small: 18px;
+  $font-size-smallest: 12px;
+  $font-weight-normal: normal;
+  $font-weight-bold: bold;
+  $apixel: 1px;
+  // Utils
+  $spacer: 12px;
+  $transition: 0.2s ease all;
+  $index: 0px;
+  $index-has-icon: 30px;
+  // Theme:
+  $color-white: white;
+  $color-grey: #9E9E9E;
+  $color-grey-light: #E0E0E0;
+  $color-blue: #2196F3;
+  $color-red: #F44336;
+  $color-black: black;
+  // Base clases:
+  %base-bar-pseudo {
+    content: '';
+    height: 1px;
+    width: 0;
+    bottom: 0;
+    position: absolute;
+    transition: $transition;
+  }
+
+  // Mixins:
+  @mixin slided-top() {
+    top: - ($font-size-base + $spacer);
+    left: 0;
+    font-size: $font-size-base;
+    font-weight: $font-weight-bold;
+  }
+
+  // Component:
+  .material-input__component {
+    margin-top: 36px;
+    position: relative;
+    * {
+      box-sizing: border-box;
+    }
+    .iconClass {
+      .material-input__icon {
+        position: absolute;
+        left: 0;
+        line-height: $font-size-base;
+        color: $color-blue;
+        top: $spacer;
+        width: $index-has-icon;
+        height: $font-size-base;
+        font-size: $font-size-base;
+        font-weight: $font-weight-normal;
+        pointer-events: none;
+      }
+      .material-label {
+        left: $index-has-icon;
+      }
+      .material-input {
+        text-indent: $index-has-icon;
+      }
+    }
+    .material-input {
+      font-size: $font-size-base;
+      padding: $spacer $spacer $spacer - $apixel * 10 $spacer / 2;
+      display: block;
+      width: 100%;
+      border: none;
+      line-height: 1;
+      border-radius: 0;
+      &:focus {
+        outline: none;
+        border: none;
+        border-bottom: 1px solid transparent; // fixes the height issue
+      }
+    }
+    .material-label {
+      font-weight: $font-weight-normal;
+      position: absolute;
+      pointer-events: none;
+      left: $index;
+      top: 0;
+      transition: $transition;
+      font-size: $font-size-small;
+    }
+    .material-input-bar {
+      position: relative;
+      display: block;
+      width: 100%;
+      &:before {
+        @extend %base-bar-pseudo;
+        left: 50%;
+      }
+      &:after {
+        @extend %base-bar-pseudo;
+        right: 50%;
+      }
+    }
+    // Disabled state:
+    &.material--disabled {
+      .material-input {
+        border-bottom-style: dashed;
+      }
+    }
+    // Raised state:
+    &.material--raised {
+      .material-label {
+        @include slided-top();
+      }
+    }
+    // Active state:
+    &.material--active {
+      .material-input-bar {
+        &:before,
+        &:after {
+          width: 50%;
+        }
+      }
+    }
+  }
+
+  .material-input__component {
+    background: $color-white;
+    .material-input {
+      background: none;
+      color: $color-black;
+      text-indent: $index;
+      border-bottom: 1px solid $color-grey-light;
+    }
+    .material-label {
+      color: $color-grey;
+    }
+    .material-input-bar {
+      &:before,
+      &:after {
+        background: $color-blue;
+      }
+    }
+    // Active state:
+    &.material--active {
+      .material-label {
+        color: $color-blue;
+      }
+    }
+    // Errors:
+    &.material--has-errors {
+      &.material--active .material-label {
+        color: $color-red;
+      }
+      .material-input-bar {
+        &:before,
+        &:after {
+          background: transparent;
+        }
+      }
+    }
+  }
+</style>

+ 31 - 0
src/components/MarkdownEditor/default-options.js

@@ -0,0 +1,31 @@
+// doc: https://nhnent.github.io/tui.editor/api/latest/ToastUIEditor.html#ToastUIEditor
+export default {
+  minHeight: '200px',
+  previewStyle: 'vertical',
+  useCommandShortcut: true,
+  useDefaultHTMLSanitizer: true,
+  usageStatistics: false,
+  hideModeSwitch: false,
+  toolbarItems: [
+    'heading',
+    'bold',
+    'italic',
+    'strike',
+    'divider',
+    'hr',
+    'quote',
+    'divider',
+    'ul',
+    'ol',
+    'task',
+    'indent',
+    'outdent',
+    'divider',
+    'table',
+    'image',
+    'link',
+    'divider',
+    'code',
+    'codeblock'
+  ]
+}

+ 118 - 0
src/components/MarkdownEditor/index.vue

@@ -0,0 +1,118 @@
+<template>
+  <div :id="id" />
+</template>
+
+<script>
+// deps for editor
+import 'codemirror/lib/codemirror.css' // codemirror
+import 'tui-editor/dist/tui-editor.css' // editor ui
+import 'tui-editor/dist/tui-editor-contents.css' // editor content
+
+import Editor from 'tui-editor'
+import defaultOptions from './default-options'
+
+export default {
+  name: 'MarkdownEditor',
+  props: {
+    value: {
+      type: String,
+      default: ''
+    },
+    id: {
+      type: String,
+      required: false,
+      default() {
+        return 'markdown-editor-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
+      }
+    },
+    options: {
+      type: Object,
+      default() {
+        return defaultOptions
+      }
+    },
+    mode: {
+      type: String,
+      default: 'markdown'
+    },
+    height: {
+      type: String,
+      required: false,
+      default: '300px'
+    },
+    language: {
+      type: String,
+      required: false,
+      default: 'en_US' // https://github.com/nhnent/tui.editor/tree/master/src/js/langs
+    }
+  },
+  data() {
+    return {
+      editor: null
+    }
+  },
+  computed: {
+    editorOptions() {
+      const options = Object.assign({}, defaultOptions, this.options)
+      options.initialEditType = this.mode
+      options.height = this.height
+      options.language = this.language
+      return options
+    }
+  },
+  watch: {
+    value(newValue, preValue) {
+      if (newValue !== preValue && newValue !== this.editor.getValue()) {
+        this.editor.setValue(newValue)
+      }
+    },
+    language(val) {
+      this.destroyEditor()
+      this.initEditor()
+    },
+    height(newValue) {
+      this.editor.height(newValue)
+    },
+    mode(newValue) {
+      this.editor.changeMode(newValue)
+    }
+  },
+  mounted() {
+    this.initEditor()
+  },
+  destroyed() {
+    this.destroyEditor()
+  },
+  methods: {
+    initEditor() {
+      this.editor = new Editor({
+        el: document.getElementById(this.id),
+        ...this.editorOptions
+      })
+      if (this.value) {
+        this.editor.setValue(this.value)
+      }
+      this.editor.on('change', () => {
+        this.$emit('input', this.editor.getValue())
+      })
+    },
+    destroyEditor() {
+      if (!this.editor) return
+      this.editor.off('change')
+      this.editor.remove()
+    },
+    setValue(value) {
+      this.editor.setValue(value)
+    },
+    getValue() {
+      return this.editor.getValue()
+    },
+    setHtml(value) {
+      this.editor.setHtml(value)
+    },
+    getHtml() {
+      return this.editor.getHtml()
+    }
+  }
+}
+</script>

+ 101 - 0
src/components/Pagination/index.vue

@@ -0,0 +1,101 @@
+<template>
+  <div :class="{'hidden':hidden}" class="pagination-container">
+    <el-pagination
+      :background="background"
+      :current-page.sync="currentPage"
+      :page-size.sync="pageSize"
+      :layout="layout"
+      :page-sizes="pageSizes"
+      :total="total"
+      v-bind="$attrs"
+      @size-change="handleSizeChange"
+      @current-change="handleCurrentChange"
+    />
+  </div>
+</template>
+
+<script>
+import { scrollTo } from '@/utils/scroll-to'
+
+export default {
+  name: 'Pagination',
+  props: {
+    total: {
+      required: true,
+      type: Number
+    },
+    page: {
+      type: Number,
+      default: 1
+    },
+    limit: {
+      type: Number,
+      default: 20
+    },
+    pageSizes: {
+      type: Array,
+      default() {
+        return [10, 20, 30, 50]
+      }
+    },
+    layout: {
+      type: String,
+      default: 'total, sizes, prev, pager, next, jumper'
+    },
+    background: {
+      type: Boolean,
+      default: true
+    },
+    autoScroll: {
+      type: Boolean,
+      default: true
+    },
+    hidden: {
+      type: Boolean,
+      default: false
+    }
+  },
+  computed: {
+    currentPage: {
+      get() {
+        return this.page
+      },
+      set(val) {
+        this.$emit('update:page', val)
+      }
+    },
+    pageSize: {
+      get() {
+        return this.limit
+      },
+      set(val) {
+        this.$emit('update:limit', val)
+      }
+    }
+  },
+  methods: {
+    handleSizeChange(val) {
+      this.$emit('pagination', { page: this.currentPage, limit: val })
+      if (this.autoScroll) {
+        scrollTo(0, 800)
+      }
+    },
+    handleCurrentChange(val) {
+      this.$emit('pagination', { page: val, limit: this.pageSize })
+      if (this.autoScroll) {
+        scrollTo(0, 800)
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.pagination-container {
+  background: #fff;
+  padding: 32px 16px;
+}
+.pagination-container.hidden {
+  display: none;
+}
+</style>

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

@@ -0,0 +1,142 @@
+<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>
+    <!-- eslint-disable-next-line -->
+    <div :style="{backgroundImage: `url(${image})`}" class="pan-thumb"></div>
+  </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-position: center center;
+  background-size: cover;
+  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>

+ 145 - 0
src/components/RightPanel/index.vue

@@ -0,0 +1,145 @@
+<template>
+  <div ref="rightPanel" :class="{show:show}" class="rightPanel-container">
+    <div class="rightPanel-background" />
+    <div class="rightPanel">
+      <div class="handle-button" :style="{'top':buttonTop+'px','background-color':theme}" @click="show=!show">
+        <i :class="show?'el-icon-close':'el-icon-setting'" />
+      </div>
+      <div class="rightPanel-items">
+        <slot />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { addClass, removeClass } from '@/utils'
+
+export default {
+  name: 'RightPanel',
+  props: {
+    clickNotClose: {
+      default: false,
+      type: Boolean
+    },
+    buttonTop: {
+      default: 250,
+      type: Number
+    }
+  },
+  data() {
+    return {
+      show: false
+    }
+  },
+  computed: {
+    theme() {
+      return this.$store.state.settings.theme
+    }
+  },
+  watch: {
+    show(value) {
+      if (value && !this.clickNotClose) {
+        this.addEventClick()
+      }
+      if (value) {
+        addClass(document.body, 'showRightPanel')
+      } else {
+        removeClass(document.body, 'showRightPanel')
+      }
+    }
+  },
+  mounted() {
+    this.insertToBody()
+  },
+  beforeDestroy() {
+    const elx = this.$refs.rightPanel
+    elx.remove()
+  },
+  methods: {
+    addEventClick() {
+      window.addEventListener('click', this.closeSidebar)
+    },
+    closeSidebar(evt) {
+      const parent = evt.target.closest('.rightPanel')
+      if (!parent) {
+        this.show = false
+        window.removeEventListener('click', this.closeSidebar)
+      }
+    },
+    insertToBody() {
+      const elx = this.$refs.rightPanel
+      const body = document.querySelector('body')
+      body.insertBefore(elx, body.firstChild)
+    }
+  }
+}
+</script>
+
+<style>
+.showRightPanel {
+  overflow: hidden;
+  position: relative;
+  width: calc(100% - 15px);
+}
+</style>
+
+<style lang="scss" scoped>
+.rightPanel-background {
+  position: fixed;
+  top: 0;
+  left: 0;
+  opacity: 0;
+  transition: opacity .3s cubic-bezier(.7, .3, .1, 1);
+  background: rgba(0, 0, 0, .2);
+  z-index: -1;
+}
+
+.rightPanel {
+  width: 100%;
+  max-width: 260px;
+  height: 100vh;
+  position: fixed;
+  top: 0;
+  right: 0;
+  box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, .05);
+  transition: all .25s cubic-bezier(.7, .3, .1, 1);
+  transform: translate(100%);
+  background: #fff;
+  z-index: 40000;
+}
+
+.show {
+  transition: all .3s cubic-bezier(.7, .3, .1, 1);
+
+  .rightPanel-background {
+    z-index: 20000;
+    opacity: 1;
+    width: 100%;
+    height: 100%;
+  }
+
+  .rightPanel {
+    transform: translate(0);
+  }
+}
+
+.handle-button {
+  width: 48px;
+  height: 48px;
+  position: absolute;
+  left: -48px;
+  text-align: center;
+  font-size: 24px;
+  border-radius: 6px 0 0 6px !important;
+  z-index: 0;
+  pointer-events: auto;
+  cursor: pointer;
+  color: #fff;
+  line-height: 48px;
+  i {
+    font-size: 24px;
+    line-height: 48px;
+  }
+}
+</style>

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

@@ -0,0 +1,60 @@
+<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.enabled) {
+        this.$message({
+          message: 'you browser can not work',
+          type: 'warning'
+        })
+        return false
+      }
+      screenfull.toggle()
+    },
+    change() {
+      this.isFullscreen = screenfull.isFullscreen
+    },
+    init() {
+      if (screenfull.enabled) {
+        screenfull.on('change', this.change)
+      }
+    },
+    destroy() {
+      if (screenfull.enabled) {
+        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>

+ 103 - 0
src/components/Share/DropdownMenu.vue

@@ -0,0 +1,103 @@
+<template>
+  <div :class="{active:isActive}" class="share-dropdown-menu">
+    <div class="share-dropdown-menu-wrapper">
+      <span class="share-dropdown-menu-title" @click.self="clickTitle">{{ title }}</span>
+      <div v-for="(item,index) of items" :key="index" class="share-dropdown-menu-item">
+        <a v-if="item.href" :href="item.href" target="_blank">{{ item.title }}</a>
+        <span v-else>{{ item.title }}</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    items: {
+      type: Array,
+      default: function() {
+        return []
+      }
+    },
+    title: {
+      type: String,
+      default: 'vue'
+    }
+  },
+  data() {
+    return {
+      isActive: false
+    }
+  },
+  methods: {
+    clickTitle() {
+      this.isActive = !this.isActive
+    }
+  }
+}
+</script>
+
+<style lang="scss" >
+$n: 9; //和items.length 相同
+$t: .1s;
+.share-dropdown-menu {
+  width: 250px;
+  position: relative;
+  z-index: 1;
+  height: auto!important;
+  &-title {
+    width: 100%;
+    display: block;
+    cursor: pointer;
+    background: black;
+    color: white;
+    height: 60px;
+    line-height: 60px;
+    font-size: 20px;
+    text-align: center;
+    z-index: 2;
+    transform: translate3d(0,0,0);
+  }
+  &-wrapper {
+    position: relative;
+  }
+  &-item {
+    text-align: center;
+    position: absolute;
+    width: 100%;
+    background: #e0e0e0;
+    color: #000;
+    line-height: 60px;
+    height: 60px;
+    cursor: pointer;
+    font-size: 18px;
+    overflow: hidden;
+    opacity: 1;
+    transition: transform 0.28s ease;
+    &:hover {
+      background: black;
+      color: white;
+    }
+    @for $i from 1 through $n {
+      &:nth-of-type(#{$i}) {
+        z-index: -1;
+        transition-delay: $i*$t;
+        transform: translate3d(0, -60px, 0);
+      }
+    }
+  }
+  &.active {
+    .share-dropdown-menu-wrapper {
+      z-index: 1;
+    }
+    .share-dropdown-menu-item {
+      @for $i from 1 through $n {
+        &:nth-of-type(#{$i}) {
+          transition-delay: ($n - $i)*$t;
+          transform: translate3d(0, ($i - 1)*60px, 0);
+        }
+      }
+    }
+  }
+}
+</style>

+ 57 - 0
src/components/SizeSelect/index.vue

@@ -0,0 +1,57 @@
+<template>
+  <el-dropdown trigger="click" @command="handleSetSize">
+    <div>
+      <svg-icon class-name="size-icon" icon-class="size" />
+    </div>
+    <el-dropdown-menu slot="dropdown">
+      <el-dropdown-item v-for="item of sizeOptions" :key="item.value" :disabled="size===item.value" :command="item.value">
+        {{
+          item.label }}
+      </el-dropdown-item>
+    </el-dropdown-menu>
+  </el-dropdown>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      sizeOptions: [
+        { label: '默认', value: 'default' },
+        { label: '中号', value: 'medium' },
+        { label: '小号', value: 'small' },
+        { label: '迷你', value: 'mini' }
+      ]
+    }
+  },
+  computed: {
+    size() {
+      return this.$store.getters.size
+    }
+  },
+  methods: {
+    handleSetSize(size) {
+      this.$ELEMENT.size = size
+      this.$store.dispatch('app/setSize', size)
+      this.refreshView()
+      this.$message({
+        message: 'Switch Size Success',
+        type: 'success'
+      })
+    },
+    refreshView() {
+      // In order to make the cached page re-rendered
+      this.$store.dispatch('tagsView/delAllCachedViews', this.$route)
+
+      const { fullPath } = this.$route
+
+      this.$nextTick(() => {
+        this.$router.replace({
+          path: '/redirect' + fullPath
+        })
+      })
+    }
+  }
+
+}
+</script>

+ 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>

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


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