Browse Source

1.2.3 后台操作按钮调整及其新增开启拼团功能

master
hupeng 5 years ago
commit
4faa83dc0c
  1. 17
      .babelrc
  2. 14
      .editorconfig
  3. 3
      .eslintignore
  4. 196
      .eslintrc.js
  5. 16
      .gitignore
  6. 10
      .postcssrc.js
  7. 5
      .travis.yml
  8. 191
      LICENSE
  9. 145
      README.md
  10. 67
      build/build.js
  11. 62
      build/check-versions.js
  12. 108
      build/utils.js
  13. 5
      build/vue-loader.conf.js
  14. 122
      build/webpack.base.conf.js
  15. 98
      build/webpack.dev.conf.js
  16. 188
      build/webpack.prod.conf.js
  17. 9
      config/dev.env.js
  18. 106
      config/index.js
  19. 5
      config/prod.env.js
  20. 1
      debug.log
  21. BIN
      favicon.ico
  22. 22
      index.html
  23. 106
      package.json
  24. 11
      src/App.vue
  25. 25
      src/api/alipay.js
  26. 16
      src/api/code.js
  27. 9
      src/api/data.js
  28. 32
      src/api/dept.js
  29. 24
      src/api/dict.js
  30. 50
      src/api/dictDetail.js
  31. 24
      src/api/email.js
  32. 16
      src/api/genConfig.js
  33. 9
      src/api/generator.js
  34. 37
      src/api/job.js
  35. 32
      src/api/localStorage.js
  36. 8
      src/api/log.js
  37. 28
      src/api/login.js
  38. 39
      src/api/menu.js
  39. 32
      src/api/permission.js
  40. 16
      src/api/picture.js
  41. 45
      src/api/qiniu.js
  42. 35
      src/api/redis.js
  43. 62
      src/api/role.js
  44. 38
      src/api/timing.js
  45. 52
      src/api/user.js
  46. 22
      src/api/visits.js
  47. 31
      src/api/yxArticle.js
  48. 31
      src/api/yxCache.js
  49. 32
      src/api/yxStoreCategory.js
  50. 32
      src/api/yxStoreCombination.js
  51. 24
      src/api/yxStoreCoupon.js
  52. 24
      src/api/yxStoreCouponIssue.js
  53. 24
      src/api/yxStoreCouponIssueUser.js
  54. 24
      src/api/yxStoreCouponUser.js
  55. 32
      src/api/yxStoreOrder.js
  56. 24
      src/api/yxStoreOrderStatus.js
  57. 24
      src/api/yxStorePink.js
  58. 72
      src/api/yxStoreProduct.js
  59. 24
      src/api/yxStoreProductReply.js
  60. 24
      src/api/yxStoreVisit.js
  61. 32
      src/api/yxSystemConfig.js
  62. 24
      src/api/yxSystemGroupData.js
  63. 32
      src/api/yxUser.js
  64. 24
      src/api/yxUserBill.js
  65. 24
      src/api/yxUserExtract.js
  66. 32
      src/api/yxWechatReply.js
  67. BIN
      src/assets/401_images/401.gif
  68. BIN
      src/assets/404_images/404.png
  69. BIN
      src/assets/404_images/404_cloud.png
  70. BIN
      src/assets/avatar/avatar.png
  71. BIN
      src/assets/logo/logo.png
  72. BIN
      src/assets/wechat/head.gif
  73. BIN
      src/assets/wechat/index.png
  74. BIN
      src/assets/wechat/mobile_foot.png
  75. BIN
      src/assets/wechat/mobile_head.png
  76. 74
      src/components/Breadcrumb/index.vue
  77. 47
      src/components/Hamburger/index.vue
  78. 69
      src/components/IconSelect/index.vue
  79. 11
      src/components/IconSelect/requireIcons.js
  80. 140
      src/components/PanThumb/index.vue
  81. 165
      src/components/RightPanel/index.vue
  82. 78
      src/components/Screenfull/index.vue
  83. 92
      src/components/ScrollPane/index.vue
  84. 43
      src/components/SvgIcon/index.vue
  85. 113
      src/components/TextHoverEffect/Mallki.vue
  86. 29
      src/components/TreeTable/eval.js
  87. 127
      src/components/TreeTable/index.vue
  88. 89
      src/components/TreeTable/readme.md
  89. 81
      src/components/YamlEdit/index.vue
  90. 30
      src/components/iframe/index.vue
  91. 94
      src/components/mul-pic-upload/index.vue
  92. 13
      src/components/permission/index.js
  93. 22
      src/components/permission/permission.js
  94. 82
      src/components/pic-upload-two/index.vue
  95. 82
      src/components/pic-upload/index.vue
  96. 82
      src/components/pic-upload2/index.vue
  97. 65
      src/config/index.js
  98. 9
      src/icons/index.js
  99. 1
      src/icons/svg/Steve-Jobs.svg
  100. 2
      src/icons/svg/add.svg
  101. Some files were not shown because too many files have changed in this diff Show More

17
.babelrc

@ -0,0 +1,17 @@
{
"presets": [
["env", {
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}],
"stage-2"
],
"plugins":["transform-vue-jsx", "transform-runtime"],
"env": {
"development":{
"plugins": ["dynamic-import-node"]
}
}
}

14
.editorconfig

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

3
.eslintignore

@ -0,0 +1,3 @@
build/*.js
config/*.js
src/assets

196
.eslintrc.js

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

16
.gitignore vendored

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

10
.postcssrc.js

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

5
.travis.yml

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

191
LICENSE

@ -0,0 +1,191 @@
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:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
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
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 2018 Elune
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.

145
README.md

@ -0,0 +1,145 @@
<h1 style="text-align: center">yshop意象商城系统</h1>
#### 项目简介
yshop基于当前流行技术组合: SpringBoot2+Jpa+MybatisPlus+SpringSecurity+jwt+redis+Vue的前后端分离的商城系统, 包含商城、拼团、砍价、
秒杀、优惠券、积分、分销等功能,更适合企业或个人二次开发;
**开发文档** 【[查看文档](https://gitee.com/guchengwuyue/yshopmall/wikis/%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83?sort_id=1718722)】
#### 体验地址
| | 后台系统 | 前端(公众号) |
|--- |--- | --- |
| | https://yshop.dayouqiantu.cn |g公众号:YshopMall |
| | 后台体验账号/密码:admin/123456 | 公众号:![输入图片说明](https://images.gitee.com/uploads/images/2019/1116/060936_fd73496c_477893.jpeg "qrcode_for_gh_95df5a2881cc_258.jpg") |
#### 项目源码
| | 后台系统源码 | 后台系统前端源码 |
|--- |--- | --- |
| 码云 | https://gitee.com/guchengwuyue/yshopmall | https://gitee.com/guchengwuyue/yshopmall_qd |
| github | https://github.com/guchengwuyue/yshopmall |https://github.com/guchengwuyue/yshopmall_qd |
#### 开源版本与VIP版本说明
### 开源版
1.包括整个商城系统后台、数据库、api(只是简单的配置好模块);
2.本版本本身属于独立后台商城管理系统、可独立作为cms、商城等等后台使用;
3.可以个人、企业直接使用。
### VIP版
1.包括整个商城系统后台、数据库、API、H5;
2.本版本是演示的所有功能代码;
3.加入VIP、享有后续所有功能免费升级及其技术支持等。
4、VIP为终身,【[详情请查看](https://gitee.com/guchengwuyue/yshopmall/wikis/pages?sort_id=1715823&doc_id=441578)】
## 商城功能
* 一:商品模块:商品添加、规格设置,商品上下架等
* 二:订单模块:下单、购物车、支付,发货、收货、评价、退款等
* 三:营销模块:积分、优惠券、分销、砍价、拼团、秒杀(、到店核销等
* 四:微信模块:自定义菜单、自动回复、微信授权、图文管理、模板消息推送
* 五:配置模块:各种配置
* 六:用户模块:登陆、注册、会员卡等
* 七:其他等
#### 已经完成功能
- 可以具体查看演示地址查看当前版本已经完成的功能,不再絮叨啦
#### 项目结构
项目采用分模块开发方式
- yshop-api 公众号(H5)API模块
- yshop-mp 微信相关模块
- yshop-common 公共模块
- yshop-system 商城系统模块
- yshop-logging 日志模块
- yshop-tools 第三方工具模块
- yshop-generator 代码生成模块
#### 系统预览
<table>
<tr>
<td><img src="https://images.gitee.com/uploads/images/2019/1107/194017_9207632f_477893.png"/></td>
<td><img src="https://images.gitee.com/uploads/images/2019/1107/194030_70f5ce92_477893.png"/></td>
</tr>
<tr>
<td><img src="https://images.gitee.com/uploads/images/2019/1107/194114_cd0b06ce_477893.png"/></td>
<td><img src="https://images.gitee.com/uploads/images/2019/1107/194130_abb4f788_477893.png"/></td>
</tr>
<tr>
<td><img src="https://images.gitee.com/uploads/images/2019/1107/194147_63de73f4_477893.png"/></td>
<td><img src="https://images.gitee.com/uploads/images/2019/1107/194207_7b3b1f53_477893.png"/></td>
</tr>
<tr>
<td><img src="https://images.gitee.com/uploads/images/2019/1107/194217_3f47214f_477893.png"/></td>
<td></td>
</tr>
</table>
<table>
<tr>
<td><img src="https://images.gitee.com/uploads/images/2019/1107/201443_df1cc3a6_477893.png"/></td>
<td><img src="https://images.gitee.com/uploads/images/2019/1107/201540_ef6123a9_477893.png"/></td>
</tr>
<tr>
<td><img src="https://images.gitee.com/uploads/images/2019/1107/201601_acd0ccd3_477893.png"/></td>
<td><img src="https://images.gitee.com/uploads/images/2019/1107/201622_f913b59c_477893.png"/></td>
</tr>
<tr>
<td><img src="https://images.gitee.com/uploads/images/2019/1107/201646_5bc6df48_477893.png"/></td>
<td><img src="https://images.gitee.com/uploads/images/2019/1107/201706_32ee305c_477893.png"/></td>
</tr>
</table>
## 技术选型
* 1 后端使用技术
* 1.1 SpringBoot
* 1.2 mybatis、MyBatis-Plus
* 1.3 SpringSecurity
* 1.4 JAP
* 1.5 Druid1
* 1.6 Slf4j
* 1.7 Fastjson
* 1.8 JWT
* 1.9 Redis
* 1.10 Quartz
* 1.11 Mysql
* 1.12 swagger
* 1.13 WxJava
* 1.14 Lombok
* 1.15 Hutool
* 1.16 Mapstruct
* 前端使用技术
* 2.1 Vue 全家桶
* 2.2 Element
#### 项目发布明细
1.0版本
1.1版本新增积分与优惠券抵扣
1.2版本分销功能已经发布
1.2.1增加了未付款订单取消功能库存销量退出、优惠券、积分功能,个人中心增加了积分流水
#### 反馈交流
- QQ交流群:907721261
- 喜欢这个商城后台的小伙伴留下你的小星星啦,star,star哦!
#### 特别鸣谢
- eladmin:https://github.com/elunez/eladmin
- mybaitsplus:https://github.com/baomidou/mybatis-plus
- hutool:https://github.com/looly/hutool
- wxjava:https://github.com/Wechat-Group/WxJava
- vue:https://github.com/vuejs/vue
- element:https://github.com/ElemeFE/element

67
build/build.js

@ -0,0 +1,67 @@
'use strict'
require('./check-versions')()
const ora = require('ora')
const rm = require('rimraf')
const path = require('path')
const chalk = require('chalk')
const webpack = require('webpack')
const config = require('../config')
const webpackConfig = require('./webpack.prod.conf')
var connect = require('connect')
var serveStatic = require('serve-static')
const spinner = ora(
'building for ' + process.env.env_config + ' environment...'
)
spinner.start()
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
if (err) throw err
webpack(webpackConfig, (err, stats) => {
spinner.stop()
if (err) throw err
process.stdout.write(
stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n\n'
)
if (stats.hasErrors()) {
console.log(chalk.red(' Build failed with errors.\n'))
process.exit(1)
}
console.log(chalk.cyan(' Build complete.\n'))
console.log(
chalk.yellow(
' Tip: built files are meant to be served over an HTTP server.\n' +
" Opening index.html over file:// won't work.\n"
)
)
if (process.env.npm_config_preview) {
const port = 9526
const host = 'http://localhost:' + port
const basePath = config.build.assetsPublicPath
const app = connect()
app.use(
basePath,
serveStatic('./dist', {
index: ['index.html', '/']
})
)
app.listen(port, function() {
console.log(
chalk.green(`> Listening at http://localhost:${port}${basePath}`)
)
})
}
})
})

62
build/check-versions.js

@ -0,0 +1,62 @@
'use strict'
const chalk = require('chalk')
const semver = require('semver')
const packageConfig = require('../package.json')
const shell = require('shelljs')
function exec(cmd) {
return require('child_process')
.execSync(cmd)
.toString()
.trim()
}
const versionRequirements = [
{
name: 'node',
currentVersion: semver.clean(process.version),
versionRequirement: packageConfig.engines.node
}
]
if (shell.which('npm')) {
versionRequirements.push({
name: 'npm',
currentVersion: exec('npm --version'),
versionRequirement: packageConfig.engines.npm
})
}
module.exports = function() {
const warnings = []
for (let i = 0; i < versionRequirements.length; i++) {
const mod = versionRequirements[i]
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
warnings.push(
mod.name +
': ' +
chalk.red(mod.currentVersion) +
' should be ' +
chalk.green(mod.versionRequirement)
)
}
}
if (warnings.length) {
console.log('')
console.log(
chalk.yellow(
'To use this template, you must update following to modules:'
)
)
for (let i = 0; i < warnings.length; i++) {
const warning = warnings[i]
console.log(' ' + warning)
}
process.exit(1)
}
}

108
build/utils.js

@ -0,0 +1,108 @@
'use strict'
const path = require('path')
const config = require('../config')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const packageConfig = require('../package.json')
exports.assetsPath = function(_path) {
const assetsSubDirectory =
process.env.NODE_ENV === 'production'
? config.build.assetsSubDirectory
: config.dev.assetsSubDirectory
return path.posix.join(assetsSubDirectory, _path)
}
exports.cssLoaders = function(options) {
options = options || {}
const cssLoader = {
loader: 'css-loader',
options: {
sourceMap: options.sourceMap
}
}
const postcssLoader = {
loader: 'postcss-loader',
options: {
sourceMap: options.sourceMap
}
}
// generate loader string to be used with extract text plugin
function generateLoaders(loader, loaderOptions) {
const loaders = []
// Extract CSS when that option is specified
// (which is the case during production build)
if (options.extract) {
loaders.push(MiniCssExtractPlugin.loader)
} else {
loaders.push('vue-style-loader')
}
loaders.push(cssLoader)
if (options.usePostCSS) {
loaders.push(postcssLoader)
}
if (loader) {
loaders.push({
loader: loader + '-loader',
options: Object.assign({}, loaderOptions, {
sourceMap: options.sourceMap
})
})
}
return loaders
}
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
return {
css: generateLoaders(),
postcss: generateLoaders(),
less: generateLoaders('less'),
sass: generateLoaders('sass', {
indentedSyntax: true
}),
scss: generateLoaders('sass'),
stylus: generateLoaders('stylus'),
styl: generateLoaders('stylus')
}
}
// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function(options) {
const output = []
const loaders = exports.cssLoaders(options)
for (const extension in loaders) {
const loader = loaders[extension]
output.push({
test: new RegExp('\\.' + extension + '$'),
use: loader
})
}
return output
}
exports.createNotifierCallback = () => {
const notifier = require('node-notifier')
return (severity, errors) => {
if (severity !== 'error') return
const error = errors[0]
const filename = error.file && error.file.split('!').pop()
notifier.notify({
title: packageConfig.name,
message: severity + ': ' + error.name,
subtitle: filename || '',
icon: path.join(__dirname, 'logo.png')
})
}
}

5
build/vue-loader.conf.js

@ -0,0 +1,5 @@
'use strict'
module.exports = {
//You can set the vue-loader configuration by yourself.
}

122
build/webpack.base.conf.js

@ -0,0 +1,122 @@
'use strict'
const path = require('path')
const utils = require('./utils')
const config = require('../config')
const { VueLoaderPlugin } = require('vue-loader')
const os = require('os');
const HappyPack = require('happypack');
const happThreadPool = HappyPack.ThreadPool({size: os.cpus().length});
function resolve(dir) {
return path.join(__dirname, '..', dir)
}
const createLintingRule = () => ({
// test: /\.(js|vue)$/,
// loader: 'eslint-loader',
// enforce: 'pre',
// include: [resolve('src'), resolve('test')],
// options: {
// formatter: require('eslint-friendly-formatter'),
// emitWarning: !config.dev.showEslintErrorsInOverlay
// }
})
module.exports = {
cache: true,
context: path.resolve(__dirname, '../'),
entry:["babel-polyfill","./src/main.js"],
output: {
path: config.build.assetsRoot,
filename: '[name].js',
publicPath:
process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
},
resolve: {
modules: [path.resolve(__dirname, '../node_modules')],
extensions: ['.js', '.vue', '.json'],
alias: {
'@': resolve('src'),
api: path.resolve(__dirname, '../src/api')
}
},
module: {
rules: [
...(config.dev.useEslint ? [createLintingRule()] : []),
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
js: 'happypack/loader?id=js' // 将loader换成happypack
}
}
},
{
test: /\.js$/,
loader: ['happypack/loader?id=js'], // 将loader换成happypack
include: [
resolve('src')
], // src是项目开发的目录
exclude: [path.resolve('../node_modules')] // 不需要编译node_modules下的js
},
{
test: /\.svg$/,
loader: 'svg-sprite-loader',
include: [resolve('src/icons')],
options: {
symbolId: 'icon-[name]'
}
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
exclude: [resolve('src/icons')],
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('media/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
]
},
plugins: [
new VueLoaderPlugin(),
new HappyPack({
id: 'js',
cache: true,
loaders: ['babel-loader?cacheDirectory=true'],
threadPool: happThreadPool
})
],
node: {
// prevent webpack from injecting useless setImmediate polyfill because Vue
// source contains it (although only uses it if it's native).
setImmediate: false,
// prevent webpack from injecting mocks to Node native modules
// that does not make sense for the client
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty'
}
}

98
build/webpack.dev.conf.js

@ -0,0 +1,98 @@
'use strict'
const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const portfinder = require('portfinder')
function resolve(dir) {
return path.join(__dirname, '..', dir)
}
const HOST = process.env.HOST
const PORT = process.env.PORT && Number(process.env.PORT)
const devWebpackConfig = merge(baseWebpackConfig, {
mode: 'development',
module: {
rules: utils.styleLoaders({
sourceMap: config.dev.cssSourceMap,
usePostCSS: true
})
},
// cheap-module-eval-source-map is faster for development
devtool: config.dev.devtool,
// these devServer options should be customized in /config/index.js
devServer: {
clientLogLevel: 'warning',
historyApiFallback: true,
hot: true,
compress: true,
host: HOST || config.dev.host,
port: PORT || config.dev.port,
open: config.dev.autoOpenBrowser,
overlay: config.dev.errorOverlay
? { warnings: false, errors: true }
: false,
publicPath: config.dev.assetsPublicPath,
proxy: config.dev.proxyTable,
quiet: true, // necessary for FriendlyErrorsPlugin
watchOptions: {
poll: config.dev.poll
}
},
plugins: [
new webpack.DefinePlugin({
'process.env': require('../config/dev.env')
}),
new webpack.HotModuleReplacementPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true,
favicon: resolve('favicon.ico'),
title: 'vue-element-admin',
templateParameters: {
BASE_URL: config.dev.assetsPublicPath + config.dev.assetsSubDirectory,
},
}),
]
})
module.exports = new Promise((resolve, reject) => {
portfinder.basePort = process.env.PORT || config.dev.port
portfinder.getPort((err, port) => {
if (err) {
reject(err)
} else {
// publish the new Port, necessary for e2e tests
process.env.PORT = port
// add port to devServer config
devWebpackConfig.devServer.port = port
// Add FriendlyErrorsPlugin
devWebpackConfig.plugins.push(
new FriendlyErrorsPlugin({
compilationSuccessInfo: {
messages: [
`Your application is running here: http://${
devWebpackConfig.devServer.host
}:${port}`
]
},
onErrors: config.dev.notifyOnErrors
? utils.createNotifierCallback()
: undefined
})
)
resolve(devWebpackConfig)
}
})
})

188
build/webpack.prod.conf.js

@ -0,0 +1,188 @@
'use strict'
const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
function resolve(dir) {
return path.join(__dirname, '..', dir)
}
const env = require('../config/' + process.env.env_config + '.env')
// For NamedChunksPlugin
const seen = new Set()
const nameLength = 4
const webpackConfig = merge(baseWebpackConfig, {
mode: 'production',
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract: true,
usePostCSS: true
})
},
devtool: config.build.productionSourceMap ? config.build.devtool : false,
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash:8].js'),
chunkFilename: utils.assetsPath('js/[name].[chunkhash:8].js')
},
plugins: [
// http://vuejs.github.io/vue-loader/en/workflow/production.html
new webpack.DefinePlugin({
'process.env': env
}),
// extract css into its own file
new MiniCssExtractPlugin({
filename: utils.assetsPath('css/[name].[contenthash:8].css'),
chunkFilename: utils.assetsPath('css/[name].[contenthash:8].css')
}),
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
favicon: resolve('favicon.ico'),
title: 'vue-element-admin',
templateParameters: {
BASE_URL: config.build.assetsPublicPath + config.build.assetsSubDirectory,
},
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
}
// default sort mode uses toposort which cannot handle cyclic deps
// in certain cases, and in webpack 4, chunk order in HTML doesn't
// matter anyway
}),
new ScriptExtHtmlWebpackPlugin({
//`runtime` must same as runtimeChunk name. default is `runtime`
inline: /runtime\..*\.js$/
}),
// keep chunk.id stable when chunk has no name
new webpack.NamedChunksPlugin(chunk => {
if (chunk.name) {
return chunk.name
}
const modules = Array.from(chunk.modulesIterable)
if (modules.length > 1) {
const hash = require('hash-sum')
const joinedHash = hash(modules.map(m => m.id).join('_'))
let len = nameLength
while (seen.has(joinedHash.substr(0, len))) len++
seen.add(joinedHash.substr(0, len))
return `chunk-${joinedHash.substr(0, len)}`
} else {
return modules[0].id
}
}),
// keep module.id stable when vender modules does not change
new webpack.HashedModuleIdsPlugin(),
// copy custom static assets
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.build.assetsSubDirectory,
ignore: ['.*']
}
])
],
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial' // 只打包初始时依赖的第三方
},
elementUI: {
name: 'chunk-elementUI', // 单独将 elementUI 拆包
priority: 20, // 权重要大于 libs 和 app 不然会被打包进 libs 或者 app
test: /[\\/]node_modules[\\/]element-ui[\\/]/
},
commons: {
name: 'chunk-commons',
test: resolve('src/components'), // 可自定义拓展你的规则
minChunks: 3, // 最小公用次数
priority: 5,
reuseExistingChunk: true
}
}
},
runtimeChunk: 'single',
minimizer: [
new UglifyJsPlugin({
uglifyOptions: {
mangle: {
safari10: true
}
},
sourceMap: config.build.productionSourceMap,
cache: true,
parallel: true
}),
// Compress extracted CSS. We are using this plugin so that possible
// duplicated CSS from different components can be deduped.
new OptimizeCSSAssetsPlugin()
]
}
})
if (config.build.productionGzip) {
const CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' + config.build.productionGzipExtensions.join('|') + ')$'
),
threshold: 10240,
minRatio: 0.8
})
)
}
if (config.build.generateAnalyzerReport || config.build.bundleAnalyzerReport) {
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
.BundleAnalyzerPlugin
if (config.build.bundleAnalyzerReport) {
webpackConfig.plugins.push(
new BundleAnalyzerPlugin({
analyzerPort: 8080,
generateStatsFile: false
})
)
}
if (config.build.generateAnalyzerReport) {
webpackConfig.plugins.push(
new BundleAnalyzerPlugin({
analyzerMode: 'static',
reportFilename: 'bundle-report.html',
openAnalyzer: false
})
)
}
}
module.exports = webpackConfig

9
config/dev.env.js

@ -0,0 +1,9 @@
'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"',
BASE_API: '"http://localhost:8000"'
// BASE_API: '"https://api.auauz.net"'
})

106
config/index.js

@ -0,0 +1,106 @@
'use strict'
// Template version: 1.2.6
const devEnv = require('./dev.env')
// 获取接口地址
const base_url = devEnv.BASE_API.replace(/"/g,'')
const path = require('path')
module.exports = {
dev: {
// Paths
assetsSubDirectory: 'static',
assetsPublicPath: '/',
// 配置代理
proxyTable: {
'/auth': {
// 测试环境
target: base_url,
secure: true,
changeOrigin: true,
pathRewrite: {
'^/auth': 'auth'
}
},
'/api': {
// 测试环境
target: base_url,
secure: true,
changeOrigin: true,
pathRewrite: {
'^/api': 'api'
}
}
},
// Various Dev Server settings
host: 'localhost', // can be overwritten by process.env.HOST
port: 8013, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
autoOpenBrowser: true,
errorOverlay: true,
notifyOnErrors: false,
poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
// Use Eslint Loader?
// If true, your code will be linted during bundling and
// linting errors and warnings will be shown in the console.
useEslint: true,
// If true, eslint errors and warnings will also be shown in the error overlay
// in the browser.
showEslintErrorsInOverlay: false,
/**
* Source Maps
*/
// https://webpack.js.org/configuration/devtool/#development
devtool: 'cheap-source-map',
// CSS Sourcemaps off by default because relative paths are "buggy"
// with this option, according to the CSS-Loader README
// (https://github.com/webpack/css-loader#sourcemaps)
// In our experience, they generally work as expected,
// just be aware of this issue when enabling this option.
cssSourceMap: false
},
build: {
// Template for index.html
index: path.resolve(__dirname, '../dist/index.html'),
// Paths
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
/**
* You can set by youself according to actual condition
* You will need to set this if you plan to deploy your site under a sub path,
* for example GitHub pages. If you plan to deploy your site to https://foo.github.io/bar/,
* then assetsPublicPath should be set to "/bar/".
* In most cases please use '/' !!!
*/
assetsPublicPath: '/',
/**
* Source Maps
*/
productionSourceMap: false,
// https://webpack.js.org/configuration/devtool/#production
devtool: 'source-map',
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,
productionGzipExtensions: ['js', 'css'],
// Run the build command with an extra argument to
// View the bundle analyzer report after build finishes:
// `npm run build --report`
// Set to `true` or `false` to always turn it on or off
bundleAnalyzerReport: process.env.npm_config_report || false,
// `npm run build:prod --generate_report`
generateAnalyzerReport: process.env.npm_config_generate_report || false
}
}

5
config/prod.env.js

@ -0,0 +1,5 @@
'use strict'
module.exports = {
NODE_ENV: '"production"',
BASE_API: '"https://yshopapi.dayouqiantu.cn"'
}

1
debug.log

@ -0,0 +1 @@
[1110/193651.524:ERROR:crashpad_client_win.cc(491)] CreateProcess: 页面文件太小,无法完成操作。 (0x5AF)

BIN
favicon.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

22
index.html

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<style>
.sampleContainer{position:fixed;top:50%;left:50%;-webkit-transform:translateX(-50%) translateY(-50%);width:75px;height:100px}.loader{position:relative;width:44px;height:8px;margin:12px auto}.dot{display:inline-block;width:8px;height:8px;border-radius:4px;background:#ccc;position:absolute}.dot_1{animation:animateDot1 1.5s linear infinite;left:12px;background:#e579b8}.dot_2{animation:animateDot2 1.5s linear infinite;animation-delay:.5s;left:24px}.dot_3{animation:animateDot3 1.5s linear infinite;left:12px}.dot_4{animation:animateDot4 1.5s linear infinite;animation-delay:.5s;left:24px}@keyframes animateDot1{0%{transform:rotate(0) translateX(-12px)}25%{transform:rotate(180deg) translateX(-12px)}75%{transform:rotate(180deg) translateX(-12px)}100%{transform:rotate(360deg) translateX(-12px)}}@keyframes animateDot2{0%{transform:rotate(0) translateX(-12px)}25%{transform:rotate(-180deg) translateX(-12px)}75%{transform:rotate(-180deg) translateX(-12px)}100%{transform:rotate(-360deg) translateX(-12px)}}@keyframes animateDot3{0%{transform:rotate(0) translateX(12px)}25%{transform:rotate(180deg) translateX(12px)}75%{transform:rotate(180deg) translateX(12px)}100%{transform:rotate(360deg) translateX(12px)}}@keyframes animateDot4{0%{transform:rotate(0) translateX(12px)}25%{transform:rotate(-180deg) translateX(12px)}75%{transform:rotate(-180deg) translateX(12px)}100%{transform:rotate(-360deg) translateX(12px)}}
</style>
</head>
<body>
<div id="app">
<div class="sampleContainer">
<div class="loader">
<span class="dot dot_1"></span>
<span class="dot dot_2"></span>
<span class="dot dot_3"></span>
<span class="dot dot_4"></span>
</div>
</div>
</div>
</body>
</html>

106
package.json

@ -0,0 +1,106 @@
{
"name": "YSHOP",
"version": "2.2.0",
"license": "Apache-2.0",
"description": "YSHOP 前端代码",
"author": "hupeng <610796224@qq.com>",
"scripts": {
"dev": "cross-env BABEL_ENV=development webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"build": "cross-env NODE_ENV=production env_config=prod node build/build.js",
"lint": "eslint --ext .js,.vue src",
"test": "npm run lint",
"precommit": "lint-staged",
"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
},
"repository": {
"type": "git",
"url": "https://github.com/elunez/eladmin-qd"
},
"dependencies": {
"@riophae/vue-treeselect": "0.0.38",
"axios": "^0.19.0",
"clipboard": "1.7.1",
"codemirror": "^5.38.0",
"connect": "3.6.6",
"echarts": "4.1.0",
"element-ui": "^2.12.0",
"file-saver": "1.3.8",
"js-cookie": "2.2.0",
"jsencrypt": "^3.0.0-rc.1",
"jszip": "3.1.5",
"mavon-editor": "^2.7.0",
"normalize.css": "7.0.0",
"nprogress": "0.2.0",
"path-to-regexp": "2.4.0",
"screenfull": "3.3.3",
"vue": "2.5.17",
"vue-count-to": "1.0.13",
"vue-router": "3.1.3",
"vuex": "3.0.1",
"wangeditor": ">=3.0.0",
"xlsx": "^0.11.16"
},
"devDependencies": {
"autoprefixer": "8.5.0",
"babel-core": "6.26.3",
"babel-eslint": "8.2.6",
"babel-helper-vue-jsx-merge-props": "2.0.3",
"babel-loader": "7.1.5",
"babel-plugin-dynamic-import-node": "2.0.0",
"babel-plugin-syntax-jsx": "6.18.0",
"babel-plugin-transform-runtime": "6.23.0",
"babel-plugin-transform-vue-jsx": "3.7.0",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "1.7.0",
"babel-preset-stage-2": "6.24.1",
"chalk": "2.4.1",
"copy-webpack-plugin": "4.5.2",
"cross-env": "5.2.0",
"css-loader": "1.0.0",
"eslint": "4.19.1",
"eslint-friendly-formatter": "4.0.1",
"eslint-loader": "2.0.0",
"eslint-plugin-vue": "4.7.1",
"eventsource-polyfill": "0.9.6",
"file-loader": "1.1.11",
"friendly-errors-webpack-plugin": "1.7.0",
"happypack": "^5.0.1",
"html-webpack-plugin": "4.0.0-alpha",
"mini-css-extract-plugin": "0.4.1",
"node-notifier": "5.2.1",
"node-sass": "^4.7.2",
"optimize-css-assets-webpack-plugin": "5.0.0",
"ora": "3.0.0",
"portfinder": "1.0.16",
"postcss-import": "12.0.0",
"postcss-loader": "2.1.6",
"postcss-url": "7.3.2",
"rimraf": "2.6.2",
"sass-loader": "7.0.3",
"script-ext-html-webpack-plugin": "2.0.1",
"script-loader": "0.7.2",
"semver": "5.5.0",
"shelljs": "^0.8.3",
"svg-sprite-loader": "3.8.0",
"svgo": "1.2.2",
"uglifyjs-webpack-plugin": "1.2.7",
"url-loader": "1.0.1",
"vue-loader": "15.3.0",
"vue-style-loader": "4.1.2",
"vue-template-compiler": "2.5.17",
"webpack": "4.16.5",
"webpack-bundle-analyzer": "3.3.2",
"webpack-cli": "3.1.0",
"webpack-dev-server": "3.3.1",
"webpack-merge": "4.1.4"
},
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}

11
src/App.vue

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

25
src/api/alipay.js

@ -0,0 +1,25 @@
import request from '@/utils/request'
export function get() {
return request({
url: 'api/aliPay',
method: 'get'
})
}
export function update(data) {
return request({
url: 'api/aliPay',
data,
method: 'put'
})
}
// 支付
export function toAliPay(url, data) {
return request({
url: 'api/' + url,
data,
method: 'post'
})
}

16
src/api/code.js

@ -0,0 +1,16 @@
import request from '@/utils/request'
export function resetEmail(data) {
return request({
url: 'api/code/resetEmail',
method: 'post',
data
})
}
export function updatePass(pass) {
return request({
url: 'api/users/updatePass/' + pass,
method: 'get'
})
}

9
src/api/data.js

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

32
src/api/dept.js

@ -0,0 +1,32 @@
import request from '@/utils/request'
export function getDepts(params) {
return request({
url: 'api/dept',
method: 'get',
params
})
}
export function add(data) {
return request({
url: 'api/dept',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/dept/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/dept',
method: 'put',
data
})
}

24
src/api/dict.js

@ -0,0 +1,24 @@
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/dict',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/dict/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/dict',
method: 'put',
data
})
}

50
src/api/dictDetail.js

@ -0,0 +1,50 @@
import request from '@/utils/request'
export function get(dictName) {
const params = {
dictName,
page: 0,
size: 9999
}
return request({
url: 'api/dictDetail',
method: 'get',
params
})
}
export function getDictMap(dictName) {
const params = {
dictName,
page: 0,
size: 9999
}
return request({
url: 'api/dictDetail/map',
method: 'get',
params
})
}
export function add(data) {
return request({
url: 'api/dictDetail',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/dictDetail/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/dictDetail',
method: 'put',
data
})
}

24
src/api/email.js

@ -0,0 +1,24 @@
import request from '@/utils/request'
export function get() {
return request({
url: 'api/email',
method: 'get'
})
}
export function update(data) {
return request({
url: 'api/email',
data,
method: 'put'
})
}
export function send(data) {
return request({
url: 'api/email',
data,
method: 'post'
})
}

16
src/api/genConfig.js

@ -0,0 +1,16 @@
import request from '@/utils/request'
export function get() {
return request({
url: 'api/genConfig',
method: 'get'
})
}
export function update(data) {
return request({
url: 'api/genConfig',
data,
method: 'put'
})
}

9
src/api/generator.js

@ -0,0 +1,9 @@
import request from '@/utils/request'
export function generator(data, tableName) {
return request({
url: 'api/generator?tableName=' + tableName,
data,
method: 'post'
})
}

37
src/api/job.js

@ -0,0 +1,37 @@
import request from '@/utils/request'
export function getAllJob(deptId) {
const params = {
deptId,
page: 0,
size: 9999
}
return request({
url: 'api/job',
method: 'get',
params
})
}
export function add(data) {
return request({
url: 'api/job',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/job/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/job',
method: 'put',
data
})
}

32
src/api/localStorage.js

@ -0,0 +1,32 @@
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/localStorage',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/localStorage/' + id,
method: 'delete'
})
}
export function delAll(ids) {
return request({
url: 'api/localStorage/',
method: 'delete',
data: ids
})
}
export function edit(data) {
return request({
url: 'api/localStorage',
method: 'put',
data
})
}

8
src/api/log.js

@ -0,0 +1,8 @@
import request from '@/utils/request'
export function getErrDetail(id) {
return request({
url: 'api/logs/error/' + id,
method: 'get'
})
}

28
src/api/login.js

@ -0,0 +1,28 @@
import request from '@/utils/request'
export function login(username, password, code, uuid) {
return request({
url: 'auth/login',
method: 'post',
data: {
username,
password,
code,
uuid
}
})
}
export function getInfo() {
return request({
url: 'auth/info',
method: 'get'
})
}
export function getCodeImg() {
return request({
url: 'auth/vCode',
method: 'get'
})
}

39
src/api/menu.js

@ -0,0 +1,39 @@
import request from '@/utils/request'
// 获取所有的菜单树
export function getMenusTree() {
return request({
url: 'api/menus/tree',
method: 'get'
})
}
export function buildMenus() {
return request({
url: 'api/menus/build',
method: 'get'
})
}
export function add(data) {
return request({
url: 'api/menus',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/menus/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/menus',
method: 'put',
data
})
}

32
src/api/permission.js

@ -0,0 +1,32 @@
import request from '@/utils/request'
// 获取所有的权限树
export function getPermissionTree() {
return request({
url: 'api/permissions/tree',
method: 'get'
})
}
export function add(data) {
return request({
url: 'api/permissions',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/permissions/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/permissions',
method: 'put',
data
})
}

16
src/api/picture.js

@ -0,0 +1,16 @@
import request from '@/utils/request'
export function del(id) {
return request({
url: 'api/pictures/' + id,
method: 'delete'
})
}
export function delAll(ids) {
return request({
url: 'api/pictures/',
method: 'delete',
data: ids
})
}

45
src/api/qiniu.js

@ -0,0 +1,45 @@
import request from '@/utils/request'
export function get() {
return request({
url: 'api/qiNiuConfig',
method: 'get'
})
}
export function update(data) {
return request({
url: 'api/qiNiuConfig',
data,
method: 'put'
})
}
export function del(id) {
return request({
url: 'api/qiNiuContent/' + id,
method: 'delete'
})
}
export function download(id) {
return request({
url: 'api/qiNiuContent/download/' + id,
method: 'get'
})
}
export function sync() {
return request({
url: 'api/qiNiuContent/synchronize',
method: 'post'
})
}
export function delAll(ids) {
return request({
url: 'api/qiNiuContent/',
method: 'delete',
data: ids
})
}

35
src/api/redis.js

@ -0,0 +1,35 @@
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/redis',
method: 'post',
data
})
}
export function del(key) {
const data = {
key
}
return request({
url: 'api/redis/',
method: 'delete',
data
})
}
export function delAll() {
return request({
url: 'api/redis/all',
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/redis',
method: 'put',
data
})
}

62
src/api/role.js

@ -0,0 +1,62 @@
import request from '@/utils/request'
// 获取所有的Role
export function getAll() {
return request({
url: 'api/roles/all',
method: 'get'
})
}
export function add(data) {
return request({
url: 'api/roles',
method: 'post',
data
})
}
export function get(id) {
return request({
url: 'api/roles/' + id,
method: 'get'
})
}
export function getLevel() {
return request({
url: 'api/roles/level',
method: 'get'
})
}
export function del(id) {
return request({
url: 'api/roles/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/roles',
method: 'put',
data
})
}
export function editPermission(data) {
return request({
url: 'api/roles/permission',
method: 'put',
data
})
}
export function editMenu(data) {
return request({
url: 'api/roles/menu',
method: 'put',
data
})
}

38
src/api/timing.js

@ -0,0 +1,38 @@
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/jobs',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/jobs/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/jobs',
method: 'put',
data
})
}
export function updateIsPause(id) {
return request({
url: 'api/jobs/' + id,
method: 'put'
})
}
export function execution(id) {
return request({
url: 'api/jobs/exec/' + id,
method: 'put'
})
}

52
src/api/user.js

@ -0,0 +1,52 @@
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/users',
method: 'post',
data
})
}
export function downloadUser() {
return request({
url: 'api/users/download',
method: 'get',
responseType: 'blob'
})
}
export function del(id) {
return request({
url: 'api/users/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/users',
method: 'put',
data
})
}
export function updatePass(user) {
const data = {
oldPass: user.oldPass,
newPass: user.newPass
}
return request({
url: 'api/users/updatePass/',
method: 'post',
data
})
}
export function updateEmail(code, data) {
return request({
url: 'api/users/updateEmail/' + code,
method: 'post',
data
})
}

22
src/api/visits.js

@ -0,0 +1,22 @@
import request from '@/utils/request'
export function count() {
return request({
url: 'api/visits',
method: 'post'
})
}
export function get() {
return request({
url: 'api/visits',
method: 'get'
})
}
export function getChartData() {
return request({
url: 'api/visits/chartData',
method: 'get'
})
}

31
src/api/yxArticle.js

@ -0,0 +1,31 @@
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/yxArticle',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/yxArticle/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/yxArticle',
method: 'put',
data
})
}
export function publish(id) {
return request({
url: 'api/yxArticle/publish/' + id,
method: 'get'
})
}

31
src/api/yxCache.js

@ -0,0 +1,31 @@
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/yxCache',
method: 'post',
data
})
}
export function del(key) {
return request({
url: 'api/yxCache/' + key,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/yxCache',
method: 'put',
data
})
}
export function get() {
return request({
url: 'api/yxCache',
method: 'get',
})
}

32
src/api/yxStoreCategory.js

@ -0,0 +1,32 @@
import request from '@/utils/request'
export function getCates(params) {
return request({
url: 'api/yxStoreCategory',
method: 'get',
params
})
}
export function add(data) {
return request({
url: 'api/yxStoreCategory',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/yxStoreCategory/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/yxStoreCategory',
method: 'put',
data
})
}

32
src/api/yxStoreCombination.js

@ -0,0 +1,32 @@
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/yxStoreCombination',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/yxStoreCombination/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/yxStoreCombination',
method: 'put',
data
})
}
export function onsale(id, data) {
return request({
url: 'api/yxStoreCombination/onsale/' + id,
method: 'post',
data
})
}

24
src/api/yxStoreCoupon.js

@ -0,0 +1,24 @@
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/yxStoreCoupon',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/yxStoreCoupon/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/yxStoreCoupon',
method: 'put',
data
})
}

24
src/api/yxStoreCouponIssue.js

@ -0,0 +1,24 @@
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/yxStoreCouponIssue',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/yxStoreCouponIssue/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/yxStoreCouponIssue',
method: 'put',
data
})
}

24
src/api/yxStoreCouponIssueUser.js

@ -0,0 +1,24 @@
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/yxStoreCouponIssueUser',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/yxStoreCouponIssueUser/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/yxStoreCouponIssueUser',
method: 'put',
data
})
}

24
src/api/yxStoreCouponUser.js

@ -0,0 +1,24 @@
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/yxStoreCouponUser',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/yxStoreCouponUser/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/yxStoreCouponUser',
method: 'put',
data
})
}

32
src/api/yxStoreOrder.js

@ -0,0 +1,32 @@
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/yxStoreOrder',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/yxStoreOrder/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/yxStoreOrder',
method: 'put',
data
})
}
export function refund(data) {
return request({
url: 'api/yxStoreOrder/refund',
method: 'post',
data
})
}

24
src/api/yxStoreOrderStatus.js

@ -0,0 +1,24 @@
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/yxStoreOrderStatus',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/yxStoreOrderStatus/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/yxStoreOrderStatus',
method: 'put',
data
})
}

24
src/api/yxStorePink.js

@ -0,0 +1,24 @@
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/yxStorePink',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/yxStorePink/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/yxStorePink',
method: 'put',
data
})
}

72
src/api/yxStoreProduct.js

@ -0,0 +1,72 @@
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/yxStoreProduct',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/yxStoreProduct/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/yxStoreProduct',
method: 'put',
data
})
}
export function onsale(id, data) {
return request({
url: 'api/yxStoreProduct/onsale/' + id,
method: 'post',
data
})
}
export function recovery(id) {
return request({
url: 'api/yxStoreProduct/recovery/' + id,
method: 'delete'
})
}
export function isFormatAttr(id,data) {
return request({
url: 'api/yxStoreProduct/isFormatAttr/' + id,
method: 'post',
data
})
}
export function setAttr(id,data) {
return request({
url: 'api/yxStoreProduct/setAttr/' + id,
method: 'post',
data
})
}
export function clearAttr(id) {
return request({
url: 'api/yxStoreProduct/clearAttr/' + id,
method: 'post',
})
}
export function getAttr(id) {
return request({
url: 'api/yxStoreProduct/attr/' + id,
method: 'get',
})
}

24
src/api/yxStoreProductReply.js

@ -0,0 +1,24 @@
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/yxStoreProductReply',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/yxStoreProductReply/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/yxStoreProductReply',
method: 'put',
data
})
}

24
src/api/yxStoreVisit.js

@ -0,0 +1,24 @@
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/yxStoreVisit',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/yxStoreVisit/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/yxStoreVisit',
method: 'put',
data
})
}

32
src/api/yxSystemConfig.js

@ -0,0 +1,32 @@
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/yxSystemConfig',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/yxSystemConfig/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/yxSystemConfig',
method: 'put',
data
})
}
export function get() {
return request({
url: 'api/yxSystemConfig',
method: 'get',
})
}

24
src/api/yxSystemGroupData.js

@ -0,0 +1,24 @@
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/yxSystemGroupData',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/yxSystemGroupData/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/yxSystemGroupData',
method: 'put',
data
})
}

32
src/api/yxUser.js

@ -0,0 +1,32 @@
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/yxUser',
method: 'post',
data
})
}
export function del(uid) {
return request({
url: 'api/yxUser/' + uid,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/yxUser',
method: 'put',
data
})
}
export function onStatus(id, data) {
return request({
url: 'api/yxUser/onStatus/' + id,
method: 'post',
data
})
}

24
src/api/yxUserBill.js

@ -0,0 +1,24 @@
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/yxUserBill',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/yxUserBill/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/yxUserBill',
method: 'put',
data
})
}

24
src/api/yxUserExtract.js

@ -0,0 +1,24 @@
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/yxUserExtract',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/yxUserExtract/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/yxUserExtract',
method: 'put',
data
})
}

32
src/api/yxWechatReply.js

@ -0,0 +1,32 @@
import request from '@/utils/request'
export function add(data) {
return request({
url: 'api/yxWechatReply',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/yxWechatReply/' + id,
method: 'delete'
})
}
export function edit(data) {
return request({
url: 'api/yxWechatReply',
method: 'put',
data
})
}
export function get() {
return request({
url: 'api/yxWechatReply',
method: 'get',
})
}

BIN
src/assets/401_images/401.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

BIN
src/assets/404_images/404.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
src/assets/avatar/avatar.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
src/assets/logo/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
src/assets/wechat/head.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

BIN
src/assets/wechat/index.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

BIN
src/assets/wechat/mobile_foot.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
src/assets/wechat/mobile_head.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

74
src/components/Breadcrumb/index.vue

@ -0,0 +1,74 @@
<template>
<el-breadcrumb class="app-breadcrumb" separator="/">
<transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(item,index) in levelList" v-if="item.meta.title" :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() {
let matched = this.$route.matched.filter(item => {
if (item.name) {
return true
}
})
const first = matched[0]
if (first && first.name !== '首页') {
matched = [{ path: '/dashboard', meta: { title: '首页' }}].concat(matched)
}
if (matched.length >= 4) {
matched.splice(1, 1)
}
this.levelList = matched
},
pathCompile(path) {
// To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
const { params } = this.$route
var toPath = pathToRegexp.compile(path)
return toPath(params)
},
handleLink(item) {
const { redirect, path } = item
if (redirect) {
this.$router.push(redirect)
return
}
this.$router.push(this.pathCompile(path))
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.app-breadcrumb.el-breadcrumb {
display: inline-block;
font-size: 14px;
line-height: 50px;
margin-left: 10px;
.no-redirect {
color: #97a8be;
cursor: text;
}
}
</style>

47
src/components/Hamburger/index.vue

@ -0,0 +1,47 @@
<template>
<div>
<svg
:class="{'is-active':isActive}"
t="1492500959545"
class="hamburger"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
p-id="1691"
width="64"
height="64"
@click="toggleClick">
<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
},
toggleClick: {
type: Function,
default: null
}
}
}
</script>
<style scoped>
.hamburger {
display: inline-block;
cursor: pointer;
width: 20px;
height: 20px;
transition: .38s;
transform-origin: 50% 50%;
}
.hamburger.is-active {
transform: rotate(180deg);
}
</style>

69
src/components/IconSelect/index.vue

@ -0,0 +1,69 @@
<!-- @author zhengjie -->
<template>
<div class="icon-body">
<el-input v-model="name" style="position: relative;" clearable placeholder="请输入图标名称" @clear="filterIcons" @input.native="filterIcons">
<i slot="suffix" class="el-icon-search el-input__icon"/>
</el-input>
<div class="icon-list">
<div v-for="(item, index) in iconList" :key="index" @click="selectedIcon(item)">
<svg-icon :icon-class="item" style="height: 30px;width: 16px;" />
<span>{{ item }}</span>
</div>
</div>
</div>
</template>
<script>
import icons from './requireIcons'
export default {
name: 'IconSelect',
data() {
return {
name: '',
iconList: icons
}
},
methods: {
filterIcons() {
if (this.name) {
this.iconList = this.iconList.filter(item => item.includes(this.name))
} else {
this.iconList = icons
}
},
selectedIcon(name) {
this.$emit('selected', name)
document.body.click()
},
reset() {
this.name = ''
this.iconList = icons
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.icon-body {
width: 100%;
padding: 10px;
.icon-list {
height: 200px;
overflow-y: scroll;
div {
height: 30px;
line-height: 30px;
margin-bottom: -5px;
cursor: pointer;
width: 33%;
float: left;
}
span {
display: inline-block;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
}
}
</style>

11
src/components/IconSelect/requireIcons.js

@ -0,0 +1,11 @@
const req = require.context('../../icons/svg', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys()
const re = /\.\/(.*)\.svg/
const icons = requireAll(req).map(i => {
return i.match(re)[1]
})
export default icons

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

165
src/components/RightPanel/index.vue

@ -0,0 +1,165 @@
<template>
<div ref="rightPanel" :class="{show:show}" class="rightPanel-container">
<div class="rightPanel-background" />
<div class="rightPanel">
<div v-if="settingBtn" :style="{'top':buttonTop+'px','background-color':theme}" class="handle-button" @click="show=!show">
<i :class="show?'el-icon-close':'el-icon-setting'" />
</div>
<div v-else :style="{'top':buttonTop+'px'}" @click="show=!show"/>
<div class="rightPanel-items">
<slot />
</div>
</div>
</div>
</template>
<script>
import { addClass, removeClass } from '@/utils'
export default {
name: 'RightPanel',
props: {
clickNotClose: {
default: false,
type: Boolean
},
buttonTop: {
default: 250,
type: Number
}
},
computed: {
show: {
get() {
return this.$store.state.settings.showRightPanel
},
set(val) {
this.$store.dispatch('changeSetting', {
key: 'showRightPanel',
value: val
})
}
},
theme: {
get() {
return this.$store.state.settings.theme
}
},
settingBtn: {
get() {
return this.$store.state.settings.settingBtn
}
}
},
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 {
opacity: 0;
transition: opacity .3s cubic-bezier(.7, .3, .1, 1);
background: rgba(0, 0, 0, .2);
width: 0;
height: 0;
top: 0;
left: 0;
position: fixed;
z-index: -1;
}
.rightPanel {
background: #fff;
position: fixed;
height: 100vh;
width: 100%;
max-width: 260px;
top: 0px;
left: 0px;
box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, .05);
transition: all .25s cubic-bezier(.7, .3, .1, 1);
transform: translate(100%);
z-index: 40000;
left: auto;
right: 0px;
}
.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 {
position: absolute;
left: -48px;
border-radius: 6px 0 0 6px !important;
width: 48px;
height: 48px;
pointer-events: auto;
z-index: 0;
cursor: pointer;
pointer-events: auto;
font-size: 24px;
text-align: center;
color: #fff;
line-height: 48px;
i {
font-size: 24px;
line-height: 48px;
}
}
</style>

78
src/components/Screenfull/index.vue

@ -0,0 +1,78 @@
<template>
<div>
<svg
t="1508738709248"
class="screenfull-svg"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="2069"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="32"
height="32"
@click="click">
<path
d="M333.493443 428.647617 428.322206 333.832158 262.572184 168.045297 366.707916 64.444754 64.09683 64.444754 63.853283 366.570793 167.283957 262.460644Z"
p-id="2070"/>
<path
d="M854.845439 760.133334 688.61037 593.95864 593.805144 688.764889 759.554142 854.56096 655.44604 958.161503 958.055079 958.161503 958.274066 656.035464Z"
p-id="2071"/>
<path
d="M688.535669 428.550403 854.31025 262.801405 957.935352 366.921787 957.935352 64.34754 655.809313 64.081481 759.919463 167.535691 593.70793 333.731874Z"
p-id="2072"/>
<path
d="M333.590658 594.033341 167.8171 759.804852 64.218604 655.67219 64.218604 958.270996 366.342596 958.502263 262.234493 855.071589 428.421466 688.86108Z"
p-id="2073"/>
</svg>
</div>
</template>
<script>
import screenfull from 'screenfull'
export default {
name: 'Screenfull',
props: {
width: {
type: Number,
default: 22
},
height: {
type: Number,
default: 22
},
fill: {
type: String,
default: '#48576a'
}
},
data() {
return {
isFullscreen: false
}
},
methods: {
click() {
if (!screenfull.enabled) {
this.$message({
message: 'you browser can not work',
type: 'warning'
})
return false
}
screenfull.toggle()
}
}
}
</script>
<style scoped>
.screenfull-svg {
display: inline-block;
cursor: pointer;
fill: #5a5e66;;
width: 20px;
height: 20px;
vertical-align: 10px;
}
</style>

92
src/components/ScrollPane/index.vue

@ -0,0 +1,92 @@
<template>
<el-scrollbar ref="scrollContainer" :vertical="false" class="scroll-container" @wheel.native.prevent="handleScroll">
<slot/>
</el-scrollbar>
</template>
<script>
const tagAndTagSpacing = 4 // tagAndTagSpacing
export default {
name: 'ScrollPane',
data() {
return {
left: 0
}
},
methods: {
handleScroll(e) {
const eventDelta = e.wheelDelta || -e.deltaY * 40
const $scrollWrapper = this.$refs.scrollContainer.$refs.wrap
$scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
},
moveToTarget(currentTag) {
const $container = this.$refs.scrollContainer.$el
const $containerWidth = $container.offsetWidth
const $scrollWrapper = this.$refs.scrollContainer.$refs.wrap
const tagList = this.$parent.$refs.tag
let firstTag = null
let lastTag = null
let prevTag = null
let nextTag = null
// find first tag and last tag
if (tagList.length > 0) {
firstTag = tagList[0]
lastTag = tagList[tagList.length - 1]
}
// find preTag and nextTag
for (let i = 0; i < tagList.length; i++) {
if (tagList[i] === currentTag) {
if (i === 0) {
nextTag = tagList[i].length > 1 && tagList[i + 1]
} else if (i === tagList.length - 1) {
prevTag = tagList[i].length > 1 && tagList[i - 1]
} else {
prevTag = tagList[i - 1]
nextTag = tagList[i + 1]
}
break
}
}
if (firstTag === currentTag) {
$scrollWrapper.scrollLeft = 0
} else if (lastTag === currentTag) {
$scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth
} else {
// the tag's offsetLeft after of nextTag
const afterNextTagOffsetLeft = nextTag.$el.offsetLeft + nextTag.$el.offsetWidth + tagAndTagSpacing
// the tag's offsetLeft before of prevTag
const beforePrevTagOffsetLeft = prevTag.$el.offsetLeft - tagAndTagSpacing
if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
$scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth
} else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
$scrollWrapper.scrollLeft = beforePrevTagOffsetLeft
}
}
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.scroll-container {
white-space: nowrap;
position: relative;
overflow: hidden;
width: 100%;
/deep/ {
.el-scrollbar__bar {
bottom: 0px;
}
.el-scrollbar__wrap {
height: 49px;
}
}
}
</style>

43
src/components/SvgIcon/index.vue

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

113
src/components/TextHoverEffect/Mallki.vue

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

29
src/components/TreeTable/eval.js

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

127
src/components/TreeTable/index.vue

@ -0,0 +1,127 @@
<template>
<el-table :data="formatData" :row-style="showRow" v-bind="$attrs">
<el-table-column v-if="columns.length===0" width="150">
<template slot-scope="scope">
<span v-for="space in scope.row._level" :key="space" class="ms-tree-space"/>
<span v-if="iconShow(0,scope.row)" class="tree-ctrl" @click="toggleExpanded(scope.$index)">
<i v-if="!scope.row._expanded" class="el-icon-plus"/>
<i v-else class="el-icon-minus"/>
</span>
{{ scope.$index }}
</template>
</el-table-column>
<el-table-column v-for="(column, index) in columns" v-else :key="column.value" :label="column.text" :width="column.width">
<template slot-scope="scope">
<!-- Todo -->
<!-- eslint-disable-next-line vue/no-confusing-v-for-v-if -->
<span v-for="space in scope.row._level" v-if="index === 0" :key="space" class="ms-tree-space"/>
<span v-if="iconShow(index,scope.row)" class="tree-ctrl" @click="toggleExpanded(scope.$index)">
<i v-if="!scope.row._expanded" class="el-icon-plus"/>
<i v-else class="el-icon-minus"/>
</span>
{{ scope.row[column.value] }}
</template>
</el-table-column>
<slot/>
</el-table>
</template>
<script>
/**
Auth: Lei.j1ang
Created: 2018/1/19-13:59
*/
import treeToArray from './eval'
export default {
name: 'TreeTable',
props: {
/* eslint-disable */
data: {
type: [Array, Object],
required: true
},
columns: {
type: Array,
default: () => []
},
evalFunc: Function,
evalArgs: Array,
expandAll: {
type: Boolean,
default: false
}
},
computed: {
//
formatData: function() {
let tmp
if (!Array.isArray(this.data)) {
tmp = [this.data]
} else {
tmp = this.data
}
const func = this.evalFunc || treeToArray
const args = this.evalArgs ? Array.concat([tmp, this.expandAll], this.evalArgs) : [tmp, this.expandAll]
return func.apply(null, args)
}
},
methods: {
showRow: function(row) {
const show = (row.row.parent ? (row.row.parent._expanded && row.row.parent._show) : true)
row.row._show = show
return show ? 'animation:treeTableShow 1s;-webkit-animation:treeTableShow 1s;' : 'display:none;'
},
//
toggleExpanded: function(trIndex) {
const record = this.formatData[trIndex]
record._expanded = !record._expanded
},
//
iconShow(index, record) {
return (index === 0 && record.children && record.children.length > 0)
}
}
}
</script>
<style rel="stylesheet/css">
@keyframes treeTableShow {
from {opacity: 0;}
to {opacity: 1;}
}
@-webkit-keyframes treeTableShow {
from {opacity: 0;}
to {opacity: 1;}
}
</style>
<style lang="scss" rel="stylesheet/scss" scoped>
$color-blue: #2196F3;
$space-width: 18px;
.ms-tree-space {
position: relative;
top: 1px;
display: inline-block;
font-style: normal;
font-weight: 400;
line-height: 1;
width: $space-width;
height: 14px;
&::before {
content: ""
}
}
.processContainer{
width: 100%;
height: 100%;
}
table td {
line-height: 26px;
}
.tree-ctrl{
position: relative;
cursor: pointer;
color: $color-blue;
margin-left: -$space-width;
}
</style>

89
src/components/TreeTable/readme.md

@ -0,0 +1,89 @@
## 写在前面
此组件仅提供一个创建TreeTable的解决思路
## prop说明
#### *data*
**必填**
原始数据,要求是一个数组或者对象
```javascript
[{
key1: value1,
key2: value2,
children: [{
key1: value1
},
{
key1: value1
}]
},
{
key1: value1
}]
```
或者
```javascript
{
key1: value1,
key2: value2,
children: [{
key1: value1
},
{
key1: value1
}]
}
```
#### columns
列属性,要求是一个数组
1. text: 显示在表头的文字
2. value: 对应data的key。treeTable将显示相应的value
3. width: 每列的宽度,为一个数字(可选)
如果你想要每个字段都有自定义的样式或者嵌套其他组件,columns可不提供,直接像在el-table一样写即可,如果没有自定义内容,提供columns将更加的便捷方便
如果你有几个字段是需要自定义的,几个不需要,那么可以将不需要自定义的字段放入columns,将需要自定义的内容放入到slot中,详情见后文
```javascript
[{
value:string,
text:string,
width:number
},{
value:string,
text:string,
width:number
}]
```
#### expandAll
是否默认全部展开,boolean值,默认为false
#### evalFunc
解析函数,function,非必须
如果不提供,将使用默认的[evalFunc](./eval.js)
如果提供了evalFunc,那么会用提供的evalFunc去解析data,并返回treeTable渲染所需要的值。如何编写一个evalFunc,请参考[*eval.js*](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/components/TreeTable/eval.js)或[*customEval.js*](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/views/table/treeTable/customEval.js)
#### evalArgs
解析函数的参数,是一个数组
**请注意,自定义的解析函数参数第一个为this.data,第二个参数为, this.expandAll,你不需要在evalArgs填写。一定记住,这两个参数是强制性的,并且位置不可颠倒** *this.data为需要解析的数据,this.expandAll为是否默认展开*
如你的解析函数需要的参数为`(this.data, this.expandAll,1,2,3,4)`,那么你只需要将`[1,2,3,4]`赋值给`evalArgs`就可以了
如果你的解析函数参数只有`(this.data, this.expandAll)`,那么就可以不用填写evalArgs了
具体可参考[*customEval.js*](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/views/table/treeTable/customEval.js)的函数参数和[customTreeTable](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/views/table/treeTable/customTreeTable.vue)的`evalArgs`属性值
## slot
这是一个自定义列的插槽。
默认情况下,treeTable只有一行行展示数据的功能。但是一般情况下,我们会要给行加上一个操作按钮或者根据当行数据展示不同的样式,这时我们就需要自定义列了。请参考[customTreeTable](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/views/table/treeTable/customTreeTable.vue),[实例效果](https://panjiachen.github.io/vue-element-admin/#/table/tree-table)
`slot`和`columns属性`可同时存在,columns里面的数据列会在slot自定义列的左边展示
## 其他
如果有其他的需求,请参考[el-table](http://element-cn.eleme.io/#/en-US/component/table)的api自行修改index.vue

81
src/components/YamlEdit/index.vue

@ -0,0 +1,81 @@
<template>
<div class="json-editor">
<textarea ref="textarea"/>
</div>
</template>
<script>
import CodeMirror from 'codemirror'
import 'codemirror/lib/codemirror.css'
//
import 'codemirror/theme/idea.css'
require('codemirror/mode/yaml/yaml.js')
export default {
props: {
value: {
type: String,
required: true
},
height: {
type: String,
required: true
}
},
data() {
return {
editor: false
}
},
watch: {
value(value) {
const editorValue = this.editor.getValue()
if (value !== editorValue) {
this.editor.setValue(this.value)
}
},
height(value) {
this.editor.setSize('auto', this.height)
}
},
mounted() {
this.editor = CodeMirror.fromTextArea(this.$refs.textarea, {
mode: 'text/x-yaml',
lineNumbers: true,
lint: true,
lineWrapping: true,
tabSize: 2,
cursorHeight: 0.9,
//
theme: 'idea'
})
this.editor.setSize('auto', this.height)
this.editor.setValue(this.value)
this.editor.on('change', cm => {
this.$emit('changed', cm.getValue())
this.$emit('input', cm.getValue())
})
},
methods: {
getValue() {
return this.editor.getValue()
}
}
}
</script>
<style scoped>
.json-editor{
height: 100%;
margin-bottom: 10px;
}
.json-editor >>> .CodeMirror {
font-size: 13px;
overflow-y:auto;
font-weight:normal
}
.json-editor >>> .CodeMirror-scroll{
}
.json-editor >>> .cm-s-rubyblue span.cm-string {
color: #F08047;
}
</style>

30
src/components/iframe/index.vue

@ -0,0 +1,30 @@
<template>
<div v-loading="loading" :style="'height:'+ height">
<iframe :src="src" frameborder="no" style="width: 100%;height: 100%" scrolling="auto"/>
</div>
</template>
<script>
export default {
props: {
src: {
type: String,
required: true
}
},
data() {
return {
height: document.documentElement.clientHeight - 94.5 + 'px;',
loading: true
}
},
mounted: function() {
setTimeout(() => {
this.loading = false
}, 230)
const that = this
window.onresize = function temp() {
that.height = document.documentElement.clientHeight - 94.5 + 'px;'
}
}
}
</script>

94
src/components/mul-pic-upload/index.vue

@ -0,0 +1,94 @@
<template>
<div>
<el-upload
:action="qiNiuUploadApi"
:headers="headers"
list-type="picture-card"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
:on-success="handleUploadSuccess"
:file-list="imageList"
:before-upload="beforeAvatarUpload">
<i class="el-icon-plus"></i>
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="dialogImageUrl" alt="">
</el-dialog>
</div>
</template>
<script>
import { getToken } from '@/utils/auth'
import { mapGetters } from 'vuex'
export default {
data () {
return {
dialogImageUrl: '',
dialogVisible: false,
resourcesUrl: '',
headers: {
'Authorization': 'Bearer ' + getToken()
}
}
},
props: {
value: {
default: '',
type: String
}
},
computed: {
...mapGetters([
'qiNiuUploadApi'
]),
imageList () {
let res = []
if (this.value) {
let imageArray = this.value.split(',')
for (let i = 0; i < imageArray.length; i++) {
res.push({url: this.resourcesUrl + imageArray[i], response: imageArray[i]})
}
}
this.$emit('input', this.value)
return res
}
},
methods: {
//
handleUploadSuccess (response, file, fileList) {
console.log(file);
console.log(fileList)
let pics = fileList.map(file => {
if((typeof file.response) == 'object'){
return file.response.data[0]
}else{
return file.response
}
}).join(',')
console.log(pics)
this.$emit('input', pics)
},
//
beforeAvatarUpload (file) {
const isLt2M = file.size / 1024 / 1024 < 2
if (!isLt2M) {
this.$message.error('上传头像图片大小不能超过 2MB!')
}
return isLt2M
},
handleRemove (file, fileList) {
let pics = fileList.map(file => {
return file.response
}).join(',')
this.$emit('input', pics)
},
handlePictureCardPreview (file) {
this.dialogImageUrl = file.url
this.dialogVisible = true
}
}
}
</script>
<style lang="scss">
</style>

13
src/components/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

22
src/components/permission/permission.js

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

82
src/components/pic-upload-two/index.vue

@ -0,0 +1,82 @@
<template>
<div>
<el-upload
class="pic-uploader-component"
:action="qiNiuUploadApi"
:headers="headers"
:show-file-list="false"
:on-success="handleUploadSuccess"
:before-upload="beforeAvatarUpload">
<img v-if="value" :src="resourcesUrl + value" class="pic">
<i v-else class="el-icon-plus pic-uploader-icon"></i>
</el-upload>
</div>
</template>
<script>
import { getToken } from '@/utils/auth'
import { mapGetters } from 'vuex'
export default {
data () {
return {
resourcesUrl: '',
headers: {
'Authorization': 'Bearer ' + getToken()
}
}
},
computed: {
...mapGetters([
'qiNiuUploadApi'
])
},
props: {
value: {
default: '',
type: String
}
},
methods: {
//
handleUploadSuccess(response, file, fileList) {
console.log(file)
this.$emit('input', file.response.data[0])
},
//
beforeAvatarUpload(file) {
const isLt2M = file.size / 1024 / 1024 < 2
if (!isLt2M) {
this.$message.error('上传头像图片大小不能超过 2MB!')
}
return isLt2M
}
}
}
</script>
<style lang="scss" scoped>
.pic-uploader-component .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
.pic-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 100%;
height: 40px;
//line-height: 178px;
text-align: center;
}
.pic {
width: 100%;
height: 40px;
display: block;
}
}
.pic-uploader-component .el-upload:hover {
border-color: #409EFF;
}
</style>

82
src/components/pic-upload/index.vue

@ -0,0 +1,82 @@
<template>
<div>
<el-upload
class="pic-uploader-component"
:action="qiNiuUploadApi"
:headers="headers"
:show-file-list="false"
:on-success="handleUploadSuccess"
:before-upload="beforeAvatarUpload">
<img v-if="value" :src="resourcesUrl + value" class="pic">
<i v-else class="el-icon-plus pic-uploader-icon"></i>
</el-upload>
</div>
</template>
<script>
import { getToken } from '@/utils/auth'
import { mapGetters } from 'vuex'
export default {
data () {
return {
resourcesUrl: '',
headers: {
'Authorization': 'Bearer ' + getToken()
}
}
},
computed: {
...mapGetters([
'qiNiuUploadApi'
])
},
props: {
value: {
default: '',
type: String
}
},
methods: {
//
handleUploadSuccess(response, file, fileList) {
console.log(file)
this.$emit('input', file.response.data[0])
},
//
beforeAvatarUpload(file) {
const isLt2M = file.size / 1024 / 1024 < 2
if (!isLt2M) {
this.$message.error('上传头像图片大小不能超过 2MB!')
}
return isLt2M
}
}
}
</script>
<style lang="scss">
.pic-uploader-component .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
.pic-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.pic {
width: 178px;
height: 178px;
display: block;
}
}
.pic-uploader-component .el-upload:hover {
border-color: #409EFF;
}
</style>

82
src/components/pic-upload2/index.vue

@ -0,0 +1,82 @@
<!--<template>-->
<!--<div>-->
<!--<el-upload-->
<!--class="pic-uploader-component"-->
<!--:action="imagesUploadApi"-->
<!--:headers="headers"-->
<!--:show-file-list="false"-->
<!--:on-success="handleUploadSuccess"-->
<!--:before-upload="beforeAvatarUpload">-->
<!--<img v-if="value" :src="resourcesUrl + value" class="pic">-->
<!--<i v-else class="el-icon-plus pic-uploader-icon"></i>-->
<!--</el-upload>-->
<!--</div>-->
<!--</template>-->
<!--<script>-->
<!--import { getToken } from '@/utils/auth'-->
<!--import { mapGetters } from 'vuex'-->
<!--export default {-->
<!--data () {-->
<!--return {-->
<!--resourcesUrl: '',-->
<!--headers: {-->
<!--'Authorization': 'Bearer ' + getToken()-->
<!--}-->
<!--}-->
<!--},-->
<!--computed: {-->
<!--...mapGetters([-->
<!--'imagesUploadApi'-->
<!--])-->
<!--},-->
<!--props: {-->
<!--value: {-->
<!--default: '',-->
<!--type: String-->
<!--}-->
<!--},-->
<!--methods: {-->
<!--// -->
<!--handleUploadSuccess(response, file, fileList) {-->
<!--console.log(file)-->
<!--this.$emit('input', file.response.data[0])-->
<!--},-->
<!--// -->
<!--beforeAvatarUpload(file) {-->
<!--const isLt2M = file.size / 1024 / 1024 < 2-->
<!--if (!isLt2M) {-->
<!--this.$message.error('上传头像图片大小不能超过 2MB!')-->
<!--}-->
<!--return isLt2M-->
<!--}-->
<!--}-->
<!--}-->
<!--</script>-->
<!--<style lang="scss">-->
<!--.pic-uploader-component .el-upload {-->
<!--border: 1px dashed #d9d9d9;-->
<!--border-radius: 6px;-->
<!--cursor: pointer;-->
<!--position: relative;-->
<!--overflow: hidden;-->
<!--.pic-uploader-icon {-->
<!--font-size: 28px;-->
<!--color: #8c939d;-->
<!--width: 100%;-->
<!--height: 40px;-->
<!--//line-height: 178px;-->
<!--text-align: center;-->
<!--}-->
<!--.pic {-->
<!--width: 100%;-->
<!--height: 40px;-->
<!--display: block;-->
<!--}-->
<!--}-->
<!--.pic-uploader-component .el-upload:hover {-->
<!--border-color: #409EFF;-->
<!--}-->
<!--</style>-->

65
src/config/index.js

@ -0,0 +1,65 @@
/**
* @description 系统全局配置
*/
export default {
/**
* @description 记住密码状态下的token在Cookie中存储的天数默认1天
*/
tokenCookieExpires: 1,
/**
* @description 记住密码状态下的密码在Cookie中存储的天数默认1天
*/
passCookieExpires: 1,
/**
* @description 此处修改网站名称
*/
webName: 'Yshop',
/**
* @description 是否只保持一个子菜单的展开
*/
uniqueOpened: true,
/**
* @description token key
*/
TokenKey: 'Yshop-TOEKN',
/**
* @description 请求超时时间毫秒默认2分钟
*/
timeout: 1200000,
/**
* @description 是否显示 tagsView
*/
tagsView: true,
/**
* @description 固定头部
*/
fixedHeader: true,
/**
* @description 是否显示logo
*/
sidebarLogo: true,
/**
* 是否显示设置的悬浮按钮
*/
settingBtn: false,
/**
* 是否显示设置的底部信息
*/
showFooter: true,
/**
* 底部文字支持html语法
*/
footerTxt: '© 2019 YSHOP <a href="http://www.apache.org/licenses/LICENSE-2.0" target="_blank">Apache License 2.0</a>',
/**
* 备案号
*/
caseNumber: '豫ICP备17049587号'
}

9
src/icons/index.js

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

1
src/icons/svg/Steve-Jobs.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.2 KiB

2
src/icons/svg/add.svg

@ -0,0 +1,2 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1543748455010" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1208" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: rbicon; src: url("chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2") format("woff2"); font-weight: normal; font-style: normal; }
</style></defs><path d="M590.769231 571.076923h324.923077c15.753846 0 29.538462-13.784615 29.538461-29.538461v-59.076924c0-15.753846-13.784615-29.538462-29.538461-29.538461H590.769231c-11.815385 0-19.692308-7.876923-19.692308-19.692308V108.307692c0-15.753846-13.784615-29.538462-29.538461-29.538461h-59.076924c-15.753846 0-29.538462 13.784615-29.538461 29.538461V433.230769c0 11.815385-7.876923 19.692308-19.692308 19.692308H108.307692c-15.753846 0-29.538462 13.784615-29.538461 29.538461v59.076924c0 15.753846 13.784615 29.538462 29.538461 29.538461H433.230769c11.815385 0 19.692308 7.876923 19.692308 19.692308v324.923077c0 15.753846 13.784615 29.538462 29.538461 29.538461h59.076924c15.753846 0 29.538462-13.784615 29.538461-29.538461V590.769231c0-11.815385 7.876923-19.692308 19.692308-19.692308z" p-id="1209"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

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

Loading…
Cancel
Save