diff --git a/.env.development b/.env.development
index c8765073..f68e3e06 100644
--- a/.env.development
+++ b/.env.development
@@ -1,4 +1,5 @@
VUE_APP_BASE_API = '/api'
+ENV = 'development'
// With this configuration, vue-cli uses babel-plugin-dynamic-import-node
// It only does one thing by converting all import() to require()
diff --git a/.env.production b/.env.production
index 1d2d6c9f..8ea6337a 100644
--- a/.env.production
+++ b/.env.production
@@ -1 +1,2 @@
VUE_APP_BASE_API = '/api'
+ENV = 'production'
diff --git a/.env.staging b/.env.staging
new file mode 100644
index 00000000..2015f804
--- /dev/null
+++ b/.env.staging
@@ -0,0 +1,3 @@
+NODE_ENV=production
+VUE_APP_BASE_API = '/api'
+ENV = 'staging'
diff --git a/.eslintrc.js b/.eslintrc.js
index 6f55c5a1..82ae4a94 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -21,7 +21,10 @@ module.exports = {
"allowFirstLine": false
}
}],
+ "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,
diff --git a/build/index.js b/build/index.js
index a750ae0b..fd9b9ff5 100644
--- a/build/index.js
+++ b/build/index.js
@@ -8,14 +8,14 @@ if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
run(`vue-cli-service build ${args}`)
const port = 9526
- const basePath = config.baseUrl
+ const publicPath = config.publicPath
var connect = require('connect')
var serveStatic = require('serve-static')
const app = connect()
app.use(
- basePath,
+ publicPath,
serveStatic('./dist', {
index: ['index.html', '/']
})
@@ -23,7 +23,7 @@ if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
app.listen(port, function() {
console.log(
- chalk.green(`> Listening at http://localhost:${port}${basePath}`)
+ chalk.green(`> Listening at http://localhost:${port}${publicPath}`)
)
})
} else {
diff --git a/jest.config.js b/jest.config.js
index 1ce813e1..f5a99474 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -1,9 +1,9 @@
module.exports = {
verbose: true,
moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
- transformIgnorePatterns: [
- 'node_modules/(?!(babel-jest|jest-vue-preprocessor)/)'
- ],
+ // 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',
diff --git a/mock/article.js b/mock/article.js
index 72b5f837..45b75296 100644
--- a/mock/article.js
+++ b/mock/article.js
@@ -13,7 +13,7 @@ for (let i = 0; i < count; i++) {
author: '@first',
reviewer: '@first',
title: '@title(5, 10)',
- content_short: '我是测试数据',
+ content_short: 'mock data',
content: baseContent,
forecast: '@float(0, 100, 2, 2)',
importance: '@integer(1, 3)',
@@ -27,48 +27,90 @@ for (let i = 0; i < count; i++) {
}))
}
-export default {
- '/article/list': config => {
- const { importance, type, title, page = 1, limit = 20, sort } = config.query
+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
- })
+ 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()
- }
+ if (sort === '-id') {
+ mockList = mockList.reverse()
+ }
- const pageList = mockList.filter((item, index) => index < limit * page && index >= limit * (page - 1))
+ const pageList = mockList.filter((item, index) => index < limit * page && index >= limit * (page - 1))
- return {
- total: mockList.length,
- items: pageList
- }
- },
- '/article/detail': config => {
- const { id } = config.query
- for (const article of List) {
- if (article.id === +id) {
- return article
+ return {
+ code: 20000,
+ data: {
+ total: mockList.length,
+ items: pageList
+ }
}
}
},
- '/article/pv': {
- pvData: [
- { key: 'PC', pv: 1024 },
- { key: 'mobile', pv: 1024 },
- { key: 'ios', pv: 1024 },
- { key: 'android', pv: 1024 }
- ]
+
+ {
+ 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
+ }
+ }
+ }
+ }
},
- '/article/create': {
- data: 'success'
+
+ {
+ 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 }
+ ]
+ }
+ }
+ }
},
- '/article/update': {
- data: 'success'
+
+ {
+ 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
index f40ac238..4fa6c3d3 100644
--- a/mock/index.js
+++ b/mock/index.js
@@ -33,18 +33,21 @@ export function mockXHR() {
}
}
- for (const [route, respond] of Object.entries(mocks)) {
- Mock.mock(new RegExp(`${route}`), XHR2ExpressReqWrap(respond))
+ for (const i of mocks) {
+ Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response))
}
}
-const responseFake = (route, respond) => (
- {
- route: new RegExp(`${MOCK_API_BASE}${route}`),
+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 Object.keys(mocks).map(route => responseFake(route, mocks[route]))
+export default mocks.map(route => {
+ return responseFake(route.url, route.type, route.response)
+})
diff --git a/mock/login.js b/mock/login.js
deleted file mode 100644
index e49f98ce..00000000
--- a/mock/login.js
+++ /dev/null
@@ -1,33 +0,0 @@
-const userMap = {
- admin: {
- roles: ['admin'],
- token: 'admin',
- introduction: '我是超级管理员',
- avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
- name: 'Super Admin'
- },
- editor: {
- roles: ['editor'],
- token: 'editor',
- introduction: '我是编辑',
- avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
- name: 'Normal Editor'
- }
-}
-
-export default {
- '/login/login': config => {
- const { username } = config.body
- return userMap[username]
- },
- '/login/logout': 'success',
- '/user/info': config => {
- const { token } = config.query
- if (userMap[token]) {
- return userMap[token]
- } else {
- return false
- }
- }
-}
-
diff --git a/mock/mocks.js b/mock/mocks.js
index 9e551722..84a25ddc 100644
--- a/mock/mocks.js
+++ b/mock/mocks.js
@@ -1,12 +1,12 @@
-import login from './login'
+import user from './user'
+import role from './role'
import article from './article'
import search from './remoteSearch'
-import transaction from './transaction'
-export default {
- ...login,
+export default [
+ ...user,
+ ...role,
...article,
- ...search,
- ...transaction
-}
+ ...search
+]
diff --git a/mock/remoteSearch.js b/mock/remoteSearch.js
index bbdc0708..bb33c2f4 100644
--- a/mock/remoteSearch.js
+++ b/mock/remoteSearch.js
@@ -8,15 +8,44 @@ for (let i = 0; i < count; i++) {
name: '@first'
}))
}
-NameList.push({ name: 'mockPan' })
+NameList.push({ name: 'mock-Pan' })
-export default {
- '/search/user': config => {
- const { name } = config.query
- const mockNameList = NameList.filter(item => {
- const lowerCaseName = item.name.toLowerCase()
- return !(name && lowerCaseName.indexOf(name.toLowerCase()) < 0)
- })
- return { items: mockNameList }
+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/transaction.js b/mock/transaction.js
deleted file mode 100644
index 61c84f0d..00000000
--- a/mock/transaction.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import Mock from 'mockjs'
-
-const count = 20
-
-export default {
- '/transaction/list': {
- total: count,
- [`items|${count}`]: [{
- order_no: '@guid()',
- timestamp: +Mock.Random.date('T'),
- username: '@name()',
- price: '@float(1000, 15000, 0, 2)',
- 'status|1': ['success', 'pending']
- }]
- }
-}
diff --git a/mock/user.js b/mock/user.js
new file mode 100644
index 00000000..21366c1e
--- /dev/null
+++ b/mock/user.js
@@ -0,0 +1,64 @@
+
+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
+ return {
+ code: 20000,
+ data: tokens[username]
+ }
+ }
+ },
+
+ // get user info
+ {
+ url: '/user/info\.*',
+ type: 'get',
+ response: config => {
+ const { token } = config.query
+ return {
+ code: 20000,
+ data: users[token]
+ }
+ }
+ },
+
+ // user logout
+ {
+ url: '/user/logout',
+ type: 'post',
+ response: _ => {
+ return {
+ code: 20000,
+ data: 'success'
+ }
+ }
+ }
+]
diff --git a/package.json b/package.json
index 3f0c4bdc..19b00aa8 100644
--- a/package.json
+++ b/package.json
@@ -7,15 +7,19 @@
"scripts": {
"dev": "vue-cli-service serve",
"build:prod": "vue-cli-service build",
- "build:sit": "vue-cli-service build --mode text",
+ "build:stage": "vue-cli-service build --mode staging",
"build:preview": "node build/index.js --preview",
"build:report": "node build/index.js --report",
"lint": "eslint --ext .js,.vue src",
"test": "npm run lint",
"test:unit": "vue-cli-service test:unit",
- "precommit": "lint-staged",
"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
},
+ "husky": {
+ "hooks": {
+ "pre-commit": "lint-staged"
+ }
+ },
"lint-staged": {
"src/**/*.{js,vue}": [
"eslint --fix",
@@ -39,56 +43,58 @@
"dependencies": {
"axios": "0.18.0",
"clipboard": "1.7.1",
- "codemirror": "5.42.0",
- "driver.js": "0.8.1",
+ "codemirror": "5.44.0",
+ "driver.js": "0.9.5",
"dropzone": "5.5.1",
"echarts": "4.1.0",
- "element-ui": "2.4.10",
- "file-saver": "1.3.8",
- "fuse.js": "3.4.2",
+ "element-ui": "2.6.1",
+ "file-saver": "2.0.1",
+ "fuse.js": "3.4.4",
"js-cookie": "2.2.0",
"jsonlint": "1.6.3",
- "jszip": "3.1.5",
+ "jszip": "3.2.0",
"normalize.css": "7.0.0",
"nprogress": "0.2.0",
"path-to-regexp": "2.4.0",
- "screenfull": "4.0.0",
- "showdown": "1.8.6",
- "sortablejs": "1.7.0",
- "tui-editor": "1.2.7",
- "vue": "2.5.17",
+ "screenfull": "4.0.1",
+ "showdown": "1.9.0",
+ "sortablejs": "1.8.3",
+ "tui-editor": "1.3.2",
+ "vue": "2.6.8",
"vue-count-to": "1.0.13",
"vue-i18n": "7.3.2",
"vue-router": "3.0.2",
"vue-splitpane": "1.0.2",
"vuedraggable": "2.17.0",
- "vuex": "3.0.1",
- "xlsx": "^0.11.16"
+ "vuex": "3.1.0",
+ "xlsx": "0.14.1"
},
"devDependencies": {
"@babel/core": "7.0.0",
"@babel/register": "7.0.0",
- "@vue/cli-plugin-babel": "3.2.0",
- "@vue/cli-plugin-eslint": "3.2.1",
- "@vue/cli-plugin-unit-jest": "3.2.0",
- "@vue/cli-service": "3.2.0",
- "@vue/test-utils": "1.0.0-beta.25",
+ "@vue/cli-plugin-babel": "3.5.0",
+ "@vue/cli-plugin-unit-jest": "3.5.0",
+ "@vue/cli-service": "3.5.0",
+ "@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.1",
- "connect": "^3.6.6",
- "husky": "0.14.3",
+ "chalk": "2.4.2",
+ "connect": "3.6.6",
+ "eslint": "5.15.1",
+ "eslint-plugin-vue": "5.2.2",
+ "husky": "1.3.1",
"lint-staged": "7.2.2",
"mockjs": "1.0.1-beta3",
"node-sass": "^4.9.0",
"runjs": "^4.3.2",
- "sass-loader": "7.0.3",
+ "sass-loader": "^7.1.0",
"script-ext-html-webpack-plugin": "2.1.3",
"script-loader": "0.7.2",
"serve-static": "^1.13.2",
"svg-sprite-loader": "4.1.3",
- "svgo": "1.1.1",
- "vue-template-compiler": "2.5.17"
+ "svgo": "1.2.0",
+ "vue-template-compiler": "2.6.8"
},
"engines": {
"node": ">=8.9",
diff --git a/src/App.vue b/src/App.vue
index ab408f3e..ec9032c1 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,11 +1,11 @@
-
+
diff --git a/src/api/remoteSearch.js b/src/api/remoteSearch.js
index f2792789..4bf914bc 100644
--- a/src/api/remoteSearch.js
+++ b/src/api/remoteSearch.js
@@ -7,3 +7,11 @@ export function userSearch(name) {
params: { name }
})
}
+
+export function transactionList(query) {
+ return request({
+ url: '/transaction/list',
+ method: 'get',
+ params: query
+ })
+}
diff --git a/src/api/role.js b/src/api/role.js
new file mode 100644
index 00000000..f6a983f1
--- /dev/null
+++ b/src/api/role.js
@@ -0,0 +1,38 @@
+import request from '@/utils/request'
+
+export function getRoutes() {
+ return request({
+ url: '/routes',
+ method: 'get'
+ })
+}
+
+export function getRoles() {
+ return request({
+ url: '/roles',
+ method: 'get'
+ })
+}
+
+export function addRole(data) {
+ return request({
+ url: '/role',
+ method: 'post',
+ data
+ })
+}
+
+export function updateRole(id, data) {
+ return request({
+ url: `/role/${id}`,
+ method: 'put',
+ data
+ })
+}
+
+export function deleteRole(id) {
+ return request({
+ url: `/role/${id}`,
+ method: 'delete'
+ })
+}
diff --git a/src/api/transaction.js b/src/api/transaction.js
deleted file mode 100644
index dfe64392..00000000
--- a/src/api/transaction.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import request from '@/utils/request'
-
-export function fetchList(query) {
- return request({
- url: '/transaction/list',
- method: 'get',
- params: query
- })
-}
diff --git a/src/api/login.js b/src/api/user.js
similarity index 57%
rename from src/api/login.js
rename to src/api/user.js
index a64935c3..a8052005 100644
--- a/src/api/login.js
+++ b/src/api/user.js
@@ -1,25 +1,14 @@
import request from '@/utils/request'
-export function loginByUsername(username, password) {
- const data = {
- username,
- password
- }
+export function login(data) {
return request({
- url: '/login/login',
+ url: '/user/login',
method: 'post',
data
})
}
-export function logout() {
- return request({
- url: '/login/logout',
- method: 'post'
- })
-}
-
-export function getUserInfo(token) {
+export function getInfo(token) {
return request({
url: '/user/info',
method: 'get',
@@ -27,3 +16,10 @@ export function getUserInfo(token) {
})
}
+export function logout() {
+ return request({
+ url: '/user/logout',
+ method: 'post'
+ })
+}
+
diff --git a/src/components/BackToTop/index.vue b/src/components/BackToTop/index.vue
index 39977178..0c1ff792 100644
--- a/src/components/BackToTop/index.vue
+++ b/src/components/BackToTop/index.vue
@@ -4,7 +4,7 @@
回到顶部
-
+
diff --git a/src/components/Breadcrumb/index.vue b/src/components/Breadcrumb/index.vue
index 5f4d054f..0dffa681 100644
--- a/src/components/Breadcrumb/index.vue
+++ b/src/components/Breadcrumb/index.vue
@@ -3,7 +3,7 @@
{{
- generateTitle(item.meta.title) }}
+ generateTitle(item.meta.title) }}
{{ generateTitle(item.meta.title) }}
diff --git a/src/components/Charts/keyboard.vue b/src/components/Charts/keyboard.vue
index 857b26ae..3f061bd0 100644
--- a/src/components/Charts/keyboard.vue
+++ b/src/components/Charts/keyboard.vue
@@ -1,5 +1,5 @@
-
+
+
+
+
+
diff --git a/src/components/SizeSelect/index.vue b/src/components/SizeSelect/index.vue
index 6d3cd43a..e88065b4 100644
--- a/src/components/SizeSelect/index.vue
+++ b/src/components/SizeSelect/index.vue
@@ -4,8 +4,10 @@
- {{
- item.label }}
+
+ {{
+ item.label }}
+
@@ -30,7 +32,7 @@ export default {
methods: {
handleSetSize(size) {
this.$ELEMENT.size = size
- this.$store.dispatch('setSize', size)
+ this.$store.dispatch('app/setSize', size)
this.refreshView()
this.$message({
message: 'Switch Size Success',
@@ -39,7 +41,7 @@ export default {
},
refreshView() {
// In order to make the cached page re-rendered
- this.$store.dispatch('delAllCachedViews', this.$route)
+ this.$store.dispatch('tagsView/delAllCachedViews', this.$route)
const { fullPath } = this.$route
diff --git a/src/components/Sticky/index.vue b/src/components/Sticky/index.vue
index 5624a989..fa165bc7 100644
--- a/src/components/Sticky/index.vue
+++ b/src/components/Sticky/index.vue
@@ -1,6 +1,9 @@
-
+
sticky
diff --git a/src/components/SvgIcon/index.vue b/src/components/SvgIcon/index.vue
index b0b6d4cb..27da76cf 100644
--- a/src/components/SvgIcon/index.vue
+++ b/src/components/SvgIcon/index.vue
@@ -1,6 +1,6 @@
-
+
diff --git a/src/components/TextHoverEffect/Mallki.vue b/src/components/TextHoverEffect/Mallki.vue
index 4ea29fc2..5d6d16ca 100644
--- a/src/components/TextHoverEffect/Mallki.vue
+++ b/src/components/TextHoverEffect/Mallki.vue
@@ -1,8 +1,8 @@
{{ text }}
-
-
+
+
diff --git a/src/components/ThemePicker/index.vue b/src/components/ThemePicker/index.vue
index 52419929..42eba3fb 100644
--- a/src/components/ThemePicker/index.vue
+++ b/src/components/ThemePicker/index.vue
@@ -1,8 +1,10 @@
+ popper-class="theme-picker-dropdown"
+ />
-
diff --git a/src/components/TreeTable/readme.md b/src/components/TreeTable/readme.md
deleted file mode 100644
index 5b598e11..00000000
--- a/src/components/TreeTable/readme.md
+++ /dev/null
@@ -1,89 +0,0 @@
-## 写在前面
-此组件仅提供一个创建TreeTable的解决思路
-
-## prop说明
-#### *data*
- **必填**
-
- 原始数据,要求是一个数组或者对象
- ```javascript
- [{
- key1: value1,
- key2: value2,
- children: [{
- key1: value1
- },
- {
- key1: value1
- }]
- },
- {
- key1: value1
- }]
- ```
- 或者
- ```javascript
- {
- key1: value1,
- key2: value2,
- children: [{
- key1: value1
- },
- {
- key1: value1
- }]
- }
- ```
-
-#### columns
- 列属性,要求是一个数组
-
- 1. text: 显示在表头的文字
- 2. value: 对应data的key。treeTable将显示相应的value
- 3. width: 每列的宽度,为一个数字(可选)
-
- 如果你想要每个字段都有自定义的样式或者嵌套其他组件,columns可不提供,直接像在el-table一样写即可,如果没有自定义内容,提供columns将更加的便捷方便
-
- 如果你有几个字段是需要自定义的,几个不需要,那么可以将不需要自定义的字段放入columns,将需要自定义的内容放入到slot中,详情见后文
- ```javascript
- [{
- value:string,
- text:string,
- width:number
- },{
- value:string,
- text:string,
- width:number
- }]
- ```
-
-#### expandAll
- 是否默认全部展开,boolean值,默认为false
-
-#### evalFunc
- 解析函数,function,非必须
-
- 如果不提供,将使用默认的[evalFunc](./eval.js)
-
- 如果提供了evalFunc,那么会用提供的evalFunc去解析data,并返回treeTable渲染所需要的值。如何编写一个evalFunc,请参考[*eval.js*](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/components/TreeTable/eval.js)或[*customEval.js*](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/views/table/treeTable/customEval.js)
-
-#### evalArgs
- 解析函数的参数,是一个数组
-
- **请注意,自定义的解析函数参数第一个为this.data,第二个参数为, this.expandAll,你不需要在evalArgs填写。一定记住,这两个参数是强制性的,并且位置不可颠倒** *this.data为需要解析的数据,this.expandAll为是否默认展开*
-
- 如你的解析函数需要的参数为`(this.data, this.expandAll,1,2,3,4)`,那么你只需要将`[1,2,3,4]`赋值给`evalArgs`就可以了
-
- 如果你的解析函数参数只有`(this.data, this.expandAll)`,那么就可以不用填写evalArgs了
-
- 具体可参考[*customEval.js*](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/views/table/treeTable/customEval.js)的函数参数和[customTreeTable](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/views/table/treeTable/customTreeTable.vue)的`evalArgs`属性值
-
- ## slot
- 这是一个自定义列的插槽。
-
- 默认情况下,treeTable只有一行行展示数据的功能。但是一般情况下,我们会要给行加上一个操作按钮或者根据当行数据展示不同的样式,这时我们就需要自定义列了。请参考[customTreeTable](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/views/table/treeTable/customTreeTable.vue),[实例效果](https://panjiachen.github.io/vue-element-admin/#/table/tree-table)
-
- `slot`和`columns属性`可同时存在,columns里面的数据列会在slot自定义列的左边展示
-
- ## 其他
- 如果有其他的需求,请参考[el-table](http://element-cn.eleme.io/#/en-US/component/table)的api自行修改index.vue
diff --git a/src/components/Upload/singleImage.vue b/src/components/Upload/singleImage.vue
index d8536f0d..7109ad2e 100644
--- a/src/components/Upload/singleImage.vue
+++ b/src/components/Upload/singleImage.vue
@@ -7,15 +7,18 @@
:on-success="handleImageSuccess"
class="image-uploader"
drag
- action="https://httpbin.org/post">
-
-
将文件拖到此处,或点击上传
+ action="https://httpbin.org/post"
+ >
+
+
+ 将文件拖到此处,或点击上传
+
-
+
diff --git a/src/components/Upload/singleImage2.vue b/src/components/Upload/singleImage2.vue
index cf4dc0b7..259e0b1b 100644
--- a/src/components/Upload/singleImage2.vue
+++ b/src/components/Upload/singleImage2.vue
@@ -7,15 +7,18 @@
:on-success="handleImageSuccess"
class="image-uploader"
drag
- action="https://httpbin.org/post">
-
-
Drag或点击上传
+ action="https://httpbin.org/post"
+ >
+
+
+ Drag或点击上传
+
-
+
diff --git a/src/components/Upload/singleImage3.vue b/src/components/Upload/singleImage3.vue
index 2cce98da..2fcad8f7 100644
--- a/src/components/Upload/singleImage3.vue
+++ b/src/components/Upload/singleImage3.vue
@@ -7,15 +7,18 @@
:on-success="handleImageSuccess"
class="image-uploader"
drag
- action="https://httpbin.org/post">
-
-
将文件拖到此处,或点击上传
+ action="https://httpbin.org/post"
+ >
+
+
+ 将文件拖到此处,或点击上传
+
-
+
@@ -23,7 +26,7 @@
-
+
diff --git a/src/components/UploadExcel/index.vue b/src/components/UploadExcel/index.vue
index a6b8dbce..9e8ba8b6 100644
--- a/src/components/UploadExcel/index.vue
+++ b/src/components/UploadExcel/index.vue
@@ -3,7 +3,9 @@
Drop excel file here or
- Browse
+
+ Browse
+
diff --git a/src/directive/el-dragDialog/drag.js b/src/directive/el-dragDialog/drag.js
index 58e29110..299e9854 100644
--- a/src/directive/el-dragDialog/drag.js
+++ b/src/directive/el-dragDialog/drag.js
@@ -1,4 +1,4 @@
-export default{
+export default {
bind(el, binding, vnode) {
const dialogHeaderEl = el.querySelector('.el-dialog__header')
const dragDom = el.querySelector('.el-dialog')
diff --git a/src/directive/el-table/adaptive.js b/src/directive/el-table/adaptive.js
new file mode 100644
index 00000000..3fa29c91
--- /dev/null
+++ b/src/directive/el-table/adaptive.js
@@ -0,0 +1,42 @@
+
+import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event'
+
+/**
+ * How to use
+ *
...
+ * el-table height is must be set
+ * bottomOffset: 30(default) // The height of the table from the bottom of the page.
+ */
+
+const doResize = (el, binding, vnode) => {
+ const { componentInstance: $table } = vnode
+
+ const { value } = binding
+
+ if (!$table.height) {
+ throw new Error(`el-$table must set the height. Such as height='100px'`)
+ }
+ const bottomOffset = (value && value.bottomOffset) || 30
+
+ if (!$table) return
+
+ const height = window.innerHeight - el.getBoundingClientRect().top - bottomOffset
+ $table.layout.setHeight(height)
+ $table.doLayout()
+}
+
+export default {
+ bind(el, binding, vnode) {
+ el.resizeListener = () => {
+ doResize(el, binding, vnode)
+ }
+
+ addResizeListener(el, el.resizeListener)
+ },
+ inserted(el, binding, vnode) {
+ doResize(el, binding, vnode)
+ },
+ unbind(el) {
+ removeResizeListener(el, el.resizeListener)
+ }
+}
diff --git a/src/directive/el-table/index.js b/src/directive/el-table/index.js
new file mode 100644
index 00000000..d4cf406d
--- /dev/null
+++ b/src/directive/el-table/index.js
@@ -0,0 +1,14 @@
+
+import adaptive from './adaptive'
+
+const install = function(Vue) {
+ Vue.directive('el-height-adaptive-table', adaptive)
+}
+
+if (window.Vue) {
+ window['el-height-adaptive-table'] = adaptive
+ Vue.use(install); // eslint-disable-line
+}
+
+adaptive.install = install
+export default adaptive
diff --git a/src/directive/permission/permission.js b/src/directive/permission/permission.js
index 17b85d79..1fc8f136 100644
--- a/src/directive/permission/permission.js
+++ b/src/directive/permission/permission.js
@@ -1,7 +1,7 @@
import store from '@/store'
-export default{
+export default {
inserted(el, binding, vnode) {
const { value } = binding
const roles = store.getters && store.getters.roles
diff --git a/src/directive/waves/waves.js b/src/directive/waves/waves.js
index a77f876e..ec2ff439 100644
--- a/src/directive/waves/waves.js
+++ b/src/directive/waves/waves.js
@@ -1,42 +1,72 @@
import './waves.css'
-export default{
- bind(el, binding) {
- el.addEventListener('click', e => {
- const customOpts = Object.assign({}, binding.value)
- const opts = Object.assign({
- ele: el, // 波纹作用元素
- type: 'hit', // hit 点击位置扩散 center中心点扩展
- color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
- }, customOpts)
- const target = opts.ele
- if (target) {
- target.style.position = 'relative'
- target.style.overflow = 'hidden'
- const rect = target.getBoundingClientRect()
- let ripple = target.querySelector('.waves-ripple')
- if (!ripple) {
- ripple = document.createElement('span')
- ripple.className = 'waves-ripple'
- ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'
- target.appendChild(ripple)
- } else {
- ripple.className = 'waves-ripple'
- }
- switch (opts.type) {
- case 'center':
- ripple.style.top = (rect.height / 2 - ripple.offsetHeight / 2) + 'px'
- ripple.style.left = (rect.width / 2 - ripple.offsetWidth / 2) + 'px'
- break
- default:
- ripple.style.top = (e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop || document.body.scrollTop) + 'px'
- ripple.style.left = (e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft || document.body.scrollLeft) + 'px'
- }
- ripple.style.backgroundColor = opts.color
- ripple.className = 'waves-ripple z-active'
- return false
+const context = '@@wavesContext'
+
+function handleClick(el, binding) {
+ function handle(e) {
+ const customOpts = Object.assign({}, binding.value)
+ const opts = Object.assign({
+ ele: el, // 波纹作用元素
+ type: 'hit', // hit 点击位置扩散 center中心点扩展
+ color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
+ },
+ customOpts
+ )
+ const target = opts.ele
+ if (target) {
+ target.style.position = 'relative'
+ target.style.overflow = 'hidden'
+ const rect = target.getBoundingClientRect()
+ let ripple = target.querySelector('.waves-ripple')
+ if (!ripple) {
+ ripple = document.createElement('span')
+ ripple.className = 'waves-ripple'
+ ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'
+ target.appendChild(ripple)
+ } else {
+ ripple.className = 'waves-ripple'
}
- }, false)
+ switch (opts.type) {
+ case 'center':
+ ripple.style.top = rect.height / 2 - ripple.offsetHeight / 2 + 'px'
+ ripple.style.left = rect.width / 2 - ripple.offsetWidth / 2 + 'px'
+ break
+ default:
+ ripple.style.top =
+ (e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop ||
+ document.body.scrollTop) + 'px'
+ ripple.style.left =
+ (e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft ||
+ document.body.scrollLeft) + 'px'
+ }
+ ripple.style.backgroundColor = opts.color
+ ripple.className = 'waves-ripple z-active'
+ return false
+ }
}
+
+ if (!el[context]) {
+ el[context] = {
+ removeHandle: handle
+ }
+ } else {
+ el[context].removeHandle = handle
+ }
+
+ return handle
}
+export default {
+ bind(el, binding) {
+ el.addEventListener('click', handleClick(el, binding), false)
+ },
+ update(el, binding) {
+ el.removeEventListener('click', el[context].removeHandle, false)
+ el.addEventListener('click', handleClick(el, binding), false)
+ },
+ unbind(el) {
+ el.removeEventListener('click', el[context].removeHandle, false)
+ el[context] = null
+ delete el[context]
+ }
+}
diff --git a/src/errorLog.js b/src/errorLog.js
deleted file mode 100644
index 00b18b72..00000000
--- a/src/errorLog.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import Vue from 'vue'
-import store from './store'
-
-// you can set only in production env show the error-log
-if (process.env.NODE_ENV === 'production') {
- Vue.config.errorHandler = function(err, vm, info, a) {
- // Don't ask me why I use Vue.nextTick, it just a hack.
- // detail see https://forum.vuejs.org/t/dispatch-in-vue-config-errorhandler-has-some-problem/23500
- Vue.nextTick(() => {
- store.dispatch('addErrorLog', {
- err,
- vm,
- info,
- url: window.location.href
- })
- console.error(err, info)
- })
- }
-}
diff --git a/src/icons/svg/tree-table.svg b/src/icons/svg/tree-table.svg
new file mode 100644
index 00000000..8aafdb82
--- /dev/null
+++ b/src/icons/svg/tree-table.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/lang/en.js b/src/lang/en.js
index 05b34598..963c60d3 100644
--- a/src/lang/en.js
+++ b/src/lang/en.js
@@ -6,6 +6,7 @@ export default {
guide: 'Guide',
permission: 'Permission',
pagePermission: 'Page Permission',
+ rolePermission: 'Role Permission',
directivePermission: 'Directive Permission',
icons: 'Icons',
components: 'Components',
@@ -56,6 +57,7 @@ export default {
excel: 'Excel',
exportExcel: 'Export Excel',
selectExcel: 'Export Selected',
+ mergeHeader: 'Merge Header',
uploadExcel: 'Upload Excel',
zip: 'Zip',
pdf: 'PDF',
@@ -86,9 +88,14 @@ export default {
github: 'Github Repository'
},
permission: {
+ addRole: 'New Role',
+ editPermission: 'Edit Permission',
roles: 'Your roles',
switchRoles: 'Switch roles',
- tips: 'In some cases it is not suitable to use v-permission, such as element Tab component or el-table-column and other asynchronous rendering dom cases which can only be achieved by manually setting the v-if.'
+ tips: 'In some cases it is not suitable to use v-permission, such as element Tab component or el-table-column and other asynchronous rendering dom cases which can only be achieved by manually setting the v-if.',
+ delete: 'Delete',
+ confirm: 'Confirm',
+ cancel: 'Cancel'
},
guide: {
description: 'The guide page is useful for some people who entered the project for the first time. You can briefly introduce the features of the project. Demo is based on ',
diff --git a/src/lang/es.js b/src/lang/es.js
index 8575d382..31fa8303 100755
--- a/src/lang/es.js
+++ b/src/lang/es.js
@@ -5,6 +5,7 @@ export default {
documentation: 'Documentación',
guide: 'Guía',
permission: 'Permisos',
+ rolePermission: 'Permisos de rol',
pagePermission: 'Permisos de la página',
directivePermission: 'Permisos de la directiva',
icons: 'Iconos',
@@ -56,6 +57,7 @@ export default {
excel: 'Excel',
exportExcel: 'Exportar a Excel',
selectExcel: 'Export seleccionado',
+ mergeHeader: 'Merge Header',
uploadExcel: 'Subir Excel',
zip: 'Zip',
pdf: 'PDF',
@@ -86,9 +88,14 @@ export default {
github: 'Repositorio Github'
},
permission: {
+ addRole: 'Nuevo rol',
+ editPermission: 'Permiso de edición',
roles: 'Tus permisos',
switchRoles: 'Cambiar permisos',
- tips: 'In some cases it is not suitable to use v-permission, such as element Tab component or el-table-column and other asynchronous rendering dom cases which can only be achieved by manually setting the v-if.'
+ tips: 'In some cases it is not suitable to use v-permission, such as element Tab component or el-table-column and other asynchronous rendering dom cases which can only be achieved by manually setting the v-if.',
+ delete: 'Borrar',
+ confirm: 'Confirmar',
+ cancel: 'Cancelar'
},
guide: {
description: 'The guide page is useful for some people who entered the project for the first time. You can briefly introduce the features of the project. Demo is based on ',
diff --git a/src/lang/zh.js b/src/lang/zh.js
index 1fd18355..574cba11 100644
--- a/src/lang/zh.js
+++ b/src/lang/zh.js
@@ -5,6 +5,7 @@ export default {
documentation: '文档',
guide: '引导页',
permission: '权限测试页',
+ rolePermission: '角色权限',
pagePermission: '页面权限',
directivePermission: '指令权限',
icons: '图标',
@@ -54,9 +55,10 @@ export default {
page404: '404',
errorLog: '错误日志',
excel: 'Excel',
- exportExcel: 'Export Excel',
- selectExcel: 'Export Selected',
- uploadExcel: 'Upload Excel',
+ exportExcel: '导出 Excel',
+ selectExcel: '导出 已选择项',
+ mergeHeader: '导出 多级表头',
+ uploadExcel: '上传 Excel',
zip: 'Zip',
pdf: 'PDF',
exportZip: 'Export Zip',
@@ -86,9 +88,14 @@ export default {
github: 'Github 地址'
},
permission: {
+ addRole: '新增角色',
+ editPermission: '编辑权限',
roles: '你的权限',
switchRoles: '切换权限',
- tips: '在某些情况下,不适合使用 v-permission。例如:Element-UI 的 Tab 组件或 el-table-column 以及其它动态渲染 dom 的场景。你只能通过手动设置 v-if 来实现。'
+ tips: '在某些情况下,不适合使用 v-permission。例如:Element-UI 的 Tab 组件或 el-table-column 以及其它动态渲染 dom 的场景。你只能通过手动设置 v-if 来实现。',
+ delete: '删除',
+ confirm: '确定',
+ cancel: '取消'
},
guide: {
description: '引导页对于一些第一次进入项目的人很有用,你可以简单介绍下项目的功能。本 Demo 是基于',
diff --git a/src/layout/Layout.vue b/src/layout/Layout.vue
new file mode 100644
index 00000000..a991b771
--- /dev/null
+++ b/src/layout/Layout.vue
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
diff --git a/src/views/layout/components/AppMain.vue b/src/layout/components/AppMain.vue
similarity index 57%
rename from src/views/layout/components/AppMain.vue
rename to src/layout/components/AppMain.vue
index b6a3378f..f6e1ea10 100644
--- a/src/views/layout/components/AppMain.vue
+++ b/src/layout/components/AppMain.vue
@@ -2,7 +2,7 @@
@@ -22,13 +22,28 @@ export default {
}
-
diff --git a/src/views/layout/components/Navbar.vue b/src/layout/components/Navbar.vue
similarity index 81%
rename from src/views/layout/components/Navbar.vue
rename to src/layout/components/Navbar.vue
index 680ba571..073fede5 100644
--- a/src/views/layout/components/Navbar.vue
+++ b/src/layout/components/Navbar.vue
@@ -1,32 +1,29 @@
-
+
-
+
@@ -45,8 +54,8 @@ export default {
visitedViews() {
return this.$store.state.tagsView.visitedViews
},
- routers() {
- return this.$store.state.permission.routers
+ routes() {
+ return this.$store.state.permission.routes
}
},
watch: {
@@ -93,18 +102,18 @@ export default {
return tags
},
initTags() {
- const affixTags = this.affixTags = this.filterAffixTags(this.routers)
+ const affixTags = this.affixTags = this.filterAffixTags(this.routes)
for (const tag of affixTags) {
// Must have tag name
if (tag.name) {
- this.$store.dispatch('addVisitedView', tag)
+ this.$store.dispatch('tagsView/addVisitedView', tag)
}
}
},
addTags() {
const { name } = this.$route
if (name) {
- this.$store.dispatch('addView', this.$route)
+ this.$store.dispatch('tagsView/addView', this.$route)
}
return false
},
@@ -116,7 +125,7 @@ export default {
this.$refs.scrollPane.moveToTarget(tag)
// when query is different then update
if (tag.to.fullPath !== this.$route.fullPath) {
- this.$store.dispatch('updateVisitedView', this.$route)
+ this.$store.dispatch('tagsView/updateVisitedView', this.$route)
}
break
}
@@ -124,7 +133,7 @@ export default {
})
},
refreshSelectedTag(view) {
- this.$store.dispatch('delCachedView', view).then(() => {
+ this.$store.dispatch('tagsView/delCachedView', view).then(() => {
const { fullPath } = view
this.$nextTick(() => {
this.$router.replace({
@@ -134,7 +143,7 @@ export default {
})
},
closeSelectedTag(view) {
- this.$store.dispatch('delView', view).then(({ visitedViews }) => {
+ this.$store.dispatch('tagsView/delView', view).then(({ visitedViews }) => {
if (this.isActive(view)) {
this.toLastView(visitedViews)
}
@@ -142,12 +151,12 @@ export default {
},
closeOthersTags() {
this.$router.push(this.selectedTag)
- this.$store.dispatch('delOthersViews', this.selectedTag).then(() => {
+ this.$store.dispatch('tagsView/delOthersViews', this.selectedTag).then(() => {
this.moveToCurrentTag()
})
},
closeAllTags(view) {
- this.$store.dispatch('delAllViews').then(({ visitedViews }) => {
+ this.$store.dispatch('tagsView/delAllViews').then(({ visitedViews }) => {
if (this.affixTags.some(tag => tag.path === view.path)) {
return
}
diff --git a/src/views/layout/components/index.js b/src/layout/components/index.js
similarity index 80%
rename from src/views/layout/components/index.js
rename to src/layout/components/index.js
index 5262e113..e9f79ddd 100644
--- a/src/views/layout/components/index.js
+++ b/src/layout/components/index.js
@@ -2,3 +2,4 @@ export { default as Navbar } from './Navbar'
export { default as Sidebar } from './Sidebar/index.vue'
export { default as TagsView } from './TagsView/index.vue'
export { default as AppMain } from './AppMain'
+export { default as Settings } from './Settings'
diff --git a/src/views/layout/mixin/ResizeHandler.js b/src/layout/mixin/ResizeHandler.js
similarity index 66%
rename from src/views/layout/mixin/ResizeHandler.js
rename to src/layout/mixin/ResizeHandler.js
index 352ab133..80d8fbfa 100644
--- a/src/views/layout/mixin/ResizeHandler.js
+++ b/src/layout/mixin/ResizeHandler.js
@@ -7,7 +7,7 @@ export default {
watch: {
$route(route) {
if (this.device === 'mobile' && this.sidebar.opened) {
- store.dispatch('closeSideBar', { withoutAnimation: false })
+ store.dispatch('app/closeSideBar', { withoutAnimation: false })
}
}
},
@@ -17,8 +17,8 @@ export default {
mounted() {
const isMobile = this.isMobile()
if (isMobile) {
- store.dispatch('toggleDevice', 'mobile')
- store.dispatch('closeSideBar', { withoutAnimation: true })
+ store.dispatch('app/toggleDevice', 'mobile')
+ store.dispatch('app/closeSideBar', { withoutAnimation: true })
}
},
methods: {
@@ -29,10 +29,10 @@ export default {
resizeHandler() {
if (!document.hidden) {
const isMobile = this.isMobile()
- store.dispatch('toggleDevice', isMobile ? 'mobile' : 'desktop')
+ store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
if (isMobile) {
- store.dispatch('closeSideBar', { withoutAnimation: true })
+ store.dispatch('app/closeSideBar', { withoutAnimation: true })
}
}
}
diff --git a/src/main.js b/src/main.js
index 856ebd3e..375a6b6e 100644
--- a/src/main.js
+++ b/src/main.js
@@ -5,7 +5,7 @@ import Cookies from 'js-cookie'
import 'normalize.css/normalize.css' // A modern alternative to CSS resets
import Element from 'element-ui'
-import 'element-ui/lib/theme-chalk/index.css'
+import './styles/element-variables.scss'
import '@/styles/index.scss' // global css
@@ -15,8 +15,8 @@ import router from './router'
import i18n from './lang' // Internationalization
import './icons' // icon
-import './errorLog' // error log
import './permission' // permission control
+import './utils/errorLog' // error log
import * as filters from './filters' // global filters
diff --git a/src/permission.js b/src/permission.js
index e556cb00..7cc2a5cf 100644
--- a/src/permission.js
+++ b/src/permission.js
@@ -2,62 +2,69 @@ import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // progress bar
-import 'nprogress/nprogress.css'// progress bar style
-import { getToken } from '@/utils/auth' // getToken from cookie
+import 'nprogress/nprogress.css' // progress bar style
+import { getToken } from '@/utils/auth' // get token from cookie
-NProgress.configure({ showSpinner: false })// NProgress Configuration
+NProgress.configure({ showSpinner: false }) // NProgress Configuration
-// permission judge function
-function hasPermission(roles, permissionRoles) {
- if (roles.indexOf('admin') >= 0) return true // admin permission passed directly
- if (!permissionRoles) return true
- return roles.some(role => permissionRoles.indexOf(role) >= 0)
-}
+const whiteList = ['/login', '/auth-redirect'] // no redirect whitelist
-const whiteList = ['/login', '/auth-redirect']// no redirect whitelist
+router.beforeEach(async(to, from, next) => {
+ // start progress bar
+ NProgress.start()
-router.beforeEach((to, from, next) => {
- NProgress.start() // start progress bar
- if (getToken()) { // determine if there has token
- /* has token*/
+ // determine whether the user has logged in
+ const hasToken = getToken()
+
+ if (hasToken) {
if (to.path === '/login') {
+ // if is logged in, redirect to the home page
next({ path: '/' })
- NProgress.done() // if current page is dashboard will not trigger afterEach hook, so manually handle it
+ NProgress.done()
} else {
- if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息
- store.dispatch('GetUserInfo').then(res => { // 拉取user_info
- const roles = res.data.roles // note: roles must be a array! such as: ['editor','develop']
- store.dispatch('GenerateRoutes', { roles }).then(() => { // 根据roles权限生成可访问的路由表
- router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
- next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
- })
- }).catch((err) => {
- store.dispatch('FedLogOut').then(() => {
- Message.error(err)
- next({ path: '/' })
- })
- })
+ // determine whether the user has obtained his permission roles through getInfo
+ const hasRoles = store.getters.roles && store.getters.roles.length > 0
+ if (hasRoles) {
+ next()
} else {
- // 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓
- if (hasPermission(store.getters.roles, to.meta.roles)) {
- next()
- } else {
- next({ path: '/401', replace: true, query: { noGoBack: true }})
+ try {
+ // get user info
+ // note: roles must be a object array! such as: ['admin'] or ,['developer','editor']
+ const { roles } = await store.dispatch('user/getInfo')
+
+ // generate accessible routes map based on roles
+ const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
+
+ // dynamically add accessible routes
+ router.addRoutes(accessRoutes)
+
+ // hack method to ensure that addRoutes is complete
+ // set the replace: true, so the navigation will not leave a history record
+ next({ ...to, replace: true })
+ } catch (error) {
+ // remove token and go to login page to re-login
+ await store.dispatch('user/resetToken')
+ Message.error(error || 'Has Error')
+ next(`/login?redirect=${to.path}`)
+ NProgress.done()
}
- // 可删 ↑
}
}
} else {
/* has no token*/
- if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
+
+ if (whiteList.indexOf(to.path) !== -1) {
+ // in the free login whitelist, go directly
next()
} else {
- next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页
- NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it
+ // other pages that do not have permission to access are redirected to the login page.
+ next(`/login?redirect=${to.path}`)
+ NProgress.done()
}
}
})
router.afterEach(() => {
- NProgress.done() // finish progress bar
+ // finish progress bar
+ NProgress.done()
})
diff --git a/src/router/index.js b/src/router/index.js
index 60524517..6f70e754 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -4,12 +4,13 @@ import Router from 'vue-router'
Vue.use(Router)
/* Layout */
-import Layout from '@/views/layout/Layout'
+import Layout from '@/layout/Layout'
/* Router Modules */
import componentsRouter from './modules/components'
import chartsRouter from './modules/charts'
import tableRouter from './modules/table'
+import treeTableRouter from './modules/tree-table'
import nestedRouter from './modules/nested'
/** note: sub-menu only appear when children.length>=1
@@ -32,7 +33,7 @@ import nestedRouter from './modules/nested'
affix: true if true, the tag will affix in the tags-view
}
**/
-export const constantRouterMap = [
+export const constantRoutes = [
{
path: '/redirect',
component: Layout,
@@ -80,7 +81,6 @@ export const constantRouterMap = [
{
path: '/documentation',
component: Layout,
- redirect: '/documentation/index',
children: [
{
path: 'index',
@@ -105,13 +105,7 @@ export const constantRouterMap = [
}
]
-export default new Router({
- // mode: 'history', // require service support
- scrollBehavior: () => ({ y: 0 }),
- routes: constantRouterMap
-})
-
-export const asyncRouterMap = [
+export const asyncRoutes = [
{
path: '/permission',
component: Layout,
@@ -140,6 +134,15 @@ export const asyncRouterMap = [
title: 'directivePermission'
// if do not set roles, means: this page does not require permission
}
+ },
+ {
+ path: 'role',
+ component: () => import('@/views/permission/role'),
+ name: 'RolePermission',
+ meta: {
+ title: 'rolePermission',
+ roles: ['admin']
+ }
}
]
},
@@ -162,6 +165,7 @@ export const asyncRouterMap = [
chartsRouter,
nestedRouter,
tableRouter,
+ treeTableRouter,
{
path: '/example',
@@ -269,6 +273,12 @@ export const asyncRouterMap = [
name: 'SelectExcel',
meta: { title: 'selectExcel' }
},
+ {
+ path: 'export-merge-header',
+ component: () => import('@/views/excel/mergeHeader'),
+ name: 'MergeHeader',
+ meta: { title: 'mergeHeader' }
+ },
{
path: 'upload-excel',
component: () => import('@/views/excel/uploadExcel'),
@@ -367,3 +377,19 @@ export const asyncRouterMap = [
{ path: '*', redirect: '/404', hidden: true }
]
+
+const createRouter = () => new Router({
+ // mode: 'history', // require service support
+ scrollBehavior: () => ({ y: 0 }),
+ routes: constantRoutes
+})
+
+const router = createRouter()
+
+// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
+export function resetRouter() {
+ const newRouter = createRouter()
+ router.matcher = newRouter.matcher // reset router
+}
+
+export default router
diff --git a/src/router/modules/charts.js b/src/router/modules/charts.js
index d11f6efd..bc6cdf53 100644
--- a/src/router/modules/charts.js
+++ b/src/router/modules/charts.js
@@ -1,6 +1,6 @@
/** When your routing table is too long, you can split it into small modules**/
-import Layout from '@/views/layout/Layout'
+import Layout from '@/layout/Layout'
const chartsRouter = {
path: '/charts',
diff --git a/src/router/modules/components.js b/src/router/modules/components.js
index 5fd9bd29..b1fba4fa 100644
--- a/src/router/modules/components.js
+++ b/src/router/modules/components.js
@@ -1,6 +1,6 @@
/** When your routing table is too long, you can split it into small modules**/
-import Layout from '@/views/layout/Layout'
+import Layout from '@/layout/Layout'
const componentsRouter = {
path: '/components',
diff --git a/src/router/modules/nested.js b/src/router/modules/nested.js
index ad8e31f9..f54a8e16 100644
--- a/src/router/modules/nested.js
+++ b/src/router/modules/nested.js
@@ -1,6 +1,6 @@
/** When your routing table is too long, you can split it into small modules**/
-import Layout from '@/views/layout/Layout'
+import Layout from '@/layout/Layout'
const nestedRouter = {
path: '/nested',
diff --git a/src/router/modules/table.js b/src/router/modules/table.js
index a9c4cb44..11fdbbc9 100644
--- a/src/router/modules/table.js
+++ b/src/router/modules/table.js
@@ -1,6 +1,6 @@
/** When your routing table is too long, you can split it into small modules**/
-import Layout from '@/views/layout/Layout'
+import Layout from '@/layout/Layout'
const tableRouter = {
path: '/table',
@@ -30,18 +30,6 @@ const tableRouter = {
name: 'InlineEditTable',
meta: { title: 'inlineEditTable' }
},
- {
- path: 'tree-table',
- component: () => import('@/views/table/treeTable/treeTable'),
- name: 'TreeTableDemo',
- meta: { title: 'treeTable' }
- },
- {
- path: 'custom-tree-table',
- component: () => import('@/views/table/treeTable/customTreeTable'),
- name: 'CustomTreeTableDemo',
- meta: { title: 'customTreeTable' }
- },
{
path: 'complex-table',
component: () => import('@/views/table/complexTable'),
diff --git a/src/router/modules/tree-table.js b/src/router/modules/tree-table.js
new file mode 100644
index 00000000..3996e08c
--- /dev/null
+++ b/src/router/modules/tree-table.js
@@ -0,0 +1,29 @@
+/** When your routing table is too long, you can split it into small modules**/
+
+import Layout from '@/layout/Layout'
+
+const treeTableRouter = {
+ path: '/tree-table',
+ component: Layout,
+ redirect: '/table/complex-table',
+ name: 'TreeTable',
+ meta: {
+ title: 'treeTable',
+ icon: 'tree-table'
+ },
+ children: [
+ {
+ path: 'index',
+ component: () => import('@/views/tree-table/index'),
+ name: 'TreeTableDemo',
+ meta: { title: 'treeTable' }
+ },
+ {
+ path: 'custom',
+ component: () => import('@/views/tree-table/custom'),
+ name: 'CustomTreeTableDemo',
+ meta: { title: 'customTreeTable' }
+ }
+ ]
+}
+export default treeTableRouter
diff --git a/src/settings.js b/src/settings.js
new file mode 100644
index 00000000..4b1a57d3
--- /dev/null
+++ b/src/settings.js
@@ -0,0 +1,29 @@
+export default {
+ title: 'vue-element-admin',
+
+ /**
+ * @type {boolean} true | false
+ * @description Whether show the settings right-panel
+ */
+ showSettings: true,
+
+ /**
+ * @type {boolean} true | false
+ * @description Whether need tagsView
+ */
+ tagsView: true,
+
+ /**
+ * @type {boolean} true | false
+ * @description Whether fix the header
+ */
+ fixedHeader: true,
+
+ /**
+ * @type {string | array} 'production' | ['production','development']
+ * @description Need show err logs component.
+ * The default is only used in the production env
+ * If you want to also use it in dev, you can pass ['production','development']
+ */
+ errorLog: 'production'
+}
diff --git a/src/store/getters.js b/src/store/getters.js
index cf314f5c..3fb5b068 100644
--- a/src/store/getters.js
+++ b/src/store/getters.js
@@ -9,11 +9,9 @@ const getters = {
avatar: state => state.user.avatar,
name: state => state.user.name,
introduction: state => state.user.introduction,
- status: state => state.user.status,
roles: state => state.user.roles,
- setting: state => state.user.setting,
- permission_routers: state => state.permission.routers,
- addRouters: state => state.permission.addRouters,
+ permission_routes: state => state.permission.routes,
+ addRoutes: state => state.permission.addRoutes,
errorLogs: state => state.errorLog.logs
}
export default getters
diff --git a/src/store/modules/app.js b/src/store/modules/app.js
index cfe6ce99..230f7007 100644
--- a/src/store/modules/app.js
+++ b/src/store/modules/app.js
@@ -1,59 +1,64 @@
import Cookies from 'js-cookie'
import { getLanguage } from '@/lang/index'
-const app = {
- state: {
- sidebar: {
- opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
- withoutAnimation: false
- },
- device: 'desktop',
- language: getLanguage(),
- size: Cookies.get('size') || 'medium'
+const state = {
+ sidebar: {
+ opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
+ withoutAnimation: false
},
- mutations: {
- TOGGLE_SIDEBAR: state => {
- state.sidebar.opened = !state.sidebar.opened
- state.sidebar.withoutAnimation = false
- if (state.sidebar.opened) {
- Cookies.set('sidebarStatus', 1)
- } else {
- Cookies.set('sidebarStatus', 0)
- }
- },
- CLOSE_SIDEBAR: (state, withoutAnimation) => {
+ device: 'desktop',
+ language: getLanguage(),
+ size: Cookies.get('size') || 'medium'
+}
+
+const mutations = {
+ TOGGLE_SIDEBAR: state => {
+ state.sidebar.opened = !state.sidebar.opened
+ state.sidebar.withoutAnimation = false
+ if (state.sidebar.opened) {
+ Cookies.set('sidebarStatus', 1)
+ } else {
Cookies.set('sidebarStatus', 0)
- state.sidebar.opened = false
- state.sidebar.withoutAnimation = withoutAnimation
- },
- TOGGLE_DEVICE: (state, device) => {
- state.device = device
- },
- SET_LANGUAGE: (state, language) => {
- state.language = language
- Cookies.set('language', language)
- },
- SET_SIZE: (state, size) => {
- state.size = size
- Cookies.set('size', size)
}
},
- actions: {
- toggleSideBar({ commit }) {
- commit('TOGGLE_SIDEBAR')
- },
- closeSideBar({ commit }, { withoutAnimation }) {
- commit('CLOSE_SIDEBAR', withoutAnimation)
- },
- toggleDevice({ commit }, device) {
- commit('TOGGLE_DEVICE', device)
- },
- setLanguage({ commit }, language) {
- commit('SET_LANGUAGE', language)
- },
- setSize({ commit }, size) {
- commit('SET_SIZE', size)
- }
+ CLOSE_SIDEBAR: (state, withoutAnimation) => {
+ Cookies.set('sidebarStatus', 0)
+ state.sidebar.opened = false
+ state.sidebar.withoutAnimation = withoutAnimation
+ },
+ TOGGLE_DEVICE: (state, device) => {
+ state.device = device
+ },
+ SET_LANGUAGE: (state, language) => {
+ state.language = language
+ Cookies.set('language', language)
+ },
+ SET_SIZE: (state, size) => {
+ state.size = size
+ Cookies.set('size', size)
}
}
-export default app
+const actions = {
+ toggleSideBar({ commit }) {
+ commit('TOGGLE_SIDEBAR')
+ },
+ closeSideBar({ commit }, { withoutAnimation }) {
+ commit('CLOSE_SIDEBAR', withoutAnimation)
+ },
+ toggleDevice({ commit }, device) {
+ commit('TOGGLE_DEVICE', device)
+ },
+ setLanguage({ commit }, language) {
+ commit('SET_LANGUAGE', language)
+ },
+ setSize({ commit }, size) {
+ commit('SET_SIZE', size)
+ }
+}
+
+export default {
+ namespaced: true,
+ state,
+ mutations,
+ actions
+}
diff --git a/src/store/modules/errorLog.js b/src/store/modules/errorLog.js
index 50fc1b1a..c97d452a 100644
--- a/src/store/modules/errorLog.js
+++ b/src/store/modules/errorLog.js
@@ -1,17 +1,23 @@
-const errorLog = {
- state: {
- logs: []
- },
- mutations: {
- ADD_ERROR_LOG: (state, log) => {
- state.logs.push(log)
- }
- },
- actions: {
- addErrorLog({ commit }, log) {
- commit('ADD_ERROR_LOG', log)
- }
+
+const state = {
+ logs: []
+}
+
+const mutations = {
+ ADD_ERROR_LOG: (state, log) => {
+ state.logs.push(log)
}
}
-export default errorLog
+const actions = {
+ addErrorLog({ commit }, log) {
+ commit('ADD_ERROR_LOG', log)
+ }
+}
+
+export default {
+ namespaced: true,
+ state,
+ mutations,
+ actions
+}
diff --git a/src/store/modules/permission.js b/src/store/modules/permission.js
index 13f60efb..820ca46b 100644
--- a/src/store/modules/permission.js
+++ b/src/store/modules/permission.js
@@ -1,4 +1,4 @@
-import { asyncRouterMap, constantRouterMap } from '@/router'
+import { asyncRoutes, constantRoutes } from '@/router'
/**
* 通过meta.role判断是否与当前用户权限匹配
@@ -15,17 +15,17 @@ function hasPermission(roles, route) {
/**
* 递归过滤异步路由表,返回符合用户角色权限的路由表
- * @param routes asyncRouterMap
+ * @param routes asyncRoutes
* @param roles
*/
-function filterAsyncRouter(routes, roles) {
+export function filterAsyncRoutes(routes, roles) {
const res = []
routes.forEach(route => {
const tmp = { ...route }
if (hasPermission(roles, tmp)) {
if (tmp.children) {
- tmp.children = filterAsyncRouter(tmp.children, roles)
+ tmp.children = filterAsyncRoutes(tmp.children, roles)
}
res.push(tmp)
}
@@ -34,32 +34,36 @@ function filterAsyncRouter(routes, roles) {
return res
}
-const permission = {
- state: {
- routers: [],
- addRouters: []
- },
- mutations: {
- SET_ROUTERS: (state, routers) => {
- state.addRouters = routers
- state.routers = constantRouterMap.concat(routers)
- }
- },
- actions: {
- GenerateRoutes({ commit }, data) {
- return new Promise(resolve => {
- const { roles } = data
- let accessedRouters
- if (roles.includes('admin')) {
- accessedRouters = asyncRouterMap
- } else {
- accessedRouters = filterAsyncRouter(asyncRouterMap, roles)
- }
- commit('SET_ROUTERS', accessedRouters)
- resolve()
- })
- }
+const state = {
+ routes: [],
+ addRoutes: []
+}
+
+const mutations = {
+ SET_ROUTES: (state, routes) => {
+ state.addRoutes = routes
+ state.routes = constantRoutes.concat(routes)
}
}
-export default permission
+const actions = {
+ generateRoutes({ commit }, roles) {
+ return new Promise(resolve => {
+ let accessedRoutes
+ if (roles.includes('admin')) {
+ accessedRoutes = asyncRoutes
+ } else {
+ accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
+ }
+ commit('SET_ROUTES', accessedRoutes)
+ resolve(accessedRoutes)
+ })
+ }
+}
+
+export default {
+ namespaced: true,
+ state,
+ mutations,
+ actions
+}
diff --git a/src/store/modules/settings.js b/src/store/modules/settings.js
new file mode 100644
index 00000000..b17b987f
--- /dev/null
+++ b/src/store/modules/settings.js
@@ -0,0 +1,30 @@
+import defaultSettings from '@/settings'
+const { showSettings, tagsView, fixedHeader } = defaultSettings
+
+const state = {
+ showSettings: showSettings,
+ tagsView: tagsView,
+ fixedHeader: fixedHeader
+}
+
+const mutations = {
+ CHANGE_SETTING: (state, { key, value }) => {
+ if (state.hasOwnProperty(key)) {
+ state[key] = value
+ }
+ }
+}
+
+const actions = {
+ changeSetting({ commit }, data) {
+ commit('CHANGE_SETTING', data)
+ }
+}
+
+export default {
+ namespaced: true,
+ state,
+ mutations,
+ actions
+}
+
diff --git a/src/store/modules/tagsView.js b/src/store/modules/tagsView.js
index 378cbcd3..5cbe32f3 100644
--- a/src/store/modules/tagsView.js
+++ b/src/store/modules/tagsView.js
@@ -1,161 +1,166 @@
-const tagsView = {
- state: {
- visitedViews: [],
- cachedViews: []
- },
- mutations: {
- ADD_VISITED_VIEW: (state, view) => {
- if (state.visitedViews.some(v => v.path === view.path)) return
- state.visitedViews.push(
- Object.assign({}, view, {
- title: view.meta.title || 'no-name'
- })
- )
- },
- ADD_CACHED_VIEW: (state, view) => {
- if (state.cachedViews.includes(view.name)) return
- if (!view.meta.noCache) {
- state.cachedViews.push(view.name)
- }
- },
- DEL_VISITED_VIEW: (state, view) => {
- for (const [i, v] of state.visitedViews.entries()) {
- if (v.path === view.path) {
- state.visitedViews.splice(i, 1)
- break
- }
- }
- },
- DEL_CACHED_VIEW: (state, view) => {
- for (const i of state.cachedViews) {
- if (i === view.name) {
- const index = state.cachedViews.indexOf(i)
- state.cachedViews.splice(index, 1)
- break
- }
- }
- },
+const state = {
+ visitedViews: [],
+ cachedViews: []
+}
- DEL_OTHERS_VISITED_VIEWS: (state, view) => {
- state.visitedViews = state.visitedViews.filter(v => {
- return v.meta.affix || v.path === view.path
+const mutations = {
+ ADD_VISITED_VIEW: (state, view) => {
+ if (state.visitedViews.some(v => v.path === view.path)) return
+ state.visitedViews.push(
+ Object.assign({}, view, {
+ title: view.meta.title || 'no-name'
})
- },
- DEL_OTHERS_CACHED_VIEWS: (state, view) => {
- for (const i of state.cachedViews) {
- if (i === view.name) {
- const index = state.cachedViews.indexOf(i)
- state.cachedViews = state.cachedViews.slice(index, index + 1)
- break
- }
- }
- },
+ )
+ },
+ ADD_CACHED_VIEW: (state, view) => {
+ if (state.cachedViews.includes(view.name)) return
+ if (!view.meta.noCache) {
+ state.cachedViews.push(view.name)
+ }
+ },
- DEL_ALL_VISITED_VIEWS: state => {
- // keep affix tags
- const affixTags = state.visitedViews.filter(tag => tag.meta.affix)
- state.visitedViews = affixTags
- },
- DEL_ALL_CACHED_VIEWS: state => {
- state.cachedViews = []
- },
-
- UPDATE_VISITED_VIEW: (state, view) => {
- for (let v of state.visitedViews) {
- if (v.path === view.path) {
- v = Object.assign(v, view)
- break
- }
+ DEL_VISITED_VIEW: (state, view) => {
+ for (const [i, v] of state.visitedViews.entries()) {
+ if (v.path === view.path) {
+ state.visitedViews.splice(i, 1)
+ break
+ }
+ }
+ },
+ DEL_CACHED_VIEW: (state, view) => {
+ for (const i of state.cachedViews) {
+ if (i === view.name) {
+ const index = state.cachedViews.indexOf(i)
+ state.cachedViews.splice(index, 1)
+ break
}
}
-
},
- actions: {
- addView({ dispatch }, view) {
- dispatch('addVisitedView', view)
- dispatch('addCachedView', view)
- },
- addVisitedView({ commit }, view) {
- commit('ADD_VISITED_VIEW', view)
- },
- addCachedView({ commit }, view) {
- commit('ADD_CACHED_VIEW', view)
- },
- delView({ dispatch, state }, view) {
- return new Promise(resolve => {
- dispatch('delVisitedView', view)
- dispatch('delCachedView', view)
- resolve({
- visitedViews: [...state.visitedViews],
- cachedViews: [...state.cachedViews]
- })
- })
- },
- delVisitedView({ commit, state }, view) {
- return new Promise(resolve => {
- commit('DEL_VISITED_VIEW', view)
- resolve([...state.visitedViews])
- })
- },
- delCachedView({ commit, state }, view) {
- return new Promise(resolve => {
- commit('DEL_CACHED_VIEW', view)
- resolve([...state.cachedViews])
- })
- },
+ DEL_OTHERS_VISITED_VIEWS: (state, view) => {
+ state.visitedViews = state.visitedViews.filter(v => {
+ return v.meta.affix || v.path === view.path
+ })
+ },
+ DEL_OTHERS_CACHED_VIEWS: (state, view) => {
+ for (const i of state.cachedViews) {
+ if (i === view.name) {
+ const index = state.cachedViews.indexOf(i)
+ state.cachedViews = state.cachedViews.slice(index, index + 1)
+ break
+ }
+ }
+ },
- delOthersViews({ dispatch, state }, view) {
- return new Promise(resolve => {
- dispatch('delOthersVisitedViews', view)
- dispatch('delOthersCachedViews', view)
- resolve({
- visitedViews: [...state.visitedViews],
- cachedViews: [...state.cachedViews]
- })
- })
- },
- delOthersVisitedViews({ commit, state }, view) {
- return new Promise(resolve => {
- commit('DEL_OTHERS_VISITED_VIEWS', view)
- resolve([...state.visitedViews])
- })
- },
- delOthersCachedViews({ commit, state }, view) {
- return new Promise(resolve => {
- commit('DEL_OTHERS_CACHED_VIEWS', view)
- resolve([...state.cachedViews])
- })
- },
+ DEL_ALL_VISITED_VIEWS: state => {
+ // keep affix tags
+ const affixTags = state.visitedViews.filter(tag => tag.meta.affix)
+ state.visitedViews = affixTags
+ },
+ DEL_ALL_CACHED_VIEWS: state => {
+ state.cachedViews = []
+ },
- delAllViews({ dispatch, state }, view) {
- return new Promise(resolve => {
- dispatch('delAllVisitedViews', view)
- dispatch('delAllCachedViews', view)
- resolve({
- visitedViews: [...state.visitedViews],
- cachedViews: [...state.cachedViews]
- })
- })
- },
- delAllVisitedViews({ commit, state }) {
- return new Promise(resolve => {
- commit('DEL_ALL_VISITED_VIEWS')
- resolve([...state.visitedViews])
- })
- },
- delAllCachedViews({ commit, state }) {
- return new Promise(resolve => {
- commit('DEL_ALL_CACHED_VIEWS')
- resolve([...state.cachedViews])
- })
- },
-
- updateVisitedView({ commit }, view) {
- commit('UPDATE_VISITED_VIEW', view)
+ UPDATE_VISITED_VIEW: (state, view) => {
+ for (let v of state.visitedViews) {
+ if (v.path === view.path) {
+ v = Object.assign(v, view)
+ break
+ }
}
}
}
-export default tagsView
+const actions = {
+ addView({ dispatch }, view) {
+ dispatch('addVisitedView', view)
+ dispatch('addCachedView', view)
+ },
+ addVisitedView({ commit }, view) {
+ commit('ADD_VISITED_VIEW', view)
+ },
+ addCachedView({ commit }, view) {
+ commit('ADD_CACHED_VIEW', view)
+ },
+
+ delView({ dispatch, state }, view) {
+ return new Promise(resolve => {
+ dispatch('delVisitedView', view)
+ dispatch('delCachedView', view)
+ resolve({
+ visitedViews: [...state.visitedViews],
+ cachedViews: [...state.cachedViews]
+ })
+ })
+ },
+ delVisitedView({ commit, state }, view) {
+ return new Promise(resolve => {
+ commit('DEL_VISITED_VIEW', view)
+ resolve([...state.visitedViews])
+ })
+ },
+ delCachedView({ commit, state }, view) {
+ return new Promise(resolve => {
+ commit('DEL_CACHED_VIEW', view)
+ resolve([...state.cachedViews])
+ })
+ },
+
+ delOthersViews({ dispatch, state }, view) {
+ return new Promise(resolve => {
+ dispatch('delOthersVisitedViews', view)
+ dispatch('delOthersCachedViews', view)
+ resolve({
+ visitedViews: [...state.visitedViews],
+ cachedViews: [...state.cachedViews]
+ })
+ })
+ },
+ delOthersVisitedViews({ commit, state }, view) {
+ return new Promise(resolve => {
+ commit('DEL_OTHERS_VISITED_VIEWS', view)
+ resolve([...state.visitedViews])
+ })
+ },
+ delOthersCachedViews({ commit, state }, view) {
+ return new Promise(resolve => {
+ commit('DEL_OTHERS_CACHED_VIEWS', view)
+ resolve([...state.cachedViews])
+ })
+ },
+
+ delAllViews({ dispatch, state }, view) {
+ return new Promise(resolve => {
+ dispatch('delAllVisitedViews', view)
+ dispatch('delAllCachedViews', view)
+ resolve({
+ visitedViews: [...state.visitedViews],
+ cachedViews: [...state.cachedViews]
+ })
+ })
+ },
+ delAllVisitedViews({ commit, state }) {
+ return new Promise(resolve => {
+ commit('DEL_ALL_VISITED_VIEWS')
+ resolve([...state.visitedViews])
+ })
+ },
+ delAllCachedViews({ commit, state }) {
+ return new Promise(resolve => {
+ commit('DEL_ALL_CACHED_VIEWS')
+ resolve([...state.cachedViews])
+ })
+ },
+
+ updateVisitedView({ commit }, view) {
+ commit('UPDATE_VISITED_VIEW', view)
+ }
+}
+
+export default {
+ namespaced: true,
+ state,
+ mutations,
+ actions
+}
diff --git a/src/store/modules/user.js b/src/store/modules/user.js
index 38e81a36..f27615b0 100644
--- a/src/store/modules/user.js
+++ b/src/store/modules/user.js
@@ -1,144 +1,128 @@
-import { loginByUsername, logout, getUserInfo } from '@/api/login'
+import { login, logout, getInfo } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
+import router, { resetRouter } from '@/router'
-const user = {
- state: {
- user: '',
- status: '',
- code: '',
- token: getToken(),
- name: '',
- avatar: '',
- introduction: '',
- roles: [],
- setting: {
- articlePlatform: []
- }
+const state = {
+ token: getToken(),
+ name: '',
+ avatar: '',
+ introduction: '',
+ roles: []
+}
+
+const mutations = {
+ SET_TOKEN: (state, token) => {
+ state.token = token
},
-
- mutations: {
- SET_CODE: (state, code) => {
- state.code = code
- },
- SET_TOKEN: (state, token) => {
- state.token = token
- },
- SET_INTRODUCTION: (state, introduction) => {
- state.introduction = introduction
- },
- SET_SETTING: (state, setting) => {
- state.setting = setting
- },
- SET_STATUS: (state, status) => {
- state.status = status
- },
- SET_NAME: (state, name) => {
- state.name = name
- },
- SET_AVATAR: (state, avatar) => {
- state.avatar = avatar
- },
- SET_ROLES: (state, roles) => {
- state.roles = roles
- }
+ SET_INTRODUCTION: (state, introduction) => {
+ state.introduction = introduction
},
-
- actions: {
- // 用户名登录
- LoginByUsername({ commit }, userInfo) {
- const username = userInfo.username.trim()
- return new Promise((resolve, reject) => {
- loginByUsername(username, userInfo.password).then(response => {
- const data = response.data
- commit('SET_TOKEN', data.token)
- setToken(response.data.token)
- resolve()
- }).catch(error => {
- reject(error)
- })
- })
- },
-
- // 获取用户信息
- GetUserInfo({ commit, state }) {
- return new Promise((resolve, reject) => {
- getUserInfo(state.token).then(response => {
- // 由于mockjs 不支持自定义状态码只能这样hack
- if (!response.data) {
- reject('Verification failed, please login again.')
- }
- const data = response.data
-
- if (data.roles && data.roles.length > 0) { // 验证返回的roles是否是一个非空数组
- commit('SET_ROLES', data.roles)
- } else {
- reject('getInfo: roles must be a non-null array!')
- }
-
- commit('SET_NAME', data.name)
- commit('SET_AVATAR', data.avatar)
- commit('SET_INTRODUCTION', data.introduction)
- resolve(response)
- }).catch(error => {
- reject(error)
- })
- })
- },
-
- // 第三方验证登录
- // LoginByThirdparty({ commit, state }, code) {
- // return new Promise((resolve, reject) => {
- // commit('SET_CODE', code)
- // loginByThirdparty(state.status, state.email, state.code).then(response => {
- // commit('SET_TOKEN', response.data.token)
- // setToken(response.data.token)
- // resolve()
- // }).catch(error => {
- // reject(error)
- // })
- // })
- // },
-
- // 登出
- LogOut({ commit, state }) {
- return new Promise((resolve, reject) => {
- logout(state.token).then(() => {
- commit('SET_TOKEN', '')
- commit('SET_ROLES', [])
- removeToken()
- resolve()
- }).catch(error => {
- reject(error)
- })
- })
- },
-
- // 前端 登出
- FedLogOut({ commit }) {
- return new Promise(resolve => {
- commit('SET_TOKEN', '')
- removeToken()
- resolve()
- })
- },
-
- // 动态修改权限
- ChangeRoles({ commit, dispatch }, role) {
- return new Promise(resolve => {
- commit('SET_TOKEN', role)
- setToken(role)
- getUserInfo(role).then(response => {
- const data = response.data
- commit('SET_ROLES', data.roles)
- commit('SET_NAME', data.name)
- commit('SET_AVATAR', data.avatar)
- commit('SET_INTRODUCTION', data.introduction)
- dispatch('GenerateRoutes', data) // 动态修改权限后 重绘侧边菜单
- resolve()
- })
- })
- }
+ SET_NAME: (state, name) => {
+ state.name = name
+ },
+ SET_AVATAR: (state, avatar) => {
+ state.avatar = avatar
+ },
+ SET_ROLES: (state, roles) => {
+ state.roles = roles
}
}
-export default user
+const actions = {
+ // user login
+ login({ commit }, userInfo) {
+ const { username, password } = userInfo
+ return new Promise((resolve, reject) => {
+ login({ username: username.trim(), password: password }).then(response => {
+ const { data } = response
+ commit('SET_TOKEN', data.token)
+ setToken(data.token)
+ resolve()
+ }).catch(error => {
+ reject(error)
+ })
+ })
+ },
+
+ // get user info
+ getInfo({ commit, state }) {
+ return new Promise((resolve, reject) => {
+ getInfo(state.token).then(response => {
+ const { data } = response
+
+ if (!data) {
+ reject('Verification failed, please Login again.')
+ }
+
+ const { roles, name, avatar, introduction } = data
+
+ // roles must be a non-empty array
+ if (!roles || roles.length <= 0) {
+ reject('getInfo: roles must be a non-null array!')
+ }
+
+ commit('SET_ROLES', roles)
+ commit('SET_NAME', name)
+ commit('SET_AVATAR', avatar)
+ commit('SET_INTRODUCTION', introduction)
+ resolve(data)
+ }).catch(error => {
+ reject(error)
+ })
+ })
+ },
+
+ // user logout
+ logout({ commit, state }) {
+ return new Promise((resolve, reject) => {
+ logout(state.token).then(() => {
+ commit('SET_TOKEN', '')
+ commit('SET_ROLES', [])
+ removeToken()
+ resetRouter()
+ resolve()
+ }).catch(error => {
+ reject(error)
+ })
+ })
+ },
+
+ // remove token
+ resetToken({ commit }) {
+ return new Promise(resolve => {
+ commit('SET_TOKEN', '')
+ commit('SET_ROLES', [])
+ removeToken()
+ resolve()
+ })
+ },
+
+ // Dynamically modify permissions
+ changeRoles({ commit, dispatch }, role) {
+ return new Promise(async resolve => {
+ const token = role + '-token'
+
+ commit('SET_TOKEN', token)
+ setToken(token)
+
+ const { roles } = await dispatch('getInfo')
+
+ resetRouter()
+
+ // generate accessible routes map based on roles
+ const accessRoutes = await dispatch('permission/generateRoutes', roles, { root: true })
+
+ // dynamically add accessible routes
+ router.addRoutes(accessRoutes)
+
+ resolve()
+ })
+ }
+}
+
+export default {
+ namespaced: true,
+ state,
+ mutations,
+ actions
+}
diff --git a/src/styles/element-variables.scss b/src/styles/element-variables.scss
new file mode 100644
index 00000000..a8fab287
--- /dev/null
+++ b/src/styles/element-variables.scss
@@ -0,0 +1,25 @@
+/**
+* I think element-ui's default theme color is too light for long-term use.
+* So I modified the default color and you can modify it to your liking.
+**/
+
+/* theme color */
+$--color-primary: #1890ff;
+$--color-success: #13ce66;
+$--color-warning: #FFBA00;
+$--color-danger: #ff4949;
+// $--color-info: #1E1E1E;
+
+$--button-font-weight: 400;
+
+// $--color-text-regular: #1f2d3d;
+
+$--border-color-light: #dfe4ed;
+$--border-color-lighter: #e6ebf5;
+
+$--table-border:1px solid#dfe6ec;
+
+/* icon font path, required */
+$--font-path: '~element-ui/lib/theme-chalk/fonts';
+
+@import "~element-ui/packages/theme-chalk/src/index";
diff --git a/src/styles/sidebar.scss b/src/styles/sidebar.scss
index 96e89be7..03449706 100644
--- a/src/styles/sidebar.scss
+++ b/src/styles/sidebar.scss
@@ -83,19 +83,26 @@
.hideSidebar {
.sidebar-container {
- width: 36px !important;
+ width: 54px !important;
}
.main-container {
- margin-left: 36px;
+ margin-left: 54px;
+ }
+
+ .svg-icon {
+ margin-right: 0px;
}
.submenu-title-noDropdown {
- padding-left: 10px !important;
+ padding: 0 !important;
position: relative;
.el-tooltip {
- padding: 0 10px !important;
+ padding: 0 !important;
+ .svg-icon {
+ margin-left: 20px;
+ }
}
}
@@ -103,7 +110,10 @@
overflow: hidden;
&>.el-submenu__title {
- padding-left: 10px !important;
+ padding: 0 !important;
+ .svg-icon {
+ margin-left: 20px;
+ }
.el-submenu__icon-arrow {
display: none;
diff --git a/src/styles/variables.scss b/src/styles/variables.scss
index 50d9b3ef..98d7b672 100644
--- a/src/styles/variables.scss
+++ b/src/styles/variables.scss
@@ -19,9 +19,10 @@ $menuHover:#263445;
$subMenuBg:#1f2d3d;
$subMenuHover:#001528;
-$sideBarWidth: 180px;
+$sideBarWidth: 210px;
// the :export directive is the magic sauce for webpack
+// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
:export {
menuText: $menuText;
menuActiveText: $menuActiveText;
diff --git a/src/utils/errorLog.js b/src/utils/errorLog.js
new file mode 100644
index 00000000..c508c789
--- /dev/null
+++ b/src/utils/errorLog.js
@@ -0,0 +1,35 @@
+import Vue from 'vue'
+import store from '@/store'
+import { isString, isArray } from '@/utils/validate'
+import settings from '@/settings'
+
+// you can set in settings.js
+// errorLog:'production' | ['production','development']
+const { errorLog: needErrorLog } = settings
+
+function checkNeed(arg) {
+ const env = process.env.NODE_ENV
+ if (isString(needErrorLog)) {
+ return env === needErrorLog
+ }
+ if (isArray(needErrorLog)) {
+ return needErrorLog.includes(env)
+ }
+ return false
+}
+
+if (checkNeed()) {
+ Vue.config.errorHandler = function(err, vm, info, a) {
+ // Don't ask me why I use Vue.nextTick, it just a hack.
+ // detail see https://forum.vuejs.org/t/dispatch-in-vue-config-errorhandler-has-some-problem/23500
+ Vue.nextTick(() => {
+ store.dispatch('errorLog/addErrorLog', {
+ err,
+ vm,
+ info,
+ url: window.location.href
+ })
+ console.error(err, info)
+ })
+ }
+}
diff --git a/src/utils/index.js b/src/utils/index.js
index 263108b4..bfff4dda 100644
--- a/src/utils/index.js
+++ b/src/utils/index.js
@@ -140,7 +140,8 @@ export function param2Obj(url) {
decodeURIComponent(search)
.replace(/"/g, '\\"')
.replace(/&/g, '","')
- .replace(/=/g, '":"') +
+ .replace(/=/g, '":"')
+ .replace(/\+/g, ' ') +
'"}'
)
}
@@ -277,7 +278,7 @@ export function debounce(func, wait, immediate) {
*/
export function deepClone(source) {
if (!source && typeof source !== 'object') {
- throw new Error('error arguments', 'shallowClone')
+ throw new Error('error arguments', 'deepClone')
}
const targetObj = source.constructor === Array ? [] : {}
Object.keys(source).forEach(keys => {
@@ -299,3 +300,16 @@ export function createUniqueString() {
const randomNum = parseInt((1 + Math.random()) * 65536) + ''
return (+(randomNum + timestamp)).toString(32)
}
+
+export function hasClass(ele, cls) {
+ return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'))
+}
+export function addClass(ele, cls) {
+ if (!hasClass(ele, cls)) ele.className += ' ' + cls
+}
+export function removeClass(ele, cls) {
+ if (hasClass(ele, cls)) {
+ const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)')
+ ele.className = ele.className.replace(reg, ' ')
+ }
+}
diff --git a/src/utils/request.js b/src/utils/request.js
index 47237685..3d1c0980 100644
--- a/src/utils/request.js
+++ b/src/utils/request.js
@@ -29,7 +29,11 @@ service.interceptors.request.use(
// response interceptor
service.interceptors.response.use(
- response => response,
+ /**
+ * If you want to get information such as headers or status
+ * Please return response => response
+ */
+ response => response.data,
/**
* 下面的注释为通过在response里,自定义code来标示请求状态
* 当code返回如下情况则说明权限有问题,登出并返回到登录页
@@ -53,7 +57,7 @@ service.interceptors.response.use(
// cancelButtonText: '取消',
// type: 'warning'
// }).then(() => {
- // store.dispatch('FedLogOut').then(() => {
+ // store.dispatch('user/resetToken').then(() => {
// location.reload() // 为了重新实例化vue-router对象 避免bug
// })
// })
diff --git a/src/utils/validate.js b/src/utils/validate.js
index 5e4056f5..ba93d1c3 100644
--- a/src/utils/validate.js
+++ b/src/utils/validate.js
@@ -11,36 +11,41 @@ export function validUsername(str) {
return valid_map.indexOf(str.trim()) >= 0
}
-/* 合法uri*/
export function validURL(url) {
const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
return reg.test(url)
}
-/* 小写字母*/
export function validLowerCase(str) {
const reg = /^[a-z]+$/
return reg.test(str)
}
-/* 大写字母*/
export function validUpperCase(str) {
const reg = /^[A-Z]+$/
return reg.test(str)
}
-/* 大小写字母*/
export function validAlphabets(str) {
const reg = /^[A-Za-z]+$/
return reg.test(str)
}
-/**
- * validate email
- * @param email
- * @returns {boolean}
- */
export function validEmail(email) {
- const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
- return re.test(email)
+ const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
+ return reg.test(email)
+}
+
+export function isString(str) {
+ if (typeof str === 'string' || str instanceof String) {
+ return true
+ }
+ return false
+}
+
+export function isArray(arg) {
+ if (typeof Array.isArray === 'undefined') {
+ return Object.prototype.toString.call(arg) === '[object Array]'
+ }
+ return Array.isArray(arg)
}
diff --git a/src/vendor/Export2Excel.js b/src/vendor/Export2Excel.js
index ba956dc1..20784f3a 100644
--- a/src/vendor/Export2Excel.js
+++ b/src/vendor/Export2Excel.js
@@ -145,9 +145,11 @@ export function export_table_to_excel(id) {
}
export function export_json_to_excel({
+ multiHeader = [],
header,
data,
filename,
+ merges = [],
autoWidth = true,
bookType= 'xlsx'
} = {}) {
@@ -155,10 +157,22 @@ export function export_json_to_excel({
filename = filename || 'excel-list'
data = [...data]
data.unshift(header);
+
+ for (let i = multiHeader.length-1; i > -1; i--) {
+ data.unshift(multiHeader[i])
+ }
+
var ws_name = "SheetJS";
var wb = new Workbook(),
ws = sheet_from_array_of_arrays(data);
+ if (merges.length > 0) {
+ if (!ws['!merges']) ws['!merges'] = [];
+ merges.forEach(item => {
+ ws['!merges'].push(XLSX.utils.decode_range(item))
+ })
+ }
+
if (autoWidth) {
/*设置worksheet每列的最大宽度*/
const colWidth = data.map(row => row.map(val => {
diff --git a/src/views/charts/keyboard.vue b/src/views/charts/keyboard.vue
index 3ea21397..3c158fcc 100644
--- a/src/views/charts/keyboard.vue
+++ b/src/views/charts/keyboard.vue
@@ -1,6 +1,6 @@
-
+
diff --git a/src/views/charts/line.vue b/src/views/charts/line.vue
index 2034d4c7..daa181fa 100644
--- a/src/views/charts/line.vue
+++ b/src/views/charts/line.vue
@@ -1,6 +1,6 @@
-
+
diff --git a/src/views/charts/mixChart.vue b/src/views/charts/mixChart.vue
index 7ccc7fa0..d41e655b 100644
--- a/src/views/charts/mixChart.vue
+++ b/src/views/charts/mixChart.vue
@@ -1,6 +1,6 @@
-
+
diff --git a/src/views/clipboard/index.vue b/src/views/clipboard/index.vue
index 607dfb66..e78c6359 100644
--- a/src/views/clipboard/index.vue
+++ b/src/views/clipboard/index.vue
@@ -2,12 +2,16 @@
-
- copy
+
+
+ copy
+
-
- copy
+
+
+ copy
+
diff --git a/src/views/components-demo/avatarUpload.vue b/src/views/components-demo/avatarUpload.vue
index 144448ce..c40ef4a4 100644
--- a/src/views/components-demo/avatarUpload.vue
+++ b/src/views/components-demo/avatarUpload.vue
@@ -5,20 +5,22 @@
{{ $t('components.imageUploadTips') }}
-
+
-
Change Avatar
+
+ Change Avatar
+ @crop-upload-success="cropSuccess"
+ />
diff --git a/src/views/components-demo/backToTop.vue b/src/views/components-demo/backToTop.vue
index 83a5529b..1404f574 100644
--- a/src/views/components-demo/backToTop.vue
+++ b/src/views/components-demo/backToTop.vue
@@ -116,7 +116,7 @@
-
+
diff --git a/src/views/components-demo/countTo.vue b/src/views/components-demo/countTo.vue
index 7044a5d2..a6b6c5ab 100644
--- a/src/views/components-demo/countTo.vue
+++ b/src/views/components-demo/countTo.vue
@@ -13,36 +13,41 @@
:prefix="_prefix"
:suffix="_suffix"
:autoplay="false"
- class="example"/>
+ class="example"
+ />
<count-to :start-val='{{ _startVal }}' :end-val='{{ _endVal }}' :duration='{{ _duration }}'
- :decimals='{{ _decimals }}' :separator='{{ _separator }}' :prefix='{{ _prefix }}' :suffix='{{ _suffix }}'
- :autoplay=false>
+ :decimals='{{ _decimals }}' :separator='{{ _separator }}' :prefix='{{ _prefix }}' :suffix='{{ _suffix }}'
+ :autoplay=false>
diff --git a/src/views/components-demo/dndList.vue b/src/views/components-demo/dndList.vue
index 9c8847a9..0e4c215a 100644
--- a/src/views/components-demo/dndList.vue
+++ b/src/views/components-demo/dndList.vue
@@ -4,7 +4,7 @@
Vue.Draggable
-
+
diff --git a/src/views/components-demo/dragDialog.vue b/src/views/components-demo/dragDialog.vue
index 0a023f90..3c985552 100644
--- a/src/views/components-demo/dragDialog.vue
+++ b/src/views/components-demo/dragDialog.vue
@@ -1,14 +1,16 @@
- open a Drag Dialog
+
+ open a Drag Dialog
+
-
+
-
-
-
+
+
+
diff --git a/src/views/components-demo/dragKanban.vue b/src/views/components-demo/dragKanban.vue
index 4353fb1e..bbbfe3df 100644
--- a/src/views/components-demo/dragKanban.vue
+++ b/src/views/components-demo/dragKanban.vue
@@ -1,8 +1,8 @@
-
-
-
+
+
+
diff --git a/src/views/excel/selectExcel.vue b/src/views/excel/selectExcel.vue
index 2695bfb4..05c52f5f 100644
--- a/src/views/excel/selectExcel.vue
+++ b/src/views/excel/selectExcel.vue
@@ -1,21 +1,24 @@
-
-
{{ $t('excel.selectedExport') }}
+
+
+ {{ $t('excel.selectedExport') }}
+
Documentation
-
+ @selection-change="handleSelectionChange"
+ >
+
{{ scope.$index }}
@@ -38,7 +41,7 @@
-
+
{{ scope.row.display_time }}
diff --git a/src/views/excel/uploadExcel.vue b/src/views/excel/uploadExcel.vue
index 4095e910..1772b7fd 100644
--- a/src/views/excel/uploadExcel.vue
+++ b/src/views/excel/uploadExcel.vue
@@ -1,8 +1,8 @@
-
+
-
+
diff --git a/src/views/guide/index.vue b/src/views/guide/index.vue
index 49502aef..31ca3177 100644
--- a/src/views/guide/index.vue
+++ b/src/views/guide/index.vue
@@ -5,7 +5,9 @@
driver.js.
- {{ $t('guide.button') }}
+
+ {{ $t('guide.button') }}
+
diff --git a/src/views/i18n-demo/index.vue b/src/views/i18n-demo/index.vue
index b5344c67..60c9a80e 100644
--- a/src/views/i18n-demo/index.vue
+++ b/src/views/i18n-demo/index.vue
@@ -7,18 +7,26 @@
- 简体中文
- English
- Español
+
+ 简体中文
+
+
+ English
+
+
+ Español
+
- {{ $t('i18nView.note') }}
+
+ {{ $t('i18nView.note') }}
+
-
+
@@ -26,23 +34,36 @@
v-for="item in options"
:key="item.value"
:label="item.label"
- :value="item.value"/>
+ :value="item.value"
+ />
- {{ $t('i18nView.default') }}
- {{ $t('i18nView.primary') }}
- {{ $t('i18nView.success') }}
- {{ $t('i18nView.info') }}
- {{ $t('i18nView.warning') }}
- {{ $t('i18nView.danger') }}
+
+ {{ $t('i18nView.default') }}
+
+
+ {{ $t('i18nView.primary') }}
+
+
+ {{ $t('i18nView.success') }}
+
+
+ {{ $t('i18nView.info') }}
+
+
+ {{ $t('i18nView.warning') }}
+
+
+ {{ $t('i18nView.danger') }}
+
-
-
-
+
+
+
@@ -89,7 +110,7 @@ export default {
},
set(lang) {
this.$i18n.locale = lang
- this.$store.dispatch('setLanguage', lang)
+ this.$store.dispatch('app/setLanguage', lang)
}
}
},
diff --git a/src/views/layout/Layout.vue b/src/views/layout/Layout.vue
deleted file mode 100644
index 0e14f160..00000000
--- a/src/views/layout/Layout.vue
+++ /dev/null
@@ -1,71 +0,0 @@
-
-
-
-
-
-
-
diff --git a/src/views/login/authredirect.vue b/src/views/login/authredirect.vue
index e749412b..69e15397 100644
--- a/src/views/login/authredirect.vue
+++ b/src/views/login/authredirect.vue
@@ -1,10 +1,15 @@
diff --git a/src/views/login/index.vue b/src/views/login/index.vue
index 352df494..221d421c 100644
--- a/src/views/login/index.vue
+++ b/src/views/login/index.vue
@@ -1,6 +1,7 @@
+
{{ $t('login.title') }}
@@ -117,10 +118,10 @@ export default {
}
},
created() {
- // window.addEventListener('hashchange', this.afterQRScan)
+ // window.addEventListener('storage', this.afterQRScan)
},
destroyed() {
- // window.removeEventListener('hashchange', this.afterQRScan)
+ // window.removeEventListener('storage', this.afterQRScan)
},
methods: {
showPwd() {
@@ -134,85 +135,91 @@ export default {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true
- this.$store.dispatch('LoginByUsername', this.loginForm).then(() => {
- this.loading = false
- this.$router.push({ path: this.redirect || '/' })
- }).catch(() => {
- this.loading = false
- })
+ this.$store.dispatch('user/login', this.loginForm)
+ .then(() => {
+ this.$router.push({ path: this.redirect || '/' })
+ this.loading = false
+ })
+ .catch(() => {
+ this.loading = false
+ })
} else {
console.log('error submit!!')
return false
}
})
- },
- afterQRScan() {
- // const hash = window.location.hash.slice(1)
- // const hashObj = getQueryObject(hash)
- // const originUrl = window.location.origin
- // history.replaceState({}, '', originUrl)
- // const codeMap = {
- // wechat: 'code',
- // tencent: 'code'
- // }
- // const codeName = hashObj[codeMap[this.auth_type]]
- // if (!codeName) {
- // alert('第三方登录失败')
- // } else {
- // this.$store.dispatch('LoginByThirdparty', codeName).then(() => {
- // this.$router.push({ path: '/' })
- // })
- // }
}
+ // afterQRScan() {
+ // if (e.key === 'x-admin-oauth-code') {
+ // const code = getQueryObject(e.newValue)
+ // const codeMap = {
+ // wechat: 'code',
+ // tencent: 'code'
+ // }
+ // const type = codeMap[this.auth_type]
+ // const codeName = code[type]
+ // if (codeName) {
+ // this.$store.dispatch('LoginByThirdparty', codeName).then(() => {
+ // this.$router.push({ path: this.redirect || '/' })
+ // })
+ // } else {
+ // alert('第三方登录失败')
+ // }
+ // }
+ // }
}
}
diff --git a/src/views/login/socialsignin.vue b/src/views/login/socialsignin.vue
index 62a63acc..567ac553 100644
--- a/src/views/login/socialsignin.vue
+++ b/src/views/login/socialsignin.vue
@@ -1,10 +1,10 @@
diff --git a/src/views/nested/menu1/index.vue b/src/views/nested/menu1/index.vue
index fdba73a4..30cb6701 100644
--- a/src/views/nested/menu1/index.vue
+++ b/src/views/nested/menu1/index.vue
@@ -1,4 +1,4 @@
-
+
diff --git a/src/views/nested/menu1/menu1-1/index.vue b/src/views/nested/menu1/menu1-1/index.vue
index 824b2cb6..27e173a6 100644
--- a/src/views/nested/menu1/menu1-1/index.vue
+++ b/src/views/nested/menu1/menu1-1/index.vue
@@ -1,4 +1,4 @@
-
+
diff --git a/src/views/pdf/download.vue b/src/views/pdf/download.vue
index b0f30f6f..27af8cf3 100644
--- a/src/views/pdf/download.vue
+++ b/src/views/pdf/download.vue
@@ -1,12 +1,14 @@
-
{{ article.title }}
+
+ {{ article.title }}
+
This article is from Evan You on
medium
-
+
diff --git a/src/views/pdf/index.vue b/src/views/pdf/index.vue
index b7728152..238d4958 100644
--- a/src/views/pdf/index.vue
+++ b/src/views/pdf/index.vue
@@ -2,7 +2,9 @@
{{ $t('pdf.tips') }}
- Click to download PDF
+
+ Click to download PDF
+
diff --git a/src/views/permission/components/SwitchRoles.vue b/src/views/permission/components/SwitchRoles.vue
index 6e5f84e5..55297d09 100644
--- a/src/views/permission/components/SwitchRoles.vue
+++ b/src/views/permission/components/SwitchRoles.vue
@@ -1,10 +1,12 @@
-
{{ $t('permission.roles') }}: {{ roles }}
+
+ {{ $t('permission.roles') }}: {{ roles }}
+
{{ $t('permission.switchRoles') }}:
-
-
+
+
@@ -20,7 +22,7 @@ export default {
return this.roles[0]
},
set(val) {
- this.$store.dispatch('ChangeRoles', val).then(() => {
+ this.$store.dispatch('user/changeRoles', val).then(() => {
this.$emit('change')
})
}
diff --git a/src/views/permission/directive.vue b/src/views/permission/directive.vue
index df277ddc..80683ed6 100644
--- a/src/views/permission/directive.vue
+++ b/src/views/permission/directive.vue
@@ -7,7 +7,9 @@
Only
admin can see this
- v-permission="['admin']"
+
+ v-permission="['admin']"
+
@@ -15,7 +17,9 @@
Only
editor can see this
- v-permission="['editor']"
+
+ v-permission="['editor']"
+
@@ -24,7 +28,9 @@
admin and
editor can see this
- v-permission="['admin','editor']"
+
+ v-permission="['admin','editor']"
+
@@ -37,17 +43,23 @@
Admin can see this
- v-if="checkPermission(['admin'])"
+
+ v-if="checkPermission(['admin'])"
+
Editor can see this
- v-if="checkPermission(['editor'])"
+
+ v-if="checkPermission(['editor'])"
+
Both admin or editor can see this
- v-if="checkPermission(['admin','editor'])"
+
+ v-if="checkPermission(['admin','editor'])"
+
@@ -59,7 +71,7 @@ import permission from '@/directive/permission/index.js' // 权限判断指令
import checkPermission from '@/utils/permission' // 权限判断函数
import SwitchRoles from './components/SwitchRoles'
-export default{
+export default {
name: 'DirectivePermission',
components: { SwitchRoles },
directives: { permission },
diff --git a/src/views/permission/page.vue b/src/views/permission/page.vue
index 3f8a50e8..5291a782 100644
--- a/src/views/permission/page.vue
+++ b/src/views/permission/page.vue
@@ -7,7 +7,7 @@
+
+
diff --git a/src/views/qiniu/upload.vue b/src/views/qiniu/upload.vue
index 9cb5eb09..9dc9aeda 100644
--- a/src/views/qiniu/upload.vue
+++ b/src/views/qiniu/upload.vue
@@ -1,7 +1,9 @@
-
- 将文件拖到此处,或点击上传
+
+
+ 将文件拖到此处,或点击上传
+
@@ -10,7 +12,7 @@ import { getToken } from '@/api/qiniu'
// 获取七牛token 后端通过Access Key,Secret Key,bucket等生成token
// 七牛官方sdk https://developer.qiniu.com/sdk#official-sdk
-export default{
+export default {
data() {
return {
dataObj: { token: '', key: '' },
diff --git a/src/views/tab/components/tabPane.vue b/src/views/tab/components/tabPane.vue
index e9ac20ac..fa3076d0 100644
--- a/src/views/tab/components/tabPane.vue
+++ b/src/views/tab/components/tabPane.vue
@@ -1,12 +1,12 @@
-
+ element-loading-text="请给我点时间!"
+ >
{{ scope.row.id }}
@@ -33,7 +33,7 @@
-
+
@@ -45,10 +45,11 @@
- {{ scope.row.status }}
+
+ {{ scope.row.status }}
+
-
diff --git a/src/views/tab/index.vue b/src/views/tab/index.vue
index 67936c0c..cce15d8a 100644
--- a/src/views/tab/index.vue
+++ b/src/views/tab/index.vue
@@ -1,11 +1,11 @@
mounted times :{{ createdTimes }}
-
+
-
+
-
+
diff --git a/src/views/table/complexTable.vue b/src/views/table/complexTable.vue
index eb355e3b..0398c9d2 100644
--- a/src/views/table/complexTable.vue
+++ b/src/views/table/complexTable.vue
@@ -1,32 +1,41 @@
-
+
-
+
-
+
-
+
- {{ $t('table.search') }}
- {{ $t('table.add') }}
- {{ $t('table.export') }}
- {{ $t('table.reviewer') }}
+
+ {{ $t('table.search') }}
+
+
+ {{ $t('table.add') }}
+
+
+ {{ $t('table.export') }}
+
+
+ {{ $t('table.reviewer') }}
+
-
+ @sort-change="sortChange"
+ >
+
{{ scope.row.id }}
@@ -54,7 +63,7 @@
-
+
@@ -65,17 +74,24 @@
- {{ scope.row.status }}
+
+ {{ scope.row.status }}
+
- {{ $t('table.edit') }}
- {{ $t('table.publish') }}
+
+ {{ $t('table.edit') }}
- {{ $t('table.draft') }}
+
+ {{ $t('table.publish') }}
- {{ $t('table.delete') }}
+
+ {{ $t('table.draft') }}
+
+
+ {{ $t('table.delete') }}
@@ -87,43 +103,46 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
diff --git a/src/views/table/dragTable.vue b/src/views/table/dragTable.vue
index 50d17da4..a5fa71cd 100644
--- a/src/views/table/dragTable.vue
+++ b/src/views/table/dragTable.vue
@@ -1,8 +1,7 @@
-
-
+
{{ scope.row.id }}
@@ -29,7 +28,7 @@
-
+
@@ -41,21 +40,25 @@
- {{ scope.row.status }}
+
+ {{ scope.row.status }}
+
-
+
-
- {{ $t('table.dragTips1') }} : {{ oldList }}
- {{ $t('table.dragTips2') }} : {{ newList }}
-
+
+ {{ $t('table.dragTips1') }} : {{ oldList }}
+
+
+ {{ $t('table.dragTips2') }} : {{ newList }}
+
@@ -93,17 +96,16 @@ export default {
this.getList()
},
methods: {
- getList() {
+ async getList() {
this.listLoading = true
- fetchList(this.listQuery).then(response => {
- this.list = response.data.items
- this.total = response.data.total
- this.listLoading = false
- this.oldList = this.list.map(v => v.id)
- this.newList = this.oldList.slice()
- this.$nextTick(() => {
- this.setSort()
- })
+ const { data } = await fetchList(this.listQuery)
+ this.list = data.items
+ this.total = data.total
+ this.listLoading = false
+ this.oldList = this.list.map(v => v.id)
+ this.newList = this.oldList.slice()
+ this.$nextTick(() => {
+ this.setSort()
})
},
setSort() {
diff --git a/src/views/table/dynamicTable/fixedThead.vue b/src/views/table/dynamicTable/fixedThead.vue
index 30dcee00..c3deb925 100644
--- a/src/views/table/dynamicTable/fixedThead.vue
+++ b/src/views/table/dynamicTable/fixedThead.vue
@@ -1,23 +1,27 @@
-
- apple
- banana
- orange
+
+ apple
+
+
+ banana
+
+
+ orange
+
-
-
+
+
{{ scope.row[fruit] }}
-
diff --git a/src/views/table/dynamicTable/index.vue b/src/views/table/dynamicTable/index.vue
index 3c16bc46..4947b4b9 100644
--- a/src/views/table/dynamicTable/index.vue
+++ b/src/views/table/dynamicTable/index.vue
@@ -1,10 +1,14 @@
-
{{ $t('table.dynamicTips1') }}
-
+
+ {{ $t('table.dynamicTips1') }}
+
+
-
{{ $t('table.dynamicTips2') }}
-
+
+ {{ $t('table.dynamicTips2') }}
+
+
diff --git a/src/views/table/dynamicTable/unfixedThead.vue b/src/views/table/dynamicTable/unfixedThead.vue
index caa97506..831b070a 100644
--- a/src/views/table/dynamicTable/unfixedThead.vue
+++ b/src/views/table/dynamicTable/unfixedThead.vue
@@ -1,23 +1,27 @@
-
- apple
- banana
- orange
+
+ apple
+
+
+ banana
+
+
+ orange
+
-
+
{{ scope.row[fruit] }}
-
diff --git a/src/views/table/inlineEditTable.vue b/src/views/table/inlineEditTable.vue
index 78800ce5..fb19d037 100644
--- a/src/views/table/inlineEditTable.vue
+++ b/src/views/table/inlineEditTable.vue
@@ -1,8 +1,6 @@
-
-
{{ scope.row.id }}
@@ -23,21 +21,25 @@
-
+
- {{ scope.row.status }}
+
+ {{ scope.row.status }}
+
-
- cancel
+
+
+ cancel
+
{{ scope.row.title }}
@@ -45,11 +47,14 @@
- Ok
- Edit
+
+ Ok
+
+
+ Edit
+
-
@@ -83,17 +88,16 @@ export default {
this.getList()
},
methods: {
- getList() {
+ async getList() {
this.listLoading = true
- fetchList(this.listQuery).then(response => {
- const items = response.data.items
- this.list = items.map(v => {
- this.$set(v, 'edit', false) // https://vuejs.org/v2/guide/reactivity.html
- v.originalTitle = v.title // will be used when user click the cancel botton
- return v
- })
- this.listLoading = false
+ const { data } = await fetchList(this.listQuery)
+ const items = data.items
+ this.list = items.map(v => {
+ this.$set(v, 'edit', false) // https://vuejs.org/v2/guide/reactivity.html
+ v.originalTitle = v.title // will be used when user click the cancel botton
+ return v
})
+ this.listLoading = false
},
cancelEdit(row) {
row.title = row.originalTitle
diff --git a/src/views/table/treeTable/customEval.js b/src/views/table/treeTable/customEval.js
deleted file mode 100644
index 73badb68..00000000
--- a/src/views/table/treeTable/customEval.js
+++ /dev/null
@@ -1,48 +0,0 @@
-/**
-* @Author: jianglei
-* @Date: 2017-10-12 12:06:49
-*/
-'use strict'
-import Vue from 'vue'
-export default function treeToArray(data, expandAll, parent, level, item) {
- const marLTemp = []
- let tmp = []
- Array.from(data).forEach(function(record) {
- if (record._expanded === undefined) {
- Vue.set(record, '_expanded', expandAll)
- }
- let _level = 1
- if (level !== undefined && level !== null) {
- _level = level + 1
- }
- Vue.set(record, '_level', _level)
- // 如果有父元素
- if (parent) {
- Vue.set(record, 'parent', parent)
- // 如果父元素有偏移量,需要计算在this的偏移量中
- // 偏移量还与前面同级元素有关,需要加上前面所有元素的长度和
- if (!marLTemp[_level]) {
- marLTemp[_level] = 0
- }
- Vue.set(record, '_marginLeft', marLTemp[_level] + parent._marginLeft)
- Vue.set(record, '_width', record[item] / parent[item] * parent._width)
- // 在本次计算过偏移量后加上自己长度,以供下一个元素使用
- marLTemp[_level] += record._width
- } else {
- // 如果为根
- // 初始化偏移量存储map
- marLTemp[record.id] = []
- // map中是一个数组,存储的是每级的长度和
- // 初始情况下为0
- marLTemp[record.id][_level] = 0
- Vue.set(record, '_marginLeft', 0)
- Vue.set(record, '_width', 1)
- }
- tmp.push(record)
- if (record.children && record.children.length > 0) {
- const children = treeToArray(record.children, expandAll, record, _level, item)
- tmp = tmp.concat(children)
- }
- })
- return tmp
-}
diff --git a/src/views/table/treeTable/customTreeTable.vue b/src/views/table/treeTable/customTreeTable.vue
deleted file mode 100644
index 2a216171..00000000
--- a/src/views/table/treeTable/customTreeTable.vue
+++ /dev/null
@@ -1,138 +0,0 @@
-
-
-
-
- Documentation
-
-
-
-
-
- {{ scope.row.event }}
- {{ scope.row.timeLine+'ms' }}
-
-
-
-
-
-
-
-
-
-
-
- 点击
-
-
-
-
-
-
-
diff --git a/src/views/table/treeTable/treeTable.vue b/src/views/table/treeTable/treeTable.vue
deleted file mode 100644
index d2ecf14d..00000000
--- a/src/views/table/treeTable/treeTable.vue
+++ /dev/null
@@ -1,129 +0,0 @@
-
-
-
-
-
diff --git a/src/views/theme/index.vue b/src/views/theme/index.vue
index 9eec5f01..b67c2c67 100644
--- a/src/views/theme/index.vue
+++ b/src/views/theme/index.vue
@@ -8,48 +8,65 @@
{{ $t('theme.change') }} :
-
+
{{ $t('theme.tips') }}
- Primary
- Success
- Info
- Warning
- Danger
-
-
-
-
-
-
- Search
- Upload
-
+ Primary
+
+
+ Success
+
+
+ Info
+
+
+ Warning
+
+
+ Danger
-
+
+
+
+
+ Search
+
+
+ Upload
+
+
+
+
+
+
{{ tag.name }}
- Option A
- Option B
- Option C
+
+ Option A
+
+
+ Option B
+
+
+ Option C
+
-
+
-
diff --git a/src/views/tree-table/custom/data.js b/src/views/tree-table/custom/data.js
new file mode 100644
index 00000000..020f6247
--- /dev/null
+++ b/src/views/tree-table/custom/data.js
@@ -0,0 +1,51 @@
+const data = [
+ {
+ name: '1',
+ timeLine: 100,
+ children: [
+ {
+ name: '1-1',
+ timeLine: 20
+ },
+ {
+ name: '1-2',
+ timeLine: 60,
+ children: [
+ {
+ name: '1-2-1',
+ timeLine: 35
+ },
+ {
+ name: '1-2-2',
+ timeLine: 25
+ }
+ ]
+ }
+ ]
+ },
+ {
+ name: '2',
+ timeLine: 80,
+ children: [
+ {
+ name: '2-1',
+ timeLine: 30
+ },
+ {
+ name: '2-2',
+ timeLine: 50
+ },
+ {
+ name: '2-3',
+ timeLine: 60
+ }
+ ]
+ },
+ {
+ name: '3',
+ timeLine: 40
+ }
+]
+
+export default data
+
diff --git a/src/views/tree-table/custom/index.vue b/src/views/tree-table/custom/index.vue
new file mode 100644
index 00000000..754ec202
--- /dev/null
+++ b/src/views/tree-table/custom/index.vue
@@ -0,0 +1,159 @@
+
+
+
+
+ Documentation
+
+
+
+
+
+
+
+
+
+
+
+ Here is just a placeholder slot, you can display anything.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Append Brother
+
+
+ Append Child
+
+
+
+
+ Edit
+
+
+ Delete
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/tree-table/data.js b/src/views/tree-table/data.js
new file mode 100644
index 00000000..67b0137a
--- /dev/null
+++ b/src/views/tree-table/data.js
@@ -0,0 +1,80 @@
+
+const data = [
+ {
+ id: 0,
+ event: 'Event-0',
+ timeLine: 50
+ },
+ {
+ id: 1,
+ event: 'Event-1',
+ timeLine: 100,
+ children: [
+ {
+ id: 2,
+ event: 'Event-2',
+ timeLine: 10
+
+ },
+ {
+ id: 3,
+ event: 'Event-3',
+ timeLine: 90,
+ children: [
+ {
+ id: 4,
+ event: 'Event-4',
+ timeLine: 5
+
+ },
+ {
+ id: 5,
+ event: 'Event-5',
+ timeLine: 10
+
+ },
+ {
+ id: 6,
+ event: 'Event-6',
+ timeLine: 75,
+
+ children: [
+ {
+ id: 7,
+ event: 'Event-7',
+ timeLine: 50,
+
+ children: [
+ {
+ id: 71,
+ event: 'Event-7-1',
+ timeLine: 25
+
+ },
+ {
+ id: 72,
+ event: 'Event-7-2',
+ timeLine: 5
+
+ },
+ {
+ id: 73,
+ event: 'Event-7-3',
+ timeLine: 20
+ }
+ ]
+ },
+ {
+ id: 8,
+ event: 'Event-8',
+ timeLine: 25
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+]
+
+export default data
diff --git a/src/views/tree-table/index.vue b/src/views/tree-table/index.vue
new file mode 100644
index 00000000..87b4bc5c
--- /dev/null
+++ b/src/views/tree-table/index.vue
@@ -0,0 +1,126 @@
+
+
+
+
+ Documentation
+
+
+
+ Expand All
+
+
+
+
+ Show Checkbox
+
+
+
+
+
+
+ level: {{ scope.row._level }}
+ expand: {{ scope.row._expand }}
+ select: {{ scope.row._select }}
+
+
+
+ Click
+
+
+
+
+
+
+
+
+
diff --git a/src/views/zip/index.vue b/src/views/zip/index.vue
index 42f0efe6..ed7e8af2 100644
--- a/src/views/zip/index.vue
+++ b/src/views/zip/index.vue
@@ -1,8 +1,10 @@
-
- {{ $t('zip.export') }} zip
+
+
+ {{ $t('zip.export') }} zip
+
@@ -26,7 +28,7 @@
-
+
{{ scope.row.display_time }}
@@ -51,12 +53,11 @@ export default {
this.fetchData()
},
methods: {
- fetchData() {
+ async fetchData() {
this.listLoading = true
- fetchList().then(response => {
- this.list = response.data.items
- this.listLoading = false
- })
+ const { data } = await fetchList()
+ this.list = data.items
+ this.listLoading = false
},
handleDownload() {
this.downloadLoading = true
diff --git a/tests/unit/utils/validate.spec.js b/tests/unit/utils/validate.spec.js
index b7d21922..ef2efe61 100644
--- a/tests/unit/utils/validate.spec.js
+++ b/tests/unit/utils/validate.spec.js
@@ -1,28 +1,28 @@
-import { validUsername, validateURL, validateLowerCase, validateUpperCase, validateAlphabets } from '@/utils/validate.js'
+import { validUsername, validURL, validLowerCase, validUpperCase, validAlphabets } from '@/utils/validate.js'
describe('Utils:validate', () => {
it('validUsername', () => {
expect(validUsername('admin')).toBe(true)
expect(validUsername('editor')).toBe(true)
expect(validUsername('xxxx')).toBe(false)
})
- it('validateURL', () => {
- expect(validateURL('https://github.com/PanJiaChen/vue-element-admin')).toBe(true)
- expect(validateURL('http://github.com/PanJiaChen/vue-element-admin')).toBe(true)
- expect(validateURL('github.com/PanJiaChen/vue-element-admin')).toBe(false)
+ it('validURL', () => {
+ expect(validURL('https://github.com/PanJiaChen/vue-element-admin')).toBe(true)
+ expect(validURL('http://github.com/PanJiaChen/vue-element-admin')).toBe(true)
+ expect(validURL('github.com/PanJiaChen/vue-element-admin')).toBe(false)
})
- it('validateLowerCase', () => {
- expect(validateLowerCase('abc')).toBe(true)
- expect(validateLowerCase('Abc')).toBe(false)
- expect(validateLowerCase('123abc')).toBe(false)
+ it('validLowerCase', () => {
+ expect(validLowerCase('abc')).toBe(true)
+ expect(validLowerCase('Abc')).toBe(false)
+ expect(validLowerCase('123abc')).toBe(false)
})
- it('validateUpperCase', () => {
- expect(validateUpperCase('ABC')).toBe(true)
- expect(validateUpperCase('Abc')).toBe(false)
- expect(validateUpperCase('123ABC')).toBe(false)
+ it('validUpperCase', () => {
+ expect(validUpperCase('ABC')).toBe(true)
+ expect(validUpperCase('Abc')).toBe(false)
+ expect(validUpperCase('123ABC')).toBe(false)
})
- it('validateAlphabets', () => {
- expect(validateAlphabets('ABC')).toBe(true)
- expect(validateAlphabets('Abc')).toBe(true)
- expect(validateAlphabets('123aBC')).toBe(false)
+ it('validAlphabets', () => {
+ expect(validAlphabets('ABC')).toBe(true)
+ expect(validAlphabets('Abc')).toBe(true)
+ expect(validAlphabets('123aBC')).toBe(false)
})
})
diff --git a/vue.config.js b/vue.config.js
index 3cf98e60..2baef48d 100644
--- a/vue.config.js
+++ b/vue.config.js
@@ -1,11 +1,14 @@
'use strict'
+require('@babel/register')
const path = require('path')
+const { default: settings } = require('./src/settings.js')
+const { name } = settings
function resolve(dir) {
return path.join(__dirname, dir)
}
-const port = 9527 // TODO: change to Settings
+const port = 9527 // dev port
// Explanation of each configuration item You can find it in https://cli.vuejs.org/config/
module.exports = {
@@ -15,12 +18,12 @@ module.exports = {
* 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 '/' !!!
- * Detail https://cli.vuejs.org/config/#baseurl
+ * Detail https://cli.vuejs.org/config/#publicPath
*/
- baseUrl: '/',
+ publicPath: '/',
outputDir: 'dist',
assetsDir: 'static',
- lintOnSave: process.env.NODE_ENV !== 'production',
+ lintOnSave: process.env.NODE_ENV === 'development' ? 'error' : false,
productionSourceMap: false,
devServer: {
port: port,
@@ -39,25 +42,25 @@ module.exports = {
}
},
after(app) {
- console.log('apple')
const bodyParser = require('body-parser')
- require('@babel/register')
+
// parse app.body
// http://expressjs.com/en/4x/api.html#req.body
app.use(bodyParser.json())
- app.use(bodyParser.urlencoded({ extended: true }))
+ app.use(bodyParser.urlencoded({
+ extended: true
+ }))
- // import ES2015 module from common.js module
const { default: mocks } = require('./mock')
for (const mock of mocks) {
- app.all(mock.route, mock.response)
+ app[mock.type](mock.url, mock.response)
}
}
},
configureWebpack: {
// We provide the app's title in Webpack's name field, so that
// it can be accessed in index.html to inject the correct title.
- name: 'vue-element-admin', // TODO: change to Settings
+ name: name,
resolve: {
alias: {
'@': resolve('src')
@@ -65,8 +68,8 @@ module.exports = {
}
},
chainWebpack(config) {
- config.plugins.delete('preload')// TODO: need test
- config.plugins.delete('prefetch')// TODO: need test
+ config.plugins.delete('preload') // TODO: need test
+ config.plugins.delete('prefetch') // TODO: need test
config.module
.rule('svg')
.exclude.add(resolve('src/icons'))
@@ -82,7 +85,15 @@ module.exports = {
symbolId: 'icon-[name]'
})
.end()
-
+ config.module
+ .rule('vue')
+ .use('vue-loader')
+ .loader('vue-loader')
+ .tap(options => {
+ options.compilerOptions.preserveWhitespace = true
+ return options
+ })
+ .end()
config
.when(process.env.NODE_ENV === 'development',
config => config.devtool('cheap-source-map')
@@ -92,11 +103,13 @@ module.exports = {
.when(process.env.NODE_ENV !== 'development',
config => {
config
- .plugin('ScriptExtHtmlWebpackPlugin')
- .use('script-ext-html-webpack-plugin', [{
- // `runtime` must same as runtimeChunk name. default is `runtime`
- inline: /runtime\..*\.js$/
- }])
+ // .plugin('ScriptExtHtmlWebpackPlugin')
+ // .after('html')
+ // .use('script-ext-html-webpack-plugin', [{
+ // // `runtime` must same as runtimeChunk name. default is `runtime`
+ // inline: /runtime\..*\.js$/
+ // }])
+ // .end()
config
.optimization.splitChunks({
chunks: 'all',