Browse Source

初始化提交

old
wangqing 5 years ago
parent
commit
a20860aa47
  1. 9
      .env.development
  2. 11
      .env.production
  3. 3
      .eslintignore
  4. 240
      .eslintrc.js
  5. 1
      .gitignore
  6. 1
      .stylelintignore
  7. 6
      .stylelintrc
  8. 201
      LICENSE
  9. 71
      README.md
  10. 7
      babel.config.js
  11. 15600
      package-lock.json
  12. 121
      package.json
  13. 35
      public/index.html
  14. 65
      scss.template.handlebars
  15. 26
      src/App.vue
  16. 45
      src/api/index.js
  17. 1
      src/assets/styles/example.scss
  18. 1
      src/assets/styles/resources/variables.scss
  19. 16
      src/components/ExampleList/index.vue
  20. 6
      src/components/autoRegister.js
  21. 18
      src/components/global/ExampleNotice/index.js
  22. 42
      src/components/global/ExampleNotice/main.vue
  23. 36
      src/components/global/SvgIcon/index.vue
  24. 1
      src/layout/example.vue
  25. 30
      src/main.js
  26. 23
      src/router/index.js
  27. 84
      src/router/modules/example.js
  28. 4
      src/store/modules/example.js
  29. 30
      src/store/modules/token.js
  30. 85
      src/util/signMd5Utils.js
  31. 103
      src/views/Home.vue
  32. 4
      src/views/account/console/index.vue
  33. 184
      src/views/account/login.vue
  34. 4
      src/views/common/enterprise/index.vue
  35. 4
      src/views/common/proposal/index.vue
  36. 4
      src/views/common/sources/index.vue
  37. 52
      src/views/example/axios.vue
  38. 21
      src/views/example/component.vue
  39. 31
      src/views/example/cookie.vue
  40. 25
      src/views/example/global.component.vue
  41. 18
      src/views/example/meta.vue
  42. 5
      src/views/example/params.vue
  43. 20
      src/views/example/permission.js.vue
  44. 3
      src/views/example/permission.router.vue
  45. 5
      src/views/example/query.vue
  46. 26
      src/views/example/reload.vue
  47. 50
      src/views/example/sprite.vue
  48. 18
      src/views/example/svgicon.vue
  49. 100
      src/views/example/user.vue
  50. 42
      src/views/example/vuex.vue
  51. 76
      vue.config.js
  52. 7619
      yarn.lock

9
.env.development

@ -1,2 +1,9 @@
# 页面标题
VUE_APP_TITLE = 填鸭测试环境
VUE_APP_API_ROOT = /
# 接口请求地址,会设置到 axios 的 baseURL 参数上
VUE_APP_API_ROOT = http://yigou.ketao.com/api/
# 是否开启 CDN 支持,开启设置 ON,关闭设置 OFF
# 详情介绍请阅读 http://eoner.gitee.io/vue-automation/#/cdn
VUE_APP_CDN = OFF
# 调试工具,可设置 eruda 或 vconsole,如果不需要开启则留空
VUE_APP_DEBUG_TOOL =

11
.env.production

@ -1,2 +1,9 @@
VUE_APP_TITLE = 网站标题
VUE_APP_API_ROOT = https://yigou.ketao.com/api/
# 页面标题
VUE_APP_TITLE = 填鸭
# 接口请求地址,会设置到 axios 的 baseURL 参数上
VUE_APP_API_ROOT = http://yigou.ketao.com/api/
# 是否开启 CDN 支持,开启设置 ON,关闭设置 OFF
# 详情介绍请阅读 http://eoner.gitee.io/vue-automation/#/cdn
VUE_APP_CDN = OFF
# 调试工具,可设置 eruda 或 vconsole,如果不需要开启则留空
VUE_APP_DEBUG_TOOL =

3
.eslintignore

@ -1,2 +1,5 @@
dist/
node_modules/
babel.config.js
vue.config.js
.eslintrc.js

240
.eslintrc.js

@ -1,166 +1,88 @@
// https://eslint.org/docs/user-guide/configuring
module.exports = {
root: true,
parserOptions: {
parser: 'babel-eslint'
},
env: {
browser: true,
jquery: true
browser: true,
es6: true
},
globals: {
process: true,
require: true,
module: true
},
extends: [
// https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
// consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
'plugin:vue/essential'
'plugin:vue/strongly-recommended',
'eslint:recommended'
],
// required to lint *.vue files
plugins: [
'vue'
],
// add your custom rules here
'rules': {
'comma-dangle': ['error', 'never'], // 是否允许对象中出现结尾逗号
'no-cond-assign': 2, // 条件语句的条件中不允许出现赋值运算符
'no-console': 0, //不允许出现console语句
'no-constant-condition': 2, // 条件语句的条件中不允许出现恒定不变的量
'no-control-regex': 2, // 正则表达式中不允许出现控制字符
'no-debugger': 1, // 不允许出现debugger语句
'no-dupe-args': 2, // 函数定义的时候不允许出现重复的参数
'no-dupe-keys': 2, // 对象中不允许出现重复的键
'no-duplicate-case': 2, // switch语句中不允许出现重复的case标签
'no-empty': 1, // 不允许出现空的代码块
'no-empty-character-class': 2, // 正则表达式中不允许出现空的字符组
'no-ex-assign': 0, // 在try catch语句中不允许重新分配异常变量
'no-extra-boolean-cast': 2, // 不允许出现不必要的布尔值转换
'no-extra-parens': 1, // 不允许出现不必要的圆括号
'no-extra-semi': 2, // 不允许出现不必要的分号
'no-func-assign': 1, // 不允许重新分配函数声明
'no-inner-declarations': ['error', 'functions'], // 不允许在嵌套代码块里声明函数
'no-invalid-regexp': 2, // 不允许在RegExp构造函数里出现无效的正则表达式
'no-irregular-whitespace': 2, // 不允许出现不规则的空格
'no-negated-in-lhs': 2, // 不允许在in表达式语句中对最左边的运算数使用取反操作
'no-obj-calls': 2, // 不允许把全局对象属性当做函数来调用
'no-regex-spaces': 2, // 正则表达式中不允许出现多个连续空格
'quote-props': 2, // 对象中的属性名是否需要用引号引起来
'no-sparse-arrays': 2, // 数组中不允许出现空位置
'no-unreachable': 2, // 在return,throw,continue,break语句后不允许出现不可能到达的语句
'use-isnan': 2, // 要求检查NaN的时候使用isNaN()
'valid-jsdoc': ['error', {
'requireReturn': false,
'requireParamDescription': false,
'requireReturnDescription': true
}], // 强制JSDoc注释
'valid-typeof': ['error', { 'requireStringLiterals': true }], // 在使用typeof表达式比较的时候强制使用有效的字符串
'block-scoped-var': 2, // 将变量声明放在合适的代码块里
'complexity': 0, // 限制条件语句的复杂度
'consistent-return': 1, // 无论有没有返回值都强制要求return语句返回一个值
'curly': ['error', 'all'], // 强制使用花括号的风格
'default-case': 0, // 在switch语句中需要有default语句
'no-alert': 1, // 不允许使用alert,confirm,prompt语句
'no-caller': 2, // 不允许使用arguments.callee和arguments.caller属性
'guard-for-in': 0, // 监视for in循环,防止出现不可预料的情况
'no-div-regex': 2, // 不能使用看起来像除法的正则表达式
'no-else-return': 0, // 如果if语句有return,else里的return不用放在else里
'no-labels': ['error', {
'allowLoop': false,
'allowSwitch': false
}], // 不允许标签语句
'no-eq-null': 1, // 不允许对null用==或者!=
'no-eval': 1, // 不允许使用eval()
'no-extend-native': 2, // 不允许扩展原生对象
'no-extra-bind': 2, // 不允许不必要的函数绑定
'no-fallthrough': 2, // 不允许switch按顺序全部执行所有case
'no-floating-decimal': 2, // 不允许浮点数缺失数字
'no-implied-eval': 2, // 不允许使用隐式eval()
'no-iterator': 2, // 不允许使用__iterator__属性
'no-lone-blocks': 2, // 不允许不必要的嵌套代码块
'no-loop-func': 2, // 不允许在循环语句中进行函数声明
'no-multi-spaces': 1, // 不允许出现多余的空格
'no-multi-str': 2, // 不允许用\来让字符串换行
'no-global-assign': 2, // 不允许重新分配原生对象
'no-new': 2, // 不允许new一个实例后不赋值或者不比较
'no-new-func': 2, // 不允许使用new Function
'no-new-wrappers': 2, // 不允许使用new String,Number和Boolean对象
'no-octal': 2, // 不允许使用八进制字面值
'no-octal-escape': 2, // 不允许使用八进制转义序列
'no-param-reassign': 0, // 不允许重新分配函数参数'no-proto': 2, //不允许使用__proto__属性
'no-redeclare': 2, // 不允许变量重复声明
'no-return-assign': 2, // 不允许在return语句中使用分配语句
'no-script-url': 1, // 不允许使用javascript:void(0)
'no-self-compare': 2, // 不允许自己和自己比较
'no-sequences': 2, // 不允许使用逗号表达式
'no-throw-literal': 2, // 不允许抛出字面量错误 throw 'error'
'no-unused-expressions': 2, // 不允许无用的表达式
'no-void': 2, // 不允许void操作符
'no-warning-comments': [1, { 'terms': ['todo', 'fixme', 'any other term'] }], // 不允许警告备注
'no-with': 2, // 不允许使用with语句
'radix': 1, // 使用parseInt时强制使用基数来指定是十进制还是其他进制
'vars-on-top': 0, // var必须放在作用域顶部
'wrap-iife': [2, 'any'], // 立即执行表达式的括号风格
'yoda': [2, 'never', { 'exceptRange': true }], // 不允许在if条件中使用yoda条件
'strict': [2, 'function'], // 使用严格模式
'no-catch-shadow': 2, // 不允许try catch语句接受的err变量与外部变量重名'no-delete-var': 2, // 不允许使用delete操作符
'no-label-var': 2, // 不允许标签和变量同名
'no-shadow': 2, // 外部作用域中的变量不能与它所包含的作用域中的变量或参数同名
'no-shadow-restricted-names': 2, // js关键字和保留字不能作为函数名或者变量名
'no-undef': 2, // 不允许未声明的变量
'no-undef-init': 2, // 不允许初始化变量时给变量赋值undefined
'no-undefined': 1, // 不允许把undefined当做标识符使用
'no-unused-vars': 'off', // 不允许有声明后未使用的变量或者参数
'no-use-before-define': [2, 'nofunc'], // 不允许在未定义之前就使用变量'indent': 2, // 强制一致的缩进风格
'brace-style': [2, '1tbs', { 'allowSingleLine': false }], // 大括号风格
'camelcase': [2, { 'properties': 'never' }], // 强制驼峰命名规则
'comma-style': [2, 'last'], // 逗号风格
'consistent-this': [0, 'self'], // 当获取当前环境的this是用一样的风格
'eol-last': 2, // 文件以换行符结束
'func-names': 0, // 函数表达式必须有名字
'func-style': 0, // 函数风格,规定只能使用函数声明或者函数表达式
'key-spacing': [2, { 'beforeColon': false, 'afterColon': true }], // 对象字面量中冒号的前后空格
'max-nested-callbacks': 0, // 回调嵌套深度
'new-cap': [2, { 'newIsCap': true, 'capIsNew': false }], // 构造函数名字首字母要大写
'new-parens': 2, // new时构造函数必须有小括号
'newline-after-var': 0, // 变量声明后必须空一行
'no-array-constructor': 2, // 不允许使用数组构造器
'no-inline-comments': 0, // 不允许行内注释
'no-lonely-if': 0, // 不允许else语句内只有if语句
'no-mixed-spaces-and-tabs': [2, 'smart-tabs'], // 不允许混用tab和空格
'no-multiple-empty-lines': [2, { 'max': 2 }], // 空行最多不能超过两行
'no-nested-ternary': 2, // 不允许使用嵌套的三目运算符
'no-new-object': 2, // 禁止使用new Object()
// 'fun-call-spacing': 2, // 函数调用时,函数名与()之间不能有空格
'no-ternary': 0, // 不允许使用三目运算符
'no-trailing-spaces': 2, // 一行最后不允许有空格
'no-underscore-dangle': 2, // 不允许标识符以下划线开头
'no-extra-parens': 0, // 不允许出现多余的括号
'no-async-promise-executor': 0, // 禁止使用异步函数作为 Promise executor
'no-misleading-character-class': 0, // 不允许在字符类语法中出现由多个代码点组成的字符
'no-useless-catch': 0, // 禁止不必要的 catch 子句
'one-var': 0, // 强制变量声明放在一起
'operator-assignment': 0, // 赋值运算符的风格
'padded-blocks': [2, 'never'], // 块内行首行尾是否空行
'quote-props': 0, // 对象字面量中属性名加引号
'quotes': [1, 'single', { 'avoidEscape': false }], // 引号风格
'semi': [0, 'always'], // 强制语句分号结尾
'semi-spacing': [2, { 'before': false, 'after': true }], // 分后前后空格
'sort-vars': 0, // 变量声明时排序
'space-before-blocks': [2, 'always'], // 块前的空格
'space-before-function-paren': [0, { 'anonymous': 'always', 'named': 'never' }], // 函数定义时括号前的空格
'space-infix-ops': [2, { 'int32Hint': true }], // 操作符周围的空格
'keyword-spacing': 2, // 关键字前后的空格
'space-unary-ops': [2, { 'words': true, 'nonwords': false }], // 一元运算符前后不要加空格
'wrap-regex': 2, // 正则表达式字面量用括号括起来
'no-var': 0, // 使用let和const代替var
'generator-star-spacing': [2, 'both'], // 生成器函数前后空格
'max-depth': 0, // 嵌套块深度
'max-len': 0, // 一行最大长度,单位为字符
'max-params': 0, // 函数最多能有多少个参数
'max-statements': 0, // 函数内最多有几个声明
'no-bitwise': 0, // 不允许使用位运算符
'no-plusplus': 0, // 不允许使用++ --运算符
'indent': ['off', 2], // 关闭缩进校验
'prefer-const': 0, // 要求使用 const 声明那些声明后不再被修改的变量
'object-curly-spacing': [2, 'always'] // 强制在大括号中使用一致的空格
parserOptions: {
ecmaVersion: 2015,
parser: 'babel-eslint',
sourceType: 'module'
},
rules: {
// 代码风格
'block-spacing': [2, 'always'],
'brace-style': [2, '1tbs', {
'allowSingleLine': true
}],
'comma-spacing': [2, {
'before': false,
'after': true
}],
'comma-dangle': [2, 'never'],
'comma-style': [2, 'last'],
'computed-property-spacing': [2, 'never'],
'indent': [2, 4, {
'SwitchCase': 1
}],
'key-spacing': [2, {
'beforeColon': false,
'afterColon': true
}],
'keyword-spacing': [2, {
'before': true,
'after': true
}],
'linebreak-style': 0,
'multiline-ternary': [2, 'always-multiline'],
'no-multiple-empty-lines': [2, {
'max': 1
}],
'no-unneeded-ternary': [2, {
'defaultAssignment': false
}],
'quotes': [2, 'single'],
'semi': [2, 'never'],
'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', '!', ',']
}],
'switch-colon-spacing': [2, {
'after': true,
'before': false
}],
// ES6
'arrow-parens': [2, 'as-needed'],
'arrow-spacing': [2, {
'before': true,
'after': true
}],
// Vue - https://github.com/vuejs/eslint-plugin-vue
'vue/html-indent': [2, 4],
'vue/max-attributes-per-line': 0,
'vue/require-default-prop': 0,
'vue/singleline-html-element-content-newline': 0,
'vue/attributes-order': 2,
'vue/order-in-components': 2,
'vue/this-in-template': 0,
'vue/script-indent': [2, 4, {
'switchCase': 1
}]
}
};
};

1
.gitignore

@ -3,6 +3,7 @@ node_modules
/dist
/dist-dev
/src/assets/sprites/*.*
!/src/assets/sprites/.gitkeep
# local env files
.env.local

1
.stylelintignore

@ -1,2 +1,3 @@
dist/
node_modules/
src/assets/sprites/

6
.stylelintrc

@ -9,6 +9,10 @@
"rules": {
"indentation": 4,
"rule-empty-line-before": "never",
"at-rule-empty-line-before": "never"
"at-rule-empty-line-before": "never",
"no-descending-specificity": null,
"selector-pseudo-class-no-unknown": null,
"selector-pseudo-element-no-unknown": [true, { "ignorePseudoElements": ["v-deep"] }],
"property-no-unknown": null
}
}

201
LICENSE

@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

71
README.md

@ -1,65 +1,36 @@
# tduck-front
# vue-automation
## 说明
## 这是什么
该仓库是为统一 Vue 项目初期配置而设立,方便快速进行业务开发,基于 Vue CLI 3 。
一款开箱即用的 Vue 项目模版,基于 Vue CLI
## 使用
## 特点
### 方法 1
- 默认集成 vue-router 和 vuex
- 全局 SASS 资源自动引入
- 精灵图自动生成
- 全局组件自动注册
- CDN支持,优化打包体积
- 轻松实现团队代码规范
> 适用于初学者快速上手,项目里包含演示文件,方便学习
## 文档
拉取该项目到本地,安装依赖包后运行即可。
[Github](https://hooray.github.io/vue-automation)
运行后,可以看到功能演示,同时项目目录里带有 `example` 的目录均为演示代码。
[Gitee](http://eoner.gitee.io/vue-automation)(推荐国内用户访问)
## 支持
## 依赖
给个小 ❤️ 吧~
- vue-router
- vuex
- axios
- lodash
- dayjs
- vue-cookies
- vue-meta
- node-sass
- sass-loader
- sass-resources-loader
- svg-sprite-loader
- webpack-spritesmith
[![star](https://img.shields.io/github/stars/hooray/vue-automation?style=social)](https://github.com/hooray/vue-automation/stargazers)
## 全局 SCSS 资源
[![star](https://gitee.com/eoner/vue-automation/badge/star.svg?theme=dark)](https://gitee.com/eoner/vue-automation/stargazers)
> 全局 SCSS 资源通过 [sass-resources-loader](https://www.npmjs.com/package/sass-resources-loader) 实现
>
> 注意!并不是全局样式,而是 SCSS 资源,是变量、@mixin 、@function 这些东西
## 推广
`assets/styles/resources/` 目录下存放全局的 scss 资源,也就是说在这个目录里的文件,无需在页面上引用即可生效并使用。
[![](https://hooray.gitee.io/fantastic-admin/logo.png)](https://hooray.gitee.io/fantastic-admin)
例子中默认存放了 `utils.scss` 文件,里面有几个 `@mixin``%` ,你可以尝试在页面中使用它们看看效果。
同样, `assets/sprites/` 目录下生成的 scss 文件也是默认全局。
## 全局组件
> 全局组件会自动注册,可直接使用。
`components/global/` 目录下存放全局组件,需要注意各个组件按文件夹区分,文件夹名称与组件名无关联。
每个组件的文件夹内至少保留一个文件名为 index 的组件入口,例如 index.vue 。
普通组件必须设置 name 并保证其唯一,自动注册会将组件的 name 设为组件名,可参考 SvgIcon 组件写法。
如果组件是通过 js 进行调用,则确保组件入口文件为 index.js,可参考 ExampleNotice 组件。
## 路由
路由也实现自动注册,但因为有优先级的概念,先定义的会先匹配,所以同一模块下的路由必须放在一个路由配置文件里,具体可参考 `router/modules/example.js` 文件。
## Vuex
Vuex 同样实现了自动注册,开发只需关注 `store/modules/` 文件夹里的文件即可,同样也按照模块区分文件。
[Fantastic-admin](https://hooray.gitee.io/fantastic-admin)
一款开箱即用的 Vue 中后台管理系统框架

7
babel.config.js

@ -1,5 +1,10 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
],
env: {
development: {
plugins: ['dynamic-import-node']
}
}
}

15600
package-lock.json

File diff suppressed because it is too large

121
package.json

@ -1,63 +1,62 @@
{
"name": "tduck-front",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve ",
"build-dev": "vue-cli-service build --mode development --dest dist-dev",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"svgo": "svgo -f src/assets/icons"
},
"dependencies": {
"axios": "^0.19.0",
"core-js": "^3.4.1",
"dayjs": "^1.8.17",
"element-ui": "^2.13.0",
"eslint": "^6.7.2",
"eslint-config-standard": "^14.1.0",
"eslint-plugin-import": "^2.19.1",
"eslint-plugin-node": "^10.0.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
"eslint-plugin-vue": "^6.0.1",
"js-md5": "^0.7.3",
"lodash": "^4.17.19",
"nprogress": "^0.2.0",
"vue": "^2.6.0",
"vue-cookies": "^1.5.7",
"vue-meta": "^2.3.1",
"vue-router": "^3.1.3",
"vuex": "^3.1.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.0.0",
"@vue/cli-plugin-eslint": "^4.0.0",
"@vue/cli-service": "^4.0.0",
"babel-eslint": "^10.0.3",
"eslint": "^6.6.0",
"eslint-plugin-vue": "^6.0.1",
"node-sass": "^4.13.1",
"sass-loader": "^8.0.0",
"sass-resources-loader": "^2.0.1",
"standard": "^14.3.1",
"stylelint": "^12.0.0",
"stylelint-config-recommended-scss": "^4.0.0",
"stylelint-config-standard": "^19.0.0",
"stylelint-scss": "^3.4.1",
"svg-sprite-loader": "^4.1.6",
"svgo": "^1.3.0",
"vue-template-compiler": "^2.6.0",
"webpack-spritesmith": "^1.0.2"
},
"postcss": {
"plugins": {
"autoprefixer": {}
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
"name": "tduck-front",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build-dev": "vue-cli-service build --mode development --dest dist-dev",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"stylelint": "vue-cli-service lint:style",
"svgo": "svgo -f src/assets/icons",
"new": "plop"
},
"dependencies": {
"axios": "^0.21.0",
"core-js": "^3.6.4",
"dayjs": "^1.9.4",
"element-ui": "^2.14.0",
"js-cookie": "^2.2.1",
"js-md5": "^0.7.3",
"nprogress": "^0.2.0",
"vue": "^2.6.12",
"vue-meta": "^2.4.0",
"vue-router": "^3.4.8",
"vuex": "^3.5.1"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.5.8",
"@vue/cli-plugin-eslint": "^4.5.8",
"@vue/cli-service": "^4.5.8",
"@winner-fed/vue-cli-plugin-stylelint": "^1.0.4",
"babel-eslint": "^10.1.0",
"babel-plugin-dynamic-import-node": "^2.3.3",
"eslint": "^7.12.1",
"eslint-plugin-vue": "^7.1.0",
"html-webpack-plugin": "^4.5.0",
"husky": "^4.3.0",
"lint-staged": "^10.5.1",
"plop": "^2.7.4",
"sass": "^1.28.0",
"sass-loader": "^10.0.4",
"sass-resources-loader": "^2.1.1",
"stylelint": "^13.7.2",
"stylelint-config-recommended-scss": "^4.2.0",
"stylelint-config-standard": "^20.0.0",
"stylelint-scss": "^3.18.0",
"svg-sprite-loader": "^5.0.0",
"svgo": "^1.3.0",
"vue-template-compiler": "^2.6.12",
"webpack-spritesmith": "^1.1.0"
},
"postcss": {
"plugins": {
"autoprefixer": {}
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}

35
public/index.html

@ -1,20 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>TDuck Cloud</title>
<style>
body {
margin: 0px;
background-color: #FFFFFF;
}
</style>
<title><%= htmlWebpackPlugin.options.title %></title>
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
<!-- 使用CDN的CSS文件 -->
<link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="external nofollow preload" as="style">
<link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="external nofollow stylesheet">
<% } %>
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
<!-- 使用CDN的JS文件 -->
<link href="<%= htmlWebpackPlugin.options.cdn.js[i] %>" rel="external nofollow preload" as="script">
<% } %>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
<script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
<% } %>
<% if (htmlWebpackPlugin.options.debugTool == 'eruda') { %>
<script src="https://cdn.jsdelivr.net/npm/eruda/eruda.min.js"></script>
<script>eruda.init();</script>
<% } %>
<% if (htmlWebpackPlugin.options.debugTool == 'vconsole') { %>
<script src="https://cdn.jsdelivr.net/npm/vconsole/dist/vconsole.min.js"></script>
<script>new VConsole();</script>
<% } %>
</body>
</html>

65
scss.template.handlebars

@ -1,65 +0,0 @@
{
// Default options
'functions': true,
'variableNameTransforms': ['dasherize']
}
{{#block "sprites"}}
{{#each sprites}}
${{../spritesheet_info.strings.name}}-sprite-{{strings.name}}: ({{px.x}}, {{px.y}}, {{px.offset_x}}, {{px.offset_y}}, {{px.width}}, {{px.height}}, {{px.total_width}}, {{px.total_height}}, '{{{escaped_image}}}', '{{name}}');
{{/each}}
${{spritesheet_info.strings.name}}-sprites: (
{{#each sprites}}
{{strings.name}}: ${{../spritesheet_info.strings.name}}-sprite-{{strings.name}},
{{/each}}
);
{{/block}}
{{#block "sprite-functions"}}
{{#if options.functions}}
@mixin {{spritesheet_info.strings.name}}-sprite-width($sprite) {
width: nth($sprite, 5);
}
@mixin {{spritesheet_info.strings.name}}-sprite-height($sprite) {
height: nth($sprite, 6);
}
@mixin {{spritesheet_info.strings.name}}-sprite-position($sprite) {
$sprite-offset-x: nth($sprite, 3);
$sprite-offset-y: nth($sprite, 4);
background-position: $sprite-offset-x $sprite-offset-y;
}
@mixin {{spritesheet_info.strings.name}}-sprite-size($sprite) {
background-size: nth($sprite, 7) nth($sprite, 8);
}
@mixin {{spritesheet_info.strings.name}}-sprite-image($sprite) {
$sprite-image: nth($sprite, 9);
background-image: url(#{$sprite-image});
}
@mixin {{spritesheet_info.strings.name}}-sprite($name) {
@include {{spritesheet_info.strings.name}}-sprite-image(map-get(${{spritesheet_info.strings.name}}-sprites, #{$name}));
@include {{spritesheet_info.strings.name}}-sprite-position(map-get(${{spritesheet_info.strings.name}}-sprites, #{$name}));
@include {{spritesheet_info.strings.name}}-sprite-size(map-get(${{spritesheet_info.strings.name}}-sprites, #{$name}));
@include {{spritesheet_info.strings.name}}-sprite-width(map-get(${{spritesheet_info.strings.name}}-sprites, #{$name}));
@include {{spritesheet_info.strings.name}}-sprite-height(map-get(${{spritesheet_info.strings.name}}-sprites, #{$name}));
}
{{/if}}
{{/block}}
{{#block "spritesheet-functions"}}
{{#if options.functions}}
@mixin all-{{spritesheet_info.strings.name}}-sprites() {
@each $key, $val in ${{spritesheet_info.strings.name}}-sprites {
$sprite-name: nth($val, 10);
.{{spritesheet_info.strings.name}}-#{$sprite-name}-sprites {
@include {{spritesheet_info.strings.name}}-sprite($key);
}
}
}
{{/if}}
{{/block}}

26
src/App.vue

@ -1,12 +1,11 @@
<template>
<div id="app">
<router-view v-if="isRouterAlive" />
<RouterView v-if="isRouterAlive" />
</div>
</template>
<script>
export default {
name: 'App',
provide() {
return {
reload: this.reload
@ -17,11 +16,26 @@ export default {
isRouterAlive: true
}
},
watch: {
$route: 'routeChange'
},
methods: {
// reload() {
// this.isRouterAlive = false;
// this.$nextTick(() => (this.isRouterAlive = true));
// }
reload() {
this.isRouterAlive = false
this.$nextTick(() => (this.isRouterAlive = true))
},
routeChange(newVal, oldVal) {
if (newVal.name == oldVal.name) {
this.reload()
}
}
},
metaInfo: {
titleTemplate: title => {
return title
? `${title} - ${process.env.VUE_APP_TITLE}`
: process.env.VUE_APP_TITLE
}
}
}
</script>

45
src/api/index.js

@ -1,7 +1,8 @@
import axios from 'axios'
import qs from 'qs'
import router from '@/router/index'
import store from '@/store/index'
import signMd5Utils from '@/util/signMd5Utils'
import signMd5Utils from '@/util/sign'
const toLogin = () => {
router.push({
@ -13,8 +14,9 @@ const toLogin = () => {
}
const api = axios.create({
baseURL: process.env.VUE_APP_API_ROOT,
timeout: 1000 * 30,
baseURL: process.env.NODE_ENV !== 'development' && process.env.VUE_APP_API_ROOT,
timeout: 10000,
responseType: 'json',
withCredentials: false,
headers: {
'Content-Type': 'application/json; charset=utf-8'
@ -23,6 +25,10 @@ const api = axios.create({
api.interceptors.request.use(
request => {
/**
* 全局拦截请求发送前提交的参数
* 以下代码为示例在登录状态下分别对 post get 请求加上 token 参数
*/
if (request.method == 'post') {
if (request.data instanceof FormData) {
if (store.getters['token/isLogin']) {
@ -37,11 +43,12 @@ api.interceptors.request.use(
if (store.getters['token/isLogin']) {
request.data.token = store.state.token.token
}
// 参数验签
let timestamp = new Date().getTime()
request.data.timestamp = '' + timestamp
let sign = signMd5Utils.getSign(request.url, request.data)
request.data.sign = sign
request.data = JSON.stringify(request.data)
request.data = qs.stringify(request.data)
}
} else {
// 带上 token
@ -56,7 +63,6 @@ api.interceptors.request.use(
request.params.timestamp = '' + timestamp
let sign = signMd5Utils.getSign(request.url, request.params)
request.params.sign = sign
}
return request
}
@ -64,22 +70,29 @@ api.interceptors.request.use(
api.interceptors.response.use(
response => {
if (response.data.code != 200) {
// 如果接口请求时发现 token 失效,则立马跳转到登录页
if (response.data.code == 0) {
toLogin()
return false
/**
* 全局拦截请求发送后返回的数据如果数据有报错则在这做全局的错误提示
* 假设返回数据格式为{ status: 1, error: '', data: '' }
* 规则是当 status 1 时表示请求成功 0 时表示接口需要登录或者登录状态失效需要重新登录
* 请求出错时 error 会返回错误信息
* 则代码如下
*/
if (response.data.status === 1) {
if (response.data.error === '') {
// 请求成功并且没有报错
return Promise.resolve(response.data)
} else {
// 这里做错误提示,如果使用了 element ui 则可以使用 Message 进行提示
// Message.error(options)
return Promise.reject(response.data)
}
return Promise.reject(response.data)
} else {
toLogin()
}
return Promise.resolve(response.data)
},
error => {
return Promise.reject(error)
}
)
export {
axios,
api
}
export default api

1
src/assets/styles/example.scss

@ -1,6 +1,5 @@
// 改目录下可存放第三方样式文件或者公用样式
// 该例子可在 view/example/sprite.vue 里查看
.sprites {
div {
border: 1px solid #000;

1
src/assets/styles/resources/variables.scss

@ -0,0 +1 @@
// 全局变量

16
src/components/ExampleList/index.vue

@ -1,16 +0,0 @@
<template>
<div>
<ul>
<li v-for="(item, index) in list" :key="index">{{ item }}</li>
</ul>
</div>
</template>
<script>
export default {
name: 'ExampleList',
props: {
list: Array
}
}
</script>

6
src/components/autoRegister.js

@ -1,7 +1,7 @@
/**
* 全局组件自动注册
*
* 全局组件统一放在 ./global 目录下需要注意各个组件按文件夹区分文件夹名称与组件名无关联
* 全局组件各个组件按文件夹区分文件夹名称与组件名无关联但建议与组件名保持一致
* 文件夹内至少保留一个文件名为 index 的组件入口例如 index.vue
* 普通组件必须设置 name 并保证其唯一自动注册会将组件的 name 设为组件名可参考 SvgIcon 组件写法
* 如果组件是通过 js 进行调用则确保组件入口文件为 index.js可参考 ExampleNotice 组件
@ -9,13 +9,13 @@
import Vue from 'vue'
const componentsContext = require.context('./global', true, /index.(vue|js)$/)
const componentsContext = require.context('./', true, /index.(vue|js)$/)
componentsContext.keys().forEach(file_name => {
// 获取文件中的 default 模块
const componentConfig = componentsContext(file_name).default
if (/.vue$/.test(file_name)) {
Vue.component(componentConfig.name, componentConfig)
} else {
Vue.prototype[`$${componentConfig.name}`] = componentConfig
Vue.use(componentConfig)
}
})

18
src/components/global/ExampleNotice/index.js

@ -1,18 +0,0 @@
import Vue from 'vue'
const constructor = Vue.extend(require('./main.vue').default)
let instance
const exampleNotice = options => {
options = options || {}
instance = new constructor({
data: options
})
instance.vm = instance.$mount()
instance.dom = instance.vm.$el
document.body.appendChild(instance.dom)
return instance.vm
}
export default exampleNotice

42
src/components/global/ExampleNotice/main.vue

@ -1,42 +0,0 @@
<template>
<transition name="notice">
<div v-if="show" class="notice">
{{ content }}
</div>
</transition>
</template>
<script>
export default {
name: 'ExampleNotice',
data() {
return {
show: false,
content: ''
}
},
mounted() {
this.show = true
setTimeout(() => {
this.show = false
}, 2000)
}
}
</script>
<style lang="scss" scoped>
.notice {
padding: 10px;
background-color: #eee;
border-radius: 10px;
@include position-center(xy);
}
.notice-leave-active,
.notice-enter-active {
transition: all 0.3s;
}
.notice-enter,
.notice-leave-to {
opacity: 0;
}
</style>

36
src/components/global/SvgIcon/index.vue

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

1
src/layout/example.vue

@ -14,7 +14,6 @@
<RouterLink to="/example/reload">刷新当前页面</RouterLink>
<RouterLink to="/example/permission/router">router鉴权</RouterLink>
<RouterLink to="/example/permission/js">js鉴权</RouterLink>
<RouterLink to="/example/user">基本操作</RouterLink>
</div>
<RouterView />
</div>

30
src/main.js

@ -1,34 +1,34 @@
import Vue from 'vue'
import App from './App.vue'
import Element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import meta from 'vue-meta'
import dayjs from 'dayjs'
import Cookies from 'js-cookie'
import router from './router/index'
import api from './api'
import store from './store/index'
import lodash from 'lodash'
import {api, axios} from './api'
import dayjs from 'dayjs'
import util from './util/index'
import meta from 'vue-meta'
import cookies from 'vue-cookies'
import Element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css';
// 全局组件自动注册
import '@/components/autoRegister'
import util from './util/index'
Vue.use(meta)
Vue.use(cookies)
Vue.use(util)
Vue.use(Element, {size: 'small', zIndex: 3000})
Vue.prototype.$api = api
Vue.prototype.$axios = axios
Vue.prototype._ = lodash
Vue.prototype.$dayjs = dayjs
Vue.prototype.$cookies = Cookies
Vue.config.productionTip = false
Vue.use(meta)
Vue.use(util)
Vue.use(util)
Vue.use(Element, {size: 'small', zIndex: 3000})
// 自动加载 svg 图标
const req = require.context('./assets/icons', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys().map(requireContext)
requireAll(req)
Vue.config.productionTip = false
new Vue({
router,
store,

23
src/router/index.js

@ -1,9 +1,6 @@
/* eslint-disable camelcase */
/* eslint-disable no-undef */
import Vue from 'vue'
import Router from 'vue-router'
import store from '@/store/index'
import flattenDeep from 'lodash/flattenDeep'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css' // progress bar style
@ -24,10 +21,28 @@ require_module.keys().forEach(file_name => {
routes.push(require_module(file_name).default)
})
routes.push({
path: '*',
component: () => import('@/views/404'),
meta: {
title: '找不到页面'
}
})
const router = new Router({
routes: flattenDeep(routes)
routes: routes.flat()
})
// 解决路由在 push/replace 了相同地址报错的问题
const originalPush = Router.prototype.push
Router.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
}
const originalReplace = Router.prototype.replace
Router.prototype.replace = function replace(location) {
return originalReplace.call(this, location).catch(err => err)
}
router.beforeEach((to, from, next) => {
NProgress.start()
if (to.meta.requireLogin) {

84
src/router/modules/example.js

@ -1,84 +0,0 @@
import ExampleLayout from '@/layout/example'
export default {
path: '/example',
redirect: '/example/sprite',
component: ExampleLayout,
children: [
{
path: 'sprite',
component: () =>
import(/* webpackChunkName: 'example' */ '@/views/example/sprite.vue')
},
{
path: 'svgicon',
component: () =>
import(/* webpackChunkName: 'example' */ '@/views/example/svgicon.vue')
},
{
path: 'globalComponent',
component: () =>
import(/* webpackChunkName: 'example' */ '@/views/example/global.component.vue')
},
{
path: 'axios',
component: () =>
import(/* webpackChunkName: 'example' */ '@/views/example/axios.vue')
},
{
path: 'cookie',
component: () =>
import(/* webpackChunkName: 'example' */ '@/views/example/cookie.vue')
},
{
path: 'meta',
component: () =>
import(/* webpackChunkName: 'example' */ '@/views/example/meta.vue')
},
{
path: 'vuex',
component: () =>
import(/* webpackChunkName: 'example' */ '@/views/example/vuex.vue')
},
{
path: 'component',
component: () =>
import(/* webpackChunkName: 'example' */ '@/views/example/component.vue')
},
{
path: 'params/:test',
name: 'exampleParams', // 设置路由的name时,建议加上模块名,避免name和其他模块重名
component: () =>
import(/* webpackChunkName: 'example' */ '@/views/example/params.vue')
},
{
path: 'query',
component: () =>
import(/* webpackChunkName: 'example' */ '@/views/example/query.vue')
},
{
path: 'reload',
component: () =>
import(/* webpackChunkName: 'example' */ '@/views/example/reload.vue')
},
{
path: 'permission/router',
component: () =>
import(/* webpackChunkName: 'example' */ '@/views/example/permission.router.vue'),
meta: {
requireLogin: true // 鉴权
}
},
{
path: 'permission/js',
component: () =>
import(/* webpackChunkName: 'example' */ '@/views/example/permission.js.vue')
},
{
path: 'user',
component: () =>
import(/* webpackChunkName: 'example' */ '@/views/example/user.vue')
}
]
}

4
src/store/modules/example.js

@ -1,6 +1,4 @@
import {
api
} from '@/api'
import api from '@/api'
const state = {
banner: []

30
src/store/modules/token.js

@ -1,7 +1,4 @@
/* eslint-disable no-shadow */
import {
api
} from '@/api'
// import api from '@/api'
const state = {
token: localStorage.token,
@ -11,7 +8,7 @@ const state = {
const getters = {
isLogin: state => {
let retn = false
if (state.token !== null) {
if (state.token != null) {
let unix = Date.parse(new Date())
if (unix < state.failuretime * 1000) {
retn = true
@ -24,23 +21,28 @@ const getters = {
const actions = {
login({
commit
}, data) {
return new Promise((resolve, reject) => {
}) {
return new Promise(resolve => {
// 模拟登录成功,写入 token 信息
commit('setData', {
token: '1234567890',
failuretime: Date.parse(new Date()) / 1000 + 24 * 60 * 60
})
resolve()
// api.post('member/login', data).then(res => {
// commit('setData', res.data)
// resolve(res)
// }).catch(error => {
// reject(error)
// })
})
}
// login({
// commit
// }, data) {
// return new Promise((resolve, reject) => {
// api.post('member/login', data).then(res => {
// commit('setData', res.data)
// resolve(res)
// }).catch(error => {
// reject(error)
// })
// })
// }
}
const mutations = {

85
src/util/signMd5Utils.js

@ -1,85 +0,0 @@
import md5 from 'js-md5'
import signSecret from './constants'
export default class signMd5Utils {
/**
* json参数升序
* @param jsonObj 发送参数
*/
static sortAsc(jsonObj) {
let arr = new Array()
let num = 0
for (let i in jsonObj) {
arr[num] = i
num++
}
let sortArr = arr.sort()
let sortObj = {}
for (let i in sortArr) {
sortObj[sortArr[i]] = jsonObj[sortArr[i]]
}
return sortObj
}
/**
* @param url 请求的url,应该包含请求参数(url的?后面的参数)
* @param requestParams 请求参数(POST的JSON参数)
* @returns {string} 获取签名
*/
static getSign(url, requestParams) {
let urlParams = this.parseQueryString(url)
let jsonObj = this.mergeObject(urlParams, requestParams)
let requestBody = this.sortAsc(jsonObj)
return md5(signSecret + JSON.stringify(requestBody)).toLowerCase()
}
/**
* @param url 请求的url
* @returns {{}} 将url中请求参数组装成json对象(url的?后面的参数)
*/
static parseQueryString(url) {
let urlReg = /^[^\?]+\?([\w\W]+)$/,
paramReg = /([^&=]+)=([\w\W]*?)(&|$|#)/g,
urlArray = urlReg.exec(url),
result = {}
if (urlArray && urlArray[1]) {
let paramString = urlArray[1], paramResult
while ((paramResult = paramReg.exec(paramString)) != null) {
result[paramResult[1]] = '' + paramResult[2]
}
}
return result
}
/**
* @returns {*} 将两个对象合并成一个
*/
static mergeObject(objectOne, objectTwo) {
if (Object.keys(objectTwo).length > 0) {
for (let key in objectTwo) {
// eslint-disable-next-line no-prototype-builtins
if (objectTwo.hasOwnProperty(key) === true) {
objectOne[key] = '' + objectTwo[key]
}
}
}
return objectOne
}
static urlEncode(param, key, encode) {
if (param == null) return ''
let paramStr = ''
let t = typeof (param)
if (t == 'string' || t == 'number' || t == 'boolean') {
paramStr += '&' + key + '=' + ((encode == null || encode) ? encodeURIComponent(param) : param)
} else {
for (let i in param) {
let k = key == null ? i : key + (param instanceof Array ? '[' + i + ']' : '.' + i)
paramStr += this.urlEncode(param[i], k, encode)
}
}
return paramStr
}
}

103
src/views/Home.vue

@ -2,28 +2,28 @@
<div id="home">
<div class="headerDiv">
<el-row>
<el-col class="headerCol" :span="3"></el-col>
<el-col class="headerCol" :span="3" />
<el-col class="headerCol" :span="18">
<el-menu :default-active="menuIndex" mode="horizontal" :router="true" text-color="#205BB5" active-text-color="#205BB5">
<div class="headerLogoDiv">
<img src="../assets/images/indexLogo.png" class="headerLogo" @click="$router.push({path:'/'})"/>
<img src="../assets/images/indexLogo.png" class="headerLogo" @click="$router.push({path:'/'})">
</div>
<el-button v-if="isLogin" class="headerBtn" @click="$router.push({path:'/console'})"> </el-button>
<el-button v-if="!isLogin" class="headerBtn" @click="$router.push({path:'/login'})"> </el-button>
<el-menu-item v-for="(item, index) in menuRouters" :key="index" :index="item.routerPath" :route="item.routerPath" class="menuItem">{{item.title}}</el-menu-item>
<el-menu-item v-for="(item, index) in menuRouters" :key="index" :index="item.routerPath" :route="item.routerPath" class="menuItem">{{ item.title }}</el-menu-item>
</el-menu>
</el-col>
<el-col class="headerCol" :span="3"></el-col>
<el-col class="headerCol" :span="3" />
</el-row>
</div>
<div class="headerBody">
<div class="indexBody" :style="menuIndex!=='/'?'border:solid thin #205BB5;border-left:none;border-right:none;':'border:none;'">
<el-row>
<el-col class="bodyCol" :span="3"></el-col>
<el-col class="bodyCol" :span="3" />
<el-col class="bodyCol" :span="18">
<div v-if="menuIndex==='/'">
<el-row>
<el-col :span=14>
<el-col :span="14">
<div class="bodyLeft">
<p class="bodyTitle">TDuck - 填鸭表单</p>
<p class="bodySlogan">有你所想有你所得</p>
@ -36,16 +36,16 @@
</div>
</div>
</el-col>
<el-col :span=10>
<el-col :span="10">
<div class="bodyRight">
<img class="bodyRightImg" src="../assets/images/bodyRight.png"/>
<img class="bodyRightImg" src="../assets/images/bodyRight.png">
</div>
</el-col>
</el-row>
</div>
<router-view></router-view>
<router-view />
</el-col>
<el-col class="bodyCol" :span="3"></el-col>
<el-col class="bodyCol" :span="3" />
</el-row>
</div>
<div class="footerDiv">
@ -55,16 +55,8 @@
</div>
</template>
<script>
import store from '@/store/index.js';
import store from '@/store/index.js'
export default {
computed: {
getStore() {
return store;
},
isLogin() {
return this.getStore.getters['token/isLogin'];
}
},
data() {
return {
menuIndex: null,
@ -88,25 +80,34 @@ export default {
]
}
},
mounted() {
this.menuIndex = this.$route.path;
computed: {
getStore() {
return store
},
isLogin() {
return this.getStore.getters['token/isLogin']
}
},
watch: {
$route(to,from) {
this.menuIndex = to.path;
$route(to) {
this.menuIndex = to.path
}
},
mounted() {
this.menuIndex = this.$route.path
}
}
</script>
<style scoped>
.headerCol, .bodyCol {
.headerCol,
.bodyCol {
border: solid thin white;
}
.headerDiv {
background-color: white;
position: fixed;
width: 100%;
top: 0px;
top: 0;
z-index: 100;
}
.headerDiv .headerCol .el-menu--horizontal {
@ -115,10 +116,8 @@ export default {
.headerLogo {
float: left;
cursor: pointer;
}
.headerLogo {
height: 60px;
padding: 25px 0px 20px 0px;
padding: 25px 0 20px 0;
}
.menuItem {
line-height: 110px;
@ -128,70 +127,76 @@ export default {
float: right;
}
.headerBtn {
margin: 35px 0px 35px 20px;
margin: 35px 0 35px 20px;
float: right;
}
.bodyBtn {
margin: 30px 20px 0px 0px;
margin: 30px 20px 0 0;
font-size: 20px;
padding-left: 20px;
padding-right: 20px;
}
.headerBtn, .bodyBtn {
color: #205BB5;
border-color: #205BB5;
.headerBtn,
.bodyBtn {
color: #205bb5;
border-color: #205bb5;
}
.primary {
color: #ffffff;
background-color: #205BB5;
color: #fff;
background-color: #205bb5;
}
.headerBtn:focus, .headerBtn:hover, .bodyBtn:focus, .bodyBtn:hover {
border-color: #205BB5;
color: #205BB5;
.headerBtn:focus,
.headerBtn:hover,
.bodyBtn:focus,
.bodyBtn:hover {
border-color: #205bb5;
color: #205bb5;
}
.primary:focus, .primary:hover {
border-color: #205BB5;
color: #ffffff;
.primary:focus,
.primary:hover {
border-color: #205bb5;
color: #fff;
}
.headerBody {
padding-top: 112px;
}
.footerDiv {
font-size: 14px;
color: #205BB5;
color: #205bb5;
text-align: center;
line-height: 110px;
}
.indexBody {
margin: 0px;
margin: 0;
}
.bodyLeft {
padding: 200px 110px;
}
.bodyLeft p {
color: #205BB5;
color: #205bb5;
}
.bodyTitle {
font-size: 35px;
font-weight: 900;
line-height: 25px;
margin: 0px 0px 30px 0px;
margin: 0 0 30px 0;
}
.bodySlogan {
font-size: 45px;
font-weight: 900;
line-height: 45px;
margin: 0px 0px 5px 0px;
margin: 0 0 5px 0;
}
.bodySloganEn {
line-height: 15px;
margin: 0px 0px 25px 0px;
margin: 0 0 25px 0;
}
.bodyDetail {
line-height: 15px;
margin: 0px 0px 10px 0px;
margin: 0 0 10px 0;
}
.bodySloganEn, .bodyDetail {
.bodySloganEn,
.bodyDetail {
font-size: 15px;
}
.bodyRightImg {

4
src/views/account/console/index.vue

@ -1,11 +1,11 @@
<template>
<div id="console" style="height:690px;/*开发时请将此样式移除*/">
<div id="console" style="height: 690px; /* 开发时请将此样式移除 */">
控制台
</div>
</template>
<script>
export default {
name: 'console',
name: 'Console',
data() {
return { }
}

184
src/views/account/login.vue

@ -1,39 +1,199 @@
<template>
<div id="login">
<el-row class="loginBody">
<el-col :span="12" style="border:solid thin black;">
<img class="loginBackImg" src="http://q2fgufcw6.bkt.clouddn.com/login-banner"/>
<el-col :span="12">
<img class="loginBackImg" src="http://qiniu.smileyi.top/login-banner">
</el-col>
<el-col :span="12" style="border:solid thin black;">
<el-tabs v-model="activeName">
<el-tab-pane label="微信扫码登录" name="wechat">
微信扫码登录主体
<el-col :span="12">
<el-tabs v-if="formType=='login'" v-model="loginType" class="login-form-tab" style="width: 320px;"
@tab-click="loginTypeHandleClick"
>
<el-tab-pane label="微信扫码登录" name="wx">
<div class="wx-login">
<div style="text-align: center;">
<img class="qrcode" :src="wxLoginQrCode">
</div>
<div style="text-align: center;">
<el-link icon="el-icon-refresh-left" :underline="false">刷新二维码</el-link>
</div>
<el-divider style="width: 100px;" />
<div style="display: flex; flex-direction: row;">
<el-link style="margin-left: 20px;">忘记密码</el-link>
<el-link style="margin-left: 20px;" @click="()=>{this.formType='reg'}">立即注册</el-link>
<div class="other-login">
<svg-icon name="loginQQ" class="other-login-icon" />
<svg-icon name="loginWx" class="other-login-icon" />
</div>
</div>
<el-divider style="width: 100px;" />
<p class="login-tip">关于TDuckCloud登录</p>
<p class="login-tip">
若微信扫码失败请打开 微信授权页面 登录
若QQ登录填鸭云异常可查阅 帮助文档
若因微信QQ公众号冻结或账号密码找回失败等无法登录 自助申请 登录账号
</p>
</div>
</el-tab-pane>
<el-tab-pane label="账号密码登录" name="account">
账号密码登录主体
<el-form ref="form" label-position="top" size="small" :model="account"
class="account-login-form"
>
<el-form-item label="手机号/邮箱登录">
<el-input v-model="account.username" placeholder="请输入手机号/邮箱" />
</el-form-item>
<el-form-item label="密码">
<el-input v-model="account.password" placeholder="请输入密码" show-password />
</el-form-item>
<el-form-item label="">
<el-radio v-model="agreeProtocol" label="" />
<span class="protocol-tip">我已同意</span>
<el-link :underline="false" type="primary" class="protocol-tip">TDuck用户服务协议</el-link>
<el-link :underline="false" type="primary" class="protocol-tip" style="float: right;">忘记密码</el-link>
</el-form-item>
<el-form-item>
<el-button style="width: 100%;" type="primary">登录</el-button>
</el-form-item>
<el-form-item style="text-align: center;">
<span class="protocol-tip">使用第三方登录 </span>
<el-link :underline="false" type="primary" class="protocol-tip" @click="()=>{this.formType='reg'}">立即注册</el-link>
<div class="other-login">
<svg-icon name="loginQQ" class="other-login-icon" />
<svg-icon name="loginWx" class="other-login-icon" />
</div>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
<div style="height:70px;border:solid thin black;"></div><!-- 暂时用来撑下面的高度等菜🐎把图片确定以后再把此div移除 -->
<el-tabs v-if="formType==='reg'" v-model="regType" class="login-form" style="width: 300px;"
@tab-click="loginTypeHandleClick"
>
<el-tab-pane label="手机号注册" name="regPhone">
<el-form ref="form" :model="account" label-width="0px">
<el-form-item label="">
<el-input v-model="account.username" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="">
<el-input v-model="account.password" placeholder="请输入密码" />
</el-form-item>
<el-form-item label="">
<el-input v-model="account.code" style="width: 150px;" placeholder="请输入验证码" />
<el-button style="margin-left: 20px;" type="primary">发送验证码</el-button>
</el-form-item>
<el-form-item>
<el-button type="primary" width: 100%;>登录</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="邮箱注册" name="regEmail">
<el-form ref="form" :model="account" label-width="0px">
<el-form-item label="">
<el-input v-model="account.username" placeholder="请输入邮箱" />
</el-form-item>
<el-form-item label="">
<el-input v-model="account.password" placeholder="请输入密码" />
</el-form-item>
<el-form-item label="">
<el-input v-model="account.code" style="width: 150px;" placeholder="请输入验证码" />
<el-button style="margin-left: 20px;" type="primary">发送验证码</el-button>
</el-form-item>
<el-form-item>
<el-button type="primary" style="width: 100%;">确定</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<div style="display: flex; flex-direction: row;">
<el-link style="margin-left: 20px;">忘记密码</el-link>
<el-link style="margin-left: 20px;" @click="reigsterHandleClick">立即注册</el-link>
<div class="other-login">
<svg-icon name="loginQQ" class="other-login-icon" />
<svg-icon name="loginWx" class="other-login-icon" />
</div>
</div>
</el-tabs>
</el-col>
</el-row>
</div>
</template>
<script>
export default {
name: 'login',
name: 'Login',
data() {
return {
activeName: 'wechat'
loginType: 'wx',
formType: 'reg',
regType: 'regPhone',
agreeProtocol: '',
account: {
username: '',
password: ''
},
wxLoginQrCode: 'http://qiniu.smileyi.top/20191213/9f217b754ad44f0caaa83040ce62fe93.png'
}
},
methods: {
loginTypeHandleClick() {
},
reigsterHandleClick() {
},
login() {
this.$store.dispatch('token/login').then(() => {
//
if (this.$route.query.redirect) {
this.$router.replace({
path: this.$route.query.redirect
})
} else {
if (window.history.length <= 1) {
this.$router.push({path: '/'})
} else {
this.$router.go(-1)
}
}
})
}
}
}
</script>
<style scoped>
.loginBody {
border: solid thin black;
margin: 70px 0px 0px 0px;
/* border: solid thin black; */
margin: 70px 0 0 0;
}
.loginBackImg {
width: 100%;
}
.login-form-tab {
margin-left: 100px;
height: 500px;
width: 520px;
.wx-login {
display: flex;
align-content: center;
align-items: center;
justify-content: center;
flex-direction: column;
.qrcode {
margin: 20px;
}
}
}
.other-login {
margin-left: auto;
}
.login-tip {
color: rgba(16, 16, 16, 1);
font-size: 13px;
font-weight: 400;
text-align: left;
line-height: 20px;
}
.account-login-form .el-form-item--small.el-form-item {
margin-bottom: 5px;
}
.account-login-form .el-radio {
margin-right: 0;
}
.account-login-form .protocol-tip {
font-size: 12px;
}
</style>

4
src/views/common/enterprise/index.vue

@ -1,11 +1,11 @@
<template>
<div id="enterprise" style="height:690px;/*开发时请将此样式移除*/">
<div id="enterprise" style="height: 690px; /* 开发时请将此样式移除 */">
企业部署
</div>
</template>
<script>
export default {
name: 'enterprise',
name: 'Enterprise',
data() {
return { }
}

4
src/views/common/proposal/index.vue

@ -1,11 +1,11 @@
<template>
<div id="proposal" style="height:690px;/*开发时请将此样式移除*/">
<div id="proposal" style="height: 690px; /* 开发时请将此样式移除 */">
提出建议
</div>
</template>
<script>
export default {
name: 'proposal',
name: 'Proposal',
data() {
return { }
}

4
src/views/common/sources/index.vue

@ -1,11 +1,11 @@
<template>
<div id="sources" style="height:690px;/*开发时请将此样式移除*/">
<div id="sources" style="height: 690px; /* 开发时请将此样式移除 */">
开源版本
</div>
</template>
<script>
export default {
name: 'sources',
name: 'Sources',
data() {
return { }
}

52
src/views/example/axios.vue

@ -1,52 +0,0 @@
signMd5Utils.js
<template>
<div>
<button type="button" @click="getInfo">获取数据</button>
<button type="button" @click="getTest">验签</button>
<img v-for="(item, index) in banner" :key="index" :src="item.image">
</div>
</template>
<script>
export default {
data() {
return {
banner: []
}
},
methods: {
getTest() {
this.$api.post('/api/v1/user/add').then(res => {
console.log(res)
})
},
getInfo() {
this.$axios.all([
this.$api.get('banner/list', {
params: {
categoryid: 1
}
}),
this.$api.get('banner/list', {
params: {
categoryid: 2
}
})
]).then(
this.$axios.spread((acct, perms) => {
this.banner = acct.data.banner.concat(
perms.data.banner
)
})
)
}
}
}
</script>
<style lang="scss" scoped>
img {
display: block;
width: 300px;
}
</style>

21
src/views/example/component.vue

@ -1,21 +0,0 @@
<template>
<div>
<p>这是一个非全局组件需要在页面上引用该组件才能使用</p>
<ExampleList :list="list" />
</div>
</template>
<script>
import ExampleList from '@/components/ExampleList'
export default {
components: {
ExampleList
},
data() {
return {
list: ['张三', '李四', '王五']
}
}
}
</script>

31
src/views/example/cookie.vue

@ -1,31 +0,0 @@
<template>
<div>
<button type="button" @click="setCookie">设置cookie</button>
<button type="button" @click="removeCookie">删除cookie</button>
<button type="button" @click="isSetCookie">判断cookie是否设置</button>
<div>a的cookie值是{{ cookie }}</div>
</div>
</template>
<script>
export default {
data() {
return {
cookie: ''
}
},
methods: {
setCookie() {
this.$cookies.set('a', 'abc')
this.cookie = this.$cookies.get('a')
},
removeCookie() {
this.$cookies.remove('a', 'abc')
this.cookie = this.$cookies.get('a')
},
isSetCookie() {
alert(this.$cookies.isKey('a'))
}
}
}
</script>

25
src/views/example/global.component.vue

@ -1,25 +0,0 @@
<template>
<div>
<p>全局组件会自动注册</p>
<p>使用方法</p>
<ol>
<li>全局组件统一放在 ./src/components/global/ 目录下需要注意各个组件按文件夹区分文件夹名称与组件名无关联</li>
<li>文件夹内至少保留一个文件名为 index 的组件入口例如 index.vue</li>
<li>普通组件必须设置 name 并保证其唯一自动注册会将组件的 name 设为组件名可参考 <RouterLink to="/example/svgicon">SvgIcon</RouterLink> 组件写法</li>
<li>如果组件是通过 js 进行调用则确保组件入口文件为 index.js下面演示 ExampleNotice 组件通过 js 调用并展示 Notice</li>
</ol>
<a href="javascript:;" @click="showNotice">显示Notice</a>
</div>
</template>
<script>
export default {
methods: {
showNotice() {
this.$exampleNotice({
content: '我是Notice!'
})
}
}
}
</script>

18
src/views/example/meta.vue

@ -1,18 +0,0 @@
<template>
<div>注意 title 的变化</div>
</template>
<script>
export default {
data() {
return {
title: '我是这个页面的title噢'
}
},
metaInfo() {
return {
title: this.title
}
}
}
</script>

5
src/views/example/params.vue

@ -1,5 +0,0 @@
<template>
<div>
<div>params:{{ $route.params.test }}</div>
</div>
</template>

20
src/views/example/permission.js.vue

@ -1,20 +0,0 @@
<template>
<div>
<p>如果未登录会跳转到登录页如果已登录则弹出用户信息</p>
<button @click="user">点我</button>
</div>
</template>
<script>
export default {
methods: {
user() {
if (this.$store.getters['token/isLogin']) {
alert('token信息:' + this.$store.state.token.token)
} else {
this.$toLogin()
}
}
}
}
</script>

3
src/views/example/permission.router.vue

@ -1,3 +0,0 @@
<template>
<div>token信息{{ $store.state.token.token }}</div>
</template>

5
src/views/example/query.vue

@ -1,5 +0,0 @@
<template>
<div>
<div>query:{{ $route.query.test }}</div>
</div>
</template>

26
src/views/example/reload.vue

@ -1,26 +0,0 @@
<template>
<div>
<p>可以修改一下 input 框内的值然后点击刷新按钮查看效果</p>
<p>
<input v-model="value" type="text">
<button type="button" @click="plus">+1</button>
</p>
<button type="button" @click="reload">刷新</button>
</div>
</template>
<script>
export default {
inject: ['reload'],
data() {
return {
value: 0
}
},
methods: {
plus() {
this.value += 1
}
}
}
</script>

50
src/views/example/sprite.vue

@ -1,50 +0,0 @@
<template>
<div class="sprites">
<div class="address" />
<div class="feedback" />
<div class="payment" />
<div class="info">
vue.config.js 里配置精灵图路径等信息如果要新增一个精灵图目录则先复制一份 new SpritesmithPlugin() 修改目录名和文件名然后重新运行 serve 任务即可
</div>
<img :src="logo" class="logo">
</div>
</template>
<script>
export default {
data() {
return {
logo: ''
}
},
created() {
// js require
this.logo = require('../../assets/images/example.png')
}
}
</script>
<style lang="scss" scoped>
.sprites {
padding: 10px;
.address,
.feedback,
.payment {
display: inline-block;
margin-right: 10px;
}
.address {
@include example-sprite(address);
}
.feedback {
@include example-sprite(feedback);
}
.payment {
@include example-sprite(payment);
}
}
.logo {
width: 200px;
height: 200px;
}
</style>

18
src/views/example/svgicon.vue

@ -1,18 +0,0 @@
<template>
<div>
<p>这是两个 Svg Icon 图标</p>
<svg-icon icon-class="example" class-name="example-icon" />
<svg-icon icon-class="example.color" class-name="example-icon" />
<p>使用方法</p>
<ol>
<li> <a href="https://www.iconfont.cn/" target="_blank">Iconfont</a> 下载需要的 svg 图标</li>
<li> svg 文件放入 assets/icons 目录文件名即为 icon-class</li>
</ol>
</div>
</template>
<style scoped>
.example-icon {
font-size: 48px;
}
</style>

100
src/views/example/user.vue

@ -1,100 +0,0 @@
<template>
<div>
<el-form :inline="true" :model="formInline" class="demo-form-inline">
<el-form-item label="审批人">
<el-input v-model="username" placeholder="审批人"></el-input>
</el-form-item>
</el-form>
<el-button type="primary" @click="getData">查询</el-button>
<el-table
:data="tableData">
<el-table-column
prop="id"
label="Id"
width="180">
</el-table-column>
<el-table-column
prop="date"
label="日期"
width="180">
</el-table-column>
<el-table-column
prop="name"
label="姓名"
width="180">
</el-table-column>
<el-table-column
prop="gender"
label="性别"
width="180">
</el-table-column>
<el-table-column
label="操作"
width="100">
<template slot-scope="scope">
<el-button @click="handleDeleteClick(scope.row,scope)" type="text" size="small">删除</el-button>
<el-button type="text" size="small">编辑</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="current"
:page-sizes="[10, 20, 50, 100]"
:page-size="size"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
</template>
<script>
export default {
name: 'user',
data() {
return {
tableData: [],
current: 1,
size: 10,
total: 0,
username: ''
}
},
methods: {
handleSizeChange(size) {
this.size = size
this.getData()
},
handleCurrentChange(current) {
this.current = current
this.getData()
},
handleDeleteClick(row, scope) {
this.$api.post(`/api/v1/user/delete?id=${row.id}`).then(res => {
this.$message({
message: '删除成功',
type: 'success'
})
})
},
getData() {
let params = {current: this.current, size: this.size, name: this.username}
this.$api.get('/api/v1/user/page', {
params: params
}).then(res => {
this.tableData = res.data.records
this.size = res.data.size
this.total = res.data.total
this.current = res.data.current
})
}
},
mounted() {
this.getData()
}
}
</script>
<style scoped>
</style>

42
src/views/example/vuex.vue

@ -1,42 +0,0 @@
<template>
<div>
<button type="button" @click="getInfo">获取数据</button>
<button type="button" @click="removeLast">删除最后一条数据</button>
<button type="button" @click="getLength">获取数据长度</button>
<img v-for="(item, index) in banner" :key="index" :src="item.image">
<div v-if="bannerCount">现在你可以切换路由你会发现切换回来后数据还在</div>
</div>
</template>
<script>
import { mapGetters, mapActions, mapMutations, mapState } from 'vuex'
export default {
computed: {
...mapState({
banner: state => state.example.banner
}),
...mapGetters({
bannerCount: 'example/bannerCount'
})
},
methods: {
...mapActions({
getInfo: 'example/getBanner'
}),
...mapMutations({
removeLast: 'example/removeLast'
}),
getLength() {
alert(this.bannerCount)
}
}
}
</script>
<style lang="scss" scoped>
img {
display: block;
width: 300px;
}
</style>

76
vue.config.js

@ -1,7 +1,8 @@
const fs = require('fs')
const path = require('path')
const spritesmithPlugin = require('webpack-spritesmith')
// 基础路径 注意发布之前要先修改这里
const terserPlugin = require('terser-webpack-plugin')
const cdnDependencies = require('./dependencies.cdn')
const spritesmithTasks = []
fs.readdirSync('src/assets/sprites').map(dirname => {
@ -22,7 +23,7 @@ fs.readdirSync('src/assets/sprites').map(dirname => {
]
},
customTemplates: {
'handlebars_based_template': path.resolve(__dirname, 'scss.template.handlebars')
'handlebars_based_template': path.resolve(__dirname, 'scss.template.hbs')
},
// 样式文件中调用雪碧图地址写法
apiOptions: {
@ -37,32 +38,57 @@ fs.readdirSync('src/assets/sprites').map(dirname => {
}
})
// CDN 相关
const isCDN = process.env.VUE_APP_CDN == 'ON'
const externals = {}
cdnDependencies.forEach(pkg => {
externals[pkg.name] = pkg.library
})
const cdn = {
css: cdnDependencies.map(e => e.css).filter(e => e),
js: cdnDependencies.map(e => e.js).filter(e => e)
}
module.exports = {
publicPath: '',
lintOnSave: true,
productionSourceMap: false,
devServer: {
disableHostCheck: true,
open: true,
host: '0.0.0.0',
port: '8080',
port: 8888,
// 开发环境默认开启反向代理,如果不需要请自行注释
proxy: {
'/api': {
target: 'http://localhost:8999', // 要请求的地址
ws: true,
changeOrigin: true,
pathRewrite: {
'^/api': '/api'
}
'/': {
target: process.env.VUE_APP_API_ROOT,
changeOrigin: true
}
}
},
configureWebpack: {
resolve: {
modules: ['node_modules', 'assets/sprites']
},
plugins: [
...spritesmithTasks
]
configureWebpack: config => {
config.resolve.modules = ['node_modules', 'assets/sprites']
config.plugins.push(...spritesmithTasks)
if (isCDN) {
config.externals = externals
}
config.optimization = {
minimizer: [
new terserPlugin({
terserOptions: {
compress: {
warnings: false,
drop_console: true,
drop_debugger: true,
pure_funcs: ['console.log']
}
}
})
]
}
},
pluginOptions: {
lintStyleOnBuild: true,
stylelint: {
fix: true
}
},
chainWebpack: config => {
const oneOfsMap = config.module.rule('scss').oneOfs.store
@ -92,5 +118,15 @@ module.exports = {
symbolId: 'icon-[name]'
})
.end()
config.plugin('html')
.tap(args => {
args[0].title = process.env.VUE_APP_TITLE
if (isCDN) {
args[0].cdn = cdn
}
args[0].debugTool = process.env.VUE_APP_DEBUG_TOOL
return args
})
.end()
}
}

7619
yarn.lock

File diff suppressed because it is too large
Loading…
Cancel
Save