Compare commits
441 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
10a7613e6e | ||
|
3c79f89416 | ||
|
35f0e431b8 | ||
|
60be8cf4ec | ||
|
17e41ce1fc | ||
|
baae56f715 | ||
|
9a7493c99c | ||
|
cc60ff1a27 | ||
|
c0e5f2ccec | ||
|
3fa8995690 | ||
|
a4d4b28ce3 | ||
|
d7a530db46 | ||
|
7f5fbb7426 | ||
|
6000da4220 | ||
|
c0aec854af | ||
|
8062905b17 | ||
|
56635a948b | ||
|
2d0c6c2a2b | ||
|
5d4bd75b22 | ||
|
6d43e9af42 | ||
|
c2d2c2c686 | ||
|
a9b3da4d19 | ||
|
baf063f9ea | ||
|
816d19f7da | ||
|
ce83564335 | ||
|
1345a02cd0 | ||
|
449fd99f9d | ||
|
97a1417112 | ||
|
ba89880736 | ||
|
aa4e3d93d5 | ||
|
d730f6d783 | ||
|
8d42b936e5 | ||
|
da1dafda54 | ||
|
8127121ab6 | ||
|
d29a14936a | ||
|
80e3ad42bc | ||
|
bb7fa9abb6 | ||
|
990daf2d27 | ||
|
939f8640d3 | ||
|
a1ae7d1e3f | ||
|
8ddc7c167c | ||
|
ce536b95c2 | ||
|
4dbbb852a9 | ||
|
f0d60a8242 | ||
|
9ddd117d5e | ||
|
2fc5b9d594 | ||
|
83c6381a4b | ||
|
0c41878174 | ||
|
a5c34a8514 | ||
|
df076bda24 | ||
|
62b57a97cb | ||
|
867377a6d2 | ||
|
a2e5370ae8 | ||
|
39b64d0704 | ||
|
d2b2631fb1 | ||
|
345b46bf6f | ||
|
b0fc3a943e | ||
|
37f66c8786 | ||
|
5a333faa2b | ||
|
f2d3823069 | ||
|
33179d96b7 | ||
|
935cd77d4f | ||
|
010ffdaacb | ||
|
5c6b2a2048 | ||
|
34a76d5894 | ||
|
0f1a845189 | ||
|
5de611523c | ||
|
c2915c93d3 | ||
|
e661ae0813 | ||
|
915c2078cb | ||
|
407c2719cb | ||
|
d9b5e4b766 | ||
|
75a510edbd | ||
|
31e22aaf0e | ||
|
63ea2a9459 | ||
|
ae2b7a86ef | ||
|
23b7dfe2a4 | ||
|
10296fd022 | ||
|
9e7a03fcd8 | ||
|
f74d08248e | ||
|
d638eaa6bf | ||
|
344ad1d1f6 | ||
|
c82103d64d | ||
|
74c54cbf73 | ||
|
a8dab1687a | ||
|
19ab79c88c | ||
|
2262cb98ef | ||
|
7cea44c216 | ||
|
6c79a54a53 | ||
|
97979c5059 | ||
|
8130f9f250 | ||
|
57dc7fe2e8 | ||
|
e841ac77fd | ||
|
ec2c70d181 | ||
|
3a7c51cd8a | ||
|
a4b0785f0f | ||
|
c1e956a5a0 | ||
|
acd66d7d6c | ||
|
533890a376 | ||
|
10fc9c11b8 | ||
|
c4e81a1a61 | ||
|
b021ce4f0b | ||
|
59bb834da8 | ||
|
1a434dbd2a | ||
|
501bd23c20 | ||
|
3619242076 | ||
|
a4281b62dc | ||
|
6b41fa7f31 | ||
|
463fc93af9 | ||
|
4d69602595 | ||
|
517c1959d8 | ||
|
3d3e56de12 | ||
|
313af63f33 | ||
|
83576d88d7 | ||
|
9df2666304 | ||
|
a03b37ff30 | ||
|
435b2ff585 | ||
|
bad44562e7 | ||
|
1738f601a3 | ||
|
7b23be808f | ||
|
d14d1ee9b8 | ||
|
a19e8a226f | ||
|
7bfb900e59 | ||
|
d593670835 | ||
|
80baf8d202 | ||
|
dcf65935e1 | ||
|
b8ccb5c6a1 | ||
|
556b5bf6fc | ||
|
f0e9a50919 | ||
|
d23cfef147 | ||
|
bdde606ee5 | ||
|
e2e92e9df9 | ||
|
4c7c5c1da1 | ||
|
e414f89e27 | ||
|
aa25af0252 | ||
|
6a0ef90d5c | ||
|
15d18db7e0 | ||
|
c61570d2ff | ||
|
fb146e2a36 | ||
|
1ebed9eaec | ||
|
f9aa9e2c53 | ||
|
4877f13239 | ||
|
75c165c83f | ||
|
6a3ec944c6 | ||
|
5a65032772 | ||
|
346c1b8a87 | ||
|
a84952577f | ||
|
fd13c7d042 | ||
|
8b38e77922 | ||
|
8a7b82ac50 | ||
|
69b514ee10 | ||
|
4856f06f41 | ||
|
8102c8a924 | ||
|
4db7c2deff | ||
|
5ec6f73d6f | ||
|
1fb75f491d | ||
|
cbda23e3db | ||
|
094935b758 | ||
|
b2b62914ef | ||
|
2436182baf | ||
|
002cf50440 | ||
|
365dde7e66 | ||
|
d4261bc401 | ||
|
88c7653dab | ||
|
04be83ac7c | ||
|
ad11e315e6 | ||
|
a44c82f937 | ||
|
902afbe47a | ||
|
3b3cd76e51 | ||
|
58784c81fd | ||
|
d1f84218c6 | ||
|
2021fb575d | ||
|
ec3e4b5571 | ||
|
1ba8f20b76 | ||
|
431c78ff26 | ||
|
51c2c354ba | ||
|
9f034092a9 | ||
|
de925b254e | ||
|
f777920f89 | ||
|
d0281e5707 | ||
|
8c3afcc3ab | ||
|
4e29918bac | ||
|
e5f2eb9955 | ||
|
93eaf9d36f | ||
|
5b5a5ea3ef | ||
|
b2317e3209 | ||
|
088b36aec1 | ||
|
23cf2fab0b | ||
|
f14936f2bf | ||
|
c1c51d7b57 | ||
|
45d62e4860 | ||
|
ed8a89afa5 | ||
|
d741904f50 | ||
|
9445f0fbbb | ||
|
c10ea64ba7 | ||
|
9c10453f97 | ||
|
f12fde6297 | ||
|
db3122327c | ||
|
a55210f9a6 | ||
|
af0cce4f1e | ||
|
523537953a | ||
|
1d85bd7a70 | ||
|
805cd799f8 | ||
|
48bb1c4bb1 | ||
|
817ee10a7a | ||
|
25a1a2582a | ||
|
a7ac5f8019 | ||
|
64589950e5 | ||
|
f32fa56ba7 | ||
|
4a8b985b99 | ||
|
b796a4c9e4 | ||
|
d7eba7e2b3 | ||
|
f82c33b0d3 | ||
|
a4f230ff6d | ||
|
9b96868586 | ||
|
55358b4107 | ||
|
182ea21a29 | ||
|
08e40f1a31 | ||
|
285233e201 | ||
|
bfb0358217 | ||
|
a4764d880d | ||
|
a5b571d2a0 | ||
|
58c62442c3 | ||
|
768dacdef7 | ||
|
7e82c948da | ||
|
58e81ffad0 | ||
|
680300e4e6 | ||
|
ebb9d19ccd | ||
|
b78cf5c6e1 | ||
|
fd3c79493f | ||
|
6c33fb0072 | ||
|
db878d5eac | ||
|
23eb948aa6 | ||
|
272f740597 | ||
|
a62c293e42 | ||
|
ad0b2bda34 | ||
|
107d64fb06 | ||
|
ad5727eaf5 | ||
|
d7fc23d78e | ||
|
21cfb454d7 | ||
|
4e6a060dfe | ||
|
d21a2ab062 | ||
|
d91f3a8661 | ||
|
82e5955c3b | ||
|
886a19ddfc | ||
|
3573a51dbd | ||
|
b2e0accd97 | ||
|
8688661c8c | ||
|
a964274d63 | ||
|
b05b8f6bc3 | ||
|
8e72934d38 | ||
|
e6fb49260d | ||
|
687a3adc37 | ||
|
3ff12474cd | ||
|
3f742a4dc1 | ||
|
75f2a1be83 | ||
|
1e37ca0c1e | ||
|
c434b81f7a | ||
|
0f7f73fd1a | ||
|
255f68709e | ||
|
107e08c08e | ||
|
1f26734a13 | ||
|
839ba55fe1 | ||
|
58caeee45c | ||
|
9d5c100960 | ||
|
d78f6d61d7 | ||
|
e619e44d4f | ||
|
40472b21dd | ||
|
b5d17cd63b | ||
|
44a99a2987 | ||
|
195624a0a1 | ||
|
1f02623837 | ||
|
ee3265a151 | ||
|
09348fd627 | ||
|
f6904cfa60 | ||
|
f206eb87e6 | ||
|
0e21e005cb | ||
|
de05ab5f80 | ||
|
cfd334a4b4 | ||
|
4ce9622589 | ||
|
4a06f1603e | ||
|
99aa951c38 | ||
|
6e5592fff7 | ||
|
719dba0bd9 | ||
|
32577b038a | ||
|
8cbde7bff5 | ||
|
4063a4f08a | ||
|
98e02874ed | ||
|
c6419173ac | ||
|
5c0ccf9df9 | ||
|
3ed878b8f3 | ||
|
c6ba8815fb | ||
|
36e9e0a9f0 | ||
|
9425bcb1f9 | ||
|
77781e418a | ||
|
2a45a8122a | ||
|
5de9f9c305 | ||
|
14f6aebfe1 | ||
|
066dc41e76 | ||
|
7c23832b18 | ||
|
702d622b31 | ||
|
7f6b9e9599 | ||
|
05a1e3215b | ||
|
0b78552a5e | ||
|
da87fedc2f | ||
|
89222781e8 | ||
|
ae2b73ce6b | ||
|
f91bfb4893 | ||
|
ab8c3308ea | ||
|
7f08145e79 | ||
|
7890b2816c | ||
|
73d7febde3 | ||
|
fa3279b04c | ||
|
47ef56cddf | ||
|
a87b1aafd0 | ||
|
b35b4a5d5a | ||
|
cac285dfdd | ||
|
38ac6c9942 | ||
|
26d9c247d4 | ||
|
1ead1eae97 | ||
|
806edf8d7f | ||
|
dcc185c25c | ||
|
a41c31fe0f | ||
|
364d93e4bb | ||
|
397f4b4ab8 | ||
|
5f68e2d231 | ||
|
42a34f7a16 | ||
|
8034f474d6 | ||
|
ec771b15aa | ||
|
f348ddfe5e | ||
|
252bbc110b | ||
|
4845e0ab17 | ||
|
5cbafb7fc7 | ||
|
49b17862fc | ||
|
112f862988 | ||
|
163b9e1b5f | ||
|
18c08f3682 | ||
|
be3cb1819e | ||
|
f3392a07ce | ||
|
748472d757 | ||
|
f6937b06c9 | ||
|
a85d954abc | ||
|
fc0101f4e3 | ||
|
2eafdef2f9 | ||
|
3d2ae39420 | ||
|
f6fa5007dc | ||
|
cd854761f5 | ||
|
d214332218 | ||
|
7c3404dc13 | ||
|
6208acc477 | ||
|
db25716d9d | ||
|
abd9df49cc | ||
|
8a780cca6e | ||
|
9dcf6557cf | ||
|
4124768c6e | ||
|
2852125b76 | ||
|
b9cdfdc476 | ||
|
f90b6e8731 | ||
|
80e3f41419 | ||
|
9caba7cc66 | ||
|
9eeb53f4ff | ||
|
2f34d7ac52 | ||
|
2cb54136b5 | ||
|
fa3fa22f3c | ||
|
ff4364262d | ||
|
75921a02ae | ||
|
d58dafeda9 | ||
|
1ab1f1b224 | ||
|
94fb7964b2 | ||
|
2e90b7c614 | ||
|
2382dd8abc | ||
|
19fc285134 | ||
|
0bc4a06611 | ||
|
b16b5893c8 | ||
|
68249a0458 | ||
|
198b1e2ab5 | ||
|
98bc9b2386 | ||
|
0a490feda3 | ||
|
54c130a1b0 | ||
|
3392beaa84 | ||
|
cb95fb2371 | ||
|
ffed8a92a7 | ||
|
9072bc93d7 | ||
|
6ef4680adb | ||
|
a5f486a03d | ||
|
a7975eea3a | ||
|
21c5a33401 | ||
|
9f2e74edef | ||
|
24f23bc9b3 | ||
|
9baf57f4ee | ||
|
50bcff1c61 | ||
|
63d9407a4a | ||
|
3d568c6caa | ||
|
5bbe94728e | ||
|
2e9ebf7b1d | ||
|
6ca991d186 | ||
|
94096a3832 | ||
|
a73159aef8 | ||
|
6530ce5a35 | ||
|
a90fa8d106 | ||
|
d4d46eeaae | ||
|
ce3ee754f5 | ||
|
25e1c0f808 | ||
|
2cef3795ac | ||
|
066ab7ec22 | ||
|
54e6a72f3a | ||
|
19c48c7a30 | ||
|
a023e7437c | ||
|
99a2a3956d | ||
|
b779b1ff9c | ||
|
e1da650eb1 | ||
|
9eaf0962c6 | ||
|
d26e78a845 | ||
|
930f4afd56 | ||
|
d4afe98eb3 | ||
|
c5271f033e | ||
|
c43d938ee0 | ||
|
c426513b5d | ||
|
5dbd0fb3d2 | ||
|
fc5f520c5b | ||
|
105bf645ac | ||
|
707ff81510 | ||
|
cf826d4230 | ||
|
b4ac11b761 | ||
|
d7ccdab2ad | ||
|
f7f7ff97b7 | ||
|
b2c6fa2d47 | ||
|
01e9be732e | ||
|
26cec23c6f | ||
|
1d665eae71 | ||
|
f8f0294c58 | ||
|
2137ccd4b3 | ||
|
e7f1540c04 | ||
|
366575e8f6 | ||
|
3846591c00 | ||
|
d588c7681e | ||
|
0b836882c5 | ||
|
b818fd8a29 | ||
|
7d7b08644f | ||
|
2eb09c01b7 | ||
|
f8483833f1 |
18
.babelrc
@ -1,18 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
["env", {
|
||||
"modules": false,
|
||||
"targets": {
|
||||
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
|
||||
}
|
||||
}],
|
||||
"stage-2"
|
||||
],
|
||||
"plugins": ["transform-vue-jsx", "transform-runtime"],
|
||||
"env": {
|
||||
"test": {
|
||||
"presets": ["env", "stage-2"],
|
||||
"plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"]
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
10
.env
Normal file
@ -0,0 +1,10 @@
|
||||
VUE_APP_PUBLIC_PATH=/
|
||||
VUE_APP_NAME=Admin
|
||||
VUE_APP_ROUTES_KEY=admin.routes
|
||||
VUE_APP_PERMISSIONS_KEY=admin.permissions
|
||||
VUE_APP_ROLES_KEY=admin.roles
|
||||
VUE_APP_USER_KEY=admin.user
|
||||
VUE_APP_SETTING_KEY=admin.setting
|
||||
VUE_APP_TBAS_KEY=admin.tabs
|
||||
VUE_APP_TBAS_TITLES_KEY=admin.tabs.titles
|
||||
VUE_APP_API_BASE_URL=http://api.iczer.com
|
1
.env.development
Normal file
@ -0,0 +1 @@
|
||||
VUE_APP_API_BASE_URL=http://dev.iczer.com
|
@ -1,5 +0,0 @@
|
||||
/build/
|
||||
/config/
|
||||
/dist/
|
||||
/*.js
|
||||
/test/unit/coverage/
|
29
.eslintrc.js
@ -1,29 +0,0 @@
|
||||
// https://eslint.org/docs/user-guide/configuring
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint'
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
},
|
||||
extends: [
|
||||
// https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
|
||||
// consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
|
||||
'plugin:vue/essential',
|
||||
// https://github.com/standard/standard/blob/master/docs/RULES-en.md
|
||||
'standard'
|
||||
],
|
||||
// required to lint *.vue files
|
||||
plugins: [
|
||||
'vue'
|
||||
],
|
||||
// add your custom rules here
|
||||
rules: {
|
||||
// allow async-await
|
||||
'generator-star-spacing': 'off',
|
||||
// allow debugger during development
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
|
||||
}
|
||||
}
|
5
.gitattributes
vendored
@ -1,5 +0,0 @@
|
||||
*.js linguist-language=vue
|
||||
|
||||
*.css linguist-language=vue
|
||||
|
||||
*.html linguist-language=vue
|
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
4
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
.DS_Store
|
||||
node_modules/
|
||||
/dist/
|
||||
dist/
|
||||
admindb/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
@ -16,3 +17,4 @@ selenium-debug.log
|
||||
*.njsproj
|
||||
*.sln
|
||||
package-lock.json
|
||||
.env.production.local
|
||||
|
@ -1,10 +0,0 @@
|
||||
// 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": {}
|
||||
}
|
||||
}
|
47
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-present iczer
|
||||
Copyright (c) 2018 iczer
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@ -19,48 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017-present ant-design-vue
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
MIT LICENSE
|
||||
|
||||
Copyright (c) 2015-present Alipay.com, https://www.alipay.com/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
53
README.en-US.md
Normal file
@ -0,0 +1,53 @@
|
||||
[简体中文](./README.md) | English
|
||||
<h1 align="center">Vue Antd Admin</h1>
|
||||
|
||||
<div align="center">
|
||||
|
||||
[Ant Design Pro](https://github.com/ant-design/ant-design-pro)'s implementation with Vue.
|
||||
An out-of-box UI solution for enterprise applications as a React boilerplate.
|
||||
|
||||
[](https://github.com/iczer/vue-antd-admin/blob/master/LICENSE)
|
||||
[](https://david-dm.org/iczer/vue-antd-admin)
|
||||
[](https://david-dm.org/iczer/vue-antd-admin?type=dev)
|
||||
[](https://github.com/iczer/vue-antd-admin/releases/latest)
|
||||

|
||||
|
||||
Multiple theme modes available:
|
||||

|
||||
</div>
|
||||
|
||||
- Preview:https://vue-antd-admin.pages.dev
|
||||
- Documentation:https://doc.vue-antd-admin.pages.dev
|
||||
- FAQ:https://doc.vue-antd-admin.pages.dev/start/faq.html
|
||||
- Mirror Repo in China:https://gitee.com/iczer/vue-antd-admin
|
||||
|
||||
## Browsers support
|
||||
Modern browsers and IE10.
|
||||
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| IE10, Edge | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
|
||||
|
||||
## Usage
|
||||
### clone
|
||||
```bash
|
||||
$ git clone https://github.com/iczer/vue-antd-admin.git
|
||||
```
|
||||
### yarn
|
||||
```bash
|
||||
$ yarn install
|
||||
$ yarn serve
|
||||
```
|
||||
### or npm
|
||||
```
|
||||
$ npm install
|
||||
$ npm run serve
|
||||
```
|
||||
More instructions at [documentation](https://iczer.gitee.io/vue-antd-admin-docs).
|
||||
|
||||
## Contributing
|
||||
Any type of contribution is welcome, here are some examples of how you may contribute to this project: :star2::
|
||||
- Use Vue Antd Admin in your daily work.
|
||||
- Submit [Issue](https://github.com/iczer/vue-antd-admin/issues) to report :bug: or ask questions.
|
||||
- Propose [Pull Request](https://github.com/iczer/vue-antd-admin/pulls) to improve our code.
|
||||
- Join the community and share your experiences with us. QQ Group:942083829、812277510(full)、610090280(full)
|
95
README.md
@ -1,47 +1,68 @@
|
||||
# vue-antd-admin
|
||||
**[Ant Design Pro](https://github.com/ant-design/ant-design-pro) 的 Vue 实现**
|
||||
|
||||
一个开箱即用的中后台前端/设计解决方案(主要依赖组件库 [ant-design-vue](https://github.com/vueComponent/ant-design-vue) )
|
||||
|
||||
[预览地址](https://iczer.gitee.io/vue-antd-pro)
|
||||
简体中文 | [English](./README.en-US.md)
|
||||
<h1 align="center">Vue Antd Admin</h1>
|
||||
|
||||

|
||||
## 环境
|
||||
* node -- 运行/编译
|
||||
* yarn -- 依赖管理
|
||||
* webpack -- 打包
|
||||
* eslint -- 代码规约
|
||||
* vue-cli -- 构建
|
||||
## 依赖
|
||||
* @antv/data-set: ^0.8.9
|
||||
* ant-design-vue: ^1.0.3
|
||||
* axios: ^0.18.0
|
||||
* clipboard: ^2.0.1
|
||||
* date-fns: ^1.29.0
|
||||
* enquire.js: ^2.1.6
|
||||
* mockjs: ^1.0.1-beta3
|
||||
* pouchdb: ^7.0.0
|
||||
* viser-vue: ^2.2.5
|
||||
* vue: ^2.5.17
|
||||
* vue-router: ^3.0.1
|
||||
* vuedraggable: ^2.16.0
|
||||
* vuex: ^3.0.1
|
||||
## 安装
|
||||
克隆项目到本地:
|
||||
```
|
||||
<div align="center">
|
||||
|
||||
[Ant Design Pro](https://github.com/ant-design/ant-design-pro) 的 Vue 实现版本
|
||||
开箱即用的中后台前端/设计解决方案
|
||||
|
||||
:star::star::star:
|
||||
vue3 版本现已推出,更名为
|
||||
[stepin-template](https://github.com/stepui/stepin-template),欢迎体验,
|
||||
[立即前往](https://github.com/stepui/stepin-template)
|
||||
--
|
||||
|
||||
[](https://github.com/iczer/vue-antd-admin/blob/master/LICENSE)
|
||||
[](https://david-dm.org/iczer/vue-antd-admin)
|
||||
[](https://david-dm.org/iczer/vue-antd-admin?type=dev)
|
||||
[](https://github.com/iczer/vue-antd-admin/releases/latest)
|
||||

|
||||
|
||||
多种主题模式可选:
|
||||

|
||||
</div>
|
||||
|
||||
- 预览地址:https://vue-antd-admin.pages.dev
|
||||
- 使用文档:https://doc.vue-antd-admin.pages.dev
|
||||
- 常见问题:https://doc.vue-antd-admin.pages.dev/start/faq.html
|
||||
- 国内镜像:https://gitee.com/iczer/vue-antd-admin
|
||||
|
||||
## 浏览器支持
|
||||
现代浏览器及 IE10
|
||||
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| IE10, Edge | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
|
||||
|
||||
## 使用
|
||||
### clone
|
||||
```bash
|
||||
$ git clone https://github.com/iczer/vue-antd-admin.git
|
||||
```
|
||||
安装依赖:
|
||||
```
|
||||
### yarn
|
||||
```bash
|
||||
$ yarn install
|
||||
$ yarn serve
|
||||
```
|
||||
## 启动
|
||||
### or npm
|
||||
```
|
||||
$ yarn start
|
||||
$ npm install
|
||||
$ npm run serve
|
||||
```
|
||||
## 文档
|
||||
编写中...
|
||||
## 说明
|
||||
该项目由仓主在业余由兴趣驱动完成,仍在不断开发完善中。详见:[开发进度](https://github.com/iczer/vue-antd-admin/projects/1)
|
||||
更多信息参考 [使用文档](https://iczer.gitee.io/vue-antd-admin-docs)
|
||||
|
||||
如有任何疑问或功能需求,欢迎 [Issue](https://github.com/iczer/vue-antd-admin/issues)。
|
||||
## 参与贡献
|
||||
我们非常欢迎你的贡献,你可以通过以下方式和我们一起共建 :star2::
|
||||
- 在你的公司或个人项目中使用 Vue Antd Admin。
|
||||
- 通过 [Issue](https://github.com/iczer/vue-antd-admin/issues) 报告:bug:或进行咨询。
|
||||
- 提交 [Pull Request](https://github.com/iczer/vue-antd-admin/pulls) 改进 Admin 的代码。
|
||||
- 加入社群,与小伙伴们一同交流心得。QQ群:942083829、 812277510(已满)、610090280(已满)
|
||||
|
||||
## 打赏
|
||||
如果该项目对您有所帮助,可以请作者喝一杯咖啡。
|
||||
<p>
|
||||
<img src="./src/assets/img/alipay.png" width="320px" style="display: inline-block;" />
|
||||
<img src="./src/assets/img/wechatpay.png" width="320px" style="display: inline-block; margin-left: 24px;" />
|
||||
</p>
|
||||
|
@ -1 +0,0 @@
|
||||
theme: jekyll-theme-slate
|
13
babel.config.js
Normal file
@ -0,0 +1,13 @@
|
||||
const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV)
|
||||
|
||||
const plugins = []
|
||||
if (IS_PROD) {
|
||||
plugins.push('transform-remove-console')
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
],
|
||||
plugins
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
'use strict'
|
||||
require('./check-versions')()
|
||||
|
||||
process.env.NODE_ENV = 'production'
|
||||
|
||||
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')
|
||||
|
||||
const spinner = ora('building for production...')
|
||||
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, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
|
||||
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'
|
||||
))
|
||||
})
|
||||
})
|
@ -1,54 +0,0 @@
|
||||
'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:'))
|
||||
console.log()
|
||||
|
||||
for (let i = 0; i < warnings.length; i++) {
|
||||
const warning = warnings[i]
|
||||
console.log(' ' + warning)
|
||||
}
|
||||
|
||||
console.log()
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
BIN
build/logo.png
Before Width: | Height: | Size: 6.7 KiB |
101
build/utils.js
@ -1,101 +0,0 @@
|
||||
'use strict'
|
||||
const path = require('path')
|
||||
const config = require('../config')
|
||||
const ExtractTextPlugin = require('extract-text-webpack-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 = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
|
||||
|
||||
if (loader) {
|
||||
loaders.push({
|
||||
loader: loader + '-loader',
|
||||
options: Object.assign({}, loaderOptions, {
|
||||
sourceMap: options.sourceMap
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Extract CSS when that option is specified
|
||||
// (which is the case during production build)
|
||||
if (options.extract) {
|
||||
return ExtractTextPlugin.extract({
|
||||
use: loaders,
|
||||
fallback: 'vue-style-loader'
|
||||
})
|
||||
} else {
|
||||
return ['vue-style-loader'].concat(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')
|
||||
})
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
'use strict'
|
||||
const utils = require('./utils')
|
||||
const config = require('../config')
|
||||
const isProduction = process.env.NODE_ENV === 'production'
|
||||
const sourceMapEnabled = isProduction
|
||||
? config.build.productionSourceMap
|
||||
: config.dev.cssSourceMap
|
||||
|
||||
module.exports = {
|
||||
loaders: utils.cssLoaders({
|
||||
sourceMap: sourceMapEnabled,
|
||||
extract: isProduction
|
||||
}),
|
||||
cssSourceMap: sourceMapEnabled,
|
||||
cacheBusting: config.dev.cacheBusting,
|
||||
transformToRequire: {
|
||||
video: ['src', 'poster'],
|
||||
source: 'src',
|
||||
img: 'src',
|
||||
image: 'xlink:href'
|
||||
}
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
'use strict'
|
||||
const path = require('path')
|
||||
const utils = require('./utils')
|
||||
const config = require('../config')
|
||||
const vueLoaderConfig = require('./vue-loader.conf')
|
||||
|
||||
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 = {
|
||||
context: path.resolve(__dirname, '../'),
|
||||
entry: {
|
||||
app: './src/main.js'
|
||||
},
|
||||
output: {
|
||||
path: config.build.assetsRoot,
|
||||
filename: '[name].js',
|
||||
publicPath: process.env.NODE_ENV === 'production'
|
||||
? config.build.assetsPublicPath
|
||||
: config.dev.assetsPublicPath
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.vue', '.json'],
|
||||
alias: {
|
||||
'vue$': 'vue/dist/vue.esm.js',
|
||||
'@': resolve('src'),
|
||||
}
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
...(config.dev.useEslint ? [createLintingRule()] : []),
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader',
|
||||
options: vueLoaderConfig
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
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]')
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
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'
|
||||
}
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
'use strict'
|
||||
const utils = require('./utils')
|
||||
const webpack = require('webpack')
|
||||
const config = require('../config')
|
||||
const merge = require('webpack-merge')
|
||||
const path = require('path')
|
||||
const baseWebpackConfig = require('./webpack.base.conf')
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
|
||||
const portfinder = require('portfinder')
|
||||
|
||||
const HOST = process.env.HOST
|
||||
const PORT = process.env.PORT && Number(process.env.PORT)
|
||||
|
||||
const devWebpackConfig = merge(baseWebpackConfig, {
|
||||
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: {
|
||||
rewrites: [
|
||||
{ from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
|
||||
],
|
||||
},
|
||||
hot: true,
|
||||
contentBase: false, // since we use CopyWebpackPlugin.
|
||||
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(),
|
||||
new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
// https://github.com/ampedandwired/html-webpack-plugin
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'index.html',
|
||||
template: 'index.html',
|
||||
inject: true
|
||||
}),
|
||||
// copy custom static assets
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
from: path.resolve(__dirname, '../static'),
|
||||
to: config.dev.assetsSubDirectory,
|
||||
ignore: ['.*']
|
||||
}
|
||||
])
|
||||
]
|
||||
})
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
})
|
@ -1,149 +0,0 @@
|
||||
'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 ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
|
||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
|
||||
|
||||
const env = process.env.NODE_ENV === 'testing'
|
||||
? require('../config/test.env')
|
||||
: require('../config/prod.env')
|
||||
|
||||
const webpackConfig = merge(baseWebpackConfig, {
|
||||
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].js'),
|
||||
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
|
||||
},
|
||||
plugins: [
|
||||
// http://vuejs.github.io/vue-loader/en/workflow/production.html
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': env
|
||||
}),
|
||||
new UglifyJsPlugin({
|
||||
uglifyOptions: {
|
||||
compress: {
|
||||
warnings: false
|
||||
}
|
||||
},
|
||||
sourceMap: config.build.productionSourceMap,
|
||||
parallel: true
|
||||
}),
|
||||
// extract css into its own file
|
||||
new ExtractTextPlugin({
|
||||
filename: utils.assetsPath('css/[name].[contenthash].css'),
|
||||
// Setting the following option to `false` will not extract CSS from codesplit chunks.
|
||||
// Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
|
||||
// It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
|
||||
// increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
|
||||
allChunks: true,
|
||||
}),
|
||||
// Compress extracted CSS. We are using this plugin so that possible
|
||||
// duplicated CSS from different components can be deduped.
|
||||
new OptimizeCSSPlugin({
|
||||
cssProcessorOptions: config.build.productionSourceMap
|
||||
? { safe: true, map: { inline: false } }
|
||||
: { safe: true }
|
||||
}),
|
||||
// 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: process.env.NODE_ENV === 'testing'
|
||||
? 'index.html'
|
||||
: config.build.index,
|
||||
template: 'index.html',
|
||||
inject: true,
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
removeAttributeQuotes: true
|
||||
// more options:
|
||||
// https://github.com/kangax/html-minifier#options-quick-reference
|
||||
},
|
||||
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
|
||||
chunksSortMode: 'dependency'
|
||||
}),
|
||||
// keep module.id stable when vendor modules does not change
|
||||
new webpack.HashedModuleIdsPlugin(),
|
||||
// enable scope hoisting
|
||||
new webpack.optimize.ModuleConcatenationPlugin(),
|
||||
// split vendor js into its own file
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'vendor',
|
||||
minChunks (module) {
|
||||
// any required modules inside node_modules are extracted to vendor
|
||||
return (
|
||||
module.resource &&
|
||||
/\.js$/.test(module.resource) &&
|
||||
module.resource.indexOf(
|
||||
path.join(__dirname, '../node_modules')
|
||||
) === 0
|
||||
)
|
||||
}
|
||||
}),
|
||||
// extract webpack runtime and module manifest to its own file in order to
|
||||
// prevent vendor hash from being updated whenever app bundle is updated
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'manifest',
|
||||
minChunks: Infinity
|
||||
}),
|
||||
// This instance extracts shared chunks from code splitted chunks and bundles them
|
||||
// in a separate chunk, similar to the vendor chunk
|
||||
// see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'app',
|
||||
async: 'vendor-async',
|
||||
children: true,
|
||||
minChunks: 3
|
||||
}),
|
||||
|
||||
// copy custom static assets
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
from: path.resolve(__dirname, '../static'),
|
||||
to: config.build.assetsSubDirectory,
|
||||
ignore: ['.*']
|
||||
}
|
||||
])
|
||||
]
|
||||
})
|
||||
|
||||
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.bundleAnalyzerReport) {
|
||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
|
||||
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
|
||||
}
|
||||
|
||||
module.exports = webpackConfig
|
@ -1,7 +0,0 @@
|
||||
'use strict'
|
||||
const merge = require('webpack-merge')
|
||||
const prodEnv = require('./prod.env')
|
||||
|
||||
module.exports = merge(prodEnv, {
|
||||
NODE_ENV: '"development"'
|
||||
})
|
@ -1,76 +0,0 @@
|
||||
'use strict'
|
||||
// Template version: 1.3.1
|
||||
// see http://vuejs-templates.github.io/webpack for documentation.
|
||||
|
||||
const path = require('path')
|
||||
|
||||
module.exports = {
|
||||
dev: {
|
||||
|
||||
// Paths
|
||||
assetsSubDirectory: 'static',
|
||||
assetsPublicPath: '/',
|
||||
proxyTable: {},
|
||||
|
||||
// Various Dev Server settings
|
||||
host: 'localhost', // can be overwritten by process.env.HOST
|
||||
port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
|
||||
autoOpenBrowser: false,
|
||||
errorOverlay: true,
|
||||
notifyOnErrors: true,
|
||||
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-module-eval-source-map',
|
||||
|
||||
// If you have problems debugging vue-files in devtools,
|
||||
// set this to false - it *may* help
|
||||
// https://vue-loader.vuejs.org/en/options.html#cachebusting
|
||||
cacheBusting: true,
|
||||
|
||||
cssSourceMap: true
|
||||
},
|
||||
|
||||
build: {
|
||||
// Template for index.html
|
||||
index: path.resolve(__dirname, '../dist/index.html'),
|
||||
|
||||
// Paths
|
||||
assetsRoot: path.resolve(__dirname, '../dist'),
|
||||
assetsSubDirectory: 'static',
|
||||
assetsPublicPath: './',
|
||||
|
||||
/**
|
||||
* Source Maps
|
||||
*/
|
||||
|
||||
productionSourceMap: true,
|
||||
// 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
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
'use strict'
|
||||
module.exports = {
|
||||
NODE_ENV: '"production"'
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
'use strict'
|
||||
const merge = require('webpack-merge')
|
||||
const devEnv = require('./dev.env')
|
||||
|
||||
module.exports = merge(devEnv, {
|
||||
NODE_ENV: '"testing"'
|
||||
})
|
42
docs/.vuepress/components/Alert.vue
Normal file
@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div class="alert" :style="`top: ${top}px`">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Alert',
|
||||
props: ['show'],
|
||||
data() {
|
||||
return {
|
||||
top: 100
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
console.log(this)
|
||||
// this.$page.alert = this.$page.alert ? this.$page.alert : {top: 100}
|
||||
// this.$page.alert.top += 20
|
||||
// this.top = this.$page.alert.top
|
||||
setTimeout(() => {
|
||||
this.$el.remove()
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.alert{
|
||||
position: absolute;
|
||||
padding: 6px 8px;
|
||||
background-color: #f0f2f5;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
border-radius: 4px;
|
||||
margin: 0 auto;
|
||||
z-index: 999;
|
||||
top: 100px;
|
||||
width: fit-content;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
</style>
|
35
docs/.vuepress/components/Color.vue
Normal file
@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<div :data-clipboard-text="color" class="color" @click="onClick" :style="`background-color:${color}`" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Clipboard from 'clipboard'
|
||||
export default {
|
||||
name: 'Color',
|
||||
props: ['color'],
|
||||
data() {
|
||||
return {
|
||||
alert: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
let clipboard = new Clipboard('.color')
|
||||
clipboard.on('success', () => {
|
||||
this.$alert(`颜色代码已复制:${this.color}`)
|
||||
clipboard.destroy()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.color{
|
||||
border: 1px dashed #a0d911;
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
18
docs/.vuepress/components/ColorList.vue
Normal file
@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<div>
|
||||
<color class="color" :key="index" v-for="(color, index) in colors" :color="color" ></color>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ColorList',
|
||||
props: ['colors']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.color{
|
||||
margin: 0 2px;
|
||||
}
|
||||
</style>
|
58
docs/.vuepress/config.js
Normal file
@ -0,0 +1,58 @@
|
||||
module.exports = {
|
||||
title: 'Vue Antd Admin',
|
||||
description: 'Vue Antd Admin',
|
||||
base: '/',
|
||||
head: [
|
||||
['link', { rel: 'icon', href: '/favicon.ico' }]
|
||||
],
|
||||
themeConfig: {
|
||||
logo: '/logo.png',
|
||||
repo: 'iczer/vue-antd-admin',
|
||||
docsDir: 'docs',
|
||||
editLinks: true,
|
||||
editLinkText: '在 Github 上帮助我们编辑此页',
|
||||
nav: [
|
||||
{text: '指南', link: '/'},
|
||||
{text: '配置', link: '/develop/layout'},
|
||||
{text: '主题', link: '/advance/theme'},
|
||||
],
|
||||
lastUpdated: 'Last Updated',
|
||||
sidebar: [
|
||||
{
|
||||
title: '开始',
|
||||
collapsable: false,
|
||||
children: [
|
||||
'/start/use', '/start/faq'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '开发',
|
||||
collapsable: false,
|
||||
children: [
|
||||
'/develop/layout', '/develop/router', '/develop/page', '/develop/theme', '/develop/service', '/develop/mock'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '进阶',
|
||||
collapsable: false,
|
||||
children: [
|
||||
'/advance/i18n', '/advance/async', '/advance/authority', '/advance/login', '/advance/guard', '/advance/interceptors',
|
||||
'/advance/api'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '其它',
|
||||
collapsable: false,
|
||||
children: [
|
||||
'/other/upgrade', '/other/community'
|
||||
]
|
||||
}
|
||||
],
|
||||
nextLinks: true,
|
||||
prevLinks: true,
|
||||
},
|
||||
plugins: ['@vuepress/back-to-top', require('./plugins/alert')],
|
||||
markdown: {
|
||||
lineNumbers: true
|
||||
}
|
||||
}
|
46
docs/.vuepress/plugins/alert/Alert.vue
Normal file
@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div class="alert" :style="`top: ${top}px`">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Alert',
|
||||
props: ['alert'],
|
||||
data() {
|
||||
return {
|
||||
top: 0
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
this.top = this.alert.top
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('alert_remove', (e) => {
|
||||
this.top -= e.detail.height
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
'page.alert.top': function (value) {
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.alert{
|
||||
position: fixed;
|
||||
padding: 6px 8px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.25);
|
||||
border-radius: 4px;
|
||||
margin: 0 auto;
|
||||
z-index: 999;
|
||||
top: 100px;
|
||||
width: fit-content;
|
||||
left: 0;
|
||||
right: 0;
|
||||
transition: top 0.3s;
|
||||
}
|
||||
</style>
|
38
docs/.vuepress/plugins/alert/alertMixin.js
Normal file
@ -0,0 +1,38 @@
|
||||
import Alert from './Alert'
|
||||
|
||||
const AlertMixin = {
|
||||
install(Vue) {
|
||||
Vue.mixin({
|
||||
methods: {
|
||||
$alert(message, duration = 2000) {
|
||||
let Constructor= Vue.extend(Alert)
|
||||
let alert = new Constructor()
|
||||
alert.$slots.default = message
|
||||
alert.$props.alert = this.$page.alert
|
||||
alert.$mount()
|
||||
document.body.appendChild(alert.$el)
|
||||
|
||||
const appendHeight = alert.$el.offsetHeight + 16
|
||||
this.$page.alert.top += appendHeight
|
||||
|
||||
setTimeout(() => {
|
||||
this.$page.alert.top -= appendHeight
|
||||
this.triggerRemoveAlert(appendHeight)
|
||||
setTimeout(() => {
|
||||
alert.$destroy()
|
||||
alert.$el.remove()
|
||||
}, 100)
|
||||
}, duration)
|
||||
},
|
||||
triggerRemoveAlert(height) {
|
||||
const event = new CustomEvent('alert_remove', {
|
||||
detail: {height}
|
||||
})
|
||||
window.dispatchEvent(event)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default AlertMixin
|
5
docs/.vuepress/plugins/alert/clientRootMixin.js
Normal file
@ -0,0 +1,5 @@
|
||||
export default {
|
||||
updated() {
|
||||
this.$page.alert.top = 100
|
||||
}
|
||||
}
|
5
docs/.vuepress/plugins/alert/enhanceApp.js
Normal file
@ -0,0 +1,5 @@
|
||||
import AlertMixin from './alertMixin'
|
||||
|
||||
export default ({Vue}) => {
|
||||
Vue.use(AlertMixin)
|
||||
}
|
13
docs/.vuepress/plugins/alert/index.js
Normal file
@ -0,0 +1,13 @@
|
||||
const path = require('path')
|
||||
|
||||
module.exports = (options, ctx) => {
|
||||
return {
|
||||
clientRootMixin: path.resolve(__dirname, 'clientRootMixin.js'),
|
||||
extendPageData($page) {
|
||||
$page.alert = {
|
||||
top: 100
|
||||
}
|
||||
},
|
||||
enhanceAppFiles: path.resolve(__dirname, 'enhanceApp.js')
|
||||
}
|
||||
}
|
BIN
docs/.vuepress/public/favicon.ico
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
docs/.vuepress/public/logo.png
Normal file
After Width: | Height: | Size: 26 KiB |
12
docs/.vuepress/styles/index.styl
Normal file
@ -0,0 +1,12 @@
|
||||
.custom-block.tip{
|
||||
border-color: #1890ff
|
||||
}
|
||||
.theme-default-content code .token.inserted{
|
||||
color: #60bd90;
|
||||
}
|
||||
//.custom-block.warning{
|
||||
// border-color: #fa8c16
|
||||
//}
|
||||
//.custom-block.error{
|
||||
// border-color: #f5222d
|
||||
//}
|
2
docs/.vuepress/styles/palette.styl
Normal file
@ -0,0 +1,2 @@
|
||||
$accentColor = #1890ff
|
||||
$contentWidth = 940px
|
17
docs/README.md
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
title: 首页
|
||||
home: true
|
||||
heroImage: /logo.png
|
||||
heroText: Vue Antd Admin
|
||||
tagline: 开箱即用的中台前端/设计解决方案
|
||||
actionText: 快速上手 →
|
||||
actionLink: /start/use
|
||||
features:
|
||||
- title: 简洁
|
||||
details: 以 Markdown 为中心的项目结构,以最少的配置帮助你专注于写作。
|
||||
- title: 优雅
|
||||
details: 享受 Vue + webpack 的开发体验,在 Markdown 中使用 Vue 组件,同时可以使用 Vue 来开发自定义主题。
|
||||
- title: 自然
|
||||
details: VuePress 为每个页面预渲染生成静态的 HTML,同时在页面被加载的时候,将作为 SPA 运行。
|
||||
footer: MIT Licensed | Copyright © 2018-present iczer
|
||||
---
|
5
docs/advance/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
title: 进阶
|
||||
lang: zn-CN
|
||||
---
|
||||
# 进阶
|
42
docs/advance/api.md
Normal file
@ -0,0 +1,42 @@
|
||||
---
|
||||
title: 全局API
|
||||
lang: zn-CN
|
||||
---
|
||||
# 全局API
|
||||
我们提供了一些全局Api,在日常功能开发中或许会有帮助,它们均被绑定到了页面组件或子组件实例上。
|
||||
在组件内可以直接通过`this.$[apiName]`的方式调用。如下:
|
||||
|
||||
## 多页签
|
||||
### $closePage(closeRoute, nextRoute)
|
||||
该api用于关闭当前已打开的页签,接收两个参数:
|
||||
* **closeRoute**
|
||||
要关闭的页签对应的 route 对象,可简写为路由的 fullPath 字符串值。
|
||||
* **nextRoute**
|
||||
关闭页签要后跳转的 route 对象,可不传,不传则会自动选择打开页签(临近原则)。
|
||||
|
||||
### $refreshPage(route)
|
||||
该api用于刷新路由对应的页签,接收一个参数:
|
||||
* **route**
|
||||
要刷新的页签对应的 route 对象,可简写为路由的 fullPath 字符串值。
|
||||
|
||||
### $openPage(route, title)
|
||||
该api用于打开一个新页签,接收两个参数:
|
||||
* **route**
|
||||
要打开的页签对应的 route 对象,可简写为路由的 fullPath 字符串值。
|
||||
* **title**
|
||||
设置打开页签的标题,可不传。
|
||||
|
||||
### $setPageTitle(route, title)
|
||||
该api用于设置页签的标题,接收两个参数:
|
||||
* **route**
|
||||
要设置的页签对应的 route 对象,可简写为路由的 fullPath 字符串值。
|
||||
* **title**
|
||||
页签的标题。
|
||||
|
||||
## 权限
|
||||
### $auth(check, type)
|
||||
该api可以用于操作权限校验,接收两个参数:
|
||||
* **check**
|
||||
需要要校验的操作权限
|
||||
* **type**
|
||||
操作权限校验类别,可选 `permission` 和 `role`,即通过权限校验还是角色进行校验,可不传(不传的话,会对两种类型都进行匹配,任意一种匹配成功即校验通过)。
|
258
docs/advance/async.md
Normal file
@ -0,0 +1,258 @@
|
||||
---
|
||||
title: 异步路由和菜单
|
||||
lang: zn-CN
|
||||
---
|
||||
# 异步路由和菜单
|
||||
在现实业务中,存在这样的场景,系统的路由和菜单会根据用户的角色变化而变化,或者路由菜单根据用户的权限动态生成。我们为此准备了一套完整的异步加载方案,
|
||||
可以让你很方便的从服务端加载路由和菜单配置,并应用到系统中。
|
||||
## 异步加载路由
|
||||
动态路由的实现主要有以下四个步骤:
|
||||
### 开启异步路由设置
|
||||
在 `/config/config.js` 文件中设置 `asyncRoutes` 的值为 true:
|
||||
```js {7}
|
||||
module.exports = {
|
||||
theme: {
|
||||
color: '#13c2c2',
|
||||
mode: 'night'
|
||||
},
|
||||
multiPage: true,
|
||||
asyncRoutes: true, //异步加载路由,true:开启,false:不开启
|
||||
animate: {
|
||||
name: 'roll',
|
||||
direction: 'default'
|
||||
}
|
||||
}
|
||||
```
|
||||
### 注册路由组件
|
||||
基础路由组件包含路由基本配置和对应的视图组件,我们统一在 `/router/async/router.map.js` 文件中注册它们。它和正常的路由配置基本无异,相当于把完整的路由拆分成单个的路由配置进行注册,为后面的路由动态配置打好基础。
|
||||
一个单独的路由组件注册示例如下:
|
||||
```jsx
|
||||
registerName: { //路由组件注册名称,唯一标识
|
||||
path: 'path', //路由path,可缺省,默认取路由注册名称 registerName 的值
|
||||
name: '演示页', //路由名称
|
||||
redirect: '/login', //路由重定向
|
||||
component: () => import('@/pages/demo'), //路由视图
|
||||
icon: 'permission', //路由的菜单icon,会注入到路由元数据meta中
|
||||
invisible: false, //是否隐藏菜单项,true 隐藏,false 不隐藏,会注入到路由元数据meta中。
|
||||
authority: { //路由权限配置,会注入到路由元数据meta中。可缺省,默认为 ‘*’, 即无权限限制
|
||||
permission: 'form', //路由需要的权限
|
||||
role: 'admin' //路由需要的角色。当permission未设置,通过 role 检查权限
|
||||
},
|
||||
page: { //路由的页面数据,会注入到路由元数据meta中
|
||||
title: '演示页', //页面标题
|
||||
breadcrumb: ['首页', '演示页'] //页面面包屑
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::details 点击查看完整的路由注册示例:
|
||||
```js
|
||||
// 视图组件
|
||||
const view = {
|
||||
tabs: () => import('@/layouts/tabs'),
|
||||
blank: () => import('@/layouts/BlankView'),
|
||||
page: () => import('@/layouts/PageView')
|
||||
}
|
||||
|
||||
// 路由组件注册
|
||||
const routerMap = {
|
||||
login: {
|
||||
authority: '*',
|
||||
path: '/login',
|
||||
component: () => import('@/pages/login')
|
||||
},
|
||||
demo: {
|
||||
name: '演示页',
|
||||
renderMenu: false,
|
||||
component: () => import('@/pages/demo')
|
||||
},
|
||||
exp403: {
|
||||
authority: '*',
|
||||
name: 'exp403',
|
||||
path: '403',
|
||||
component: () => import('@/pages/exception/403')
|
||||
},
|
||||
exp404: {
|
||||
name: 'exp404',
|
||||
path: '404',
|
||||
component: () => import('@/pages/exception/404')
|
||||
},
|
||||
exp500: {
|
||||
name: 'exp500',
|
||||
path: '500',
|
||||
component: () => import('@/pages/exception/500')
|
||||
},
|
||||
root: {
|
||||
path: '/',
|
||||
name: '首页',
|
||||
redirect: '/login',
|
||||
component: view.tabs
|
||||
},
|
||||
parent1: {
|
||||
name: '父级路由1',
|
||||
icon: 'dashboard',
|
||||
component: view.blank
|
||||
},
|
||||
parent2: {
|
||||
name: '父级路由2',
|
||||
icon: 'form',
|
||||
component: view.page
|
||||
},
|
||||
exception: {
|
||||
name: '异常页',
|
||||
icon: 'warning',
|
||||
component: view.blank
|
||||
}
|
||||
}
|
||||
export default routerMap
|
||||
```
|
||||
:::
|
||||
### 配置基本路由
|
||||
如果没有任何路由,你的应用是无法访问的,所以我们需要在本地配置一些基本的路由,比如登录页、404、403 等。你可以在 `/router/async/config.async.js` 文件中配置一些本地必要的路由。如下:
|
||||
```js
|
||||
const routesConfig = [
|
||||
'login', //匹配 router.map.js 中注册的 registerName = login 的路由
|
||||
'root', //匹配 router.map.js 中注册的 registerName = root 的路由
|
||||
{
|
||||
router: 'exp404', //匹配 router.map.js 中注册的 registerName = exp404 的路由
|
||||
path: '*', //重写 exp404 路由的 path 属性
|
||||
name: '404' //重写 exp404 路由的 name 属性
|
||||
},
|
||||
{
|
||||
router: 'exp403', //匹配 router.map.js 中注册的 registerName = exp403 的路由
|
||||
path: '/403', //重写 exp403 路由的 path 属性
|
||||
name: '403' //重写 exp403 路由的 name 属性
|
||||
}
|
||||
]
|
||||
```
|
||||
完成配置后,即可通过 `routesConfig` 和已注册的 `routerMap` 生成 [router.options.routes](https://router.vuejs.org/zh/api/#router-%E6%9E%84%E5%BB%BA%E9%80%89%E9%A1%B9) 配置,如下:
|
||||
```js
|
||||
const options = {
|
||||
routes: parseRoutes(routesConfig, routerMap)
|
||||
}
|
||||
```
|
||||
:::details 点击查看完整的 config.async.js 代码
|
||||
```js
|
||||
import routerMap from './router.map'
|
||||
import {parseRoutes} from '@/utils/routerUtil'
|
||||
|
||||
// 异步路由配置
|
||||
const routesConfig = [
|
||||
'login',
|
||||
'root',
|
||||
{
|
||||
router: 'exp404',
|
||||
path: '*',
|
||||
name: '404'
|
||||
},
|
||||
{
|
||||
router: 'exp403',
|
||||
path: '/403',
|
||||
name: '403'
|
||||
}
|
||||
]
|
||||
const options = {
|
||||
routes: parseRoutes(routesConfig, routerMap)
|
||||
}
|
||||
export default options
|
||||
```
|
||||
:::
|
||||
完成以上设置后,本地就已经有了包含 login、404、403 页面的路由,并且这些路由是可以直接访问的。
|
||||
### 异步获取路由配置
|
||||
当用户登录后(或者其它的前提条件),你可能想根据不同用户加载不同的路由和菜单。
|
||||
那么我们就需要先从后端服务获取异步路由配置,后端返回的异步路由配置 `routesConfig` 是一个异步路由配置数组, 应当如下格式:
|
||||
```jsx
|
||||
[{
|
||||
router: 'root', //匹配 router.map.js 中注册名 registerName = root 的路由
|
||||
children: [ //root 路由的子路由配置
|
||||
{
|
||||
router: 'dashboard', //匹配 router.map.js 中注册名 registerName = dashboard 的路由
|
||||
children: ['workplace', 'analysis'], //dashboard 路由的子路由配置,依次匹配 registerName 为 workplace 和 analysis 的路由
|
||||
},
|
||||
{
|
||||
router: 'form', //匹配 router.map.js 中注册名 registerName = form 的路由
|
||||
children: [ //form 路由的子路由配置
|
||||
'basicForm', //匹配 router.map.js 中注册名 registerName = basicForm 的路由
|
||||
'stepForm', //匹配 router.map.js 中注册名 registerName = stepForm 的路由
|
||||
{
|
||||
router: 'advanceForm', //匹配 router.map.js 中注册名 registerName = advanceForm 的路由
|
||||
path: 'advance' //重写 advanceForm 路由的 path 属性
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
router: 'basicForm', //匹配 router.map.js 中注册名 registerName = basicForm 的路由
|
||||
name: '验权表单', //重写 basicForm 路由的 name 属性
|
||||
icon: 'file-excel', //重写 basicForm 路由的 icon 属性
|
||||
authority: 'form' //重写 basicForm 路由的 authority 属性
|
||||
}
|
||||
]
|
||||
}]
|
||||
```
|
||||
其中 `router` 属性 对应 `router.map.js` 中已注册的`基础路由`的注册名称 `registerName`,`children` 属性为路由的嵌套子路由配置。
|
||||
有些情况下你可能想重写已注册路由的属性,你可以为 `routesConfig` 配置同名属性去覆盖它。如上面的`验权表单`路由覆盖了注册路由的 `name`、`icon`、`authority` 属性。
|
||||
|
||||
### 加载路由并应用
|
||||
我们提供了一个路由加载工具,你只需调用 `/utils/routerUtil.js` 中的 `loadRoutes` 方法加载上一步获取到的 `routesConfig` 即可,如下:
|
||||
```js {3}
|
||||
getRoutesConfig().then(result => {
|
||||
const routesConfig = result.data.data
|
||||
loadRoutes(routesConfig)
|
||||
})
|
||||
```
|
||||
至此,异步路由的加载就完成了,你可以访问异步加载的路由了。
|
||||
:::tip
|
||||
上面获取异步路由的代码,在 /pages/login/Login.vue 文件中可以找到。
|
||||
loadRoutes 方法会合并 /router/async/config.async.js 文件中配置的基本路由。
|
||||
:::
|
||||
:::details 点击查看 loadRoutes 的详细代码
|
||||
```js
|
||||
/**
|
||||
* 加载路由
|
||||
* @param routesConfig 路由配置
|
||||
*/
|
||||
function loadRoutes(routesConfig) {
|
||||
// 如果 routesConfig 有值,则更新到本地,否则从本地获取
|
||||
if (routesConfig) {
|
||||
store.commit('account/setRoutesConfig', routesConfig)
|
||||
} else {
|
||||
routesConfig = store.getters['account/routesConfig']
|
||||
}
|
||||
// 如果开启了异步路由,则加载异步路由配置
|
||||
const asyncRoutes = store.state.setting.asyncRoutes
|
||||
if (asyncRoutes) {
|
||||
if (routesConfig && routesConfig.length > 0) {
|
||||
const routes = parseRoutes(routesConfig, routerMap)
|
||||
formatAuthority(routes)
|
||||
const finalRoutes = mergeRoutes(router.options.routes, routes)
|
||||
router.options = {...router.options, routes: finalRoutes}
|
||||
router.matcher = new Router({...router.options, routes:[]}).matcher
|
||||
router.addRoutes(finalRoutes)
|
||||
}
|
||||
}
|
||||
// 初始化Admin后台菜单数据
|
||||
const rootRoute = router.options.routes.find(item => item.path === '/')
|
||||
const menuRoutes = rootRoute && rootRoute.children
|
||||
if (menuRoutes) {
|
||||
mergeI18nFromRoutes(i18n, menuRoutes)
|
||||
store.commit('setting/setMenuData', menuRoutes)
|
||||
}
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
## 异步加载菜单
|
||||
Vue Antd Admin 的菜单,是根据路由配置自动生成的,默认获取根路由 `‘/’` 下所有子路由作为菜单配置。
|
||||
当你完成了异步路由的加载,菜单也会随之改变,无需你做其它额外的操作。主要代码如下:
|
||||
```js
|
||||
// 初始化Admin后台菜单数据
|
||||
const rootRoute = router.options.routes.find(item => item.path === '/')
|
||||
const menuRoutes = rootRoute && rootRoute.children
|
||||
if (menuRoutes) {
|
||||
mergeI18nFromRoutes(i18n, menuRoutes)
|
||||
store.commit('setting/setMenuData', menuRoutes)
|
||||
}
|
||||
```
|
||||
:::tip
|
||||
如果你不想从根路由 `‘/’` 下获取菜单数据,可以根据自己的需求更改。
|
||||
:::
|
230
docs/advance/authority.md
Normal file
@ -0,0 +1,230 @@
|
||||
---
|
||||
title: 权限管理
|
||||
lang: zn-CN
|
||||
---
|
||||
# 权限管理
|
||||
权限控制是中后台系统中常见的需求之一,你可以利用 Vue Antd Admin 提供的权限控制脚手架,实现一些基本的权限控制功能。
|
||||
## 角色和权限
|
||||
通常情况下有两种方式可以控制用户权限,一种是通过用户角色 role 来控制权限,另一种是通过更细致的权限 permission 来控制。
|
||||
这两种方式 Vue Antd Admin 都支持。
|
||||
我们定义了 role 和 permission 的基本格式,如果你获取的 role 和 permission 数据格式与 Vue Antd Admin 不一致,
|
||||
你需要在获取到 role 和 permission 后将其转换为 Vue Antd Admin 的格式。
|
||||
### 角色
|
||||
Vue Antd Admin 的 `角色/role` 包含 `id` 和 `operation` 两个属性。其中 `id` 为 `角色/role` 的 id,`operation` 为 `角色/role` 具有的操作权限,是一个字符串数组。
|
||||
```js
|
||||
role = {
|
||||
id: 'admin', //角色ID
|
||||
operation: ['add', 'delete', 'edit', 'close'] //角色的操作权限
|
||||
}
|
||||
```
|
||||
你也可以设置 role 的值为字符串,比如 role = 'admin', 它等同于:
|
||||
```js
|
||||
role = {
|
||||
id: 'admin'
|
||||
}
|
||||
```
|
||||
### 权限
|
||||
Vue Antd Admin 的 `权限/permission` 也包含 `id` 和 `operation` 两个属性。其中 `id` 为 `权限/permission` 的 id,`operation` 为 `权限/permission` 下的操作权限,是一个字符串数组。
|
||||
```js
|
||||
permission = {
|
||||
id: 'form', //权限ID
|
||||
operation: ['add', 'delete', 'edit', 'close'] //权限下的操作权限
|
||||
}
|
||||
```
|
||||
你也可以设置 permission 的值为字符串,比如 permission = 'form', 它等同于:
|
||||
```js
|
||||
permission = {
|
||||
id: 'form'
|
||||
}
|
||||
```
|
||||
### 设置用户的角色和权限
|
||||
你只需为用户配置 roles 和 permissions 两者中的其中一种,即可完成权限管理功能。当然你也可以两者都配置。
|
||||
|
||||
获取到用户权限或角色后,将其格式化转为 Vue Antd Admin 可用的格式,然后使用 `store.commit('account/setPermissions', permissions)` 或 `store.commit('account/setRoles', roles)`
|
||||
将其存在本地即可。如下:
|
||||
```js
|
||||
getPermissions().then(res => {
|
||||
const permissions = res.data
|
||||
this.$store.commit('account/setPermissions', permissions)
|
||||
})
|
||||
getRoles().then(res => {
|
||||
const roles = res.data
|
||||
this.$store.commit('account/setRoles', roles)
|
||||
})
|
||||
```
|
||||
:::tip
|
||||
注意,存在本地的 permissions 和 roles 都应该是数组。
|
||||
你可以在 /pages/login/Login.vue 查看完整的用户角色和权限设置代码。
|
||||
:::
|
||||
## 页面权限
|
||||
如果你想给一些页面设置准入权限,只需要给该页面对应的路由设置元数据 authority 即可。 authority 的值可以是一个字符串,也可以是对象。
|
||||
|
||||
如下路由配置,则表明 `验权页面` 需要准入权限(permission): `form`
|
||||
```js {5}
|
||||
const route = {
|
||||
name: '验权页面',
|
||||
path: 'auth/demo',
|
||||
meta: {
|
||||
authority: 'form',
|
||||
},
|
||||
component: () => import('@/pages/demo')
|
||||
}
|
||||
```
|
||||
下面是 authority 的值为对象的写法,这种写法和上面字符串的写法具有相同的效果:
|
||||
```js {5-7}
|
||||
const route = {
|
||||
name: '验权页面',
|
||||
path: 'auth/demo',
|
||||
meta: {
|
||||
authority: {
|
||||
permission: 'form'
|
||||
}
|
||||
},
|
||||
component: () => import('@/pages/demo')
|
||||
}
|
||||
```
|
||||
有时你可能需要通过用户角色来配置页面权限,我们同样支持,用法和上面类似。
|
||||
|
||||
如下配置,表明 `验权页面` 需要准入角色(role) `admin`:
|
||||
```js {5-7}
|
||||
const route = {
|
||||
name: '验权页面',
|
||||
path: 'auth/demo',
|
||||
meta: {
|
||||
authority: {
|
||||
role: 'admin'
|
||||
}
|
||||
},
|
||||
component: () => import('@/pages/demo')
|
||||
}
|
||||
```
|
||||
:::tip
|
||||
当你未设置 authority 或 设置 authority 的值 为 `*` 时,等同于该页面无需权限限制,我们会忽略此页面的权限检查。
|
||||
:::
|
||||
:::tip
|
||||
当 authority 的值为字符串时,会以 [权限/permission](#权限) 验证权限。如果你需要以 [角色/role](#角色) 验证权限,请以对象形式设置 authority 的值。
|
||||
:::
|
||||
## 操作权限
|
||||
在一些复杂的些场景下,权限可能不仅仅是页面层级这么简单。在一些页面你可能需要校验用户是否具有某些操作的权限,比如 增、删、改、查等。
|
||||
为此,我们提供了 `权限校验注入` 和 `权限校验指令` 两个实用的功能。
|
||||
### 权限校验注入
|
||||
通过对Vue组件的实例方法进行 `权限校验注入`,我们可以控制该实例方法的执行权限,从而精准且安全的验证用户操作。
|
||||
|
||||
比如,QueryList 页面的 deleteRecord 方法,我们希望具有操作权限 `delete` 的用户才能调用此方法。
|
||||
只需为 `deleteRecord` 方法注入权限校验,按如下方式配置 `authorize` 即可:
|
||||
```vue {9-11,13}
|
||||
<template>
|
||||
...
|
||||
</template>
|
||||
<script>
|
||||
...
|
||||
export default {
|
||||
name: 'QueryList',
|
||||
data () {...},
|
||||
authorize: { //权限校验注入设置
|
||||
deleteRecord: 'delete' //key为需要注入权限校验的方法名,这里为 deleteRecord 方法;值为需要校验的操作权限,这里为 delete 操作权限
|
||||
},
|
||||
methods: {
|
||||
deleteRecord(key) {
|
||||
this.dataSource = this.dataSource.filter(item => item.key !== key)
|
||||
this.selectedRows = this.selectedRows.filter(item => item.key !== key)
|
||||
},
|
||||
...
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
如果用户没有 `delete` 权限,调用 deleteRecord 方法,会看到如下提示:
|
||||
|
||||

|
||||
### 操作权限校验的类型
|
||||
`authorize` 会根据当前页面匹配到的权限类型([permission](#权限) / [role](#角色)),来判断是使用 `permission.operation` 还是 `role.operation` 来进行权限校验。
|
||||
如果当前页面同时匹配到了 permission 和 role 权限,则默认通过 permission.operation 来进行操作权限校验。
|
||||
|
||||
当然你也可以指定操作权限校验的类型,如下设置即可:
|
||||
```js {2-5}
|
||||
authorize: {
|
||||
deleteRecord: { //需要 注入权限校验 的方法名:deleteRecord
|
||||
check: 'delete', //需要校验的操作权限:check
|
||||
type: 'role' //指定操作权限校验的类型,可选 permission 和 role。这里指定以 role.operation 校验操作权限
|
||||
}
|
||||
}
|
||||
```
|
||||
### 权限校验指令
|
||||
有时我们可能希望用户能够更直观的了解自己的操作权限。比如给没有操作权限的控件应用 disable 样式,禁用 click 事件等。
|
||||
我们提供了权限校验指令 `v-auth` 来实现这个功能。
|
||||
|
||||
比如,我们想为 QueryList 页面的删除控件进行 `delete` 操作权限校验,只需为删除控件设置 v-auth="\`delete\`" 指令即可,如下:
|
||||
```vue {6}
|
||||
<template>
|
||||
<a-card>...
|
||||
<standard-table ...>
|
||||
...
|
||||
<div slot="action" slot-scope="{text, record}">
|
||||
<a @click="deleteRecord(record.key)" v-auth="`delete`">
|
||||
<a-icon type="delete" />删除
|
||||
</a>
|
||||
</div>
|
||||
...
|
||||
</standard-table>
|
||||
</a-card>
|
||||
</template>
|
||||
```
|
||||
假如用户没有 `delete` 操作权限,则控件会被应用 disable 样式,且 click 事件无效,如下图:
|
||||
|
||||

|
||||
:::warning 重要!!!
|
||||
v-auth 是我们自定义的一个 [Vue指令](https://cn.vuejs.org/v2/guide/custom-directive.html#ad)。因为 `Vue指令` 的值需要是一个 javascript 表达式,因此你不能直接给 v-auth 赋值为字符串,
|
||||
需要把 v-auth 的字符串值用 ` `` ` 包裹起来,否则可能会报 undefined 错误。
|
||||
:::
|
||||
### 权限校验指令的类型
|
||||
你同样也可以指定 v-auth 的权限校验类型,可选 [permission](#权限) 和 [role](#角色)。它的校验方式和 [authorize](#权限校验注入) 类似,如未指定则会自动识别。
|
||||
`v-auth:role` 表示通过 `role.operation` 进行校验,`v-auth:permission` 表示通过 `permission.operation` 进行校验。
|
||||
|
||||
如下,指定通过 `role.operation` 校验删除控件的操作权限:
|
||||
```vue {3}
|
||||
<div slot="action" slot-scope="{text, record}">
|
||||
...
|
||||
<a v-auth:role="`delete`">
|
||||
<a-icon type="delete" />删除
|
||||
</a>
|
||||
...
|
||||
</div>
|
||||
```
|
||||
## 异步路由权限
|
||||
异步路由同样可以进行权限校验配置,它和正常的路由权限配置基本无异,只是无需把 [authority](#页面权限) 配置在元数据属性 meta 里。
|
||||
你可以在路由组件注册时设置 authority,也可以在异步路由配置里设置 authority。
|
||||
|
||||
路由组件注册时设置 [authority](#页面权限):
|
||||
```js {6}
|
||||
// 路由组件注册
|
||||
const routerMap = {
|
||||
...
|
||||
demo: {
|
||||
name: '演示页',
|
||||
authority: 'form',
|
||||
component: () => import('@/pages/demo')
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
异步路由配置里设置 [authority](#页面权限):
|
||||
```js {11-13}
|
||||
const routesConfig = [{
|
||||
router: 'root',
|
||||
children: ['demo',
|
||||
{router: 'parent1'...},
|
||||
...
|
||||
{
|
||||
router: 'demo',
|
||||
icon: 'file-ppt',
|
||||
path: 'auth/demo',
|
||||
name: '验权页面',
|
||||
authority: {
|
||||
permission: 'form',
|
||||
}
|
||||
}
|
||||
]
|
||||
}]
|
||||
```
|
7
docs/advance/chart.md
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
title: 图表
|
||||
lang: zn-CN
|
||||
---
|
||||
# 图表
|
||||
|
||||
### 作者还没来得及编辑该页面,如果你感兴趣,可以点击下方链接,帮助作者完善此页
|
7
docs/advance/error.md
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
title: 错误处理
|
||||
lang: zn-CN
|
||||
---
|
||||
# 错误处理
|
||||
|
||||
### 作者还没来得及编辑该页面,如果你感兴趣,可以点击下方链接,帮助作者完善此页
|
109
docs/advance/guard.md
Normal file
@ -0,0 +1,109 @@
|
||||
---
|
||||
title: 路由守卫
|
||||
lang: zn-CN
|
||||
---
|
||||
# 路由守卫
|
||||
Vue Antd Admin 使用 vue-router 实现路由导航功能,因此可以为路由配置一些守卫。
|
||||
我们统一把导航守卫配置在 router/guards.js 文件中。
|
||||
|
||||
## 前置守卫
|
||||
Vue Antd Admin 为每个前置导航守卫函数注入 to,from,next,options 四个参数:
|
||||
* `to: Route`: 即将要进入的目标[路由对象](https://router.vuejs.org/zh/api/#%E8%B7%AF%E7%94%B1%E5%AF%B9%E8%B1%A1)
|
||||
* `from: Route`: 当前导航正要离开的路由对象
|
||||
* `next: Function`: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。详情查看 [Vue Router #导航守卫](https://router.vuejs.org/zh/guide/advanced/navigation-guards.html)
|
||||
* `options: Object`: 应用配置,包含: {router, i18n, store, message},可根据需要扩展。
|
||||
如下,是登录拦截导航守卫的定义
|
||||
```js
|
||||
const loginGuard = (to, from, next, options) => {
|
||||
const {message} = options
|
||||
if (!loginIgnore.includes(to) && !checkAuthorization()) {
|
||||
message.warning('登录已失效,请重新登录')
|
||||
next({path: '/login'})
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 后置守卫
|
||||
你也可以定义后置导航守卫,Vue Antd Admin 为每个后置导航函数注入 to,from,options 三个参数:
|
||||
* `to: Route`: 即将要进入的目标[路由对象](https://router.vuejs.org/zh/api/#%E8%B7%AF%E7%94%B1%E5%AF%B9%E8%B1%A1)
|
||||
* `from: Route`: 当前导航正要离开的路由对象
|
||||
* `options: Object`: 应用配置,包含: {router, i18n, store, message},可根据需要扩展。
|
||||
如下,是一个后置导航守卫的定义
|
||||
```js
|
||||
const afterGuard = (to, from, options) => {
|
||||
const {store, message} = options
|
||||
// 做些什么
|
||||
message.info('do something')
|
||||
}
|
||||
```
|
||||
|
||||
## 导出守卫配置
|
||||
定义好导航守卫后,只需按照类别在 guard.js 中导出即可。分为两类,`前置守卫`和`后置守卫`。如下:
|
||||
```js
|
||||
export default {
|
||||
beforeEach: [loginGuard, authorityGuard],
|
||||
afterEach: [afterGuard]
|
||||
}
|
||||
```
|
||||
|
||||
:::details 点击查看完整的导航守卫配置
|
||||
```js
|
||||
import {loginIgnore} from '@/router/index'
|
||||
import {checkAuthorization} from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 登录守卫
|
||||
* @param to
|
||||
* @param form
|
||||
* @param next
|
||||
* @param options
|
||||
*/
|
||||
const loginGuard = (to, from, next, options) => {
|
||||
const {message} = options
|
||||
if (!loginIgnore.includes(to) && !checkAuthorization()) {
|
||||
message.warning('登录已失效,请重新登录')
|
||||
next({path: '/login'})
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限守卫
|
||||
* @param to
|
||||
* @param form
|
||||
* @param next
|
||||
* @param options
|
||||
*/
|
||||
const authorityGuard = (to, from, next, options) => {
|
||||
const {store, message} = options
|
||||
const permissions = store.getters['account/permissions']
|
||||
const roles = store.getters['account/roles']
|
||||
if (!hasAuthority(to, permissions, roles)) {
|
||||
message.warning(`对不起,您无权访问页面: ${to.fullPath},请联系管理员`)
|
||||
next({path: '/403'})
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 后置守卫
|
||||
* @param to
|
||||
* @param form
|
||||
* @param options
|
||||
*/
|
||||
const afterGuard = (to, from, options) => {
|
||||
const {store, message} = options
|
||||
// 做些什么
|
||||
message.info('do something')
|
||||
}
|
||||
|
||||
export default {
|
||||
beforeEach: [loginGuard, authorityGuard],
|
||||
afterEach: [afterGuard]
|
||||
}
|
||||
```
|
||||
:::
|
129
docs/advance/i18n.md
Normal file
@ -0,0 +1,129 @@
|
||||
---
|
||||
title: 国际化
|
||||
lang: zn-CN
|
||||
---
|
||||
# 国际化
|
||||
vue-antd-admin 采用 [vue-i18n](https://kazupon.github.io/vue-i18n/) 插件来实现国际化,该项目已经内置并且加载好了基础配置。可以直接上手使用。
|
||||
|
||||
> 如果你还没有看快速入门,请先移步查看: [页面 -> i18n国际化配置](../develop/page.html#i18n国际化配置)
|
||||
|
||||
|
||||
## 菜单和路由
|
||||
|
||||
### 默认情况
|
||||
如果你没有对菜单进行国际化配置,admin 默认会从路由数据中提取数据作为国际化配置。route.name 作为中文语言,route.path 作为英文语言。
|
||||
国际化提取函数定义在 `@/utils/i18n.js` 文件中,会在路由加载时调用,如下:
|
||||
```js
|
||||
/**
|
||||
* 从路由提取国际化数据
|
||||
* @param i18n
|
||||
* @param routes
|
||||
*/
|
||||
function mergeI18nFromRoutes(i18n, routes) {
|
||||
formatFullPath(routes)
|
||||
const CN = generateI18n(new Object(), routes, 'name')
|
||||
const US = generateI18n(new Object(), routes, 'path')
|
||||
i18n.mergeLocaleMessage('CN', CN)
|
||||
i18n.mergeLocaleMessage('US', US)
|
||||
const messages = routesI18n.messages
|
||||
Object.keys(messages).forEach(lang => {
|
||||
i18n.mergeLocaleMessage(lang, messages[lang])
|
||||
})
|
||||
}
|
||||
```
|
||||
### 自定义
|
||||
如果你想自定义菜单国际化数据,可在 `@/router/i18n.js` 文件中配置。我们以路由的 path 作为 key(嵌套path 的写法也会被解析),name 作为 国际化语言的值。
|
||||
假设你有一个路由的配置如下:
|
||||
```js
|
||||
[{
|
||||
path: 'parent',
|
||||
...
|
||||
children: [{
|
||||
path: 'self',
|
||||
...
|
||||
}]
|
||||
}]
|
||||
|
||||
or
|
||||
|
||||
[{
|
||||
path: 'other',
|
||||
...
|
||||
children: [{
|
||||
path: '/parent/self', // 在国际化配置中 key 会解析为 parent.self
|
||||
...
|
||||
}]
|
||||
}]
|
||||
```
|
||||
那么你需要在 `@/router/i18n.js` 中这样配置:
|
||||
```jsx
|
||||
messages: {
|
||||
CN: {
|
||||
parent: {
|
||||
name: '父級菜單',
|
||||
self: {name: '菜單名'},
|
||||
},
|
||||
US: {
|
||||
parent: {
|
||||
name: 'parent menu',
|
||||
self: {name: 'menu name'},
|
||||
},
|
||||
HK: {
|
||||
parent: {
|
||||
name: '父級菜單',
|
||||
self: {name: '菜單名'},
|
||||
},
|
||||
```
|
||||
|
||||
## 添加语言
|
||||
|
||||
首先在 `@/layouts/header/AdminHeader.vue` ,新增一门语言 (多个同理)。
|
||||
|
||||
```vue {15}
|
||||
<template>
|
||||
...
|
||||
</template>
|
||||
<script>
|
||||
...
|
||||
export default {
|
||||
...
|
||||
data() {
|
||||
return {
|
||||
langList: [
|
||||
{key: 'CN', name: '简体中文', alias: '简体'},
|
||||
{key: 'HK', name: '繁體中文', alias: '繁體'},
|
||||
{key: 'US', name: 'English', alias: 'English'},
|
||||
// 新增一个语言选项, key是i18n的索引,name是菜单显示名称
|
||||
{key: 'JP', name: 'Japanese', alias: 'Japanese'}
|
||||
],
|
||||
searchActive: false
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
> TIP: 后续开发建议把这里改成动态配置的方式!
|
||||
|
||||
然后开始往 `@/router/i18n.js` 和 `@/pages/你的页面/i18n.js` 里面分别添加上语言的翻译。
|
||||
|
||||
```vue {12,13,14}
|
||||
module.exports = {
|
||||
messages: {
|
||||
CN: {
|
||||
home: {name: '首页'},
|
||||
},
|
||||
US: {
|
||||
home: {name: 'home'},
|
||||
},
|
||||
HK: {
|
||||
home: {name: '首頁'},
|
||||
},
|
||||
JP: {
|
||||
home: {name: '最初のページ'},
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> Notice: 更多用法请移步到 [vue-i18n](https://kazupon.github.io/vue-i18n/) 。
|
131
docs/advance/interceptors.md
Normal file
@ -0,0 +1,131 @@
|
||||
---
|
||||
title: 拦截器配置
|
||||
lang: zn-CN
|
||||
---
|
||||
# 拦截器配置
|
||||
Vue Antd Admin 基于 aixos 封装了 http 通信功能,我们可以为 http 请求响应配置一些拦截器。拦截器统一配置在 /utils/axios-interceptors.js 文件中。
|
||||
## 请求拦截器
|
||||
你可以为每个请求拦截器配置 `onFulfilled` 或 `onRejected` 两个钩子函数。
|
||||
### onFulfilled
|
||||
我们会为 onFulfilled 钩子函数注入 config 和 options 两个参数:
|
||||
* `config: AxiosRequestConfig`: axios 请求配置,详情参考 [axios 请求配置](http://www.axios-js.com/zh-cn/docs/#%E8%AF%B7%E6%B1%82%E9%85%8D%E7%BD%AE)
|
||||
* `options: Object`: 应用配置,包含: {router, i18n, store, message},可根据需要扩展。
|
||||
|
||||
### onRejected
|
||||
我们会为 onFulfilled 钩子函数注入 error 和 options 两个参数:
|
||||
* `error: Error`: axios 请求错误对象
|
||||
* `options: Object`: 应用配置,包含: {router, i18n, store, message},可根据需要扩展。
|
||||
|
||||
如下,为一个完整的请求拦截器配置:
|
||||
```js
|
||||
const tokenCheck = {
|
||||
// 发送请求之前做些什么
|
||||
onFulfilled(config, options) {
|
||||
const {message} = options
|
||||
const {url, xsrfCookieName} = config
|
||||
if (url.indexOf('login') === -1 && xsrfCookieName && !Cookie.get(xsrfCookieName)) {
|
||||
message.warning('认证 token 已过期,请重新登录')
|
||||
}
|
||||
return config
|
||||
},
|
||||
// 请求出错时做点什么
|
||||
onRejected(error, options) {
|
||||
const {message} = options
|
||||
message.error(error.message)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
}
|
||||
```
|
||||
## 响应拦截器
|
||||
响应拦截器也同样可以配置 `onFulfilled` 或 `onRejected` 两个钩子函数。
|
||||
### onFulfilled
|
||||
我们会为 onFulfilled 钩子函数注入 response 和 options 两个参数:
|
||||
* `response: AxiosResponse`: axios 响应对象,详情参考 [axios 响应对象](http://www.axios-js.com/zh-cn/docs/#%E5%93%8D%E5%BA%94%E7%BB%93%E6%9E%84)
|
||||
* `options: Object`: 应用配置,包含: {router, i18n, store, message},可根据需要扩展。
|
||||
|
||||
### onRejected
|
||||
我们会为 onRejected 钩子函数注入 error 和 options 两个参数:
|
||||
* `error: Error`: axios 请求错误对象
|
||||
* `options: Object`: 应用配置,包含: {router, i18n, store, message},可根据需要扩展。
|
||||
|
||||
如下,为一个完整的响应拦截器配置:
|
||||
```js
|
||||
const resp401 = {
|
||||
// 响应数据之前做点什么
|
||||
onFulfilled(response, options) {
|
||||
const {message} = options
|
||||
if (response.status === 401) {
|
||||
message.error('无此接口权限')
|
||||
}
|
||||
return response
|
||||
},
|
||||
// 响应出错时做点什么
|
||||
onRejected(error, options) {
|
||||
const {message} = options
|
||||
if (response.status === 401) {
|
||||
message.error('无此接口权限')
|
||||
}
|
||||
return Promise.reject(error)
|
||||
}
|
||||
}
|
||||
```
|
||||
## 导出拦截器
|
||||
定义好拦截器后,只需在 axios-interceptors.js 文件中导出即可。分为两类,`请求拦截器`和`响应拦截器`。如下:
|
||||
```js
|
||||
export default {
|
||||
request: [tokenCheck], // 请求拦截
|
||||
response: [resp401] // 响应拦截
|
||||
}
|
||||
```
|
||||
|
||||
:::details 点击查看完整的拦截器配置示例
|
||||
```js
|
||||
import Cookie from 'js-cookie'
|
||||
// 401拦截
|
||||
const resp401 = {
|
||||
onFulfilled(response, options) {
|
||||
const {message} = options
|
||||
if (response.status === 401) {
|
||||
message.error('无此接口权限')
|
||||
}
|
||||
return response
|
||||
},
|
||||
onRejected(error, options) {
|
||||
const {message} = options
|
||||
message.error(error.message)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
}
|
||||
|
||||
const resp403 = {
|
||||
onFulfilled(response, options) {
|
||||
const {message} = options
|
||||
if (response.status === 403) {
|
||||
message.error(`请求被拒绝`)
|
||||
}
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
const reqCommon = {
|
||||
onFulfilled(config, options) {
|
||||
const {message} = options
|
||||
const {url, xsrfCookieName} = config
|
||||
if (url.indexOf('login') === -1 && xsrfCookieName && !Cookie.get(xsrfCookieName)) {
|
||||
message.warning('认证 token 已过期,请重新登录')
|
||||
}
|
||||
return config
|
||||
},
|
||||
onRejected(error, options) {
|
||||
const {message} = options
|
||||
message.error(error.message)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
request: [reqCommon], // 请求拦截
|
||||
response: [resp401, resp403] // 响应拦截
|
||||
}
|
||||
```
|
||||
:::
|
76
docs/advance/login.md
Normal file
@ -0,0 +1,76 @@
|
||||
---
|
||||
title: 登录认证
|
||||
lang: zn-CN
|
||||
---
|
||||
# 登录认证
|
||||
Vue Antd Admin 使用 js-cookie.js 管理用户的 token,结合 axios 配置,可以为每个请求头加上 token 信息。
|
||||
|
||||
## token名称
|
||||
后端系统通常会从请求 header 中获取用户的 token,因此我们需要配置好 token 名称,好让后端能正确的识别到用户 token。
|
||||
Vue Antd Admin 默认token 名称为 `Authorization`,你可以在 /utils/request.js 中修改它。
|
||||
```js{5}
|
||||
import axios from 'axios'
|
||||
import Cookie from 'js-cookie'
|
||||
|
||||
// 跨域认证信息 header 名
|
||||
const xsrfHeaderName = 'Authorization'
|
||||
...
|
||||
```
|
||||
## token 设置
|
||||
调用登录接口后拿到用户的 token 和 token 过期时间(如无过期时间,可忽略),并使用 /utils/request.js #setAuthorization 方法保存token。
|
||||
```js{5}
|
||||
import {setAuthorization} from '@/utils/request'
|
||||
|
||||
login(name, password).then(res => {
|
||||
const {token, expireAt} = res.data
|
||||
setAuthorization({token, expireAt: new Date(expireAt)})
|
||||
})
|
||||
```
|
||||
## token 校验
|
||||
Vue Antd Admin 默认添加了登录导航守卫,如检查到本地cookie 中不包含 token 信息,则会拦截跳转至登录页。你可以在 /router/index.js 中配置
|
||||
不需要登录拦截的路由
|
||||
```js
|
||||
// 不需要登录拦截的路由配置
|
||||
const loginIgnore = {
|
||||
names: ['404', '403'], //根据路由名称匹配
|
||||
paths: ['/login'], //根据路由fullPath匹配
|
||||
/**
|
||||
* 判断路由是否包含在该配置中
|
||||
* @param route vue-router 的 route 对象
|
||||
* @returns {boolean}
|
||||
*/
|
||||
includes(route) {
|
||||
return this.names.includes(route.name) || this.paths.includes(route.path)
|
||||
}
|
||||
}
|
||||
```
|
||||
或者在 /router/guards.js 中移出登录守卫
|
||||
```diff
|
||||
...
|
||||
export default {
|
||||
- beforeEach: [loginGuard, authorityGuard, redirectGuard],
|
||||
+ beforeEach: [authorityGuard, redirectGuard],
|
||||
afterEach: []
|
||||
}
|
||||
```
|
||||
## Api
|
||||
### setAuthorization(auth, authType)
|
||||
来源:/utils/request.js
|
||||
该方法用于保存用户 token,接收两个参数:
|
||||
* **auth**
|
||||
认证信息,包含 token、expireAt 等认证数据。
|
||||
* **authType**
|
||||
认证类型,默认为 `AUTH_TYPE.BEARER`(AUTH_TYPE.BEARER 默认会给token 加上 Bearer 识别前缀),可根据自己的认证类型自行扩展。
|
||||
|
||||
### checkAuthorization(authType)
|
||||
该方法用于校验用户 token 是否过期,接收一个参数:
|
||||
* **authType**
|
||||
认证类型,默认为 `AUTH_TYPE.BEARER`。
|
||||
|
||||
### removeAuthorization(authType)
|
||||
该方法用于移出用户本地存储的 token,接收一个参数:
|
||||
* **authType**
|
||||
认证类型,默认为 `AUTH_TYPE.BEARER`。
|
||||
:::tip
|
||||
以上 Api 均可在 /utils/request.js 文件中找到。
|
||||
:::
|
10
docs/advance/skill.md
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
title: 108个小技巧
|
||||
lang: zn-CN
|
||||
---
|
||||
# 108个小技巧
|
||||
|
||||
## 自定义菜单icon
|
||||
## 隐藏页面标题
|
||||
## 关闭页签API
|
||||
## 权限校验PI
|
7
docs/advance/theme.md
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
title: 更换主题
|
||||
lang: zn-CN
|
||||
---
|
||||
# 更换主题
|
||||
|
||||
### 作者还没来得及编辑该页面,如果你感兴趣,可以点击下方链接,帮助作者完善此页
|
BIN
docs/assets/admin-layout.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
docs/assets/auth.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
docs/assets/blank-view.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
docs/assets/common-layout.png
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
docs/assets/menu-demo.png
Normal file
After Width: | Height: | Size: 56 KiB |
BIN
docs/assets/mode-dark.png
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
docs/assets/mode-light.png
Normal file
After Width: | Height: | Size: 67 KiB |
BIN
docs/assets/mode-night.png
Normal file
After Width: | Height: | Size: 86 KiB |
BIN
docs/assets/new-page-2.png
Normal file
After Width: | Height: | Size: 152 KiB |
BIN
docs/assets/new-page-us.png
Normal file
After Width: | Height: | Size: 157 KiB |
BIN
docs/assets/new-page.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
docs/assets/page-layout.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
docs/assets/page-view.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
docs/assets/permission.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
docs/assets/tabs-view.png
Normal file
After Width: | Height: | Size: 38 KiB |
5
docs/develop/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
title: 开发
|
||||
lang: zh-CN
|
||||
---
|
||||
# 开发
|
79
docs/develop/layout.md
Normal file
@ -0,0 +1,79 @@
|
||||
---
|
||||
title: 布局
|
||||
lang: zh-CN
|
||||
---
|
||||
# 布局
|
||||
页面整体布局是一个产品最外层的框架结构,往往会包含导航、页脚、侧边栏、通知栏以及内容等。在页面之中,也有很多区块的布局结构。在真实项目中,页面布局通常统领整个应用的界面,有非常重要的作用。
|
||||
|
||||
## Admin 的布局
|
||||
在 Vue Antd Admin 中,我们抽离了使用过程中一些常用的布局,都放在 layouts 目录中,分别为:
|
||||
* [AdminLayout](https://github.com/iczer/vue-antd-admin/blob/master/src/layouts/AdminLayout.vue) / **管理后台布局**,包含了头部导航,侧边导航、内容区和页脚,一般用于后台系统的整体布局
|
||||
|
||||

|
||||
* [PageLayout](https://github.com/iczer/vue-antd-admin/blob/master/src/layouts/PageLayout.vue) / **页面布局**,包含了页头和内容区,常用于需要页头(包含面包屑、标题、额外操作等)的页面
|
||||
|
||||

|
||||
* [CommonLayout](https://github.com/iczer/vue-antd-admin/blob/master/src/layouts/CommonLayout.vue) / **通用布局**,仅包含内容区和页脚的简单布局,项目中常用于注册、登录或展示页面
|
||||
|
||||

|
||||
## Admin 的视图
|
||||
在 Vue Antd Admin 中,除了基本布局外,通常有很多页面的结构是相似的。因此,我们把这部分结构抽离为视图组件。
|
||||
一个视图组件通常包含一个基本布局组件、视图公共区块、路由视图内容区、页脚等,常常结合路由配置使用。它们也被放入了 layouts 目录中,分别为:
|
||||
* [TabsView](https://github.com/iczer/vue-antd-admin/blob/master/src/layouts/TabsView.vue) / **多页签视图**,包含了 AdminLayout 布局、多页签头和路由视图内容区
|
||||
|
||||

|
||||
* [PageView](https://github.com/iczer/vue-antd-admin/blob/master/src/layouts/PageView.vue) / **页面视图**,包含了 PageLayout 布局和路由视图内容区
|
||||
|
||||

|
||||
* [BlankView](https://github.com/iczer/vue-antd-admin/blob/master/src/layouts/BlankView.vue) / **空白视图**,仅包含一个路由视图内容区
|
||||
|
||||

|
||||
## 如何使用
|
||||
通常我们会把视图组件和路由配置结合一起使用,我们把配置信息抽离在路由配置文件中 [src/router/config.js](https://github.com/iczer/vue-antd-admin/blob/master/src/router/config.js) 。如下:
|
||||
```jsx {7,12}
|
||||
{
|
||||
path: 'form',
|
||||
name: '表单页',
|
||||
meta: {
|
||||
icon: 'form',
|
||||
},
|
||||
component: PageView,
|
||||
children: [
|
||||
{
|
||||
path: 'basic',
|
||||
name: '基础表单',
|
||||
component: () => import('@/pages/form/basic/BasicForm'),
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
当然,如果这满足不了你的需求,你也可以自定义一些视图组件,或者直接在页面组件中使用布局。参考
|
||||
[workplace](https://github.com/iczer/vue-antd-admin/blob/master/src/pages/dashboard/workplace/WorkPlace.vue) 页面:
|
||||
```vue {2,13}
|
||||
<template>
|
||||
<page-layout :avatar="currUser.avatar">
|
||||
<div slot="headerContent">
|
||||
<div class="title">{{$t('timeFix')}},{{currUser.name}},{{$t('welcome')}}</div>
|
||||
<div>{{$t('position')}}</div>
|
||||
</div>
|
||||
<template slot="extra">
|
||||
<head-info class="split-right" :title="$t('project')" content="56"/>
|
||||
<head-info class="split-right" :title="$t('ranking')" content="8/24"/>
|
||||
<head-info class="split-right" :title="$t('visit')" content="2,223"/>
|
||||
</template>
|
||||
<div>...</div>
|
||||
</page-layout>
|
||||
</template>
|
||||
```
|
||||
## 其它布局组件
|
||||
除了 Admin 里的内建布局以外,在一些页面中需要进行布局,还可以使用 Ant Design Vue 提供的布局组件:Grid 和 Layout。
|
||||
### Grid 组件
|
||||
栅格布局是网页中最常用的布局,其特点就是按照一定比例划分页面,能够随着屏幕的变化依旧保持比例,从而具有弹性布局的特点。
|
||||
|
||||
而 Ant Design Vue 的栅格组件提供的功能更为强大,能够设置间距、具有支持响应式的比例设置,以及支持 flex 模式,基本上涵盖了大部分的布局场景,详情查看:[Grid](https://www.antdv.com/components/grid-cn/)。
|
||||
### Layout 组件
|
||||
如果你需要辅助页面框架级别的布局设计,那么 Layout 则是你最佳的选择,它抽象了大部分框架布局结构,使得只需要填空就可以开发规范专业的页面整体布局,详情查看:[Layout](https://www.antdv.com/components/layout-cn/)。
|
||||
### 根据不同场景区分抽离布局组件
|
||||
在大部分场景下,我们需要基于上面两个组件封装一些适用于当下具体业务的组件,包含了通用的导航、侧边栏、顶部通知、页面标题等元素。例如 Vue Antd Admin 的 [AdminLayout](https://github.com/iczer/vue-antd-admin/blob/master/src/layouts/AdminLayout.vue)。
|
||||
|
||||
通常,我们会把抽象出来的布局组件,放到 layouts 文件夹中方便管理。需要注意的是,这些布局组件和我们平时使用的其它组件并没有什么不同,只不过功能性上是为了处理布局问题而单独归类。
|
7
docs/develop/mock.md
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Mock
|
||||
lang: zh-CN
|
||||
---
|
||||
# Mock
|
||||
|
||||
### 作者还没来得及编辑该页面,如果你感兴趣,可以点击下方链接,帮助作者完善此页
|
218
docs/develop/page.md
Normal file
@ -0,0 +1,218 @@
|
||||
---
|
||||
title: 页面
|
||||
lang: zh-CN
|
||||
---
|
||||
# 页面
|
||||
这里的『页面』包含新建页面文件,配置路由、样式文件及i18n国际化等。通常情况下,你仅需简单的配置就可以添加一个新的页面。
|
||||
## 新建页面文件
|
||||
在 src/pages 下创建新的 .vue 文件。如果页面相关文件过多,您可以创建一个文件夹来放置这些文件。
|
||||
```diff
|
||||
├── public
|
||||
├── src
|
||||
│ ├── assets # 本地静态资源
|
||||
: :
|
||||
│ ├── pages # 页面组件和通用模板
|
||||
+ │ │ └── NewPage.vue # 新页面文件
|
||||
or
|
||||
+ │ │ └── newPage # 为新页面创建一个文件夹
|
||||
+ │ │ ├── NewPage.vue # 新页面文件
|
||||
+ │ │ ├── index.less # 页面样式文件
|
||||
+ │ │ └── index.js # import 引导文件
|
||||
: :
|
||||
│ └── main.js # 应用入口js
|
||||
├── package.json # package.json
|
||||
├── README.md # README.md
|
||||
└── vue.config.js # vue 配置文件
|
||||
```
|
||||
为了更好地演示,我们初始化 NewPage.vue 文件如下:
|
||||
```vue
|
||||
<template>
|
||||
<div class="new-page" :style="`min-height: ${pageMinHeight}px`">
|
||||
<h1>演示页面</h1>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import {mapState} from 'vuex'
|
||||
export default {
|
||||
name: 'NewPage',
|
||||
data() {
|
||||
return {
|
||||
desc: '这是一个演示页面'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('setting', ['pageMinHeight']),
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
@import "index.less";
|
||||
</style>
|
||||
```
|
||||
index.less 文件:
|
||||
```less
|
||||
.new-page{
|
||||
height: 100%;
|
||||
background-color: @base-bg-color;
|
||||
text-align: center;
|
||||
padding: 200px 0 0 0;
|
||||
margin-top: -24px;
|
||||
h1{
|
||||
font-size: 48px;
|
||||
}
|
||||
}
|
||||
```
|
||||
index.js 文件:
|
||||
```js
|
||||
import NewPage from './NewPage'
|
||||
export default NewPage
|
||||
```
|
||||
## 配置路由
|
||||
路由配置在 src/router/config.js 文件中,我们把上面创建的页面文件加入路由配置中
|
||||
```js {10-14}
|
||||
const options = {
|
||||
routes: [
|
||||
{name: '登录页'...},
|
||||
{
|
||||
path: '/',
|
||||
name: '首页',
|
||||
component: TabsView,
|
||||
redirect: '/login',
|
||||
children: [
|
||||
{
|
||||
path: 'newPage',
|
||||
name: '新页面',
|
||||
component: () => import('@/pages/newPage'),
|
||||
},
|
||||
{
|
||||
path: 'dashboard',
|
||||
name: 'Dashboard',
|
||||
meta: {
|
||||
icon: 'dashboard'
|
||||
},
|
||||
component: BlankView,
|
||||
children: [...]
|
||||
}
|
||||
]
|
||||
...
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
:::tip
|
||||
我们建议使用英文设置路由的 path 属性,用中文设置路由的 name 属性。因为系统将自动提取路由的 path 和 name 属性作为国际化配置。这在后面的章节
|
||||
[进阶>国际化](../advance/i18n.md)中将会讲到。
|
||||
当然,如果你的项目不需要国际化,可以忽略。
|
||||
:::
|
||||
启动服务,你将看到新增页面如下:
|
||||

|
||||
如果你想把它配置为二级页面或更深层级的页面,只需为它配置一个父级路由,并为父级路由配置一个[视图组件](./layout.md#admin-的视图),
|
||||
这里我们选择 [PageView](https://github.com/iczer/vue-antd-admin/blob/master/src/layouts/PageView.vue),如下:
|
||||
```js {10-21}
|
||||
const options = {
|
||||
routes: [
|
||||
{name: '登录页'...},
|
||||
{
|
||||
path: '/',
|
||||
name: '首页',
|
||||
component: TabsView,
|
||||
redirect: '/login',
|
||||
children: [
|
||||
{
|
||||
path: 'parent',
|
||||
name: '父级路由',
|
||||
component: PageView,
|
||||
children: [
|
||||
{
|
||||
path: 'newPage',
|
||||
name: '新页面',
|
||||
component: () => import('@/pages/newPage'),
|
||||
}
|
||||
]
|
||||
},
|
||||
{name: 'dashboard'...}
|
||||
]
|
||||
...
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
:::warning
|
||||
页面所有父级路由的组件必须配置为[视图组件](../develop/layout.md#admin-的视图),否则页面的内容可能不会显示。
|
||||
目前有 [PageView](https://github.com/iczer/vue-antd-admin/blob/master/src/layouts/PageView.vue)、
|
||||
[TabsView](https://github.com/iczer/vue-antd-admin/blob/master/src/layouts/tabs/TabsView.vue) 和
|
||||
[BlankView](https://github.com/iczer/vue-antd-admin/blob/master/src/layouts/BlankView.vue) 可选,
|
||||
你也可以自己创建视图组件。([什么是视图组件?](../develop/layout.md#admin-的视图))
|
||||
:::
|
||||
页面如下:
|
||||

|
||||
## i18n国际化配置
|
||||
如果你想为页面增加i18n国际化配置,只需在页面同级文件夹下创建 i18n.js 文件,然后在页面文件中引入并使用即可。
|
||||
创建 i18n.js 文件:
|
||||
```diff {9}
|
||||
├── public
|
||||
├── src
|
||||
│ ├── assets # 本地静态资源
|
||||
: :
|
||||
│ ├── pages # 页面组件和通用模板
|
||||
│ │ └── newPage # 为新页面创建一个文件夹
|
||||
│ │ ├── NewPage.vue # 新页面文件
|
||||
│ │ ├── index.less # 页面样式文件
|
||||
+ │ │ ├── i18n.js # i18n 国际化配置文件
|
||||
│ │ └── index.js # import 引导文件
|
||||
: :
|
||||
│ └── main.js # 应用入口js
|
||||
├── package.json # package.json
|
||||
├── README.md # README.md
|
||||
└── vue.config.js # vue 配置文件
|
||||
```
|
||||
i18n.js 文件内容:
|
||||
```js
|
||||
module.exports = {
|
||||
messages: {
|
||||
CN: {
|
||||
content: '演示页面',
|
||||
description: '这是一个演示页面'
|
||||
},
|
||||
HK: {
|
||||
content: '演示頁面',
|
||||
description: '這是一個演示頁面'
|
||||
},
|
||||
US: {
|
||||
content: 'Demo Page',
|
||||
description: 'This is a demo page'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
在 NewPage.vue 文件中引入 i18n.js,并添加需要国际化的内容。如下修改:
|
||||
```vue {3,10,13-15}
|
||||
<template>
|
||||
<div class="new-page" :style="`min-height: ${pageMinHeight}px`">
|
||||
<h1>{{$t('content')}}</h1>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import {mapState} from 'vuex'
|
||||
export default {
|
||||
name: 'NewPage',
|
||||
i18n: require('./i18n'),
|
||||
computed: {
|
||||
...mapState('setting', ['pageMinHeight']),
|
||||
desc() {
|
||||
return this.$t('description')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
@import "index";
|
||||
</style>
|
||||
```
|
||||
然后页面右上角语言项选择 ``English``,你会发现,页面语言切换为英文了。如下:
|
||||

|
||||
一切就是这么的简单!
|
||||
:::tip
|
||||
如果你尝试切换为繁体语言,可能会发现``页面标题``和``面包屑``显示为英文。
|
||||
这涉及到路由的国际化配置,在章节 [进阶 > 国际化](../advance/i18n.md) 中,我们会对此作详细讲解。
|
||||
:::
|
143
docs/develop/router.md
Normal file
@ -0,0 +1,143 @@
|
||||
---
|
||||
title: 路由和菜单
|
||||
lang: zh-CN
|
||||
---
|
||||
# 路由和菜单
|
||||
路由和菜单起到组织一个应用的关键骨架的作用,Vue Antd Admin 使用 [vue-router](https://router.vuejs.org/zh/) 来配置和管理我们的路由和菜单。
|
||||
## 基本结构
|
||||
得益于 vue-router 路由配置的可扩展性,Vue Antd Admin 通过结合 router 配置文件、基本算法及 [menu.js](https://github.com/iczer/vue-antd-admin/blob/master/src/components/menu/menu.js) 菜单生成工具,搭建了路由和菜单的基本框架,主要涉及以下几个模块/功能:
|
||||
|
||||
|功能 |配置 |
|
||||
|:----------|:-------------------------------|
|
||||
|*路由管理* |通过 [vue-router](https://router.vuejs.org/zh/) 的路由规则进行管理和配置|
|
||||
|*菜单生成* |根据路由配置自动生成菜单,菜单项名称、图标和层级等全部可以通过路由配置进行自定义|
|
||||
|*面包屑* |布局组件 [PageLayout](https://github.com/iczer/vue-antd-admin/blob/master/src/layouts/PageLayout.vue) 提取当前页面路由,并根据当前路由层次关系自动生成面包屑,当然你也可以自定义面包屑|
|
||||
|*页面标题* |同面包屑,布局组件 [PageLayout](https://github.com/iczer/vue-antd-admin/blob/master/src/layouts/PageLayout.vue) 根据提取到的当前页面的路由名称设置为页面标题,你也同样可以自定义标题|
|
||||
|
||||
## 路由
|
||||
Vue Antd Admin 的路由配置完全遵循 vue-router 的 [routes 配置规则](https://router.vuejs.org/zh/api/#routes)。
|
||||
另外我们还在 routes 的元数据属性 [meta](https://router.vuejs.org/zh/guide/advanced/meta.html#%E8%B7%AF%E7%94%B1%E5%85%83%E4%BF%A1%E6%81%AF) 中注入了三个属性 icon、invisible 和 page,它们将在生成菜单和页头时发挥作用。配置示例如下:
|
||||
```js {7,13}
|
||||
const options = {
|
||||
routes: [{
|
||||
path: '/',
|
||||
name: '首页',
|
||||
component: TabsView,
|
||||
meta: {
|
||||
invisible: true
|
||||
},
|
||||
children: [{
|
||||
path: 'dashboard',
|
||||
name: 'Dashboard',
|
||||
meta: {
|
||||
icon: 'dashboard'
|
||||
},
|
||||
component: BlankView,
|
||||
children: [{
|
||||
path: 'workplace',
|
||||
name: '工作台',
|
||||
component: () => import('@/pages/dashboard/workplace/WorkPlace'),
|
||||
}, {
|
||||
path: 'analysis',
|
||||
name: '分析页',
|
||||
component: () => import('@/pages/dashboard/analysis/Analysis'),
|
||||
}]
|
||||
}]
|
||||
}]
|
||||
}
|
||||
```
|
||||
完整配置示例,请查看 [src/router/config.js](https://github.com/iczer/vue-antd-admin/blob/master/src/router/config.js)
|
||||
|
||||
## 菜单
|
||||
Admin 系统的菜单直接通过路由配置生成,路由属性和菜单功能对应关系如下
|
||||
|
||||
|路由属性|对应菜单功能|
|
||||
|:-----------------|:-------|
|
||||
|**name** |菜单名称 |
|
||||
|**path** |点击菜单时的跳转链接|
|
||||
|**meta.icon** |菜单图标,图标使用 ant-design-vue 图标库,对应 [Icon](https://www.antdv.com/components/icon-cn/#API) 组件 的 type 属性|
|
||||
|**meta.invisible**|是否不将此路由项渲染为菜单项,默认false;如设置为 true,则生成菜单时将忽略此路由|
|
||||
|
||||
假如使用上面 [路由](#路由) 文档中的 [配置示例](#路由),将会生成如下菜单:
|
||||
|
||||

|
||||
实际项目中,我们是在 AdminLayout 组件创建之前,提取 router 配置中根路由 '/' 下所有子路由配置,
|
||||
并将此配置传递给 menu.js 插件,从而生成菜单。如下:
|
||||
```vue {4,12,13,14}
|
||||
<template>
|
||||
<a-layout :class="['admin-layout'...]">
|
||||
...
|
||||
<side-menu :menuData="menuData".../>
|
||||
</a-layout>
|
||||
</template>
|
||||
<script>
|
||||
import ...
|
||||
export default {
|
||||
name: 'AdminLayout',
|
||||
...
|
||||
beforeCreate () {
|
||||
menuData = this.$router.options.routes.find((item) => item.path === '/').children
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
详细代码可查看 [layouts/AdminLayout#L83](https://github.com/iczer/vue-antd-admin/blob/master/src/layouts/AdminLayout.vue#L83)。
|
||||
当然你也可以不使用 router 配置生成菜单,你只需按照配置规则给菜单传递你所定义配置即可。菜单组件配置规则如下:
|
||||
```jsx {}
|
||||
[{
|
||||
name: '菜单标题',
|
||||
path: '菜单路由',
|
||||
meta: {
|
||||
icon: '菜单图标',
|
||||
invisible: 'boolean, 是否隐藏此菜单项, 默认 false',
|
||||
},
|
||||
children: [ //子菜单配置
|
||||
{
|
||||
name: '子菜单标题',
|
||||
path: '子菜单路由',
|
||||
meta: {
|
||||
icon: '子菜单图标',
|
||||
invisible: 'boolean, 是否隐藏此菜单项, 默认 false',
|
||||
},
|
||||
}
|
||||
]
|
||||
}]
|
||||
```
|
||||
更多细节可查看 [components/menu/menu.js](https://github.com/iczer/vue-antd-admin/blob/master/src/components/menu/menu.js)
|
||||
|
||||
## 面包屑
|
||||
面包屑由 [PageHeader](https://github.com/iczer/vue-antd-admin/blob/master/src/components/page/PageHeader.vue) 实现,PageLayout 组件会从当前页面路由提取面包屑配置(如未设置,则根据当前路由层次关系生成面包屑)。所以只要页面中使用了 PageLayout 布局或者它的父级组件使用了 PageLayout 布局,面包屑都将自动生成。
|
||||
|
||||
当然,如果你想在某个页面自定义面包屑,只需在对应的路由元数据 meta 中定义 page.breadcrumb 属性即可。Vue Antd Admin 将会优先使用路由元数据 meta 中定义的面包屑配置。
|
||||
|
||||
比如,想自定义工作台页面面包屑,可以在工作台的 route 配置中如下设置:
|
||||
```jsx {5,6,7}
|
||||
{
|
||||
path: 'workplace',
|
||||
name: '工作台',
|
||||
meta: {
|
||||
page: {
|
||||
breadcrumb: ['首页', 'Dashboard', '自定义']
|
||||
}
|
||||
},
|
||||
component: () => import('@/pages/dashboard/workplace/WorkPlace'),
|
||||
}
|
||||
```
|
||||
更多细节可查看 [layouts/PageLayout.vue#L55](https://github.com/iczer/vue-antd-admin/blob/master/src/layouts/PageLayout.vue#L55)
|
||||
## 页面标题
|
||||
页面标题的实现方式与面包屑基本一致,也是由 PageLayout 组件从当前页面路由提取标题(如未设置,则提取当前路由名称作为标题)。
|
||||
|
||||
如果你想自定义页面标题,在页面对应的路由元数据 meta 中定义 page.title 属性即可,如下示例,定义了工作台页面的标题:
|
||||
```jsx {5,6,7}
|
||||
{
|
||||
path: 'workplace',
|
||||
name: '工作台',
|
||||
meta: {
|
||||
page: {
|
||||
title: '自定义标题'
|
||||
}
|
||||
},
|
||||
component: () => import('@/pages/dashboard/workplace/WorkPlace'),
|
||||
}
|
||||
```
|
||||
更多细节可查看 [layouts/PageLayout.vue#L48](https://github.com/iczer/vue-antd-admin/blob/master/src/layouts/PageLayout.vue#L48)
|
161
docs/develop/service.md
Normal file
@ -0,0 +1,161 @@
|
||||
---
|
||||
title: 服务端交互
|
||||
lang: zh-CN
|
||||
---
|
||||
# 服务端交互
|
||||
数据服务是一个应用的灵魂,它驱动着应用的各个功能模块的正常运转。Vue Antd Admin 在 service 模块封装了服务端交互,通过 API 的形式可以和任何技术栈的服务端应用一起工作。
|
||||
## 服务交互流程
|
||||
在 Vue Antd Admin 中,服务端交互流程如下:
|
||||
* 组件内调用 service 服务 API
|
||||
* service 服务 API 封装请求数据,通过 request.js 发送请求
|
||||
* 组件获取 service 返回的数据,更新视图数据或触发其它行为
|
||||
|
||||
我们以登录为例,Login.vue 组件内,用户输入账号密码,点击登录,调用 services/user/login api
|
||||
```vue {5,17}
|
||||
<template>
|
||||
...
|
||||
</template>
|
||||
<script>
|
||||
import {login} from '@/services/user'
|
||||
...
|
||||
export default {
|
||||
name: 'Login',
|
||||
methods: {
|
||||
onSubmit (e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((err) => {
|
||||
if (!err) {
|
||||
this.logging = true
|
||||
const name = this.form.getFieldValue('name')
|
||||
const password = this.form.getFieldValue('password')
|
||||
login(name, password).then(res => this.afterLogin(res))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
`services/user/login` 封装账户密码数据,通过 `request.js` 发送登录服务请求
|
||||
```js
|
||||
import {request, METHOD} from '@/utils/request'
|
||||
/**
|
||||
* 登录服务
|
||||
* @param name 账户名
|
||||
* @param password 账户密码
|
||||
* @returns {Promise<AxiosResponse<T>>}
|
||||
*/
|
||||
async function login(name, password) {
|
||||
return request(LOGIN, METHOD.POST, {
|
||||
name: name,
|
||||
password: password
|
||||
})
|
||||
}
|
||||
```
|
||||
Login.vue 获取登录服务返回的数据,进行后续操作
|
||||
```vue {14,18-23}
|
||||
<template>
|
||||
...
|
||||
</template>
|
||||
<script>
|
||||
import {login} from '@/services/user'
|
||||
...
|
||||
export default {
|
||||
name: 'Login',
|
||||
methods: {
|
||||
onSubmit (e) {
|
||||
this.form.validateFields((err) => {
|
||||
if (!err) {
|
||||
...
|
||||
login(name, password).then(res => this.afterLogin(res))
|
||||
}
|
||||
})
|
||||
},
|
||||
afterLogin(res) {
|
||||
if (res.data.code >= 0) { //登录成功
|
||||
...
|
||||
} else { //登录失败
|
||||
this.error = loginRes.message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
## 服务模块结构
|
||||
服务模块结构如下:
|
||||
```bash
|
||||
...
|
||||
├── src
|
||||
│ └── services # 数据服务模块
|
||||
│ ├── user.js # 用户数据服务
|
||||
│ ├── product.js # 产品服务
|
||||
│ ...
|
||||
│ ├── api.js # api 地址管理
|
||||
│ └── index.js # 服务模块导出
|
||||
...
|
||||
│ └── utils # 数据服务模块
|
||||
│ ├── request.js # 基于 axios 的 http 请求工具
|
||||
...
|
||||
```
|
||||
services 文件夹下, api.js 用于服务请求地址的统一管理,index.js 用于模块化导出服务,其它 *.js 文件对应各个服务模块。
|
||||
## request.js
|
||||
request.js 基于 axios 封装了一些常用的函数,如下:
|
||||
```js
|
||||
export {
|
||||
METHOD, //http method 常量
|
||||
AUTH_TYPE, //凭证认证类型 常量
|
||||
request, //http请求函数
|
||||
setAuthorization, //设置身份凭证函数
|
||||
removeAuthorization, //移除身份凭证函数
|
||||
checkAuthorization //检查身份凭证是否过期函数
|
||||
}
|
||||
```
|
||||
:::tip
|
||||
凭证认证类型默认为 [Bearer](https://www.jianshu.com/p/8f7009456abc),你可以根据自己的需要实现其它类型的认证
|
||||
:::
|
||||
## Base url 配置
|
||||
你可以在项目根目录下的环境变量文件(.env 和 .env.development)中配置你的 API 服务 base url 地址。
|
||||
|
||||
生产环境,.env 文件
|
||||
```properties
|
||||
VUE_APP_API_BASE_URL=https://www.server.com
|
||||
```
|
||||
开发环境,.env.development 文件:
|
||||
```properties
|
||||
VUE_APP_API_BASE_URL=https://localhost:8000
|
||||
```
|
||||
## 跨域设置
|
||||
在开发环境中,通常我们的Vue应用和服务应用运行在不同的地址或端口上。我们可以通过简单的设置,代理前端请求,来避免跨域问题。如下:
|
||||
|
||||
首先,在 services/api.js 文件中设置 API_PROXY_PREFIX 常量,BASE_URL 像下面这样设置:
|
||||
```js {2,4}
|
||||
//跨域代理前缀
|
||||
const API_PROXY_PREFIX='/api'
|
||||
//base url
|
||||
const BASE_URL = process.env.NODE_ENV === 'production' ? process.env.VUE_APP_API_BASE_URL : API_PROXY_PREFIX
|
||||
//导出api服务地址
|
||||
module.exports = {
|
||||
LOGIN: `${BASE_URL}/login`,
|
||||
ROUTES: `${BASE_URL}/routes`
|
||||
}
|
||||
```
|
||||
然后,在 vue.config.js 文件中配置代理:
|
||||
```js
|
||||
model.exports = {
|
||||
devServer: {
|
||||
proxy: {
|
||||
'/api': { //此处要与 /services/api.js 中的 API_PROXY_PREFIX 值保持一致
|
||||
target: process.env.VUE_APP_API_BASE_URL,
|
||||
changeOrigin: true,
|
||||
pathRewrite: {
|
||||
'^/api': ''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
:::tip
|
||||
此代理配置仅适用于开发环境,生产环境的跨域代理请在自己的web服务器配置。
|
||||
:::
|
502
docs/develop/theme.md
Normal file
@ -0,0 +1,502 @@
|
||||
---
|
||||
title: 主题定制
|
||||
lang: zh-CN
|
||||
---
|
||||
# 主题定制
|
||||
|
||||
## 主题颜色
|
||||
### 主题色
|
||||
我们内置了一个色盘供您选择
|
||||
|
||||
<color color="#fa541c"/>
|
||||
<color color="#fadb14"/>
|
||||
<color color="#3eaf7c"/>
|
||||
<color color="#13c2c2"/>
|
||||
<color color="#1890ff"/>
|
||||
<color color="#722ed1"/>
|
||||
<color color="#eb2f96"/>
|
||||
|
||||
如果这不能满足你的需求,你也可以使用任何你喜欢的颜色,只需要在 src/config/config.js 文件中配置你的主题色即可。如:
|
||||
```js {3}
|
||||
module.exports = {
|
||||
theme: {
|
||||
color: '#13c2c2', //换成任何你喜欢的颜色,支持 hex 色值
|
||||
mode: 'night'
|
||||
},
|
||||
multiPage: true,
|
||||
animate: {
|
||||
name: 'roll',
|
||||
direction: 'default'
|
||||
}
|
||||
}
|
||||
```
|
||||
当你设置好主题色后,系统会根据这个主题色为你生成一系列配套的颜色,并应用到vue组件中。
|
||||
:::tip
|
||||
你可以在你的样式文件中直接使用 less 变量 ``@theme-color``。
|
||||
:::
|
||||
:::warning
|
||||
主题色目前只支持 ``hex`` 模式的色值。如果设置为 ``rgb`` 或其它模式的色值,可能会导致配套颜色无法生成。
|
||||
:::
|
||||
### 功能色
|
||||
除了主题色,系统还有一些功能性颜色,分别为:成功色、警告色和错误色。默认色值分别为:
|
||||
|名称|success |warning |error |
|
||||
|:-:|:--------:|:-------:|:-----:|
|
||||
|色值|``#52c41a``|``#faad14``|``#f5222d``|
|
||||
|颜色|<color color="#52c41a"/>|<color color="#faad14"/>|<color color="#f5222d" />|
|
||||
|less变量|@success-color|@warning-color|@error-color|
|
||||
|
||||
你也可以在 src/config/config.js 重新定义这些功能色
|
||||
```js {5-7}
|
||||
module.exports = {
|
||||
theme: {
|
||||
color: '#13c2c2',
|
||||
mode: 'night',
|
||||
success: '#52c41a', //定义成功色,支持 hex 色值
|
||||
warning: '#faad14', //定义警告色,支持 hex 色值
|
||||
error: '#f5222d' //定义错误色,支持 hex 色值
|
||||
},
|
||||
multiPage: true,
|
||||
animate: {
|
||||
name: 'roll',
|
||||
direction: 'default'
|
||||
}
|
||||
}
|
||||
```
|
||||
:::tip
|
||||
想在在你的样式文件中使用以上各功能色,引用各功能色对应的 less 变量即可。
|
||||
:::
|
||||
:::warning
|
||||
功能色目前也只支持 ``hex`` 模式的色值。如果设置为 ``rgb`` 或其它模式的色值,可能会导致配套颜色无法生成。
|
||||
:::
|
||||
### 文本色
|
||||
<table style="text-align: center" >
|
||||
<tr>
|
||||
<th>主题模式</th>
|
||||
<th>标题色</th>
|
||||
<th>文本色</th>
|
||||
<th>次级文本色</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">light/dark</td>
|
||||
<td><color color="rgba(0,0,0,0.85)"/></td>
|
||||
<td><color color="rgba(0,0,0,0.65)"/></td>
|
||||
<td><color color="rgba(0,0,0,0.45)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>rgba(0,0,0,0.85)</code></td>
|
||||
<td><code>rgba(0,0,0,0.65)</code></td>
|
||||
<td><code>rgba(0,0,0,0.45)</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">night</td>
|
||||
<td><color color="rgba(255,255,255,0.85)"/></td>
|
||||
<td><color color="rgba(255,255,255,0.65)"/></td>
|
||||
<td><color color="rgba(255,255,255,0.45)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>rgba(255,255,255,0.85)</code></td>
|
||||
<td><code>rgba(255,255,255,0.65)</code></td>
|
||||
<td><code>rgba(255,255,255,0.45)</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>less变量</td>
|
||||
<td>@title-color</td>
|
||||
<td>@text-color</td>
|
||||
<td>@text-color-second</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
:::tip
|
||||
想在在你的样式文件中使用以上文本色,引用各文本色对应的 less 变量即可。
|
||||
:::
|
||||
:::warning
|
||||
目前不支持自定义文本色,因为涉及到主题模式切换时文本色的置换问题。如强行修改,可能会导致主题模式切换时出现样式异常。
|
||||
如果你的项目不需要主题模式切换,可自行替换以上文本色。
|
||||
:::
|
||||
|
||||
### 背景色
|
||||
|
||||
<table style="text-align: center">
|
||||
<tr>
|
||||
<th>主题模式</th>
|
||||
<th>布局背景色</th>
|
||||
<th>基础背景色</th>
|
||||
<th>hover背景色</th>
|
||||
<th>边框颜色</th>
|
||||
<th>阴影颜色</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">light/dark</td>
|
||||
<td><color color="#f0f2f5"/></td>
|
||||
<td><color color="#fff"/></td>
|
||||
<td><color color="rgba(0,0,0,0.025)"/></td>
|
||||
<td><color color="#f0f0f0"/></td>
|
||||
<td><color color="rgba(0,0,0,0.15)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#f0f2f5</code></td>
|
||||
<td><code>#fff</code></td>
|
||||
<td><code>rgba(0,0,0,0.025)</code></td>
|
||||
<td><code>#f0f0f0</code></td>
|
||||
<td><code>rgba(0,0,0,0.15)</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">night</td>
|
||||
<td><color color="#000"/></td>
|
||||
<td><color color="#141414"/></td>
|
||||
<td><color color="rgba(255,255,255,0.025)"/></td>
|
||||
<td><color color="#303030"/></td>
|
||||
<td><color color="rgba(255,255,255,0.15)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#000</code></td>
|
||||
<td><code>#141414</code></td>
|
||||
<td><code>rgba(255,255,255,0.025)</code></td>
|
||||
<td><code>#303030</code></td>
|
||||
<td><code>rgba(255,255,255,0.15)</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>less变量</td>
|
||||
<td>@layout-bg-color</td>
|
||||
<td>@base-bg-color</td>
|
||||
<td>@hover-bg-color</td>
|
||||
<td>@border-color</td>
|
||||
<td>@shadow-color</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
:::tip
|
||||
想在在你的样式文件中使用以上背景色,引用各背景色对应的 less 变量即可。
|
||||
:::
|
||||
:::warning
|
||||
目前也不支持自定义背景色,因为涉及到主题模式切换时背景色的置换问题。如强行修改,可能会导致主题模式切换时出现样式异常。
|
||||
如果你的项目不需要主题模式切换,可自行替换以上背景色。
|
||||
:::
|
||||
|
||||
### antd 的色系
|
||||
除了以上颜色,我们还引入了 ant-design 内置的色系。如下:
|
||||
|
||||
<table style="text-align: center">
|
||||
<tr>
|
||||
<th>色系</th>
|
||||
<th>类型</th>
|
||||
<th>颜色</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">blue/拂晓蓝</td>
|
||||
<td>色盘</td>
|
||||
<td >
|
||||
<color-list
|
||||
:colors="['#e6f7ff', '#bae7ff', '#91d5ff', '#69c0ff', '#40a9ff', '#1890ff', '#096dd9', '#0050b3', '#003a8c', '#002766']"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>less变量</td>
|
||||
<td>
|
||||
<code>@blue-1</code>、
|
||||
<code>@blue-2</code>
|
||||
<code>...</code>
|
||||
<code>@blue-10</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">purple/酱紫</td>
|
||||
<td>色盘</td>
|
||||
<td>
|
||||
<color-list
|
||||
:colors="['#f9f0ff', '#efdbff', '#d3adf7', '#b37feb', '#9254de', '#722ed1', '#531dab', '#391085', '#22075e', '#120338']"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>less变量</td>
|
||||
<td>
|
||||
<code>@purple-1</code>、
|
||||
<code>@purple-2</code>
|
||||
<code>...</code>
|
||||
<code>@purple-10</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">cyan/明青</td>
|
||||
<td>色盘</td>
|
||||
<td>
|
||||
<color-list
|
||||
:colors="['#e6fffb', '#b5f5ec', '#87e8de', '#5cdbd3', '#36cfc9', '#13c2c2', '#08979c', '#006d75', '#00474f', '#002329']"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>less变量</td>
|
||||
<td>
|
||||
<code>@cyan-1</code>、
|
||||
<code>@cyan-2</code>
|
||||
<code>...</code>
|
||||
<code>@cyan-10</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">green/极光绿</td>
|
||||
<td>色盘</td>
|
||||
<td>
|
||||
<color-list
|
||||
:colors="['#f6ffed', '#d9f7be', '#b7eb8f', '#95de64', '#73d13d', '#52c41a', '#389e0d', '#237804', '#135200', '#092b00']"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>less变量</td>
|
||||
<td>
|
||||
<code>@green-1</code>、
|
||||
<code>@green-2</code>
|
||||
<code>...</code>
|
||||
<code>@green-10</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">magenta/法式洋红</td>
|
||||
<td>色盘</td>
|
||||
<td>
|
||||
<color-list
|
||||
:colors="['#fff0f6', '#ffd6e7', '#ffadd2', '#ff85c0', '#f759ab', '#eb2f96', '#c41d7f', '#9e1068', '#780650', '#520339']"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>less变量</td>
|
||||
<td>
|
||||
<code>@magenta-1</code>、
|
||||
<code>@magenta-2</code>
|
||||
<code>...</code>
|
||||
<code>@magenta-10</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">red/薄暮</td>
|
||||
<td>色盘</td>
|
||||
<td>
|
||||
<color-list
|
||||
:colors="['#fff1f0', '#ffccc7', '#ffa39e', '#ff7875', '#ff4d4f', '#f5222d', '#cf1322', '#a8071a', '#820014', '#5c0011']"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>less变量</td>
|
||||
<td>
|
||||
<code>@red-1</code>、
|
||||
<code>@red-2</code>
|
||||
<code>...</code>
|
||||
<code>@red-10</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">orange/日暮</td>
|
||||
<td>色盘</td>
|
||||
<td>
|
||||
<color-list
|
||||
:colors="['#fff7e6', '#ffe7ba', '#ffd591', '#ffc069', '#ffa940', '#fa8c16', '#d46b08', '#ad4e00', '#873800', '#612500']"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>less变量</td>
|
||||
<td>
|
||||
<code>@orange-1</code>、
|
||||
<code>@orange-2</code>
|
||||
<code>...</code>
|
||||
<code>@orange-10</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">yellow/日出</td>
|
||||
<td>色盘</td>
|
||||
<td>
|
||||
<color-list
|
||||
:colors="['#feffe6', '#ffffb8', '#fffb8f', '#fff566', '#ffec3d', '#fadb14', '#d4b106', '#ad8b00', '#876800', '#614700']"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>less变量</td>
|
||||
<td>
|
||||
<code>@yellow-1</code>、
|
||||
<code>@yellow-2</code>
|
||||
<code>...</code>
|
||||
<code>@yellow-10</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">volcano/火山</td>
|
||||
<td>色盘</td>
|
||||
<td>
|
||||
<color-list
|
||||
:colors="['#fff2e8', '#ffd8bf', '#ffbb96', '#ff9c6e', '#ff7a45', '#fa541c', '#d4380d', '#ad2102', '#871400', '#610b00']"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>less变量</td>
|
||||
<td>
|
||||
<code>@volcano-1</code>、
|
||||
<code>@volcano-2</code>
|
||||
<code>...</code>
|
||||
<code>@volcano-10</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">geekblue/极客蓝</td>
|
||||
<td>色盘</td>
|
||||
<td>
|
||||
<color-list
|
||||
:colors="['#f0f5ff', '#d6e4ff', '#adc6ff', '#85a5ff', '#597ef7', '#2f54eb', '#1d39c4', '#10239e', '#061178', '#030852']"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>less变量</td>
|
||||
<td>
|
||||
<code>@geekblue-1</code>、
|
||||
<code>@geekblue-2</code>
|
||||
<code>...</code>
|
||||
<code>@geekblue-10</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">lime/青柠</td>
|
||||
<td>色盘</td>
|
||||
<td>
|
||||
<color-list
|
||||
:colors="['#fcffe6', '#f4ffb8', '#eaff8f', '#d3f261', '#bae637', '#a0d911', '#7cb305', '#5b8c00', '#3f6600', '#254000']"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>less变量</td>
|
||||
<td>
|
||||
<code>@lime-1</code>、
|
||||
<code>@lime-2</code>
|
||||
<code>...</code>
|
||||
<code>@lime-10</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">gold/金盏花</td>
|
||||
<td>色盘</td>
|
||||
<td>
|
||||
<color-list
|
||||
:colors="['#fffbe6', '#fff1b8', '#ffe58f', '#ffd666', '#ffc53d', '#faad14', '#d48806', '#ad6800', '#874d00', '#613400']"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>less变量</td>
|
||||
<td>
|
||||
<code>@gold-1</code>、
|
||||
<code>@gold-2</code>
|
||||
<code>...</code>
|
||||
<code>@gold-10</code>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
以上色系对应的less变量均可以在你的样式代码中直接使用。
|
||||
|
||||
:::tip
|
||||
我们建议在开发中使用 `less变量` 而不是直接使用 `颜色值` 来设置颜色。这样做对主题色和主题模式切换很有帮助。
|
||||
:::
|
||||
## 主题模式
|
||||
Vue Antd Admin 有三种主题模式,分别为:`light/亮色菜单模式`、`dark/暗色菜单模式` 和 `night/黑夜模式`。
|
||||
|
||||
light / 亮色菜单模式:
|
||||

|
||||
dark / 暗色菜单模式:
|
||||

|
||||
night / 黑夜模式:
|
||||

|
||||
|
||||
你可以在这三种模式之间随意切换,也可以在 src/config/config.js 中设置默认的主题模式。
|
||||
```js {4}
|
||||
module.exports = {
|
||||
theme: {
|
||||
color: '#13c2c2',
|
||||
mode: 'night' //设置你的默认主题模式,可选 light、dark 和 night
|
||||
},
|
||||
multiPage: true,
|
||||
animate: {
|
||||
name: 'roll',
|
||||
direction: 'default'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 导航布局
|
||||
Vue Antd Admin 有两种导航布局,`side/侧边导航` 和 `head/顶部导航`。
|
||||
默认为侧边导航,你可以在 src/config/config.js 中修改导航布局
|
||||
```js {6}
|
||||
module.exports = {
|
||||
theme: {
|
||||
color: '#13c2c2',
|
||||
mode: 'night'
|
||||
},
|
||||
layout: 'side', //设置你的默认导航布局,有 side 和 head 可选
|
||||
multiPage: true,
|
||||
animate: {
|
||||
name: 'roll',
|
||||
direction: 'default'
|
||||
}
|
||||
}
|
||||
```
|
||||
## 动画
|
||||
Vue Antd Admin 内置了 [animate.css](https://animate.style) 动画库,在页面切换时会应用动画效果。你可以在 src/config/config.js 中配置动画效果或者禁用动画。
|
||||
```js {7-11}
|
||||
module.exports = {
|
||||
theme: {
|
||||
color: '#13c2c2',
|
||||
mode: 'night'
|
||||
},
|
||||
multiPage: true,
|
||||
animate: {
|
||||
disabled: false, //禁用动画,true:禁用,false:启用
|
||||
name: 'roll', //动画效果,支持的动画效果可参考 src/config/default/animate.config.js
|
||||
direction: 'default' //动画方向,切换页面时动画的方向,参考 src/config/default/animate.config.js
|
||||
}
|
||||
}
|
||||
```
|
||||
支持的动画特效种类,可以参考 src/config/default/animate.config.js 文件。
|
||||
## 其它
|
||||
### 色弱模式
|
||||
对于有视觉障碍的群体,我们提供了色弱模式,你可以通过配置 src/config/config.js 启用色弱模式
|
||||
```js {7}
|
||||
module.exports = {
|
||||
theme: {
|
||||
color: '#13c2c2',
|
||||
mode: 'night'
|
||||
},
|
||||
multiPage: true,
|
||||
weekMode: false, //色弱模式,true:开启,false:不开启
|
||||
animate: {
|
||||
name: 'roll',
|
||||
direction: 'default'
|
||||
}
|
||||
}
|
||||
```
|
||||
### 多页签
|
||||
在 src/config/config.js 设置 multiPage 来启用或关闭多页签模式
|
||||
```js {6}
|
||||
module.exports = {
|
||||
theme: {
|
||||
color: '#13c2c2',
|
||||
mode: 'night'
|
||||
},
|
||||
multiPage: true, //多页签模式,true:开启,false:不开启
|
||||
animate: {
|
||||
name: 'roll',
|
||||
direction: 'default'
|
||||
}
|
||||
}
|
||||
```
|
||||
完整的系统设置参考 src/config/default/setting.config.js
|
||||
:::tip
|
||||
以上所有主题设置项,均已映射到 vuex/setting 模块的 state 中,你可以通过提交 setting/mutations 实时修改设置项。
|
||||
如何使用 [mutations](https://vuex.vuejs.org/zh/guide/mutations.html) ?
|
||||
:::
|
5
docs/other/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
title: 其它
|
||||
lang: zh-CN
|
||||
---
|
||||
# 其它
|
8
docs/other/community.md
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
title: 社区
|
||||
lang: zh-CN
|
||||
---
|
||||
# 社区
|
||||
|
||||
## 交流学习
|
||||
### QQ群:812277510、610090280(已满)
|
5
docs/other/upgrade.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
title: 更新日志
|
||||
lang: zh-CN
|
||||
---
|
||||
# 更新日志
|
5
docs/start/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
title: 开始
|
||||
lang: zh-CN
|
||||
---
|
||||
## 开始
|
22
docs/start/faq.md
Normal file
@ -0,0 +1,22 @@
|
||||
---
|
||||
title: 常见问题
|
||||
lang: zh-CN
|
||||
---
|
||||
# 常见问题
|
||||
### 为什么不是 Ant Design Pro Vue ?
|
||||
[Ant Design Pro Vue](https://github.com/vueComponent/ant-design-vue-pro) 是 [Ant Design Pro](https://github.com/ant-design/ant-design-pro) 的 Vue 版本,其中项目结构、组件、
|
||||
布局和使用方法等基本与 Ant Design Pro 的 react 版本保持一致。如果你比较熟悉 react 版,或者你已经在使用它,这确实是一个不错的选择。
|
||||
|
||||
[Vue Antd Admin](https://github.com/iczer/vue-antd-admin) 同样实现了 Ant Design Pro 的所有功能。与此同时,我们还根据 Vue 的特性,对 Ant Design Pro 的一些组件和布局作出了相应的修改及优化,同时不影响保持与 Ant Design Pro 的一致。
|
||||
|
||||
另外,我们还在添加一些 Ant Design Pro 没有的功能,比如全局动画、多页签模式等。
|
||||
|
||||
如果你想使用 Ant Design Pro,但又觉得它缺乏一些你想要的功能,不妨看看 [Vue Antd Admin](https://github.com/iczer/vue-antd-admin),我们会认真考虑每个用户的需求。
|
||||
|
||||
因此,如果你有一些不错的想法和建议,欢迎随时和我们交流,很可能你的想法就在我们下一个版本中实现。
|
||||
|
||||
### 如何使用 Vue Antd Admin ?
|
||||
请阅读文档 [开始使用](./use.md)。有任何疑问,欢迎在 github 上给我们提交 [issue](https://github.com/iczer/vue-antd-admin/issues/new)。
|
||||
|
||||
### 是否支持国际化 ?
|
||||
Vue Antd Admin 引入了 vue-i18n 支持。因此你可以使用 vue-i18n 的特性对项目做国际化修改,详细请查看 [国际化](../advance/i18n.md)
|
62
docs/start/use.md
Normal file
@ -0,0 +1,62 @@
|
||||
---
|
||||
title: 使用
|
||||
lang: zh-CN
|
||||
---
|
||||
# 使用
|
||||
## 准备
|
||||
你的本地环境需要安装 yarn、node 和 git。我们的技术栈基于 ES2015+、Vue、Antd,提前学习这些知识会非常有帮助。
|
||||
## 安装
|
||||
克隆本项目到本地
|
||||
```bash
|
||||
$ git clone https://github.com/iczer/vue-antd-admin.git
|
||||
```
|
||||
安装依赖
|
||||
```bash
|
||||
$ yarn install
|
||||
or
|
||||
$ npm install
|
||||
```
|
||||
:::tip
|
||||
master 分支是 Vue Antd Admin 的标准版代码,此分支代码适合用于用于学习研究,不推荐在此分支做正式开发。
|
||||
我们在 basic 分支提供了 Vue Antd Admin 的基础版代码,正式开发请切换至此分支,以便于后续的版本更新。
|
||||
:::
|
||||
:::warning
|
||||
如果基于 `master分支` 进行开发,在版本更新时遇到的代码冲突问题请自行解决,我们不对基于 `master分支` 开发时遇到的问题提供技术支持。
|
||||
再次强调,`master分支` 仅推荐用于学习参考,正式开发请切换至 `basic` 分支!!!
|
||||
:::
|
||||
## 目录结构
|
||||
我们已经为你生成了一个完整的开发框架,提供了涵盖中后台开发的各类功能和坑位,下面是整个项目的目录结构。
|
||||
|
||||
```bash
|
||||
├── docs # 使用文档
|
||||
├── public
|
||||
│ └── favicon.png # favicon
|
||||
│ └── index.html # 入口 HTML
|
||||
├── src
|
||||
│ ├── assets # 本地静态资源
|
||||
│ ├── components # 内置通用组件
|
||||
│ ├── config # 系统配置
|
||||
│ ├── layouts # 通用布局
|
||||
│ ├── mock # 本地 mock 数据
|
||||
│ ├── pages # 页面组件和通用模板
|
||||
│ ├── plugins # vue 插件
|
||||
│ ├── router # 路由配置
|
||||
│ ├── services # 数据服务模块
|
||||
│ ├── store # vuex 状态管理配置
|
||||
│ ├── theme # 主题相关
|
||||
│ ├── utils # js 工具
|
||||
│ ├── App.vue # 应用入口组件
|
||||
│ ├── bootstrap.js # 应用启动引导js
|
||||
│ └── main.js # 应用入口js
|
||||
├── package.json # package.json
|
||||
├── README.md # README.md
|
||||
└── vue.config.js # vue 配置文件
|
||||
```
|
||||
## 本地开发
|
||||
启动服务
|
||||
```bash
|
||||
$ yarn serve
|
||||
or
|
||||
$ npm run serve
|
||||
```
|
||||
启动成功后,会看到一个本地预览地址,通常是 http://localhost:8080 。接下来就可以修改代码,并实时预览修改结果啦!
|
13
index.html
@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="shortcut icon" href="static/favicon.icon" type="image/icon" />
|
||||
<title>vue-antd-admin</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
147
package.json
@ -1,100 +1,79 @@
|
||||
{
|
||||
"name": "vue-antd-admin",
|
||||
"version": "1.0.0",
|
||||
"description": "A Vue.js project",
|
||||
"author": "chenghx <chenghx@nfex.com>",
|
||||
"version": "0.7.4",
|
||||
"homepage": "https://iczer.github.io/vue-antd-admin",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
|
||||
"start": "npm run dev",
|
||||
"unit": "jest --config test/unit/jest.conf.js --coverage",
|
||||
"e2e": "node test/e2e/runner.js",
|
||||
"test": "npm run unit && npm run e2e",
|
||||
"lint": "eslint --ext .js,.vue src test/unit test/e2e/specs",
|
||||
"build": "node build/build.js"
|
||||
"serve": "export NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve",
|
||||
"build": "export NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build",
|
||||
"lint": "vue-cli-service lint",
|
||||
"predeploy": "yarn build",
|
||||
"deploy": "gh-pages -d dist -b pages -r https://github.com/iczer/vue-antd-admin.git",
|
||||
"docs:dev": "export NODE_OPTIONS=--openssl-legacy-provider && vuepress dev docs",
|
||||
"docs:build": "export NODE_OPTIONS=--openssl-legacy-provider && vuepress build docs",
|
||||
"predocs:deploy": "yarn docs:build",
|
||||
"docs:deploy": "gh-pages -d docs/.vuepress/dist -b doc -r https://github.com/iczer/vue-antd-admin.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@antv/data-set": "^0.8.9",
|
||||
"ant-design-vue": "^1.0.3",
|
||||
"axios": "^0.18.0",
|
||||
"clipboard": "^2.0.1",
|
||||
"date-fns": "^1.29.0",
|
||||
"@antv/data-set": "^0.11.4",
|
||||
"animate.css": "^4.1.0",
|
||||
"ant-design-vue": "1.7.2",
|
||||
"axios": "^0.19.2",
|
||||
"clipboard": "^2.0.6",
|
||||
"core-js": "^3.6.5",
|
||||
"date-fns": "^2.14.0",
|
||||
"enquire.js": "^2.1.6",
|
||||
"mockjs": "^1.0.1-beta3",
|
||||
"pouchdb": "^7.0.0",
|
||||
"viser-vue": "^2.2.5",
|
||||
"vue": "^2.5.17",
|
||||
"vue-router": "^3.0.1",
|
||||
"vuedraggable": "^2.16.0",
|
||||
"vuex": "^3.0.1"
|
||||
"highlight.js": "^10.2.1",
|
||||
"js-cookie": "^2.2.1",
|
||||
"mockjs": "^1.1.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"viser-vue": "^2.4.8",
|
||||
"vue": "^2.6.11",
|
||||
"vue-i18n": "^8.18.2",
|
||||
"vue-router": "^3.3.4",
|
||||
"vuedraggable": "^2.23.2",
|
||||
"vuex": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^7.1.2",
|
||||
"babel-core": "^6.22.1",
|
||||
"babel-eslint": "^8.2.1",
|
||||
"babel-helper-vue-jsx-merge-props": "^2.0.3",
|
||||
"babel-jest": "^21.0.2",
|
||||
"babel-loader": "^7.1.1",
|
||||
"babel-plugin-dynamic-import-node": "^1.2.0",
|
||||
"babel-plugin-syntax-jsx": "^6.18.0",
|
||||
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
|
||||
"babel-plugin-transform-runtime": "^6.22.0",
|
||||
"babel-plugin-transform-vue-jsx": "^3.5.0",
|
||||
"babel-preset-env": "^1.3.2",
|
||||
"babel-preset-stage-2": "^6.22.0",
|
||||
"babel-register": "^6.22.0",
|
||||
"chalk": "^2.0.1",
|
||||
"chromedriver": "^2.27.2",
|
||||
"copy-webpack-plugin": "^4.0.1",
|
||||
"cross-spawn": "^5.0.1",
|
||||
"css-loader": "^0.28.0",
|
||||
"eslint": "^4.15.0",
|
||||
"eslint-config-standard": "^10.2.1",
|
||||
"eslint-friendly-formatter": "^3.0.0",
|
||||
"eslint-loader": "^1.7.1",
|
||||
"eslint-plugin-import": "^2.7.0",
|
||||
"eslint-plugin-node": "^5.2.0",
|
||||
"eslint-plugin-promise": "^3.4.0",
|
||||
"eslint-plugin-standard": "^3.0.1",
|
||||
"eslint-plugin-vue": "^4.0.0",
|
||||
"extract-text-webpack-plugin": "^3.0.0",
|
||||
"file-loader": "^1.1.4",
|
||||
"friendly-errors-webpack-plugin": "^1.6.1",
|
||||
"html-webpack-plugin": "^2.30.1",
|
||||
"jest": "^22.0.4",
|
||||
"jest-serializer-vue": "^0.3.0",
|
||||
"less": "^3.7.1",
|
||||
"less-loader": "^4.1.0",
|
||||
"nightwatch": "^0.9.12",
|
||||
"node-notifier": "^5.1.2",
|
||||
"optimize-css-assets-webpack-plugin": "^3.2.0",
|
||||
"ora": "^1.2.0",
|
||||
"portfinder": "^1.0.13",
|
||||
"postcss-import": "^11.0.0",
|
||||
"postcss-loader": "^2.0.8",
|
||||
"postcss-url": "^7.2.1",
|
||||
"rimraf": "^2.6.0",
|
||||
"selenium-server": "^3.0.1",
|
||||
"semver": "^5.3.0",
|
||||
"shelljs": "^0.7.6",
|
||||
"uglifyjs-webpack-plugin": "^1.1.1",
|
||||
"url-loader": "^0.5.8",
|
||||
"vue-jest": "^1.0.2",
|
||||
"vue-loader": "^13.3.0",
|
||||
"vue-style-loader": "^3.0.1",
|
||||
"vue-template-compiler": "2.5.17",
|
||||
"webpack": "^3.6.0",
|
||||
"webpack-bundle-analyzer": "^2.9.0",
|
||||
"webpack-dev-server": "^2.9.1",
|
||||
"webpack-merge": "^4.1.0"
|
||||
"@ant-design/colors": "^4.0.1",
|
||||
"@vue/cli-plugin-babel": "^4.4.0",
|
||||
"@vue/cli-plugin-eslint": "^4.4.0",
|
||||
"@vue/cli-service": "^4.4.0",
|
||||
"@vuepress/plugin-back-to-top": "^1.5.2",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"compression-webpack-plugin": "^2.0.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"gh-pages": "^3.1.0",
|
||||
"less-loader": "^6.1.1",
|
||||
"style-resources-loader": "^1.3.2",
|
||||
"vue-cli-plugin-style-resources-loader": "^0.1.4",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"vuepress": "^1.5.2",
|
||||
"webpack-theme-color-replacer": "1.3.18",
|
||||
"whatwg-fetch": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0.0",
|
||||
"npm": ">= 3.0.0"
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie <= 8"
|
||||
"not ie <= 10"
|
||||
]
|
||||
}
|
||||
|
BIN
public/favicon.ico
Normal file
After Width: | Height: | Size: 2.8 KiB |
27
public/index.html
Normal file
@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="beauty-scroll">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= process.env.VUE_APP_NAME %></title>
|
||||
<!-- require cdn assets css -->
|
||||
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
|
||||
<link rel="stylesheet" href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" />
|
||||
<% } %>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="popContainer" class="beauty-scroll" style="height: 100vh; overflow-y: scroll">
|
||||
<div id="app"></div>
|
||||
</div>
|
||||
<!-- require cdn assets js -->
|
||||
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
|
||||
<script type="text/javascript" src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
|
||||
<% } %>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
111
src/App.vue
@ -1,48 +1,93 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<a-config-provider :locale="locale" :get-popup-container="popContainer">
|
||||
<router-view/>
|
||||
</div>
|
||||
</a-config-provider>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import enquireScreen from './utils/device'
|
||||
import {enquireScreen} from './utils/util'
|
||||
import {mapState, mapMutations} from 'vuex'
|
||||
import themeUtil from '@/utils/themeUtil';
|
||||
import {getI18nKey} from '@/utils/routerUtil'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
data() {
|
||||
return {
|
||||
locale: {}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
let _this = this
|
||||
enquireScreen(isMobile => {
|
||||
_this.$store.commit('setting/setDevice', isMobile)
|
||||
})
|
||||
this.setHtmlTitle()
|
||||
this.setLanguage(this.lang)
|
||||
enquireScreen(isMobile => this.setDevice(isMobile))
|
||||
},
|
||||
mounted() {
|
||||
this.setWeekModeTheme(this.weekMode)
|
||||
},
|
||||
watch: {
|
||||
weekMode(val) {
|
||||
this.setWeekModeTheme(val)
|
||||
},
|
||||
lang(val) {
|
||||
this.setLanguage(val)
|
||||
this.setHtmlTitle()
|
||||
},
|
||||
$route() {
|
||||
this.setHtmlTitle()
|
||||
},
|
||||
'theme.mode': function(val) {
|
||||
let closeMessage = this.$message.loading(`您选择了主题模式 ${val}, 正在切换...`)
|
||||
themeUtil.changeThemeColor(this.theme.color, val).then(closeMessage)
|
||||
},
|
||||
'theme.color': function(val) {
|
||||
let closeMessage = this.$message.loading(`您选择了主题色 ${val}, 正在切换...`)
|
||||
themeUtil.changeThemeColor(val, this.theme.mode).then(closeMessage)
|
||||
},
|
||||
'layout': function() {
|
||||
window.dispatchEvent(new Event('resize'))
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('setting', ['layout', 'theme', 'weekMode', 'lang'])
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('setting', ['setDevice']),
|
||||
setWeekModeTheme(weekMode) {
|
||||
if (weekMode) {
|
||||
document.body.classList.add('week-mode')
|
||||
} else {
|
||||
document.body.classList.remove('week-mode')
|
||||
}
|
||||
},
|
||||
setLanguage(lang) {
|
||||
this.$i18n.locale = lang
|
||||
switch (lang) {
|
||||
case 'CN':
|
||||
this.locale = require('ant-design-vue/es/locale-provider/zh_CN').default
|
||||
break
|
||||
case 'HK':
|
||||
this.locale = require('ant-design-vue/es/locale-provider/zh_TW').default
|
||||
break
|
||||
case 'US':
|
||||
default:
|
||||
this.locale = require('ant-design-vue/es/locale-provider/en_US').default
|
||||
break
|
||||
}
|
||||
},
|
||||
setHtmlTitle() {
|
||||
const route = this.$route
|
||||
const key = route.path === '/' ? 'home.name' : getI18nKey(route.matched[route.matched.length - 1].path)
|
||||
document.title = process.env.VUE_APP_NAME + ' | ' + this.$t(key)
|
||||
},
|
||||
popContainer() {
|
||||
return document.getElementById("popContainer")
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
:global{
|
||||
//拖拽控件全局样式
|
||||
.dragable-ghost{
|
||||
border: 1px dashed #aaaaaa;
|
||||
opacity: 0.65;
|
||||
}
|
||||
.dragable-chose{
|
||||
border: 1px dashed #aaaaaa;
|
||||
opacity: 0.65;
|
||||
}
|
||||
.dragable-drag{
|
||||
border: 1px dashed #aaaaaa;
|
||||
opacity: 0.65;
|
||||
}
|
||||
//页面切换动画
|
||||
.page-toggle-enter-active{
|
||||
transition: all 0.2s ease-in 0.25s;
|
||||
}
|
||||
.page-toggle-leave-active{
|
||||
transition: all 0.2s ease-out 0s;
|
||||
}
|
||||
.page-toggle-enter, .page-toggle-leave-to{
|
||||
opacity: 0;
|
||||
padding: 0px;
|
||||
}
|
||||
<style lang="less" scoped>
|
||||
#id{
|
||||
}
|
||||
</style>
|
||||
|
BIN
src/assets/img/alipay.png
Normal file
After Width: | Height: | Size: 150 KiB |
BIN
src/assets/img/logo.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
src/assets/img/preview-nine.png
Normal file
After Width: | Height: | Size: 131 KiB |
BIN
src/assets/img/preview.png
Normal file
After Width: | Height: | Size: 70 KiB |
BIN
src/assets/img/wechatpay.png
Normal file
After Width: | Height: | Size: 119 KiB |
Before Width: | Height: | Size: 6.7 KiB |
25
src/bootstrap.js
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
import {loadRoutes, loadGuards, setAppOptions} from '@/utils/routerUtil'
|
||||
import {loadInterceptors} from '@/utils/request'
|
||||
import guards from '@/router/guards'
|
||||
import interceptors from '@/utils/axios-interceptors'
|
||||
|
||||
/**
|
||||
* 启动引导方法
|
||||
* 应用启动时需要执行的操作放在这里
|
||||
* @param router 应用的路由实例
|
||||
* @param store 应用的 vuex.store 实例
|
||||
* @param i18n 应用的 vue-i18n 实例
|
||||
* @param i18n 应用的 message 实例
|
||||
*/
|
||||
function bootstrap({router, store, i18n, message}) {
|
||||
// 设置应用配置
|
||||
setAppOptions({router, store, i18n})
|
||||
// 加载 axios 拦截器
|
||||
loadInterceptors(interceptors, {router, store, i18n, message})
|
||||
// 加载路由
|
||||
loadRoutes()
|
||||
// 加载路由守卫
|
||||
loadGuards(guards, {router, store, i18n, message})
|
||||
}
|
||||
|
||||
export default bootstrap
|
@ -1,113 +0,0 @@
|
||||
<template>
|
||||
<div class="hello">
|
||||
<h1>{{ msg }}</h1>
|
||||
<h2>Essential Links</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href="https://vuejs.org"
|
||||
target="_blank"
|
||||
>
|
||||
Core Docs
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://forum.vuejs.org"
|
||||
target="_blank"
|
||||
>
|
||||
Forum
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://chat.vuejs.org"
|
||||
target="_blank"
|
||||
>
|
||||
Community Chat
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://twitter.com/vuejs"
|
||||
target="_blank"
|
||||
>
|
||||
Twitter
|
||||
</a>
|
||||
</li>
|
||||
<br>
|
||||
<li>
|
||||
<a
|
||||
href="http://vuejs-templates.github.io/webpack/"
|
||||
target="_blank"
|
||||
>
|
||||
Docs for This Template
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h2>Ecosystem</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href="http://router.vuejs.org/"
|
||||
target="_blank"
|
||||
>
|
||||
vue-router
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="http://vuex.vuejs.org/"
|
||||
target="_blank"
|
||||
>
|
||||
vuex
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="http://vue-loader.vuejs.org/"
|
||||
target="_blank"
|
||||
>
|
||||
vue-loader
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://github.com/vuejs/awesome-vue"
|
||||
target="_blank"
|
||||
>
|
||||
awesome-vue
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'HelloWorld',
|
||||
data () {
|
||||
return {
|
||||
msg: 'Welcome to Your Vue.js App'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
h1, h2 {
|
||||
font-weight: normal;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
</style>
|
172
src/components/cache/AKeepAlive.js
vendored
Normal file
@ -0,0 +1,172 @@
|
||||
import {isDef, isRegExp, remove} from '@/utils/util'
|
||||
|
||||
const patternTypes = [String, RegExp, Array]
|
||||
|
||||
function matches (pattern, name) {
|
||||
if (Array.isArray(pattern)) {
|
||||
if (pattern.indexOf(name) > -1) {
|
||||
return true
|
||||
} else {
|
||||
for (let item of pattern) {
|
||||
if (isRegExp(item) && item.test(name)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
} else if (typeof pattern === 'string') {
|
||||
return pattern.split(',').indexOf(name) > -1
|
||||
} else if (isRegExp(pattern)) {
|
||||
return pattern.test(name)
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
return false
|
||||
}
|
||||
|
||||
function getComponentName (opts) {
|
||||
return opts && (opts.Ctor.options.name || opts.tag)
|
||||
}
|
||||
|
||||
function getComponentKey (vnode) {
|
||||
const {componentOptions, key} = vnode
|
||||
return key == null
|
||||
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
|
||||
: key + componentOptions.Ctor.cid
|
||||
}
|
||||
|
||||
function getFirstComponentChild (children) {
|
||||
if (Array.isArray(children)) {
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const c = children[i]
|
||||
if (isDef(c) && (isDef(c.componentOptions) || c.isAsyncPlaceholder)) {
|
||||
return c
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pruneCache (keepAliveInstance, filter) {
|
||||
const { cache, keys, _vnode } = keepAliveInstance
|
||||
for (const key in cache) {
|
||||
const cachedNode = cache[key]
|
||||
if (cachedNode) {
|
||||
const name = getComponentName(cachedNode.componentOptions)
|
||||
const componentKey = getComponentKey(cachedNode)
|
||||
if (name && !filter(name, componentKey)) {
|
||||
pruneCacheEntry(cache, key, keys, _vnode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pruneCacheEntry2(cache, key, keys) {
|
||||
const cached = cache[key]
|
||||
if (cached) {
|
||||
cached.componentInstance.$destroy()
|
||||
}
|
||||
cache[key] = null
|
||||
remove(keys, key)
|
||||
}
|
||||
|
||||
function pruneCacheEntry (cache, key, keys, current) {
|
||||
const cached = cache[key]
|
||||
if (cached && (!current || cached.tag !== current.tag)) {
|
||||
cached.componentInstance.$destroy()
|
||||
}
|
||||
cache[key] = null
|
||||
remove(keys, key)
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'AKeepAlive',
|
||||
abstract: true,
|
||||
model: {
|
||||
prop: 'clearCaches',
|
||||
event: 'clear',
|
||||
},
|
||||
props: {
|
||||
include: patternTypes,
|
||||
exclude: patternTypes,
|
||||
excludeKeys: patternTypes,
|
||||
max: [String, Number],
|
||||
clearCaches: Array
|
||||
},
|
||||
watch: {
|
||||
clearCaches: function(val) {
|
||||
if (val && val.length > 0) {
|
||||
const {cache, keys} = this
|
||||
val.forEach(key => {
|
||||
pruneCacheEntry2(cache, key, keys)
|
||||
})
|
||||
this.$emit('clear', [])
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.cache = Object.create(null)
|
||||
this.keys = []
|
||||
},
|
||||
|
||||
destroyed () {
|
||||
for (const key in this.cache) {
|
||||
pruneCacheEntry(this.cache, key, this.keys)
|
||||
}
|
||||
},
|
||||
|
||||
mounted () {
|
||||
this.$watch('include', val => {
|
||||
pruneCache(this, (name) => matches(val, name))
|
||||
})
|
||||
this.$watch('exclude', val => {
|
||||
pruneCache(this, (name) => !matches(val, name))
|
||||
})
|
||||
this.$watch('excludeKeys', val => {
|
||||
pruneCache(this, (name, key) => !matches(val, key))
|
||||
})
|
||||
},
|
||||
|
||||
render () {
|
||||
const slot = this.$slots.default
|
||||
const vnode = getFirstComponentChild(slot)
|
||||
const componentOptions = vnode && vnode.componentOptions
|
||||
if (componentOptions) {
|
||||
// check pattern
|
||||
const name = getComponentName(componentOptions)
|
||||
const componentKey = getComponentKey(vnode)
|
||||
const { include, exclude, excludeKeys } = this
|
||||
if (
|
||||
// not included
|
||||
(include && (!name || !matches(include, name))) ||
|
||||
// excluded
|
||||
(exclude && name && matches(exclude, name)) ||
|
||||
(excludeKeys && componentKey && matches(excludeKeys, componentKey))
|
||||
) {
|
||||
return vnode
|
||||
}
|
||||
|
||||
const { cache, keys } = this
|
||||
const key = vnode.key == null
|
||||
// same constructor may get registered as different local components
|
||||
// so cid alone is not enough (#3269)
|
||||
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
|
||||
: vnode.key + componentOptions.Ctor.cid
|
||||
if (cache[key]) {
|
||||
vnode.componentInstance = cache[key].componentInstance
|
||||
// make current key freshest
|
||||
remove(keys, key)
|
||||
keys.push(key)
|
||||
} else {
|
||||
cache[key] = vnode
|
||||
keys.push(key)
|
||||
// prune oldest entry
|
||||
if (this.max && keys.length > parseInt(this.max)) {
|
||||
pruneCacheEntry(cache, keys[0], keys, this._vnode)
|
||||
}
|
||||
}
|
||||
|
||||
vnode.data.keepAlive = true
|
||||
}
|
||||
return vnode || (slot && slot[0])
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<a-card :body-style="{padding: '20px 24px 8px'}" :bordered="false">
|
||||
<a-card :loading="loading" :body-style="{padding: '20px 24px 8px'}" :bordered="false">
|
||||
<div class="chart-card-header">
|
||||
<div class="meta">
|
||||
<span class="chart-card-title">{{title}}</span>
|
||||
@ -21,15 +21,13 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ACard from 'ant-design-vue/es/card/Card'
|
||||
export default {
|
||||
name: 'ChartCard',
|
||||
components: {ACard},
|
||||
props: ['title', 'total']
|
||||
props: ['title', 'total', 'loading']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="less">
|
||||
.chart-card-header{
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
@ -39,7 +37,7 @@ export default {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
color: rgba(0,0,0,.45);
|
||||
color: @text-color-second;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
@ -54,7 +52,6 @@ export default {
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
white-space: nowrap;
|
||||
color: #000;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 0;
|
||||
font-size: 30px;
|
||||
@ -62,7 +59,7 @@ export default {
|
||||
height: 38px;
|
||||
}
|
||||
.chart-card-footer{
|
||||
border-top: 1px solid #e8e8e8;
|
||||
border-top: 1px solid @border-color-base;
|
||||
padding-top: 9px;
|
||||
margin-top: 8px;
|
||||
}
|
@ -1,12 +1,14 @@
|
||||
<template>
|
||||
<div >
|
||||
<div class="bar">
|
||||
<h4>{{title}}</h4>
|
||||
<v-chart :force-fit="true" height="251" :data="data">
|
||||
<div class="chart">
|
||||
<v-chart :force-fit="true" height="312" :data="data" :padding="[24, 0, 0, 0]">
|
||||
<v-tooltip />
|
||||
<v-axis />
|
||||
<v-bar position="x*y"/>
|
||||
</v-chart>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -48,6 +50,10 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
<style scoped lang="less">
|
||||
.bar{
|
||||
position: relative;
|
||||
.chart{
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -18,7 +18,7 @@ const beginDay = new Date().getTime()
|
||||
const fakeY = [7, 5, 4, 2, 4, 7, 5, 6, 5, 9, 6, 3, 1, 5, 3, 6, 5]
|
||||
for (let i = 0; i < fakeY.length; i += 1) {
|
||||
data.push({
|
||||
x: format(new Date(beginDay + 1000 * 60 * 60 * 24 * i), 'YYYY-MM-DD'),
|
||||
x: format(new Date(beginDay + 1000 * 60 * 60 * 24 * i), 'yyyy-MM-dd'),
|
||||
y: fakeY[i]
|
||||
})
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ const beginDay = new Date().getTime()
|
||||
const fakeY = [7, 5, 4, 2, 4, 7, 5, 6, 5, 9, 6, 3, 1, 5, 3, 6, 5]
|
||||
for (let i = 0; i < fakeY.length; i += 1) {
|
||||
data.push({
|
||||
x: format(new Date(beginDay + 1000 * 60 * 60 * 24 * i), 'YYYY-MM-DD'),
|
||||
x: format(new Date(beginDay + 1000 * 60 * 60 * 24 * i), 'yyyy-MM-dd'),
|
||||
y: fakeY[i]
|
||||
})
|
||||
}
|
||||
|