Compare commits

..

64 Commits

Author SHA1 Message Date
花裤衩 4c18a3f47b docs: update readme 2021-11-19 15:21:54 +08:00
花裤衩 714ded1155 docs: remove gitads 2020-08-30 22:59:10 +08:00
花裤衩 785f19f551
Update README.md 2020-07-26 10:07:01 +08:00
花裤衩 75ba8cf038 docs: update ads url 2020-07-13 10:42:23 +08:00
花裤衩 abcd5d810d docs: add gitads 2020-07-05 10:57:28 +08:00
花裤衩 8c4e38fb1f
Update README.md 2020-06-30 21:30:58 +08:00
花裤衩 fe326aa2b0
Update README.md 2020-06-30 21:20:43 +08:00
morrxy c9cb7db3ce
perf[getInfo]:stop run after reject (#599) 2020-06-26 18:40:32 +08:00
morrxy cd8d52bfbd
fix: add route name(#598) 2020-06-25 13:51:25 +08:00
花裤衩 879318692c
perf[chore]: remove preserveWhitespace config (#597) 2020-06-24 10:21:07 +08:00
花裤衩 8e76fdb622
fix: do not preload runtime.js (#596)
add runtime.js to fileBlacklist
2020-06-23 21:11:06 +08:00
花裤衩 129ea7e654 [release] 4.4.0 2020-06-21 22:04:00 +08:00
花裤衩 83f3a9cc57
bump: update to vue-cli@4 (#591) 2020-06-21 21:46:49 +08:00
花裤衩 b292e46f45 fix: fixed param2Obj.spec.js 2020-06-19 15:52:59 +08:00
花裤衩 4958db1bad fix typo 2020-06-15 17:15:29 +08:00
花裤衩 88303a2959 chore: update element-ui to 2.13.2 2020-06-15 17:14:28 +08:00
花裤衩 8865514c81 fix[utils]: param2Obj bug when url params includes == 2020-06-15 17:13:53 +08:00
花裤衩 e2fd7c7528 feat[Menu]: menu icon support el-icon 2020-06-15 17:11:28 +08:00
花裤衩 5c87772025 refactor: change mock files to commonjs 2020-06-15 17:07:49 +08:00
花裤衩 efc976ae5a chore: turn on the preload 2020-06-15 17:03:06 +08:00
花裤衩 8f2c04e60c [release] 4.3.0 2020-06-09 16:01:17 +08:00
花裤衩 e1c24e7e85 chore: chore: change node-sass to dart-sass 2020-06-09 16:00:51 +08:00
潘嘉晨 4a822b95d6 fix[parseTime]: fixed when pass null
5890499077
2020-06-04 21:26:13 +08:00
潘嘉晨 aff349a863 fix: compatibility with vetur
fc26c3106f
2020-06-04 21:24:14 +08:00
潘嘉晨 00c68bc5cd fix: use vue-cli default source-map
d4405464ce
2020-06-04 21:22:40 +08:00
潘嘉晨 ad02035220 fix: svg support old broswer
6eccffeb2f
2020-06-04 21:22:05 +08:00
潘嘉晨 fc7c3f1da4 docs: add smallsticker 2020-06-04 21:21:19 +08:00
潘嘉晨 97290e6f49 fix:fixed parseTime bug in ie and safari
776f10e197
2020-06-04 21:19:22 +08:00
潘嘉晨 4502d3ebc7 perf: remove script-loader 2020-06-04 21:15:31 +08:00
潘嘉晨 d0518bce5b perf[Mock]: set responseFake to mock-server.js
https://github.com/PanJiaChen/vue-element-admin/pull/2966
2020-06-04 21:14:26 +08:00
花裤衩 95c7bf5d10
fix: fixed mock server (#541) 2020-01-09 20:53:06 +08:00
花裤衩 227ca9b649 bump: update element-ui version 2020-01-09 20:41:01 +08:00
花裤衩 d4c29f903f perf: import mockXHR only in production 2020-01-09 20:39:42 +08:00
花裤衩 bc94111867 perf[Menu]: remove unused menu-wrapper 2020-01-09 20:38:04 +08:00
花裤衩 9cf3082659 fix[Logout]: click blank area of ​​dropDown able to logout 2020-01-09 20:37:26 +08:00
花裤衩 f70f0675ba perf: refine code 2019-12-29 14:35:13 +08:00
花裤衩 6169577990 fix: logout reset state 2019-12-29 14:24:11 +08:00
花裤衩 dc5aa51577 style: fix el-date-picker css style 2019-10-25 19:44:44 +08:00
花裤衩 35ea50deea doc: perf zh docs 2019-10-25 18:23:53 +08:00
花裤衩 5afac23d2f perf[SvgIcon]: change xlink:href to href 2019-10-22 20:29:58 +08:00
花裤衩 f32b49e662 perf[utils.js]: perf parseTime function 2019-10-22 20:29:20 +08:00
花裤衩 0e8fa65568 perf: VS Code support webpack alias file jump 2019-10-22 20:27:25 +08:00
花裤衩 65730989c4 docs: add awesome-project 2019-10-22 20:26:47 +08:00
Pan 00bfe07dc7 bump: update axios for security vulnerability 2019-07-04 16:36:52 +08:00
Pan 44c0d705c5 bump: update @vue/cli-plugin-eslint version 2019-07-04 16:34:24 +08:00
Stephy Cat b9202ade9b chore: allow dynamic set port (#389) 2019-07-01 23:38:18 +08:00
Pan 737d9fc9ee perf: default not set withCredentials 2019-05-28 11:07:36 +08:00
Pan 3d9ec1b729 [release] 4.2.1 2019-05-27 17:02:10 +08:00
Pan cb9d1c0989 perf[request.js]: refine error reject 2019-05-27 17:01:46 +08:00
Pan 7aef039149 perf: use $route.path to the router-view key 2019-05-27 17:01:11 +08:00
lisgroup ca46b22204 chore: add autoprefixer to devDependencies 2019-05-27 16:59:39 +08:00
Pan 969f43415d chore: set localhost => 127.0.0.1 2019-05-27 16:57:29 +08:00
Pan 0353f1a4be perf: update browserslist 2019-05-24 16:59:08 +08:00
Pan 1e2cf43a4f refactor[chore]: generate postcss.config.js instead of .postcssrc.js 2019-05-24 16:58:40 +08:00
Pan 79eeed7d59 feat[SvgIcon]: support import svg from url 2019-05-24 16:57:40 +08:00
Bobby fa7310902a doc: fixed default port error in README.md (#373) 2019-05-21 12:17:08 +08:00
Pan b6753d0373 fix[sidebar.css]: remove redundant css 2019-05-13 12:57:01 +08:00
Pan ac4865ab2d chore: use mockjs in production environment 2019-05-13 12:56:39 +08:00
Pan 9793fff12d perf[Style]: refine fixed-header style when open el-dialog 2019-05-08 18:11:50 +08:00
Pan 896962a5c5 perf: optimize page scrolling when setting fixedHeader 2019-05-05 15:55:25 +08:00
Pan d482e0bb9f docs: tips for set port 2019-05-05 15:07:18 +08:00
Pan 16d39eb015 fix[Mock]: add error handling 2019-04-28 17:57:29 +08:00
Pan 74da10ca29 chore: set ci node version 2019-04-26 17:58:55 +08:00
PLAsusu b2fe1a22e1 fix: fixed error action name (#339) 2019-04-23 09:50:47 +08:00
33 changed files with 341 additions and 178 deletions

View File

@ -3,12 +3,3 @@ ENV = 'development'
# base api # base api
VUE_APP_BASE_API = '/dev-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

View File

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

View File

@ -8,17 +8,28 @@
目前版本为 `v4.0+` 基于 `vue-cli` 进行构建,若你想使用旧版本,可以切换分支到[tag/3.11.0](https://github.com/PanJiaChen/vue-admin-template/tree/tag/3.11.0),它不依赖 `vue-cli` 目前版本为 `v4.0+` 基于 `vue-cli` 进行构建,若你想使用旧版本,可以切换分支到[tag/3.11.0](https://github.com/PanJiaChen/vue-admin-template/tree/tag/3.11.0),它不依赖 `vue-cli`
<p align="center">
<b>SPONSORED BY</b>
</p>
<p align="center">
<a href="https://finclip.com?from=vue_element" title="FinClip" target="_blank">
<img height="200px" src="https://gitee.com/panjiachen/gitee-cdn/raw/master/vue%E8%B5%9E%E5%8A%A9.png" title="FinClip">
</a>
</p>
## Extra ## Extra
如果你想要根据用户角色来动态生成侧边栏和 router你可以使用该分支[permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control) 如果你想要根据用户角色来动态生成侧边栏和 router你可以使用该分支[permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control)
## 相关项目 ## 相关项目
[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) - [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
[electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin) - [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)
[vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) - [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template)
- [awesome-project](https://github.com/PanJiaChen/vue-element-admin/issues/2312)
写了一个系列的教程配套文章,如何从零构建后一个完整的后台项目: 写了一个系列的教程配套文章,如何从零构建后一个完整的后台项目:
@ -77,6 +88,10 @@ npm run lint -- --fix
更多信息请参考 [使用文档](https://panjiachen.github.io/vue-element-admin-site/zh/) 更多信息请参考 [使用文档](https://panjiachen.github.io/vue-element-admin-site/zh/)
## 购买贴纸
你也可以通过 购买[官方授权的贴纸](https://smallsticker.com/product/vue-element-admin) 的方式来支持 vue-element-admin - 每售出一张贴纸,我们将获得 2 元的捐赠。
## Demo ## Demo
![demo](https://github.com/PanJiaChen/PanJiaChen.github.io/blob/master/images/demo.gif) ![demo](https://github.com/PanJiaChen/PanJiaChen.github.io/blob/master/images/demo.gif)

View File

@ -9,8 +9,16 @@ English | [简体中文](./README-zh.md)
**The current version is `v4.0+` build on `vue-cli`. If you want to use the old version , you can switch branch to [tag/3.11.0](https://github.com/PanJiaChen/vue-admin-template/tree/tag/3.11.0), it does not rely on `vue-cli`** **The current version is `v4.0+` build on `vue-cli`. If you want to use the old version , you can switch branch to [tag/3.11.0](https://github.com/PanJiaChen/vue-admin-template/tree/tag/3.11.0), it does not rely on `vue-cli`**
## Build Setup <p align="center">
<b>SPONSORED BY</b>
</p>
<p align="center">
<a href="https://finclip.com?from=vue_element" title="FinClip" target="_blank">
<img height="200px" src="https://gitee.com/panjiachen/gitee-cdn/raw/master/vue%E8%B5%9E%E5%8A%A9.png" title="FinClip">
</a>
</p>
## Build Setup
```bash ```bash
# clone the project # clone the project
@ -26,7 +34,7 @@ npm install
npm run dev npm run dev
``` ```
This will automatically open http://localhost:9527 This will automatically open http://localhost:9528
## Build ## Build
@ -68,11 +76,13 @@ For `typescript` version, you can use [vue-typescript-admin-template](https://gi
## Related Project ## Related Project
[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) - [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
[electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin) - [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)
[vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) - [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template)
- [awesome-project](https://github.com/PanJiaChen/vue-element-admin/issues/2312)
## Browsers support ## Browsers support

View File

@ -1,5 +1,14 @@
module.exports = { module.exports = {
presets: [ 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']
}
}
} }

9
jsconfig.json 100644
View File

@ -0,0 +1,9 @@
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

View File

@ -1,8 +1,8 @@
import Mock from 'mockjs' const Mock = require('mockjs')
import { param2Obj } from '../src/utils' const { param2Obj } = require('./utils')
import user from './user' const user = require('./user')
import table from './table' const table = require('./table')
const mocks = [ const mocks = [
...user, ...user,
@ -12,7 +12,7 @@ const mocks = [
// for front mock // for front mock
// please use it cautiously, it will redefine XMLHttpRequest, // please use it cautiously, it will redefine XMLHttpRequest,
// which will cause many of your third-party libraries to be invalidated(like progress event). // which will cause many of your third-party libraries to be invalidated(like progress event).
export function mockXHR() { function mockXHR() {
// mock patch // mock patch
// https://github.com/nuysoft/Mock/issues/300 // https://github.com/nuysoft/Mock/issues/300
Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
@ -50,17 +50,8 @@ export function mockXHR() {
} }
} }
// for mock server module.exports = {
const responseFake = (url, type, respond) => { mocks,
return { mockXHR
url: new RegExp(`/mock${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)
})

View File

@ -2,17 +2,21 @@ const chokidar = require('chokidar')
const bodyParser = require('body-parser') const bodyParser = require('body-parser')
const chalk = require('chalk') const chalk = require('chalk')
const path = require('path') const path = require('path')
const Mock = require('mockjs')
const mockDir = path.join(process.cwd(), 'mock') const mockDir = path.join(process.cwd(), 'mock')
function registerRoutes(app) { function registerRoutes(app) {
let mockLastIndex let mockLastIndex
const { default: mocks } = require('./index.js') const { mocks } = require('./index.js')
for (const mock of mocks) { const mocksForServer = mocks.map(route => {
return responseFake(route.url, route.type, route.response)
})
for (const mock of mocksForServer) {
app[mock.type](mock.url, mock.response) app[mock.type](mock.url, mock.response)
mockLastIndex = app._router.stack.length mockLastIndex = app._router.stack.length
} }
const mockRoutesLength = Object.keys(mocks).length const mockRoutesLength = Object.keys(mocksForServer).length
return { return {
mockRoutesLength: mockRoutesLength, mockRoutesLength: mockRoutesLength,
mockStartIndex: mockLastIndex - mockRoutesLength mockStartIndex: mockLastIndex - mockRoutesLength
@ -27,10 +31,19 @@ function unregisterRoutes() {
}) })
} }
module.exports = app => { // for mock server
// es6 polyfill const responseFake = (url, type, respond) => {
require('@babel/register') return {
url: new RegExp(`${process.env.VUE_APP_BASE_API}${url}`),
type: type || 'get',
response(req, res) {
console.log('request invoke:' + req.path)
res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond))
}
}
}
module.exports = app => {
// parse app.body // parse app.body
// https://expressjs.com/en/4x/api.html#req.body // https://expressjs.com/en/4x/api.html#req.body
app.use(bodyParser.json()) app.use(bodyParser.json())
@ -48,6 +61,7 @@ module.exports = app => {
ignoreInitial: true ignoreInitial: true
}).on('all', (event, path) => { }).on('all', (event, path) => {
if (event === 'change' || event === 'add') { if (event === 'change' || event === 'add') {
try {
// remove mock routes stack // remove mock routes stack
app._router.stack.splice(mockStartIndex, mockRoutesLength) app._router.stack.splice(mockStartIndex, mockRoutesLength)
@ -59,6 +73,9 @@ module.exports = app => {
mockStartIndex = mockRoutes.mockStartIndex mockStartIndex = mockRoutes.mockStartIndex
console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed ${path}`)) console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed ${path}`))
} catch (error) {
console.log(chalk.redBright(error))
}
} }
}) })
} }

View File

@ -1,4 +1,4 @@
import Mock from 'mockjs' const Mock = require('mockjs')
const data = Mock.mock({ const data = Mock.mock({
'items|30': [{ 'items|30': [{
@ -11,9 +11,9 @@ const data = Mock.mock({
}] }]
}) })
export default [ module.exports = [
{ {
url: '/table/list', url: '/vue-admin-template/table/list',
type: 'get', type: 'get',
response: config => { response: config => {
const items = data.items const items = data.items

View File

@ -23,10 +23,10 @@ const users = {
} }
} }
export default [ module.exports = [
// user login // user login
{ {
url: '/user/login', url: '/vue-admin-template/user/login',
type: 'post', type: 'post',
response: config => { response: config => {
const { username } = config.body const { username } = config.body
@ -49,7 +49,7 @@ export default [
// get user info // get user info
{ {
url: '/user/info\.*', url: '/vue-admin-template/user/info\.*',
type: 'get', type: 'get',
response: config => { response: config => {
const { token } = config.query const { token } = config.query
@ -72,7 +72,7 @@ export default [
// user logout // user logout
{ {
url: '/user/logout', url: '/vue-admin-template/user/logout',
type: 'post', type: 'post',
response: _ => { response: _ => {
return { return {

25
mock/utils.js 100644
View File

@ -0,0 +1,25 @@
/**
* @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
}
module.exports = {
param2Obj
}

View File

@ -1,22 +1,22 @@
{ {
"name": "vue-admin-template", "name": "vue-admin-template",
"version": "4.1.0", "version": "4.4.0",
"description": "A vue admin template with Element UI & axios & iconfont & permission control & lint", "description": "A vue admin template with Element UI & axios & iconfont & permission control & lint",
"author": "Pan <panfree23@gmail.com>", "author": "Pan <panfree23@gmail.com>",
"license": "MIT",
"scripts": { "scripts": {
"dev": "vue-cli-service serve", "dev": "vue-cli-service serve",
"build:prod": "vue-cli-service build", "build:prod": "vue-cli-service build",
"build:stage": "vue-cli-service build --mode staging", "build:stage": "vue-cli-service build --mode staging",
"preview": "node build/index.js --preview", "preview": "node build/index.js --preview",
"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml",
"lint": "eslint --ext .js,.vue src", "lint": "eslint --ext .js,.vue src",
"test:unit": "jest --clearCache && vue-cli-service test:unit", "test:unit": "jest --clearCache && vue-cli-service test:unit",
"test:ci": "npm run lint && npm run test:unit", "test:ci": "npm run lint && npm run test:unit"
"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
}, },
"dependencies": { "dependencies": {
"axios": "0.18.0", "axios": "0.18.1",
"element-ui": "2.7.2", "core-js": "3.6.5",
"element-ui": "2.13.2",
"js-cookie": "2.2.0", "js-cookie": "2.2.0",
"normalize.css": "7.0.0", "normalize.css": "7.0.0",
"nprogress": "0.2.0", "nprogress": "0.2.0",
@ -26,39 +26,37 @@
"vuex": "3.1.0" "vuex": "3.1.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.0.0", "@vue/cli-plugin-babel": "4.4.4",
"@babel/register": "7.0.0", "@vue/cli-plugin-eslint": "4.4.4",
"@vue/cli-plugin-babel": "3.6.0", "@vue/cli-plugin-unit-jest": "4.4.4",
"@vue/cli-plugin-eslint": "3.6.0", "@vue/cli-service": "4.4.4",
"@vue/cli-plugin-unit-jest": "3.6.3",
"@vue/cli-service": "3.6.0",
"@vue/test-utils": "1.0.0-beta.29", "@vue/test-utils": "1.0.0-beta.29",
"babel-core": "7.0.0-bridge.0", "autoprefixer": "9.5.1",
"babel-eslint": "10.0.1", "babel-eslint": "10.1.0",
"babel-jest": "23.6.0", "babel-jest": "23.6.0",
"babel-plugin-dynamic-import-node": "2.3.3",
"chalk": "2.4.2", "chalk": "2.4.2",
"connect": "3.6.6", "connect": "3.6.6",
"eslint": "5.15.3", "eslint": "6.7.2",
"eslint-plugin-vue": "5.2.2", "eslint-plugin-vue": "6.2.2",
"html-webpack-plugin": "3.2.0", "html-webpack-plugin": "3.2.0",
"mockjs": "1.0.1-beta3", "mockjs": "1.0.1-beta3",
"node-sass": "^4.9.0", "runjs": "4.3.2",
"runjs": "^4.3.2", "sass": "1.26.8",
"sass-loader": "^7.1.0", "sass-loader": "8.0.2",
"script-ext-html-webpack-plugin": "2.1.3", "script-ext-html-webpack-plugin": "2.1.3",
"script-loader": "0.7.2", "serve-static": "1.13.2",
"serve-static": "^1.13.2",
"svg-sprite-loader": "4.1.3", "svg-sprite-loader": "4.1.3",
"svgo": "1.2.2", "svgo": "1.2.2",
"vue-template-compiler": "2.6.10" "vue-template-compiler": "2.6.10"
}, },
"browserslist": [
"> 1%",
"last 2 versions"
],
"engines": { "engines": {
"node": ">=8.9", "node": ">=8.9",
"npm": ">= 3.0.0" "npm": ">= 3.0.0"
}, },
"browserslist": [ "license": "MIT"
"> 1%",
"last 2 versions",
"not ie <= 8"
]
} }

View File

@ -2,7 +2,7 @@ import request from '@/utils/request'
export function getList(params) { export function getList(params) {
return request({ return request({
url: '/table/list', url: '/vue-admin-template/table/list',
method: 'get', method: 'get',
params params
}) })

View File

@ -2,7 +2,7 @@ import request from '@/utils/request'
export function login(data) { export function login(data) {
return request({ return request({
url: '/user/login', url: '/vue-admin-template/user/login',
method: 'post', method: 'post',
data data
}) })
@ -10,7 +10,7 @@ export function login(data) {
export function getInfo(token) { export function getInfo(token) {
return request({ return request({
url: '/user/info', url: '/vue-admin-template/user/info',
method: 'get', method: 'get',
params: { token } params: { token }
}) })
@ -18,7 +18,7 @@ export function getInfo(token) {
export function logout() { export function logout() {
return request({ return request({
url: '/user/logout', url: '/vue-admin-template/user/logout',
method: 'post' method: 'post'
}) })
} }

View File

@ -1,10 +1,14 @@
<template> <template>
<svg :class="svgClass" aria-hidden="true" v-on="$listeners"> <div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" />
<svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
<use :xlink:href="iconName" /> <use :xlink:href="iconName" />
</svg> </svg>
</template> </template>
<script> <script>
// doc: https://panjiachen.github.io/vue-element-admin-site/feature/component/svg-icon.html#usage
import { isExternal } from '@/utils/validate'
export default { export default {
name: 'SvgIcon', name: 'SvgIcon',
props: { props: {
@ -18,6 +22,9 @@ export default {
} }
}, },
computed: { computed: {
isExternal() {
return isExternal(this.iconClass)
},
iconName() { iconName() {
return `#icon-${this.iconClass}` return `#icon-${this.iconClass}`
}, },
@ -27,6 +34,12 @@ export default {
} else { } else {
return 'svg-icon' return 'svg-icon'
} }
},
styleExternalIcon() {
return {
mask: `url(${this.iconClass}) no-repeat 50% 50%`,
'-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
}
} }
} }
} }
@ -40,4 +53,10 @@ export default {
fill: currentColor; fill: currentColor;
overflow: hidden; overflow: hidden;
} }
.svg-external-icon {
background-color: currentColor;
mask-size: cover!important;
display: inline-block;
}
</style> </style>

View File

@ -11,7 +11,7 @@ export default {
name: 'AppMain', name: 'AppMain',
computed: { computed: {
key() { key() {
return this.$route.fullPath return this.$route.path
} }
} }
} }
@ -29,3 +29,12 @@ export default {
padding-top: 50px; padding-top: 50px;
} }
</style> </style>
<style lang="scss">
// fix css style bug in open el-dialog
.el-popup-parent--hidden {
.fixed-header {
padding-right: 15px;
}
}
</style>

View File

@ -22,8 +22,8 @@
<a target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/#/"> <a target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/#/">
<el-dropdown-item>Docs</el-dropdown-item> <el-dropdown-item>Docs</el-dropdown-item>
</a> </a>
<el-dropdown-item divided> <el-dropdown-item divided @click.native="logout">
<span style="display:block;" @click="logout">Log Out</span> <span style="display:block;">Log Out</span>
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>

View File

@ -17,8 +17,12 @@ export default {
const vnodes = [] const vnodes = []
if (icon) { if (icon) {
if (icon.includes('el-icon')) {
vnodes.push(<i class={[icon, 'sub-el-icon']} />)
} else {
vnodes.push(<svg-icon icon-class={icon}/>) vnodes.push(<svg-icon icon-class={icon}/>)
} }
}
if (title) { if (title) {
vnodes.push(<span slot='title'>{(title)}</span>) vnodes.push(<span slot='title'>{(title)}</span>)
@ -27,3 +31,11 @@ export default {
} }
} }
</script> </script>
<style scoped>
.sub-el-icon {
color: currentColor;
width: 1em;
height: 1em;
}
</style>

View File

@ -1,7 +1,5 @@
<template> <template>
<!-- eslint-disable vue/require-component-is --> <component :is="type" v-bind="linkProps(to)">
<component v-bind="linkProps(to)">
<slot /> <slot />
</component> </component>
</template> </template>
@ -16,19 +14,28 @@ export default {
required: true required: true
} }
}, },
computed: {
isExternal() {
return isExternal(this.to)
},
type() {
if (this.isExternal) {
return 'a'
}
return 'router-link'
}
},
methods: { methods: {
linkProps(url) { linkProps(to) {
if (isExternal(url)) { if (this.isExternal) {
return { return {
is: 'a', href: to,
href: url,
target: '_blank', target: '_blank',
rel: 'noopener' rel: 'noopener'
} }
} }
return { return {
is: 'router-link', to: to
to: url
} }
} }
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<div v-if="!item.hidden" class="menu-wrapper"> <div v-if="!item.hidden">
<template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow"> <template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)"> <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}"> <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">

View File

@ -44,7 +44,7 @@ export default {
}, },
methods: { methods: {
handleClickOutside() { handleClickOutside() {
this.$store.dispatch('CloseSideBar', { withoutAnimation: false }) this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
} }
} }
} }

View File

@ -17,15 +17,21 @@ import '@/permission' // permission control
/** /**
* If you don't want to use mock-server * If you don't want to use mock-server
* you want to use mockjs for request interception * you want to use MockJs for mock api
* you can execute: * you can execute: mockXHR()
* *
* import { mockXHR } from '../mock' * Currently MockJs will be used in the production environment,
* mockXHR() * please remove it before going online ! ! !
*/ */
if (process.env.NODE_ENV === 'production') {
const { mockXHR } = require('../mock')
mockXHR()
}
// set ElementUI lang to EN // set ElementUI lang to EN
Vue.use(ElementUI, { locale }) Vue.use(ElementUI, { locale })
// 如果想要中文版 element-ui按如下方式声明
// Vue.use(ElementUI)
Vue.config.productionTip = false Vue.config.productionTip = false

View File

@ -19,7 +19,7 @@ import Layout from '@/layout'
* meta : { * meta : {
roles: ['admin','editor'] control the page roles (you can set multiple roles) roles: ['admin','editor'] control the page roles (you can set multiple roles)
title: 'title' the name show in sidebar and breadcrumb (recommend set) 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
breadcrumb: false if set false, the item will hidden in breadcrumb(default is true) breadcrumb: false if set false, the item will hidden in breadcrumb(default is true)
activeMenu: '/example/list' if set path, the sidebar will highlight the path you set activeMenu: '/example/list' if set path, the sidebar will highlight the path you set
} }
@ -60,7 +60,7 @@ export const constantRoutes = [
component: Layout, component: Layout,
redirect: '/example/table', redirect: '/example/table',
name: 'Example', name: 'Example',
meta: { title: 'Example', icon: 'example' }, meta: { title: 'Example', icon: 'el-icon-s-help' },
children: [ children: [
{ {
path: 'table', path: 'table',
@ -143,6 +143,7 @@ export const constantRoutes = [
{ {
path: 'menu2', path: 'menu2',
component: () => import('@/views/nested/menu2/index'), component: () => import('@/views/nested/menu2/index'),
name: 'Menu2',
meta: { title: 'menu2' } meta: { title: 'menu2' }
} }
] ]

View File

@ -10,6 +10,7 @@ const state = {
const mutations = { const mutations = {
CHANGE_SETTING: (state, { key, value }) => { CHANGE_SETTING: (state, { key, value }) => {
// eslint-disable-next-line no-prototype-builtins
if (state.hasOwnProperty(key)) { if (state.hasOwnProperty(key)) {
state[key] = value state[key] = value
} }

View File

@ -2,13 +2,20 @@ import { login, logout, getInfo } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth' import { getToken, setToken, removeToken } from '@/utils/auth'
import { resetRouter } from '@/router' import { resetRouter } from '@/router'
const state = { const getDefaultState = () => {
return {
token: getToken(), token: getToken(),
name: '', name: '',
avatar: '' avatar: ''
}
} }
const state = getDefaultState()
const mutations = { const mutations = {
RESET_STATE: (state) => {
Object.assign(state, getDefaultState())
},
SET_TOKEN: (state, token) => { SET_TOKEN: (state, token) => {
state.token = token state.token = token
}, },
@ -43,7 +50,7 @@ const actions = {
const { data } = response const { data } = response
if (!data) { if (!data) {
reject('Verification failed, please Login again.') return reject('Verification failed, please Login again.')
} }
const { name, avatar } = data const { name, avatar } = data
@ -61,9 +68,9 @@ const actions = {
logout({ commit, state }) { logout({ commit, state }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
logout(state.token).then(() => { logout(state.token).then(() => {
commit('SET_TOKEN', '') removeToken() // must remove token first
removeToken()
resetRouter() resetRouter()
commit('RESET_STATE')
resolve() resolve()
}).catch(error => { }).catch(error => {
reject(error) reject(error)
@ -74,8 +81,8 @@ const actions = {
// remove token // remove token
resetToken({ commit }) { resetToken({ commit }) {
return new Promise(resolve => { return new Promise(resolve => {
commit('SET_TOKEN', '') removeToken() // must remove token first
removeToken() commit('RESET_STATE')
resolve() resolve()
}) })
} }

View File

@ -42,3 +42,8 @@
display: block display: block
} }
} }
// to fix el-date-picker css style
.el-range-separator {
box-sizing: content-box;
}

View File

@ -57,6 +57,11 @@
margin-right: 16px; margin-right: 16px;
} }
.sub-el-icon {
margin-right: 12px;
margin-left: -2px;
}
.el-menu { .el-menu {
border: none; border: none;
height: 100%; height: 100%;
@ -95,10 +100,6 @@
margin-left: 54px; margin-left: 54px;
} }
.svg-icon {
margin-right: 0px;
}
.submenu-title-noDropdown { .submenu-title-noDropdown {
padding: 0 !important; padding: 0 !important;
position: relative; position: relative;
@ -109,6 +110,10 @@
.svg-icon { .svg-icon {
margin-left: 20px; margin-left: 20px;
} }
.sub-el-icon {
margin-left: 19px;
}
} }
} }
@ -122,6 +127,10 @@
margin-left: 20px; margin-left: 20px;
} }
.sub-el-icon {
margin-left: 19px;
}
.el-submenu__icon-arrow { .el-submenu__icon-arrow {
display: none; display: none;
} }
@ -182,6 +191,10 @@
.svg-icon { .svg-icon {
margin-right: 16px; margin-right: 16px;
} }
.sub-el-icon {
margin-right: 12px;
margin-left: -2px;
}
} }
.nest-menu .el-submenu>.el-submenu__title, .nest-menu .el-submenu>.el-submenu__title,

View File

@ -6,10 +6,10 @@
* Parse the time to string * Parse the time to string
* @param {(Object|string|number)} time * @param {(Object|string|number)} time
* @param {string} cFormat * @param {string} cFormat
* @returns {string} * @returns {string | null}
*/ */
export function parseTime(time, cFormat) { export function parseTime(time, cFormat) {
if (arguments.length === 0) { if (arguments.length === 0 || !time) {
return null return null
} }
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}' const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
@ -17,9 +17,17 @@ export function parseTime(time, cFormat) {
if (typeof time === 'object') { if (typeof time === 'object') {
date = time date = time
} else { } else {
if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) { if ((typeof time === 'string')) {
if ((/^[0-9]+$/.test(time))) {
// support "1548221490638"
time = parseInt(time) time = parseInt(time)
} else {
// support safari
// https://stackoverflow.com/questions/4310953/invalid-date-in-safari
time = time.replace(new RegExp(/-/gm), '/')
} }
}
if ((typeof time === 'number') && (time.toString().length === 10)) { if ((typeof time === 'number') && (time.toString().length === 10)) {
time = time * 1000 time = time * 1000
} }
@ -34,14 +42,11 @@ export function parseTime(time, cFormat) {
s: date.getSeconds(), s: date.getSeconds(),
a: date.getDay() a: date.getDay()
} }
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
let value = formatObj[key] const value = formatObj[key]
// Note: getDay() returns 0 on Sunday // Note: getDay() returns 0 on Sunday
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] } if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] }
if (result.length > 0 && value < 10) { return value.toString().padStart(2, '0')
value = '0' + value
}
return value || 0
}) })
return time_str return time_str
} }
@ -94,17 +99,19 @@ export function formatTime(time, option) {
* @returns {Object} * @returns {Object}
*/ */
export function param2Obj(url) { export function param2Obj(url) {
const search = url.split('?')[1] const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
if (!search) { if (!search) {
return {} return {}
} }
return JSON.parse( const obj = {}
'{"' + const searchArr = search.split('&')
decodeURIComponent(search) searchArr.forEach(v => {
.replace(/"/g, '\\"') const index = v.indexOf('=')
.replace(/&/g, '","') if (index !== -1) {
.replace(/=/g, '":"') const name = v.substring(0, index)
.replace(/\+/g, ' ') + const val = v.substring(index + 1, v.length)
'"}' obj[name] = val
) }
})
return obj
} }

View File

@ -6,7 +6,7 @@ import { getToken } from '@/utils/auth'
// create an axios instance // create an axios instance
const service = axios.create({ const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
withCredentials: true, // send cookies when cross-domain requests // withCredentials: true, // send cookies when cross-domain requests
timeout: 5000 // request timeout timeout: 5000 // request timeout
}) })
@ -48,7 +48,7 @@ service.interceptors.response.use(
// if the custom code is not 20000, it is judged as an error. // if the custom code is not 20000, it is judged as an error.
if (res.code !== 20000) { if (res.code !== 20000) {
Message({ Message({
message: res.message || 'error', message: res.message || 'Error',
type: 'error', type: 'error',
duration: 5 * 1000 duration: 5 * 1000
}) })
@ -66,7 +66,7 @@ service.interceptors.response.use(
}) })
}) })
} }
return Promise.reject(res.message || 'error') return Promise.reject(new Error(res.message || 'Error'))
} else { } else {
return res return res
} }

View File

@ -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: '测试'
})
})
})

View File

@ -5,6 +5,9 @@ describe('Utils:parseTime', () => {
it('timestamp', () => { it('timestamp', () => {
expect(parseTime(d)).toBe('2018-07-13 17:54:01') expect(parseTime(d)).toBe('2018-07-13 17:54:01')
}) })
it('timestamp string', () => {
expect(parseTime((d + ''))).toBe('2018-07-13 17:54:01')
})
it('ten digits timestamp', () => { it('ten digits timestamp', () => {
expect(parseTime((d / 1000).toFixed(0))).toBe('2018-07-13 17:54:01') expect(parseTime((d / 1000).toFixed(0))).toBe('2018-07-13 17:54:01')
}) })
@ -25,4 +28,8 @@ describe('Utils:parseTime', () => {
it('empty argument', () => { it('empty argument', () => {
expect(parseTime()).toBeNull() expect(parseTime()).toBeNull()
}) })
it('null', () => {
expect(parseTime(null)).toBeNull()
})
}) })

View File

@ -7,7 +7,13 @@ function resolve(dir) {
} }
const name = defaultSettings.title || 'vue Admin Template' // page title const name = defaultSettings.title || 'vue Admin Template' // page title
const port = 9528 // dev port
// If your port is set to 80,
// use administrator privileges to execute the command line.
// For example, Mac: sudo npm run
// You can change the port by the following methods:
// port = 9528 npm run dev OR npm run dev --port = 9528
const port = process.env.port || process.env.npm_config_port || 9528 // dev port
// All configuration item explanations can be find in https://cli.vuejs.org/config/ // All configuration item explanations can be find in https://cli.vuejs.org/config/
module.exports = { module.exports = {
@ -30,18 +36,7 @@ module.exports = {
warnings: false, warnings: false,
errors: true errors: true
}, },
proxy: { before: require('./mock/mock-server.js')
// change xxx-api/login => mock/login
// detail: https://cli.vuejs.org/config/#devserver-proxy
[process.env.VUE_APP_BASE_API]: {
target: `http://localhost:${port}/mock`,
changeOrigin: true,
pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: ''
}
}
},
after: require('./mock/mock-server.js')
}, },
configureWebpack: { configureWebpack: {
// provide the app's title in webpack's name field, so that // provide the app's title in webpack's name field, so that
@ -54,8 +49,19 @@ module.exports = {
} }
}, },
chainWebpack(config) { chainWebpack(config) {
config.plugins.delete('preload') // TODO: need test // it can improve the speed of the first screen, it is recommended to turn on preload
config.plugins.delete('prefetch') // TODO: need test config.plugin('preload').tap(() => [
{
rel: 'preload',
// to ignore runtime.js
// https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171
fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
include: 'initial'
}
])
// when there are many pages, it will cause too many meaningless requests
config.plugins.delete('prefetch')
// set svg-sprite-loader // set svg-sprite-loader
config.module config.module
@ -74,23 +80,6 @@ module.exports = {
}) })
.end() .end()
// set preserveWhitespace
config.module
.rule('vue')
.use('vue-loader')
.loader('vue-loader')
.tap(options => {
options.compilerOptions.preserveWhitespace = true
return options
})
.end()
config
// https://webpack.js.org/configuration/devtool/#development
.when(process.env.NODE_ENV === 'development',
config => config.devtool('cheap-source-map')
)
config config
.when(process.env.NODE_ENV !== 'development', .when(process.env.NODE_ENV !== 'development',
config => { config => {
@ -126,6 +115,7 @@ module.exports = {
} }
} }
}) })
// https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk
config.optimization.runtimeChunk('single') config.optimization.runtimeChunk('single')
} }
) )