4
0
mirror of https://github.com/iczer/vue-antd-admin.git synced 2025-10-04 13:59:57 +08:00

Compare commits

..

No commits in common. "master" and "0.0.1" have entirely different histories.

302 changed files with 10630 additions and 19294 deletions

18
.babelrc Normal file
View File

@ -0,0 +1,18 @@
{
"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"]
}
}
}

9
.editorconfig Normal file
View File

@ -0,0 +1,9 @@
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
View File

@ -1,10 +0,0 @@
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

View File

@ -1 +0,0 @@
VUE_APP_API_BASE_URL=http://dev.iczer.com

5
.eslintignore Normal file
View File

@ -0,0 +1,5 @@
/build/
/config/
/dist/
/*.js
/test/unit/coverage/

29
.eslintrc.js Normal file
View File

@ -0,0 +1,29 @@
// 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 Normal file
View File

@ -0,0 +1,5 @@
*.js linguist-language=vue
*.css linguist-language=vue
*.html linguist-language=vue

View File

@ -1,38 +0,0 @@
---
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.

View File

@ -1,20 +0,0 @@
---
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
View File

@ -1,7 +1,6 @@
.DS_Store
node_modules/
dist/
admindb/
/dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
@ -17,4 +16,3 @@ selenium-debug.log
*.njsproj
*.sln
package-lock.json
.env.production.local

10
.postcssrc.js Normal file
View File

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

47
LICENSE
View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2018 iczer
Copyright (c) 2018-present 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,3 +19,48 @@ 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.

View File

@ -1,53 +0,0 @@
[简体中文](./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.
[![MIT](https://img.shields.io/github/license/iczer/vue-antd-admin)](https://github.com/iczer/vue-antd-admin/blob/master/LICENSE)
[![Dependence](https://img.shields.io/david/iczer/vue-antd-admin)](https://david-dm.org/iczer/vue-antd-admin)
[![DevDependencies](https://img.shields.io/david/dev/iczer/vue-antd-admin)](https://david-dm.org/iczer/vue-antd-admin?type=dev)
[![Release](https://img.shields.io/github/v/release/iczer/vue-antd-admin)](https://github.com/iczer/vue-antd-admin/releases/latest)
![image](./src/assets/img/preview.png)
Multiple theme modes available
![image](./src/assets/img/preview-nine.png)
</div>
- Previewhttps://vue-antd-admin.pages.dev
- Documentationhttps://doc.vue-antd-admin.pages.dev
- FAQhttps://doc.vue-antd-admin.pages.dev/start/faq.html
- Mirror Repo in Chinahttps://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)

View File

@ -1,68 +1,47 @@
# 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)
简体中文 | [English](./README.en-US.md)
<h1 align="center">Vue Antd Admin</h1>
[预览地址](https://iczer.gitee.io/vue-antd-pro)
<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)
--
[![MIT](https://img.shields.io/github/license/iczer/vue-antd-admin)](https://github.com/iczer/vue-antd-admin/blob/master/LICENSE)
[![Dependence](https://img.shields.io/david/iczer/vue-antd-admin)](https://david-dm.org/iczer/vue-antd-admin)
[![DevDependencies](https://img.shields.io/david/dev/iczer/vue-antd-admin)](https://david-dm.org/iczer/vue-antd-admin?type=dev)
[![Release](https://img.shields.io/github/v/release/iczer/vue-antd-admin)](https://github.com/iczer/vue-antd-admin/releases/latest)
![image](./src/assets/img/preview.png)
多种主题模式可选:
![image](./src/assets/img/preview-nine.png)
</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
![](https://github.com/iczer/vue-antd-admin/blob/master/static/img/preview.jpg)
## 环境
* 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
## 安装
克隆项目到本地:
```
$ git clone https://github.com/iczer/vue-antd-admin.git
```
### yarn
```bash
安装依赖:
```
$ yarn install
$ yarn serve
```
### or npm
## 启动
```
$ npm install
$ npm run serve
$ yarn start
```
更多信息参考 [使用文档](https://iczer.gitee.io/vue-antd-admin-docs)
## 文档
编写中...
## 说明
该项目由仓主在业余由兴趣驱动完成,仍在不断开发完善中。详见:[开发进度](https://github.com/iczer/vue-antd-admin/projects/1)
## 参与贡献
我们非常欢迎你的贡献,你可以通过以下方式和我们一起共建 :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>
如有任何疑问或功能需求,欢迎 [Issue](https://github.com/iczer/vue-antd-admin/issues)。

1
_config.yml Normal file
View File

@ -0,0 +1 @@
theme: jekyll-theme-slate

View File

@ -1,13 +0,0 @@
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
}

41
build/build.js Normal file
View File

@ -0,0 +1,41 @@
'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'
))
})
})

54
build/check-versions.js Normal file
View File

@ -0,0 +1,54 @@
'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 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

101
build/utils.js Normal file
View File

@ -0,0 +1,101 @@
'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')
})
}
}

22
build/vue-loader.conf.js Normal file
View File

@ -0,0 +1,22 @@
'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'
}
}

View File

@ -0,0 +1,92 @@
'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'
}
}

95
build/webpack.dev.conf.js Normal file
View File

@ -0,0 +1,95 @@
'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)
}
})
})

149
build/webpack.prod.conf.js Normal file
View File

@ -0,0 +1,149 @@
'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

7
config/dev.env.js Normal file
View File

@ -0,0 +1,7 @@
'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"'
})

76
config/index.js Normal file
View File

@ -0,0 +1,76 @@
'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
}
}

4
config/prod.env.js Normal file
View File

@ -0,0 +1,4 @@
'use strict'
module.exports = {
NODE_ENV: '"production"'
}

7
config/test.env.js Normal file
View File

@ -0,0 +1,7 @@
'use strict'
const merge = require('webpack-merge')
const devEnv = require('./dev.env')
module.exports = merge(devEnv, {
NODE_ENV: '"testing"'
})

View File

@ -1,42 +0,0 @@
<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>

View File

@ -1,35 +0,0 @@
<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>

View File

@ -1,18 +0,0 @@
<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>

View File

@ -1,58 +0,0 @@
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
}
}

View File

@ -1,46 +0,0 @@
<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>

View File

@ -1,38 +0,0 @@
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

View File

@ -1,5 +0,0 @@
export default {
updated() {
this.$page.alert.top = 100
}
}

View File

@ -1,5 +0,0 @@
import AlertMixin from './alertMixin'
export default ({Vue}) => {
Vue.use(AlertMixin)
}

View File

@ -1,13 +0,0 @@
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')
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

View File

@ -1,12 +0,0 @@
.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
//}

View File

@ -1,2 +0,0 @@
$accentColor = #1890ff
$contentWidth = 940px

View File

@ -1,17 +0,0 @@
---
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
---

View File

@ -1,5 +0,0 @@
---
title: 进阶
lang: zn-CN
---
# 进阶

View File

@ -1,42 +0,0 @@
---
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`,即通过权限校验还是角色进行校验,可不传(不传的话,会对两种类型都进行匹配,任意一种匹配成功即校验通过)。

View File

@ -1,258 +0,0 @@
---
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
如果你不想从根路由 `/` 下获取菜单数据,可以根据自己的需求更改。
:::

View File

@ -1,230 +0,0 @@
---
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 方法,会看到如下提示:
![无此权限](../assets/permission.png)
### 操作权限校验的类型
`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 事件无效,如下图:
![权限校验指令](../assets/auth.png)
:::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',
}
}
]
}]
```

View File

@ -1,7 +0,0 @@
---
title: 图表
lang: zn-CN
---
# 图表
### 作者还没来得及编辑该页面,如果你感兴趣,可以点击下方链接,帮助作者完善此页

View File

@ -1,7 +0,0 @@
---
title: 错误处理
lang: zn-CN
---
# 错误处理
### 作者还没来得及编辑该页面,如果你感兴趣,可以点击下方链接,帮助作者完善此页

View File

@ -1,109 +0,0 @@
---
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]
}
```
:::

View File

@ -1,129 +0,0 @@
---
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/) 。

View File

@ -1,131 +0,0 @@
---
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] // 响应拦截
}
```
:::

View File

@ -1,76 +0,0 @@
---
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 文件中找到。
:::

View File

@ -1,10 +0,0 @@
---
title: 108个小技巧
lang: zn-CN
---
# 108个小技巧
## 自定义菜单icon
## 隐藏页面标题
## 关闭页签API
## 权限校验PI

View File

@ -1,7 +0,0 @@
---
title: 更换主题
lang: zn-CN
---
# 更换主题
### 作者还没来得及编辑该页面,如果你感兴趣,可以点击下方链接,帮助作者完善此页

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

View File

@ -1,5 +0,0 @@
---
title: 开发
lang: zh-CN
---
# 开发

View File

@ -1,79 +0,0 @@
---
title: 布局
lang: zh-CN
---
# 布局
页面整体布局是一个产品最外层的框架结构,往往会包含导航、页脚、侧边栏、通知栏以及内容等。在页面之中,也有很多区块的布局结构。在真实项目中,页面布局通常统领整个应用的界面,有非常重要的作用。
## Admin 的布局
在 Vue Antd Admin 中,我们抽离了使用过程中一些常用的布局,都放在 layouts 目录中,分别为:
* [AdminLayout](https://github.com/iczer/vue-antd-admin/blob/master/src/layouts/AdminLayout.vue) / **管理后台布局**,包含了头部导航,侧边导航、内容区和页脚,一般用于后台系统的整体布局
![admin-layout](../assets/admin-layout.png)
* [PageLayout](https://github.com/iczer/vue-antd-admin/blob/master/src/layouts/PageLayout.vue) / **页面布局**,包含了页头和内容区,常用于需要页头(包含面包屑、标题、额外操作等)的页面
![page-layout](../assets/page-layout.png)
* [CommonLayout](https://github.com/iczer/vue-antd-admin/blob/master/src/layouts/CommonLayout.vue) / **通用布局**,仅包含内容区和页脚的简单布局,项目中常用于注册、登录或展示页面
![common-layout](../assets/common-layout.png)
## Admin 的视图
在 Vue Antd Admin 中,除了基本布局外,通常有很多页面的结构是相似的。因此,我们把这部分结构抽离为视图组件。
一个视图组件通常包含一个基本布局组件、视图公共区块、路由视图内容区、页脚等,常常结合路由配置使用。它们也被放入了 layouts 目录中,分别为:
* [TabsView](https://github.com/iczer/vue-antd-admin/blob/master/src/layouts/TabsView.vue) / **多页签视图**,包含了 AdminLayout 布局、多页签头和路由视图内容区
![tabs-view](../assets/tabs-view.png)
* [PageView](https://github.com/iczer/vue-antd-admin/blob/master/src/layouts/PageView.vue) / **页面视图**,包含了 PageLayout 布局和路由视图内容区
![page-view](../assets/page-view.png)
* [BlankView](https://github.com/iczer/vue-antd-admin/blob/master/src/layouts/BlankView.vue) / **空白视图**,仅包含一个路由视图内容区
![blank-view](../assets/blank-view.png)
## 如何使用
通常我们会把视图组件和路由配置结合一起使用,我们把配置信息抽离在路由配置文件中 [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 文件夹中方便管理。需要注意的是,这些布局组件和我们平时使用的其它组件并没有什么不同,只不过功能性上是为了处理布局问题而单独归类。

View File

@ -1,7 +0,0 @@
---
title: Mock
lang: zh-CN
---
# Mock
### 作者还没来得及编辑该页面,如果你感兴趣,可以点击下方链接,帮助作者完善此页

View File

@ -1,218 +0,0 @@
---
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)中将会讲到。
当然,如果你的项目不需要国际化,可以忽略。
:::
启动服务,你将看到新增页面如下:
![newPage](../assets/new-page.png)
如果你想把它配置为二级页面或更深层级的页面,只需为它配置一个父级路由,并为父级路由配置一个[视图组件](./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-的视图)
:::
页面如下:
![newPage2](../assets/new-page-2.png)
## 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``,你会发现,页面语言切换为英文了。如下:
![newPageUs](../assets/new-page-us.png)
一切就是这么的简单!
:::tip
如果你尝试切换为繁体语言,可能会发现``页面标题``和``面包屑``显示为英文。
这涉及到路由的国际化配置,在章节 [进阶 > 国际化](../advance/i18n.md) 中,我们会对此作详细讲解。
:::

View File

@ -1,143 +0,0 @@
---
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则生成菜单时将忽略此路由|
假如使用上面 [路由](#路由) 文档中的 [配置示例](#路由),将会生成如下菜单:
![menu-demo](../assets/menu-demo.png)
实际项目中,我们是在 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)

View File

@ -1,161 +0,0 @@
---
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服务器配置。
:::

View File

@ -1,502 +0,0 @@
---
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 / 亮色菜单模式:
![light](../assets/mode-light.png)
dark / 暗色菜单模式:
![dark](../assets/mode-dark.png)
night / 黑夜模式:
![night](../assets/mode-night.png)
你可以在这三种模式之间随意切换,也可以在 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)
:::

View File

@ -1,5 +0,0 @@
---
title: 其它
lang: zh-CN
---
# 其它

View File

@ -1,8 +0,0 @@
---
title: 社区
lang: zh-CN
---
# 社区
## 交流学习
### QQ群812277510、610090280已满

View File

@ -1,5 +0,0 @@
---
title: 更新日志
lang: zh-CN
---
# 更新日志

View File

@ -1,5 +0,0 @@
---
title: 开始
lang: zh-CN
---
## 开始

View File

@ -1,22 +0,0 @@
---
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)

View File

@ -1,62 +0,0 @@
---
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 Normal file
View File

@ -0,0 +1,13 @@
<!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>

View File

@ -1,79 +1,100 @@
{
"name": "vue-antd-admin",
"version": "0.7.4",
"homepage": "https://iczer.github.io/vue-antd-admin",
"version": "1.0.0",
"description": "A Vue.js project",
"author": "chenghx <chenghx@nfex.com>",
"private": true,
"scripts": {
"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"
"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"
},
"dependencies": {
"@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",
"@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",
"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"
"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"
},
"devDependencies": {
"@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"
"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"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 10"
"not ie <= 8"
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -1,27 +0,0 @@
<!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>

View File

@ -1,93 +1,48 @@
<template>
<a-config-provider :locale="locale" :get-popup-container="popContainer">
<div id="app">
<router-view/>
</a-config-provider>
</div>
</template>
<script>
import {enquireScreen} from './utils/util'
import {mapState, mapMutations} from 'vuex'
import themeUtil from '@/utils/themeUtil';
import {getI18nKey} from '@/utils/routerUtil'
import enquireScreen from './utils/device'
export default {
name: 'App',
data() {
return {
locale: {}
}
},
created () {
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")
}
let _this = this
enquireScreen(isMobile => {
_this.$store.commit('setting/setDevice', isMobile)
})
}
}
</script>
<style lang="less" scoped>
#id{
<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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

25
src/bootstrap.js vendored
View File

@ -1,25 +0,0 @@
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

View File

@ -0,0 +1,113 @@
<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>

View File

@ -3,12 +3,7 @@
<a-row style="margin: 0 -34px">
<a-col style="padding: 0 34px; margin-bottom: 24px" :sm="12" :xs="24">
<div class="num-info">
<span class="title">
{{$t('search')}}
<a-tooltip :title="$t('introduce')">
<a-icon type="info-circle" style="font-size: 14px; margin-left: 8px" />
</a-tooltip>
</span>
<span class="title">搜索用户数</span>
<div class="value">
<span class="total">12321</span>
<span class="subtotal">71.2<a-icon type="caret-up" /></span>
@ -18,12 +13,7 @@
</a-col>
<a-col style="padding: 0 34px; margin-bottom: 24px" :sm="12" :xs="24">
<div class="num-info">
<span class="title">
{{$t('capita')}}
<a-tooltip :title="$t('introduce')">
<a-icon type="info-circle" style="font-size: 14px; margin-left: 8px" />
</a-tooltip>
</span>
<span class="title">搜索用户数</span>
<div class="value">
<span class="total">2.7</span>
<span class="subtotal">71.2<a-icon type="caret-down" /></span>
@ -34,10 +24,8 @@
</a-row>
<a-table
:dataSource="searchData"
:columns="tableColumns"
:columns="columns" size="small"
:pagination="{style: { marginBottom: 0 }, pageSize: 5}"
size="small"
rowKey="index"
>
<a href="#/" slot="keyword" slot-scope="text">{{text}}</a>
<span slot="rang" slot-scope="text">{{text}} %<a-icon type="caret-up" /> </span>
@ -46,7 +34,11 @@
</template>
<script>
import MiniArea from '../../../components/chart/MiniArea'
import ARow from 'ant-design-vue/es/grid/Row'
import ACol from 'ant-design-vue/es/grid/Col'
import AIcon from 'ant-design-vue/es/icon/icon'
import MiniArea from '../chart/MiniArea'
import ATable from 'ant-design-vue/es/table'
const searchData = []
for (let i = 0; i < 50; i++) {
@ -61,17 +53,20 @@ for (let i = 0; i < 50; i++) {
const columns = [
{
title: '排名',
dataIndex: 'index',
key: 'rank'
key: 'index'
},
{
title: '搜索关键词',
dataIndex: 'keyword',
key: 'keyword',
scopedSlots: {customRender: 'keyword'}
},
{
title: '用户数',
dataIndex: 'count',
key: 'users',
key: 'count',
sorter: (a, b) => a.count - b.count
},
{
@ -84,22 +79,12 @@ const columns = [
export default {
name: 'HotSearch',
components: {MiniArea},
i18n: require('./i18n-search'),
components: {ATable, MiniArea, AIcon, ARow, ACol},
data () {
return {
searchData,
columns
}
},
computed: {
tableColumns() {
let columns = this.columns
return columns.map(item => {
item.title = this.$t(item.key)
return item
})
}
}
}
</script>
@ -107,7 +92,7 @@ export default {
<style lang="less" scoped>
.num-info{
.title{
color: @text-color-second;
color: rgba(0,0,0,.45);
font-size: 14px;
height: 22px;
line-height: 22px;
@ -118,7 +103,7 @@ export default {
}
.value{
.total{
color: @title-color;
color: rgba(0,0,0,.85);
display: inline-block;
line-height: 32px;
height: 32px;
@ -126,7 +111,7 @@ export default {
margin-right: 32px;
}
.subtotal{
color: @text-color-second;
color: rgba(0,0,0,.45);
font-size: 16px;
vertical-align: top;
margin-right: 0;

View File

@ -1,5 +1,10 @@
<template>
<div style="">
<a-radio-group>
<a-radio-button value="a">全渠道</a-radio-button>
<a-radio-button value="b">线上</a-radio-button>
<a-radio-button value="c">门店</a-radio-button>
</a-radio-group>
<v-chart :forceFit="true" :height="height" :data="data" :scale="scale">
<v-tooltip :showTitle="false" dataKey="item*percent" />
<v-axis />
@ -11,8 +16,12 @@
</template>
<script>
import ARadio from 'ant-design-vue/es/radio'
const DataSet = require('@antv/data-set')
const ARadioGroup = ARadio.Group
const ARadioButton = ARadio.Button
const sourceData = [
{ item: '事例一', count: 40 },
{ item: '事例二', count: 21 },
@ -37,11 +46,12 @@ dv.transform({
const data = dv.rows
export default {
name: 'SalesData',
components: {ARadioGroup, ARadioButton},
data () {
return {
data,
scale,
height: 385,
height: 356,
pieStyle: {
stroke: '#fff',
lineWidth: 1

View File

@ -1,172 +0,0 @@
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])
}
}

View File

@ -1,13 +1,11 @@
<template>
<div class="bar">
<div >
<h4>{{title}}</h4>
<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>
<v-chart :force-fit="true" height="251" :data="data">
<v-tooltip />
<v-axis />
<v-bar position="x*y"/>
</v-chart>
</div>
</template>
@ -50,10 +48,6 @@ export default {
}
</script>
<style scoped lang="less">
.bar{
position: relative;
.chart{
}
}
<style scoped>
</style>

View File

@ -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]
})
}

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