diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 6c0b7f27..00000000 --- a/.babelrc +++ /dev/null @@ -1,17 +0,0 @@ -{ - "presets": [ - ["env", { - "modules": false, - "targets": { - "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] - } - }], - "stage-2" - ], - "plugins": ["transform-vue-jsx", "transform-runtime"], - "env": { - "development":{ - "plugins": ["dynamic-import-node"] - } - } -} diff --git a/.env.development b/.env.development new file mode 100644 index 00000000..8f5856db --- /dev/null +++ b/.env.development @@ -0,0 +1,14 @@ +# just a flag +ENV = 'development' + +# base api +VUE_APP_BASE_API = '/dev-api' + +# vue-cli uses the VUE_CLI_BABEL_TRANSPILE_MODULES environment variable, +# to control whether the babel-plugin-dynamic-import-node plugin is enabled. +# It only does one thing by converting all import() to require(). +# This configuration can significantly increase the speed of hot updates, +# when you have a large number of pages. +# Detail: https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/babel-preset-app/index.js + +VUE_CLI_BABEL_TRANSPILE_MODULES = true diff --git a/.env.production b/.env.production new file mode 100644 index 00000000..80c81030 --- /dev/null +++ b/.env.production @@ -0,0 +1,6 @@ +# just a flag +ENV = 'production' + +# base api +VUE_APP_BASE_API = '/prod-api' + diff --git a/.env.staging b/.env.staging new file mode 100644 index 00000000..a8793a09 --- /dev/null +++ b/.env.staging @@ -0,0 +1,8 @@ +NODE_ENV = production + +# just a flag +ENV = 'staging' + +# base api +VUE_APP_BASE_API = '/stage-api' + diff --git a/.eslintignore b/.eslintignore index e3a4037e..e6529fc0 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ build/*.js -config/*.js src/assets +public +dist diff --git a/.eslintrc.js b/.eslintrc.js index 1266f38d..c9775054 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -24,6 +24,7 @@ module.exports = { "vue/singleline-html-element-content-newline": "off", "vue/multiline-html-element-content-newline":"off", "vue/name-property-casing": ["error", "PascalCase"], + "vue/no-v-html": "off", 'accessor-pairs': 2, 'arrow-spacing': [2, { 'before': true, @@ -46,7 +47,7 @@ module.exports = { 'curly': [2, 'multi-line'], 'dot-location': [2, 'property'], 'eol-last': 2, - 'eqeqeq': [2, 'allow-null'], + 'eqeqeq': ["error", "always", {"null": "ignore"}], 'generator-star-spacing': [2, { 'before': true, 'after': true diff --git a/.gitignore b/.gitignore index 9322b8a6..78a752d8 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,8 @@ yarn-debug.log* yarn-error.log* **/*.log -test/unit/coverage -test/e2e/reports +tests/**/coverage/ +tests/e2e/reports selenium-debug.log # Editor directories and files @@ -17,5 +17,7 @@ selenium-debug.log *.ntvs* *.njsproj *.sln +*.local package-lock.json +yarn.lock diff --git a/.postcssrc.js b/.postcssrc.js index eee3e92d..09948d63 100644 --- a/.postcssrc.js +++ b/.postcssrc.js @@ -2,8 +2,6 @@ module.exports = { "plugins": { - "postcss-import": {}, - "postcss-url": {}, // to edit target browsers: use "browserslist" field in package.json "autoprefixer": {} } diff --git a/README.md b/README.md index 5f26a357..48599a7e 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,10 @@

- vue + vue - element-ui + element-ui Build Status @@ -30,48 +30,41 @@ English | [简体中文](./README.zh-CN.md) ## Introduction -[vue-element-admin](http://panjiachen.github.io/vue-element-admin) is a front-end management background integration solution. It based on [vue](https://github.com/vuejs/vue) and use the UI Toolkit [element](https://github.com/ElemeFE/element). +[vue-element-admin](http://panjiachen.github.io/vue-element-admin) is a production-ready front-end solution for admin interfaces. It based on [vue](https://github.com/vuejs/vue) and use the UI Toolkit [element-ui](https://github.com/ElemeFE/element). It is a magical vue admin based on the newest development stack of vue, built-in i18n solution, typical templates for enterprise applications, lots of awesome features. It helps you build a large complex Single-Page Applications. I believe whatever your needs are, this project will help you. -**[v4.0](https://github.com/PanJiaChen/vue-element-admin/tree/v4.0) has in beta. It built on vue-cli@3, optimized a lot of code and added a lot of new features. Welcome to use and make suggestions.** - - [Preview](http://panjiachen.github.io/vue-element-admin) - [Documentation](https://panjiachen.github.io/vue-element-admin-site/) - [Gitter](https://gitter.im/vue-element-admin/discuss) -- [Wiki](https://github.com/PanJiaChen/vue-element-admin/wiki) - - [Donate](https://panjiachen.github.io/vue-element-admin-site/donate/) -- [Gitee](https://panjiachen.gitee.io/vue-element-admin/) 国内用户可访问该地址在线预览 +- [Wiki](https://github.com/PanJiaChen/vue-element-admin/wiki) -**This project is positioned as a background integration solution and is not suitable for secondary development as a basic template.** +- [Gitee](https://panjiachen.gitee.io/vue-element-admin/) 国内用户可访问该地址在线预览 - Base template recommends using: [vue-admin-template](https://github.com/PanJiaChen/vue-admin-template) - Desktop: [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin) - Typescript: [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) (Credits: [@Armour](https://github.com/Armour)) -**This project does not support low version browsers (e.g. IE). Please add polyfill yourself if you need them.** - -**Note: This project uses element-ui@2.3.0+ version, so the minimum compatible vue@2.5.0+** +**The current version is `4.0-beta`. If you find a problem, please put [issue](https://github.com/PanJiaChen/vue-element-admin/issues/new). If you want to use the old version - stable version, you can switch branch to [tag/3.11.0](https://github.com/PanJiaChen/vue-element-admin/tree/tag/3.11.0)** -**Start using `webpack4` from `v3.8.0`. If you still want to continue using `webpack3`, please use this branch [webpack3](https://github.com/PanJiaChen/vue-element-admin/tree/webpack3)** +**This project does not support low version browsers (e.g. IE). Please add polyfill by yourself.** ## Preparation -You need to install [node](http://nodejs.org/) and [git](https://git-scm.com/) locally. The project is based on [ES2015+](http://es6.ruanyifeng.com/), [vue](https://cn.vuejs.org/index.html), [vuex](https://vuex.vuejs.org/zh-cn/), [vue-router](https://router.vuejs.org/zh-cn/), [axios](https://github.com/axios/axios) and [element-ui](https://github.com/ElemeFE/element), all request data is simulated using [Mock.js](https://github.com/nuysoft/Mock). +You need to install [node](http://nodejs.org/) and [git](https://git-scm.com/) locally. The project is based on [ES2015+](http://es6.ruanyifeng.com/), [vue](https://cn.vuejs.org/index.html), [vuex](https://vuex.vuejs.org/zh-cn/), [vue-router](https://router.vuejs.org/zh-cn/), [vue-cli](https://github.com/vuejs/vue-cli) , [axios](https://github.com/axios/axios) and [element-ui](https://github.com/ElemeFE/element), all request data is simulated using [Mock.js](https://github.com/nuysoft/Mock). Understanding and learning this knowledge in advance will greatly help the use of this project. ---- -

## Sponsors + Become a sponsor and get your logo on our README on GitHub with a link to your site. [[Become a sponsor]](https://www.patreon.com/panjiachen)

Admin Dashboard Templates made with Vue, React and Angular.

@@ -84,6 +77,7 @@ Become a sponsor and get your logo on our README on GitHub with a link to your s - Permission Authentication - Page permission - Directive permission + - Permission configuration page - Two-step login - Multi-environment build @@ -107,14 +101,13 @@ Become a sponsor and get your logo on our README on GitHub with a link to your s - Excel - Export Excel - - Export zip - Upload Excel - Visualization Excel + - Export zip - Table - Dynamic Table - Drag And Drop Table - - Tree Table - Inline Edit Table - Error Page @@ -148,6 +141,9 @@ Become a sponsor and get your logo on our README on GitHub with a link to your s # clone the project git clone https://github.com/PanJiaChen/vue-element-admin.git +# enter the project directory +cd vue-element-admin + # install dependency npm install @@ -155,13 +151,13 @@ npm install npm run dev ``` -This will automatically open http://localhost:9527. +This will automatically open http://localhost:9527 ## Build ```bash # build for test environment -npm run build:sit +npm run build:stage # build for production environment npm run build:prod @@ -170,19 +166,16 @@ npm run build:prod ## Advanced ```bash -# --report to build with bundle size analytics -npm run build:prod --report - -# --generate a bundle size analytics. default: bundle-report.html -npm run build:prod --generate_report +# preview the release environment effect +npm run preview -# --preview to start a server in local to preview -npm run build:prod --preview +# preview the release environment effect + static resource analysis +npm run preview -- --report -# lint code +# code format check npm run lint -# auto fix +# code format check and auto fix npm run lint -- --fix ``` diff --git a/README.zh-CN.md b/README.zh-CN.md index 18d4aca3..e0ec09bb 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -4,10 +4,10 @@

- vue + vue - element-ui + element-ui Build Status @@ -30,43 +30,37 @@ ## 简介 -[vue-element-admin](http://panjiachen.github.io/vue-element-admin) 是一个后台集成解决方案,它基于 [vue](https://github.com/vuejs/vue) 和 [element](https://github.com/ElemeFE/element)。它使用了最新的前端技术栈,内置了 i18n 国际化解决方案,动态路由,权限验证,提炼了典型的业务模型,提供了丰富的功能组件,它可以帮助你快速搭建企业级中后台产品原型。相信不管你的需求是什么,本项目都能帮助到你。 +[vue-element-admin](http://panjiachen.github.io/vue-element-admin) 是一个后台前端解决方案,它基于 [vue](https://github.com/vuejs/vue) 和 [element-ui](https://github.com/ElemeFE/element)实现。它使用了最新的前端技术栈,内置了 i18n 国际化解决方案,动态路由,权限验证,提炼了典型的业务模型,提供了丰富的功能组件,它可以帮助你快速搭建企业级中后台产品原型。相信不管你的需求是什么,本项目都能帮助到你。 -**[v4.0](https://github.com/PanJiaChen/vue-element-admin/tree/v4.0) 已经进入 beta 测试阶段。 它基于 vue-cli@3 进行构建,优化了大量代码(尤其是权限和 mock),并且增加了不少新特性。欢迎使用并提出建议。** - -- [在线访问](http://panjiachen.github.io/vue-element-admin) +- [在线预览](http://panjiachen.github.io/vue-element-admin) - [使用文档](https://panjiachen.github.io/vue-element-admin-site/zh/) - [Gitter 讨论组](https://gitter.im/vue-element-admin/discuss) -- [Wiki](https://github.com/PanJiaChen/vue-element-admin/wiki) - - [Donate](https://panjiachen.gitee.io/vue-element-admin-site/zh/donate) -- [Gitee](https://panjiachen.gitee.io/vue-element-admin/) 国内用户可访问该地址在线预览 +- [Wiki](https://github.com/PanJiaChen/vue-element-admin/wiki) -- [国内访问文档](https://panjiachen.gitee.io/vue-element-admin-site/zh/) 方便没翻墙的用户查看文档 +- [Gitee](https://panjiachen.gitee.io/vue-element-admin/) 在线预览(国内用户可访问该地址) -**本项目的定位是后台集成方案,不适合当基础模板来开发。** +- [国内访问文档](https://panjiachen.gitee.io/vue-element-admin-site/zh/) 文档(方便没翻墙的用户查看) -- 模板建议使用: [vue-admin-template](https://github.com/PanJiaChen/vue-admin-template) +- 基础模板建议使用: [vue-admin-template](https://github.com/PanJiaChen/vue-admin-template) - 桌面端: [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin) -- Typescript版: [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) (鸣谢: [@Armour](https://github.com/Armour)) +- Typescript 版: [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) (鸣谢: [@Armour](https://github.com/Armour)) -群主 **[圈子](https://jianshiapp.com/circles/1209)** 楼主会经常分享一些技术相关的东西,或者加入[qq 群](https://github.com/PanJiaChen/vue-element-admin/issues/602)或者关注[微博](https://weibo.com/u/3423485724?is_all=1) - -**注意:该项目使用 element-ui@2.3.0+ 版本,所以最低兼容 vue@2.5.0+** - -**从`v3.8.0`开始使用`webpack4`。所以若还想使用`webpack3`开发,请使用该分支[webpack3](https://github.com/PanJiaChen/vue-element-admin/tree/webpack3)** +**目前版本为 `4.0-beta`,若发现问题,欢迎提[issue](https://github.com/PanJiaChen/vue-element-admin/issues/new)。若你想使用旧版本-稳定版,可以切换分支到[tag/3.11.0](https://github.com/PanJiaChen/vue-element-admin/tree/tag/3.11.0)** **该项目不支持低版本浏览器(如 ie),有需求请自行添加 polyfill [详情](https://github.com/PanJiaChen/vue-element-admin/wiki#babel-polyfill)** +群主 **[圈子](https://jianshiapp.com/circles/1209)** 群主会经常分享一些技术相关的东西,或者加入 [qq 群](https://github.com/PanJiaChen/vue-element-admin/issues/602) 或者关注 [微博](https://weibo.com/u/3423485724?is_all=1) + ## 前序准备 -你需要在本地安装 [node](http://nodejs.org/) 和 [git](https://git-scm.com/)。本项目技术栈基于 [ES2015+](http://es6.ruanyifeng.com/)、[vue](https://cn.vuejs.org/index.html)、[vuex](https://vuex.vuejs.org/zh-cn/)、[vue-router](https://router.vuejs.org/zh-cn/) 、[axios](https://github.com/axios/axios) 和 [element-ui](https://github.com/ElemeFE/element),所有的请求数据都使用[Mock.js](https://github.com/nuysoft/Mock)模拟,提前了解和学习这些知识会对使用本项目有很大的帮助。 +你需要在本地安装 [node](http://nodejs.org/) 和 [git](https://git-scm.com/)。本项目技术栈基于 [ES2015+](http://es6.ruanyifeng.com/)、[vue](https://cn.vuejs.org/index.html)、[vuex](https://vuex.vuejs.org/zh-cn/)、[vue-router](https://router.vuejs.org/zh-cn/) 、[vue-cli](https://github.com/vuejs/vue-cli) 、[axios](https://github.com/axios/axios) 和 [element-ui](https://github.com/ElemeFE/element),所有的请求数据都使用[Mock.js](https://github.com/nuysoft/Mock)进行模拟,提前了解和学习这些知识会对使用本项目有很大的帮助。 -同时配套一个系列的教程文章,如何从零构建后一个完整的后台项目,建议大家先看完这些文章再来实践本项目 +同时配套了系列教程文章,如何从零构建后一个完整的后台项目,建议大家先看完这些文章再来实践本项目 - [手摸手,带你用 vue 撸后台 系列一(基础篇)](https://juejin.im/post/59097cd7a22b9d0065fb61d2) - [手摸手,带你用 vue 撸后台 系列二(登录权限篇)](https://juejin.im/post/591aa14f570c35006961acac) @@ -84,6 +78,7 @@

## Sponsors + Become a sponsor and get your logo on our README on GitHub with a link to your site. [[Become a sponsor]](https://www.patreon.com/panjiachen)

Admin Dashboard Templates made with Vue, React and Angular.

@@ -96,6 +91,7 @@ Become a sponsor and get your logo on our README on GitHub with a link to your s - 权限验证 - 页面权限 - 指令权限 + - 权限配置 - 二步登录 - 多环境发布 @@ -108,7 +104,7 @@ Become a sponsor and get your logo on our README on GitHub with a link to your s - 动态面包屑 - 快捷导航(标签页) - Svg Sprite 图标 - - 本地mock数据 + - 本地/后端 mock 数据 - Screenfull全屏 - 自适应收缩侧边栏 @@ -119,14 +115,13 @@ Become a sponsor and get your logo on our README on GitHub with a link to your s - Excel - 导出excel - - 导出zip - 导入excel - 前端可视化excel + - 导出zip - 表格 - 动态表格 - 拖拽表格 - - 树形表格 - 内联编辑 - 错误页面 @@ -160,6 +155,9 @@ Become a sponsor and get your logo on our README on GitHub with a link to your s # 克隆项目 git clone https://github.com/PanJiaChen/vue-element-admin.git +# 进入项目目录 +cd vue-element-admin + # 安装依赖 npm install @@ -176,7 +174,7 @@ npm run dev ```bash # 构建测试环境 -npm run build:sit +npm run build:stage # 构建生产环境 npm run build:prod @@ -185,19 +183,16 @@ npm run build:prod ## 其它 ```bash -# --report to build with bundle size analytics -npm run build:prod - -# --generate a bundle size analytics. default: bundle-report.html -npm run build:prod --generate_report +# 预览发布环境效果 +npm run preview -# --preview to start a server in local to preview -npm run build:prod --preview +# 预览发布环境效果 + 静态资源分析 +npm run preview -- --report -# lint code +# 代码格式检查 npm run lint -# auto fix +# 代码格式检查并自动修复 npm run lint -- --fix ``` @@ -220,6 +215,8 @@ Detailed changes for each release are documented in the [release notes](https:// [Paypal Me](https://www.paypal.me/panfree23) +[Buy me a coffee](https://www.buymeacoffee.com/Pan) + ## Browsers support Modern browsers and Internet Explorer 10+. diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 00000000..ba179669 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,5 @@ +module.exports = { + presets: [ + '@vue/app' + ] +} diff --git a/build/build.js b/build/build.js deleted file mode 100644 index 34c71a55..00000000 --- a/build/build.js +++ /dev/null @@ -1,67 +0,0 @@ -'use strict' -require('./check-versions')() - -const ora = require('ora') -const rm = require('rimraf') -const path = require('path') -const chalk = require('chalk') -const webpack = require('webpack') -const config = require('../config') -const webpackConfig = require('./webpack.prod.conf') -var connect = require('connect') -var serveStatic = require('serve-static') - -const spinner = ora( - 'building for ' + process.env.env_config + ' environment...' -) -spinner.start() - -rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { - if (err) throw err - webpack(webpackConfig, (err, stats) => { - spinner.stop() - if (err) throw err - process.stdout.write( - stats.toString({ - colors: true, - modules: false, - children: false, - chunks: false, - chunkModules: false - }) + '\n\n' - ) - - if (stats.hasErrors()) { - console.log(chalk.red(' Build failed with errors.\n')) - process.exit(1) - } - - console.log(chalk.cyan(' Build complete.\n')) - console.log( - chalk.yellow( - ' Tip: built files are meant to be served over an HTTP server.\n' + - " Opening index.html over file:// won't work.\n" - ) - ) - - if (process.env.npm_config_preview) { - const port = 9526 - const host = 'http://localhost:' + port - const basePath = config.build.assetsPublicPath - const app = connect() - - app.use( - basePath, - serveStatic('./dist', { - index: ['index.html', '/'] - }) - ) - - app.listen(port, function() { - console.log( - chalk.green(`> Listening at http://localhost:${port}${basePath}`) - ) - }) - } - }) -}) diff --git a/build/check-versions.js b/build/check-versions.js deleted file mode 100644 index c5c29e90..00000000 --- a/build/check-versions.js +++ /dev/null @@ -1,64 +0,0 @@ -'use strict' -const chalk = require('chalk') -const semver = require('semver') -const packageConfig = require('../package.json') -const shell = require('shelljs') - -function exec(cmd) { - return require('child_process') - .execSync(cmd) - .toString() - .trim() -} - -const versionRequirements = [ - { - name: 'node', - currentVersion: semver.clean(process.version), - versionRequirement: packageConfig.engines.node - } -] - -if (shell.which('npm')) { - versionRequirements.push({ - name: 'npm', - currentVersion: exec('npm --version'), - versionRequirement: packageConfig.engines.npm - }) -} - -module.exports = function() { - const warnings = [] - - for (let i = 0; i < versionRequirements.length; i++) { - const mod = versionRequirements[i] - - if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { - warnings.push( - mod.name + - ': ' + - chalk.red(mod.currentVersion) + - ' should be ' + - chalk.green(mod.versionRequirement) - ) - } - } - - if (warnings.length) { - console.log('') - console.log( - chalk.yellow( - 'To use this template, you must update following to modules:' - ) - ) - console.log() - - for (let i = 0; i < warnings.length; i++) { - const warning = warnings[i] - console.log(' ' + warning) - } - - console.log() - process.exit(1) - } -} diff --git a/build/index.js b/build/index.js new file mode 100644 index 00000000..baf3807a --- /dev/null +++ b/build/index.js @@ -0,0 +1,35 @@ +const { run } = require('runjs') +const chalk = require('chalk') +const config = require('../vue.config.js') +const rawArgv = process.argv.slice(2) +const args = rawArgv.join(' ') + +if (process.env.npm_config_preview || rawArgv.includes('--preview')) { + const report = rawArgv.includes('--report') + + run(`vue-cli-service build ${args}`) + + const port = 9526 + const publicPath = config.publicPath + + var connect = require('connect') + var serveStatic = require('serve-static') + const app = connect() + + app.use( + publicPath, + serveStatic('./dist', { + index: ['index.html', '/'] + }) + ) + + app.listen(port, function () { + console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`)) + if (report) { + console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}/report.html`)) + } + + }) +} else { + run(`vue-cli-service build ${args}`) +} diff --git a/build/logo.png b/build/logo.png deleted file mode 100644 index f3d2503f..00000000 Binary files a/build/logo.png and /dev/null differ diff --git a/build/utils.js b/build/utils.js deleted file mode 100644 index c96d0936..00000000 --- a/build/utils.js +++ /dev/null @@ -1,108 +0,0 @@ -'use strict' -const path = require('path') -const config = require('../config') -const MiniCssExtractPlugin = require('mini-css-extract-plugin') -const packageConfig = require('../package.json') - -exports.assetsPath = function(_path) { - const assetsSubDirectory = - process.env.NODE_ENV === 'production' - ? config.build.assetsSubDirectory - : config.dev.assetsSubDirectory - - return path.posix.join(assetsSubDirectory, _path) -} - -exports.cssLoaders = function(options) { - options = options || {} - - const cssLoader = { - loader: 'css-loader', - options: { - sourceMap: options.sourceMap - } - } - - const postcssLoader = { - loader: 'postcss-loader', - options: { - sourceMap: options.sourceMap - } - } - - // generate loader string to be used with extract text plugin - function generateLoaders(loader, loaderOptions) { - const loaders = [] - - // Extract CSS when that option is specified - // (which is the case during production build) - if (options.extract) { - loaders.push(MiniCssExtractPlugin.loader) - } else { - loaders.push('vue-style-loader') - } - - loaders.push(cssLoader) - - if (options.usePostCSS) { - loaders.push(postcssLoader) - } - - if (loader) { - loaders.push({ - loader: loader + '-loader', - options: Object.assign({}, loaderOptions, { - sourceMap: options.sourceMap - }) - }) - } - - return loaders - } - // https://vue-loader.vuejs.org/en/configurations/extract-css.html - return { - css: generateLoaders(), - postcss: generateLoaders(), - less: generateLoaders('less'), - sass: generateLoaders('sass', { - indentedSyntax: true - }), - scss: generateLoaders('sass'), - stylus: generateLoaders('stylus'), - styl: generateLoaders('stylus') - } -} - -// Generate loaders for standalone style files (outside of .vue) -exports.styleLoaders = function(options) { - const output = [] - const loaders = exports.cssLoaders(options) - - for (const extension in loaders) { - const loader = loaders[extension] - output.push({ - test: new RegExp('\\.' + extension + '$'), - use: loader - }) - } - - return output -} - -exports.createNotifierCallback = () => { - const notifier = require('node-notifier') - - return (severity, errors) => { - if (severity !== 'error') return - - const error = errors[0] - const filename = error.file && error.file.split('!').pop() - - notifier.notify({ - title: packageConfig.name, - message: severity + ': ' + error.name, - subtitle: filename || '', - icon: path.join(__dirname, 'logo.png') - }) - } -} diff --git a/build/vue-loader.conf.js b/build/vue-loader.conf.js deleted file mode 100644 index 5496c931..00000000 --- a/build/vue-loader.conf.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict' - -module.exports = { - //You can set the vue-loader configuration by yourself. -} diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js deleted file mode 100644 index 3b946b4b..00000000 --- a/build/webpack.base.conf.js +++ /dev/null @@ -1,107 +0,0 @@ -'use strict' -const path = require('path') -const utils = require('./utils') -const config = require('../config') -const { VueLoaderPlugin } = require('vue-loader') -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: { - '@': resolve('src') - } - }, - module: { - rules: [ - ...(config.dev.useEslint ? [createLintingRule()] : []), - { - test: /\.vue$/, - loader: 'vue-loader', - options: vueLoaderConfig - }, - { - test: /\.js$/, - loader: 'babel-loader?cacheDirectory', - include: [ - resolve('src'), - resolve('test'), - resolve('node_modules/webpack-dev-server/client') - ] - }, - { - test: /\.svg$/, - loader: 'svg-sprite-loader', - include: [resolve('src/icons')], - options: { - symbolId: 'icon-[name]' - } - }, - { - test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, - loader: 'url-loader', - exclude: [resolve('src/icons')], - options: { - limit: 10000, - name: utils.assetsPath('img/[name].[hash:7].[ext]') - } - }, - { - test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, - loader: 'url-loader', - options: { - limit: 10000, - name: utils.assetsPath('media/[name].[hash:7].[ext]') - } - }, - { - test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, - loader: 'url-loader', - options: { - limit: 10000, - name: utils.assetsPath('fonts/[name].[hash:7].[ext]') - } - } - ] - }, - plugins: [new VueLoaderPlugin()], - 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' - } -} diff --git a/build/webpack.dev.conf.js b/build/webpack.dev.conf.js deleted file mode 100644 index 26a5584a..00000000 --- a/build/webpack.dev.conf.js +++ /dev/null @@ -1,98 +0,0 @@ -'use strict' -const path = require('path') -const utils = require('./utils') -const webpack = require('webpack') -const config = require('../config') -const merge = require('webpack-merge') -const baseWebpackConfig = require('./webpack.base.conf') -const HtmlWebpackPlugin = require('html-webpack-plugin') -const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') -const portfinder = require('portfinder') - -function resolve(dir) { - return path.join(__dirname, '..', dir) -} - -const HOST = process.env.HOST -const PORT = process.env.PORT && Number(process.env.PORT) - -const devWebpackConfig = merge(baseWebpackConfig, { - mode: 'development', - module: { - rules: utils.styleLoaders({ - sourceMap: config.dev.cssSourceMap, - usePostCSS: true - }) - }, - // cheap-module-eval-source-map is faster for development - devtool: config.dev.devtool, - - // these devServer options should be customized in /config/index.js - devServer: { - clientLogLevel: 'warning', - historyApiFallback: true, - hot: true, - compress: true, - host: HOST || config.dev.host, - port: PORT || config.dev.port, - open: config.dev.autoOpenBrowser, - overlay: config.dev.errorOverlay - ? { warnings: false, errors: true } - : false, - publicPath: config.dev.assetsPublicPath, - proxy: config.dev.proxyTable, - quiet: true, // necessary for FriendlyErrorsPlugin - watchOptions: { - poll: config.dev.poll - } - }, - plugins: [ - new webpack.DefinePlugin({ - 'process.env': require('../config/dev.env') - }), - new webpack.HotModuleReplacementPlugin(), - // https://github.com/ampedandwired/html-webpack-plugin - new HtmlWebpackPlugin({ - filename: 'index.html', - template: 'index.html', - inject: true, - favicon: resolve('favicon.ico'), - title: 'vue-element-admin', - templateParameters: { - BASE_URL: config.dev.assetsPublicPath + config.dev.assetsSubDirectory, - }, - }), - ] -}) - -module.exports = new Promise((resolve, reject) => { - portfinder.basePort = process.env.PORT || config.dev.port - portfinder.getPort((err, port) => { - if (err) { - reject(err) - } else { - // publish the new Port, necessary for e2e tests - process.env.PORT = port - // add port to devServer config - devWebpackConfig.devServer.port = port - - // Add FriendlyErrorsPlugin - devWebpackConfig.plugins.push( - new FriendlyErrorsPlugin({ - compilationSuccessInfo: { - messages: [ - `Your application is running here: http://${ - devWebpackConfig.devServer.host - }:${port}` - ] - }, - onErrors: config.dev.notifyOnErrors - ? utils.createNotifierCallback() - : undefined - }) - ) - - resolve(devWebpackConfig) - } - }) -}) diff --git a/build/webpack.prod.conf.js b/build/webpack.prod.conf.js deleted file mode 100644 index 4f84e0c6..00000000 --- a/build/webpack.prod.conf.js +++ /dev/null @@ -1,187 +0,0 @@ -'use strict' -const path = require('path') -const utils = require('./utils') -const webpack = require('webpack') -const config = require('../config') -const merge = require('webpack-merge') -const baseWebpackConfig = require('./webpack.base.conf') -const CopyWebpackPlugin = require('copy-webpack-plugin') -const HtmlWebpackPlugin = require('html-webpack-plugin') -const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin') -const MiniCssExtractPlugin = require('mini-css-extract-plugin') -const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin') -const UglifyJsPlugin = require('uglifyjs-webpack-plugin') - -function resolve(dir) { - return path.join(__dirname, '..', dir) -} - -const env = require('../config/' + process.env.env_config + '.env') - -// For NamedChunksPlugin -const seen = new Set() -const nameLength = 4 - -const webpackConfig = merge(baseWebpackConfig, { - mode: 'production', - module: { - rules: utils.styleLoaders({ - sourceMap: config.build.productionSourceMap, - extract: true, - usePostCSS: true - }) - }, - devtool: config.build.productionSourceMap ? config.build.devtool : false, - output: { - path: config.build.assetsRoot, - filename: utils.assetsPath('js/[name].[chunkhash:8].js'), - chunkFilename: utils.assetsPath('js/[name].[chunkhash:8].js') - }, - plugins: [ - // http://vuejs.github.io/vue-loader/en/workflow/production.html - new webpack.DefinePlugin({ - 'process.env': env - }), - // extract css into its own file - new MiniCssExtractPlugin({ - filename: utils.assetsPath('css/[name].[contenthash:8].css'), - chunkFilename: utils.assetsPath('css/[name].[contenthash:8].css') - }), - // generate dist index.html with correct asset hash for caching. - // you can customize output by editing /index.html - // see https://github.com/ampedandwired/html-webpack-plugin - new HtmlWebpackPlugin({ - filename: config.build.index, - template: 'index.html', - inject: true, - favicon: resolve('favicon.ico'), - title: 'vue-element-admin', - templateParameters: { - BASE_URL: config.build.assetsPublicPath + config.build.assetsSubDirectory, - }, - minify: { - removeComments: true, - collapseWhitespace: true, - removeAttributeQuotes: true - // more options: - // https://github.com/kangax/html-minifier#options-quick-reference - } - // default sort mode uses toposort which cannot handle cyclic deps - // in certain cases, and in webpack 4, chunk order in HTML doesn't - // matter anyway - }), - new ScriptExtHtmlWebpackPlugin({ - //`runtime` must same as runtimeChunk name. default is `runtime` - inline: /runtime\..*\.js$/ - }), - // keep chunk.id stable when chunk has no name - new webpack.NamedChunksPlugin(chunk => { - if (chunk.name) { - return chunk.name - } - const modules = Array.from(chunk.modulesIterable) - if (modules.length > 1) { - const hash = require('hash-sum') - const joinedHash = hash(modules.map(m => m.id).join('_')) - let len = nameLength - while (seen.has(joinedHash.substr(0, len))) len++ - seen.add(joinedHash.substr(0, len)) - return `chunk-${joinedHash.substr(0, len)}` - } else { - return modules[0].id - } - }), - // keep module.id stable when vender modules does not change - new webpack.HashedModuleIdsPlugin(), - // copy custom static assets - new CopyWebpackPlugin([ - { - from: path.resolve(__dirname, '../static'), - to: config.build.assetsSubDirectory, - ignore: ['.*'] - } - ]) - ], - optimization: { - splitChunks: { - chunks: 'all', - cacheGroups: { - libs: { - name: 'chunk-libs', - test: /[\\/]node_modules[\\/]/, - priority: 10, - chunks: 'initial' // 只打包初始时依赖的第三方 - }, - elementUI: { - name: 'chunk-elementUI', // 单独将 elementUI 拆包 - priority: 20, // 权重要大于 libs 和 app 不然会被打包进 libs 或者 app - test: /[\\/]node_modules[\\/]element-ui[\\/]/ - }, - commons: { - name: 'chunk-commons', - test: resolve('src/components'), // 可自定义拓展你的规则 - minChunks: 3, // 最小公用次数 - priority: 5, - reuseExistingChunk: true - } - } - }, - runtimeChunk: 'single', - minimizer: [ - new UglifyJsPlugin({ - uglifyOptions: { - mangle: { - safari10: true - } - }, - sourceMap: config.build.productionSourceMap, - cache: true, - parallel: true - }), - // Compress extracted CSS. We are using this plugin so that possible - // duplicated CSS from different components can be deduped. - new OptimizeCSSAssetsPlugin() - ] - } -}) - -if (config.build.productionGzip) { - const CompressionWebpackPlugin = require('compression-webpack-plugin') - - webpackConfig.plugins.push( - new CompressionWebpackPlugin({ - algorithm: 'gzip', - test: new RegExp( - '\\.(' + config.build.productionGzipExtensions.join('|') + ')$' - ), - threshold: 10240, - minRatio: 0.8 - }) - ) -} - -if (config.build.generateAnalyzerReport || config.build.bundleAnalyzerReport) { - const BundleAnalyzerPlugin = require('webpack-bundle-analyzer') - .BundleAnalyzerPlugin - - if (config.build.bundleAnalyzerReport) { - webpackConfig.plugins.push( - new BundleAnalyzerPlugin({ - analyzerPort: 8080, - generateStatsFile: false - }) - ) - } - - if (config.build.generateAnalyzerReport) { - webpackConfig.plugins.push( - new BundleAnalyzerPlugin({ - analyzerMode: 'static', - reportFilename: 'bundle-report.html', - openAnalyzer: false - }) - ) - } -} - -module.exports = webpackConfig diff --git a/config/dev.env.js b/config/dev.env.js deleted file mode 100644 index 68ddea56..00000000 --- a/config/dev.env.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - NODE_ENV: '"development"', - ENV_CONFIG: '"dev"', - BASE_API: '"https://api-dev"' -} diff --git a/config/index.js b/config/index.js deleted file mode 100644 index 599e4a63..00000000 --- a/config/index.js +++ /dev/null @@ -1,88 +0,0 @@ -'use strict' -// Template version: 1.2.6 -// 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 - - // can be overwritten by process.env.HOST - // if you want dev by ip, please set host: '0.0.0.0' - host: 'localhost', - port: 9527, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined - autoOpenBrowser: true, - errorOverlay: true, - notifyOnErrors: false, - poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- - - // Use Eslint Loader? - // If true, your code will be linted during bundling and - // linting errors and warnings will be shown in the console. - useEslint: true, - // If true, eslint errors and warnings will also be shown in the error overlay - // in the browser. - showEslintErrorsInOverlay: false, - - /** - * Source Maps - */ - - // https://webpack.js.org/configuration/devtool/#development - devtool: 'cheap-source-map', - - // CSS Sourcemaps off by default because relative paths are "buggy" - // with this option, according to the CSS-Loader README - // (https://github.com/webpack/css-loader#sourcemaps) - // In our experience, they generally work as expected, - // just be aware of this issue when enabling this option. - cssSourceMap: false - }, - - build: { - // Template for index.html - index: path.resolve(__dirname, '../dist/index.html'), - - // Paths - assetsRoot: path.resolve(__dirname, '../dist'), - assetsSubDirectory: 'static', - - /** - * You can set by youself according to actual condition - * You will need to set this if you plan to deploy your site under a sub path, - * for example GitHub pages. If you plan to deploy your site to https://foo.github.io/bar/, - * then assetsPublicPath should be set to "/bar/". - * In most cases please use '/' !!! - */ - assetsPublicPath: '/', - - /** - * Source Maps - */ - productionSourceMap: false, - // https://webpack.js.org/configuration/devtool/#production - devtool: 'source-map', - - // Gzip off by default as many popular static hosts such as - // Surge or Netlify already gzip all static assets for you. - // Before setting to `true`, make sure to: - // npm install --save-dev compression-webpack-plugin - productionGzip: false, - productionGzipExtensions: ['js', 'css'], - - // Run the build command with an extra argument to - // View the bundle analyzer report after build finishes: - // `npm run build:prod --report` - // Set to `true` or `false` to always turn it on or off - bundleAnalyzerReport: process.env.npm_config_report || false, - - // `npm run build:prod --generate_report` - generateAnalyzerReport: process.env.npm_config_generate_report || false - } -} diff --git a/config/prod.env.js b/config/prod.env.js deleted file mode 100644 index bfcd6d27..00000000 --- a/config/prod.env.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - NODE_ENV: '"production"', - ENV_CONFIG: '"prod"', - BASE_API: '"https://api-prod"' -} diff --git a/config/sit.env.js b/config/sit.env.js deleted file mode 100644 index 93178e80..00000000 --- a/config/sit.env.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - NODE_ENV: '"production"', - ENV_CONFIG: '"sit"', - BASE_API: '"https://api-sit"' -} diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..1ce813e1 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,27 @@ +module.exports = { + verbose: true, + moduleFileExtensions: ['js', 'jsx', 'json', 'vue'], + transformIgnorePatterns: [ + 'node_modules/(?!(babel-jest|jest-vue-preprocessor)/)' + ], + transform: { + '^.+\\.vue$': 'vue-jest', + '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', + '^.+\\.jsx?$': 'babel-jest' + }, + moduleNameMapper: { + '^@/(.*)$': '/src/$1' + }, + snapshotSerializers: ['jest-serializer-vue'], + testMatch: [ + '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' + ], + collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'], + coverageDirectory: '/tests/unit/coverage', + // 'collectCoverage': true, + 'coverageReporters': [ + 'lcov', + 'text-summary' + ], + testURL: 'http://localhost/' +} diff --git a/mock/article.js b/mock/article.js new file mode 100644 index 00000000..45b75296 --- /dev/null +++ b/mock/article.js @@ -0,0 +1,116 @@ +import Mock from 'mockjs' + +const List = [] +const count = 100 + +const baseContent = '

我是测试数据我是测试数据

' +const image_uri = 'https://wpimg.wallstcn.com/e4558086-631c-425c-9430-56ffb46e70b3' + +for (let i = 0; i < count; i++) { + List.push(Mock.mock({ + id: '@increment', + timestamp: +Mock.Random.date('T'), + author: '@first', + reviewer: '@first', + title: '@title(5, 10)', + content_short: 'mock data', + content: baseContent, + forecast: '@float(0, 100, 2, 2)', + importance: '@integer(1, 3)', + 'type|1': ['CN', 'US', 'JP', 'EU'], + 'status|1': ['published', 'draft', 'deleted'], + display_time: '@datetime', + comment_disabled: true, + pageviews: '@integer(300, 5000)', + image_uri, + platforms: ['a-platform'] + })) +} + +export default [ + { + url: '/article/list', + type: 'get', + response: config => { + const { importance, type, title, page = 1, limit = 20, sort } = config.query + + let mockList = List.filter(item => { + if (importance && item.importance !== +importance) return false + if (type && item.type !== type) return false + if (title && item.title.indexOf(title) < 0) return false + return true + }) + + if (sort === '-id') { + mockList = mockList.reverse() + } + + const pageList = mockList.filter((item, index) => index < limit * page && index >= limit * (page - 1)) + + return { + code: 20000, + data: { + total: mockList.length, + items: pageList + } + } + } + }, + + { + url: '/article/detail', + type: 'get', + response: config => { + const { id } = config.query + for (const article of List) { + if (article.id === +id) { + return { + code: 20000, + data: article + } + } + } + } + }, + + { + url: '/article/pv', + type: 'get', + response: _ => { + return { + code: 20000, + data: { + pvData: [ + { key: 'PC', pv: 1024 }, + { key: 'mobile', pv: 1024 }, + { key: 'ios', pv: 1024 }, + { key: 'android', pv: 1024 } + ] + } + } + } + }, + + { + url: '/article/create', + type: 'post', + response: _ => { + return { + code: 20000, + data: 'success' + } + } + }, + + { + url: '/article/update', + type: 'post', + response: _ => { + return { + code: 20000, + data: 'success' + } + } + } +] + diff --git a/mock/index.js b/mock/index.js new file mode 100644 index 00000000..15e07afa --- /dev/null +++ b/mock/index.js @@ -0,0 +1,54 @@ +import Mock from 'mockjs' +import mocks from './mocks' +import { param2Obj } from '../src/utils' + +const MOCK_API_BASE = '/mock' + +export function mockXHR() { + // 修复在使用 MockJS 情况下,设置 withCredentials = true,且未被拦截的跨域请求丢失 Cookies 的问题 + // https://github.com/nuysoft/Mock/issues/300 + Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send + Mock.XHR.prototype.send = function() { + if (this.custom.xhr) { + this.custom.xhr.withCredentials = this.withCredentials || false + this.custom.xhr.responseType = this.responseType + } + this.proxy_send(...arguments) + } + + function XHR2ExpressReqWrap(respond) { + return function(options) { + let result = null + if (respond instanceof Function) { + const { body, type, url } = options + // https://expressjs.com/en/4x/api.html#req + result = respond({ + method: type, + body: JSON.parse(body), + query: param2Obj(url) + }) + } else { + result = respond + } + return Mock.mock(result) + } + } + + for (const i of mocks) { + Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response)) + } +} + +const responseFake = (url, type, respond) => { + return { + url: new RegExp(`${MOCK_API_BASE}${url}`), + type: type || 'get', + response(req, res) { + res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond)) + } + } +} + +export default mocks.map(route => { + return responseFake(route.url, route.type, route.response) +}) diff --git a/mock/mocks.js b/mock/mocks.js new file mode 100644 index 00000000..84a25ddc --- /dev/null +++ b/mock/mocks.js @@ -0,0 +1,12 @@ +import user from './user' +import role from './role' +import article from './article' +import search from './remoteSearch' + +export default [ + ...user, + ...role, + ...article, + ...search +] + diff --git a/mock/remoteSearch.js b/mock/remoteSearch.js new file mode 100644 index 00000000..bb33c2f4 --- /dev/null +++ b/mock/remoteSearch.js @@ -0,0 +1,51 @@ +import Mock from 'mockjs' + +const NameList = [] +const count = 100 + +for (let i = 0; i < count; i++) { + NameList.push(Mock.mock({ + name: '@first' + })) +} +NameList.push({ name: 'mock-Pan' }) + +export default [ + // username search + { + url: '/search/user', + type: 'get', + response: config => { + const { name } = config.query + const mockNameList = NameList.filter(item => { + const lowerCaseName = item.name.toLowerCase() + return !(name && lowerCaseName.indexOf(name.toLowerCase()) < 0) + }) + return { + code: 20000, + data: { items: mockNameList } + } + } + }, + + // transaction list + { + url: '/transaction/list', + type: 'get', + response: _ => { + return { + code: 20000, + data: { + total: 20, + 'items|20': [{ + order_no: '@guid()', + timestamp: +Mock.Random.date('T'), + username: '@name()', + price: '@float(1000, 15000, 0, 2)', + 'status|1': ['success', 'pending'] + }] + } + } + } + } +] diff --git a/mock/role/index.js b/mock/role/index.js new file mode 100644 index 00000000..39148076 --- /dev/null +++ b/mock/role/index.js @@ -0,0 +1,98 @@ +import Mock from 'mockjs' +import { deepClone } from '../../src/utils/index.js' +import { asyncRoutes, constantRoutes } from './routes.js' + +const routes = deepClone([...constantRoutes, ...asyncRoutes]) + +const roles = [ + { + key: 'admin', + name: 'admin', + description: 'Super Administrator. Have access to view all pages.', + routes: routes + }, + { + key: 'editor', + name: 'editor', + description: 'Normal Editor. Can see all pages except permission page', + routes: routes.filter(i => i.path !== '/permission')// just a mock + }, + { + key: 'visitor', + name: 'visitor', + description: 'Just a visitor. Can only see the home page and the document page', + routes: [{ + path: '', + redirect: 'dashboard', + children: [ + { + path: 'dashboard', + name: 'Dashboard', + meta: { title: 'dashboard', icon: 'dashboard' } + } + ] + }] + } +] + +export default [ + // mock get all routes form server + { + url: '/routes', + type: 'get', + response: _ => { + return { + code: 20000, + data: routes + } + } + }, + + // mock get all roles form server + { + url: '/roles', + type: 'get', + response: _ => { + return { + code: 20000, + data: roles + } + } + }, + + // add role + { + url: '/role', + type: 'post', + response: { + code: 20000, + data: { + key: Mock.mock('@integer(300, 5000)') + } + } + }, + + // update role + { + url: '/role/[A-Za-z0-9]', + type: 'put', + response: { + code: 20000, + data: { + status: 'success' + } + } + }, + + // delete role + { + url: '/role/[A-Za-z0-9]', + type: 'delete', + response: { + code: 20000, + data: { + status: 'success' + } + } + } +] diff --git a/mock/role/routes.js b/mock/role/routes.js new file mode 100644 index 00000000..d8eaf42a --- /dev/null +++ b/mock/role/routes.js @@ -0,0 +1,525 @@ +// Just a mock data + +export const constantRoutes = [ + { + path: '/redirect', + component: 'layout/Layout', + hidden: true, + children: [ + { + path: '/redirect/:path*', + component: 'views/redirect/index' + } + ] + }, + { + path: '/login', + component: 'views/login/index', + hidden: true + }, + { + path: '/auth-redirect', + component: 'views/login/authredirect', + hidden: true + }, + { + path: '/404', + component: 'views/errorPage/404', + hidden: true + }, + { + path: '/401', + component: 'views/errorPage/401', + hidden: true + }, + { + path: '', + component: 'layout/Layout', + redirect: 'dashboard', + children: [ + { + path: 'dashboard', + component: 'views/dashboard/index', + name: 'Dashboard', + meta: { title: 'dashboard', icon: 'dashboard', noCache: true, affix: true } + } + ] + }, + { + path: '/documentation', + component: 'layout/Layout', + children: [ + { + path: 'index', + component: 'views/documentation/index', + name: 'Documentation', + meta: { title: 'documentation', icon: 'documentation', affix: true } + } + ] + }, + { + path: '/guide', + component: 'layout/Layout', + redirect: '/guide/index', + children: [ + { + path: 'index', + component: 'views/guide/index', + name: 'Guide', + meta: { title: 'guide', icon: 'guide', noCache: true } + } + ] + } +] + +export const asyncRoutes = [ + { + path: '/permission', + component: 'layout/Layout', + redirect: '/permission/index', + alwaysShow: true, + meta: { + title: 'permission', + icon: 'lock', + roles: ['admin', 'editor'] + }, + children: [ + { + path: 'page', + component: 'views/permission/page', + name: 'PagePermission', + meta: { + title: 'pagePermission', + roles: ['admin'] + } + }, + { + path: 'directive', + component: 'views/permission/directive', + name: 'DirectivePermission', + meta: { + title: 'directivePermission' + } + }, + { + path: 'role', + component: 'views/permission/role', + name: 'RolePermission', + meta: { + title: 'rolePermission', + roles: ['admin'] + } + } + ] + }, + + { + path: '/icon', + component: 'layout/Layout', + children: [ + { + path: 'index', + component: 'views/svg-icons/index', + name: 'Icons', + meta: { title: 'icons', icon: 'icon', noCache: true } + } + ] + }, + + { + path: '/components', + component: 'layout/Layout', + redirect: 'noredirect', + name: 'ComponentDemo', + meta: { + title: 'components', + icon: 'component' + }, + children: [ + { + path: 'tinymce', + component: 'views/components-demo/tinymce', + name: 'TinymceDemo', + meta: { title: 'tinymce' } + }, + { + path: 'markdown', + component: 'views/components-demo/markdown', + name: 'MarkdownDemo', + meta: { title: 'markdown' } + }, + { + path: 'json-editor', + component: 'views/components-demo/jsonEditor', + name: 'JsonEditorDemo', + meta: { title: 'jsonEditor' } + }, + { + path: 'splitpane', + component: 'views/components-demo/splitpane', + name: 'SplitpaneDemo', + meta: { title: 'splitPane' } + }, + { + path: 'avatar-upload', + component: 'views/components-demo/avatarUpload', + name: 'AvatarUploadDemo', + meta: { title: 'avatarUpload' } + }, + { + path: 'dropzone', + component: 'views/components-demo/dropzone', + name: 'DropzoneDemo', + meta: { title: 'dropzone' } + }, + { + path: 'sticky', + component: 'views/components-demo/sticky', + name: 'StickyDemo', + meta: { title: 'sticky' } + }, + { + path: 'count-to', + component: 'views/components-demo/countTo', + name: 'CountToDemo', + meta: { title: 'countTo' } + }, + { + path: 'mixin', + component: 'views/components-demo/mixin', + name: 'ComponentMixinDemo', + meta: { title: 'componentMixin' } + }, + { + path: 'back-to-top', + component: 'views/components-demo/backToTop', + name: 'BackToTopDemo', + meta: { title: 'backToTop' } + }, + { + path: 'drag-dialog', + component: 'views/components-demo/dragDialog', + name: 'DragDialogDemo', + meta: { title: 'dragDialog' } + }, + { + path: 'drag-select', + component: 'views/components-demo/dragSelect', + name: 'DragSelectDemo', + meta: { title: 'dragSelect' } + }, + { + path: 'dnd-list', + component: 'views/components-demo/dndList', + name: 'DndListDemo', + meta: { title: 'dndList' } + }, + { + path: 'drag-kanban', + component: 'views/components-demo/dragKanban', + name: 'DragKanbanDemo', + meta: { title: 'dragKanban' } + } + ] + }, + { + path: '/charts', + component: 'layout/Layout', + redirect: 'noredirect', + name: 'Charts', + meta: { + title: 'charts', + icon: 'chart' + }, + children: [ + { + path: 'keyboard', + component: 'views/charts/keyboard', + name: 'KeyboardChart', + meta: { title: 'keyboardChart', noCache: true } + }, + { + path: 'line', + component: 'views/charts/line', + name: 'LineChart', + meta: { title: 'lineChart', noCache: true } + }, + { + path: 'mixchart', + component: 'views/charts/mixChart', + name: 'MixChart', + meta: { title: 'mixChart', noCache: true } + } + ] + }, + { + path: '/nested', + component: 'layout/Layout', + redirect: '/nested/menu1/menu1-1', + name: 'Nested', + meta: { + title: 'nested', + icon: 'nested' + }, + children: [ + { + path: 'menu1', + component: 'views/nested/menu1/index', + name: 'Menu1', + meta: { title: 'menu1' }, + redirect: '/nested/menu1/menu1-1', + children: [ + { + path: 'menu1-1', + component: 'views/nested/menu1/menu1-1', + name: 'Menu1-1', + meta: { title: 'menu1-1' } + }, + { + path: 'menu1-2', + component: 'views/nested/menu1/menu1-2', + name: 'Menu1-2', + redirect: '/nested/menu1/menu1-2/menu1-2-1', + meta: { title: 'menu1-2' }, + children: [ + { + path: 'menu1-2-1', + component: 'views/nested/menu1/menu1-2/menu1-2-1', + name: 'Menu1-2-1', + meta: { title: 'menu1-2-1' } + }, + { + path: 'menu1-2-2', + component: 'views/nested/menu1/menu1-2/menu1-2-2', + name: 'Menu1-2-2', + meta: { title: 'menu1-2-2' } + } + ] + }, + { + path: 'menu1-3', + component: 'views/nested/menu1/menu1-3', + name: 'Menu1-3', + meta: { title: 'menu1-3' } + } + ] + }, + { + path: 'menu2', + name: 'Menu2', + component: 'views/nested/menu2/index', + meta: { title: 'menu2' } + } + ] + }, + + { + path: '/example', + component: 'layout/Layout', + redirect: '/example/list', + name: 'Example', + meta: { + title: 'example', + icon: 'example' + }, + children: [ + { + path: 'create', + component: 'views/example/create', + name: 'CreateArticle', + meta: { title: 'createArticle', icon: 'edit' } + }, + { + path: 'edit/:id(\\d+)', + component: 'views/example/edit', + name: 'EditArticle', + meta: { title: 'editArticle', noCache: true }, + hidden: true + }, + { + path: 'list', + component: 'views/example/list', + name: 'ArticleList', + meta: { title: 'articleList', icon: 'list' } + } + ] + }, + + { + path: '/tab', + component: 'layout/Layout', + children: [ + { + path: 'index', + component: 'views/tab/index', + name: 'Tab', + meta: { title: 'tab', icon: 'tab' } + } + ] + }, + + { + path: '/error', + component: 'layout/Layout', + redirect: 'noredirect', + name: 'ErrorPages', + meta: { + title: 'errorPages', + icon: '404' + }, + children: [ + { + path: '401', + component: 'views/errorPage/401', + name: 'Page401', + meta: { title: 'page401', noCache: true } + }, + { + path: '404', + component: 'views/errorPage/404', + name: 'Page404', + meta: { title: 'page404', noCache: true } + } + ] + }, + + { + path: '/error-log', + component: 'layout/Layout', + redirect: 'noredirect', + children: [ + { + path: 'log', + component: 'views/errorLog/index', + name: 'ErrorLog', + meta: { title: 'errorLog', icon: 'bug' } + } + ] + }, + + { + path: '/excel', + component: 'layout/Layout', + redirect: '/excel/export-excel', + name: 'Excel', + meta: { + title: 'excel', + icon: 'excel' + }, + children: [ + { + path: 'export-excel', + component: 'views/excel/exportExcel', + name: 'ExportExcel', + meta: { title: 'exportExcel' } + }, + { + path: 'export-selected-excel', + component: 'views/excel/selectExcel', + name: 'SelectExcel', + meta: { title: 'selectExcel' } + }, + { + path: 'export-merge-header', + component: 'views/excel/mergeHeader', + name: 'MergeHeader', + meta: { title: 'mergeHeader' } + }, + { + path: 'upload-excel', + component: 'views/excel/uploadExcel', + name: 'UploadExcel', + meta: { title: 'uploadExcel' } + } + ] + }, + + { + path: '/zip', + component: 'layout/Layout', + redirect: '/zip/download', + alwaysShow: true, + meta: { title: 'zip', icon: 'zip' }, + children: [ + { + path: 'download', + component: 'views/zip/index', + name: 'ExportZip', + meta: { title: 'exportZip' } + } + ] + }, + + { + path: '/pdf', + component: 'layout/Layout', + redirect: '/pdf/index', + children: [ + { + path: 'index', + component: 'views/pdf/index', + name: 'PDF', + meta: { title: 'pdf', icon: 'pdf' } + } + ] + }, + { + path: '/pdf/download', + component: 'views/pdf/download', + hidden: true + }, + + { + path: '/theme', + component: 'layout/Layout', + redirect: 'noredirect', + children: [ + { + path: 'index', + component: 'views/theme/index', + name: 'Theme', + meta: { title: 'theme', icon: 'theme' } + } + ] + }, + + { + path: '/clipboard', + component: 'layout/Layout', + redirect: 'noredirect', + children: [ + { + path: 'index', + component: 'views/clipboard/index', + name: 'ClipboardDemo', + meta: { title: 'clipboardDemo', icon: 'clipboard' } + } + ] + }, + + { + path: '/i18n', + component: 'layout/Layout', + children: [ + { + path: 'index', + component: 'views/i18n-demo/index', + name: 'I18n', + meta: { title: 'i18n', icon: 'international' } + } + ] + }, + + { + path: 'external-link', + component: 'layout/Layout', + children: [ + { + path: 'https://github.com/PanJiaChen/vue-element-admin', + meta: { title: 'externalLink', icon: 'link' } + } + ] + }, + + { path: '*', redirect: '/404', hidden: true } +] diff --git a/mock/user.js b/mock/user.js new file mode 100644 index 00000000..43f93a04 --- /dev/null +++ b/mock/user.js @@ -0,0 +1,84 @@ + +const tokens = { + admin: { + token: 'admin-token' + }, + editor: { + token: 'editor-token' + } +} + +const users = { + 'admin-token': { + roles: ['admin'], + introduction: 'I am a super administrator', + avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', + name: 'Super Admin' + }, + 'editor-token': { + roles: ['editor'], + introduction: 'I am an editor', + avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', + name: 'Normal Editor' + } +} + +export default [ + // user login + { + url: '/user/login', + type: 'post', + response: config => { + const { username } = config.body + const token = tokens[username] + + // mock error + if (!token) { + return { + code: 60204, + message: 'Account and password are incorrect.' + } + } + + return { + code: 20000, + data: token + } + } + }, + + // get user info + { + url: '/user/info\.*', + type: 'get', + response: config => { + const { token } = config.query + const info = users[token] + + // mock error + if (!info) { + return { + code: 50008, + message: 'Login failed, unable to get user details.' + } + } + + return { + code: 20000, + data: info + } + } + }, + + // user logout + { + url: '/user/logout', + type: 'post', + response: _ => { + return { + code: 20000, + data: 'success' + } + } + } +] diff --git a/package.json b/package.json index 86c334f2..12bf3ffc 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,24 @@ { "name": "vue-element-admin", - "version": "3.11.0", - "description": "A magical vue admin. Typical templates for enterprise applications. Newest development stack of vue. Lots of awesome features", + "version": "4.0.0", + "description": "A magical vue admin. An out-of-box UI solution for enterprise applications. Newest development stack of vue. Lots of awesome features", "author": "Pan ", "license": "MIT", "scripts": { - "dev": "cross-env BABEL_ENV=development webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", - "build:prod": "cross-env NODE_ENV=production env_config=prod node build/build.js", - "build:sit": "cross-env NODE_ENV=production env_config=sit node build/build.js", + "dev": "vue-cli-service serve", + "build:prod": "vue-cli-service build", + "build:stage": "vue-cli-service build --mode staging", + "preview": "node build/index.js --preview", "lint": "eslint --ext .js,.vue src", - "test": "npm run lint", - "precommit": "lint-staged", - "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml" + "test:unit": "vue-cli-service test:unit", + "test:ci": "npm run lint && npm run test:unit", + "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml", + "new": "plop" + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } }, "lint-staged": { "src/**/*.{js,vue}": [ @@ -21,10 +28,12 @@ }, "keywords": [ "vue", - "element-ui", "admin", - "management-system", - "admin-template" + "dashboard", + "element-ui", + "boilerplate", + "admin-template", + "management-system" ], "repository": { "type": "git", @@ -35,93 +44,64 @@ }, "dependencies": { "axios": "0.18.0", - "clipboard": "1.7.1", - "codemirror": "5.39.2", - "driver.js": "0.8.1", - "dropzone": "5.2.0", - "echarts": "4.1.0", - "element-ui": "2.4.11", - "file-saver": "1.3.8", - "fuse.js": "3.4.2", + "clipboard": "2.0.4", + "codemirror": "5.45.0", + "driver.js": "0.9.5", + "dropzone": "5.5.1", + "echarts": "4.2.1", + "element-ui": "2.7.0", + "file-saver": "2.0.1", + "fuse.js": "3.4.4", "js-cookie": "2.2.0", "jsonlint": "1.6.3", - "jszip": "3.1.5", - "mockjs": "1.0.1-beta3", + "jszip": "3.2.1", "normalize.css": "7.0.0", "nprogress": "0.2.0", - "screenfull": "4.0.0", - "showdown": "1.8.6", - "sortablejs": "1.7.0", - "tui-editor": "1.2.7", - "vue": "2.5.17", + "path-to-regexp": "2.4.0", + "screenfull": "4.2.0", + "showdown": "1.9.0", + "sortablejs": "1.8.4", + "tui-editor": "1.3.3", + "vue": "2.6.10", "vue-count-to": "1.0.13", "vue-i18n": "7.3.2", "vue-router": "3.0.2", - "vue-splitpane": "1.0.2", - "vuedraggable": "^2.16.0", - "vuex": "3.0.1", - "xlsx": "^0.11.16" + "vue-splitpane": "1.0.4", + "vuedraggable": "2.20.0", + "vuex": "3.1.0", + "xlsx": "0.14.1" }, "devDependencies": { - "autoprefixer": "8.5.0", - "babel-core": "6.26.3", - "babel-eslint": "8.2.6", - "babel-helper-vue-jsx-merge-props": "2.0.3", - "babel-loader": "7.1.5", - "babel-plugin-dynamic-import-node": "2.0.0", - "babel-plugin-syntax-jsx": "6.18.0", - "babel-plugin-transform-runtime": "6.23.0", - "babel-plugin-transform-vue-jsx": "3.7.0", - "babel-preset-env": "1.7.0", - "babel-preset-stage-2": "6.24.1", - "chalk": "2.4.1", - "compression-webpack-plugin": "2.0.0", + "@babel/core": "7.0.0", + "@babel/register": "7.0.0", + "@vue/cli-plugin-babel": "3.5.3", + "@vue/cli-plugin-unit-jest": "3.5.3", + "@vue/cli-service": "3.5.3", + "@vue/test-utils": "1.0.0-beta.29", + "babel-core": "7.0.0-bridge.0", + "babel-eslint": "10.0.1", + "babel-jest": "23.6.0", + "chalk": "2.4.2", "connect": "3.6.6", - "copy-webpack-plugin": "4.5.2", - "cross-env": "5.2.0", - "css-loader": "1.0.0", - "eslint": "5.15.2", - "eslint-friendly-formatter": "4.0.1", - "eslint-loader": "2.1.2", + "eslint": "5.15.3", "eslint-plugin-vue": "5.2.2", - "file-loader": "1.1.11", - "friendly-errors-webpack-plugin": "1.7.0", - "hash-sum": "1.0.2", - "html-webpack-plugin": "4.0.0-alpha", - "husky": "0.14.3", - "lint-staged": "7.2.2", - "mini-css-extract-plugin": "0.4.1", - "node-notifier": "5.2.1", - "node-sass": "^4.7.2", - "optimize-css-assets-webpack-plugin": "5.0.0", - "ora": "3.0.0", - "path-to-regexp": "2.4.0", - "portfinder": "1.0.13", - "postcss-import": "11.1.0", - "postcss-loader": "2.1.6", - "postcss-url": "7.3.2", - "rimraf": "2.6.2", - "sass-loader": "7.0.3", - "script-ext-html-webpack-plugin": "2.0.1", + "html-webpack-plugin": "3.2.0", + "husky": "1.3.1", + "lint-staged": "8.1.5", + "mockjs": "1.0.1-beta3", + "node-sass": "^4.9.0", + "plop": "2.3.0", + "runjs": "^4.3.2", + "sass-loader": "^7.1.0", + "script-ext-html-webpack-plugin": "2.1.3", "script-loader": "0.7.2", - "semver": "5.5.0", - "serve-static": "1.13.2", - "shelljs": "0.8.2", - "svg-sprite-loader": "3.8.0", - "svgo": "1.0.5", - "uglifyjs-webpack-plugin": "1.2.7", - "url-loader": "1.0.1", - "vue-loader": "15.3.0", - "vue-style-loader": "4.1.2", - "vue-template-compiler": "2.5.17", - "webpack": "4.16.5", - "webpack-bundle-analyzer": "2.13.1", - "webpack-cli": "3.1.0", - "webpack-dev-server": "3.1.14", - "webpack-merge": "4.1.4" + "serve-static": "^1.13.2", + "svg-sprite-loader": "4.1.3", + "svgo": "1.2.0", + "vue-template-compiler": "2.6.10" }, "engines": { - "node": ">= 6.0.0", + "node": ">=8.9", "npm": ">= 3.0.0" }, "browserslist": [ diff --git a/plop-templates/component/index.hbs b/plop-templates/component/index.hbs new file mode 100644 index 00000000..76610552 --- /dev/null +++ b/plop-templates/component/index.hbs @@ -0,0 +1,26 @@ +{{#if template}} + +{{/if}} + +{{#if script}} + +{{/if}} + +{{#if style}} + +{{/if}} diff --git a/plop-templates/component/prompt.js b/plop-templates/component/prompt.js new file mode 100644 index 00000000..3723e8e1 --- /dev/null +++ b/plop-templates/component/prompt.js @@ -0,0 +1,55 @@ +const { notEmpty } = require('../utils.js') + +module.exports = { + description: 'generate vue component', + prompts: [{ + type: 'input', + name: 'name', + message: 'component name please', + validate: notEmpty('name') + }, + { + type: 'checkbox', + name: 'blocks', + message: 'Blocks:', + choices: [{ + name: '