diff --git a/.env.development b/.env.development index 8f5856db..de583d09 100644 --- a/.env.development +++ b/.env.development @@ -3,12 +3,3 @@ 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/babel.config.js b/babel.config.js index ba179669..fb82b271 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,5 +1,14 @@ module.exports = { presets: [ - '@vue/app' - ] + // https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app + '@vue/cli-plugin-babel/preset' + ], + 'env': { + 'development': { + // babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require(). + // This plugin can significantly increase the speed of hot updates, when you have a large number of pages. + // https://panjiachen.github.io/vue-element-admin-site/guide/advanced/lazy-loading.html + 'plugins': ['dynamic-import-node'] + } + } } diff --git a/mock/article.js b/mock/article.js index 50218ae4..23d8ba51 100644 --- a/mock/article.js +++ b/mock/article.js @@ -1,4 +1,4 @@ -import Mock from 'mockjs' +const Mock = require('mockjs') const List = [] const count = 100 @@ -27,7 +27,7 @@ for (let i = 0; i < count; i++) { })) } -export default [ +module.exports = [ { url: '/vue-element-admin/article/list', type: 'get', diff --git a/mock/index.js b/mock/index.js index 196e2927..2eed65db 100644 --- a/mock/index.js +++ b/mock/index.js @@ -1,10 +1,10 @@ -import Mock from 'mockjs' -import { param2Obj } from '../src/utils' +const Mock = require('mockjs') +const { param2Obj } = require('./utils') -import user from './user' -import role from './role' -import article from './article' -import search from './remote-search' +const user = require('./user') +const role = require('./role') +const article = require('./article') +const search = require('./remote-search') const mocks = [ ...user, @@ -16,7 +16,7 @@ const mocks = [ // for front mock // please use it cautiously, it will redefine XMLHttpRequest, // which will cause many of your third-party libraries to be invalidated(like progress event). -export function mockXHR() { +function mockXHR() { // mock patch // https://github.com/nuysoft/Mock/issues/300 Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send @@ -54,4 +54,7 @@ export function mockXHR() { } } -export default mocks +module.exports = { + mocks, + mockXHR +} diff --git a/mock/mock-server.js b/mock/mock-server.js index 806fdacc..8941ec0f 100644 --- a/mock/mock-server.js +++ b/mock/mock-server.js @@ -8,7 +8,7 @@ const mockDir = path.join(process.cwd(), 'mock') function registerRoutes(app) { let mockLastIndex - const { default: mocks } = require('./index.js') + const { mocks } = require('./index.js') const mocksForServer = mocks.map(route => { return responseFake(route.url, route.type, route.response) }) @@ -44,9 +44,6 @@ const responseFake = (url, type, respond) => { } module.exports = app => { - // es6 polyfill - require('@babel/register') - // parse app.body // https://expressjs.com/en/4x/api.html#req.body app.use(bodyParser.json()) diff --git a/mock/remote-search.js b/mock/remote-search.js index 60809cb8..8fc49267 100644 --- a/mock/remote-search.js +++ b/mock/remote-search.js @@ -1,4 +1,4 @@ -import Mock from 'mockjs' +const Mock = require('mockjs') const NameList = [] const count = 100 @@ -10,7 +10,7 @@ for (let i = 0; i < count; i++) { } NameList.push({ name: 'mock-Pan' }) -export default [ +module.exports = [ // username search { url: '/vue-element-admin/search/user', diff --git a/mock/role/index.js b/mock/role/index.js index d957493b..4643f006 100644 --- a/mock/role/index.js +++ b/mock/role/index.js @@ -1,6 +1,6 @@ -import Mock from 'mockjs' -import { deepClone } from '../../src/utils/index.js' -import { asyncRoutes, constantRoutes } from './routes.js' +const Mock = require('mockjs') +const { deepClone } = require('../utils') +const { asyncRoutes, constantRoutes } = require('./routes.js') const routes = deepClone([...constantRoutes, ...asyncRoutes]) @@ -35,7 +35,7 @@ const roles = [ } ] -export default [ +module.exports = [ // mock get all routes form server { url: '/vue-element-admin/routes', diff --git a/mock/role/routes.js b/mock/role/routes.js index c4654566..757aa38b 100644 --- a/mock/role/routes.js +++ b/mock/role/routes.js @@ -1,6 +1,6 @@ // Just a mock data -export const constantRoutes = [ +const constantRoutes = [ { path: '/redirect', component: 'layout/Layout', @@ -72,7 +72,7 @@ export const constantRoutes = [ } ] -export const asyncRoutes = [ +const asyncRoutes = [ { path: '/permission', component: 'layout/Layout', @@ -523,3 +523,8 @@ export const asyncRoutes = [ { path: '*', redirect: '/404', hidden: true } ] + +module.exports = { + constantRoutes, + asyncRoutes +} diff --git a/mock/user.js b/mock/user.js index 859bd6f3..d82e079d 100644 --- a/mock/user.js +++ b/mock/user.js @@ -23,7 +23,7 @@ const users = { } } -export default [ +module.exports = [ // user login { url: '/vue-element-admin/user/login', diff --git a/mock/utils.js b/mock/utils.js new file mode 100644 index 00000000..f909a293 --- /dev/null +++ b/mock/utils.js @@ -0,0 +1,48 @@ +/** + * @param {string} url + * @returns {Object} + */ +function param2Obj(url) { + const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ') + if (!search) { + return {} + } + const obj = {} + const searchArr = search.split('&') + searchArr.forEach(v => { + const index = v.indexOf('=') + if (index !== -1) { + const name = v.substring(0, index) + const val = v.substring(index + 1, v.length) + obj[name] = val + } + }) + return obj +} + +/** + * This is just a simple version of deep copy + * Has a lot of edge cases bug + * If you want to use a perfect deep copy, use lodash's _.cloneDeep + * @param {Object} source + * @returns {Object} + */ +function deepClone(source) { + if (!source && typeof source !== 'object') { + throw new Error('error arguments', 'deepClone') + } + const targetObj = source.constructor === Array ? [] : {} + Object.keys(source).forEach(keys => { + if (source[keys] && typeof source[keys] === 'object') { + targetObj[keys] = deepClone(source[keys]) + } else { + targetObj[keys] = source[keys] + } + }) + return targetObj +} + +module.exports = { + param2Obj, + deepClone +} diff --git a/package.json b/package.json index c829a00d..70b3aba0 100644 --- a/package.json +++ b/package.json @@ -1,56 +1,29 @@ { "name": "vue-element-admin", - "version": "4.3.0", + "version": "4.3.1", "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": "vue-cli-service serve", + "lint": "eslint --ext .js,.vue src", "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", + "new": "plop", + "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml", "test:unit": "jest --clearCache && 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", "deploy": "bash deploy.sh" }, - "husky": { - "hooks": { - "pre-commit": "lint-staged" - } - }, - "lint-staged": { - "src/**/*.{js,vue}": [ - "eslint --fix", - "git add" - ] - }, - "keywords": [ - "vue", - "admin", - "dashboard", - "element-ui", - "boilerplate", - "admin-template", - "management-system" - ], - "repository": { - "type": "git", - "url": "git+https://github.com/PanJiaChen/vue-element-admin.git" - }, - "bugs": { - "url": "https://github.com/PanJiaChen/vue-element-admin/issues" - }, "dependencies": { "axios": "0.18.1", "clipboard": "2.0.4", "codemirror": "5.45.0", + "core-js": "3.6.5", "driver.js": "0.9.5", "dropzone": "5.5.1", "echarts": "4.2.1", - "element-ui": "2.13.0", + "element-ui": "2.13.2", "file-saver": "2.0.1", "fuse.js": "3.4.4", "js-cookie": "2.2.0", @@ -62,7 +35,6 @@ "pinyin": "2.9.0", "screenfull": "4.2.0", "script-loader": "0.7.2", - "showdown": "1.9.0", "sortablejs": "1.8.4", "tui-editor": "1.3.3", "vue": "2.6.10", @@ -76,42 +48,68 @@ "xlsx": "0.14.1" }, "devDependencies": { - "@babel/core": "7.0.0", - "@babel/register": "7.0.0", - "@vue/cli-plugin-babel": "3.5.3", - "@vue/cli-plugin-eslint": "^3.9.1", - "@vue/cli-plugin-unit-jest": "3.5.3", - "@vue/cli-service": "3.5.3", + "@vue/cli-plugin-babel": "4.4.4", + "@vue/cli-plugin-eslint": "4.4.4", + "@vue/cli-plugin-unit-jest": "4.4.4", + "@vue/cli-service": "4.4.4", "@vue/test-utils": "1.0.0-beta.29", - "autoprefixer": "^9.5.1", - "babel-core": "7.0.0-bridge.0", - "babel-eslint": "10.0.1", + "autoprefixer": "9.5.1", + "babel-eslint": "10.1.0", "babel-jest": "23.6.0", + "babel-plugin-dynamic-import-node": "2.3.3", "chalk": "2.4.2", "chokidar": "2.1.5", "connect": "3.6.6", - "eslint": "5.15.3", - "eslint-plugin-vue": "5.2.2", + "eslint": "6.7.2", + "eslint-plugin-vue": "6.2.2", "html-webpack-plugin": "3.2.0", "husky": "1.3.1", "lint-staged": "8.1.5", "mockjs": "1.0.1-beta3", "plop": "2.3.0", - "runjs": "^4.3.2", - "sass": "^1.26.2", - "sass-loader": "^7.1.0", + "runjs": "4.3.2", + "sass": "1.26.2", + "sass-loader": "8.0.2", "script-ext-html-webpack-plugin": "2.1.3", - "serve-static": "^1.13.2", + "serve-static": "1.13.2", "svg-sprite-loader": "4.1.3", "svgo": "1.2.0", "vue-template-compiler": "2.6.10" }, + "browserslist": [ + "> 1%", + "last 2 versions" + ], + "bugs": { + "url": "https://github.com/PanJiaChen/vue-element-admin/issues" + }, "engines": { "node": ">=8.9", "npm": ">= 3.0.0" }, - "browserslist": [ - "> 1%", - "last 2 versions" - ] + "keywords": [ + "vue", + "admin", + "dashboard", + "element-ui", + "boilerplate", + "admin-template", + "management-system" + ], + "license": "MIT", + "lint-staged": { + "src/**/*.{js,vue}": [ + "eslint --fix", + "git add" + ] + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "repository": { + "type": "git", + "url": "git+https://github.com/PanJiaChen/vue-element-admin.git" + } } diff --git a/plop-templates/utils.js b/plop-templates/utils.js index 0310ca02..04987539 100644 --- a/plop-templates/utils.js +++ b/plop-templates/utils.js @@ -1,9 +1,2 @@ -exports.notEmpty = name => { - return v => { - if (!v || v.trim === '') { - return `${name} is required` - } else { - return true - } - } -} +exports.notEmpty = name => v => + !v || v.trim() === '' ? `${name} is required` : true diff --git a/src/components/ImageCropper/index.vue b/src/components/ImageCropper/index.vue index 13ae9a11..65a4262b 100644 --- a/src/components/ImageCropper/index.vue +++ b/src/components/ImageCropper/index.vue @@ -248,6 +248,7 @@ export default { // 浏览器是否支持该控件 isSupported, // 浏览器是否支持触屏事件 + // eslint-disable-next-line no-prototype-builtins isSupportTouch: document.hasOwnProperty('ontouchstart'), // 步骤 step: 1, // 1选择文件 2剪裁 3上传 diff --git a/src/directive/permission/permission.js b/src/directive/permission/permission.js index b27f24da..49d1f889 100644 --- a/src/directive/permission/permission.js +++ b/src/directive/permission/permission.js @@ -1,11 +1,11 @@ import store from '@/store' -export default { - inserted(el, binding, vnode) { - const { value } = binding - const roles = store.getters && store.getters.roles +function checkPermission(el, binding) { + const { value } = binding + const roles = store.getters && store.getters.roles - if (value && value instanceof Array && value.length > 0) { + if (value && value instanceof Array) { + if (value.length > 0) { const permissionRoles = value const hasPermission = roles.some(role => { @@ -15,8 +15,17 @@ export default { if (!hasPermission) { el.parentNode && el.parentNode.removeChild(el) } - } else { - throw new Error(`need roles! Like v-permission="['admin','editor']"`) } + } else { + throw new Error(`need roles! Like v-permission="['admin','editor']"`) + } +} + +export default { + inserted(el, binding) { + checkPermission(el, binding) + }, + update(el, binding) { + checkPermission(el, binding) } } diff --git a/src/layout/components/Sidebar/Item.vue b/src/layout/components/Sidebar/Item.vue index b515f615..aa1f5da4 100644 --- a/src/layout/components/Sidebar/Item.vue +++ b/src/layout/components/Sidebar/Item.vue @@ -17,7 +17,11 @@ export default { const vnodes = [] if (icon) { - vnodes.push() + if (icon.includes('el-icon')) { + vnodes.push() + } else { + vnodes.push() + } } if (title) { @@ -27,3 +31,11 @@ export default { } } + + diff --git a/src/router/index.js b/src/router/index.js index f6151ec6..dec899ea 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -25,7 +25,7 @@ import nestedRouter from './modules/nested' * meta : { roles: ['admin','editor'] control the page roles (you can set multiple roles) title: 'title' the name show in sidebar and breadcrumb (recommend set) - icon: 'svg-name' the icon show in the sidebar + icon: 'svg-name'/'el-icon-x' the icon show in the sidebar noCache: true if set true, the page will no be cached(default is false) affix: true if set true, the tag will affix in the tags-view breadcrumb: false if set false, the item will hidden in breadcrumb(default is true) @@ -197,7 +197,7 @@ export const asyncRoutes = [ name: 'Example', meta: { title: 'example', - icon: 'example' + icon: 'el-icon-s-help' }, children: [ { diff --git a/src/store/modules/settings.js b/src/store/modules/settings.js index e638a4c4..640368cd 100644 --- a/src/store/modules/settings.js +++ b/src/store/modules/settings.js @@ -14,6 +14,7 @@ const state = { const mutations = { CHANGE_SETTING: (state, { key, value }) => { + // eslint-disable-next-line no-prototype-builtins if (state.hasOwnProperty(key)) { state[key] = value } diff --git a/src/store/modules/user.js b/src/store/modules/user.js index 1391fa4a..78009412 100644 --- a/src/store/modules/user.js +++ b/src/store/modules/user.js @@ -103,28 +103,23 @@ const actions = { }, // dynamically modify permissions - changeRoles({ commit, dispatch }, role) { - return new Promise(async resolve => { - const token = role + '-token' + async changeRoles({ commit, dispatch }, role) { + const token = role + '-token' - commit('SET_TOKEN', token) - setToken(token) + commit('SET_TOKEN', token) + setToken(token) - const { roles } = await dispatch('getInfo') + const { roles } = await dispatch('getInfo') - resetRouter() + resetRouter() - // generate accessible routes map based on roles - const accessRoutes = await dispatch('permission/generateRoutes', roles, { root: true }) + // generate accessible routes map based on roles + const accessRoutes = await dispatch('permission/generateRoutes', roles, { root: true }) + // dynamically add accessible routes + router.addRoutes(accessRoutes) - // dynamically add accessible routes - router.addRoutes(accessRoutes) - - // reset visited views and cached views - dispatch('tagsView/delAllViews', null, { root: true }) - - resolve() - }) + // reset visited views and cached views + dispatch('tagsView/delAllViews', null, { root: true }) } } diff --git a/src/styles/sidebar.scss b/src/styles/sidebar.scss index 3dad4c39..94760cc7 100644 --- a/src/styles/sidebar.scss +++ b/src/styles/sidebar.scss @@ -57,6 +57,11 @@ margin-right: 16px; } + .sub-el-icon { + margin-right: 12px; + margin-left: -2px; + } + .el-menu { border: none; height: 100%; @@ -105,6 +110,10 @@ .svg-icon { margin-left: 20px; } + + .sub-el-icon { + margin-left: 19px; + } } } @@ -118,6 +127,10 @@ margin-left: 20px; } + .sub-el-icon { + margin-left: 19px; + } + .el-submenu__icon-arrow { display: none; } @@ -178,6 +191,10 @@ .svg-icon { margin-right: 16px; } + .sub-el-icon { + margin-right: 12px; + margin-left: -2px; + } } .nest-menu .el-submenu>.el-submenu__title, diff --git a/src/utils/index.js b/src/utils/index.js index 96ee6e7f..3225d3c4 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -162,19 +162,21 @@ export function param(json) { * @returns {Object} */ export function param2Obj(url) { - const search = url.split('?')[1] + const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ') if (!search) { return {} } - return JSON.parse( - '{"' + - decodeURIComponent(search) - .replace(/"/g, '\\"') - .replace(/&/g, '","') - .replace(/=/g, '":"') - .replace(/\+/g, ' ') + - '"}' - ) + const obj = {} + const searchArr = search.split('&') + searchArr.forEach(v => { + const index = v.indexOf('=') + if (index !== -1) { + const name = v.substring(0, index) + const val = v.substring(index + 1, v.length) + obj[name] = val + } + }) + return obj } /** diff --git a/src/views/excel/merge-header.vue b/src/views/excel/merge-header.vue index 2646b9d0..4be0227d 100644 --- a/src/views/excel/merge-header.vue +++ b/src/views/excel/merge-header.vue @@ -71,21 +71,21 @@ export default { }, handleDownload() { this.downloadLoading = true - import('@/vendor/Export2Excel').then(excel => { - const multiHeader = [['Id', 'Main Information', '', '', 'Date']] - const header = ['', 'Title', 'Author', 'Readings', ''] - const filterVal = ['id', 'title', 'author', 'pageviews', 'display_time'] - const list = this.list - const data = this.formatJson(filterVal, list) - const merges = ['A1:A2', 'B1:D1', 'E1:E2'] - excel.export_json_to_excel({ - multiHeader, - header, - merges, - data - }) - this.downloadLoading = false + import('@/vendor/Export2Excel').then(excel => { + const multiHeader = [['Id', 'Main Information', '', '', 'Date']] + const header = ['', 'Title', 'Author', 'Readings', ''] + const filterVal = ['id', 'title', 'author', 'pageviews', 'display_time'] + const list = this.list + const data = this.formatJson(filterVal, list) + const merges = ['A1:A2', 'B1:D1', 'E1:E2'] + excel.export_json_to_excel({ + multiHeader, + header, + merges, + data }) + this.downloadLoading = false + }) }, formatJson(filterVal, jsonData) { return jsonData.map(v => filterVal.map(j => { diff --git a/tests/unit/utils/param2Obj.spec.js b/tests/unit/utils/param2Obj.spec.js new file mode 100644 index 00000000..e106ed88 --- /dev/null +++ b/tests/unit/utils/param2Obj.spec.js @@ -0,0 +1,14 @@ +import { param2Obj } from '@/utils/index.js' +describe('Utils:param2Obj', () => { + const url = 'https://github.com/PanJiaChen/vue-element-admin?name=bill&age=29&sex=1&field=dGVzdA==&key=%E6%B5%8B%E8%AF%95' + + it('param2Obj test', () => { + expect(param2Obj(url)).toEqual({ + name: 'bill', + age: '29', + sex: '1', + field: window.btoa('test'), + key: '测试' + }) + }) +}) diff --git a/vue.config.js b/vue.config.js index 4a58a00b..386a776c 100644 --- a/vue.config.js +++ b/vue.config.js @@ -49,8 +49,11 @@ module.exports = { } }, chainWebpack(config) { - config.plugins.delete('preload') // TODO: need test - config.plugins.delete('prefetch') // TODO: need test + // it can improve the speed of the first screen, it is recommended to turn on preload + // config.plugins.delete('preload') + + // when there are many pages, it will cause too many meaningless requests + config.plugins.delete('prefetch') // set svg-sprite-loader config.module