LAPTOP-FO2T5SIU\35838 9 meses atrás
pai
commit
d49255b3b5
100 arquivos alterados com 3302 adições e 13 exclusões
  1. 14 0
      .editorconfig
  2. 14 0
      .env.development
  3. 5 0
      .env.production
  4. 7 0
      .env.staging
  5. 4 0
      .eslintignore
  6. 374 0
      .eslintrc.js
  7. 15 12
      .gitignore
  8. 5 0
      .travis.yml
  9. 21 0
      LICENSE
  10. 76 0
      README-zh.md
  11. 75 1
      README.md
  12. 5 0
      babel.config.js
  13. 35 0
      build/index.js
  14. 24 0
      jest.config.js
  15. 66 0
      mock/index.js
  16. 68 0
      mock/mock-server.js
  17. 29 0
      mock/table.js
  18. 85 0
      mock/user.js
  19. 0 0
      output.js
  20. 81 0
      package.json
  21. 8 0
      postcss.config.js
  22. BIN
      public/base.ico
  23. BIN
      public/favicon.ico
  24. 44 0
      public/index.html
  25. BIN
      public/logo.png
  26. 11 0
      src/App.vue
  27. 68 0
      src/api/user.js
  28. BIN
      src/assets/404_images/404.png
  29. BIN
      src/assets/404_images/404_cloud.png
  30. 70 0
      src/components/Breadcrumb/index.vue
  31. 534 0
      src/components/FullCalendar/comp/body.vue
  32. 11 0
      src/components/FullCalendar/comp/eventCard.vue
  33. 127 0
      src/components/FullCalendar/comp/header.vue
  34. 122 0
      src/components/FullCalendar/fullCalendar.vue
  35. 5 0
      src/components/FullCalendar/index.js
  36. 92 0
      src/components/FullCalendar/js/dateFunc.js
  37. 18 0
      src/components/FullCalendar/js/langSets.js
  38. 52 0
      src/components/Hamburger/index.vue
  39. 1 0
      src/components/Hamburger/moreSvg.svg
  40. 1 0
      src/components/Hamburger/selMore.svg
  41. 100 0
      src/components/Pagination/index.vue
  42. 140 0
      src/components/PanThumb/index.vue
  43. 179 0
      src/components/QuillEditor/index.vue
  44. 145 0
      src/components/RightPanel/index.vue
  45. 65 0
      src/components/Screenfull/index.vue
  46. 1 0
      src/components/Screenfull/screen.svg
  47. 61 0
      src/components/SvgIcon/index.vue
  48. 111 0
      src/components/TextHoverEffect/Mallki.vue
  49. 175 0
      src/components/ThemePicker/index.vue
  50. 49 0
      src/directive/clipboard/clipboard.js
  51. 13 0
      src/directive/clipboard/index.js
  52. 13 0
      src/directive/permission/index.js
  53. 29 0
      src/directive/permission/permission.js
  54. 9 0
      src/icons/index.js
  55. 1 0
      src/icons/svg/404.svg
  56. 1 0
      src/icons/svg/bug.svg
  57. 1 0
      src/icons/svg/chart.svg
  58. 1 0
      src/icons/svg/clipboard.svg
  59. 1 0
      src/icons/svg/component.svg
  60. 1 0
      src/icons/svg/dashboard.svg
  61. 1 0
      src/icons/svg/documentation.svg
  62. 1 0
      src/icons/svg/drag.svg
  63. 1 0
      src/icons/svg/edit.svg
  64. 1 0
      src/icons/svg/education.svg
  65. 1 0
      src/icons/svg/email.svg
  66. 1 0
      src/icons/svg/example.svg
  67. 1 0
      src/icons/svg/excel.svg
  68. 1 0
      src/icons/svg/exit-fullscreen.svg
  69. 1 0
      src/icons/svg/eye-open.svg
  70. 1 0
      src/icons/svg/eye.svg
  71. 1 0
      src/icons/svg/form.svg
  72. 1 0
      src/icons/svg/fullscreen.svg
  73. 1 0
      src/icons/svg/guide.svg
  74. 1 0
      src/icons/svg/icon.svg
  75. 1 0
      src/icons/svg/international.svg
  76. 1 0
      src/icons/svg/language.svg
  77. 1 0
      src/icons/svg/link.svg
  78. 1 0
      src/icons/svg/list.svg
  79. 1 0
      src/icons/svg/lock.svg
  80. 1 0
      src/icons/svg/message.svg
  81. 1 0
      src/icons/svg/money.svg
  82. 1 0
      src/icons/svg/nested.svg
  83. 1 0
      src/icons/svg/password.svg
  84. 1 0
      src/icons/svg/pdf.svg
  85. 1 0
      src/icons/svg/people.svg
  86. 1 0
      src/icons/svg/peoples.svg
  87. 1 0
      src/icons/svg/qq.svg
  88. 1 0
      src/icons/svg/search.svg
  89. 1 0
      src/icons/svg/shopping.svg
  90. 1 0
      src/icons/svg/size.svg
  91. 1 0
      src/icons/svg/skill.svg
  92. 1 0
      src/icons/svg/star.svg
  93. 1 0
      src/icons/svg/tab.svg
  94. 1 0
      src/icons/svg/table.svg
  95. 1 0
      src/icons/svg/theme.svg
  96. 1 0
      src/icons/svg/tree-table.svg
  97. 1 0
      src/icons/svg/tree.svg
  98. 1 0
      src/icons/svg/user.svg
  99. 1 0
      src/icons/svg/wechat.svg
  100. 0 0
      src/icons/svg/zip.svg

+ 14 - 0
.editorconfig

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

+ 14 - 0
.env.development

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

+ 5 - 0
.env.production

@@ -0,0 +1,5 @@
+# just a flag
+ENV = 'production'
+
+# base api
+VUE_APP_WEB = 'http://47.113.200.81:9005'

+ 7 - 0
.env.staging

@@ -0,0 +1,7 @@
+NODE_ENV = production
+
+# just a flag
+ENV = 'staging'
+
+# base api
+VUE_APP_WEB = 'http://localhost:9001'

+ 4 - 0
.eslintignore

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

+ 374 - 0
.eslintrc.js

@@ -0,0 +1,374 @@
+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, 4, {
+          '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': 1,
+        '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'],
+
+        // "no-alert": 0,//禁止使用alert confirm prompt
+        // "no-array-constructor": 2,//禁止使用数组构造器
+        // "no-bitwise": 0,//禁止使用按位运算符
+        // "no-caller": 1,//禁止使用arguments.caller或arguments.callee
+        // "no-catch-shadow": 2,//禁止catch子句参数与外部作用域变量同名
+        // "no-class-assign": 2,//禁止给类赋值
+        // "no-cond-assign": 2,//禁止在条件表达式中使用赋值语句
+        // "no-console": 2,//禁止使用console
+        // "no-const-assign": 2,//禁止修改const声明的变量
+        // "no-constant-condition": 2,//禁止在条件中使用常量表达式 if(true) if(1)
+        // "no-continue": 0,//禁止使用continue
+        // "no-control-regex": 2,//禁止在正则表达式中使用控制字符
+        // "no-debugger": 2,//禁止使用debugger
+        // "no-delete-var": 2,//不能对var声明的变量使用delete操作符
+        // "no-div-regex": 1,//不能使用看起来像除法的正则表达式/=foo/
+        // "no-dupe-keys": 2,//在创建对象字面量时不允许键重复 {a:1,a:1}
+        // "no-dupe-args": 2,//函数参数不能重复
+        // "no-duplicate-case": 2,//switch中的case标签不能重复
+        // "no-else-return": 2,//如果if语句里面有return,后面不能跟else语句
+        // "no-empty": 2,//块语句中的内容不能为空
+        // "no-empty-character-class": 2,//正则表达式中的[]内容不能为空
+        // "no-empty-label": 2,//禁止使用空label
+        // "no-eq-null": 2,//禁止对null使用==或!=运算符
+        // "no-eval": 1,//禁止使用eval
+        // "no-ex-assign": 2,//禁止给catch语句中的异常参数赋值
+        // "no-extend-native": 2,//禁止扩展native对象
+        // "no-extra-bind": 2,//禁止不必要的函数绑定
+        // "no-extra-boolean-cast": 2,//禁止不必要的bool转换
+        // "no-extra-parens": 2,//禁止非必要的括号
+        // "no-extra-semi": 2,//禁止多余的冒号
+        // "no-fallthrough": 1,//禁止switch穿透
+        // "no-floating-decimal": 2,//禁止省略浮点数中的0 .5 3.
+        // "no-func-assign": 2,//禁止重复的函数声明
+        // "no-implicit-coercion": 1,//禁止隐式转换
+        // "no-implied-eval": 2,//禁止使用隐式eval
+        // "no-inline-comments": 0,//禁止行内备注
+        // "no-inner-declarations": [2, "functions"],//禁止在块语句中使用声明(变量或函数)
+        // "no-invalid-regexp": 2,//禁止无效的正则表达式
+        // "no-invalid-this": 2,//禁止无效的this,只能用在构造器,类,对象字面量
+        // "no-irregular-whitespace": 2,//不能有不规则的空格
+        // "no-iterator": 2,//禁止使用__iterator__ 属性
+        // "no-label-var": 2,//label名不能与var声明的变量名相同
+        // "no-labels": 2,//禁止标签声明
+        // "no-lone-blocks": 2,//禁止不必要的嵌套块
+        // "no-lonely-if": 2,//禁止else语句内只有if语句
+        // "no-loop-func": 1,//禁止在循环中使用函数(如果没有引用外部变量不形成闭包就可以)
+        // "no-mixed-requires": [0, false],//声明时不能混用声明类型
+        // "no-mixed-spaces-and-tabs": [2, false],//禁止混用tab和空格
+        // "linebreak-style": [0, "windows"],//换行风格
+        // "no-multi-spaces": 1,//不能用多余的空格
+        // "no-multi-str": 2,//字符串不能用\换行
+        // "no-multiple-empty-lines": [1, {"max": 2}],//空行最多不能超过2行
+        // "no-native-reassign": 2,//不能重写native对象
+        // "no-negated-in-lhs": 2,//in 操作符的左边不能有!
+        // "no-nested-ternary": 0,//禁止使用嵌套的三目运算
+        // "no-new": 1,//禁止在使用new构造一个实例后不赋值
+        // "no-new-func": 1,//禁止使用new Function
+        // "no-new-object": 2,//禁止使用new Object()
+        // "no-new-require": 2,//禁止使用new require
+        // "no-new-wrappers": 2,//禁止使用new创建包装实例,new String new Boolean new Number
+        // "no-obj-calls": 2,//不能调用内置的全局对象,比如Math() JSON()
+        // "no-octal": 2,//禁止使用八进制数字
+        // "no-octal-escape": 2,//禁止使用八进制转义序列
+        // "no-param-reassign": 2,//禁止给参数重新赋值
+        // "no-path-concat": 0,//node中不能使用__dirname或__filename做路径拼接
+        // "no-plusplus": 0,//禁止使用++,--
+        // "no-process-env": 0,//禁止使用process.env
+        // "no-process-exit": 0,//禁止使用process.exit()
+        // "no-proto": 2,//禁止使用__proto__属性
+        // "no-redeclare": 2,//禁止重复声明变量
+        // "no-regex-spaces": 2,//禁止在正则表达式字面量中使用多个空格 /foo bar/
+        // "no-restricted-modules": 0,//如果禁用了指定板块,使用就会报错
+        // "no-return-assign": 1,//return 语句中不能有赋值表达式
+        // "no-script-url": 0,//禁止使用javascript:void(0)
+        // "no-self-compare": 2,//不能比较自身
+        // "no-sequences": 0,//禁止使用逗号运算符
+        // "no-shadow": 2,//外部作用域中的变量不能与它所包含的作用域中的变量或参数同名
+        // "no-shadow-restricted-names": 2,//严格模式中规定的限制标识符不能作为声明时的变量名使用
+        // "no-spaced-func": 2,//函数调用时 函数名与()之间不能有空格
+        // "no-sparse-arrays": 2,//禁止稀疏数组, [1,,2]
+        // "no-sync": 0,//nodejs 禁止同步方法
+        // "no-ternary": 0,//禁止使用三目运算符
+        // "no-trailing-spaces": 1,//一行结束后面不要有空格
+        // "no-this-before-super": 0,//在调用super()之前不能使用this或super
+        // "no-throw-literal": 2,//禁止抛出字面量错误 throw "error";
+        // "no-undef": 1,//不能有未定义的变量
+        // "no-undef-init": 2,//变量初始化时不能直接给它赋值为undefined
+        // "no-undefined": 2,//不能使用undefined
+        // "no-unexpected-multiline": 2,//避免多行表达式
+        // "no-underscore-dangle": 1,//标识符不能以_开头或结尾
+        // "no-unneeded-ternary": 2,//禁止不必要的嵌套 var isYes = answer === 1 ? true : false;
+        // "no-unreachable": 2,//不能有无法执行的代码
+        // "no-unused-expressions": 2,//禁止无用的表达式
+        // "no-unused-vars": [2, {"vars": "all", "args": "after-used"}],//不能有声明后未被使用的变量或参数
+        // "no-use-before-define": 2,//未定义前不能使用
+        // "no-useless-call": 2,//禁止不必要的call和apply
+        // "no-void": 2,//禁用void操作符
+        // "no-var": 0,//禁用var,用let和const代替
+        // "no-warning-comments": [1, {"terms": ["todo", "fixme", "xxx"], "location": "start"}],//不能有警告备注
+        // "no-with": 2,//禁用with
+        //
+        // "array-bracket-spacing": [2, "never"],//是否允许非空数组里面有多余的空格
+        // "arrow-parens": 0,//箭头函数用小括号括起来
+        // "arrow-spacing": 0,//=>的前/后括号
+        // "accessor-pairs": 0,//在对象中使用getter/setter
+        // "block-scoped-var": 0,//块语句中使用var
+        // "brace-style": [1, "1tbs"],//大括号风格
+        // "callback-return": 1,//避免多次调用回调什么的
+        // "camelcase": 2,//强制驼峰法命名
+        // "comma-dangle": [2, "never"],//对象字面量项尾不能有逗号
+        // "comma-spacing": 0,//逗号前后的空格
+        // "comma-style": [2, "last"],//逗号风格,换行时在行首还是行尾
+        // "complexity": [0, 11],//循环复杂度
+        // "computed-property-spacing": [0, "never"],//是否允许计算后的键名什么的
+        // "consistent-return": 0,//return 后面是否允许省略
+        // "consistent-this": [2, "that"],//this别名
+        // "constructor-super": 0,//非派生类不能调用super,派生类必须调用super
+        // "curly": [2, "all"],//必须使用 if(){} 中的{}
+        // "default-case": 2,//switch语句最后必须有default
+        // "dot-location": 0,//对象访问符的位置,换行的时候在行首还是行尾
+        // "dot-notation": [0, {"allowKeywords": true}],//避免不必要的方括号
+        // "eol-last": 0,//文件以单一的换行符结束
+        // "eqeqeq": 2,//必须使用全等
+        // "func-names": 0,//函数表达式必须有名字
+        // "func-style": [0, "declaration"],//函数风格,规定只能使用函数声明/函数表达式
+        // "generator-star-spacing": 0,//生成器函数*的前后空格
+        // "guard-for-in": 0,//for in循环要用if语句过滤
+        // "handle-callback-err": 0,//nodejs 处理错误
+        // "id-length": 0,//变量名长度
+        // "indent": [2, 4],//缩进风格
+        // "init-declarations": 0,//声明时必须赋初值
+        // "key-spacing": [0, {"beforeColon": false, "afterColon": true}],//对象字面量中冒号的前后空格
+        // "lines-around-comment": 0,//行前/行后备注
+        // "max-depth": [0, 4],//嵌套块深度
+        // "max-len": [0, 80, 4],//字符串最大长度
+        // "max-nested-callbacks": [0, 2],//回调嵌套深度
+        // "max-params": [0, 3],//函数最多只能有3个参数
+        // "max-statements": [0, 10],//函数内最多有几个声明
+        // "new-cap": 2,//函数名首行大写必须使用new方式调用,首行小写必须用不带new方式调用
+        // "new-parens": 2,//new时必须加小括号
+        // "newline-after-var": 2,//变量声明后是否需要空一行
+        // "object-curly-spacing": [0, "never"],//大括号内是否允许不必要的空格
+        // "object-shorthand": 0,//强制对象字面量缩写语法
+        // "one-var": 1,//连续声明
+        // "operator-assignment": [0, "always"],//赋值运算符 += -=什么的
+        // "operator-linebreak": [2, "after"],//换行时运算符在行尾还是行首
+        // "padded-blocks": 0,//块语句内行首行尾是否要空行
+        // "prefer-const": 0,//首选const
+        // "prefer-spread": 0,//首选展开运算
+        // "prefer-reflect": 0,//首选Reflect的方法
+        // "quotes": [1, "single"],//引号类型 `` "" ''
+        // "quote-props": [2, "always"],//对象字面量中的属性名是否强制双引号
+        // "radix": 2,//parseInt必须指定第二个参数
+        // "id-match": 0,//命名检测
+        // "require-yield": 0,//生成器函数必须有yield
+        // "semi": [2, "always"],//语句强制分号结尾
+        // "semi-spacing": [0, {"before": false, "after": true}],//分号前后空格
+        // "sort-vars": 0,//变量声明时排序
+        // "space-after-keywords": [0, "always"],//关键字后面是否要空一格
+        // "space-before-blocks": [0, "always"],//不以新行开始的块{前面要不要有空格
+        // "space-before-function-paren": [0, "always"],//函数定义时括号前面要不要有空格
+        // "space-in-parens": [0, "never"],//小括号里面要不要有空格
+        // "space-infix-ops": 0,//中缀操作符周围要不要有空格
+        // "space-return-throw-case": 2,//return throw case后面要不要加空格
+        // "space-unary-ops": [0, {"words": true, "nonwords": false}],//一元运算符的前/后要不要加空格
+        // "spaced-comment": 0,//注释风格要不要有空格什么的
+        // "strict": 2,//使用严格模式
+        // "use-isnan": 2,//禁止比较时使用NaN,只能用isNaN()
+        // "valid-jsdoc": 0,//jsdoc规则
+        // "valid-typeof": 2,//必须使用合法的typeof的值
+        // "vars-on-top": 2,//var必须放在作用域顶部
+        // "wrap-iife": [2, "inside"],//立即执行函数表达式的小括号风格
+        // "wrap-regex": 0,//正则表达式字面量用小括号包起来
+        // "yoda": [2, "never"]//禁止尤达条件
+
+    }
+}

+ 15 - 12
.gitignore

@@ -1,13 +1,16 @@
-# ---> Actionscript
-# Build and Release Folders
-bin/
-bin-debug/
-bin-release/
-
-# Other files and folders
-.settings/
-
-# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties`
-# should NOT be excluded as they contain compiler settings and other important
-# information for Eclipse / Flash Builder.
+.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.

+ 76 - 0
README-zh.md

@@ -0,0 +1,76 @@
+# Rock-Star 1.0 页面版
+
+> 这是一个Rock-Star系统体系的基础平台后台管理。它包含了 Element UI & axios & iconfont & permission control & lint,这些搭建后台必要的东西。
+
+目前版本为 `v4.0+` 基于 `vue-cli` 进行构建
+整个项目是基于panjiachen的vue-element-admin为基础进行构建的 [使用文档](https://panjiachen.github.io/vue-element-admin-site/zh/)
+
+## Extra
+
+如果你想要根据用户角色来动态生成侧边栏和 router,你可以使用该分支[permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control)
+
+## Build Setup
+
+```bash
+# 克隆项目
+git clone http://47.96.128.196:3000/pengyq/rock-web-base.git
+
+# 进入项目目录
+cd rock-web-base
+
+# 安装依赖
+npm install
+
+# 建议不要直接使用 cnpm 安装以来,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题
+npm install --registry=https://registry.npm.taobao.org
+
+# 启动服务
+npm run dev
+```
+
+浏览器访问 [http://localhost:9525](http://localhost:9525)
+
+## 发布
+
+```bash
+# 构建测试环境
+npm run build:stage
+
+# 构建生产环境
+npm run build:prod
+
+# 本地运行代码
+npm run dev
+```
+
+## 其它
+
+```bash
+# 预览发布环境效果
+npm run preview
+
+# 预览发布环境效果 + 静态资源分析
+npm run preview -- --report
+
+# 代码格式检查
+npm run lint
+
+# 代码格式检查并自动修复
+npm run lint -- --fix
+```
+
+更多信息请参考 [使用文档](https://panjiachen.github.io/vue-element-admin-site/zh/)
+
+## Browsers support
+
+Modern browsers and Internet Explorer 10+.
+
+| IE / Edge |   Firefox |    Chrome |   Safari  |
+| --------- | --------- | --------- | --------- |
+| IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions |
+
+## License
+
+PanJiaChen [MIT](https://github.com/PanJiaChen/vue-admin-template/blob/master/LICENSE) license.
+
+Copyright (c) 2020-present Rock-Idea

+ 75 - 1
README.md

@@ -1,2 +1,76 @@
-# settle_down_web
+# Rock-Star 1.0 页面版
 
+> 这是一个Rock-Star系统体系的基础平台后台管理。它包含了 Element UI & axios & iconfont & permission control & lint,这些搭建后台必要的东西。
+
+目前版本为 `v4.0+` 基于 `vue-cli` 进行构建
+整个项目是基于panjiachen的vue-element-admin为基础进行构建的 [使用文档](https://panjiachen.github.io/vue-element-admin-site/zh/)
+
+## Extra
+
+如果你想要根据用户角色来动态生成侧边栏和 router,你可以使用该分支[permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control)
+
+## Build Setup
+
+```bash
+# 克隆项目
+git clone http://47.96.128.196:3000/pengyq/rock-web-base.git
+
+# 进入项目目录
+cd rock-web-base
+
+# 安装依赖
+npm install
+
+# 建议不要直接使用 cnpm 安装以来,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题
+npm install --registry=https://registry.npm.taobao.org
+
+# 启动服务
+npm run dev
+```
+
+浏览器访问 [http://localhost:9525](http://localhost:9525)
+
+## 发布
+
+```bash
+# 构建测试环境
+npm run build:stage
+
+# 构建生产环境
+npm run build:prod
+
+# 本地运行代码
+npm run dev
+```
+
+## 其它
+
+```bash
+# 预览发布环境效果
+npm run preview
+
+# 预览发布环境效果 + 静态资源分析
+npm run preview -- --report
+
+# 代码格式检查
+npm run lint
+
+# 代码格式检查并自动修复
+npm run lint -- --fix
+```
+
+更多信息请参考 [使用文档](https://panjiachen.github.io/vue-element-admin-site/zh/)
+
+## Browsers support
+
+Modern browsers and Internet Explorer 10+.
+
+| IE / Edge |   Firefox |    Chrome |   Safari  |
+| --------- | --------- | --------- | --------- |
+| IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions |
+
+## License
+
+PanJiaChen [MIT](https://github.com/PanJiaChen/vue-admin-template/blob/master/LICENSE) license.
+
+Copyright (c) 2020-present Rock-Idea

+ 5 - 0
babel.config.js

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

+ 35 - 0
build/index.js

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

+ 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/static/utils/**/*.{js,vue}', '!src/static/utils/auth.js', 'src/components/**/*.{js,vue}'],
+    coverageDirectory: '<rootDir>/tests/unit/coverage',
+    // 'collectCoverage': true,
+    'coverageReporters': [
+        'lcov',
+        'text-summary'
+    ],
+    testURL: 'http://localhost/'
+}

+ 66 - 0
mock/index.js

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

+ 68 - 0
mock/mock-server.js

@@ -0,0 +1,68 @@
+const chokidar = require('chokidar')
+const bodyParser = require('body-parser')
+const chalk = require('chalk')
+const path = require('path')
+
+const mockDir = path.join(process.cwd(), 'mock')
+
+function registerRoutes(app) {
+    let mockLastIndex
+    const { default: mocks } = require('./index.js')
+    for (const mock of mocks) {
+        app[mock.type](mock.url, mock.response)
+        mockLastIndex = app._router.stack.length
+    }
+    const mockRoutesLength = Object.keys(mocks).length
+    return {
+        mockRoutesLength: mockRoutesLength,
+        mockStartIndex: mockLastIndex - mockRoutesLength
+    }
+}
+
+function unregisterRoutes() {
+    Object.keys(require.cache).forEach(i => {
+        if (i.includes(mockDir)) {
+            delete require.cache[require.resolve(i)]
+        }
+    })
+}
+
+module.exports = app => {
+    // es6 polyfill
+    require('@babel/register')
+
+    // parse app.body
+    // https://expressjs.com/en/4x/api.html#req.body
+    app.use(bodyParser.json())
+    app.use(bodyParser.urlencoded({
+        extended: true
+    }))
+
+    const mockRoutes = registerRoutes(app)
+    var mockRoutesLength = mockRoutes.mockRoutesLength
+    var mockStartIndex = mockRoutes.mockStartIndex
+
+    // watch files, hot reload mock server
+    chokidar.watch(mockDir, {
+        ignored: /mock-server/,
+        ignoreInitial: true
+    }).on('all', (event, path) => {
+        if (event === 'change' || event === 'add') {
+            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 @@
+import Mock from 'mockjs'
+
+const data = Mock.mock({
+    'items|30': [{
+        id: '@id',
+        title: '@sentence(10, 20)',
+        'status|1': ['published', 'draft', 'deleted'],
+        author: 'name',
+        display_time: '@datetime',
+        pageviews: '@integer(300, 5000)'
+    }]
+})
+
+export default [
+    {
+        url: '/table/list',
+        type: 'get',
+        response: config => {
+            const items = data.items
+            return {
+                code: 20000,
+                data: {
+                    total: items.length,
+                    items: items
+                }
+            }
+        }
+    }
+]

+ 85 - 0
mock/user.js

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

+ 0 - 0
output.js


+ 81 - 0
package.json

@@ -0,0 +1,81 @@
+{
+  "name": "rock_web_base",
+  "version": "1.4.0",
+  "description": "Idea base template based vue-admin-template",
+  "author": "Peng <peng.y.q@hotmail.com>",
+  "license": "MIT",
+  "scripts": {
+    "dev": "vue-cli-service serve",
+    "build:prod": "vue-cli-service build",
+    "build:stage": "vue-cli-service build --mode staging",
+    "preview": "node build/index.js --preview",
+    "lint": "eslint --ext .js,.vue src",
+    "test:unit": "jest --clearCache && vue-cli-service test:unit",
+    "test:ci": "npm run lint && npm run test:unit",
+    "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
+  },
+  "dependencies": {
+    "@packy-tang/vue-tinymce": "^1.1.2",
+    "axios": "^0.26.1",
+    "echarts": "^4.2.1",
+    "element-ui": "2.10.1",
+    "gg-editor": "^3.0.0-beta.3",
+    "js-cookie": "2.2.0",
+    "js-md5": "^0.7.3",
+    "normalize.css": "7.0.0",
+    "nprogress": "0.2.0",
+    "path-to-regexp": "2.4.0",
+    "qs.js": "^0.1.12",
+    "quill-image-extend-module": "^1.1.2",
+    "quill-image-resize-module": "^3.0.0",
+    "react": "^16.11.0",
+    "react-dom": "^16.13.0",
+    "screenfull": "^4.2.1",
+    "search-bar-vue2": "^0.2.1",
+    "speak-tts": "^2.0.8",
+    "tinymce": "^6.1.2",
+    "v-viewer": "^1.6.4",
+    "vue": "2.6.10",
+    "vue-count-to": "^1.0.13",
+    "vue-quill-editor": "^3.0.6",
+    "vue-router": "3.0.6",
+    "vuex": "3.1.0",
+    "xlsx": "^0.18.5"
+  },
+  "devDependencies": {
+    "@babel/core": "7.0.0",
+    "@babel/register": "7.0.0",
+    "@vue/cli-plugin-babel": "3.6.0",
+    "@vue/cli-plugin-eslint": "3.6.0",
+    "@vue/cli-plugin-unit-jest": "^3.8.0",
+    "@vue/cli-service": "3.6.0",
+    "@vue/test-utils": "1.0.0-beta.29",
+    "autoprefixer": "^9.5.1",
+    "babel-core": "7.0.0-bridge.0",
+    "babel-eslint": "8.0.1",
+    "babel-jest": "23.6.0",
+    "chalk": "2.4.2",
+    "connect": "3.6.6",
+    "eslint": "5.15.3",
+    "eslint-plugin-vue": "5.2.2",
+    "html-webpack-plugin": "3.2.0",
+    "mockjs": "1.0.1-beta3",
+    "node-sass": "^4.13.1",
+    "runjs": "^4.3.2",
+    "sass-loader": "^7.1.0",
+    "script-ext-html-webpack-plugin": "2.1.3",
+    "script-loader": "0.7.2",
+    "serve-static": "^1.13.2",
+    "svg-sprite-loader": "4.1.3",
+    "svgo": "1.2.2",
+    "vue-template-compiler": "2.6.10"
+  },
+  "engines": {
+    "node": ">=8.9",
+    "npm": ">= 3.0.0"
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions"
+  ]
+}

+ 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/base.ico


BIN
public/favicon.ico


+ 44 - 0
public/index.html

@@ -0,0 +1,44 @@
+<!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 %>base.ico">
+    <title><%= webpackConfig.name %></title>
+  </head>
+  <body>
+    <noscript>
+      <strong>We're sorry but <%= webpackConfig.name %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+    </noscript>
+    <div id="app"></div>
+    <!--<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>-->
+    <!--<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.0.6/vue-router.min.js"></script>-->
+    <!--<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.min.js"></script>-->
+    <!--<script src="https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.10.1/index.js"></script>-->
+    <!--<script src="https://cdnjs.cloudflare.com/ajax/libs/qs/6.5.2/qs.min.js"></script>-->
+
+    <!--<script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>-->
+    <!--<script src="https://cdn.bootcss.com/vue-router/3.0.6/vue-router.min.js"></script>-->
+    <!--<script src="https://cdn.bootcss.com/axios/0.19.0/axios.min.js"></script>-->
+    <!--<script src="https://cdn.bootcss.com/element-ui/2.10.1/index.js"></script>-->
+    <!--<script src="https://cdn.bootcss.com/qs/6.5.2/qs.min.js"></script>-->
+
+    <!--<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.10/vue.min.js"></script>-->
+    <!--<script src="https://cdn.bootcdn.net/ajax/libs/vue-router/3.0.6/vue-router.min.js"></script>-->
+    <!--<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.19.0/axios.min.js"></script>-->
+    <!--<script src="https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.2/index.js"></script>-->
+    <!--<script src="https://cdn.bootcdn.net/ajax/libs/qs/6.5.2/qs.min.js"></script>-->
+    <!--<script src="https://cdn.bootcdn.net/ajax/libs/xlsx/0.16.8/xlsx.js"></script>-->
+
+    <script src="http://cdn.idea-sf.com/vue/2.6.10/vue.min.js"></script>
+    <script src="http://cdn.idea-sf.com/vue-router/3.0.6/vue-router.min.js"></script>
+    <script src="http://cdn.idea-sf.com/axios/0.19.0/axios.min.js"></script>
+    <script src="http://cdn.idea-sf.com/element-ui/2.15.2/index.js"></script>
+    <script src="http://cdn.idea-sf.com/qs/6.5.2/qs.min.js"></script>
+    <script src="http://cdn.idea-sf.com/xlsx/0.16.8/xlsx.js"></script>
+    <script src="http://cdn.idea-sf.com/echarts/4.2.1/echarts.min.js"></script>
+    <script src="http://cdn.idea-sf.com/moments/2.20.1/moment.min.js"></script>
+    <!-- built files will be auto injected -->
+  </body>
+</html>

BIN
public/logo.png


+ 11 - 0
src/App.vue

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

+ 68 - 0
src/api/user.js

@@ -0,0 +1,68 @@
+import channel from '../static/utils/channel'
+
+const users = {
+    'admin-token': {
+        perms: ['admin'],
+        introduction: 'I am a super administrator',
+        avatar: require('../static/images/header.gif'),
+        name: 'Super Admin'
+        // perms: ['GET /admin/admin/list', 'GET /admin/role/list']
+    },
+    'editor-token': {
+        perms: ['editor'],
+        introduction: 'I am an editor',
+        avatar: require('../static/images/header.gif'),
+        name: 'Normal Editor'
+        // perms: []
+    }
+}
+
+export function getInfo(token) {
+    return new Promise((resolve, reject) => {
+        const info = users[token]
+        if (!info) {
+            const rej = {
+                code: 50008,
+                message: 'Login failed, unable to get user details.'
+            }
+            reject(rej)
+        } else {
+            const res = {
+                code: 20000,
+                data: info
+            }
+            resolve(res)
+            // debugger
+        }
+    })
+}
+
+export function logout() {
+    return new Promise((resolve, reject) => {
+        channel.baseRequest('web', 'logout', '', 'logout').then((res) => {
+            if (res) {
+                resolve(res)
+                return
+            }
+            reject('logout err')
+        }).catch((err, x) => {
+            reject(err)
+            console.log('api/user/logout', err, x)
+        })
+    })
+}
+
+export function unlockAdmin() {
+    return new Promise((resolve, reject) => {
+        channel.baseRequest('/api/pub', 'unlockFrameAdmin', '', 'unlock admin').then((res) => {
+            if (res) {
+                resolve(res)
+                return
+            }
+            reject('logout err')
+        }).catch((err, x) => {
+            reject(err)
+            console.log('api/pub/unlockFrameAdmin', err, x)
+        })
+    })
+}

BIN
src/assets/404_images/404.png


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


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

@@ -0,0 +1,70 @@
+<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() {
+            const matched = this.$route.matched.filter(item => item.meta && item.meta.title)
+            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) {
+            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>

+ 534 - 0
src/components/FullCalendar/comp/body.vue

@@ -0,0 +1,534 @@
+<template>
+  <div class="full-calendar-body">
+    <div class="weeks">
+      <strong v-for="(week, index) in weekNames" :key="index" class="week">{{ week }}</strong>
+    </div>
+    <div ref="dates" class="dates">
+      <!-- absolute so we can make dynamic td -->
+      <div class="dates-events">
+        <div
+          v-for="(week, index) in currentDates"
+          :key="index"
+          class="events-week"
+        >
+          <div
+            v-for="(day, index) in week"
+            :key="index"
+            class="events-day"
+            track-by="$index"
+            :class="{
+              today: day.isToday,
+              'not-cur-month': !day.isCurMonth,
+              eventBg: day.events.length > 0,
+              clickDay: day.date === selectedDay,
+            }"
+            @click.stop="eventClick(day.date, day.events, $event)"
+          >
+            <el-button v-if="day.date === selectedDay && day.events.length>0" class="day-close" size="mini" icon="el-icon-delete" circle @click.stop="eventDelete(day.events)"/>
+            <div class="event-box">
+              <p
+                v-for="(event, index) in day.events"
+                v-show="event.cellIndex <= eventLimit"
+                :key="index"
+                class="event-item"
+                :class="[
+                  classNames(event.cssClass),
+                  {
+                    'is-start': isStart(event.start, day.date),
+                    'is-end': isEnd(event.end, day.date),
+                    'is-opacity': !event.isShow,
+                    'is-work':event.holidayFlag/1==1
+                  },
+                ]"
+              >
+                {{ isBegin(event, day.date, day.weekDay) }}
+
+                <span v-if="event.holidayName">{{ event.holidayName?event.holidayName:'' }}</span>
+              </p>
+              <p
+                v-if="day.events.length > eventLimit"
+                class="more-link"
+                @click.stop="selectThisDay(day, $event)"
+              >
+                + {{ day.events[day.events.length - 1].cellIndex - eventLimit }} more
+              </p>
+            </div>
+            <p class="day-number" :class="{ eventDay: day.events.length > 0,'is-work':day.events.length > 0&&day.events[0].holidayFlag/1==1 }">
+              {{ day.monthDay }}
+            </p>
+          </div>
+        </div>
+      </div>
+
+      <!-- <div class="dates-bg">
+              <div class="week-row" v-for="week in currentDates">
+                <div class="day-cell" v-for="day in week"
+                  :class="{'today' : day.isToday,
+                    'not-cur-month' : !day.isCurMonth}">
+                  <p v-if="day.events.length === 0" class="day-number">{{day.monthDay}}</p>
+                </div>
+              </div>
+            </div> -->
+
+      <!-- full events when click show more -->
+      <div
+        v-show="showMore"
+        class="more-events"
+        :style="{ left: morePos.left + 'px', top: morePos.top + 'px' }"
+      >
+        <div class="more-header">
+          <span class="title">{{ moreTitle(selectDay.date) }}</span>
+          <span class="close" @click.stop="showMore = false">x</span>
+        </div>
+        <div class="more-body">
+          <ul class="body-list">
+            <li
+              v-for="(event, index) in selectDay.events"
+              v-show="event.isShow"
+              :id="index"
+              :key="index"
+              class="body-item"
+              @click="eventClick(event.holidayDate, event, $event)"
+            >
+              {{ event.holidayName }}
+            </li>
+          </ul>
+        </div>
+      </div>
+
+      <slot name="body-card" />
+    </div>
+  </div>
+</template>
+<script type="text/babel">
+import dateFunc from '../js/dateFunc'
+export default {
+    props: {
+        currentDate: {},
+        events: {
+            type: Array
+        },
+        weekNames: {
+            type: Array,
+            required: () => []
+        },
+        monthNames: {},
+        firstDay: {}
+    },
+    data() {
+        return {
+            // weekNames : DAY_NAMES,
+            weekMask: [1, 2, 3, 4, 5, 6, 7],
+            // events : [],
+            isLismit: true,
+            eventLimit: 1,
+            showMore: false,
+            morePos: {
+                top: 0,
+                left: 0
+            },
+            selectDay: {},
+            eventTitle: '休',
+            selectedDay: ''
+        }
+    },
+    computed: {
+        currentDates() {
+            return this.getCalendar()
+        }
+    },
+    watch: {
+        weekNames(val) {
+            console.log('watch weekNames', val)
+        },
+        events(newVal, oldVal) {
+            if (newVal.length > 0) {
+                this.handleEvents()
+            }
+        }
+    },
+    created() {
+        // setTimeout(() => {
+        //   }, 1000)
+        this.handleEvents()
+        // this.events = events
+    },
+    methods: {
+        handleEvents() {
+            this.events.forEach((item, index) => {
+                item._id = item.id || index
+                item.start = item.holidayDate
+                item.end = item.start || item.holidayDate
+            })
+            // console.log(this.events)
+        },
+        isBegin(event, date, index) {
+            //  console.log(event)
+            const st = new Date(event.start)
+            if (index === 0 || st.toDateString() === date.toDateString()) {
+                if (event.holidayFlag / 1 === 0) {
+                    return this.eventTitle
+                } else if (event.holidayFlag / 1 === 1) {
+                    return '班'
+                }
+            }
+            return ' '
+        },
+        moreTitle(date) {
+            const dt = new Date(date)
+            return (
+                this.weekNames[dt.getDay() - 1] +
+                    ', ' +
+                    this.monthNames[dt.getMonth()] +
+                    dt.getDate()
+            )
+        },
+        classNames(cssClass) {
+            // console.log(cssClass)
+            if (!cssClass) return ''
+            // string
+            if (typeof cssClass === 'string') return cssClass
+
+            // Array
+            if (Array.isArray(cssClass)) return cssClass.join(' ')
+
+            // else
+            return ''
+        },
+        getCalendar() {
+            // calculate 2d-array of each month
+            // first day of this month
+            const now = new Date() // today
+            const current = new Date(this.currentDate)
+
+            const startDate = dateFunc.getStartDate(current) // 1st day of this month
+
+            const curWeekDay = startDate.getDay()
+            // begin date of this table may be some day of last month
+            let diff = parseInt(this.firstDay) - curWeekDay
+            diff = diff > 0 ? diff - 7 : diff
+
+            startDate.setDate(startDate.getDate() + diff)
+            const calendar = []
+
+            for (let perWeek = 0; perWeek < 6; perWeek++) {
+                const week = []
+
+                for (let perDay = 0; perDay < 7; perDay++) {
+                    week.push({
+                        monthDay: startDate.getDate(),
+                        isToday: now.toDateString() === startDate.toDateString(),
+                        isCurMonth: startDate.getMonth() === current.getMonth(),
+                        weekDay: perDay,
+                        date: new Date(startDate),
+                        events: this.slotEvents(startDate)
+                    })
+                    startDate.setDate(startDate.getDate() + 1)
+                    // if (startDate.toDateString() === endDate.toDateString()) {
+                    //   isFinal = true
+                    //   break
+                    // }
+                }
+
+                calendar.push(week)
+            }
+            return calendar
+        },
+        slotEvents(date) {
+            // find all events start from this date
+            const thisDayEvents = this.events.filter((day) => {
+                const dt = new Date(day.start)
+                const st = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate())
+                const ed = day.end ? new Date(day.end) : st
+                // console.log('slotEvt', st, ed, date)
+                return date >= st && date <= ed
+            })
+
+            // sort by duration
+            thisDayEvents.sort((a, b) => {
+                if (!a.cellIndex) return 1
+                if (!b.cellIndex) return -1
+                return a.cellIndex - b.cellIndex
+            })
+
+            // mark cellIndex and place holder
+            for (let i = 0; i < thisDayEvents.length; i++) {
+                thisDayEvents[i].cellIndex = thisDayEvents[i].cellIndex || i + 1
+                thisDayEvents[i].isShow = true
+                if (thisDayEvents[i].cellIndex === i + 1 || i > 2) continue
+                thisDayEvents.splice(i, 0, {
+                    title: 'holder',
+                    cellIndex: i + 1,
+                    start: dateFunc.format(date, 'yyyy-MM-dd'),
+                    end: dateFunc.format(date, 'yyyy-MM-dd'),
+                    isShow: false
+                })
+            }
+
+            return thisDayEvents
+        },
+        isStart(eventDate, date) {
+            const st = new Date(eventDate)
+            return st.toDateString() === date.toDateString()
+        },
+        isEnd(eventDate, date) {
+            const ed = new Date(eventDate)
+            return ed.toDateString() === date.toDateString()
+        },
+        selectThisDay(day, jsEvent) {
+            this.selectDay = day
+            this.showMore = true
+            this.morePos = this.computePos(event.target)
+            this.morePos.top -= 100
+            const events = day.events.filter((item) => {
+                return item.isShow === true
+            })
+            this.$emit('moreclick', day.date, events, jsEvent)
+        },
+        computePos(target) {
+            const eventRect = target.getBoundingClientRect()
+            const pageRect = this.$refs.dates.getBoundingClientRect()
+            return {
+                left: eventRect.left - pageRect.left,
+                top: eventRect.top + eventRect.height - pageRect.top
+            }
+        },
+        dayClick(day, event) {
+            this.$emit('dayclick', day, event)
+        },
+        eventClick(date, event, jsEvent) {
+            // console.log(date, event, jsEvent)
+            // if (!event.isShow) {
+            //   return
+            // }
+            // jsEvent.target.classList.add("clickDay");
+            this.selectedDay = date
+            if (jsEvent) {
+                jsEvent.stopPropagation()
+            }
+            this.$emit('eventclick', date, event, jsEvent, (jsEvent ? this.computePos(jsEvent.target) : ''))
+        },
+        eventDelete(event) {
+            this.$emit('eventdelete', event)
+        }
+    }
+}
+</script>
+<style lang="scss">
+    .full-calendar-body {
+        margin-top: 20px;
+        .weeks {
+            height: 30px;
+            line-height: 30px;
+            display: flex;
+            border-top: 1px solid #dfe6ec;
+            border-bottom: 1px solid #dfe6ec;
+            border-left: 1px solid #dfe6ec;
+            .week {
+                flex: 1;
+                text-align: center;
+                border-right: 1px solid #dfe6ec;
+            }
+        }
+        .dates {
+            position: relative;
+            .week-row {
+                // width: 100%;
+                // position:absolute;
+                border-left: 1px solid #dfe6ec;
+                display: flex;
+                .day-cell {
+                    flex: 1;
+                    min-height: 90px;
+                    padding: 4px;
+                    border-right: 1px solid #dfe6ec;
+                    border-bottom: 1px solid #dfe6ec;
+                    .day-number {
+                        text-align: center;
+                        font-size: 20px;
+                        font-weight: bold;
+                        height: 100%;
+                        display: flex;
+                        justify-content: center;
+                        align-items: center;
+                    }
+                    &.today {
+                        background-color: #fcf8e3;
+                    }
+                    &.not-cur-month {
+                        .day-number {
+                            color: rgba(0, 0, 0, 0.24);
+                        }
+                    }
+                }
+            }
+            .dates-events {
+                // position:absolute;
+                position: relative;
+                top: 0;
+                left: 0;
+                z-index: 1;
+                width: 100%;
+                border-left: 1px solid #dfe6ec;
+                .events-week {
+                    display: flex;
+                    border-bottom: 1px solid #dfe6ec;
+                    .events-day:hover {
+                        background-color: rgba(102, 153, 255, 0.1);
+                    }
+                    .events-day {
+                        cursor: pointer;
+                        flex: 1;
+                        min-height: 90px;
+                        overflow: hidden;
+                        text-overflow: ellipsis;
+                        border-right: 1px solid #dfe6ec;
+                        position: relative;
+                        &.eventBg {
+                            background-color: #f5f5f5;
+                        }
+                        &.clickDay {
+                            background-color: rgba(102, 153, 255, 0.2);
+                        }
+                        .day-number {
+                            text-align: center;
+                            font-size: 20px;
+                            font-weight: bold;
+                            height: 100%;
+                            margin-top: -30px;
+                            display: flex;
+                            justify-content: center;
+                            align-items: center;
+
+                            &.eventDay {
+                                color:#e02d2d;
+                            }
+                            &.is-work{
+                                color:#000;
+                            }
+                        }
+                        &.today {
+                            background-color: #fcf8e3;
+                        }
+                        &.not-cur-month {
+                            pointer-events: none;
+                            .day-number {
+                                color: rgba(0, 0, 0, 0.24);
+                            }
+                        }
+                        .day-close {
+                            position: absolute;
+                            top: 3px;
+                            right: 5px;
+                        }
+                        .event-box {
+                            height: 30px;
+
+                            .event-item {
+                                cursor: pointer;
+                                text-align: center;
+                                font-size: 18px;
+                                font-weight: bold;
+                                background-color: #f43;
+                                margin-bottom: 2px;
+                                color: #fff;
+                                // padding: 2px 5px;
+                                height: 100%;
+                                line-height: 30px;
+                                width:30px;
+
+                                white-space: nowrap;
+                                overflow: hidden;
+                                text-overflow: ellipsis;
+
+                                &.is-start {
+                                    margin-left: 4px;
+                                    // border-top-left-radius:4px;
+                                    // border-bottom-left-radius:4px;
+                                }
+                                &.is-end {
+                                    margin-right: 4px;
+                                    // border-top-right-radius:4px;
+                                    // border-bottom-right-radius:4px;
+                                }
+                                &.is-opacity {
+                                    opacity: 0;
+                                }
+                                &.is-work{
+                                    background-color: #969799;
+                                    span{
+                                        color:#999;
+                                    }
+                                }
+                                span{
+                                    position: absolute;
+                                    bottom: 5px;
+                                    left: 0;
+                                    right: 0;
+                                    margin: auto;
+                                    color:#e02d2d;
+                                    font-size: 14px;
+                                }
+                            }
+                            .more-link {
+                                cursor: pointer;
+                                // text-align: right;
+                                padding-left: 8px;
+                                padding-right: 2px;
+                                color: rgba(0, 0, 0, 0.38);
+                                font-size: 14px;
+                            }
+                        }
+                    }
+                }
+            }
+            .more-events {
+                // position:absolute;
+                position: relative;
+                width: 150px;
+                z-index: 2;
+                border: 1px solid #eee;
+                box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
+                .more-header {
+                    background-color: #eee;
+                    padding: 5px;
+                    display: flex;
+                    align-items: center;
+                    font-size: 14px;
+                    .title {
+                        flex: 1;
+                    }
+                    .close {
+                        margin-right: 2px;
+                        cursor: pointer;
+                        font-size: 16px;
+                    }
+                }
+                .more-body {
+                    height: 140px;
+                    overflow: hidden;
+                    .body-list {
+                        height: 120px;
+                        padding: 5px;
+                        overflow: auto;
+                        background-color: #fff;
+                        .body-item {
+                            cursor: pointer;
+                            font-size: 12px;
+                            background-color: #c7e6fd;
+                            margin-bottom: 2px;
+                            color: rgba(0, 0, 0, 0.87);
+                            padding: 0 0 0 4px;
+                            height: 18px;
+                            line-height: 18px;
+                            white-space: nowrap;
+                            overflow: hidden;
+                            text-overflow: ellipsis;
+                        }
+                    }
+                }
+            }
+        }
+    }
+</style>
+

+ 11 - 0
src/components/FullCalendar/comp/eventCard.vue

@@ -0,0 +1,11 @@
+<template />
+
+<script>
+export default {
+    name: 'EventCard'
+}
+</script>
+
+<style scoped>
+
+</style>

+ 127 - 0
src/components/FullCalendar/comp/header.vue

@@ -0,0 +1,127 @@
+<template>
+  <div class="full-calendar-header">
+    <div class="header-left">
+      <slot name="header-left" />
+    </div>
+    <div class="header-center">
+      <!-- <span class="prev-month" @click.stop="goPrev">{{leftArrow}}</span>
+            <span class="title">{{title}}</span>
+            <span class="next-month" @click.stop="goNext">{{rightArrow}}</span> -->
+      <el-date-picker
+        v-model="nowMonth"
+        type="month"
+        placeholder="选择月"
+        value-format="yyyy-MM"
+        :clearable="false"
+        style="font-size: 16px;font-weight: bold;"
+        @change="changeMonthDay"
+      />
+      <el-button class="to-today" type="primary" plain @click="toDate">本月</el-button>
+    </div>
+    <div class="header-right">
+      <slot name="header-right" />
+    </div>
+  </div>
+</template>
+<script type="text/babel">
+import dateFunc from '../js/dateFunc'
+
+export default {
+    props: {
+        currentDate: {},
+        titleFormat: {},
+        firstDay: {},
+        monthNames: {}
+    },
+    data() {
+        return {
+            title: '',
+            leftArrow: '<',
+            rightArrow: '>',
+            headDate: new Date(),
+            nowMonth: dateFunc.format(new Date(), 'yyyy-MM')
+        }
+    },
+    watch: {
+        currentDate(val) {
+            if (!val) return
+            this.headDate = val
+            // this.headDate = JSON.parse(JSON.stringify(val))
+        }
+    },
+    created() {
+        this.dispatchEvent()
+    },
+    methods: {
+        toDate() {
+            this.headDate = new Date()
+            this.nowMonth = new Date()
+            this.dispatchEvent()
+        },
+        goPrev() {
+            this.headDate = this.changeMonth(this.headDate, -1)
+            this.dispatchEvent()
+        },
+        goNext() {
+            this.headDate = this.changeMonth(this.headDate, 1)
+            this.dispatchEvent()
+        },
+        changeMonth(date, num) {
+            const dt = new Date(date)
+            return new Date(dt.setMonth(dt.getMonth() + num))
+        },
+        dispatchEvent() {
+            this.title = dateFunc.format(this.headDate, this.titleFormat, this.monthNames)
+
+            const startDate = dateFunc.getStartDate(this.headDate)
+            const curWeekDay = startDate.getDay()
+
+            // 1st day of this monthView
+            let diff = parseInt(this.firstDay) - curWeekDay
+            if (diff) diff -= 7
+            startDate.setDate(startDate.getDate() + diff)
+
+            // the month view is 6*7
+            const endDate = dateFunc.changeDay(startDate, 41)
+
+            // 1st day of current month
+            const currentDate = dateFunc.getStartDate(this.headDate)
+
+            this.$emit('change',
+                dateFunc.format(startDate, 'yyyy-MM-dd'),
+                dateFunc.format(endDate, 'yyyy-MM-dd'),
+                dateFunc.format(currentDate, 'yyyy-MM-dd'),
+                this.headDate
+            )
+        },
+        changeMonthDay(value) {
+            this.headDate = new Date(value)
+            this.dispatchEvent()
+        }
+    }
+}
+</script>
+<style lang="scss">
+    .full-calendar-header{
+        display: flex;
+        align-items: center;
+        .header-left,.header-right{
+            flex:1;
+        }
+        .header-center{
+            flex:3;
+            text-align:center;
+            font-size:18px;
+            .to-today{
+                margin-left: 30px;
+            }
+            .title{
+                margin: 0 10px;
+            }
+            .prev-month,.next-month{
+                cursor: pointer;
+            }
+        }
+    }
+</style>
+

+ 122 - 0
src/components/FullCalendar/fullCalendar.vue

@@ -0,0 +1,122 @@
+<template>
+  <div class="comp-full-calendar">
+    <!-- header pick month -->
+    <fc-header
+      :current-date="currentDate"
+      :title-format="titleFormat"
+      :first-day="firstDay"
+      :month-names="monthNames"
+      @change="emitChangeMonth"
+    >
+
+      <div slot="header-left">
+        <slot name="fc-header-left" />
+      </div>
+
+      <div slot="header-right">
+        <slot name="fc-header-right" />
+      </div>
+    </fc-header>
+    <fc-body
+      :current-date="currentDate"
+      :events="events"
+      :month-names="monthNames"
+      :week-names="weekNames"
+      :first-day="firstDay"
+      @eventclick="emitEventClick"
+      @dayclick="emitDayClick"
+      @moreclick="emitMoreClick"
+      @eventdelete="emitEventDelete"
+    >
+      <div slot="body-card">
+        <slot name="fc-body-card" />
+      </div>
+    </fc-body>
+  </div>
+</template>
+<script type="text/babel">
+import langSets from './js/langSets'
+import header from './comp/header.vue'
+import body from './comp/body.vue'
+export default {
+    components: {
+        'fc-body': body,
+        'fc-header': header
+    },
+    props: {
+        events: { // events will be displayed on calendar
+            type: Array,
+            default: () => []
+        },
+        lang: {
+            type: String,
+            default: 'en'
+        },
+        firstDay: {
+            type: [String, Number],
+            validator(val) {
+                const res = parseInt(val)
+                return res >= 0 && res <= 6
+            },
+            default: 0
+        },
+        titleFormat: {
+            type: String,
+            default() {
+                return langSets[this.lang].titleFormat
+            }
+        },
+        monthNames: {
+            type: Array,
+            default() {
+                return langSets[this.lang].monthNames
+            }
+        },
+        weekNames: {
+            type: Array,
+            default() {
+                const arr = langSets[this.lang].weekNames
+                return arr.slice(this.firstDay).concat(arr.slice(0, this.firstDay))
+            }
+        }
+    },
+    data() {
+        return {
+            currentDate: new Date()
+        }
+    },
+    methods: {
+        emitChangeMonth(start, end, currentStart, current) {
+            this.currentDate = current
+            this.$emit('changeMonth', start, end, currentStart, current)
+        },
+        emitEventClick(date, event, jsEvent, pos) {
+            // console.log(date,event)
+            this.$emit('eventClick', date, event, jsEvent, pos)
+        },
+        emitDayClick(day, jsEvent) {
+            this.$emit('dayClick', day, jsEvent)
+        },
+        emitMoreClick(day, events, jsEvent) {
+            this.$emit('moreClick', day, events, jsEvent)
+        },
+        emitEventDelete(events) {
+            this.$emit('eventDelete', events)
+        }
+    }
+}
+
+</script>
+<style lang="scss">
+    .comp-full-calendar{
+        background: #fff;
+        width: 100%;
+        padding-left: 50px;
+        padding-right: 50px;
+        ul,p{
+            margin:0;
+            padding:0;
+            font-size: 14px;
+        }
+    }
+</style>

+ 5 - 0
src/components/FullCalendar/index.js

@@ -0,0 +1,5 @@
+import vueFullCalendar from './fullCalendar'
+
+const fc = vueFullCalendar
+
+module.exports = fc

+ 92 - 0
src/components/FullCalendar/js/dateFunc.js

@@ -0,0 +1,92 @@
+const shortMonth = [
+    'Jan',
+    'Feb',
+    'Mar',
+    'Apr',
+    'May',
+    'Jun',
+    'Jul',
+    'Aug',
+    'Sep',
+    'Oct',
+    'Nov',
+    'Dec'
+]
+const defMonthNames = [
+    'January',
+    'February',
+    'March',
+    'April',
+    'May',
+    'June',
+    'July',
+    'August',
+    'September',
+    'October',
+    'November',
+    'December'
+]
+
+const dateFunc = {
+    getDuration(date) {
+        // how many days of this month
+        const dt = new Date(date)
+        dt.setMonth(dt.getMonth() + 1)
+        dt.setDate(0)
+        return dt.getDate()
+    },
+    changeDay(date, num) {
+        const dt = new Date(date)
+        return new Date(dt.setDate(dt.getDate() + num))
+    },
+    getStartDate(date) {
+        // return first day of this month
+        return new Date(date.getFullYear(), date.getMonth(), 1)
+    },
+    getEndDate(date) {
+        // get last day of this month
+        const dt = new Date(date.getFullYear(), date.getMonth() + 1, 1) // 1st day of next month
+        return new Date(dt.setDate(dt.getDate() - 1)) // last day of this month
+    },
+    format(date, format, monthNames) {
+        monthNames = monthNames || defMonthNames
+        if (typeof date === 'string') {
+            date = new Date(date.replace(/-/g, '/'))
+        } else {
+            date = new Date(date)
+        }
+
+        const map = {
+            'M': date.getMonth() + 1,
+            'd': date.getDate(),
+            'h': date.getHours(),
+            'm': date.getMinutes(),
+            's': date.getSeconds(),
+            'q': Math.floor((date.getMonth() + 3) / 3),
+            'S': date.getMilliseconds()
+        }
+
+        format = format.replace(/([yMdhmsqS])+/g, (all, t) => {
+            var v = map[t]
+            if (v !== undefined) {
+                if (all === 'MMMM') {
+                    return monthNames[v - 1]
+                }
+                if (all === 'MMM') {
+                    return shortMonth[v - 1]
+                }
+                if (all.length > 1) {
+                    v = '0' + v
+                    v = v.substr(v.length - 2)
+                }
+                return v
+            } else if (t === 'y') {
+                return String(date.getFullYear()).substr(4 - all.length)
+            }
+            return all
+        })
+        return format
+    }
+}
+export default dateFunc
+

+ 18 - 0
src/components/FullCalendar/js/langSets.js

@@ -0,0 +1,18 @@
+export default {
+    en: {
+        weekNames: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+        monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
+        titleFormat: 'MMMM yyyy'
+    },
+    zh: {
+        weekNames: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
+        monthNames: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
+        titleFormat: 'yyyy年MM月'
+    },
+    fr: {
+        weekNames: ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam'],
+        monthNames: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
+        titleFormat: 'MMMM yyyy'
+    }
+}
+

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

@@ -0,0 +1,52 @@
+<template>
+  <div style="padding-left: 10px;z-index: 100" @click="toggleClick">
+      <img :src="isSel ?selMore: moreSvg" />
+<!--    <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
+        }
+    },
+    data(){
+      return{
+          moreSvg:require('./moreSvg.svg'),
+          selMore:require('./selMore.svg'),
+          isSel:false,
+      }
+    },
+    methods: {
+        toggleClick() {
+            this.$emit('toggleClick')
+        }
+    }
+}
+</script>
+
+<style scoped>
+.hamburger {
+  display: inline-block;
+  vertical-align: middle;
+  width: 20px;
+  height: 20px;
+}
+
+.hamburger.is-active {
+  transform: rotate(180deg);
+}
+</style>

Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/components/Hamburger/moreSvg.svg


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/components/Hamburger/selMore.svg


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

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

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

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

+ 179 - 0
src/components/QuillEditor/index.vue

@@ -0,0 +1,179 @@
+<template>
+  <div class="edit_container" style="margin-bottom: 20px">
+    <!-- 图片上传组件辅助-->
+    <el-upload
+      hidden
+      class="avatar-uploader"
+      :action="serverUrl"
+      name="file"
+      :http-request="upload"
+    />
+    <quill-editor
+      ref="myQuillEditor"
+      v-model="value"
+      :options="editorOption"
+      @change="onEditorChange($event)"
+    />
+  </div>
+</template>
+<script>
+import { quillEditor, Quill } from 'vue-quill-editor'
+import { ImageExtend } from 'quill-image-extend-module'
+import ImageResize from 'quill-image-resize-module'
+// import { ImageDrop } from 'quill-image-drop-module'
+// Quill.register('modules/imageDrop', ImageDrop)
+import axios from 'axios'
+Quill.register('modules/ImageExtend', ImageExtend)
+// use resize module
+Quill.register('modules/ImageResize', ImageResize)
+const toolbarOptions = [
+    ['bold', 'italic', 'underline', 'strike'], // 加粗 斜体 下划线 删除线
+    ['blockquote', 'code-block'], // 引用  代码块
+    [{ header: 1 }, { header: 2 }], // 1、2 级标题
+    [{ list: 'ordered' }, { list: 'bullet' }], // 有序、无序列表
+    [{ script: 'sub' }, { script: 'super' }], // 上标/下标
+    [{ indent: '-1' }, { indent: '+1' }], // 缩进
+    // [{'direction': 'rtl'}],                         // 文本方向
+    [{ size: ['small', false, 'large', 'huge'] }], // 字体大小
+    [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
+    [{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
+    [{ font: [] }], // 字体种类
+    [{ align: [] }], // 对齐方式
+    ['clean'], // 清除文本格式
+    ['image'] // 链接、图片、视频
+]
+/**
+ * 图片粘贴上传需修改: 将 包quill-image-extend-module 下 index.js 文件
+ * 第82和84行中 this.uploadImg() 和 this.toBase64() 的注释去掉
+ */
+export default {
+    components: { quillEditor },
+    props: {
+        value: {
+            type: String,
+            default: ''
+        }
+    },
+    data() {
+        return {
+            content: '',
+            once: true,
+            serverUrl: this.$constant.BASE_URI + '/FileController/upload',
+            myQuillEditor: 'vue-quill-' + +new Date() + ((Math.random() * 1000).toFixed(0) + ''),
+            // 富文本框参数设置
+            editorOption: {
+                modules: {
+                    ImageResize: {},
+                    ImageExtend: {
+                        loading: false,
+                        name: 'file',
+                        size: 20, // 单位为M, 1M = 1024KB
+                        action: this.$constant.BASE_URI + '/FileController/upload',
+                        headers: (xhr) => {
+                            // xhr.setRequestHeader('Auth', JSON.parse(localStorage.getItem('userInfo')).auth || '')
+                        },
+                        response: (res) => {
+                            return this.$constant.BASE_URI + '/FileController/download/' + res.data // 返回的图片信息
+                        }
+                    },
+                    clipboard: {
+                        // 粘贴版,处理粘贴时候的自带样式
+                        matchers: [['img', this.HandleCustomMatcher]]
+                    },
+                    toolbar: {
+                        container: toolbarOptions,
+                        handlers: {
+                            'image': function(value) {
+                                if (value) {
+                                    // 触发input框选择图片文件
+                                    document.querySelector('.avatar-uploader input').click()
+                                } else {
+                                    this.quill.format('image', false)
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    },
+    watch: {
+        value(val) {
+            // this.content = val
+            this.$emit('input', val)
+        }
+    },
+    mounted() {
+    },
+    methods: {
+        // 失去焦点
+        onEditorBlur(editor) {
+        },
+        // 获得焦点
+        onEditorFocus(editor) {
+            debugger
+        },
+        // 开始
+        onEditorReady(editor) {
+            debugger
+        },
+        HandleCustomMatcher(node, Delta) {
+            debugger
+            if (this.once === false) {
+                const ops = []
+                Delta.ops.forEach(op => {
+                    debugger
+                    if (op.insert && typeof op.insert === 'string') {
+                        ops.push({
+                            insert: op.insert
+                        })
+                    }
+                })
+                Delta.ops = ops
+                return Delta
+            } else {
+                return Delta
+            }
+        },
+        // 值发生变化
+        onEditorChange(event) {
+            this.once = false
+        },
+        // 上传图片前
+        upload(param, isDownload, returnId) { // element 上传图片的方法
+            var _this = this
+            // 获取富文本组件实例
+            const quill = this.$refs.myQuillEditor.quill
+            const formParam = new FormData() // 创建form对象
+            formParam.append('file', param.file)// 通过append向form对象添加数据
+            console.log(formParam.get('file')) // FormData私有类对象,访问不到,可以通过get判断值是否传进去
+            const config = {
+                headers: {
+                    'Content-Type': 'multipart/form-data',
+                    'MVVM-Key': String(new Date().getTime()),
+                    'xx': 'anything'
+                } // 这里是重点,需要和后台沟通好请求头,Content-Type不一定是这个值
+            } // 添加请求头
+            return new Promise((resolve, reject) => {
+                axios.post(this.$constant.BASE_URI + '/FileController/upload', formParam, config)
+                    .then(response => {
+                        console.log('Upload success file:', response.data)
+                        // 获取光标所在位置
+                        const length = quill.getSelection(true).index
+                        // 插入图片  res.info为服务器返回的图片地址
+                        quill.insertEmbed(length, 'image', this.$constant.BASE_URI + '/FileController/download/' + response.data.data)
+                        // 调整光标到最后
+                        quill.setSelection(length + 1)
+                    }).catch((err, x) => {
+                        reject(err, x)
+                    })
+            })
+        }
+    }
+}
+</script>
+<style>
+.ql-editor{
+    height:400px
+}
+</style>

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

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

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

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

Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/components/Screenfull/screen.svg


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

@@ -0,0 +1,61 @@
+<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>
+import { isExternal } from '@/static/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>

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

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

+ 175 - 0
src/components/ThemePicker/index.vue

@@ -0,0 +1,175 @@
+<template>
+  <el-color-picker
+    v-model="theme"
+    :predefine="['#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d', ]"
+    class="theme-picker"
+    popper-class="theme-picker-dropdown"
+  />
+</template>
+
+<script>
+const version = require('element-ui/package.json').version // element-ui version from node_modules
+const ORIGINAL_THEME = '#409EFF' // default color
+
+export default {
+    data() {
+        return {
+            chalk: '', // content of theme-chalk css
+            theme: ''
+        }
+    },
+    computed: {
+        defaultTheme() {
+            return this.$store.state.settings.theme
+        }
+    },
+    watch: {
+        defaultTheme: {
+            handler: function(val, oldVal) {
+                this.theme = val
+            },
+            immediate: true
+        },
+        async theme(val) {
+            const oldVal = this.chalk ? this.theme : ORIGINAL_THEME
+            if (typeof val !== 'string') return
+            const themeCluster = this.getThemeCluster(val.replace('#', ''))
+            const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
+            console.log(themeCluster, originalCluster)
+
+            const $message = this.$message({
+                message: '  Compiling the theme',
+                customClass: 'theme-message',
+                type: 'success',
+                duration: 0,
+                iconClass: 'el-icon-loading'
+            })
+
+            const getHandler = (variable, id) => {
+                return () => {
+                    const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', ''))
+                    const newStyle = this.updateStyle(this[variable], originalCluster, themeCluster)
+
+                    let styleTag = document.getElementById(id)
+                    if (!styleTag) {
+                        styleTag = document.createElement('style')
+                        styleTag.setAttribute('id', id)
+                        document.head.appendChild(styleTag)
+                    }
+                    styleTag.innerText = newStyle
+                }
+            }
+
+            if (!this.chalk) {
+                const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
+                await this.getCSSString(url, 'chalk')
+            }
+
+            const chalkHandler = getHandler('chalk', 'chalk-style')
+
+            chalkHandler()
+
+            const styles = [].slice.call(document.querySelectorAll('style'))
+                .filter(style => {
+                    const text = style.innerText
+                    return new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)
+                })
+            styles.forEach(style => {
+                const { innerText } = style
+                if (typeof innerText !== 'string') return
+                style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)
+            })
+
+            this.$emit('change', val)
+
+            $message.close()
+        }
+    },
+
+    methods: {
+        updateStyle(style, oldCluster, newCluster) {
+            let newStyle = style
+            oldCluster.forEach((color, index) => {
+                newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
+            })
+            return newStyle
+        },
+
+        getCSSString(url, variable) {
+            return new Promise(resolve => {
+                const xhr = new XMLHttpRequest()
+                xhr.onreadystatechange = () => {
+                    if (xhr.readyState === 4 && xhr.status === 200) {
+                        this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
+                        resolve()
+                    }
+                }
+                xhr.open('GET', url)
+                xhr.send()
+            })
+        },
+
+        getThemeCluster(theme) {
+            const tintColor = (color, tint) => {
+                let red = parseInt(color.slice(0, 2), 16)
+                let green = parseInt(color.slice(2, 4), 16)
+                let blue = parseInt(color.slice(4, 6), 16)
+
+                if (tint === 0) { // when primary color is in its rgb space
+                    return [red, green, blue].join(',')
+                } else {
+                    red += Math.round(tint * (255 - red))
+                    green += Math.round(tint * (255 - green))
+                    blue += Math.round(tint * (255 - blue))
+
+                    red = red.toString(16)
+                    green = green.toString(16)
+                    blue = blue.toString(16)
+
+                    return `#${red}${green}${blue}`
+                }
+            }
+
+            const shadeColor = (color, shade) => {
+                let red = parseInt(color.slice(0, 2), 16)
+                let green = parseInt(color.slice(2, 4), 16)
+                let blue = parseInt(color.slice(4, 6), 16)
+
+                red = Math.round((1 - shade) * red)
+                green = Math.round((1 - shade) * green)
+                blue = Math.round((1 - shade) * blue)
+
+                red = red.toString(16)
+                green = green.toString(16)
+                blue = blue.toString(16)
+
+                return `#${red}${green}${blue}`
+            }
+
+            const clusters = [theme]
+            for (let i = 0; i <= 9; i++) {
+                clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))
+            }
+            clusters.push(shadeColor(theme, 0.1))
+            return clusters
+        }
+    }
+}
+</script>
+
+<style>
+.theme-message,
+.theme-picker-dropdown {
+  z-index: 99999 !important;
+}
+
+.theme-picker .el-color-picker__trigger {
+  height: 26px !important;
+  width: 26px !important;
+  padding: 2px;
+}
+
+.theme-picker-dropdown .el-color-dropdown__link-btn {
+  display: none;
+}
+</style>

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

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

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

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

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

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

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

@@ -0,0 +1,29 @@
+
+import store from '@/store'
+
+export default {
+    inserted(el, binding, vnode) {
+        const { value } = binding
+        const perms = store.getters && store.getters.perms
+
+        if (value && value instanceof Array && value.length > 0) {
+            const permissions = value
+
+            var hasPermission = false
+
+            if (perms.indexOf('*') >= 0) {
+                hasPermission = true
+            } else {
+                hasPermission = perms.some(perm => {
+                    return permissions.includes(perm)
+                })
+            }
+
+            if (!hasPermission) {
+                el.parentNode && el.parentNode.removeChild(el)
+            }
+        } else {
+            throw new Error(`need perms! Like v-permission="['GET /aaa','POST /bbb']"`)
+        }
+    }
+}

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

Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/404.svg


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/bug.svg


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

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

Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/clipboard.svg


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

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

Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/dashboard.svg


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

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

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

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

Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/edit.svg


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/education.svg


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

@@ -0,0 +1 @@
+<svg width="128" height="96" xmlns="http://www.w3.org/2000/svg"><path d="M64.125 56.975L120.188.912A12.476 12.476 0 0 0 115.5 0h-103c-1.588 0-3.113.3-4.513.838l56.138 56.137z"/><path d="M64.125 68.287l-62.3-62.3A12.42 12.42 0 0 0 0 12.5v71C0 90.4 5.6 96 12.5 96h103c6.9 0 12.5-5.6 12.5-12.5v-71a12.47 12.47 0 0 0-1.737-6.35L64.125 68.287z"/></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/excel.svg

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M78.208 16.576v8.384h38.72v5.376h-38.72v8.704h38.72v5.376h-38.72v8.576h38.72v5.376h-38.72v8.576h38.72v5.376h-38.72v8.576h38.72v5.376h-38.72v8.512h38.72v5.376h-38.72v11.136H128v-94.72H78.208zM0 114.368L72.128 128V0L0 13.632v100.736z"/><path d="M28.672 82.56h-11.2l14.784-23.488-14.08-22.592h11.52l8.192 14.976 8.448-14.976h11.136l-14.08 22.208L58.368 82.56H46.656l-8.768-15.68z"/></svg>

Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/exit-fullscreen.svg


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/eye-open.svg


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/eye.svg


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/form.svg


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

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M38.47 52L52 38.462l-23.648-23.67L43.209 0H.035L0 43.137l14.757-14.865L38.47 52zm74.773 47.726L89.526 76 76 89.536l23.648 23.672L84.795 128h43.174L128 84.863l-14.757 14.863zM89.538 52l23.668-23.648L128 43.207V.038L84.866 0 99.73 14.76 76 38.472 89.538 52zM38.46 76L14.792 99.651 0 84.794v43.173l43.137.033-14.865-14.757L52 89.53 38.46 76z"/></svg>

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

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M1.482 70.131l36.204 16.18 69.932-65.485-61.38 70.594 46.435 18.735c1.119.425 2.397-.17 2.797-1.363v-.085L127.998.047 1.322 65.874c-1.12.597-1.519 1.959-1.04 3.151.32.511.72.937 1.2 1.107zm44.676 57.821L64.22 107.26l-18.062-7.834v28.527z"/></svg>

Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/icon.svg


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/international.svg


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/language.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>

Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/list.svg


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

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M119.88 49.674h-7.987V39.52C111.893 17.738 90.45.08 63.996.08 37.543.08 16.1 17.738 16.1 39.52v10.154H8.113c-4.408 0-7.987 2.94-7.987 6.577v65.13c0 3.637 3.57 6.577 7.987 6.577H119.88c4.407 0 7.987-2.94 7.987-6.577v-65.13c-.008-3.636-3.58-6.577-7.987-6.577zm-23.953 0H32.065V39.52c0-14.524 14.301-26.295 31.931-26.295 17.63 0 31.932 11.777 31.932 26.295v10.153z"/></svg>

Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/message.svg


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

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M54.122 127.892v-28.68H7.513V87.274h46.609v-12.4H7.513v-12.86h38.003L.099 0h22.6l32.556 45.07c3.617 5.144 6.44 9.611 8.487 13.385 1.788-3.05 4.89-7.779 9.301-14.186L103.93 0h24.01L82.385 62.013h38.34v12.862h-46.41v12.4h46.41v11.937h-46.41v28.68H54.123z"/></svg>

Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/nested.svg


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/password.svg


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/pdf.svg


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/people.svg


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/peoples.svg


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/qq.svg


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/search.svg


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/shopping.svg


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

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 54.857h54.796v18.286H36.531V128H18.265V73.143H0V54.857zm127.857-36.571H91.935V128H72.456V18.286H36.534V0h91.326l-.003 18.286z"/></svg>

Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/skill.svg


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/star.svg


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/tab.svg


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/table.svg


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/theme.svg


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/tree-table.svg


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/tree.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>

Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/icons/svg/wechat.svg


+ 0 - 0
src/icons/svg/zip.svg


Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff