Bladeren bron

首次提交

戴艳蓉 3 jaren geleden
commit
37aeaf0559
100 gewijzigde bestanden met toevoegingen van 5840 en 0 verwijderingen
  1. 14 0
      .editorconfig
  2. 7 0
      .env.development
  3. 7 0
      .env.production
  4. 7 0
      .env.staging
  5. 4 0
      .eslintignore
  6. 198 0
      .eslintrc.js
  7. 16 0
      .gitignore
  8. 5 0
      .travis.yml
  9. 21 0
      LICENSE
  10. 39 0
      README.md
  11. 14 0
      babel.config.js
  12. 35 0
      build/index.js
  13. 24 0
      jest.config.js
  14. 9 0
      jsconfig.json
  15. 57 0
      mock/index.js
  16. 81 0
      mock/mock-server.js
  17. 29 0
      mock/table.js
  18. 84 0
      mock/user.js
  19. 25 0
      mock/utils.js
  20. 75 0
      package.json
  21. 8 0
      postcss.config.js
  22. BIN
      public/favicon.ico
  23. 18 0
      public/index.html
  24. 11 0
      src/App.vue
  25. 9 0
      src/api/table.js
  26. 24 0
      src/api/user.js
  27. 94 0
      src/asnyc/config/axios.js
  28. 71 0
      src/asnyc/service/index.js
  29. BIN
      src/assets/404_images/404.png
  30. BIN
      src/assets/404_images/404_cloud.png
  31. BIN
      src/assets/avatar-default.png
  32. 90 0
      src/components/Breadcrumb/index.vue
  33. 196 0
      src/components/EditGuestList.vue
  34. 799 0
      src/components/ExTable.vue
  35. 47 0
      src/components/Hamburger/index.vue
  36. 171 0
      src/components/IntervalSelect.vue
  37. 24 0
      src/components/NoAuth.vue
  38. 129 0
      src/components/PeriodDatePicker.vue
  39. 62 0
      src/components/SvgIcon/index.vue
  40. 76 0
      src/components/Upload.vue
  41. 194 0
      src/components/activityFindSelect.vue
  42. 181 0
      src/components/businessFindSelect.vue
  43. 138 0
      src/components/dragTable.vue
  44. 190 0
      src/components/guestFindSelect.vue
  45. 210 0
      src/components/tree-table/Index.vue
  46. 25 0
      src/components/tree-table/eval.js
  47. 12 0
      src/config/env.development.js
  48. 10 0
      src/config/env.production.js
  49. 11 0
      src/config/env.staging.js
  50. 4 0
      src/config/index.js
  51. 9 0
      src/icons/index.js
  52. 0 0
      src/icons/svg/dashboard.svg
  53. 1 0
      src/icons/svg/example.svg
  54. 1 0
      src/icons/svg/eye-open.svg
  55. 1 0
      src/icons/svg/eye.svg
  56. 0 0
      src/icons/svg/form.svg
  57. 1 0
      src/icons/svg/link.svg
  58. 1 0
      src/icons/svg/nested.svg
  59. 1 0
      src/icons/svg/password.svg
  60. 1 0
      src/icons/svg/table.svg
  61. 1 0
      src/icons/svg/tree.svg
  62. 1 0
      src/icons/svg/user.svg
  63. 22 0
      src/icons/svgo.yml
  64. 43 0
      src/layout/components/AppMain.vue
  65. 140 0
      src/layout/components/Navbar.vue
  66. 26 0
      src/layout/components/Sidebar/FixiOSBug.js
  67. 41 0
      src/layout/components/Sidebar/Item.vue
  68. 43 0
      src/layout/components/Sidebar/Link.vue
  69. 82 0
      src/layout/components/Sidebar/Logo.vue
  70. 95 0
      src/layout/components/Sidebar/SidebarItem.vue
  71. 56 0
      src/layout/components/Sidebar/index.vue
  72. 3 0
      src/layout/components/index.js
  73. 94 0
      src/layout/index.vue
  74. 45 0
      src/layout/mixin/ResizeHandler.js
  75. 57 0
      src/main.js
  76. 65 0
      src/permission.js
  77. 139 0
      src/router/index.js
  78. 16 0
      src/settings.js
  79. 8 0
      src/store/getters.js
  80. 19 0
      src/store/index.js
  81. 48 0
      src/store/modules/app.js
  82. 32 0
      src/store/modules/settings.js
  83. 92 0
      src/store/modules/user.js
  84. 49 0
      src/styles/element-ui.scss
  85. 65 0
      src/styles/index.scss
  86. 28 0
      src/styles/mixin.scss
  87. 226 0
      src/styles/sidebar.scss
  88. 48 0
      src/styles/transition.scss
  89. 25 0
      src/styles/variables.scss
  90. 15 0
      src/utils/auth.js
  91. 19 0
      src/utils/dealOptions.js
  92. 49 0
      src/utils/directives.js
  93. 29 0
      src/utils/filters.js
  94. 10 0
      src/utils/get-page-title.js
  95. 117 0
      src/utils/index.js
  96. 40 0
      src/utils/publicMethods.js
  97. 85 0
      src/utils/request.js
  98. 146 0
      src/utils/static-json.js
  99. 63 0
      src/utils/tree.utils.js
  100. 87 0
      src/utils/validate.js

+ 14 - 0
.editorconfig

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

+ 7 - 0
.env.development

@@ -0,0 +1,7 @@
+NODE_ENV='development'
+# must start with VUE_APP_
+VUE_APP_ENV = 'development'
+#base url
+#BASE_URL = ''
+#appid
+VUE_APP_WECHAT_APPID='wx5ac3a2c2d72b6f26'

+ 7 - 0
.env.production

@@ -0,0 +1,7 @@
+NODE_ENV='production'
+# must start with VUE_APP_
+VUE_APP_ENV = 'production'
+#base url
+#BASE_URL = ''
+#appid
+VUE_APP_WECHAT_APPID='wx5ac3a2c2d72b6f26'

+ 7 - 0
.env.staging

@@ -0,0 +1,7 @@
+NODE_ENV='staging'
+# must start with VUE_APP_
+VUE_APP_ENV = 'staging'
+#base url
+#BASE_URL = ''
+#appid
+VUE_APP_WECHAT_APPID='wx5ac3a2c2d72b6f26'

+ 4 - 0
.eslintignore

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

+ 198 - 0
.eslintrc.js

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

+ 16 - 0
.gitignore

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

+ 5 - 0
.travis.yml

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

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

+ 39 - 0
README.md

@@ -0,0 +1,39 @@
+# tk-jz-pc-ui
+
+
+```bash
+
+# install dependency
+npm install
+
+# develop
+npm run dev
+```
+
+This will automatically open http://localhost:9528
+
+## 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']
+    }
+  }
+}

+ 35 - 0
build/index.js

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

+ 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"]
+}

+ 57 - 0
mock/index.js

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

+ 81 - 0
mock/mock-server.js

@@ -0,0 +1,81 @@
+const chokidar = require('chokidar')
+const bodyParser = require('body-parser')
+const chalk = require('chalk')
+const path = require('path')
+const Mock = require('mockjs')
+
+const mockDir = path.join(process.cwd(), 'mock')
+
+function registerRoutes(app) {
+  let mockLastIndex
+  const { mocks } = require('./index.js')
+  const mocksForServer = mocks.map(route => {
+    return responseFake(route.url, route.type, route.response)
+  })
+  for (const mock of mocksForServer) {
+    app[mock.type](mock.url, mock.response)
+    mockLastIndex = app._router.stack.length
+  }
+  const mockRoutesLength = Object.keys(mocksForServer).length
+  return {
+    mockRoutesLength: mockRoutesLength,
+    mockStartIndex: mockLastIndex - mockRoutesLength
+  }
+}
+
+function unregisterRoutes() {
+  Object.keys(require.cache).forEach(i => {
+    if (i.includes(mockDir)) {
+      delete require.cache[require.resolve(i)]
+    }
+  })
+}
+
+// for mock server
+const responseFake = (url, type, respond) => {
+  return {
+    url: new RegExp(`${process.env.VUE_APP_BASE_API}${url}`),
+    type: type || 'get',
+    response(req, res) {
+      console.log('request invoke:' + req.path)
+      res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond))
+    }
+  }
+}
+
+module.exports = app => {
+  // parse app.body
+  // https://expressjs.com/en/4x/api.html#req.body
+  app.use(bodyParser.json())
+  app.use(bodyParser.urlencoded({
+    extended: true
+  }))
+
+  const mockRoutes = registerRoutes(app)
+  var mockRoutesLength = mockRoutes.mockRoutesLength
+  var mockStartIndex = mockRoutes.mockStartIndex
+
+  // watch files, hot reload mock server
+  chokidar.watch(mockDir, {
+    ignored: /mock-server/,
+    ignoreInitial: true
+  }).on('all', (event, path) => {
+    if (event === 'change' || event === 'add') {
+      try {
+        // remove mock routes stack
+        app._router.stack.splice(mockStartIndex, mockRoutesLength)
+
+        // clear routes cache
+        unregisterRoutes()
+
+        const mockRoutes = registerRoutes(app)
+        mockRoutesLength = mockRoutes.mockRoutesLength
+        mockStartIndex = mockRoutes.mockStartIndex
+
+        console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed  ${path}`))
+      } catch (error) {
+        console.log(chalk.redBright(error))
+      }
+    }
+  })
+}

+ 29 - 0
mock/table.js

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

+ 84 - 0
mock/user.js

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

+ 25 - 0
mock/utils.js

@@ -0,0 +1,25 @@
+/**
+ * @param {string} url
+ * @returns {Object}
+ */
+function param2Obj(url) {
+  const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
+  if (!search) {
+    return {}
+  }
+  const obj = {}
+  const searchArr = search.split('&')
+  searchArr.forEach(v => {
+    const index = v.indexOf('=')
+    if (index !== -1) {
+      const name = v.substring(0, index)
+      const val = v.substring(index + 1, v.length)
+      obj[name] = val
+    }
+  })
+  return obj
+}
+
+module.exports = {
+  param2Obj
+}

+ 75 - 0
package.json

@@ -0,0 +1,75 @@
+{
+  "name": "tk-jz-pc-ui",
+  "version": "4.4.0",
+  "description": "A vue admin template with Element UI & axios & iconfont & permission control & lint",
+  "author": "Pan <panfree23@gmail.com>",
+  "scripts": {
+    "dev": "vue-cli-service serve",
+    "build:dev": "vue-cli-service build --mode development",
+    "build:sit": "vue-cli-service build --mode staging",
+    "build:prod": "vue-cli-service build --mode production",
+    "preview": "node build/index.js --preview",
+    "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml",
+    "lint": "eslint --ext .js,.vue src",
+    "test:unit": "jest --clearCache && vue-cli-service test:unit",
+    "test:ci": "npm run lint && npm run test:unit"
+  },
+  "dependencies": {
+    "axios": "^0.21.1",
+    "core-js": "3.6.5",
+    "element-ui": "2.13.2",
+    "file-loader": "^6.2.0",
+    "file-saver": "^2.0.5",
+    "js-cookie": "2.2.0",
+    "js-md5": "^0.7.3",
+    "mavon-editor": "^2.9.1",
+    "normalize.css": "7.0.0",
+    "nprogress": "0.2.0",
+    "path-to-regexp": "2.4.0",
+    "script-loader": "^0.7.2",
+    "sortablejs": "^1.13.0",
+    "underscore": "^1.12.0",
+    "vue": "2.6.10",
+    "vue-count-to": "^1.0.13",
+    "vue-print-nb": "^1.6.0",
+    "vue-quill-editor": "^3.0.6",
+    "vue-router": "3.0.6",
+    "vuex": "3.1.0",
+    "xlsx": "^0.11.16"
+  },
+  "devDependencies": {
+    "@riophae/vue-treeselect": "0.0.38",
+    "@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",
+    "connect": "3.6.6",
+    "eslint": "6.7.2",
+    "eslint-plugin-vue": "6.2.2",
+    "html-webpack-plugin": "3.2.0",
+    "mockjs": "1.0.1-beta3",
+    "runjs": "4.3.2",
+    "sass": "1.26.8",
+    "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.2",
+    "vue-template-compiler": "2.6.10"
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions"
+  ],
+  "engines": {
+    "node": ">=8.9",
+    "npm": ">= 3.0.0"
+  },
+  "license": "MIT"
+}

+ 8 - 0
postcss.config.js

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

BIN
public/favicon.ico


+ 18 - 0
public/index.html

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

+ 11 - 0
src/App.vue

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

+ 9 - 0
src/api/table.js

@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+
+export function getList(params) {
+  return request({
+    url: '/tk-jz-pc-ui/table/list',
+    method: 'get',
+    params
+  })
+}

+ 24 - 0
src/api/user.js

@@ -0,0 +1,24 @@
+import request from '@/utils/request'
+
+export function login(data) {
+  return request({
+    url: '/tk-jz-pc-ui/user/login',
+    method: 'post',
+    data
+  })
+}
+
+export function getInfo(token) {
+  return request({
+    url: '/tk-jz-pc-ui/user/info',
+    method: 'get',
+    params: { token }
+  })
+}
+
+export function logout() {
+  return request({
+    url: '/tk-jz-pc-ui/user/logout',
+    method: 'post'
+  })
+}

+ 94 - 0
src/asnyc/config/axios.js

@@ -0,0 +1,94 @@
+import axios from "axios";
+import store from '@/store'
+import router from '@/router'
+const header = {
+  // "X-Requested-With": "XMLHttpRequest",
+  // "X-Frame-Options": "DENY", // 告诉浏览器不要(DENY)把这个网页放在iFrame内,通常的目的就是要帮助用户对抗点击劫持。
+  // "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
+};
+window.ajaxTimeout = 20000;
+
+export default async (
+  url = "",
+  data = {},
+  type = "GET",
+  params = {},
+  headers = header,
+  onUploadProgress
+) => {
+  type = type.toLowerCase();
+  let obj = {
+    method: type,
+    baseURL: "",
+    url: url,
+    data,
+    params,
+    // // `onUploadProgress` 允许为上传处理进度事件
+    // onUploadProgress: function(progressEvent) {
+    //   // 对原生进度事件的处理
+    // },
+    // // `onDownloadProgress` 允许为下载处理进度事件
+    // onDownloadProgress: function(progressEvent) {
+    //   // 对原生进度事件的处理
+    // },
+    // `cancelToken` 指定用于取消请求的 cancel token
+    // (查看后面的 Cancellation 这节了解更多)
+    // cancelToken: new CancelToken(function (cancel) {
+    // }),
+    // processData: true, // 告诉axios不要去处理发送的数据(重要参数)
+    timeout: window.ajaxTimeout,
+    headers,
+    onUploadProgress
+  };
+  if (onUploadProgress && typeof onUploadProgress === "function") {
+    obj.onUploadProgress = onUploadProgress;
+  }
+  // return await axios(obj)
+  //   .then(response => {
+  //     return response;
+  //   })
+  //   .catch(res => {
+  //     return res;
+  //   });
+  // 请求拦截器
+  axios.interceptors.request.use(
+    config => {
+      if (!config.data.token) {
+        config.data.token = localStorage.getItem("token")
+      }
+      return config;
+    },
+    error => {
+      Toast.clear();
+      // 错误抛到业务代码
+      return Promise.reject(new Error("服务器异常,请联系管理员!"));
+    }
+  );
+  // 添加响应拦截器
+  axios.interceptors.response.use(
+    response => {
+      if (response.data.status == 404 || response.data.status == 405 || response.data.status == 10011) {
+        store.dispatch('user/logout')
+        router.push(`/login`)
+      }
+      return response;
+    },
+    error => {
+      return Promise.reject(error.response.data)
+    }
+  )
+  return new Promise((resolve, reject) => {
+    axios(obj)
+      .then(response => {
+        if (response && response.status === 200) {
+          let res = response.data
+          resolve(res);
+        } else {
+          reject(response);
+        }
+      })
+      .catch(res => {
+        reject(res);
+      });
+  });
+};

+ 71 - 0
src/asnyc/service/index.js

@@ -0,0 +1,71 @@
+import http from "@/asnyc/config/axios";
+import { api } from "@/config";
+export default {
+  //用户登录
+  login: (data, params) =>
+    http(`${api.baseApi}recruit/v1/login`, data, "post", params),
+
+  //获取用户列表
+  user_list: (data, params) =>
+    http(`${api.baseApi}recruit/v1/user_list`, data, "post", params),
+  //客服列表
+  coumster_list: (data, params) =>
+    http(`${api.baseApi}recruit/v1/coumster_list`, data, "post", params),
+  //医生列表
+  doctor_list: (data, params) =>
+    http(`${api.baseApi}recruit/v1/doctor_list`, data, "post", params),
+  //订单管理
+  order_list: (data, params) =>
+    http(`${api.baseApi}recruit/v1/order_list`, data, "post", params),
+  //订单详情
+  order_info: (data, params) =>
+    http(`${api.baseApi}recruit/v1/order_info`, data, "post", params),
+
+  //用户详情
+  get_user_info: (data, params) =>
+    http(`${api.baseApi}recruit/v1/get_user_info`, data, "post", params),
+
+
+  //用户完善接口
+  perfect_info: (data, params) =>
+    http(`${api.baseApi}recruit/v1/perfect_info`, data, "post", params),
+
+  //病案补充
+  prescrib_add: (data, params) =>
+    http(`${api.baseApi}recruit/v1/prescrib_add`, data, "post", params),
+  //病例补充
+  case_add: (data, params) =>
+    http(`${api.baseApi}recruit/case_add`, data, "post", params),
+  //后台管理账户
+  account_add: (data, params) =>
+    http(`${api.baseApi}recruit/v1/account_add`, data, "post", params),
+  //后台账户信息编辑
+  account_save: (data, params) =>
+    http(`${api.baseApi}recruit/v1/account_save`, data, "post", params),
+  //密码修改
+  pwd_reset: (data, params) =>
+    http(`${api.baseApi}recruit/v1/ResetPwd`, data, "post", params),
+  //医生账户上下线
+  doctor_onine: (data, params) =>
+    http(`${api.baseApi}recruit/v1/DoctorOnline`, data, "post", params),
+  //客服账户禁用启用
+  customer_onine: (data, params) =>
+    http(`${api.baseApi}recruit/v1/CustomerOnline`, data, "post", params),
+  //获取省市区备选数据
+  levelLinkage: (data, params) =>
+    http(`${api.baseApi}recruitphone/v1/p_levelLinkage`, data, "post", params),
+  //获取职业备选数据
+  getOccupation: (data, params) =>
+    http(`${api.baseApi}recruitphone/v1/Occupation`, data, "post", params),
+  //获取客户来源备选数据
+  getSource: (data, params) =>
+    http(`${api.baseApi}recruit/v1/getSource`, data, "post", params),
+  //分配就医
+  BindDoctor: (data, params) =>
+    http(`${api.baseApi}recruit/v1/BindDoctor`, data, "post", params),
+
+  //用户就诊记录
+  user_order: (data, params) =>
+    http(`${api.baseApi}recruit/v1/user_order`, data, "post", params),
+
+};

BIN
src/assets/404_images/404.png


BIN
src/assets/404_images/404_cloud.png


BIN
src/assets/avatar-default.png


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

@@ -0,0 +1,90 @@
+<template>
+  <el-breadcrumb class="app-breadcrumb" separator="/">
+    <transition-group name="breadcrumb">
+      <el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
+        <span
+          v-if="item.redirect === 'noRedirect' || index == levelList.length - 1"
+          class="no-redirect"
+          >{{ item.meta.title }}</span
+        >
+        <a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
+      </el-breadcrumb-item>
+    </transition-group>
+  </el-breadcrumb>
+</template>
+
+<script>
+import pathToRegexp from "path-to-regexp";
+
+export default {
+  data() {
+    return {
+      levelList: null,
+    };
+  },
+  watch: {
+    $route() {
+      this.getBreadcrumb();
+    },
+  },
+  created() {
+    this.getBreadcrumb();
+  },
+  methods: {
+    getBreadcrumb() {
+      // only show routes with meta.title
+      let matched = this.$route.matched.filter(
+        (item) => item.meta && item.meta.title
+      );
+      // const first = matched[0];
+      // console.log(first);
+      // if (!this.isDashboard(first)) {
+      //   matched = [{ path: "/dashboard", meta: { title: "Dashboard" } }].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>

+ 196 - 0
src/components/EditGuestList.vue

@@ -0,0 +1,196 @@
+<template>
+  <div>
+    <el-tag
+      v-for="tag in tags"
+      :key="tag.value"
+      :closable="!isEdit"
+      :disabled="isEdit"
+      :disable-transitions="false"
+      class="my-tag"
+      @close="handleClose(tag)"
+    >
+      {{tag.label}}
+    </el-tag>
+    <el-select
+      v-if="!isEdit"
+      :disabled="isEdit"
+      v-model="value"
+      filterable
+      remote
+      reserve-keyword
+      placeholder="请输入嘉宾姓名"
+      :remote-method="getAll"
+      :loading="loading"
+      @change="change()"
+    >
+
+      <el-option
+        v-for="(item,index) in options"
+        :key="index"
+        :label="item.label"
+        :value="item.value"
+        :disabled="item.disabled"
+      >
+        <span class="fl fl-label">{{item.label}}</span><span class="fr">{{item.phone}}</span>
+      </el-option>
+      <span
+        v-if="(pageInfo.currPage+1)*pageInfo.pageSize<pageInfo.total"
+        id="add-more"
+        @click="addMore()"
+      >
+        <i
+          v-if="loading"
+          class="el-icon-loading"
+        ></i>
+
+        加载更多...</span>
+    </el-select>
+  </div>
+</template>
+<script>
+  import { setTimeout } from "timers"
+  //修改嘉宾列表
+  //timeReturned 返回值{startTime: Number,endTime: Number}
+  export default {
+    name: "editGuestList",
+    data() {
+      return {
+        dataForm: {
+          guestName: "",
+          guestPhone: "",
+          orderField: "",
+          orderType: "",
+          page: 0,
+          pageSize: 0,
+          startTime: "",
+          endTime: ""
+        },
+        pageInfo: {
+          currPage: 1,
+          pageSize: 10,
+          total: 0
+        },
+        options: [],
+        value: "",
+        loading: false,
+        tags: this.tagsList,
+        isEdit: this.disabled
+      }
+    },
+    props: {
+      tagsList: Array,
+      disabled: Boolean
+    },
+    mounted() {
+      setTimeout(() => {
+        this.tags = this.tagsList
+        this.isEdit = this.disabled
+      }, 500)
+    },
+    methods: {
+      getAll(query) {
+        if (typeof query == "string" && query !== "") {
+          let str = query.replace(/ /g, "")
+          if (str !== "" && str.length <= 20) {
+            this.dataForm.guestName = str
+            this.pageInfo.currPage = 1
+            this.getGuest()
+          } else if (str.length > 20) {
+            this.$message.success({
+              message: `关键字不得超过20字`,
+              type: "waining"
+            })
+          }
+        }
+      },
+      getGuest() {
+        this.loading = true
+        this.dataForm.page = this.pageInfo.currPage
+        this.dataForm.pageSize = this.pageInfo.pageSize
+        this.axios
+          .post(`/guestManage/queryGuestList`, this.dataForm)
+          .then(res => {
+            if (
+              res &&
+              res.data &&
+              res.status == 200 &&
+              res.data.data &&
+              res.data.data.length > 0
+            ) {
+              if (this.dataForm.page == 1) {
+                this.options = []
+              }
+              let data = [].concat(...res.data.data)
+              data.forEach(item => {
+                let ischanged = this.tags.findIndex(
+                  item2 => item2.value == item.guestId
+                )
+                let model = {
+                  value: item.guestId,
+                  label: item.guestName,
+                  phone: item.guestPhone,
+                  disabled: ischanged == -1 ? false : true
+                }
+                this.options.push(model)
+              })
+              this.pageInfo.currPage = res.data.currPage
+              this.pageInfo.pageSize = res.data.pageSize
+              this.pageInfo.total = res.data.totalCount
+              this.loading = false
+            } else {
+              this.loading = false
+            }
+          })
+          .catch(error => {
+            this.loading = false
+            // console.log(error);
+          })
+      },
+      addMore() {
+        this.pageInfo.currPage++
+        this.getGuest()
+      },
+      change() {
+        let index1 = this.findIndex(this.options, this.value)
+        let index2 = this.findIndex(this.tags, this.value)
+        if (this.tags.length > 0 && index2 > -1) {
+        } else {
+          this.tags.push(this.options[index1])
+        }
+        this.options[index1].disabled = true
+        this.value = ""
+        this.$emit("resTags", {
+          tags: this.tags
+        })
+      },
+
+      findIndex(list, value) {
+        return list.findIndex(item => item.value == value)
+      },
+
+      handleClose(tag) {
+        let index = this.findIndex(this.options, tag.value)
+        if (index > -1) {
+          this.options[index].disabled = false
+        }
+        this.tags.splice(this.tags.indexOf(tag), 1)
+        this.$emit("resTags", {
+          tags: this.tags
+        })
+      }
+    }
+  }
+</script>
+<style lang="scss" scoped>
+.my-tag {
+  margin-right: 10px;
+}
+.fl-label {
+  display: inline-block;
+  width: 90px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+</style>
+
+

+ 799 - 0
src/components/ExTable.vue

@@ -0,0 +1,799 @@
+<template>
+  <div>
+    <div class="table-header">
+      <template v-for="(item, index) in table._defaultHeader_">
+        <el-dropdown
+          v-if="item == 'setcol'"
+          placement="bottom-end"
+          trigger="click"
+          :hide-on-click="false"
+          :key="index"
+        >
+          <el-button size="small">列设置</el-button>
+          <el-dropdown-menu slot="dropdown" :class="'ex-table-setcol-dropdown'">
+            <template v-for="(column, index) in setColumns">
+              <el-dropdown-item v-if="!column._noset_" :key="'setColumns'+index">
+                <el-switch
+                  v-model="column._hidden_"
+                  :active-text="column.label"
+                  :active-value="false"
+                  :inactive-value="true"
+                ></el-switch>
+              </el-dropdown-item>
+            </template>
+          </el-dropdown-menu>
+        </el-dropdown>
+        <el-button
+          v-if="item == 'screen'"
+          :key="index"
+          size="small"
+          @click="isScreenDialog = true"
+        >筛选</el-button>
+      </template>
+      <slot name="table-header" :selection="tableSelection" :alldata="data"></slot>
+    </div>
+    <el-table
+      ref="elTable"
+      :data="data"
+      v-bind="table"
+      v-on="$listeners"
+      @selection-change="tableSelection = $event;"
+      style="width:100%;"
+    >
+      <template v-for="(column,index) in columns">
+        <el-table-column v-if="!column._hidden_ && column.type && !column._slot_" v-bind="column" :key="index"></el-table-column>
+        <el-table-column v-else-if="!column._hidden_" v-bind="column" :key="index">
+          <template slot="header" slot-scope="scope">
+            <span>{{column.label}}</span>
+            <span v-if="column._screen_" @click.stop="addConditionItemByCol(column, scope)">
+              <i
+                v-if="column._screen_tip_ === false"
+                :style="isHasScreenColumn(column) ? 'color:#3888e5;' : ''"
+                class="el-icon-connection"
+              />
+              <el-tooltip
+                v-else
+                class="item"
+                :effect="column._screen_tip_effect_ ||'dark'"
+                :content="column._screen_tip_ || '筛选'"
+                :placement="column._screen_tip_placement_ ||'top'"
+              >
+                <i
+                  :style="isHasScreenColumn(column) ? 'color:#3888e5;' : ''"
+                  class="el-icon-connection"
+                />
+              </el-tooltip>
+            </span>
+          </template>
+          <slot v-if="column._slot_" slot-scope="scope" :name="column._slot_" :scope="scope"></slot>
+          <template v-else-if="column._render_" slot-scope="scope">
+            <ex-slot :column="column" :render="column._render_" :scope="scope"></ex-slot>
+          </template>
+          <template v-else slot-scope="scope">
+            <span
+              v-html="column._format_ ? column._format_(scope.row) : getObjPrototype(scope.row, column.prop)"
+            ></span>
+          </template>
+        </el-table-column>
+      </template>
+    </el-table>
+    <el-pagination
+      v-if="page !== false && data && data.length > 0"
+      :current-page="page.curr"
+      :page-sizes="[10, 20, 50, 100]"
+      :total="page.total"
+      layout="total, sizes, prev, pager, next, jumper"
+      style="text-align:right"
+      class="fu-page"
+      @current-change="$emit('page-curr-change', $event)"
+      @size-change="$emit('page-size-change', $event)"
+    />
+    <!-- 筛选框 begin -->
+    <div class="dialog-screen" v-show="isScreenDialog">
+      <div class="header" v-drap.parent.limit>
+        <span>设置筛选</span>
+        <i
+          @click="screenReset"
+          class="el-icon-close"
+          :style="'line-height:inherit;cursor:pointer;'"
+        />
+      </div>
+      <div class="body">
+        <div class="condition">
+          <div class="condition-left">且</div>
+          <div class="condition-right">
+            <div
+              :id="'conditionItem'+conditionIndex"
+              class="condition-item"
+              v-for="(conditionItem, conditionIndex) in conditions"
+              :key="'conditionItem'+conditionIndex"
+            >
+              <hr />
+              <el-select
+                :class="{'el-error': !conditionItem.prop || conditionItem.prop == ''}"
+                v-model="conditionItem.prop"
+                :clearable="true"
+                placeholder="请选择"
+                @change="screenPropsChange($event, conditionIndex, conditionItem)"
+              >
+                <el-option
+                  v-for="screenColumnItem in screenColumns"
+                  :key="'conditionItem_screenCol'+conditionIndex+screenColumnItem.prop"
+                  :label="screenColumnItem.label"
+                  :value="screenColumnItem.prop"
+                ></el-option>
+              </el-select>
+              <el-select
+                :class="{'el-error': !conditionItem.keyId || conditionItem.keyId == ''}"
+                v-model="conditionItem.keyId"
+                :clearable="true"
+                placeholder="请选择"
+                @change="screenKeysChange($event, conditionIndex, conditionItem)"
+              >
+                <el-option
+                  v-for="screenKeyItem in conditionItem._screenKeys_"
+                  :key="'conditionItem_screenKey'+conditionIndex+screenKeyItem.id"
+                  :label="screenKeyItem.label"
+                  :value="screenKeyItem.id"
+                ></el-option>
+              </el-select>
+              <template v-for="(typeItem, typeIndex) in conditionItem.type">
+                <hr
+                  v-if="typeIndex > 0"
+                  :key="'conditionItem_hr'+conditionIndex+typeItem+typeIndex"
+                  :class="'divider-hr'"
+                />
+                <el-input
+                  v-if="typeIndex == 0 && typeItem === 'text'"
+                  :class="{'el-error': conditionItem.value === undefined || conditionItem.value === ''}"
+                  v-model="conditionItem.value"
+                  :key="'conditionItem_val'+conditionIndex+typeItem+typeIndex"
+                  placeholder="请输入内容"
+                  maxlength="50"
+                  @input="$forceUpdate()"
+                ></el-input>
+                <el-input
+                  v-else-if="typeIndex > 0 && typeItem === 'text'"
+                  :class="{'el-error': conditionItem.value_end === undefined || conditionItem.value_end === ''}"
+                  v-model="conditionItem.value_end"
+                  :key="'conditionItem_val_end'+conditionIndex+typeItem+typeIndex"
+                  placeholder="请输入内容"
+                  maxlength="50"
+                  @input="$forceUpdate()"
+                ></el-input>
+                <el-input-number
+                  v-else-if="typeIndex == 0 && typeItem === 'number'"
+                  :class="{'el-error': conditionItem.value === undefined || conditionItem.value === ''}"
+                  v-model="conditionItem.value"
+                  :key="'conditionItem_val'+conditionIndex+typeItem+typeIndex"
+                  :min="0"
+                  :max="999"
+                  @change="$forceUpdate()"
+                ></el-input-number>
+                <el-input-number
+                  v-else-if="typeIndex > 0 && typeItem === 'number'"
+                  :class="{'el-error': conditionItem.value_end === undefined || conditionItem.value_end === ''}"
+                  v-model="conditionItem.value_end"
+                  :key="'conditionItem_val_end'+conditionIndex+typeItem+typeIndex"
+                  :min="0"
+                  :max="999"
+                  @change="$forceUpdate()"
+                ></el-input-number>
+                <el-date-picker
+                  v-else-if="typeIndex == 0 && (typeItem === 'date' || typeItem === 'datetime')"
+                  :class="{'el-error': conditionItem.value === undefined || conditionItem.value === ''}"
+                  v-model="conditionItem.value"
+                  :key="'conditionItem_val'+conditionIndex+typeItem+typeIndex"
+                  :type="typeItem"
+                  :value-format="'timestamp'"
+                  placeholder="选择日期"
+                  @blur="$forceUpdate()"
+                  @input="$forceUpdate()"
+                ></el-date-picker>
+                <el-date-picker
+                  v-else-if="typeIndex > 0 && (typeItem === 'date' || typeItem === 'datetime')"
+                  :class="{'el-error': conditionItem.value_end === undefined || conditionItem.value_end === ''}"
+                  v-model="conditionItem.value_end"
+                  :key="'conditionItem_val_end'+conditionIndex+typeItem+typeIndex"
+                  :type="typeItem"
+                  :value-format="'timestamp'"
+                  placeholder="选择日期"
+                  @blur="$forceUpdate()"
+                  @input="$forceUpdate()"
+                ></el-date-picker>
+                <el-select
+                  v-else-if="typeIndex == 0 && typeItem === 'select'"
+                  :class="{'el-select-val': true, 'el-error': conditionItem.value === undefined || conditionItem.value === ''}"
+                  v-model="conditionItem.value"
+                  :key="'conditionItem_val'+conditionIndex+typeItem+typeIndex"
+                  :clearable="true"
+                  placeholder="请选择"
+                  @change="$forceUpdate()"
+                >
+                  <el-option
+                    v-for="(optionItem, optionIndex) in conditionItem._select_options_"
+                    :key="'conditionItem_val_option'+conditionIndex+typeItem+typeIndex+optionIndex"
+                    :label="optionItem.label"
+                    :value="optionItem.id"
+                  ></el-option>
+                </el-select>
+                <el-select
+                  v-else-if="typeIndex > 0 && typeItem === 'select'"
+                  :class="{'el-select-val': true, 'el-error': conditionItem.value_end === undefined || conditionItem.value_end === ''}"
+                  v-model="conditionItem.value_end"
+                  :key="'conditionItem_val_end'+conditionIndex+typeItem+typeIndex"
+                  :clearable="true"
+                  placeholder="请选择"
+                  @change="$forceUpdate()"
+                >
+                  <el-option
+                    v-for="(optionItem, optionIndex) in conditionItem._select_options_end_"
+                    :key="'conditionItem_val_option_end'+conditionIndex+typeItem+typeIndex+optionIndex"
+                    :label="optionItem.label"
+                    :value="optionItem.id"
+                  ></el-option>
+                </el-select>
+              </template>
+              <el-button
+                :style="'padding:10px;flex-shrink:0;'"
+                size="mini"
+                icon="el-icon-delete"
+                @click="delConditionItemByIndex(conditionIndex, conditionItem)"
+              ></el-button>
+            </div>
+            <div class="condition-item">
+              <hr />
+              <el-button
+                type="primary"
+                icon="el-icon-circle-plus-outline"
+                circle
+                @click="addConditionItemByCol()"
+              ></el-button>
+            </div>
+          </div>
+        </div>
+      </div>
+      <el-row :class="'footer'">
+        <el-button @click.stop="screenReset">重置</el-button>
+        <el-button @click.stop="screenSubmit" type="primary">筛选</el-button>
+      </el-row>
+    </div>
+    <!-- 筛选框 end -->
+  </div>
+</template>
+
+<script>
+import Sortable from "sortablejs";
+
+export default {
+  name: "ex-table",
+  components: {
+    "ex-slot": {
+      functional: true,
+      props: {
+        render: Function,
+        scope: {
+          type: Object,
+          default: null
+        },
+        column: {
+          type: Object,
+          default: null
+        }
+      },
+      render: (h, data) => {
+        if (data.props.column) data.props.scope.column = data.props.column;
+        return data.props.render(h, data.props.scope);
+      }
+    }
+  },
+  props: {
+    /**
+     * el-table 属性集合
+     * 自定义属性:
+     * @param {Array} _defaultHeader_  : 默认的表格头部,支持['setcol', 'screen'] setcol-列设置 screen-筛选
+     * 可使用插槽扩展表格头部:
+     * table-header - 具名插槽
+     * slotProps:selection - 表格选中的数据  alldata - 表格当前全部数据
+     * <template #table-header="slotProps">
+     *   <el-button size="small" @click="click(slotProps)">扩展1</el-button>
+     * </template>
+     */
+    table: {
+      type: Object,
+      default: () => {}
+    },
+    // el-table 表格数据 对应data字段
+    data: {
+      type: Array,
+      default: () => []
+    },
+    /**
+     * el-table-column 表格列属性集合
+     * 自定义属性:
+     * @param {Boolean}       _noset_        : 是否不允许设置列
+     * @param {Boolean}        _screen_      : 是否允许筛选
+     * @param {Boolean}        _hidden_      : 是否隐藏列
+     * @param {String/Boolean} _screen_tip_  : 筛选按钮提示信息,为false不提示
+     * @param {String} _screen_tip_effect_   : 筛选按钮提示信息主题,dark/light
+     * @param {String} _screen_tip_placement_: 筛选按钮提示信息位置,top/top-start/top-end/bottom/bottom-start/bottom-end/left/left-start/left-end/right/right-start/right-end
+     * @param {Array}          _keys_        : 支持的筛选key
+     * @param {Array}      _select_options_  : 若需支持包含select的key,value的备选数据,示例:{_select_options_: [{id:'', label:''},...]}
+     * @param {Array}  _select_options_end_  : 若需支持包含select的key,value_end的备选数据,默认使用_select_options_,示例:{_select_options_: [{id:'', label:''},...]}
+     * @param {Array}          _keys_        : 支持的筛选key
+     * @param {String}         _slot_        : 使用具名插槽时的名称
+     * @param {Function}       _format_      : 数据格式化函数,支持返回html
+     * @param {Function}       _render_      : 自定义列,采用vue中的render函数,示例: { _render_:(h, scope) => { return h(...) } }
+     */
+    columns: {
+      type: Array,
+      default: () => []
+    },
+    /**
+     * el-pagination
+     * @param {Boolean} page 是否开启表格分页,若不传或传false则不开启
+     * @param {Object} page {
+     *   size: '10'
+     *   total: '100'
+     *   curr: '1'
+     * }
+     * Event:
+     * page-curr-change  分页当前页改变
+     * page-size-change  分页大小改变
+     */
+    page: {
+      type: [Boolean, Object],
+      default: false
+    },
+    /**
+     * 筛选条件集合
+     */
+    conditions: {
+      type: Array,
+      default: () => []
+    },
+    /**
+     * 是否允许拖拽行
+     */
+    isRowDrop: {
+      type: Boolean,
+      default: false
+    },
+    /**
+     * 是否允许拖拽列
+     */
+    isColDrop: {
+      type: Boolean,
+      default: false
+    }
+  },
+  computed: {
+    // 允许筛选的列
+    screenColumns() {
+      return this.columns.filter(item => item._screen_ === true);
+    },
+    // 允许设置的列
+    setColumns() {
+      return this.columns.filter(item => !item._noset_);
+    },
+    // 筛选条件是否包含当前列
+    isHasScreenColumn() {
+      return function(col) {
+        return this.conditions.some(item => item.prop === col.prop);
+      };
+    }
+  },
+  data() {
+    return {
+      // 表格选中项
+      tableSelection: [],
+      // 是否显示筛选框
+      isScreenDialog: false,
+      // 筛选的key集合
+      screenKeys: [
+        { id: "1", key: "1", type: "text", label: "包含" },
+        { id: "2", key: "2", type: "text", label: "不包含" },
+        { id: "3", key: "3", type: "text", label: "等于" },
+        { id: "4", key: "4", type: "text", label: "不等于" },
+        { id: "5", key: "5", type: "", label: "为空" },
+        { id: "6", key: "6", type: "", label: "不为空" },
+        { id: "7", key: "7", type: "date", label: "开始于" },
+        { id: "8", key: "8", type: "date", label: "结束于" },
+        { id: "9", key: "9", type: "date", label: "早于" },
+        { id: "10", key: "10", type: "date", label: "晚于" },
+        { id: "11", key: "11", type: "date", label: "不早于" },
+        { id: "12", key: "12", type: "date", label: "不晚于" },
+        { id: "13", key: "13", type: "datetime_datetime", label: "时间段" },
+        { id: "14", key: "14", type: "number", label: "前N天" },
+        { id: "15", key: "15", type: "number", label: "后N天" },
+        { id: "16", key: "16", type: "number", label: "前N月" },
+        { id: "17", key: "17", type: "number", label: "后N月" },
+        { id: "18", key: "18", type: "number", label: "大于" },
+        { id: "19", key: "19", type: "number", label: "大于等于" },
+        { id: "20", key: "20", type: "number", label: "小于" },
+        { id: "21", key: "21", type: "number", label: "小于等于" },
+        { id: "22", key: "22", type: "number_number", label: "介于" },
+        { id: "23", key: "3", type: "select", label: "单项选择" },
+        { id: "24", key: "22", type: "select_select", label: "区间选择" },
+      ]
+    };
+  },
+  watch: {},
+  mounted() {
+    // 拖拽绑定
+    this.rowDrop();
+    this.columnDrop();
+  },
+  directives: {
+    // v-drap  可拖拽指令
+    drap: {
+      inserted: function(element, binding) {
+        element.onmousedown = function(e) {
+          let el = binding.modifiers.parent ? element.parentNode : element,
+            bodyWidth = document.body.offsetWidth,
+            bodyHeight = document.body.offsetHeight,
+            elWidth = el.offsetWidth,
+            elHeight = el.offsetHeight;
+          // 开始拖动,记录左上角坐标点
+          let disX = e.clientX - el.offsetLeft;
+          let disY = e.clientY - el.offsetTop;
+          document.onmousemove = function(e) {
+            // 拖动中,修改dom的左上角坐标点
+            let l = e.clientX - disX;
+            let t = e.clientY - disY;
+            if (binding.modifiers.limit) {
+              l = l < 0 ? 0 : l;
+              t = t < 0 ? 0 : t;
+              l = l > bodyWidth - elWidth ? bodyWidth - elWidth : l;
+              t = t > bodyHeight - elHeight ? bodyHeight - elHeight : t;
+            }
+            el.style.left = l + "px";
+            el.style.top = t + "px";
+          };
+          document.onmouseup = function() {
+            // 结束拖动
+            document.onmousemove = null;
+          };
+        };
+      }
+    }
+  },
+  methods: {
+    // 添加一行条件
+    addConditionItemByCol(col, scope) {
+      this.isScreenDialog = true;
+      // 当前项
+      const index = col
+        ? this.conditions.findIndex(item => item.prop === col.prop)
+        : undefined;
+      if (col && index !== undefined && index > -1) {
+        const dom = document.querySelector(
+          `.dialog-screen .body #conditionItem${index}`
+        );
+        this.$nextTick(() => {
+          this.screenBodyScrollTop(dom.offsetTop - dom.scrollHeight);
+        });
+      } else {
+        this.conditions.push(
+          Object.assign(
+            {},
+            col || {},
+            col
+              ? {
+                  _select_options_end_:
+                    col._select_options_ && !col._select_options_end_
+                      ? col._select_options_
+                      : col._select_options_end_,
+                  _screenKeys_: col._keys_
+                    ? col._keys_.map(item =>
+                        this.screenKeys.find(fitem => item == fitem.id)
+                      )
+                    : [].concat(this.screenKeys)
+                }
+              : {}
+          )
+        );
+        this.$nextTick(() => {
+          this.screenBodyScrollTop(
+            document.querySelector(".dialog-screen .body").scrollHeight
+          );
+        });
+      }
+    },
+    // 删除一行条件
+    delConditionItemByIndex(index, item) {
+      this.conditions.splice(index, 1);
+    },
+    // 筛选重置
+    screenReset() {
+      this.conditions.splice(0);
+      this.$emit("screen-reset", this.conditions);
+      this.isScreenDialog = false;
+    },
+    // 筛选提交
+    screenSubmit() {
+      if (this.validotarScreen()) {
+        this.$emit("screen-submit", this.conditions);
+        this.isScreenDialog = false;
+      } else {
+        this.$message.error("筛选项及筛选值不可为空");
+      }
+    },
+    // 滚动筛选框body
+    screenBodyScrollTop(t) {
+      document.querySelector(".dialog-screen .body").scrollTop = t;
+    },
+    // 校验筛选框筛选条件
+    validotarScreen() {
+      return this.conditions.every(
+        item =>
+          item.prop &&
+          item.prop != "" &&
+          item.keyId &&
+          item.keyId != "" &&
+          item.key &&
+          item.key != "" &&
+          (item.type.length === 0 ||
+            (item.type.length === 1 &&
+              item.value !== undefined &&
+              item.value !== "") ||
+            (item.type.length > 1 &&
+              item.value !== undefined &&
+              item.value !== "" &&
+              item.value_end !== undefined &&
+              item.value_end !== ""))
+      );
+    },
+    // 筛选条件prop改变
+    screenPropsChange(val, scindex, scitem) {
+      if (val === undefined || val === "") throw new Error("数据异常");
+      // 选中列的原始数据
+      const columnsItem = this.columns.find(item => item.prop === val);
+      // 重置当前筛选条件数据
+      this.$set(
+        this.conditions,
+        scindex,
+        Object.assign(
+          {
+            keyId: "",
+            key: "",
+            value: "",
+            value_end: ""
+          },
+          columnsItem,
+          {
+            _select_options_end_:
+              columnsItem._select_options_ && !columnsItem._select_options_end_
+                ? columnsItem._select_options_
+                : columnsItem._select_options_end_,
+            _screenKeys_: columnsItem._keys_
+              ? columnsItem._keys_.map(item =>
+                  this.screenKeys.find(fitem => item == fitem.id)
+                )
+              : [].concat(this.screenKeys)
+          }
+        )
+      );
+    },
+    // 筛选条件key改变
+    screenKeysChange(val, scindex, scitem) {
+      if (val === undefined || val === "") throw new Error("数据异常");
+      // 选中key支持的类型
+      const typeText = this.screenKeys.find(item => item.id === val).type || "",
+            key = this.screenKeys.find(item => item.id === val).key || "";
+      // 重置当前筛选条件数据
+      this.$set(
+        this.conditions,
+        scindex,
+        Object.assign({}, scitem, {
+          key: key,
+          value: "",
+          value_end: "",
+          typeText: typeText,
+          type: typeText.split("_").filter(item => item !== undefined && item !== "")
+        })
+      );
+    },
+    //行拖拽
+    rowDrop() {
+      const tbody = document.querySelector(
+        ".el-table__body-wrapper > table > tbody"
+      );
+      Sortable.create(tbody, {
+        disabled: !this.isRowDrop,
+        ghostClass: "sortable-ghost",
+        animation: 180,
+        delay: 0,
+        onEndevt: evt => {
+          const currItem = this.data.splice(evt.oldIndex, 1)[0];
+          this.data.splice(evt.newIndex, 0, currItem);
+        }
+      });
+    },
+    //列拖拽
+    columnDrop() {
+      const wrapperTr = document.querySelector(".el-table__header-wrapper tr");
+      Sortable.create(wrapperTr, {
+        disabled: !this.isColDrop,
+        ghostClass: "sortable-ghost",
+        animation: 180,
+        delay: 0,
+        onEnd: evt => {
+          const currItem = this.columns.splice(evt.oldIndex, 1)[0];
+          this.columns.splice(evt.newIndex, 0, currItem);
+        }
+      });
+    },
+    // 获取对象的属性
+    getObjPrototype(o, a) {
+      let fn = Function;
+      return new fn("obj", `return obj.${a}`)(o);
+    },
+    /** 表格方法 传递给el-table,后续有需要可继续增加 **/
+    // 对 Table 进行重新布局。当 Table 或其祖先元素由隐藏切换为显示时,可能需要调用此方法
+    doLayout(...arg) {
+      this.$refs.elTable.doLayout(...arg);
+    },
+    // 用于多选表格,清空用户的选择
+    clearSelection(...arg) {
+      this.$refs.elTable.clearSelection(...arg);
+    },
+    // 用于多选表格,切换某一行的选中状态
+    toggleRowSelection(...arg) {
+      this.$refs.elTable.toggleRowSelection(...arg);
+    },
+    // 用于多选表格,切换所有行的选中状态
+    toggleAllSelection(...arg) {
+      this.$refs.elTable.toggleAllSelection(...arg);
+    },
+    // 用于单选表格,设定某一行为选中行
+    setCurrentRow(...arg) {
+      this.$refs.elTable.setCurrentRow(...arg);
+    },
+    // 用于可展开表格与树形表格,切换某一行的展开状态,如果使用了第二个参数,则是设置这一行展开与否(expanded 为 true 则展开)
+    toggleRowExpansion(...arg) {
+      this.$refs.elTable.toggleRowExpansion(...arg);
+    },
+  }
+};
+</script>
+
+<style lang="css">
+.el-switch__core {
+  flex-shrink: 0;
+}
+.el-switch,
+.el-switch__label {
+  height: auto;
+}
+.dialog-screen .el-error input {
+  border-color: red;
+}
+.dialog-screen .el-input-number,
+.dialog-screen .el-input,
+.dialog-screen .el-date-editor,
+.dialog-screen .el-select-val {
+  width: 100%;
+}
+.dialog-screen .el-input-number__decrease,
+.dialog-screen .el-input-number__increase {
+  top: 2px;
+  bottom: 2px;
+}
+.ex-table-setcol-dropdown {
+  max-width: 180px;
+  max-height: 280px;
+  overflow-y: auto;
+}
+</style>
+
+<style lang="scss" scoped>
+.table-header {
+  display: flex;
+  display: -webkit-flex;
+  background-color: #f5f5f5;
+  padding: 0 10px;
+}
+.table-header > * {
+  margin-top: 10px;
+  margin-bottom: 10px;
+}
+.table-header :nth-child(n + 2) {
+  margin-left: 10px;
+}
+.dialog-screen {
+  position: fixed;
+  z-index: 2000;
+  top: 200px;
+  left: 20%;
+  width: 60%;
+  min-width: 600px;
+  max-height: 80vh;
+  background-color: #fff;
+  box-shadow: 1px 1px 50px rgba(0, 0, 0, 0.3);
+  .header {
+    cursor: move;
+    padding: 0 10px;
+    height: 44px;
+    line-height: 44px;
+    display: flex;
+    display: -webkit-flex;
+    justify-content: space-between;
+    border-bottom: 1px solid #eee;
+    font-size: 14px;
+    color: #333;
+    overflow: hidden;
+    background-color: #f8f8f8;
+    border-radius: 2px 2px 0 0;
+  }
+  .footer {
+    height: 64px;
+    line-height: 64px;
+    float: right;
+    padding: 0 10px;
+  }
+  .body {
+    max-height: calc(80vh - 45px - 64px);
+    overflow: auto;
+    background-color: #fff;
+    .condition {
+      display: flex;
+      display: -webkit-flex;
+      margin: 10px 20px 10px 10px;
+      .condition-left {
+        margin: 25px 0;
+        width: 30px;
+        border-right: 1px #303133 solid;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+      }
+      .condition-right {
+        width: calc(100% - 30px);
+      }
+      .condition-item {
+        padding: 5px 0 5px 20px;
+        margin: 5px 0;
+        height: 38px;
+        background-color: #eff4fd;
+        position: relative;
+        display: flex;
+        display: -webkit-flex;
+        align-items: center;
+        hr {
+          position: absolute;
+          left: 0;
+          top: 23px;
+          width: 20px;
+          margin: 0;
+        }
+        hr ~ * {
+          margin: 0 5px;
+        }
+        :nth-child(2).el-select {
+          flex-shrink: 0;
+          width: 20%;
+        }
+        :nth-child(3).el-select {
+          flex-shrink: 0;
+          width: 20%;
+        }
+        .divider-hr {
+          position: relative !important;
+          flex-shrink: 0 !important;
+          top: 0 !important;
+          margin: 0 !important;
+          width: 10px !important;
+          color: rgb(51, 51, 51) !important;
+        }
+      }
+      :last-child.condition-item hr {
+        top: 27px;
+      }
+      :first-child.condition-item hr {
+        top: 20px;
+      }
+    }
+  }
+}
+</style>

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

@@ -0,0 +1,47 @@
+<template>
+  <div style="padding: 0 0 0 15px;" @click="toggleClick">
+    <i class="el-icon-s-fold"  style="font-size:22px;color:#97a8be;width:30px;heigit:52px;line-height:52px" v-if="isActive"></i>
+     <i class="el-icon-s-unfold" style="font-size:22px;color:#97a8be;width:30px;heigit:52px;line-height:52px" v-else></i>
+    <!-- <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;
+  color: #fff;
+}
+
+.hamburger.is-active {
+  transform: rotate(180deg);
+}
+</style>

+ 171 - 0
src/components/IntervalSelect.vue

@@ -0,0 +1,171 @@
+<template>
+  <div>
+    <el-row class="IntervalSelect">
+      <el-col :span="select === 1 ? 24 : 7" class="select">
+        <el-select v-model="select" placeholder="请选择" style="width:100%;" :disabled="disabled">
+          <el-option
+            v-for="(item, index) of testList"
+            :key="index"
+            :label="item.name"
+            :value="item.id"
+          ></el-option>
+        </el-select>
+      </el-col>
+      <el-col
+        :span="select === 7 ? 8 : select === 1 ? 0 : 17"
+        class="input1 interval-input"
+      >
+        <el-input v-if="isnumber" placeholder="0" v-model="data1" :disabled="disabled" :maxlength="maxlength" @input="data1=$event.replace(/[^\d]/g,'')">
+          <template slot="append">/{{appendName}}</template>
+          <!-- <el-button slot="" icon="el-icon-search"></el-button> -->
+        </el-input>
+        <el-input v-else placeholder="0" v-model="data1" :disabled="disabled" :maxlength="maxlength">
+          <template slot="append">/{{appendName}}</template>
+          <!-- <el-button slot="" icon="el-icon-search"></el-button> -->
+        </el-input>
+      </el-col>
+      <el-col :span="1" v-if="select === 7" class="Interval">-</el-col>
+      <el-col :span="8" v-if="select === 7" class="interval-input">
+        <el-input v-if="isnumber" placeholder="0" v-model="data2" :disabled="disabled" :maxlength="maxlength" @input="data2=$event.replace(/[^\d]/g,'')">
+          <template slot="append">/{{appendName}}</template>
+        </el-input>
+        <el-input v-else placeholder="0" v-model="data2" :disabled="disabled" :maxlength="maxlength">
+          <template slot="append">/{{appendName}}</template>
+        </el-input>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+<script>
+// import { setTimeout } from "timers"
+//修改嘉宾列表
+//timeReturned 返回值{startTime: Number,endTime: Number}
+export default {
+  name: "IntervalSelect",
+  data() {
+    return {
+      data1: "",
+      data2: "",
+      select: 1,
+     
+      testList: [
+        {
+          id: 1,
+          name: "不限"
+        },
+         {
+          id: 2,
+          name: "等于"
+        },
+        {
+          id: 3,
+          name: "小于"
+        },
+        {
+          id: 4,
+          name: "小于等于"
+        },
+        {
+          id: 5,
+          name: "大于"
+        },
+        {
+          id: 6,
+          name: "大于等于"
+        },
+        {
+          id: 7,
+          name: "介于"
+        }
+      ]
+    };
+  },
+  props: {
+    type: {
+      type: String,
+      required: true
+    },
+    disabled: {
+      type: Boolean,
+      required: true
+    },
+    appendName: {
+      type: String,
+      required: true
+    },
+    propSelect: {
+      type: Number,
+      default: 1
+    },
+    propData1: {
+      type: String,
+    },
+    propData2: {
+      type: String,
+    },
+    maxlength: {
+      type: [String, Number],
+      default: 8
+    },
+    isnumber: {
+      type: Boolean,
+      default: false
+    }
+  },
+  watch: {
+    propSelect: function(n, o) {
+      this.select = n;
+    },
+    select: function(n, o) {
+      this.$emit("update:propSelect", n);
+    },
+    propData1: function(n, o) {
+      this.data1 = n;
+    },
+    data1: function(n, o) {
+      this.$emit("update:propData1", n);
+    },
+    propData2: function(n, o) {
+      this.data2 = n;
+    },
+    data2: function(n, o) {
+      this.$emit("update:propData2", n);
+    }
+  },
+  mounted() {
+    setTimeout(() => {
+      if(this.type=='equal'){
+      this.testList= [
+        {
+          id: 1,
+          name: "不限"
+        },
+         {
+          id: 2,
+          name: "等于"
+        },
+        {
+          id: 7,
+          name: "介于"
+        }
+      ]
+      }
+    }, 500)
+  },
+  methods: {
+  }
+};
+</script>
+<style lang="scss" scoped>
+.my-tag {
+  margin-right: 10px;
+}
+.fl-label {
+  display: inline-block;
+  width: 90px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+</style>
+
+

+ 24 - 0
src/components/NoAuth.vue

@@ -0,0 +1,24 @@
+<template>
+  <div class="no-auth">{{ tips }}</div>
+</template>
+
+<script>
+export default {
+  name: "noAuth",
+  props: {
+    tips: {
+      type: String,
+      default: "暂无权限"
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.no-auth {
+  width: 100%;
+  margin-top: 50px;
+  text-align: center;
+  font-size: 20px;
+}
+</style>

+ 129 - 0
src/components/PeriodDatePicker.vue

@@ -0,0 +1,129 @@
+<template>
+  <div class="fl time">
+    <el-date-picker
+      class="date-picker"
+      v-model="startTime"
+      type="date"
+      style="width:150px"
+      placeholder="开始日期"
+      value-format="timestamp"
+      @change="timeChange"
+      :picker-options="pickerOptions1"
+      :editable="false"
+      :clearable="true"
+      :disabled="isEdit"
+    ></el-date-picker>
+    <samp>至</samp>
+    <el-date-picker
+      class="date-picker"
+      style="width:150px"
+      v-model="endTime"
+      type="date"
+      placeholder="结束日期"
+      @change="timeChange"
+      :disabled="isEdit"
+      :picker-options="pickerOptions2"
+      :editable="false"
+      value-format="timestamp"
+      :clearable="true"
+    ></el-date-picker>
+  </div>
+</template>
+
+<script>
+//选择时间段(只有日期)组件
+//timeReturned 返回值{startTime: Number,endTime: Number}
+export default {
+  name: "PeriodDatePicker",
+  data() {
+    return {
+      startTime: "",
+      endTime: "",
+      isEdit: this.disabled,
+      pickerOptions1: {
+        disabledDate: time => {
+          if (this.endTime != null && this.endTime != "" && time) {
+            return time.getTime() >= this.endTime;
+          }
+        }
+      },
+      pickerOptions2: {
+        disabledDate: time => {
+          if (this.startTime != null && this.startTime != "" && time) {
+            return time.getTime() <= this.startTime;
+          }
+        }
+      }
+    };
+  },
+  props: {
+    start: {
+      type: String,
+      default: ""
+    },
+    end: {
+      type: String,
+      default: ""
+    },
+    disabled: {
+      type: Boolean,
+      default: false
+    }
+  },
+  watch: {
+    disabled(a, b) {
+      this.isEdit = a;
+      //  console.log(a, b,this.isEdit);
+    }
+  },
+  mounted() {
+    // setTimeout(() => {
+    //   this.isEdit = this.disabled;
+    //   this.startTime = this.start;
+    //   this.endTime = this.end;
+    // }, 500);
+  },
+  methods: {
+    timeChange() {
+      if (
+        this.startTime !== "" &&
+        this.startTime !== null &&
+        this.endTime !== "" &&
+        this.endTime !== null
+      ) {
+        if (
+          this.endTime &&
+          this.endTime - this.startTime > 24 * 3600 * 365 * 1000
+        ) {
+          this.$message.error("时间跨度不能超过一年");
+          this.startTime = "";
+          this.endTime = "";
+          this.timeReturned();
+          return;
+        } else if (this.endTime < this.startTime) {
+          this.$message.error("结束时间不大于开始时间!");
+          this.startTime = "";
+          this.endTime = "";
+          this.timeReturned();
+          return;
+        } else {
+          this.timeReturned();
+        }
+      } else {
+        this.timeReturned();
+      }
+    },
+    timeReturned() {
+      this.$emit("timeReturned", {
+        startTime: this.startTime == null ? "" : this.startTime,
+        endTime: this.endTime == null ? "" : this.endTime
+      });
+    }
+  }
+};
+</script>
+<style lang="scss">
+.date-picker.el-input {
+  width: 150px !important;
+}
+</style>

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

@@ -0,0 +1,62 @@
+<template>
+  <div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" />
+  <svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
+    <use :xlink:href="iconName" />
+  </svg>
+</template>
+
+<script>
+// doc: https://panjiachen.github.io/vue-element-admin-site/feature/component/svg-icon.html#usage
+import { isExternal } from '@/utils/validate'
+
+export default {
+  name: 'SvgIcon',
+  props: {
+    iconClass: {
+      type: String,
+      required: true
+    },
+    className: {
+      type: String,
+      default: ''
+    }
+  },
+  computed: {
+    isExternal() {
+      return isExternal(this.iconClass)
+    },
+    iconName() {
+      return `#icon-${this.iconClass}`
+    },
+    svgClass() {
+      if (this.className) {
+        return 'svg-icon ' + this.className
+      } else {
+        return 'svg-icon'
+      }
+    },
+    styleExternalIcon() {
+      return {
+        mask: `url(${this.iconClass}) no-repeat 50% 50%`,
+        '-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.svg-icon {
+  width: 1em;
+  height: 1em;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+}
+
+.svg-external-icon {
+  background-color: currentColor;
+  mask-size: cover!important;
+  display: inline-block;
+}
+</style>

+ 76 - 0
src/components/Upload.vue

@@ -0,0 +1,76 @@
+<template>
+  <div>
+    <input
+      ref="inputer"
+      :accept="accept"
+      :multiple="false"
+      class="fileUp"
+      type="file"
+      name="file"
+      @change="handleChange($event)"
+    />
+  </div>
+</template>
+
+<script>
+import { api } from "@/config";
+//Uploadcondition 用于判断个性化文件 //返回 false 就不会上传文件
+//UploadErrorEvent 上传成功
+//UploadErrorEvent 上传失败
+
+export default {
+  name: "Upload",
+  props: {
+    //@reject 默认值
+    accept: String, //上传文件类型
+    multiple: Boolean, //暂时没有用
+    uploadcondition: {
+      type: Function,
+      default: null,
+    },
+  },
+  data() {
+    return {};
+  },
+  methods: {
+    handleChange(event) {
+      let that = this;
+      let inputDOM1 = this.$refs.inputer;
+      const files = inputDOM1.files;
+      if (files.length === 0) return;
+      //   that.$emit("UploadfilesEvent", files[0]);
+      if (!this.uploadcondition(files[0])) {
+        that.$refs.inputer.value = "";
+        return false;
+      }
+      const file = files[0];
+      console.log(file);
+      let form = new FormData();
+      form.append("image", file);
+      this.axios
+        .post(`${api.baseApi}recruit/v1/ImgUpload`, form)
+        .then((res) => {
+          if (res && res.data && res.status === 200 && res.data.data) {
+            let data = res.data.data;
+            that.$emit("UploadSuccessEvent", {
+              url: `${api.baseApi}${data[0]}`,
+            });
+          } else {
+            that.$emit("UploadErrorEvent");
+          }
+        })
+        .catch((error) => {
+          that.$emit("UploadErrorEvent");
+        });
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+.fileUp {
+  opacity: 0;
+  width: 100%;
+  height: 100%;
+  outline: none;
+}
+</style>

+ 194 - 0
src/components/activityFindSelect.vue

@@ -0,0 +1,194 @@
+<template>
+  <div>
+    <el-select
+      v-model="activeTitle"
+      filterable
+      multiple
+      :multiple-limit="limit"
+      remote
+      reserve-keyword
+      placeholder="请输入活动标题"
+      :remote-method="remoteMethod"
+      :loading="loading"
+      :disabled="isShow==true"
+      class="setWidth"
+      @change="change($event)"
+    >
+      <el-option
+        v-for="(item,index) in activeOptions"
+        :key="index"
+        :label="item.title"
+        :value="index"
+      >
+      </el-option>
+      <div
+        v-if="(pageInfo.currPage+1)*pageInfo.pageSize<pageInfo.total"
+        id="add-more"
+        @click="getAllone()"
+      >
+        <i
+          v-if="loading"
+          class="el-icon-loading"
+        ></i>
+        加载更多...</div>
+    </el-select>
+  </div>
+</template>
+<script>
+  import { setTimeout } from "timers"
+  //selectActivity 返回值{id: Number,title:String}
+  export default {
+    name: "activityFindSelect",
+    data() {
+      return {
+        dataForm: {
+          ids: [],
+          isLogin: -1,
+          startTime: "",
+          endTime: "",
+          status: -1,
+          title: "",
+          type: -1,
+          orderField: "",
+          orderType: ""
+        },
+        limit: 1,
+        activeIndex: 0,
+        activeId: this.id,
+        activeTitle: this.title,
+        isShow: this.disabled,
+        initialize: this.init,
+        loading: false,
+        activeOptions: [],
+        pageInfo: {
+          currPage: 1,
+          pageSize: 20,
+          total: 0
+        },
+        dataRule: {
+          title: [
+            { required: false, trigger: "blur" },
+            { max: 50, message: "关键字不得超过50字", trigger: "blur" }
+          ]
+        }
+      }
+    },
+
+    props: {
+      id: Number,
+      title: Array,
+      disabled: Boolean,
+      init: Boolean
+    },
+    mounted() {
+      setTimeout(() => {
+        this.activeId = this.id
+        this.activeTitle = this.title
+        this.isShow = this.disabled
+        this.initialize = this.init
+        if (this.initialize) {
+          this.change(1)
+          // this.getAllActive()
+        }
+      }, 500)
+    },
+    methods: {
+      remoteMethod(query) {
+        if (query !== "" && query != undefined && query != null) {
+          let str = query.replace(/ /g, "")
+          if (str !== "" && str.length <= 50) {
+            this.activeTitle = ""
+            this.dataForm.title = str
+            this.pageInfo.currPage = 1
+            this.getAllActive()
+          } else if (str.length > 50) {
+            this.$message.success({
+              message: `关键字不得超过50字`,
+              type: "waining"
+            })
+          }
+        }
+      },
+      getAllone() {
+        if (
+          !(
+            (this.pageInfo.currPage + 1) * this.pageInfo.pageSize >
+            this.pageInfo.total
+          )
+        ) {
+          this.pageInfo.currPage++
+          this.getAllActive()
+        }
+      },
+      getAllActive() {
+        if (!this.loading) {
+          this.loading = true
+          this.axios
+            .post(
+              `/activity/queryAcitivtyInfoList/${this.pageInfo.currPage}/${this.pageInfo.pageSize}`,
+              this.dataForm
+            )
+            .then(res => {
+              if (res && res.data && res.data.status == 200) {
+                if (this.pageInfo.currPage === 1) {
+                  this.activeOptions = []
+                }
+                let data = res.data.data
+                this.activeOptions.push(...data)
+                this.pageInfo.currPage = res.data.currPage
+                this.pageInfo.total = res.data.totalCount
+                // if (
+                //   this.initialize &&
+                //   this.activeTitle.length == 0 &&
+                //   this.activeOptions.length > 0 &&
+                //   this.pageInfo.currPage === 1
+                // ) {
+                //   // this.activeTitle = [0]
+
+                // }
+                this.loading = false
+              } else {
+                this.loading = false
+              }
+            })
+            .catch(error => {
+              this.loading = false
+              // console.log(error)
+            })
+        }
+      },
+      change(event) {
+        if (event && this.activeTitle.length > 0) {
+          this.$emit("selectActivity", {
+            index: this.activeTitle[0],
+            id: this.activeOptions[this.activeTitle[0]].id,
+            title: [this.activeOptions[this.activeTitle[0]].title]
+          })
+        } else {
+          this.$emit("repealGuest", {
+            index: 0,
+            id: 0,
+            title: []
+          })
+        }
+      }
+    }
+  }
+</script>
+<style lang="scss" scoped>
+// .my-tag {
+//   margin-right: 10px;
+// }
+.setWidth {
+  width: 100% !important;
+  // display: flex;
+  // display: block !important;
+  // .el-input__inner {
+  //   display: flex;
+  //   width: 1000px !important;
+  //   display: block !important;
+  // }
+}
+</style>
+
+

+ 181 - 0
src/components/businessFindSelect.vue

@@ -0,0 +1,181 @@
+<template>
+  <div>
+    <el-select
+      v-model="businessTitle"
+      filterable
+      multiple
+      :multiple-limit="limit"
+      remote
+      reserve-keyword
+      placeholder="请输入企业名称"
+      :remote-method="remoteMethod"
+      :loading="loading"
+      :disabled="disabled == true"
+      class="setWidth"
+      @change="change($event)"
+    >
+      <el-option
+        v-for="(item, index) in businessOptions"
+        :key="index"
+        :label="item.comname"
+        :value="index"
+      >
+      </el-option>
+      <div
+        v-if="(pageInfo.currPage + 1) * pageInfo.pageSize < pageInfo.total"
+        id="add-more"
+        @click="getAllone()"
+      >
+        <i v-if="loading" class="el-icon-loading"></i>
+        加载更多...
+      </div>
+    </el-select>
+  </div>
+</template>
+<script>
+import { setTimeout } from "timers";
+//selectbusiness 返回值{id: Number,title:String}
+export default {
+  name: "businessFindSelect",
+  data() {
+    return {
+      businessTitle: [],
+      dataForm: {
+        comname: ""
+      },
+      limit: 1,
+      businessIndex: 0,
+      loading: false,
+      businessOptions: [],
+      pageInfo: {
+        currPage: 1,
+        pageSize: 5,
+        total: 0
+      },
+      dataRule: {
+        title: [
+          { required: false, trigger: "blur" },
+          { max: 50, message: "关键字不得超过50字", trigger: "blur" }
+        ]
+      }
+    };
+  },
+
+  props: {
+    id: {
+      type: Number,
+      default: 0
+    },
+    title: {
+      type: Array,
+      default: () => []
+    },
+    disabled: {
+      type: Boolean,
+      default: false
+    },
+    init: {
+      type: Boolean,
+      default: false
+    }
+  },
+
+  mounted() {
+    // if(init){
+    //    this.getAllbusiness()
+    // }
+  },
+  methods: {
+    remoteMethod(query) {
+      if (query !== "" && query != undefined && query != null) {
+        let str = query.replace(/ /g, "");
+        if (str !== "" && str.length <= 50) {
+          this.businessTitle = [];
+          this.dataForm.comname = str;
+          this.pageInfo.currPage = 1;
+          this.getAllbusiness();
+        } else if (str.length > 50) {
+          this.$message.success({
+            message: `关键字不得超过50字`,
+            type: "waining"
+          });
+        }
+      }
+    },
+    getAllone() {
+      if (
+        !(
+          (this.pageInfo.currPage + 1) * this.pageInfo.pageSize >
+          this.pageInfo.total
+        )
+      ) {
+        this.pageInfo.currPage++;
+        this.getAllbusiness();
+      }
+    },
+    getAllbusiness() {
+      if (!this.loading) {
+        if (this.pageInfo.currPage === 1) {
+          this.loading = true;
+        }
+        let model = {
+          page: this.pageInfo.currPage,
+          limit: this.pageInfo.pageSize,
+          comname: this.dataForm.comname
+        };
+        this.axios
+          .post(`/recruit/v1/company_sellist`, model)
+          .then(res => {
+            if (res && res.data && res.data.status == 0) {
+              if (this.pageInfo.currPage === 1) {
+                this.businessOptions = [];
+              }
+              let data = res.data.data;
+              this.businessOptions.push(...data);
+              this.pageInfo.total = res.data.count;
+              this.loading = false;
+            } else {
+              this.loading = false;
+            }
+          })
+          .catch(error => {
+            this.loading = false;
+            // console.log(error)
+          });
+      }
+    },
+    change(event) {
+      if (event && this.businessTitle.length > 0) {
+        this.$emit("selectbusiness", {
+          index: this.businessTitle[0],
+          id: this.businessOptions[this.businessTitle[0]].id,
+          title: [this.businessOptions[this.businessTitle[0]].comname]
+        });
+      } else {
+        this.$emit("repealGuest", {
+          index: 0,
+          id: 0,
+          title: []
+        });
+      }
+    }
+  }
+};
+</script>
+<style lang="scss" scoped>
+// .my-tag {
+//   margin-right: 10px;
+// }
+.setWidth {
+  width: 400px !important;
+  // display: flex;
+  // display: block !important;
+  // .el-input__inner {
+  //   display: flex;
+  //   width: 1000px !important;
+  //   display: block !important;
+  // }
+}
+</style>
+
+

+ 138 - 0
src/components/dragTable.vue

@@ -0,0 +1,138 @@
+
+<template>
+  <div>
+    <el-table
+      v-loading="tableLoading"
+      :data="datas"
+      border
+      stripe
+      tooltip-effect="dark"
+      style="width: 100%"
+    >
+      <template v-for="(column, index) in columns">
+        <el-table-column
+          v-bind="column"
+          :key="index"
+          :header-align="column.align"
+          :align="column.align"
+        >
+          <template slot="header" slot-scope="scope">
+            <span>{{ column.label }}</span>
+          </template>
+          <template slot-scope="scope">
+            <ex-slot
+              v-if="column._render_"
+              :column="column"
+              :render="column._render_"
+              :scope="scope"
+            ></ex-slot>
+            <div v-if="column.prop === 'status'">
+              <el-switch v-model="scope.row.status"> </el-switch>
+            </div>
+            <div v-else-if="column.prop === 'required'">
+              <el-checkbox v-model="scope.row.required"></el-checkbox>
+            </div>
+            <div v-else-if="column.prop === 'field_values'">
+              <div
+                v-if="
+                  scope.row.field_values && scope.row.field_values.length > 0
+                "
+              >
+                <el-tag
+                  v-for="(tag, index) in scope.row.field_values"
+                  :key="index"
+                  class="setTag"
+                >
+                  {{ tag.value }}
+                </el-tag>
+              </div>
+            </div>
+            <span v-else>{{ scope.row[column.prop] }}</span>
+          </template>
+        </el-table-column>
+      </template>
+    </el-table>
+  </div>
+</template>
+<script>
+import Sortable from "sortablejs";
+
+export default {
+  data() {
+    return {
+      tableLoading: false,
+      columns: [
+        // { prop: "index", label: "顺序", width: "80", align: "center" },
+        { prop: "field_id", label: "表单项ID", width: "100", align: "center" },
+        {
+          prop: "field_name",
+          label: "字段名称",
+          width: "180",
+          align: "center"
+        },
+        { prop: "status", label: "启用/禁用", width: "100", align: "center" },
+        // { prop: "required", label: "必填", width: "100", align: "center" },
+        { prop: "type", label: "类型", width: "140", align: "center" },
+        { prop: "field_values", label: "选项", align: "left" }
+      ]
+    };
+  },
+  props: {
+    // loading: {
+    //   type: Boolean,
+    //   default: false
+    // },
+    datas: {
+      type: Array,
+      default: () => []
+    }
+  },
+  // watch: {
+  //   loading(a, b) {
+  //     this.tableLoading = a;
+  //     console.log(this.tableLoading);
+  //   }
+  // },
+  mounted() {
+    // this.rowDrop();
+    // this.columnDrop();
+  },
+  methods: {
+    // 行拖拽
+    rowDrop() {
+      // 此时找到的元素是要拖拽元素的父容器
+      const tbody = document.querySelector(".el-table__body-wrapper tbody");
+      const _this = this;
+      Sortable.create(tbody, {
+        //  指定父元素下可被拖拽的子元素
+        draggable: ".el-table__row",
+        onEnd({ newIndex, oldIndex }) {
+          const currRow = _this.datas.splice(oldIndex, 1)[0];
+          _this.datas.splice(newIndex, 0, currRow);
+        }
+      });
+    }
+    // 列拖拽
+    // columnDrop() {
+    //   const wrapperTr = document.querySelector(".el-table__header-wrapper tr");
+    //   this.sortable = Sortable.create(wrapperTr, {
+    //     animation: 180,
+    //     delay: 0,
+    //     onEnd: evt => {
+    //       const oldItem = this.dropCol[evt.oldIndex];
+    //       this.dropCol.splice(evt.oldIndex, 1);
+    //       this.dropCol.splice(evt.newIndex, 0, oldItem);
+    //     }
+    //   });
+    // }
+  }
+};
+</script>
+<style lang="scss">
+.setTag {
+  margin: 3px;
+  // &:first-child{
+
+  // }
+}
+</style>

+ 190 - 0
src/components/guestFindSelect.vue

@@ -0,0 +1,190 @@
+<template>
+  <div>
+    <el-select
+      v-model="isssuesName"
+      multiple
+      :multiple-limit="limit"
+      filterable
+      remote
+      reserve-keyword
+      placeholder="请输入嘉宾姓名"
+      :remote-method="remoteMethod"
+      :loading="loading"
+      style="width:100%"
+      @change="change()"
+      :disabled="isShow"
+    >
+      <el-option
+        v-for="(item,index) in isssuesOptions"
+        :key="index"
+        :label="item.guestName"
+        :value="index"
+      >
+        <span class="fl fl-label">{{item.guestName}}</span><span class="fr">{{item.guestPhone}}</span>
+      </el-option>
+      <span
+        id="add-more"
+        v-if="(pageInfo.currPage+1)*pageInfo.pageSize<pageInfo.total"
+        @click="getAllone()"
+      >
+        <i
+          v-if="loading"
+          class="el-icon-loading"
+        ></i>
+        加载更多...</span>
+    </el-select>
+  </div>
+</template>
+<script>
+  import { setTimeout } from "timers"
+  //selectActivity 返回值{id: Number,title:String}
+  export default {
+    name: "guestFindSelect",
+    data() {
+      return {
+        limit: 1,
+        isssuesIndex: this.index,
+        isssuesId: this.id,
+        isssuesName: this.name,
+        isssuesType: this.type,
+        isShow: this.disabled,
+        // indexItem: {
+        //   guestId: 0,
+        //   guestName: "嘉宾不限"
+        // },
+        dataForm: {
+          guestName: "",
+          guestPhone: "",
+          orderField: "",
+          orderType: "",
+          page: 0,
+          pageSize: 0,
+          startTime: "",
+          endTime: ""
+        },
+        activeIndex: 0,
+        activeId: this.id,
+        activeTitle: this.title,
+        loading: false,
+        iconLoading: false,
+        isssuesOptions: [],
+        pageInfo: {
+          currPage: 1,
+          pageSize: 20,
+          total: 0
+        }
+      }
+    },
+
+    props: {
+      index: Number,
+      id: Number,
+      name: Array,
+      type: String,
+      disabled: Boolean
+    },
+    mounted() {
+      setTimeout(() => {
+        this.isssuesIndex = this.index
+        this.isssuesId = this.id
+        this.isssuesName = this.name
+        this.isssuesType = this.type
+        this.isShow = this.disabled
+        // this.getAllIssues()
+      }, 500)
+    },
+    methods: {
+      remoteMethod(query) {
+        if (query !== "" && query != undefined && query != null) {
+          let str = query.replace(/ /g, "")
+          if (str !== "" && str.length <= 20) {
+            this.activeTitle = ""
+            this.dataForm.guestName = str
+            this.pageInfo.currPage = 1
+            this.getAllGuest()
+          } else if (str.length > 50) {
+            this.$message.success({
+              message: `关键字不得超过20字`,
+              type: "waining"
+            })
+          }
+        }
+      },
+      getAllone() {
+        if (
+          this.pageInfo.currPage * this.pageInfo.pageSize <
+          this.pageInfo.total
+        ) {
+          this.pageInfo.currPage++
+          this.getAllIssues()
+        }
+      },
+      getAllGuest() {
+        if (!this.loading) {
+          this.loading = true
+          this.dataForm.page = this.pageInfo.currPage
+          this.dataForm.pageSize = this.pageInfo.pageSize
+          this.axios
+            .post(`/guestManage/queryGuestList`, this.dataForm)
+            .then(res => {
+              if (res && res.data && res.status == 200) {
+                if (this.pageInfo.currPage === 1) {
+                  this.isssuesOptions = []
+                }
+                let data = res.data.data
+                this.isssuesOptions.push(...data)
+                this.pageInfo.currPage = res.data.currPage
+                this.pageInfo.pageSize = res.data.pageSize
+                this.pageInfo.total = res.data.totalCount
+                this.loading = false
+              } else {
+                this.loading = false
+              }
+            })
+            .catch(error => {
+              this.loading = false
+            })
+        }
+      },
+      change() {
+        if (this.isssuesName.length > 0) {
+          this.$emit("selectGuest", {
+            index: this.isssuesName[0],
+            id: this.isssuesOptions[this.isssuesName[0]].guestId,
+            title: [this.isssuesOptions[this.isssuesName[0]].guestName]
+          })
+        } else {
+          this.$emit("repealGuest", {
+            index: 0,
+            id: 0,
+            title: []
+          })
+        }
+      }
+    }
+  }
+</script>
+<style lang="scss" scoped>
+// .my-tag {
+//   margin-right: 10px;
+// }
+.setWidth {
+  width: 100% !important;
+  display: flex;
+  display: block !important;
+  // .el-input__inner {
+  //   display: flex;
+  //   width: 1000px !important;
+  //   display: block !important;
+  // }
+}
+
+.fl-label {
+  display: inline-block;
+  width: 90px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+</style>
+
+

+ 210 - 0
src/components/tree-table/Index.vue

@@ -0,0 +1,210 @@
+<template>
+  <el-table
+    :data="formatData"
+    :row-style="showRow"
+    v-bind="$attrs"
+    class="mgt20"
+    @selection-change="selectionChangeHandle"
+  >
+    <el-table-column
+      v-if="columns.length === 0"
+      header-align="center"
+      align="center"
+    >
+      <template slot-scope="scope">
+        <span
+          v-for="space in scope.row._level"
+          :key="space"
+          class="ms-tree-space"
+        />
+        <span
+          v-if="iconShow(0, scope.row)"
+          class="tree-ctrl"
+          @click="toggleExpanded(scope.$index)"
+        >
+          <i v-if="!scope.row._expanded" class="el-icon-plus" />
+          <i v-else class="el-icon-minus" />
+        </span>
+        {{ scope.$index }}
+      </template>
+    </el-table-column>
+
+    <el-table-column
+      v-for="(column, index) in columns"
+      v-else
+      :key="column.value"
+      :label="column.text"
+      :width="column.width"
+      header-align="center"
+      align="center"
+    >
+      <template slot-scope="scope">
+        <!-- <span v-if="index === 0" v-for="space in scope.row._level" class="ms-tree-space" :key="space"></span> -->
+        <span
+          v-if="iconShow(index, scope.row)"
+          class="tree-ctrl"
+          @click="toggleExpanded(scope.$index, column, scope.row)"
+        >
+          <i v-if="!scope.row._expanded" class="el-icon-plus" />
+          <i v-else class="el-icon-minus" />
+        </span>
+        <span v-if="column.text == '操作'" class="SeePdf"
+          ><span v-if="scope.row.fileUrl" @click="seePdf(scope.row)"
+            >查看pdf</span
+          >
+          <span v-else @click="seeResults(scope.row)"> 查看结果</span>
+        </span>
+        <span v-else>{{ scope.row[column.value] }}</span>
+      </template>
+    </el-table-column>
+
+    <slot />
+  </el-table>
+</template>
+
+<script>
+import treeToArray from "./eval";
+export default {
+  name: "TreeTable",
+  props: {
+    data: {
+      type: [Array, Object],
+      required: true
+    },
+    evaluateType: Number,
+    columns: {
+      type: Array,
+      default: () => []
+    },
+    evalFunc: Function,
+    evalArgs: Array,
+    expandAll: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      index: -1
+    };
+  },
+  computed: {
+    // 格式化数据源
+    formatData: function() {
+      let tmp;
+      if (!Array.isArray(this.data)) {
+        tmp = [this.data];
+      } else {
+        tmp = this.data;
+      }
+      const func = this.evalFunc || treeToArray;
+      const args = this.evalArgs
+        ? Array.concat([tmp, this.expandAll], this.evalArgs)
+        : [tmp, this.expandAll];
+      return func.apply(null, args);
+    }
+  },
+  methods: {
+    showRow: function(row) {
+      const show = row.row.parent
+        ? row.row.parent._expanded && row.row.parent._show
+        : true;
+      row.row._show = show;
+      return show
+        ? "animation:treeTableShow 1s;-webkit-animation:treeTableShow 1s;"
+        : "display:none;";
+    },
+    // 切换下级是否展开
+    toggleExpanded: function(trIndex, scope1, data) {
+      //   console.log(trIndex,scope1,data)
+      if (this.index != trIndex) {
+        this.index = trIndex;
+        this.$emit("getfindChildQuiz", data);
+      }
+      const record = this.formatData[trIndex];
+      for (let i = 0; i < this.formatData.length; i++) {
+        if (trIndex != i) {
+          this.formatData[i]._expanded = false;
+        }
+      }
+      record._expanded = !record._expanded;
+    },
+    // 图标显示
+    iconShow(index, record) {
+      // return (index === 1 && record.children && record.children.length > 0)
+      return index === 1 && this.evaluateType != 1 && record.children1;
+    },
+    //显示查看PDF按钮
+    pdfShow(index, record) {
+      return index === 5;
+    },
+    seeResults(row) {
+      this.$emit("seeResults", row);
+    },
+    //查看PDF
+    seePdf(id) {
+      this.$emit("SeepdfEvent", id);
+    },
+    selectionChangeHandle(val) {
+      this.$emit("selectionChangeHandleEvent", val);
+    }
+  }
+};
+</script>
+<style rel="stylesheet/css">
+@keyframes treeTableShow {
+  from {
+    opacity: 0;
+  }
+  to {
+    opacity: 1;
+  }
+}
+@-webkit-keyframes treeTableShow {
+  from {
+    opacity: 0;
+  }
+  to {
+    opacity: 1;
+  }
+}
+</style>
+
+<style lang="scss" rel="stylesheet/scss" scoped>
+$color-blue: #2196f3;
+$space-width: 18px;
+.ms-tree-space {
+  position: relative;
+  top: 1px;
+  display: inline-block;
+  font-style: normal;
+  font-weight: 400;
+  line-height: 1;
+  width: $space-width;
+  height: 14px;
+  &::before {
+    content: "";
+  }
+}
+.processContainer {
+  width: 100%;
+  height: 100%;
+}
+table td {
+  line-height: 26px;
+}
+
+.tree-ctrl {
+  position: relative;
+  cursor: pointer;
+  color: $color-blue;
+  margin-left: -$space-width;
+}
+.cell {
+  text-align: center;
+}
+.SeePdf {
+  color: #169bd5;
+  cursor: pointer;
+}
+</style>

+ 25 - 0
src/components/tree-table/eval.js

@@ -0,0 +1,25 @@
+'use strict'
+import Vue from 'vue'
+export default function treeToArray(data, expandAll, parent = null, level = null) {
+    let tmp = []
+    Array.from(data).forEach(function(record) {
+        if (record._expanded === undefined) {
+            Vue.set(record, '_expanded', expandAll)
+        }
+        let _level = 1
+        if (level !== undefined && level !== null) {
+            _level = level + 1
+        }
+        Vue.set(record, '_level', _level)
+            // 如果有父元素
+        if (parent) {
+            Vue.set(record, 'parent', parent)
+        }
+        tmp.push(record)
+        if (record.children1 && record.children1.length > 0) {
+            const children = treeToArray(record.children1, expandAll, record, _level)
+            tmp = tmp.concat(children)
+        }
+    })
+    return tmp
+}

+ 12 - 0
src/config/env.development.js

@@ -0,0 +1,12 @@
+// 本地
+module.exports = {
+  title: "招聘系统",
+  baseUrl: "http://localhost:8080",
+  api: {
+   // baseApi: "http://chtest.wanyuhengtong.com/"
+    //baseApi: "http://api.sit.i-zhaopin.com"
+    baseApi: "http://wxhr-php.sit.zretchome.com/"
+  },
+  fileURL: `https://api2.edusit.zretchome.com`,
+  appId: "wx5ac3a2c2d72b6f26"
+};

+ 10 - 0
src/config/env.production.js

@@ -0,0 +1,10 @@
+// 生产环境
+module.exports = {
+  title: "招聘系统",
+  baseUrl: "http://wx.wxhr.sit.futurelab.tv",
+  api: {
+    baseApi: "http://api.i-zhaopin.com"
+  },
+  fileURL: `https://api2.edu.futurelab.tv`,
+  appId: "wx5ac3a2c2d72b6f26"
+};

+ 11 - 0
src/config/env.staging.js

@@ -0,0 +1,11 @@
+// 测试环境
+module.exports = {
+  title: "招聘系统",
+  baseUrl: "http://mobile.recruit.sit.zretchome.com",
+  api: {
+    baseApi: "http://wxhr-php.sit.zretchome.com"
+    //baseApi: "http://api.sit.i-zhaopin.com"
+  },
+  fileURL: `https://api2.edusit.zretchome.com`,
+  appId: "wx5ac3a2c2d72b6f26"
+};

+ 4 - 0
src/config/index.js

@@ -0,0 +1,4 @@
+// 根据环境引入不同配置 process.env.NODE_ENV
+console.log(process.env.VUE_APP_ENV);
+const config = require("./env." + process.env.VUE_APP_ENV);
+module.exports = config;

+ 9 - 0
src/icons/index.js

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

File diff suppressed because it is too large
+ 0 - 0
src/icons/svg/dashboard.svg


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

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M96.258 57.462h31.421C124.794 27.323 100.426 2.956 70.287.07v31.422a32.856 32.856 0 0 1 25.971 25.97zm-38.796-25.97V.07C27.323 2.956 2.956 27.323.07 57.462h31.422a32.856 32.856 0 0 1 25.97-25.97zm12.825 64.766v31.421c30.46-2.885 54.507-27.253 57.713-57.712H96.579c-2.886 13.466-13.146 23.726-26.292 26.291zM31.492 70.287H.07c2.886 30.46 27.253 54.507 57.713 57.713V96.579c-13.466-2.886-23.726-13.146-26.291-26.292z"/></svg>

+ 1 - 0
src/icons/svg/eye-open.svg

@@ -0,0 +1 @@
+<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="128" height="128"><defs><style/></defs><path d="M512 128q69.675 0 135.51 21.163t115.498 54.997 93.483 74.837 73.685 82.006 51.67 74.837 32.17 54.827L1024 512q-2.347 4.992-6.315 13.483T998.87 560.17t-31.658 51.669-44.331 59.99-56.832 64.34-69.504 60.16-82.347 51.5-94.848 34.687T512 896q-69.675 0-135.51-21.163t-115.498-54.826-93.483-74.326-73.685-81.493-51.67-74.496-32.17-54.997L0 513.707q2.347-4.992 6.315-13.483t18.816-34.816 31.658-51.84 44.331-60.33 56.832-64.683 69.504-60.331 82.347-51.84 94.848-34.816T512 128.085zm0 85.333q-46.677 0-91.648 12.331t-81.152 31.83-70.656 47.146-59.648 54.485-48.853 57.686-37.675 52.821-26.325 43.99q12.33 21.674 26.325 43.52t37.675 52.351 48.853 57.003 59.648 53.845T339.2 767.02t81.152 31.488T512 810.667t91.648-12.331 81.152-31.659 70.656-46.848 59.648-54.186 48.853-57.344 37.675-52.651T927.957 512q-12.33-21.675-26.325-43.648t-37.675-52.65-48.853-57.345-59.648-54.186-70.656-46.848-81.152-31.659T512 213.334zm0 128q70.656 0 120.661 50.006T682.667 512 632.66 632.661 512 682.667 391.339 632.66 341.333 512t50.006-120.661T512 341.333zm0 85.334q-35.328 0-60.33 25.002T426.666 512t25.002 60.33T512 597.334t60.33-25.002T597.334 512t-25.002-60.33T512 426.666z"/></svg>

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

@@ -0,0 +1 @@
+<svg width="128" height="64" xmlns="http://www.w3.org/2000/svg"><path d="M127.072 7.994c1.37-2.208.914-5.152-.914-6.87-2.056-1.717-4.797-1.226-6.396.982-.229.245-25.586 32.382-55.74 32.382-29.24 0-55.74-32.382-55.968-32.627-1.6-1.963-4.57-2.208-6.397-.49C-.17 3.086-.399 6.275 1.2 8.238c.457.736 5.94 7.36 14.62 14.72L4.17 35.96c-1.828 1.963-1.6 5.152.228 6.87.457.98 1.6 1.471 2.742 1.471s2.284-.49 3.198-1.472l12.564-13.983c5.94 4.416 13.021 8.587 20.788 11.53l-4.797 17.418c-.685 2.699.686 5.397 3.198 6.133h1.37c2.057 0 3.884-1.472 4.341-3.68L52.6 42.83c3.655.736 7.538 1.227 11.422 1.227 3.883 0 7.767-.49 11.422-1.227l4.797 17.173c.457 2.208 2.513 3.68 4.34 3.68.457 0 .914 0 1.143-.246 2.513-.736 3.883-3.434 3.198-6.133l-4.797-17.172c7.767-2.944 14.848-7.114 20.788-11.53l12.336 13.738c.913.981 2.056 1.472 3.198 1.472s2.284-.49 3.198-1.472c1.828-1.963 1.828-4.906.228-6.87l-11.65-13.001c9.366-7.36 14.849-14.474 14.849-14.474z"/></svg>

File diff suppressed because it is too large
+ 0 - 0
src/icons/svg/form.svg


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

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M115.625 127.937H.063V12.375h57.781v12.374H12.438v90.813h90.813V70.156h12.374z"/><path d="M116.426 2.821l8.753 8.753-56.734 56.734-8.753-8.745z"/><path d="M127.893 37.982h-12.375V12.375H88.706V0h39.187z"/></svg>

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

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M.002 9.2c0 5.044 3.58 9.133 7.998 9.133 4.417 0 7.997-4.089 7.997-9.133 0-5.043-3.58-9.132-7.997-9.132S.002 4.157.002 9.2zM31.997.066h95.981V18.33H31.997V.066zm0 45.669c0 5.044 3.58 9.132 7.998 9.132 4.417 0 7.997-4.088 7.997-9.132 0-3.263-1.524-6.278-3.998-7.91-2.475-1.63-5.524-1.63-7.998 0-2.475 1.632-4 4.647-4 7.91zM63.992 36.6h63.986v18.265H63.992V36.6zm-31.995 82.2c0 5.043 3.58 9.132 7.998 9.132 4.417 0 7.997-4.089 7.997-9.132 0-5.044-3.58-9.133-7.997-9.133s-7.998 4.089-7.998 9.133zm31.995-9.131h63.986v18.265H63.992V109.67zm0-27.404c0 5.044 3.58 9.133 7.998 9.133 4.417 0 7.997-4.089 7.997-9.133 0-3.263-1.524-6.277-3.998-7.909-2.475-1.631-5.524-1.631-7.998 0-2.475 1.632-4 4.646-4 7.91zm31.995-9.13h31.991V91.4H95.987V73.135z"/></svg>

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

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M108.8 44.322H89.6v-5.36c0-9.04-3.308-24.163-25.6-24.163-23.145 0-25.6 16.881-25.6 24.162v5.361H19.2v-5.36C19.2 15.281 36.798 0 64 0c27.202 0 44.8 15.281 44.8 38.961v5.361zm-32 39.356c0-5.44-5.763-9.832-12.8-9.832-7.037 0-12.8 4.392-12.8 9.832 0 3.682 2.567 6.808 6.407 8.477v11.205c0 2.718 2.875 4.962 6.4 4.962 3.524 0 6.4-2.244 6.4-4.962V92.155c3.833-1.669 6.393-4.795 6.393-8.477zM128 64v49.201c0 8.158-8.645 14.799-19.2 14.799H19.2C8.651 128 0 121.359 0 113.201V64c0-8.153 8.645-14.799 19.2-14.799h89.6c10.555 0 19.2 6.646 19.2 14.799z"/></svg>

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

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/></svg>

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

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M126.713 90.023c.858.985 1.287 2.134 1.287 3.447v29.553c0 1.423-.429 2.6-1.287 3.53-.858.93-1.907 1.395-3.146 1.395H97.824c-1.145 0-2.146-.465-3.004-1.395-.858-.93-1.287-2.107-1.287-3.53V93.47c0-.875.19-1.696.572-2.462.382-.766.906-1.368 1.573-1.806a3.84 3.84 0 0 1 2.146-.657h9.725V69.007a3.84 3.84 0 0 0-.43-1.806 3.569 3.569 0 0 0-1.143-1.313 2.714 2.714 0 0 0-1.573-.492h-36.47v23.149h9.725c1.144 0 2.145.492 3.004 1.478.858.985 1.287 2.134 1.287 3.447v29.553c0 .876-.191 1.696-.573 2.463-.38.766-.905 1.368-1.573 1.806a3.84 3.84 0 0 1-2.145.656H51.915a3.84 3.84 0 0 1-2.145-.656c-.668-.438-1.216-1.04-1.645-1.806a4.96 4.96 0 0 1-.644-2.463V93.47c0-1.313.43-2.462 1.288-3.447.858-.986 1.907-1.478 3.146-1.478h9.582v-23.15h-37.9c-.953 0-1.74.356-2.359 1.068-.62.711-.93 1.56-.93 2.544v19.538h9.726c1.239 0 2.264.492 3.074 1.478.81.985 1.216 2.134 1.216 3.447v29.553c0 1.423-.405 2.6-1.216 3.53-.81.93-1.835 1.395-3.074 1.395H4.29c-.476 0-.93-.082-1.358-.246a4.1 4.1 0 0 1-1.144-.657 4.658 4.658 0 0 1-.93-1.067 5.186 5.186 0 0 1-.643-1.395 5.566 5.566 0 0 1-.215-1.56V93.47c0-.437.048-.875.143-1.313a3.95 3.95 0 0 1 .429-1.15c.19-.328.429-.656.715-.984.286-.329.572-.602.858-.821.286-.22.62-.383 1.001-.493.382-.11.763-.164 1.144-.164h9.726V61.619c0-.985.31-1.833.93-2.544.619-.712 1.358-1.068 2.216-1.068h44.335V39.62h-9.582c-1.24 0-2.288-.492-3.146-1.477a5.09 5.09 0 0 1-1.287-3.448V5.14c0-1.423.429-2.627 1.287-3.612.858-.985 1.907-1.477 3.146-1.477h25.743c.763 0 1.478.246 2.145.739a5.17 5.17 0 0 1 1.573 1.888c.382.766.573 1.587.573 2.462v29.553c0 1.313-.43 2.463-1.287 3.448-.859.985-1.86 1.477-3.004 1.477h-9.725v18.389h42.762c.954 0 1.74.355 2.36 1.067.62.711.93 1.56.93 2.545v26.925h9.582c1.239 0 2.288.492 3.146 1.478z"/></svg>

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

@@ -0,0 +1 @@
+<svg width="130" height="130" xmlns="http://www.w3.org/2000/svg"><path d="M63.444 64.996c20.633 0 37.359-14.308 37.359-31.953 0-17.649-16.726-31.952-37.359-31.952-20.631 0-37.36 14.303-37.358 31.952 0 17.645 16.727 31.953 37.359 31.953zM80.57 75.65H49.434c-26.652 0-48.26 18.477-48.26 41.27v2.664c0 9.316 21.608 9.325 48.26 9.325H80.57c26.649 0 48.256-.344 48.256-9.325v-2.663c0-22.794-21.605-41.271-48.256-41.271z" stroke="#979797"/></svg>

+ 22 - 0
src/icons/svgo.yml

@@ -0,0 +1,22 @@
+# replace default config
+
+# multipass: true
+# full: true
+
+plugins:
+
+  # - name
+  #
+  # or:
+  # - name: false
+  # - name: true
+  #
+  # or:
+  # - name:
+  #     param1: 1
+  #     param2: 2
+
+- removeAttrs:
+    attrs:
+      - 'fill'
+      - 'fill-rule'

+ 43 - 0
src/layout/components/AppMain.vue

@@ -0,0 +1,43 @@
+<template>
+  <section class="app-main">
+    <transition name="fade-transform" mode="out-in">
+      <router-view :key="key" />
+    </transition>
+  </section>
+</template>
+
+<script>
+export default {
+  name: 'AppMain',
+  computed: {
+    key() {
+      return this.$route.path
+    }
+  }
+}
+</script>
+
+<style scoped>
+.app-main {
+  /*50 = navbar  */
+  min-height: calc(100vh - 50px);
+  width: 100%;
+  position: relative;
+  overflow: hidden;
+  box-sizing: border-box;
+  padding: 16px;
+  min-width: 900px;
+}
+.fixed-header+.app-main {
+  padding-top: 50px;
+}
+</style>
+
+<style lang="scss">
+// fix css style bug in open el-dialog
+.el-popup-parent--hidden {
+  .fixed-header {
+    padding-right: 15px;
+  }
+}
+</style>

+ 140 - 0
src/layout/components/Navbar.vue

@@ -0,0 +1,140 @@
+<template>
+  <div class="navbar">
+    <hamburger :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
+
+    <breadcrumb class="breadcrumb-container" />
+
+    <div class="right-menu">
+      <el-dropdown class="avatar-container" trigger="click">
+        <div class="avatar-wrapper">
+          <img src="~@/assets/avatar-default.png" class="user-avatar">
+          <i class="el-icon-caret-bottom" style="color:rgb(191, 203, 217)" />
+        </div>
+        <el-dropdown-menu slot="dropdown" class="user-dropdown">
+          <!-- <router-link to="/">
+            <el-dropdown-item>
+              Home
+            </el-dropdown-item>
+          </router-link>
+          <a target="_blank" href="https://github.com/PanJiaChen/tk-jz-pc-ui/">
+            <el-dropdown-item>Github</el-dropdown-item>
+          </a>
+          <a target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/#/">
+            <el-dropdown-item>Docs</el-dropdown-item>
+          </a> -->
+          <!-- divided -->
+          <el-dropdown-item  @click.native="logout">
+            <span style="display:block;">退出登录</span>
+          </el-dropdown-item>
+        </el-dropdown-menu>
+      </el-dropdown>
+    </div>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import Breadcrumb from '@/components/Breadcrumb'
+import Hamburger from '@/components/Hamburger'
+
+export default {
+  components: {
+    Breadcrumb,
+    Hamburger
+  },
+  computed: {
+    ...mapGetters([
+      'sidebar',
+      'avatar'
+    ])
+  },
+  methods: {
+    toggleSideBar() {
+      this.$store.dispatch('app/toggleSideBar')
+    },
+    async logout() {
+      await this.$store.dispatch('user/logout')
+      this.$router.push(`/login?redirect=${this.$route.fullPath}`)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.navbar {
+  height: 50px;
+  overflow: hidden;
+  position: relative;
+  background: #304156;
+  box-shadow: 0 1px 4px rgba(0,21,41,.08);
+
+  .hamburger-container {
+    line-height: 46px;
+    height: 100%;
+    float: left;
+    cursor: pointer;
+    transition: background .3s;
+    -webkit-tap-highlight-color:transparent;
+
+    &:hover {
+      background: rgba(0, 0, 0, .025)
+    }
+  }
+
+  .breadcrumb-container {
+    float: left;
+  }
+
+  .right-menu {
+    float: right;
+    height: 100%;
+    line-height: 50px;
+
+    &:focus {
+      outline: none;
+    }
+
+    .right-menu-item {
+      display: inline-block;
+      padding: 0 8px;
+      height: 100%;
+      font-size: 18px;
+      color: #5a5e66;
+      vertical-align: text-bottom;
+
+      &.hover-effect {
+        cursor: pointer;
+        transition: background .3s;
+
+        &:hover {
+          background: rgba(0, 0, 0, .025)
+        }
+      }
+    }
+
+    .avatar-container {
+      margin-right: 30px;
+
+      .avatar-wrapper {
+        margin-top: 5px;
+        position: relative;
+
+        .user-avatar {
+          cursor: pointer;
+          width: 40px;
+          height: 40px;
+          border-radius: 10px;
+        }
+
+        .el-icon-caret-bottom {
+          cursor: pointer;
+          position: absolute;
+          right: -20px;
+          top: 15px;
+          font-size: 12px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 26 - 0
src/layout/components/Sidebar/FixiOSBug.js

@@ -0,0 +1,26 @@
+export default {
+  computed: {
+    device() {
+      return this.$store.state.app.device
+    }
+  },
+  mounted() {
+    // In order to fix the click on menu on the ios device will trigger the mouseleave bug
+    // https://github.com/PanJiaChen/vue-element-admin/issues/1135
+    this.fixBugIniOS()
+  },
+  methods: {
+    fixBugIniOS() {
+      const $subMenu = this.$refs.subMenu
+      if ($subMenu) {
+        const handleMouseleave = $subMenu.handleMouseleave
+        $subMenu.handleMouseleave = (e) => {
+          if (this.device === 'mobile') {
+            return
+          }
+          handleMouseleave(e)
+        }
+      }
+    }
+  }
+}

+ 41 - 0
src/layout/components/Sidebar/Item.vue

@@ -0,0 +1,41 @@
+<script>
+export default {
+  name: 'MenuItem',
+  functional: true,
+  props: {
+    icon: {
+      type: String,
+      default: ''
+    },
+    title: {
+      type: String,
+      default: ''
+    }
+  },
+  render(h, context) {
+    const { icon, title } = context.props
+    const vnodes = []
+
+    if (icon) {
+      if (icon.includes('el-icon')) {
+        vnodes.push(<i class={[icon, 'sub-el-icon']} />)
+      } else {
+        vnodes.push(<svg-icon icon-class={icon}/>)
+      }
+    }
+
+    if (title) {
+      vnodes.push(<span slot='title'>{(title)}</span>)
+    }
+    return vnodes
+  }
+}
+</script>
+
+<style scoped>
+.sub-el-icon {
+  color: currentColor;
+  width: 1em;
+  height: 1em;
+}
+</style>

+ 43 - 0
src/layout/components/Sidebar/Link.vue

@@ -0,0 +1,43 @@
+<template>
+  <component :is="type" v-bind="linkProps(to)">
+    <slot />
+  </component>
+</template>
+
+<script>
+import { isExternal } from '@/utils/validate'
+
+export default {
+  props: {
+    to: {
+      type: String,
+      required: true
+    }
+  },
+  computed: {
+    isExternal() {
+      return isExternal(this.to)
+    },
+    type() {
+      if (this.isExternal) {
+        return 'a'
+      }
+      return 'router-link'
+    }
+  },
+  methods: {
+    linkProps(to) {
+      if (this.isExternal) {
+        return {
+          href: to,
+          target: '_blank',
+          rel: 'noopener'
+        }
+      }
+      return {
+        to: to
+      }
+    }
+  }
+}
+</script>

+ 82 - 0
src/layout/components/Sidebar/Logo.vue

@@ -0,0 +1,82 @@
+<template>
+  <div class="sidebar-logo-container" :class="{'collapse':collapse}">
+    <transition name="sidebarLogoFade">
+      <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
+        <img v-if="logo" :src="logo" class="sidebar-logo">
+        <h1 v-else class="sidebar-title">{{ title }} </h1>
+      </router-link>
+      <router-link v-else key="expand" class="sidebar-logo-link" to="/">
+        <img v-if="logo" :src="logo" class="sidebar-logo">
+        <h1 class="sidebar-title">{{ title }} </h1>
+      </router-link>
+    </transition>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'SidebarLogo',
+  props: {
+    collapse: {
+      type: Boolean,
+      required: true
+    }
+  },
+  data() {
+    return {
+      title: 'Vue Admin Template',
+      logo: 'https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png'
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.sidebarLogoFade-enter-active {
+  transition: opacity 1.5s;
+}
+
+.sidebarLogoFade-enter,
+.sidebarLogoFade-leave-to {
+  opacity: 0;
+}
+
+.sidebar-logo-container {
+  position: relative;
+  width: 100%;
+  height: 50px;
+  line-height: 50px;
+  background: #2b2f3a;
+  text-align: center;
+  overflow: hidden;
+
+  & .sidebar-logo-link {
+    height: 100%;
+    width: 100%;
+
+    & .sidebar-logo {
+      width: 32px;
+      height: 32px;
+      vertical-align: middle;
+      margin-right: 12px;
+    }
+
+    & .sidebar-title {
+      display: inline-block;
+      margin: 0;
+      color: #fff;
+      font-weight: 600;
+      line-height: 50px;
+      font-size: 14px;
+      font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
+      vertical-align: middle;
+    }
+  }
+
+  &.collapse {
+    .sidebar-logo {
+      margin-right: 0px;
+    }
+  }
+}
+</style>

+ 95 - 0
src/layout/components/Sidebar/SidebarItem.vue

@@ -0,0 +1,95 @@
+<template>
+  <div v-if="!item.hidden">
+    <template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
+      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
+        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
+          <item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" />
+        </el-menu-item>
+      </app-link>
+    </template>
+
+    <el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
+      <template slot="title">
+        <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
+      </template>
+      <sidebar-item
+        v-for="child in item.children"
+        :key="child.path"
+        :is-nest="true"
+        :item="child"
+        :base-path="resolvePath(child.path)"
+        class="nest-menu"
+      />
+    </el-submenu>
+  </div>
+</template>
+
+<script>
+import path from 'path'
+import { isExternal } from '@/utils/validate'
+import Item from './Item'
+import AppLink from './Link'
+import FixiOSBug from './FixiOSBug'
+
+export default {
+  name: 'SidebarItem',
+  components: { Item, AppLink },
+  mixins: [FixiOSBug],
+  props: {
+    // route object
+    item: {
+      type: Object,
+      required: true
+    },
+    isNest: {
+      type: Boolean,
+      default: false
+    },
+    basePath: {
+      type: String,
+      default: ''
+    }
+  },
+  data() {
+    // To fix https://github.com/PanJiaChen/tk-jz-pc-ui/issues/237
+    // TODO: refactor with render function
+    this.onlyOneChild = null
+    return {}
+  },
+  methods: {
+    hasOneShowingChild(children = [], parent) {
+      const showingChildren = children.filter(item => {
+        if (item.hidden) {
+          return false
+        } else {
+          // Temp set(will be used if only has one showing child)
+          this.onlyOneChild = item
+          return true
+        }
+      })
+
+      // When there is only one child router, the child router is displayed by default
+      if (showingChildren.length === 1) {
+        return true
+      }
+
+      // Show parent if there are no child router to display
+      if (showingChildren.length === 0) {
+        this.onlyOneChild = { ... parent, path: '', noShowingChildren: true }
+        return true
+      }
+
+      return false
+    },
+    resolvePath(routePath) {
+      if (isExternal(routePath)) {
+        return routePath
+      }
+      if (isExternal(this.basePath)) {
+        return this.basePath
+      }
+      return path.resolve(this.basePath, routePath)
+    }
+  }
+}
+</script>

+ 56 - 0
src/layout/components/Sidebar/index.vue

@@ -0,0 +1,56 @@
+<template>
+  <div :class="{'has-logo':showLogo}">
+    <logo v-if="showLogo" :collapse="isCollapse" />
+    <el-scrollbar wrap-class="scrollbar-wrapper">
+      <el-menu
+        :default-active="activeMenu"
+        :collapse="isCollapse"
+        :background-color="variables.menuBg"
+        :text-color="variables.menuText"
+        :unique-opened="false"
+        :active-text-color="variables.menuActiveText"
+        :collapse-transition="false"
+        mode="vertical"
+      >
+        <sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" />
+      </el-menu>
+    </el-scrollbar>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import Logo from './Logo'
+import SidebarItem from './SidebarItem'
+import variables from '@/styles/variables.scss'
+
+export default {
+  components: { SidebarItem, Logo },
+  computed: {
+    ...mapGetters([
+      'sidebar'
+    ]),
+    routes() {
+      return this.$router.options.routes
+    },
+    activeMenu() {
+      const route = this.$route
+      const { meta, path } = route
+      // if set path, the sidebar will highlight the path you set
+      if (meta.activeMenu) {
+        return meta.activeMenu
+      }
+      return path
+    },
+    showLogo() {
+      return this.$store.state.settings.sidebarLogo
+    },
+    variables() {
+      return variables
+    },
+    isCollapse() {
+      return !this.sidebar.opened
+    }
+  }
+}
+</script>

+ 3 - 0
src/layout/components/index.js

@@ -0,0 +1,3 @@
+export { default as Navbar } from './Navbar'
+export { default as Sidebar } from './Sidebar'
+export { default as AppMain } from './AppMain'

+ 94 - 0
src/layout/index.vue

@@ -0,0 +1,94 @@
+<template>
+  <div :class="classObj" class="app-wrapper">
+    <div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
+    <sidebar class="sidebar-container" />
+    <div class="main-container">
+      <div :class="{'fixed-header':fixedHeader}">
+        <navbar />
+      </div>
+      <app-main />
+    </div>
+  </div>
+</template>
+
+<script>
+import { Navbar, Sidebar, AppMain } from './components'
+import ResizeMixin from './mixin/ResizeHandler'
+
+export default {
+  name: 'Layout',
+  components: {
+    Navbar,
+    Sidebar,
+    AppMain
+  },
+  mixins: [ResizeMixin],
+  computed: {
+    sidebar() {
+      return this.$store.state.app.sidebar
+    },
+    device() {
+      return this.$store.state.app.device
+    },
+    fixedHeader() {
+      return this.$store.state.settings.fixedHeader
+    },
+    classObj() {
+      return {
+        hideSidebar: !this.sidebar.opened,
+        openSidebar: this.sidebar.opened,
+        withoutAnimation: this.sidebar.withoutAnimation,
+        mobile: this.device === 'mobile'
+      }
+    }
+  },
+  
+  methods: {
+    handleClickOutside() {
+      this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  @import "~@/styles/mixin.scss";
+  @import "~@/styles/variables.scss";
+
+  .app-wrapper {
+    @include clearfix;
+    position: relative;
+    height: 100%;
+    width: 100%;
+    &.mobile.openSidebar{
+      position: fixed;
+      top: 0;
+    }
+  }
+  .drawer-bg {
+    background: #000;
+    opacity: 0.3;
+    width: 100%;
+    top: 0;
+    height: 100%;
+    position: absolute;
+    z-index: 999;
+  }
+
+  .fixed-header {
+    position: fixed;
+    top: 0;
+    right: 0;
+    z-index: 9;
+    width: calc(100% - #{$sideBarWidth});
+    transition: width 0.28s;
+  }
+
+  .hideSidebar .fixed-header {
+    width: calc(100% - 54px)
+  }
+
+  .mobile .fixed-header {
+    width: 100%;
+  }
+</style>

+ 45 - 0
src/layout/mixin/ResizeHandler.js

@@ -0,0 +1,45 @@
+import store from '@/store'
+
+const { body } = document
+const WIDTH = 992 // refer to Bootstrap's responsive design
+
+export default {
+  watch: {
+    $route(route) {
+      if (this.device === 'mobile' && this.sidebar.opened) {
+        store.dispatch('app/closeSideBar', { withoutAnimation: false })
+      }
+    }
+  },
+  beforeMount() {
+    window.addEventListener('resize', this.$_resizeHandler)
+  },
+  beforeDestroy() {
+    window.removeEventListener('resize', this.$_resizeHandler)
+  },
+  mounted() {
+    const isMobile = this.$_isMobile()
+    if (isMobile) {
+      store.dispatch('app/toggleDevice', 'mobile')
+      store.dispatch('app/closeSideBar', { withoutAnimation: true })
+    }
+  },
+  methods: {
+    // use $_ for mixins properties
+    // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
+    $_isMobile() {
+      const rect = body.getBoundingClientRect()
+      return rect.width - 1 < WIDTH
+    },
+    $_resizeHandler() {
+      if (!document.hidden) {
+        const isMobile = this.$_isMobile()
+        store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
+
+        if (isMobile) {
+          store.dispatch('app/closeSideBar', { withoutAnimation: true })
+        }
+      }
+    }
+  }
+}

+ 57 - 0
src/main.js

@@ -0,0 +1,57 @@
+import Vue from 'vue'
+
+import 'normalize.css/normalize.css' // A modern alternative to CSS resets
+import axios from 'axios'
+import ElementUI from 'element-ui'
+import 'element-ui/lib/theme-chalk/index.css'
+// import locale from 'element-ui/lib/locale/lang/en' // lang i18n
+import underscore from 'underscore'
+import Print from 'vue-print-nb'
+import noAuth from './components/NoAuth.vue'
+import mavonEditor from 'mavon-editor'
+import '@/styles/index.scss' // global css
+import VueQuillEditor from 'vue-quill-editor'
+import App from './App'
+import store from './store'
+import router from './router'
+import md5 from 'js-md5';
+import '@/icons' // icon
+import '@/permission' // permission control
+import 'mavon-editor/dist/css/index.css'
+// require styles
+import 'quill/dist/quill.core.css'
+import 'quill/dist/quill.snow.css'
+import 'quill/dist/quill.bubble.css'
+/**
+ * If you don't want to use mock-server
+ * you want to use MockJs for mock api
+ * you can execute: mockXHR()
+ *
+ * Currently MockJs will be used in the production environment,
+ * please remove it before going online ! ! !
+ */
+// if (process.env.NODE_ENV === 'production') {
+//   const { mockXHR } = require('../mock')
+//   mockXHR()
+// }
+window._ = underscore
+// 暂无权限
+Vue.component('noAuth', noAuth)
+Vue.use(mavonEditor)
+Vue.use(Print); 
+Vue.use(VueQuillEditor)
+Vue.prototype.$md5 = md5;
+Vue.prototype.axios = axios
+// set ElementUI lang to EN
+// Vue.use(ElementUI, { locale })
+// 如果想要中文版 element-ui,按如下方式声明
+Vue.use(ElementUI)
+
+Vue.config.productionTip = false
+
+new Vue({
+  el: '#app',
+  router,
+  store,
+  render: h => h(App)
+})

+ 65 - 0
src/permission.js

@@ -0,0 +1,65 @@
+import router from './router'
+import store from './store'
+import { Message } from 'element-ui'
+import NProgress from 'nprogress' // progress bar
+import 'nprogress/nprogress.css' // progress bar style
+import { getToken } from '@/utils/auth' // get token from cookie
+import getPageTitle from '@/utils/get-page-title'
+
+NProgress.configure({ showSpinner: false }) // NProgress Configuration
+
+const whiteList = ['/login'] // no redirect whitelist
+
+router.beforeEach(async(to, from, next) => {
+  // start progress bar
+  NProgress.start()
+
+  // set page title
+  document.title = getPageTitle(to.meta.title)
+
+  // determine whether the user has logged in
+  const hasToken = getToken()
+
+  if (hasToken) {
+    if (to.path === '/login') {
+      // if is logged in, redirect to the home page
+      next({ path: '/' })
+      NProgress.done()
+    } else {
+      next()
+      // const hasGetUserInfo = store.getters.name
+      // if (hasGetUserInfo) {
+      //   next()
+      // } else {
+      //   try {
+      //     // get user info
+      //     await store.dispatch('user/getInfo')
+
+      //     next()
+      //   } catch (error) {
+      //     // remove token and go to login page to re-login
+      //     await store.dispatch('user/resetToken')
+      //     Message.error(error || 'Has Error')
+      //     next(`/login?redirect=${to.path}`)
+      //     NProgress.done()
+      //   }
+      // }
+    }
+  } else {
+    /* has no token*/
+
+    if (whiteList.indexOf(to.path) !== -1) {
+      // in the free login whitelist, go directly
+      next()
+    } else {
+      // other pages that do not have permission to access are redirected to the login page.
+      next(`/login?redirect=${to.path}`)
+      NProgress.done()
+    }
+  }
+})
+
+router.afterEach(() => {
+  // finish progress bar
+  NProgress.done()
+})

+ 139 - 0
src/router/index.js

@@ -0,0 +1,139 @@
+import Vue from 'vue'
+import Router from 'vue-router'
+
+Vue.use(Router)
+
+/* Layout */
+import Layout from '@/layout'
+
+/**
+ * Note: sub-menu only appear when route children.length >= 1
+ * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
+ *
+ * hidden: true                   if set true, item will not show in the sidebar(default is false)
+ * alwaysShow: true               if set true, will always show the root menu
+ *                                if not set alwaysShow, when item has more than one children route,
+ *                                it will becomes nested mode, otherwise not show the root menu
+ * redirect: noRedirect           if set noRedirect will no redirect in the breadcrumb
+ * name:'router-name'             the name is used by <keep-alive> (must set!!!)
+ * meta : {
+    roles: ['admin','editor']    control the page roles (you can set multiple roles)
+    title: 'title'               the name show in sidebar and breadcrumb (recommend set)
+    icon: 'svg-name'/'el-icon-x' the icon show in the sidebar
+    breadcrumb: false            if set false, the item will hidden in breadcrumb(default is true)
+    activeMenu: '/example/list'  if set path, the sidebar will highlight the path you set
+  }
+ */
+
+/**
+ * constantRoutes
+ * a base page that does not have permission requirements
+ * all roles can be accessed
+ */
+export const constantRoutes = [
+  {
+    path: '/login',
+    component: () => import('@/views/login/index'),
+    hidden: true
+  },
+
+  {
+    path: '/404',
+    component: () => import('@/views/404'),
+    hidden: true
+  },
+  {
+    path: '/',
+    component: Layout,
+    redirect: '/userManagement',
+    children: [
+      {
+        path: 'userManagement',
+        name: 'userManagement',
+        component: () => import('@/views/userManagement/userManagement'),
+        meta: { title: '客户管理', icon: 'dashboard' }
+      },
+      {
+        path: 'userEdit',
+        name: 'userEdit',
+        hidden: true,
+        component: () => import('@/views/kehuEdit/index'),
+        meta: { title: '客户编辑', icon: 'dashboard', breadcrumb: false }
+      },]
+  },
+
+
+
+  {
+    path: '/operate',
+    component: Layout,
+    redirect: '/operate/operateUserManagement',
+    name: 'operate',
+    // meta: { title: 'operate', icon: 'el-icon-s-help' },
+    children: [
+      {
+        path: 'operateUserManagement',
+        name: 'operateUserManagement',
+        component: () => import('@/views/operateUserManage/OperateUserManagement'),
+        meta: { title: '客服管理', icon: 'table' }
+      },
+
+    ]
+  },
+  {
+    path: '/hr',
+    component: Layout,
+    redirect: '/hr/hrManagement',
+    name: 'hr',
+    // meta: { title: 'operate', icon: 'el-icon-s-help' },
+    children: [
+      {
+        path: 'hrManagement',
+        name: 'hrManagement',
+        component: () => import('@/views/hrManagement/hrManagement'),
+        meta: { title: '医生管理', icon: 'tree' }
+      },
+
+    ]
+  },
+  {
+    path: '/order',
+    component: Layout,
+    redirect: '/order/orderManagement',
+    name: 'order',
+    // meta: { title: 'operate', icon: 'el-icon-s-help' },
+    children: [
+      {
+        path: 'orderManagement',
+        name: 'orderManagement',
+        component: () => import('@/views/orderManagement/orderManagement'),
+        meta: { title: '订单管理', icon: 'form' }
+      }
+
+    ]
+  },
+
+
+
+  // { path: '*', redirect: '/404', hidden: true }
+  // { path: '*', redirect: '/404', hidden: true }
+
+  { path: '*', redirect: '/404', hidden: true }
+
+]
+
+const createRouter = () => new Router({
+  // mode: 'history', // require service support
+  scrollBehavior: () => ({ y: 0 }),
+  routes: constantRoutes
+})
+
+const router = createRouter()
+
+// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
+export function resetRouter() {
+  const newRouter = createRouter()
+  router.matcher = newRouter.matcher // reset router
+}
+
+export default router

+ 16 - 0
src/settings.js

@@ -0,0 +1,16 @@
+module.exports = {
+
+  title: 'Vue Admin Template',
+
+  /**
+   * @type {boolean} true | false
+   * @description Whether fix the header
+   */
+  fixedHeader: false,
+
+  /**
+   * @type {boolean} true | false
+   * @description Whether show the logo in sidebar
+   */
+  sidebarLogo: false
+}

+ 8 - 0
src/store/getters.js

@@ -0,0 +1,8 @@
+const getters = {
+  sidebar: state => state.app.sidebar,
+  device: state => state.app.device,
+  token: state => state.user.token,
+  avatar: state => state.user.avatar,
+  name: state => state.user.name
+}
+export default getters

+ 19 - 0
src/store/index.js

@@ -0,0 +1,19 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+import getters from './getters'
+import app from './modules/app'
+import settings from './modules/settings'
+import user from './modules/user'
+
+Vue.use(Vuex)
+
+const store = new Vuex.Store({
+  modules: {
+    app,
+    settings,
+    user
+  },
+  getters
+})
+
+export default store

+ 48 - 0
src/store/modules/app.js

@@ -0,0 +1,48 @@
+import Cookies from 'js-cookie'
+
+const state = {
+  sidebar: {
+    opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
+    withoutAnimation: false
+  },
+  device: 'desktop'
+}
+
+const mutations = {
+  TOGGLE_SIDEBAR: state => {
+    state.sidebar.opened = !state.sidebar.opened
+    state.sidebar.withoutAnimation = false
+    if (state.sidebar.opened) {
+      Cookies.set('sidebarStatus', 1)
+    } else {
+      Cookies.set('sidebarStatus', 0)
+    }
+  },
+  CLOSE_SIDEBAR: (state, withoutAnimation) => {
+    Cookies.set('sidebarStatus', 0)
+    state.sidebar.opened = false
+    state.sidebar.withoutAnimation = withoutAnimation
+  },
+  TOGGLE_DEVICE: (state, device) => {
+    state.device = device
+  }
+}
+
+const actions = {
+  toggleSideBar({ commit }) {
+    commit('TOGGLE_SIDEBAR')
+  },
+  closeSideBar({ commit }, { withoutAnimation }) {
+    commit('CLOSE_SIDEBAR', withoutAnimation)
+  },
+  toggleDevice({ commit }, device) {
+    commit('TOGGLE_DEVICE', device)
+  }
+}
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions
+}

+ 32 - 0
src/store/modules/settings.js

@@ -0,0 +1,32 @@
+import defaultSettings from '@/settings'
+
+const { showSettings, fixedHeader, sidebarLogo } = defaultSettings
+
+const state = {
+  showSettings: showSettings,
+  fixedHeader: fixedHeader,
+  sidebarLogo: sidebarLogo
+}
+
+const mutations = {
+  CHANGE_SETTING: (state, { key, value }) => {
+    // eslint-disable-next-line no-prototype-builtins
+    if (state.hasOwnProperty(key)) {
+      state[key] = value
+    }
+  }
+}
+
+const actions = {
+  changeSetting({ commit }, data) {
+    commit('CHANGE_SETTING', data)
+  }
+}
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions
+}
+

+ 92 - 0
src/store/modules/user.js

@@ -0,0 +1,92 @@
+// import { login, logout, getInfo } from '@/api/user'
+import { getToken, setToken, removeToken } from '@/utils/auth'
+import { resetRouter } from '@/router'
+import asyncRequest from "@/asnyc/service/index";
+const getDefaultState = () => {
+  return {
+    token: getToken(),
+    name: '',
+    avatar: '',
+    isLogin:false
+  }
+}
+
+const state = getDefaultState()
+
+const mutations = {
+  RESET_STATE: (state) => {
+    Object.assign(state, getDefaultState())
+  },
+  SET_TOKEN: (state, token) => {
+    state.token = token
+    state.isLogin=true 
+  },
+  SET_NAME: (state, name) => {
+    state.name = name
+  },
+  SET_AVATAR: (state, avatar) => {
+    state.avatar = avatar
+  }
+}
+
+const actions = {
+
+  
+  async  logins({ commit }, userInfo) {
+    const { username, password } = userInfo
+    let res = await asyncRequest.login({ username: username.trim(), password: password })
+    if(res&&res.status===0){
+     const { data } = res
+     commit('SET_TOKEN', data.token)
+      localStorage.setItem("token",data.token)
+     commit('SET_NAME', username)
+     setToken(data.token)
+
+    }else{
+    }
+  },
+  // get user info
+  getInfo({ commit, state }) {
+    return new Promise((resolve, reject) => {
+      getInfo(state.token).then(response => {
+        const { data } = response
+
+        if (!data) {
+          return reject('Verification failed, please Login again.')
+        }
+
+        const { name, avatar } = data
+
+        commit('SET_NAME', name)
+        commit('SET_AVATAR', avatar)
+        resolve(data)
+      }).catch(error => {
+        reject(error)
+      })
+    })
+  },
+
+  // user logout
+  logout({ commit, state }) {
+    removeToken() // must remove  token  first
+        resetRouter()
+        commit('RESET_STATE')
+  },
+
+  // remove token
+  resetToken({ commit }) {
+    return new Promise(resolve => {
+      removeToken() // must remove  token  first
+      commit('RESET_STATE')
+      resolve()
+    })
+  }
+}
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions
+}
+

+ 49 - 0
src/styles/element-ui.scss

@@ -0,0 +1,49 @@
+// cover some element-ui styles
+
+.el-breadcrumb__inner,
+.el-breadcrumb__inner a {
+  font-weight: 400 !important;
+}
+
+.el-upload {
+  input[type="file"] {
+    display: none !important;
+  }
+}
+
+.el-upload__input {
+  display: none;
+}
+
+
+// to fixed https://github.com/ElemeFE/element/issues/2461
+.el-dialog {
+  transform: none;
+  left: 0;
+  position: relative;
+  margin: 0 auto;
+}
+
+// refine element ui upload
+.upload-container {
+  .el-upload {
+    width: 100%;
+
+    .el-upload-dragger {
+      width: 100%;
+      height: 200px;
+    }
+  }
+}
+
+// dropdown
+.el-dropdown-menu {
+  a {
+    display: block
+  }
+}
+
+// to fix el-date-picker css style
+.el-range-separator {
+  box-sizing: content-box;
+}

+ 65 - 0
src/styles/index.scss

@@ -0,0 +1,65 @@
+@import './variables.scss';
+@import './mixin.scss';
+@import './transition.scss';
+@import './element-ui.scss';
+@import './sidebar.scss';
+
+body {
+  height: 100%;
+  -moz-osx-font-smoothing: grayscale;
+  -webkit-font-smoothing: antialiased;
+  text-rendering: optimizeLegibility;
+  font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
+}
+
+label {
+  font-weight: 700;
+}
+
+html {
+  height: 100%;
+  box-sizing: border-box;
+}
+
+#app {
+  height: 100%;
+}
+
+*,
+*:before,
+*:after {
+  box-sizing: inherit;
+}
+
+a:focus,
+a:active {
+  outline: none;
+}
+
+a,
+a:focus,
+a:hover {
+  cursor: pointer;
+  color: inherit;
+  text-decoration: none;
+}
+
+div:focus {
+  outline: none;
+}
+
+.clearfix {
+  &:after {
+    visibility: hidden;
+    display: block;
+    font-size: 0;
+    content: " ";
+    clear: both;
+    height: 0;
+  }
+}
+
+// main-container global css
+.app-container {
+  padding: 20px;
+}

+ 28 - 0
src/styles/mixin.scss

@@ -0,0 +1,28 @@
+@mixin clearfix {
+  &:after {
+    content: "";
+    display: table;
+    clear: both;
+  }
+}
+
+@mixin scrollBar {
+  &::-webkit-scrollbar-track-piece {
+    background: #d3dce6;
+  }
+
+  &::-webkit-scrollbar {
+    width: 6px;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background: #99a9bf;
+    border-radius: 20px;
+  }
+}
+
+@mixin relative {
+  position: relative;
+  width: 100%;
+  height: 100%;
+}

+ 226 - 0
src/styles/sidebar.scss

@@ -0,0 +1,226 @@
+#app {
+
+  .main-container {
+    min-height: 100%;
+    transition: margin-left .28s;
+    margin-left: $sideBarWidth;
+    position: relative;
+  }
+
+  .sidebar-container {
+    transition: width 0.28s;
+    width: $sideBarWidth !important;
+    background-color: $menuBg;
+    height: 100%;
+    position: fixed;
+    font-size: 0px;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    z-index: 1001;
+    overflow: hidden;
+
+    // reset element-ui css
+    .horizontal-collapse-transition {
+      transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
+    }
+
+    .scrollbar-wrapper {
+      overflow-x: hidden !important;
+    }
+
+    .el-scrollbar__bar.is-vertical {
+      right: 0px;
+    }
+
+    .el-scrollbar {
+      height: 100%;
+    }
+
+    &.has-logo {
+      .el-scrollbar {
+        height: calc(100% - 50px);
+      }
+    }
+
+    .is-horizontal {
+      display: none;
+    }
+
+    a {
+      display: inline-block;
+      width: 100%;
+      overflow: hidden;
+    }
+
+    .svg-icon {
+      margin-right: 16px;
+    }
+
+    .sub-el-icon {
+      margin-right: 12px;
+      margin-left: -2px;
+    }
+
+    .el-menu {
+      border: none;
+      height: 100%;
+      width: 100% !important;
+    }
+
+    // menu hover
+    .submenu-title-noDropdown,
+    .el-submenu__title {
+      &:hover {
+        background-color: $menuHover !important;
+      }
+    }
+
+    .is-active>.el-submenu__title {
+      color: $subMenuActiveText !important;
+    }
+
+    & .nest-menu .el-submenu>.el-submenu__title,
+    & .el-submenu .el-menu-item {
+      min-width: $sideBarWidth !important;
+      background-color: $subMenuBg !important;
+
+      &:hover {
+        background-color: $subMenuHover !important;
+      }
+    }
+  }
+
+  .hideSidebar {
+    .sidebar-container {
+      width: 54px !important;
+    }
+
+    .main-container {
+      margin-left: 54px;
+    }
+
+    .submenu-title-noDropdown {
+      padding: 0 !important;
+      position: relative;
+
+      .el-tooltip {
+        padding: 0 !important;
+
+        .svg-icon {
+          margin-left: 20px;
+        }
+
+        .sub-el-icon {
+          margin-left: 19px;
+        }
+      }
+    }
+
+    .el-submenu {
+      overflow: hidden;
+
+      &>.el-submenu__title {
+        padding: 0 !important;
+
+        .svg-icon {
+          margin-left: 20px;
+        }
+
+        .sub-el-icon {
+          margin-left: 19px;
+        }
+
+        .el-submenu__icon-arrow {
+          display: none;
+        }
+      }
+    }
+
+    .el-menu--collapse {
+      .el-submenu {
+        &>.el-submenu__title {
+          &>span {
+            height: 0;
+            width: 0;
+            overflow: hidden;
+            visibility: hidden;
+            display: inline-block;
+          }
+        }
+      }
+    }
+  }
+
+  .el-menu--collapse .el-menu .el-submenu {
+    min-width: $sideBarWidth !important;
+  }
+
+  // mobile responsive
+  .mobile {
+    .main-container {
+      margin-left: 0px;
+    }
+
+    .sidebar-container {
+      transition: transform .28s;
+      width: $sideBarWidth !important;
+    }
+
+    &.hideSidebar {
+      .sidebar-container {
+        pointer-events: none;
+        transition-duration: 0.3s;
+        transform: translate3d(-$sideBarWidth, 0, 0);
+      }
+    }
+  }
+
+  .withoutAnimation {
+
+    .main-container,
+    .sidebar-container {
+      transition: none;
+    }
+  }
+}
+
+// when menu collapsed
+.el-menu--vertical {
+  &>.el-menu {
+    .svg-icon {
+      margin-right: 16px;
+    }
+    .sub-el-icon {
+      margin-right: 12px;
+      margin-left: -2px;
+    }
+  }
+
+  .nest-menu .el-submenu>.el-submenu__title,
+  .el-menu-item {
+    &:hover {
+      // you can use $subMenuHover
+      background-color: $menuHover !important;
+    }
+  }
+
+  // the scroll bar appears when the subMenu is too long
+  >.el-menu--popup {
+    max-height: 100vh;
+    overflow-y: auto;
+
+    &::-webkit-scrollbar-track-piece {
+      background: #d3dce6;
+    }
+
+    &::-webkit-scrollbar {
+      width: 6px;
+    }
+
+    &::-webkit-scrollbar-thumb {
+      background: #99a9bf;
+      border-radius: 20px;
+    }
+  }
+}

+ 48 - 0
src/styles/transition.scss

@@ -0,0 +1,48 @@
+// global transition css
+
+/* fade */
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity 0.28s;
+}
+
+.fade-enter,
+.fade-leave-active {
+  opacity: 0;
+}
+
+/* fade-transform */
+.fade-transform-leave-active,
+.fade-transform-enter-active {
+  transition: all .5s;
+}
+
+.fade-transform-enter {
+  opacity: 0;
+  transform: translateX(-30px);
+}
+
+.fade-transform-leave-to {
+  opacity: 0;
+  transform: translateX(30px);
+}
+
+/* breadcrumb transition */
+.breadcrumb-enter-active,
+.breadcrumb-leave-active {
+  transition: all .5s;
+}
+
+.breadcrumb-enter,
+.breadcrumb-leave-active {
+  opacity: 0;
+  transform: translateX(20px);
+}
+
+.breadcrumb-move {
+  transition: all .5s;
+}
+
+.breadcrumb-leave-active {
+  position: absolute;
+}

+ 25 - 0
src/styles/variables.scss

@@ -0,0 +1,25 @@
+// sidebar
+$menuText:#bfcbd9;
+$menuActiveText:#409EFF;
+$subMenuActiveText:#f4f4f5; //https://github.com/ElemeFE/element/issues/12951
+
+$menuBg:#304156;
+$menuHover:#263445;
+
+$subMenuBg:#1f2d3d;
+$subMenuHover:#001528;
+
+$sideBarWidth: 210px;
+
+// the :export directive is the magic sauce for webpack
+// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
+:export {
+  menuText: $menuText;
+  menuActiveText: $menuActiveText;
+  subMenuActiveText: $subMenuActiveText;
+  menuBg: $menuBg;
+  menuHover: $menuHover;
+  subMenuBg: $subMenuBg;
+  subMenuHover: $subMenuHover;
+  sideBarWidth: $sideBarWidth;
+}

+ 15 - 0
src/utils/auth.js

@@ -0,0 +1,15 @@
+import Cookies from 'js-cookie'
+
+const TokenKey = 'vue_admin_template_token'
+
+export function getToken() {
+  return Cookies.get(TokenKey)
+}
+
+export function setToken(token) {
+  return Cookies.set(TokenKey, token)
+}
+
+export function removeToken() {
+  return Cookies.remove(TokenKey)
+}

+ 19 - 0
src/utils/dealOptions.js

@@ -0,0 +1,19 @@
+
+// 备选数据返回id
+export function returnId(list, name) {
+    let id = ""
+    list.forEach(e => {
+        if (e.name === name) {
+            id = e.id
+        }
+
+    });
+    return id===""?1:id
+}
+// 备选数据返回name
+export function returnName(list, id) {
+    let index = list.findIndex(item => {
+        return parseInt(item.id) === parseInt(id);
+    });
+    return index !== -1 ? list[index].name : "--";
+}

+ 49 - 0
src/utils/directives.js

@@ -0,0 +1,49 @@
+import Vue from 'vue';
+
+// v-dialogDrag: 弹窗拖拽
+Vue.directive('dialogDrag', {
+    bind(el, binding, vnode, oldVnode) {
+        const dialogHeaderEl = el.querySelector('.el-dialog__header');
+        const dragDom = el.querySelector('.el-dialog');
+        dialogHeaderEl.style.cursor = 'move';
+
+        // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
+        const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null);
+        
+        dialogHeaderEl.onmousedown = (e) => {
+            // 鼠标按下,计算当前元素距离可视区的距离
+            const disX = e.clientX - dialogHeaderEl.offsetLeft;
+            const disY = e.clientY - dialogHeaderEl.offsetTop;
+            
+            // 获取到的值带px 正则匹配替换
+            let styL, styT;
+
+            // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
+            if(sty.left.includes('%')) {
+                styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100);
+                styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100);
+            }else {
+                styL = +sty.left.replace(/\px/g, '');
+                styT = +sty.top.replace(/\px/g, '');
+            };
+            
+            document.onmousemove = function (e) {
+                // 通过事件委托,计算移动的距离 
+                const l = e.clientX - disX;
+                const t = e.clientY - disY;
+
+                // 移动当前元素  
+                dragDom.style.left = `${l + styL}px`;
+                dragDom.style.top = `${t + styT}px`;
+
+                //将此时的位置传出去
+                //binding.value({x:e.pageX,y:e.pageY})
+            };
+
+            document.onmouseup = function (e) {
+                document.onmousemove = null;
+                document.onmouseup = null;
+            };
+        }  
+    }
+})

+ 29 - 0
src/utils/filters.js

@@ -0,0 +1,29 @@
+let formatDate = time => {
+    if (time == '' || !time) {
+        return '--'
+    } else {
+        var date = moment(parseInt(time)).format("YYYY-MM-DD HH:mm:ss");
+        return date;
+    }
+}
+export { formatDate }
+
+let formatDate1 = time => {
+    if (time == '' || !time) {
+        return '--'
+    } else {
+        var date = moment(parseInt(time)).format("YYYY-MM-DD");
+        return date;
+    }
+}
+export { formatDate1 }
+
+let formatDate2 = time => {
+    if (time == '' || !time) {
+        return '--'
+    } else {
+        var date = moment(parseInt(time)).format("YYYY-MM-DD HH:mm");
+        return date;
+    }
+}
+export { formatDate2 }

+ 10 - 0
src/utils/get-page-title.js

@@ -0,0 +1,10 @@
+import defaultSettings from '@/settings'
+
+const title = defaultSettings.title || 'Vue Admin Template'
+
+export default function getPageTitle(pageTitle) {
+  if (pageTitle) {
+    return `${pageTitle} - ${title}`
+  }
+  return `${title}`
+}

+ 117 - 0
src/utils/index.js

@@ -0,0 +1,117 @@
+/**
+ * Created by PanJiaChen on 16/11/18.
+ */
+
+/**
+ * Parse the time to string
+ * @param {(Object|string|number)} time
+ * @param {string} cFormat
+ * @returns {string | null}
+ */
+export function parseTime(time, cFormat) {
+  if (arguments.length === 0 || !time) {
+    return null
+  }
+  const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
+  let date
+  if (typeof time === 'object') {
+    date = time
+  } else {
+    if ((typeof time === 'string')) {
+      if ((/^[0-9]+$/.test(time))) {
+        // support "1548221490638"
+        time = parseInt(time)
+      } else {
+        // support safari
+        // https://stackoverflow.com/questions/4310953/invalid-date-in-safari
+        time = time.replace(new RegExp(/-/gm), '/')
+      }
+    }
+
+    if ((typeof time === 'number') && (time.toString().length === 10)) {
+      time = time * 1000
+    }
+    date = new Date(time)
+  }
+  const formatObj = {
+    y: date.getFullYear(),
+    m: date.getMonth() + 1,
+    d: date.getDate(),
+    h: date.getHours(),
+    i: date.getMinutes(),
+    s: date.getSeconds(),
+    a: date.getDay()
+  }
+  const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
+    const value = formatObj[key]
+    // Note: getDay() returns 0 on Sunday
+    if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] }
+    return value.toString().padStart(2, '0')
+  })
+  return time_str
+}
+
+/**
+ * @param {number} time
+ * @param {string} option
+ * @returns {string}
+ */
+export function formatTime(time, option) {
+  if (('' + time).length === 10) {
+    time = parseInt(time) * 1000
+  } else {
+    time = +time
+  }
+  const d = new Date(time)
+  const now = Date.now()
+
+  const diff = (now - d) / 1000
+
+  if (diff < 30) {
+    return '刚刚'
+  } else if (diff < 3600) {
+    // less 1 hour
+    return Math.ceil(diff / 60) + '分钟前'
+  } else if (diff < 3600 * 24) {
+    return Math.ceil(diff / 3600) + '小时前'
+  } else if (diff < 3600 * 24 * 2) {
+    return '1天前'
+  }
+  if (option) {
+    return parseTime(time, option)
+  } else {
+    return (
+      d.getMonth() +
+      1 +
+      '月' +
+      d.getDate() +
+      '日' +
+      d.getHours() +
+      '时' +
+      d.getMinutes() +
+      '分'
+    )
+  }
+}
+
+/**
+ * @param {string} url
+ * @returns {Object}
+ */
+export function param2Obj(url) {
+  const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
+  if (!search) {
+    return {}
+  }
+  const obj = {}
+  const searchArr = search.split('&')
+  searchArr.forEach(v => {
+    const index = v.indexOf('=')
+    if (index !== -1) {
+      const name = v.substring(0, index)
+      const val = v.substring(index + 1, v.length)
+      obj[name] = val
+    }
+  })
+  return obj
+}

+ 40 - 0
src/utils/publicMethods.js

@@ -0,0 +1,40 @@
+/**
+ * table/complex-table
+ *  列表json转换
+ * @param {*} filterVal //对应字段
+ * @param {*} jsonData //json列表
+ */
+export function formatJson (filterVal, jsonData) {
+  return jsonData.map(v =>
+    filterVal.map(j => {
+      return v[j]
+    })
+  )
+}
+/**
+ * 获取时间凌晨(秒)
+ * @param {*} s //推移几天
+ */
+export function getMorningTime (passTime) {
+  let miao = passTime * 1000 * 60 * 60 * 24
+  return new Date(new Date().toLocaleDateString()).getTime() + miao
+}
+
+/**
+ * 时间处理
+ * @param {*} dataForm //post参数
+ * @param {*} timeStamp //需要处理的时间戳
+ */
+export function switchingTime (dataForm, timeStamp) {
+  let params = Object.assign({}, dataForm)
+  if (params) {
+    if (params.startTime === 0 && params.endTime === 0) {
+      params.startTime = ""
+      params.endTime = ""
+    } else if (params.startTime && params.endTime) {
+      params.startTime = params.startTime + ""
+      params.endTime = (parseInt(params.endTime) + timeStamp) + ""
+    }
+  }
+  return params
+}

+ 85 - 0
src/utils/request.js

@@ -0,0 +1,85 @@
+import axios from 'axios'
+import { MessageBox, Message } from 'element-ui'
+import store from '@/store'
+import { getToken } from '@/utils/auth'
+
+// create an axios instance
+const service = axios.create({
+  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
+  // withCredentials: true, // send cookies when cross-domain requests
+  timeout: 5000 // request timeout
+})
+
+// request interceptor
+service.interceptors.request.use(
+  config => {
+    // do something before request is sent
+
+    if (store.getters.token) {
+      // let each request carry token
+      // ['X-Token'] is a custom headers key
+      // please modify it according to the actual situation
+      config.headers['X-Token'] = getToken()
+    }
+    return config
+  },
+  error => {
+    // do something with request error
+    console.log(error) // for debug
+    return Promise.reject(error)
+  }
+)
+
+// response interceptor
+service.interceptors.response.use(
+  /**
+   * If you want to get http information such as headers or status
+   * Please return  response => response
+  */
+
+  /**
+   * Determine the request status by custom code
+   * Here is just an example
+   * You can also judge the status by HTTP Status Code
+   */
+  response => {
+    const res = response.data
+
+    // if the custom code is not 20000, it is judged as an error.
+    if (res.code !== 20000) {
+      Message({
+        message: res.message || 'Error',
+        type: 'error',
+        duration: 5 * 1000
+      })
+
+      // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
+      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
+        // to re-login
+        MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
+          confirmButtonText: 'Re-Login',
+          cancelButtonText: 'Cancel',
+          type: 'warning'
+        }).then(() => {
+          store.dispatch('user/resetToken').then(() => {
+            location.reload()
+          })
+        })
+      }
+      return Promise.reject(new Error(res.message || 'Error'))
+    } else {
+      return res
+    }
+  },
+  error => {
+    console.log('err' + error) // for debug
+    Message({
+      message: error.message,
+      type: 'error',
+      duration: 5 * 1000
+    })
+    return Promise.reject(error)
+  }
+)
+
+export default service

+ 146 - 0
src/utils/static-json.js

@@ -0,0 +1,146 @@
+export default {
+    sex: [
+        { id: 1, name: "男" },
+        { id: 2, name: "女" }
+    ],//性别
+    marriage: [
+        { id: 1, name: "未婚" },
+        { id: 2, name: "已婚" }
+    ],//婚姻情况
+    degree: [
+        { id: 1, name: "博士" },
+        { id: 2, name: "硕士" },
+        { id: 3, name: "本科" },
+        { id: 4, name: "专科" },
+        { id: 5, name: "专科以下" }
+    ],//文化程度
+    statusListAll: [
+        {
+            id: 0,
+            name: "状态不限"
+        },
+        {
+            id: 1,
+            name: "未分配",
+        },
+        {
+            id: 2,
+            name: "已分配",
+        },
+        {
+            id: 3,
+            name: "已接诊",
+        },
+        {
+            id: 4,
+            name: "已完成",
+        },
+        {
+            id: 5,
+            name: "未到诊",
+        },
+        {
+            id: 6,
+            name: "注销约诊",
+        },
+    ],
+    //订单状态
+    statusList: [
+        {
+            id: 1,
+            name: "未分配",
+        },
+        {
+            id: 2,
+            name: "已分配",
+        },
+        {
+            id: 3,
+            name: "已接诊",
+        },
+        {
+            id: 4,
+            name: "已完成",
+        },
+        {
+            id: 5,
+            name: "未到诊",
+        },
+        {
+            id: 6,
+            name: "注销约诊",
+        },
+    ],
+    customerLevelAll: [{
+        id: 0,
+        name: "等级不限",
+    },
+    { id: 1, name: "黑钻卡" },
+    { id: 2, name: "私钻卡" },
+    { id: 3, name: "尊钻卡" },
+    { id: 4, name: "钻石卡" },
+    ],//用户等级/会员级别
+    customerLevel: [
+        { id: 1, name: "黑钻卡" },
+        { id: 2, name: "私钻卡" },
+        { id: 3, name: "尊钻卡" },
+        { id: 4, name: "钻石卡" },
+    ],//用户等级/会员级别
+
+    dosageForm: [
+        { id: 1, name: "汤剂" },
+        { id: 2, name: "散剂" },
+        { id: 3, name: "丸剂" },
+        { id: 4, name: "膏剂" },
+        { id: 5, name: "颗粒剂" }
+    ],//剂型
+
+    administration: [
+        { id: 1, name: "内服" },
+        { id: 2, name: "外用" }
+    ],//给药途径
+
+    medicationTime: [
+        { id: 1, name: "饭后" },
+        { id: 2, name: "饭前" },
+        { id: 3, name: "晨起服" },
+        { id: 4, name: "空腹服" },
+        { id: 5, name: "睡前服用" }
+    ],//用药时间
+
+    decoctionTime: [{
+        id: 1,
+        name: "一煎",
+        desc: "(建议20-45分钟)"
+    }, {
+        id: 2,
+        name: "二煎",
+        desc: "(建议15-20分钟)"
+    }, {
+        id: 3,
+        name: "三煎",
+        desc: ""
+    }],//煎煮时间
+
+    avoidCertainFood: [
+        { id: 1, name: "生冷食物" },
+        { id: 2, name: "肉蛋奶" },
+        { id: 3, name: "荤腥" },
+        { id: 4, name: "酸涩" },
+        { id: 5, name: "辛辣刺激性食物" },
+        { id: 6, name: "光敏性食物" },
+        { id: 7, name: "难消化食物" }
+    ],//忌口
+
+    Taboo: [
+        { id: 1, name: "忌烟酒" },
+        { id: 2, name: "忌熬夜" },
+        { id: 3, name: "忌性生活" },
+        { id: 4, name: "备孕禁服" },
+        { id: 5, name: "怀孕禁服" },
+        { id: 6, name: "感冒停服" },
+        { id: 7, name: "经期停服" },
+        { id: 8, name: "忌与西药同服" }
+    ],//禁忌
+
+}

+ 63 - 0
src/utils/tree.utils.js

@@ -0,0 +1,63 @@
+
+export function getDefaultContent (h, data, node) {
+  let self = this
+  return (
+    <div class="ly-visible">
+      {
+        self.is_superuser && data.type == '0' &&
+        (<span>
+          <el-button
+            size="mini"
+            type="text"
+            on-click={() => self.update(node, data)}
+          >
+            编辑
+          </el-button>
+
+          <el-button
+            size="mini"
+            type="text"
+            on-click={() => self.remove(node, data, "0")}
+          >
+            删除
+            </el-button>
+
+          {
+            node.level == 1 &&
+            <el-button
+              size="mini"
+              type="text"
+              on-click={() => self.append(node, data)}
+            >
+              新增岗位
+            </el-button>
+          }
+
+          {
+            node.level == 2 &&
+            <el-button
+              size="mini"
+              type="text"
+              on-click={() => self.copyto(node, data)}
+            >
+              复制到岗位胜任力
+            </el-button>
+          }
+        </span>)
+      }
+
+      {
+        self.is_superuser && data.type == '1' &&
+        (<span>
+          <el-button
+            size="mini"
+            type="text"
+            on-click={() => self.remove(node, data, "1")}
+          >
+            删除
+            </el-button>
+        </span>)
+      }
+    </div>
+  )
+}

+ 87 - 0
src/utils/validate.js

@@ -0,0 +1,87 @@
+/**
+ * Created by PanJiaChen on 16/11/18.
+ */
+
+/**
+ * @param {string} path
+ * @returns {Boolean}
+ */
+export function isExternal(path) {
+  return /^(https?:|mailto:|tel:)/.test(path)
+}
+
+/**
+ * @param {string} str
+ * @returns {Boolean}
+ */
+export function validUsername(str) {
+  const valid_map = ['admin', 'editor']
+  return valid_map.indexOf(str.trim()) >= 0
+}
+
+/**
+ * 邮箱
+ * @param {*} s
+ */
+ export function isEmail (s) {
+  return /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((.[a-zA-Z0-9_-]{2,3}){1,2})$/.test(s)
+}
+
+/**
+ * 手机号码
+ * @param {*} s
+ */
+export function isMobile (s) {
+  return /^1[3|4|5|6|7|8|9][0-9]\d{8}$/.test(s)
+}
+
+/**
+ * 电话号码
+ * @param {*} s
+ */
+export function isPhone (s) {
+  return /^([0-9]{3,4}-)?[0-9]{7,8}$/.test(s)
+}
+/**
+ * 身份证号码
+ * @param {*} s
+ */
+export function isIDentityCard (s) {
+  return /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(s)
+}
+
+/**
+ * URL地址
+ * @param {*} s
+ */
+export function isURL (s) {
+  return /^http[s]?:\/\/.*/.test(s)
+}
+
+/**
+ * 汉语
+ * @param {*} s
+ */
+export function isChinese (s) {
+  return /[\u4e00-\u9fa5]$/.test(s)
+}
+
+/**
+ * 数字 
+ * @param {*} s
+ */
+export function isnumber (s) {
+  return /^[0-9]*$/.test(s)
+}
+
+/**
+ * 表情包
+ * @param {*} s
+ */
+ export function isEmoticon(s) {
+  let reg = /[^\u0020-\u007E\u00A0-\u00BE\u2E80-\uA4CF\uF900-\uFAFF\uFE30-\uFE4F\uFF00-\uFFEF\u0080-\u009F\u2000-\u201f\u2026\u2022\u20ac\r\n]/g;
+  //console.log(reg.test(s));
+  return reg.test(s);
+}
+
+

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