├── .editorconfig
├── .env.development
├── .env.production
├── .env.staging
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.es.md
├── README.ja.md
├── README.md
├── README.zh-CN.md
├── babel.config.js
├── build
└── index.js
├── index.html
├── jest.config.js
├── jsconfig.json
├── mock
├── article.js
├── index.js
├── mock-server.js
├── remote-search.js
├── role
│ ├── index.js
│ └── routes.js
├── user.js
└── utils.js
├── package-lock.json
├── package.json
├── plop-templates
├── build
│ └── index.js
├── component
│ ├── index.hbs
│ └── prompt.js
├── mock
│ ├── article.js
│ ├── index.js
│ ├── mock-server.js
│ ├── remote-search.js
│ ├── role
│ │ ├── index.js
│ │ └── routes.js
│ ├── user.js
│ └── utils.js
├── store
│ ├── index.hbs
│ └── prompt.js
├── utils.js
└── view
│ ├── index.hbs
│ └── prompt.js
├── plopfile.js
├── postcss.config.js
├── public
├── favicon.ico
└── index.html
├── src
├── App.vue
├── api
│ ├── article.js
│ ├── qiniu.js
│ ├── remote-search.js
│ ├── role.js
│ └── user.js
├── assets
│ ├── 401_images
│ │ └── 401.gif
│ ├── 404_images
│ │ ├── 404.png
│ │ └── 404_cloud.png
│ ├── custom-theme
│ │ ├── fonts
│ │ │ ├── element-icons.ttf
│ │ │ └── element-icons.woff
│ │ └── index.css
│ └── logo.png
├── components
│ ├── BackToTop
│ │ └── index.vue
│ ├── Breadcrumb
│ │ └── index.vue
│ ├── Charts
│ │ ├── Keyboard.vue
│ │ ├── LineMarker.vue
│ │ ├── MixChart.vue
│ │ └── mixins
│ │ │ └── resize.js
│ ├── DndList
│ │ └── index.vue
│ ├── DragSelect
│ │ └── index.vue
│ ├── Dropzone
│ │ └── index.vue
│ ├── ErrorLog
│ │ └── index.vue
│ ├── GithubCorner
│ │ └── index.vue
│ ├── Hamburger
│ │ └── index.vue
│ ├── HeaderSearch
│ │ └── index.vue
│ ├── ImageCropper
│ │ ├── index.vue
│ │ └── utils
│ │ │ ├── data2blob.js
│ │ │ ├── effectRipple.js
│ │ │ ├── language.js
│ │ │ └── mimes.js
│ ├── JsonEditor
│ │ └── index.vue
│ ├── Kanban
│ │ └── index.vue
│ ├── MDinput
│ │ └── index.vue
│ ├── MarkdownEditor
│ │ ├── default-options.js
│ │ └── index.vue
│ ├── Pagination
│ │ └── index.vue
│ ├── PanThumb
│ │ └── index.vue
│ ├── RightPanel
│ │ └── index.vue
│ ├── Screenfull
│ │ └── index.vue
│ ├── Share
│ │ └── DropdownMenu.vue
│ ├── SizeSelect
│ │ └── index.vue
│ ├── Sticky
│ │ └── index.vue
│ ├── SvgIcon
│ │ └── index.vue
│ ├── TextHoverEffect
│ │ └── Mallki.vue
│ ├── ThemePicker
│ │ └── index.vue
│ ├── Tinymce
│ │ ├── components
│ │ │ └── EditorImage.vue
│ │ ├── dynamicLoadScript.js
│ │ ├── index.vue
│ │ ├── plugins.js
│ │ └── toolbar.js
│ ├── Upload
│ │ ├── SingleImage.vue
│ │ ├── SingleImage2.vue
│ │ └── SingleImage3.vue
│ └── UploadExcel
│ │ └── index.vue
├── directive
│ ├── clipboard
│ │ ├── clipboard.js
│ │ └── index.js
│ ├── el-drag-dialog
│ │ ├── drag.js
│ │ └── index.js
│ ├── el-table
│ │ ├── adaptive.js
│ │ └── index.js
│ ├── permission
│ │ ├── index.js
│ │ └── permission.js
│ ├── sticky.js
│ └── waves
│ │ ├── index.js
│ │ ├── waves.css
│ │ └── waves.js
├── filters
│ └── index.js
├── icons
│ ├── index.js
│ ├── svg
│ │ ├── 404.svg
│ │ ├── bug.svg
│ │ ├── chart.svg
│ │ ├── clipboard.svg
│ │ ├── component.svg
│ │ ├── dashboard.svg
│ │ ├── documentation.svg
│ │ ├── drag.svg
│ │ ├── edit.svg
│ │ ├── education.svg
│ │ ├── email.svg
│ │ ├── example.svg
│ │ ├── excel.svg
│ │ ├── exit-fullscreen.svg
│ │ ├── eye-open.svg
│ │ ├── eye.svg
│ │ ├── form.svg
│ │ ├── fullscreen.svg
│ │ ├── guide.svg
│ │ ├── icon.svg
│ │ ├── international.svg
│ │ ├── language.svg
│ │ ├── link.svg
│ │ ├── list.svg
│ │ ├── lock.svg
│ │ ├── message.svg
│ │ ├── money.svg
│ │ ├── nested.svg
│ │ ├── password.svg
│ │ ├── pdf.svg
│ │ ├── people.svg
│ │ ├── peoples.svg
│ │ ├── qq.svg
│ │ ├── search.svg
│ │ ├── shopping.svg
│ │ ├── size.svg
│ │ ├── skill.svg
│ │ ├── star.svg
│ │ ├── tab.svg
│ │ ├── table.svg
│ │ ├── theme.svg
│ │ ├── tree-table.svg
│ │ ├── tree.svg
│ │ ├── user.svg
│ │ ├── wechat.svg
│ │ └── zip.svg
│ └── svgo.yml
├── index.css
├── layout
│ ├── components
│ │ ├── AppMain.vue
│ │ ├── Navbar.vue
│ │ ├── Settings
│ │ │ └── index.vue
│ │ ├── Sidebar
│ │ │ ├── FixiOSBug.js
│ │ │ ├── Item.vue
│ │ │ ├── Link.vue
│ │ │ ├── Logo.vue
│ │ │ ├── SidebarItem.vue
│ │ │ └── index.vue
│ │ ├── TagsView
│ │ │ ├── ScrollPane.vue
│ │ │ └── index.vue
│ │ └── index.js
│ ├── index.vue
│ └── mixin
│ │ └── ResizeHandler.js
├── main.js
├── permission.js
├── router
│ ├── index.js
│ └── modules
│ │ ├── charts.js
│ │ ├── components.js
│ │ ├── nested.js
│ │ └── table.js
├── settings.js
├── store
│ ├── getters.js
│ ├── index.js
│ └── modules
│ │ ├── app.js
│ │ ├── errorLog.js
│ │ ├── permission.js
│ │ ├── settings.js
│ │ ├── tagsView.js
│ │ └── user.js
├── styles
│ ├── btn.scss
│ ├── element-ui.scss
│ ├── element-variables.scss
│ ├── index.scss
│ ├── mixin.scss
│ ├── sidebar.scss
│ ├── transition.scss
│ └── variables.scss
├── utils
│ ├── auth.js
│ ├── clipboard.js
│ ├── error-log.js
│ ├── get-page-title.js
│ ├── index.js
│ ├── open-window.js
│ ├── permission.js
│ ├── request.js
│ ├── scroll-to.js
│ └── validate.js
├── vendor
│ ├── Export2Excel.js
│ └── Export2Zip.js
└── views
│ ├── charts
│ ├── keyboard.vue
│ ├── line.vue
│ └── mix-chart.vue
│ ├── clipboard
│ └── index.vue
│ ├── components-demo
│ ├── avatar-upload.vue
│ ├── back-to-top.vue
│ ├── dnd-list.vue
│ ├── drag-dialog.vue
│ ├── drag-kanban.vue
│ ├── drag-select.vue
│ ├── dropzone.vue
│ ├── json-editor.vue
│ ├── markdown.vue
│ ├── mixin.vue
│ ├── split-pane.vue
│ ├── sticky.vue
│ └── tinymce.vue
│ ├── dashboard
│ ├── admin
│ │ ├── components
│ │ │ ├── BarChart.vue
│ │ │ ├── BoxCard.vue
│ │ │ ├── LineChart.vue
│ │ │ ├── PanelGroup.vue
│ │ │ ├── PieChart.vue
│ │ │ ├── RaddarChart.vue
│ │ │ ├── TodoList
│ │ │ │ ├── Todo.vue
│ │ │ │ ├── index.scss
│ │ │ │ └── index.vue
│ │ │ ├── TransactionTable.vue
│ │ │ └── mixins
│ │ │ │ └── resize.js
│ │ └── index.vue
│ ├── editor
│ │ └── index.vue
│ └── index.vue
│ ├── documentation
│ └── index.vue
│ ├── error-log
│ ├── components
│ │ ├── ErrorTestA.vue
│ │ └── ErrorTestB.vue
│ └── index.vue
│ ├── error-page
│ ├── 401.vue
│ └── 404.vue
│ ├── example
│ ├── components
│ │ ├── ArticleDetail.vue
│ │ ├── Dropdown
│ │ │ ├── Comment.vue
│ │ │ ├── Platform.vue
│ │ │ ├── SourceUrl.vue
│ │ │ └── index.js
│ │ └── Warning.vue
│ ├── create.vue
│ ├── edit.vue
│ └── list.vue
│ ├── excel
│ ├── components
│ │ ├── AutoWidthOption.vue
│ │ ├── BookTypeOption.vue
│ │ └── FilenameOption.vue
│ ├── export-excel.vue
│ ├── merge-header.vue
│ ├── select-excel.vue
│ └── upload-excel.vue
│ ├── guide
│ ├── index.vue
│ └── steps.js
│ ├── icons
│ ├── element-icons.js
│ ├── index.vue
│ └── svg-icons.js
│ ├── login
│ ├── auth-redirect.vue
│ ├── components
│ │ └── SocialSignin.vue
│ └── index.vue
│ ├── nested
│ ├── menu1
│ │ ├── index.vue
│ │ ├── menu1-1
│ │ │ └── index.vue
│ │ ├── menu1-2
│ │ │ ├── index.vue
│ │ │ ├── menu1-2-1
│ │ │ │ └── index.vue
│ │ │ └── menu1-2-2
│ │ │ │ └── index.vue
│ │ └── menu1-3
│ │ │ └── index.vue
│ └── menu2
│ │ └── index.vue
│ ├── pdf
│ ├── content.js
│ ├── download.vue
│ └── index.vue
│ ├── permission
│ ├── components
│ │ └── SwitchRoles.vue
│ ├── directive.vue
│ ├── page.vue
│ └── role.vue
│ ├── profile
│ ├── components
│ │ ├── Account.vue
│ │ ├── Activity.vue
│ │ ├── Timeline.vue
│ │ └── UserCard.vue
│ └── index.vue
│ ├── qiniu
│ └── upload.vue
│ ├── redirect
│ └── index.vue
│ ├── tab
│ ├── components
│ │ └── TabPane.vue
│ └── index.vue
│ ├── table
│ ├── complex-table.vue
│ ├── drag-table.vue
│ ├── dynamic-table
│ │ ├── components
│ │ │ ├── FixedThead.vue
│ │ │ └── UnfixedThead.vue
│ │ └── index.vue
│ └── inline-edit-table.vue
│ ├── theme
│ └── index.vue
│ └── zip
│ └── index.vue
├── tests
└── unit
│ ├── .eslintrc.js
│ ├── components
│ ├── Hamburger.spec.js
│ └── SvgIcon.spec.js
│ └── utils
│ ├── formatTime.spec.js
│ ├── param2Obj.spec.js
│ ├── parseTime.spec.js
│ └── validate.spec.js
└── vue.config.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | end_of_line = lf
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | insert_final_newline = false
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/.env.development:
--------------------------------------------------------------------------------
1 | # just a flag
2 | ENV = 'development'
3 |
4 | # base api
5 | VUE_APP_BASE_API = '/dev-api'
6 |
--------------------------------------------------------------------------------
/.env.production:
--------------------------------------------------------------------------------
1 | # just a flag
2 | ENV = 'production'
3 |
4 | # base api
5 | VUE_APP_BASE_API = '/prod-api'
6 |
7 |
--------------------------------------------------------------------------------
/.env.staging:
--------------------------------------------------------------------------------
1 | NODE_ENV = production
2 |
3 | # just a flag
4 | ENV = 'staging'
5 |
6 | # base api
7 | VUE_APP_BASE_API = '/stage-api'
8 |
9 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build/*.js
2 | src
3 | public
4 | dist
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js: 10
3 | script: npm run test
4 | notifications:
5 | email: false
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Ziwen Mei
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | // https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app
4 | '@vue/cli-plugin-babel/preset'
5 | ],
6 | 'env': {
7 | 'development': {
8 | // babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require().
9 | // This plugin can significantly increase the speed of hot updates, when you have a large number of pages.
10 | // https://panjiachen.github.io/vue-element-admin-site/guide/advanced/lazy-loading.html
11 | 'plugins': ['dynamic-import-node']
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/build/index.js:
--------------------------------------------------------------------------------
1 | const { run } = require('runjs')
2 | const chalk = require('chalk')
3 | const config = require('../vue.config.js')
4 | const rawArgv = process.argv.slice(2)
5 | const args = rawArgv.join(' ')
6 |
7 | if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
8 | const report = rawArgv.includes('--report')
9 |
10 | run(`vue-cli-service build ${args}`)
11 |
12 | const port = 9526
13 | const publicPath = config.publicPath
14 |
15 | var connect = require('connect')
16 | var serveStatic = require('serve-static')
17 | const app = connect()
18 |
19 | app.use(
20 | publicPath,
21 | serveStatic('./dist', {
22 | index: ['index.html', '/']
23 | })
24 | )
25 |
26 | app.listen(port, function () {
27 | console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`))
28 | if (report) {
29 | console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`))
30 | }
31 |
32 | })
33 | } else {
34 | run(`vue-cli-service build ${args}`)
35 | }
36 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | <%= webpackConfig.name %>
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
3 | transform: {
4 | '^.+\\.vue$': 'vue-jest',
5 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$':
6 | 'jest-transform-stub',
7 | '^.+\\.jsx?$': 'babel-jest'
8 | },
9 | moduleNameMapper: {
10 | '^@/(.*)$': '/src/$1'
11 | },
12 | snapshotSerializers: ['jest-serializer-vue'],
13 | testMatch: [
14 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
15 | ],
16 | collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'],
17 | coverageDirectory: '/tests/unit/coverage',
18 | // 'collectCoverage': true,
19 | 'coverageReporters': [
20 | 'lcov',
21 | 'text-summary'
22 | ],
23 | testURL: 'http://localhost/'
24 | }
25 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "./",
4 | "paths": {
5 | "@/*": ["src/*"]
6 | }
7 | },
8 | "exclude": ["node_modules", "dist"]
9 | }
--------------------------------------------------------------------------------
/mock/index.js:
--------------------------------------------------------------------------------
1 | const Mock = require('mockjs')
2 | const { param2Obj } = require('./utils')
3 |
4 | const user = require('./user')
5 | const role = require('./role')
6 | const article = require('./article')
7 | const search = require('./remote-search')
8 |
9 | const mocks = [
10 | ...user,
11 | ...role,
12 | ...article,
13 | ...search
14 | ]
15 |
16 | // for front mock
17 | // please use it cautiously, it will redefine XMLHttpRequest,
18 | // which will cause many of your third-party libraries to be invalidated(like progress event).
19 | function mockXHR() {
20 | // mock patch
21 | // https://github.com/nuysoft/Mock/issues/300
22 | Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
23 | Mock.XHR.prototype.send = function() {
24 | if (this.custom.xhr) {
25 | this.custom.xhr.withCredentials = this.withCredentials || false
26 |
27 | if (this.responseType) {
28 | this.custom.xhr.responseType = this.responseType
29 | }
30 | }
31 | this.proxy_send(...arguments)
32 | }
33 |
34 | function XHR2ExpressReqWrap(respond) {
35 | return function(options) {
36 | let result = null
37 | if (respond instanceof Function) {
38 | const { body, type, url } = options
39 | // https://expressjs.com/en/4x/api.html#req
40 | result = respond({
41 | method: type,
42 | body: JSON.parse(body),
43 | query: param2Obj(url)
44 | })
45 | } else {
46 | result = respond
47 | }
48 | return Mock.mock(result)
49 | }
50 | }
51 |
52 | for (const i of mocks) {
53 | Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response))
54 | }
55 | }
56 |
57 | module.exports = {
58 | mocks,
59 | mockXHR
60 | }
61 |
--------------------------------------------------------------------------------
/mock/remote-search.js:
--------------------------------------------------------------------------------
1 | const Mock = require('mockjs')
2 |
3 | const NameList = []
4 | const count = 100
5 |
6 | for (let i = 0; i < count; i++) {
7 | NameList.push(Mock.mock({
8 | name: '@first'
9 | }))
10 | }
11 | NameList.push({ name: 'mock-Pan' })
12 |
13 | module.exports = [
14 | // username search
15 | {
16 | url: '/vue-element-admin/search/user',
17 | type: 'get',
18 | response: config => {
19 | const { name } = config.query
20 | const mockNameList = NameList.filter(item => {
21 | const lowerCaseName = item.name.toLowerCase()
22 | return !(name && lowerCaseName.indexOf(name.toLowerCase()) < 0)
23 | })
24 | return {
25 | code: 20000,
26 | data: { items: mockNameList }
27 | }
28 | }
29 | },
30 |
31 | // transaction list
32 | {
33 | url: '/vue-element-admin/transaction/list',
34 | type: 'get',
35 | response: _ => {
36 | return {
37 | code: 20000,
38 | data: {
39 | total: 20,
40 | 'items|20': [{
41 | order_no: '@guid()',
42 | timestamp: +Mock.Random.date('T'),
43 | username: '@name()',
44 | price: '@float(1000, 15000, 0, 2)',
45 | 'status|1': ['success', 'pending']
46 | }]
47 | }
48 | }
49 | }
50 | }
51 | ]
52 |
--------------------------------------------------------------------------------
/mock/role/index.js:
--------------------------------------------------------------------------------
1 | const Mock = require('mockjs')
2 | const { deepClone } = require('../utils')
3 | const { asyncRoutes, constantRoutes } = require('./routes.js')
4 |
5 | const routes = deepClone([...constantRoutes, ...asyncRoutes])
6 |
7 | const roles = [
8 | {
9 | key: 'admin',
10 | name: 'admin',
11 | description: 'Super Administrator. Have access to view all pages.',
12 | routes: routes
13 | },
14 | {
15 | key: 'editor',
16 | name: 'editor',
17 | description: 'Normal Editor. Can see all pages except permission page',
18 | routes: routes.filter(i => i.path !== '/permission')// just a mock
19 | },
20 | {
21 | key: 'visitor',
22 | name: 'visitor',
23 | description: 'Just a visitor. Can only see the home page and the document page',
24 | routes: [{
25 | path: '',
26 | redirect: 'dashboard',
27 | children: [
28 | {
29 | path: 'dashboard',
30 | name: 'Dashboard',
31 | meta: { title: 'dashboard', icon: 'dashboard' }
32 | }
33 | ]
34 | }]
35 | }
36 | ]
37 |
38 | module.exports = [
39 | // mock get all routes form server
40 | {
41 | url: '/vue-element-admin/routes',
42 | type: 'get',
43 | response: _ => {
44 | return {
45 | code: 20000,
46 | data: routes
47 | }
48 | }
49 | },
50 |
51 | // mock get all roles form server
52 | {
53 | url: '/vue-element-admin/roles',
54 | type: 'get',
55 | response: _ => {
56 | return {
57 | code: 20000,
58 | data: roles
59 | }
60 | }
61 | },
62 |
63 | // add role
64 | {
65 | url: '/vue-element-admin/role',
66 | type: 'post',
67 | response: {
68 | code: 20000,
69 | data: {
70 | key: Mock.mock('@integer(300, 5000)')
71 | }
72 | }
73 | },
74 |
75 | // update role
76 | {
77 | url: '/vue-element-admin/role/[A-Za-z0-9]',
78 | type: 'put',
79 | response: {
80 | code: 20000,
81 | data: {
82 | status: 'success'
83 | }
84 | }
85 | },
86 |
87 | // delete role
88 | {
89 | url: '/vue-element-admin/role/[A-Za-z0-9]',
90 | type: 'delete',
91 | response: {
92 | code: 20000,
93 | data: {
94 | status: 'success'
95 | }
96 | }
97 | }
98 | ]
99 |
--------------------------------------------------------------------------------
/mock/user.js:
--------------------------------------------------------------------------------
1 |
2 | const tokens = {
3 | admin: {
4 | token: 'admin-token'
5 | },
6 | editor: {
7 | token: 'editor-token'
8 | }
9 | }
10 |
11 | const users = {
12 | 'admin-token': {
13 | roles: ['admin'],
14 | introduction: 'I am a super administrator',
15 | avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
16 | name: 'Super Admin'
17 | },
18 | 'editor-token': {
19 | roles: ['editor'],
20 | introduction: 'I am an editor',
21 | avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
22 | name: 'Normal Editor'
23 | }
24 | }
25 |
26 | module.exports = [
27 | // user login
28 | {
29 | url: '/vue-element-admin/user/login',
30 | type: 'post',
31 | response: config => {
32 | const { username } = config.body
33 | const token = tokens[username]
34 |
35 | // mock error
36 | if (!token) {
37 | return {
38 | code: 60204,
39 | message: 'Account and password are incorrect.'
40 | }
41 | }
42 |
43 | return {
44 | code: 20000,
45 | data: token
46 | }
47 | }
48 | },
49 |
50 | // get user info
51 | {
52 | url: '/vue-element-admin/user/info\.*',
53 | type: 'get',
54 | response: config => {
55 | const { token } = config.query
56 | const info = users[token]
57 |
58 | // mock error
59 | if (!info) {
60 | return {
61 | code: 50008,
62 | message: 'Login failed, unable to get user details.'
63 | }
64 | }
65 |
66 | return {
67 | code: 20000,
68 | data: info
69 | }
70 | }
71 | },
72 |
73 | // user logout
74 | {
75 | url: '/vue-element-admin/user/logout',
76 | type: 'post',
77 | response: _ => {
78 | return {
79 | code: 20000,
80 | data: 'success'
81 | }
82 | }
83 | }
84 | ]
85 |
--------------------------------------------------------------------------------
/mock/utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @param {string} url
3 | * @returns {Object}
4 | */
5 | function param2Obj(url) {
6 | const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
7 | if (!search) {
8 | return {}
9 | }
10 | const obj = {}
11 | const searchArr = search.split('&')
12 | searchArr.forEach(v => {
13 | const index = v.indexOf('=')
14 | if (index !== -1) {
15 | const name = v.substring(0, index)
16 | const val = v.substring(index + 1, v.length)
17 | obj[name] = val
18 | }
19 | })
20 | return obj
21 | }
22 |
23 | /**
24 | * This is just a simple version of deep copy
25 | * Has a lot of edge cases bug
26 | * If you want to use a perfect deep copy, use lodash's _.cloneDeep
27 | * @param {Object} source
28 | * @returns {Object}
29 | */
30 | function deepClone(source) {
31 | if (!source && typeof source !== 'object') {
32 | throw new Error('error arguments', 'deepClone')
33 | }
34 | const targetObj = source.constructor === Array ? [] : {}
35 | Object.keys(source).forEach(keys => {
36 | if (source[keys] && typeof source[keys] === 'object') {
37 | targetObj[keys] = deepClone(source[keys])
38 | } else {
39 | targetObj[keys] = source[keys]
40 | }
41 | })
42 | return targetObj
43 | }
44 |
45 | module.exports = {
46 | param2Obj,
47 | deepClone
48 | }
49 |
--------------------------------------------------------------------------------
/plop-templates/build/index.js:
--------------------------------------------------------------------------------
1 | const { run } = require('runjs')
2 | const chalk = require('chalk')
3 | const config = require('../vue.config.js')
4 | const rawArgv = process.argv.slice(2)
5 | const args = rawArgv.join(' ')
6 |
7 | if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
8 | const report = rawArgv.includes('--report')
9 |
10 | run(`vue-cli-service build ${args}`)
11 |
12 | const port = 9526
13 | const publicPath = config.publicPath
14 |
15 | var connect = require('connect')
16 | var serveStatic = require('serve-static')
17 | const app = connect()
18 |
19 | app.use(
20 | publicPath,
21 | serveStatic('./dist', {
22 | index: ['index.html', '/']
23 | })
24 | )
25 |
26 | app.listen(port, function() {
27 | console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`))
28 | if (report) {
29 | console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`))
30 | }
31 | })
32 | } else {
33 | run(`vue-cli-service build ${args}`)
34 | }
35 |
--------------------------------------------------------------------------------
/plop-templates/component/index.hbs:
--------------------------------------------------------------------------------
1 | {{#if template}}
2 |
3 |
4 |
5 | {{/if}}
6 |
7 | {{#if script}}
8 |
20 | {{/if}}
21 |
22 | {{#if style}}
23 |
26 | {{/if}}
27 |
--------------------------------------------------------------------------------
/plop-templates/component/prompt.js:
--------------------------------------------------------------------------------
1 | const { notEmpty } = require('../utils.js')
2 |
3 | module.exports = {
4 | description: 'generate vue component',
5 | prompts: [{
6 | type: 'input',
7 | name: 'name',
8 | message: 'component name please',
9 | validate: notEmpty('name')
10 | },
11 | {
12 | type: 'checkbox',
13 | name: 'blocks',
14 | message: 'Blocks:',
15 | choices: [{
16 | name: '',
17 | value: 'template',
18 | checked: true
19 | },
20 | {
21 | name: '
20 | {{/if}}
21 |
22 | {{#if style}}
23 |
26 | {{/if}}
27 |
--------------------------------------------------------------------------------
/plop-templates/view/prompt.js:
--------------------------------------------------------------------------------
1 | const { notEmpty } = require('../utils.js')
2 |
3 | module.exports = {
4 | description: 'generate a view',
5 | prompts: [{
6 | type: 'input',
7 | name: 'name',
8 | message: 'view name please',
9 | validate: notEmpty('name')
10 | },
11 | {
12 | type: 'checkbox',
13 | name: 'blocks',
14 | message: 'Blocks:',
15 | choices: [{
16 | name: '',
17 | value: 'template',
18 | checked: true
19 | },
20 | {
21 | name: '
12 |
--------------------------------------------------------------------------------
/src/api/article.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function fetchList(query) {
4 | return request({
5 | url: '/vue-element-admin/article/list',
6 | method: 'get',
7 | params: query
8 | })
9 | }
10 |
11 | export function fetchArticle(id) {
12 | return request({
13 | url: '/vue-element-admin/article/detail',
14 | method: 'get',
15 | params: { id }
16 | })
17 | }
18 |
19 | export function fetchPv(pv) {
20 | return request({
21 | url: '/vue-element-admin/article/pv',
22 | method: 'get',
23 | params: { pv }
24 | })
25 | }
26 |
27 | export function createArticle(data) {
28 | return request({
29 | url: '/vue-element-admin/article/create',
30 | method: 'post',
31 | data
32 | })
33 | }
34 |
35 | export function updateArticle(data) {
36 | return request({
37 | url: '/vue-element-admin/article/update',
38 | method: 'post',
39 | data
40 | })
41 | }
42 |
--------------------------------------------------------------------------------
/src/api/qiniu.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function getToken() {
4 | return request({
5 | url: '/qiniu/upload/token', // 假地址 自行替换
6 | method: 'get'
7 | })
8 | }
9 |
--------------------------------------------------------------------------------
/src/api/remote-search.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function searchUser(name) {
4 | return request({
5 | url: '/vue-element-admin/search/user',
6 | method: 'get',
7 | params: { name }
8 | })
9 | }
10 |
11 | export function transactionList(query) {
12 | return request({
13 | url: '/vue-element-admin/transaction/list',
14 | method: 'get',
15 | params: query
16 | })
17 | }
18 |
--------------------------------------------------------------------------------
/src/api/role.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function getRoutes() {
4 | console.log('request routes')
5 | return request({
6 | url: '/vue-element-admin/routes',
7 | method: 'get'
8 | })
9 | }
10 |
11 | export function getRoles() {
12 | console.log('request roles')
13 | return request({
14 | url: '/vue-element-admin/roles',
15 | method: 'get'
16 | })
17 | }
18 |
19 | export function addRole(data) {
20 | return request({
21 | url: '/vue-element-admin/role',
22 | method: 'post',
23 | data
24 | })
25 | }
26 |
27 | export function updateRole(id, data) {
28 | return request({
29 | url: `/vue-element-admin/role/${id}`,
30 | method: 'put',
31 | data
32 | })
33 | }
34 |
35 | export function deleteRole(id) {
36 | return request({
37 | url: `/vue-element-admin/role/${id}`,
38 | method: 'delete'
39 | })
40 | }
41 |
--------------------------------------------------------------------------------
/src/api/user.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function login(data) {
4 | return request({
5 | url: '/vue-element-admin/user/login',
6 | method: 'post',
7 | data
8 | })
9 | }
10 |
11 | export function getInfo(token) {
12 | return request({
13 | url: '/vue-element-admin/user/info',
14 | method: 'get',
15 | params: { token }
16 | })
17 | }
18 |
19 | export function logout() {
20 | return request({
21 | url: '/vue-element-admin/user/logout',
22 | method: 'post'
23 | })
24 | }
25 |
--------------------------------------------------------------------------------
/src/assets/401_images/401.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rudeus3Greyrat/vue3-element-admin/d8ccb5c9b90d4a25c8bd6a412aadaaebc403ca0f/src/assets/401_images/401.gif
--------------------------------------------------------------------------------
/src/assets/404_images/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rudeus3Greyrat/vue3-element-admin/d8ccb5c9b90d4a25c8bd6a412aadaaebc403ca0f/src/assets/404_images/404.png
--------------------------------------------------------------------------------
/src/assets/404_images/404_cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rudeus3Greyrat/vue3-element-admin/d8ccb5c9b90d4a25c8bd6a412aadaaebc403ca0f/src/assets/404_images/404_cloud.png
--------------------------------------------------------------------------------
/src/assets/custom-theme/fonts/element-icons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rudeus3Greyrat/vue3-element-admin/d8ccb5c9b90d4a25c8bd6a412aadaaebc403ca0f/src/assets/custom-theme/fonts/element-icons.ttf
--------------------------------------------------------------------------------
/src/assets/custom-theme/fonts/element-icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rudeus3Greyrat/vue3-element-admin/d8ccb5c9b90d4a25c8bd6a412aadaaebc403ca0f/src/assets/custom-theme/fonts/element-icons.woff
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rudeus3Greyrat/vue3-element-admin/d8ccb5c9b90d4a25c8bd6a412aadaaebc403ca0f/src/assets/logo.png
--------------------------------------------------------------------------------
/src/components/Charts/mixins/resize.js:
--------------------------------------------------------------------------------
1 | import { debounce } from '@/utils'
2 |
3 | export default {
4 | data() {
5 | return {
6 | $_sidebarElm: null,
7 | $_resizeHandler: null
8 | }
9 | },
10 | mounted() {
11 | this.initListener()
12 | },
13 | activated() {
14 | if (!this.$_resizeHandler) {
15 | // avoid duplication init
16 | this.initListener()
17 | }
18 |
19 | // when keep-alive chart activated, auto resize
20 | this.resize()
21 | },
22 | beforeUnmount() {
23 | this.destroyListener()
24 | },
25 | deactivated() {
26 | this.destroyListener()
27 | },
28 | methods: {
29 | // use $_ for mixins properties
30 | // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
31 | $_sidebarResizeHandler(e) {
32 | if (e.propertyName === 'width') {
33 | this.$_resizeHandler()
34 | }
35 | },
36 | initListener() {
37 | this.$_resizeHandler = debounce(() => {
38 | this.resize()
39 | }, 100)
40 | window.addEventListener('resize', this.$_resizeHandler)
41 |
42 | this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0]
43 | this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)
44 | },
45 | destroyListener() {
46 | window.removeEventListener('resize', this.$_resizeHandler)
47 | this.$_resizeHandler = null
48 |
49 | this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)
50 | },
51 | resize() {
52 | const { chart } = this
53 | chart && chart.resize()
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/components/DragSelect/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
50 |
51 |
66 |
--------------------------------------------------------------------------------
/src/components/GithubCorner/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
23 |
24 |
25 |
26 |
55 |
--------------------------------------------------------------------------------
/src/components/Hamburger/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
32 |
33 |
45 |
--------------------------------------------------------------------------------
/src/components/ImageCropper/utils/data2blob.js:
--------------------------------------------------------------------------------
1 | /**
2 | * database64文件格式转换为2进制
3 | *
4 | * @param {[String]} data dataURL 的格式为 “data:image/png;base64,****”,逗号之前都是一些说明性的文字,我们只需要逗号之后的就行了
5 | * @param {[String]} mime [description]
6 | * @return {[blob]} [description]
7 | */
8 | export default function(data, mime) {
9 | data = data.split(',')[1]
10 | data = window.atob(data)
11 | var ia = new Uint8Array(data.length)
12 | for (var i = 0; i < data.length; i++) {
13 | ia[i] = data.charCodeAt(i)
14 | }
15 | // canvas.toDataURL 返回的默认格式就是 image/png
16 | return new Blob([ia], {
17 | type: mime
18 | })
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/ImageCropper/utils/effectRipple.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 点击波纹效果
3 | *
4 | * @param {[event]} e [description]
5 | * @param {[Object]} arg_opts [description]
6 | * @return {[bollean]} [description]
7 | */
8 | export default function(e, arg_opts) {
9 | var opts = Object.assign({
10 | ele: e.target, // 波纹作用元素
11 | type: 'hit', // hit点击位置扩散center中心点扩展
12 | bgc: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
13 | }, arg_opts)
14 | var target = opts.ele
15 | if (target) {
16 | var rect = target.getBoundingClientRect()
17 | var ripple = target.querySelector('.e-ripple')
18 | if (!ripple) {
19 | ripple = document.createElement('span')
20 | ripple.className = 'e-ripple'
21 | ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'
22 | target.appendChild(ripple)
23 | } else {
24 | ripple.className = 'e-ripple'
25 | }
26 | switch (opts.type) {
27 | case 'center':
28 | ripple.style.top = (rect.height / 2 - ripple.offsetHeight / 2) + 'px'
29 | ripple.style.left = (rect.width / 2 - ripple.offsetWidth / 2) + 'px'
30 | break
31 | default:
32 | ripple.style.top = (e.pageY - rect.top - ripple.offsetHeight / 2 - document.body.scrollTop) + 'px'
33 | ripple.style.left = (e.pageX - rect.left - ripple.offsetWidth / 2 - document.body.scrollLeft) + 'px'
34 | }
35 | ripple.style.backgroundColor = opts.bgc
36 | ripple.className = 'e-ripple z-active'
37 | return false
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/components/ImageCropper/utils/mimes.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'jpg': 'image/jpeg',
3 | 'png': 'image/png',
4 | 'gif': 'image/gif',
5 | 'svg': 'image/svg+xml',
6 | 'psd': 'image/photoshop'
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/JsonEditor/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
56 |
57 |
78 |
--------------------------------------------------------------------------------
/src/components/MarkdownEditor/default-options.js:
--------------------------------------------------------------------------------
1 | // doc: https://nhnent.github.io/tui.editor/api/latest/ToastUIEditor.html#ToastUIEditor
2 | export default {
3 | minHeight: '200px',
4 | previewStyle: 'vertical',
5 | useCommandShortcut: true,
6 | useDefaultHTMLSanitizer: true,
7 | usageStatistics: false,
8 | hideModeSwitch: false,
9 | toolbarItems: [
10 | 'heading',
11 | 'bold',
12 | 'italic',
13 | 'strike',
14 | 'divider',
15 | 'hr',
16 | 'quote',
17 | 'divider',
18 | 'ul',
19 | 'ol',
20 | 'task',
21 | 'indent',
22 | 'outdent',
23 | 'divider',
24 | 'table',
25 | 'image',
26 | 'link',
27 | 'divider',
28 | 'code',
29 | 'codeblock'
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/src/components/Screenfull/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
50 |
51 |
61 |
--------------------------------------------------------------------------------
/src/components/SizeSelect/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
14 | {{ item.label }}
15 |
16 |
17 |
18 |
19 |
20 |
21 |
63 |
--------------------------------------------------------------------------------
/src/components/SvgIcon/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
47 |
48 |
63 |
--------------------------------------------------------------------------------
/src/components/Tinymce/dynamicLoadScript.js:
--------------------------------------------------------------------------------
1 | let callbacks = []
2 |
3 | function loadedTinymce() {
4 | // to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2144
5 | // check is successfully downloaded script
6 | return window.tinymce
7 | }
8 |
9 | const dynamicLoadScript = (src, callback) => {
10 | const existingScript = document.getElementById(src)
11 | const cb = callback || function() {}
12 |
13 | if (!existingScript) {
14 | const script = document.createElement('script')
15 | script.src = src // src url for the third-party library being loaded.
16 | script.id = src
17 | document.body.appendChild(script)
18 | callbacks.push(cb)
19 | const onEnd = 'onload' in script ? stdOnEnd : ieOnEnd
20 | onEnd(script)
21 | }
22 |
23 | if (existingScript && cb) {
24 | if (loadedTinymce()) {
25 | cb(null, existingScript)
26 | } else {
27 | callbacks.push(cb)
28 | }
29 | }
30 |
31 | function stdOnEnd(script) {
32 | script.onload = function() {
33 | // this.onload = null here is necessary
34 | // because even IE9 works not like others
35 | this.onerror = this.onload = null
36 | for (const cb of callbacks) {
37 | cb(null, script)
38 | }
39 | callbacks = null
40 | }
41 | script.onerror = function() {
42 | this.onerror = this.onload = null
43 | cb(new Error('Failed to load ' + src), script)
44 | }
45 | }
46 |
47 | function ieOnEnd(script) {
48 | script.onreadystatechange = function() {
49 | if (this.readyState !== 'complete' && this.readyState !== 'loaded') return
50 | this.onreadystatechange = null
51 | for (const cb of callbacks) {
52 | cb(null, script) // there is no way to catch loading errors in IE8
53 | }
54 | callbacks = null
55 | }
56 | }
57 | }
58 |
59 | export default dynamicLoadScript
60 |
--------------------------------------------------------------------------------
/src/components/Tinymce/plugins.js:
--------------------------------------------------------------------------------
1 | // Any plugins you want to use has to be imported
2 | // Detail plugins list see https://www.tinymce.com/docs/plugins/
3 | // Custom builds see https://www.tinymce.com/download/custom-builds/
4 |
5 | const plugins = ['advlist anchor autolink autosave code codesample colorpicker colorpicker contextmenu directionality emoticons fullscreen hr image imagetools insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textcolor textpattern visualblocks visualchars wordcount']
6 |
7 | export default plugins
8 |
--------------------------------------------------------------------------------
/src/components/Tinymce/toolbar.js:
--------------------------------------------------------------------------------
1 | // Here is a list of the toolbar
2 | // Detail list see https://www.tinymce.com/docs/advanced/editor-control-identifiers/#toolbarcontrols
3 |
4 | const toolbar = ['searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample', 'hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen']
5 |
6 | export default toolbar
7 |
--------------------------------------------------------------------------------
/src/directive/clipboard/clipboard.js:
--------------------------------------------------------------------------------
1 | // Inspired by https://github.com/Inndy/vue-clipboard2
2 | const Clipboard = require('clipboard')
3 | if (!Clipboard) {
4 | throw new Error('you should npm install `clipboard` --save at first ')
5 | }
6 |
7 | export default {
8 | bind(el, binding) {
9 | if (binding.arg === 'success') {
10 | el._v_clipboard_success = binding.value
11 | } else if (binding.arg === 'error') {
12 | el._v_clipboard_error = binding.value
13 | } else {
14 | const clipboard = new Clipboard(el, {
15 | text() { return binding.value },
16 | action() { return binding.arg === 'cut' ? 'cut' : 'copy' }
17 | })
18 | clipboard.on('success', e => {
19 | const callback = el._v_clipboard_success
20 | callback && callback(e) // eslint-disable-line
21 | })
22 | clipboard.on('error', e => {
23 | const callback = el._v_clipboard_error
24 | callback && callback(e) // eslint-disable-line
25 | })
26 | el._v_clipboard = clipboard
27 | }
28 | },
29 | update(el, binding) {
30 | if (binding.arg === 'success') {
31 | el._v_clipboard_success = binding.value
32 | } else if (binding.arg === 'error') {
33 | el._v_clipboard_error = binding.value
34 | } else {
35 | el._v_clipboard.text = function() { return binding.value }
36 | el._v_clipboard.action = function() { return binding.arg === 'cut' ? 'cut' : 'copy' }
37 | }
38 | },
39 | unbind(el, binding) {
40 | if (binding.arg === 'success') {
41 | delete el._v_clipboard_success
42 | } else if (binding.arg === 'error') {
43 | delete el._v_clipboard_error
44 | } else {
45 | el._v_clipboard.destroy()
46 | delete el._v_clipboard
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/directive/clipboard/index.js:
--------------------------------------------------------------------------------
1 | import Clipboard from './clipboard'
2 |
3 | const install = function(Vue) {
4 | Vue.directive('Clipboard', Clipboard)
5 | }
6 |
7 | if (window.Vue) {
8 | window.clipboard = Clipboard
9 | Vue.use(install); // eslint-disable-line
10 | }
11 |
12 | Clipboard.install = install
13 | export default Clipboard
14 |
--------------------------------------------------------------------------------
/src/directive/el-drag-dialog/index.js:
--------------------------------------------------------------------------------
1 | import drag from './drag'
2 |
3 | const install = function(Vue) {
4 | Vue.directive('el-drag-dialog', drag)
5 | }
6 |
7 | if (window.Vue) {
8 | window['el-drag-dialog'] = drag
9 | Vue.use(install); // eslint-disable-line
10 | }
11 |
12 | drag.install = install
13 | export default drag
14 |
--------------------------------------------------------------------------------
/src/directive/el-table/adaptive.js:
--------------------------------------------------------------------------------
1 | import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event'
2 |
3 | /**
4 | * How to use
5 | * ...
6 | * el-table height is must be set
7 | * bottomOffset: 30(default) // The height of the table from the bottom of the page.
8 | */
9 |
10 | const doResize = (el, binding, vnode) => {
11 | const { componentInstance: $table } = vnode
12 |
13 | const { value } = binding
14 |
15 | if (!$table.height) {
16 | throw new Error(`el-$table must set the height. Such as height='100px'`)
17 | }
18 | const bottomOffset = (value && value.bottomOffset) || 30
19 |
20 | if (!$table) return
21 |
22 | const height = window.innerHeight - el.getBoundingClientRect().top - bottomOffset
23 | $table.layout.setHeight(height)
24 | $table.doLayout()
25 | }
26 |
27 | export default {
28 | bind(el, binding, vnode) {
29 | el.resizeListener = () => {
30 | doResize(el, binding, vnode)
31 | }
32 | // parameter 1 is must be "Element" type
33 | addResizeListener(window.document.body, el.resizeListener)
34 | },
35 | inserted(el, binding, vnode) {
36 | doResize(el, binding, vnode)
37 | },
38 | unbind(el) {
39 | removeResizeListener(window.document.body, el.resizeListener)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/directive/el-table/index.js:
--------------------------------------------------------------------------------
1 | import adaptive from './adaptive'
2 |
3 | const install = function(Vue) {
4 | Vue.directive('el-height-adaptive-table', adaptive)
5 | }
6 |
7 | if (window.Vue) {
8 | window['el-height-adaptive-table'] = adaptive
9 | Vue.use(install); // eslint-disable-line
10 | }
11 |
12 | adaptive.install = install
13 | export default adaptive
14 |
--------------------------------------------------------------------------------
/src/directive/permission/index.js:
--------------------------------------------------------------------------------
1 | import permission from './permission'
2 |
3 | const install = function(Vue) {
4 | Vue.directive('permission', permission)
5 | }
6 |
7 | if (window.Vue) {
8 | window['permission'] = permission
9 | Vue.use(install); // eslint-disable-line
10 | }
11 |
12 | permission.install = install
13 | export default permission
14 |
--------------------------------------------------------------------------------
/src/directive/permission/permission.js:
--------------------------------------------------------------------------------
1 | import store from '@/store'
2 |
3 | function checkPermission(el, binding) {
4 | const { value } = binding
5 | const roles = store.getters && store.getters.roles
6 |
7 | if (value && value instanceof Array) {
8 | if (value.length > 0) {
9 | const permissionRoles = value
10 |
11 | const hasPermission = roles.some(role => {
12 | return permissionRoles.includes(role)
13 | })
14 |
15 | if (!hasPermission) {
16 | el.parentNode && el.parentNode.removeChild(el)
17 | }
18 | }
19 | } else {
20 | throw new Error(`need roles! Like v-permission="['admin','editor']"`)
21 | }
22 | }
23 |
24 | export default {
25 | inserted(el, binding) {
26 | checkPermission(el, binding)
27 | },
28 | update(el, binding) {
29 | checkPermission(el, binding)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/directive/waves/index.js:
--------------------------------------------------------------------------------
1 | import waves from './waves'
2 |
3 | const install = function(Vue) {
4 | Vue.directive('waves', waves)
5 | }
6 |
7 | if (window.Vue) {
8 | window.waves = waves
9 | Vue.use(install); // eslint-disable-line
10 | }
11 |
12 | waves.install = install
13 | export default waves
14 |
--------------------------------------------------------------------------------
/src/directive/waves/waves.css:
--------------------------------------------------------------------------------
1 | .waves-ripple {
2 | position: absolute;
3 | border-radius: 100%;
4 | background-color: rgba(0, 0, 0, 0.15);
5 | background-clip: padding-box;
6 | pointer-events: none;
7 | -webkit-user-select: none;
8 | -moz-user-select: none;
9 | -ms-user-select: none;
10 | user-select: none;
11 | -webkit-transform: scale(0);
12 | -ms-transform: scale(0);
13 | transform: scale(0);
14 | opacity: 1;
15 | }
16 |
17 | .waves-ripple.z-active {
18 | opacity: 0;
19 | -webkit-transform: scale(2);
20 | -ms-transform: scale(2);
21 | transform: scale(2);
22 | -webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
23 | transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
24 | transition: opacity 1.2s ease-out, transform 0.6s ease-out;
25 | transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
26 | }
--------------------------------------------------------------------------------
/src/filters/index.js:
--------------------------------------------------------------------------------
1 | // import parseTime, formatTime and set to filter
2 | export { parseTime, formatTime } from '@/utils'
3 |
4 | /**
5 | * Show plural label if time is plural number
6 | * @param {number} time
7 | * @param {string} label
8 | * @return {string}
9 | */
10 | function pluralize(time, label) {
11 | if (time === 1) {
12 | return time + label
13 | }
14 | return time + label + 's'
15 | }
16 |
17 | /**
18 | * @param {number} time
19 | */
20 | export function timeAgo(time) {
21 | const between = Date.now() / 1000 - Number(time)
22 | if (between < 3600) {
23 | return pluralize(~~(between / 60), ' minute')
24 | } else if (between < 86400) {
25 | return pluralize(~~(between / 3600), ' hour')
26 | } else {
27 | return pluralize(~~(between / 86400), ' day')
28 | }
29 | }
30 |
31 | /**
32 | * Number formatting
33 | * like 10000 => 10k
34 | * @param {number} num
35 | * @param {number} digits
36 | */
37 | export function numberFormatter(num, digits) {
38 | const si = [
39 | { value: 1E18, symbol: 'E' },
40 | { value: 1E15, symbol: 'P' },
41 | { value: 1E12, symbol: 'T' },
42 | { value: 1E9, symbol: 'G' },
43 | { value: 1E6, symbol: 'M' },
44 | { value: 1E3, symbol: 'k' }
45 | ]
46 | for (let i = 0; i < si.length; i++) {
47 | if (num >= si[i].value) {
48 | return (num / si[i].value).toFixed(digits).replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + si[i].symbol
49 | }
50 | }
51 | return num.toString()
52 | }
53 |
54 | /**
55 | * 10000 => "10,000"
56 | * @param {number} num
57 | */
58 | export function toThousandFilter(num) {
59 | return (+num || 0).toString().replace(/^-?\d+/g, m => m.replace(/(?=(?!\b)(\d{3})+$)/g, ','))
60 | }
61 |
62 | /**
63 | * Upper case first char
64 | * @param {String} string
65 | */
66 | export function uppercaseFirst(string) {
67 | return string.charAt(0).toUpperCase() + string.slice(1)
68 | }
69 |
--------------------------------------------------------------------------------
/src/icons/index.js:
--------------------------------------------------------------------------------
1 | const req = require.context('./svg', false, /\.svg$/)
2 | const requireAll = requireContext => requireContext.keys().map(requireContext)
3 | requireAll(req)
4 |
--------------------------------------------------------------------------------
/src/icons/svg/404.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/bug.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/chart.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/clipboard.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/component.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/dashboard.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/documentation.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/drag.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/edit.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/education.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/email.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/example.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/excel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/exit-fullscreen.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/eye-open.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/eye.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/fullscreen.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/guide.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/international.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/language.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/link.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/list.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/lock.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/message.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/money.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/nested.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/password.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/pdf.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/people.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/peoples.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/search.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/shopping.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/size.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/skill.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/star.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/tab.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/table.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/theme.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/tree-table.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/tree.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/user.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/wechat.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/zip.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svgo.yml:
--------------------------------------------------------------------------------
1 | # replace default config
2 |
3 | # multipass: true
4 | # full: true
5 |
6 | plugins:
7 |
8 | # - name
9 | #
10 | # or:
11 | # - name: false
12 | # - name: true
13 | #
14 | # or:
15 | # - name:
16 | # param1: 1
17 | # param2: 2
18 |
19 | - removeAttrs:
20 | attrs:
21 | - 'fill'
22 | - 'fill-rule'
23 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | #app {
2 | font-family: Avenir, Helvetica, Arial, sans-serif;
3 | -webkit-font-smoothing: antialiased;
4 | -moz-osx-font-smoothing: grayscale;
5 | text-align: center;
6 | color: #2c3e50;
7 | margin-top: 60px;
8 | }
9 |
--------------------------------------------------------------------------------
/src/layout/components/AppMain.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
26 |
27 |
51 |
52 |
60 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/FixiOSBug.js:
--------------------------------------------------------------------------------
1 | export default {
2 | computed: {
3 | device() {
4 | return this.$store.state.app.device
5 | }
6 | },
7 | mounted() {
8 | // In order to fix the click on menu on the ios device will trigger the mouseleave bug
9 | // https://github.com/PanJiaChen/vue-element-admin/issues/1135
10 | this.fixBugIniOS()
11 | },
12 | methods: {
13 | fixBugIniOS() {
14 | const $subMenu = this.$refs.subMenu
15 | if ($subMenu) {
16 | const handleMouseleave = $subMenu.handleMouseleave
17 | $subMenu.handleMouseleave = (e) => {
18 | if (this.device === 'mobile') {
19 | return
20 | }
21 | handleMouseleave(e)
22 | }
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/Item.vue:
--------------------------------------------------------------------------------
1 |
31 |
32 |
39 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/Link.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
44 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/Logo.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
33 |
34 |
83 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
55 |
--------------------------------------------------------------------------------
/src/layout/components/index.js:
--------------------------------------------------------------------------------
1 | export { default as AppMain } from './AppMain'
2 | export { default as Navbar } from './Navbar'
3 | export { default as Settings } from './Settings'
4 | export { default as Sidebar } from './Sidebar/index.vue'
5 | export { default as TagsView } from './TagsView/index.vue'
6 |
--------------------------------------------------------------------------------
/src/layout/mixin/ResizeHandler.js:
--------------------------------------------------------------------------------
1 | import store from '@/store'
2 |
3 | const { body } = document
4 | const WIDTH = 992 // refer to Bootstrap's responsive design
5 |
6 | export default {
7 | watch: {
8 | $route(route) {
9 | if (this.device === 'mobile' && this.sidebar.opened) {
10 | store.dispatch('app/closeSideBar', { withoutAnimation: false })
11 | }
12 | }
13 | },
14 | beforeMount() {
15 | window.addEventListener('resize', this.$_resizeHandler)
16 | },
17 | beforeUnmount() {
18 | window.removeEventListener('resize', this.$_resizeHandler)
19 | },
20 | mounted() {
21 | const isMobile = this.$_isMobile()
22 | if (isMobile) {
23 | store.dispatch('app/toggleDevice', 'mobile')
24 | store.dispatch('app/closeSideBar', { withoutAnimation: true })
25 | }
26 | },
27 | methods: {
28 | // use $_ for mixins properties
29 | // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
30 | $_isMobile() {
31 | const rect = body.getBoundingClientRect()
32 | return rect.width - 1 < WIDTH
33 | },
34 | $_resizeHandler() {
35 | if (!document.hidden) {
36 | const isMobile = this.$_isMobile()
37 | store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
38 |
39 | if (isMobile) {
40 | store.dispatch('app/closeSideBar', { withoutAnimation: true })
41 | }
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import App from './App.vue'
3 | import ElementPlus from 'element-plus'
4 | import 'element-plus/lib/theme-chalk/index.css'
5 | import store from './store'
6 | import router from './router'
7 |
8 | import Cookies from 'js-cookie'
9 |
10 | import 'normalize.css/normalize.css' // a modern alternative to CSS resets
11 |
12 | import './styles/element-variables.scss'
13 | import enLang from 'element-plus/lib/locale/lang/en'// 如果使用中文语言包请默认支持,无需额外引入,请删除该依赖
14 |
15 | import '@/styles/index.scss' // global css
16 |
17 | import './icons' // icon
18 | import './permission' // permission control
19 | import './utils/error-log' // error log
20 |
21 | import SvgIcon from '@/components/SvgIcon'// svg component
22 |
23 | /**
24 | * If you don't want to use mock-server
25 | * you want to use MockJs for mock api
26 | * you can execute: mockXHR()
27 | *
28 | * Currently MockJs will be used in the production environment,
29 | * please remove it before going online ! ! !
30 | */
31 | if (process.env.NODE_ENV === 'production') {
32 | const { mockXHR } = require('../mock')
33 | mockXHR()
34 | }
35 |
36 | const app = createApp(App)
37 |
38 | app.config.productionTip = false
39 |
40 | // register globally
41 | app.component('SvgIcon', SvgIcon)
42 | app.use(ElementPlus, {
43 | size: Cookies.get('size') || 'medium', // set element-ui default size
44 | locale: enLang // 如果使用中文,无需设置,请删除
45 | })
46 | app.use(store)
47 | app.use(router)
48 | app.mount('#app')
49 |
50 | export default app
51 |
--------------------------------------------------------------------------------
/src/router/modules/charts.js:
--------------------------------------------------------------------------------
1 | /** When your routing table is too long, you can split it into small modules**/
2 |
3 | const chartsRouter = {
4 | path: '/charts',
5 | component: () => import('@/layout/index'),
6 | redirect: 'noRedirect',
7 | name: 'Charts',
8 | meta: {
9 | title: 'Charts',
10 | icon: 'chart'
11 | },
12 | children: [
13 | {
14 | path: 'keyboard',
15 | component: () => import('@/views/charts/keyboard'),
16 | name: 'KeyboardChart',
17 | meta: { title: 'Keyboard Chart', noCache: true }
18 | },
19 | {
20 | path: 'line',
21 | component: () => import('@/views/charts/line'),
22 | name: 'LineChart',
23 | meta: { title: 'Line Chart', noCache: true }
24 | },
25 | {
26 | path: 'mix-chart',
27 | component: () => import('@/views/charts/mix-chart'),
28 | name: 'MixChart',
29 | meta: { title: 'Mix Chart', noCache: true }
30 | }
31 | ]
32 | }
33 |
34 | export default chartsRouter
35 |
--------------------------------------------------------------------------------
/src/router/modules/nested.js:
--------------------------------------------------------------------------------
1 | /** When your routing table is too long, you can split it into small modules **/
2 |
3 | const nestedRouter = {
4 | path: '/nested',
5 | component: () => import('@/layout/index'),
6 | redirect: '/nested/menu1/menu1-1',
7 | name: 'Nested',
8 | meta: {
9 | title: 'Nested Routes',
10 | icon: 'nested'
11 | },
12 | children: [
13 | {
14 | path: 'menu1',
15 | component: () => import('@/views/nested/menu1/index'), // Parent router-view
16 | name: 'Menu1',
17 | meta: { title: 'Menu 1' },
18 | redirect: '/nested/menu1/menu1-1',
19 | children: [
20 | {
21 | path: 'menu1-1',
22 | component: () => import('@/views/nested/menu1/menu1-1'),
23 | name: 'Menu1-1',
24 | meta: { title: 'Menu 1-1' }
25 | },
26 | {
27 | path: 'menu1-2',
28 | component: () => import('@/views/nested/menu1/menu1-2'),
29 | name: 'Menu1-2',
30 | redirect: '/nested/menu1/menu1-2/menu1-2-1',
31 | meta: { title: 'Menu 1-2' },
32 | children: [
33 | {
34 | path: 'menu1-2-1',
35 | component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1'),
36 | name: 'Menu1-2-1',
37 | meta: { title: 'Menu 1-2-1' }
38 | },
39 | {
40 | path: 'menu1-2-2',
41 | component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2'),
42 | name: 'Menu1-2-2',
43 | meta: { title: 'Menu 1-2-2' }
44 | }
45 | ]
46 | },
47 | {
48 | path: 'menu1-3',
49 | component: () => import('@/views/nested/menu1/menu1-3'),
50 | name: 'Menu1-3',
51 | meta: { title: 'Menu 1-3' }
52 | }
53 | ]
54 | },
55 | {
56 | path: 'menu2',
57 | name: 'Menu2',
58 | component: () => import('@/views/nested/menu2/index'),
59 | meta: { title: 'Menu 2' }
60 | }
61 | ]
62 | }
63 |
64 | export default nestedRouter
65 |
--------------------------------------------------------------------------------
/src/router/modules/table.js:
--------------------------------------------------------------------------------
1 | /** When your routing table is too long, you can split it into small modules **/
2 |
3 | const tableRouter = {
4 | path: '/table',
5 | component: () => import('@/layout/index'),
6 | redirect: '/table/complex-table',
7 | name: 'Table',
8 | meta: {
9 | title: 'Table',
10 | icon: 'table'
11 | },
12 | children: [
13 | {
14 | path: 'dynamic-table',
15 | component: () => import('@/views/table/dynamic-table/index'),
16 | name: 'DynamicTable',
17 | meta: { title: 'Dynamic Table' }
18 | },
19 | // {
20 | // path: 'drag-table',
21 | // component: () => import('@/views/table/drag-table'),
22 | // name: 'DragTable',
23 | // meta: { title: 'Drag Table' }
24 | // },
25 | {
26 | path: 'inline-edit-table',
27 | component: () => import('@/views/table/inline-edit-table'),
28 | name: 'InlineEditTable',
29 | meta: { title: 'Inline Edit' }
30 | },
31 | {
32 | path: 'complex-table',
33 | component: () => import('@/views/table/complex-table'),
34 | name: 'ComplexTable',
35 | meta: { title: 'Complex Table' }
36 | }
37 | ]
38 | }
39 | export default tableRouter
40 |
--------------------------------------------------------------------------------
/src/settings.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | title: 'Vue Element Admin',
3 |
4 | /**
5 | * @type {boolean} true | false
6 | * @description Whether show the settings right-panel
7 | */
8 | showSettings: true,
9 |
10 | /**
11 | * @type {boolean} true | false
12 | * @description Whether need tagsView
13 | */
14 | tagsView: true,
15 |
16 | /**
17 | * @type {boolean} true | false
18 | * @description Whether fix the header
19 | */
20 | fixedHeader: false,
21 |
22 | /**
23 | * @type {boolean} true | false
24 | * @description Whether show the logo in sidebar
25 | */
26 | sidebarLogo: false,
27 |
28 | /**
29 | * @type {string | array} 'production' | ['production', 'development']
30 | * @description Need show err logs component.
31 | * The default is only used in the production env
32 | * If you want to also use it in dev, you can pass ['production', 'development']
33 | */
34 | errorLog: 'production'
35 | }
36 |
--------------------------------------------------------------------------------
/src/store/getters.js:
--------------------------------------------------------------------------------
1 | const getters = {
2 | sidebar: state => state.app.sidebar,
3 | size: state => state.app.size,
4 | device: state => state.app.device,
5 | visitedViews: state => state.tagsView.visitedViews,
6 | cachedViews: state => state.tagsView.cachedViews,
7 | token: state => state.user.token,
8 | avatar: state => state.user.avatar,
9 | name: state => state.user.name,
10 | introduction: state => state.user.introduction,
11 | roles: state => state.user.roles,
12 | permission_routes: state => state.permission.routes,
13 | errorLogs: state => state.errorLog.logs
14 | }
15 | export default getters
16 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore } from 'vuex'
2 | import getters from './getters'
3 |
4 | // https://webpack.js.org/guides/dependency-management/#requirecontext
5 | const modulesFiles = require.context('./modules', true, /\.js$/)
6 |
7 | // you do not need `import app from './modules/app'`
8 | // it will auto require all vuex module from modules file
9 | const modules = modulesFiles.keys().reduce((modules, modulePath) => {
10 | // set './app.js' => 'app'
11 | const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
12 | const value = modulesFiles(modulePath)
13 | modules[moduleName] = value.default
14 | return modules
15 | }, {})
16 |
17 | const store = createStore({
18 | modules,
19 | getters
20 | })
21 |
22 | export default store
23 |
--------------------------------------------------------------------------------
/src/store/modules/app.js:
--------------------------------------------------------------------------------
1 | import Cookies from 'js-cookie'
2 |
3 | const state = {
4 | sidebar: {
5 | opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
6 | withoutAnimation: false
7 | },
8 | device: 'desktop',
9 | size: Cookies.get('size') || 'medium'
10 | }
11 |
12 | const mutations = {
13 | TOGGLE_SIDEBAR: state => {
14 | state.sidebar.opened = !state.sidebar.opened
15 | state.sidebar.withoutAnimation = false
16 | if (state.sidebar.opened) {
17 | Cookies.set('sidebarStatus', 1)
18 | } else {
19 | Cookies.set('sidebarStatus', 0)
20 | }
21 | },
22 | CLOSE_SIDEBAR: (state, withoutAnimation) => {
23 | Cookies.set('sidebarStatus', 0)
24 | state.sidebar.opened = false
25 | state.sidebar.withoutAnimation = withoutAnimation
26 | },
27 | TOGGLE_DEVICE: (state, device) => {
28 | state.device = device
29 | },
30 | SET_SIZE: (state, size) => {
31 | state.size = size
32 | Cookies.set('size', size)
33 | }
34 | }
35 |
36 | const actions = {
37 | toggleSideBar({ commit }) {
38 | commit('TOGGLE_SIDEBAR')
39 | },
40 | closeSideBar({ commit }, { withoutAnimation }) {
41 | commit('CLOSE_SIDEBAR', withoutAnimation)
42 | },
43 | toggleDevice({ commit }, device) {
44 | commit('TOGGLE_DEVICE', device)
45 | },
46 | setSize({ commit }, size) {
47 | commit('SET_SIZE', size)
48 | }
49 | }
50 |
51 | export default {
52 | namespaced: true,
53 | state,
54 | mutations,
55 | actions
56 | }
57 |
--------------------------------------------------------------------------------
/src/store/modules/errorLog.js:
--------------------------------------------------------------------------------
1 | const state = {
2 | logs: []
3 | }
4 |
5 | const mutations = {
6 | ADD_ERROR_LOG: (state, log) => {
7 | state.logs.push(log)
8 | },
9 | CLEAR_ERROR_LOG: (state) => {
10 | state.logs.splice(0)
11 | }
12 | }
13 |
14 | const actions = {
15 | addErrorLog({ commit }, log) {
16 | commit('ADD_ERROR_LOG', log)
17 | },
18 | clearErrorLog({ commit }) {
19 | commit('CLEAR_ERROR_LOG')
20 | }
21 | }
22 |
23 | export default {
24 | namespaced: true,
25 | state,
26 | mutations,
27 | actions
28 | }
29 |
--------------------------------------------------------------------------------
/src/store/modules/permission.js:
--------------------------------------------------------------------------------
1 | import { asyncRoutes, constantRoutes } from '@/router'
2 |
3 | /**
4 | * Use meta.role to determine if the current user has permission
5 | * @param roles
6 | * @param route
7 | */
8 | function hasPermission(roles, route) {
9 | if (route.meta && route.meta.roles) {
10 | return roles.some(role => route.meta.roles.includes(role))
11 | } else {
12 | return true
13 | }
14 | }
15 |
16 | /**
17 | * Filter asynchronous routing tables by recursion
18 | * @param routes asyncRoutes
19 | * @param roles
20 | */
21 | export function filterAsyncRoutes(routes, roles) {
22 | const res = []
23 |
24 | routes.forEach(route => {
25 | const tmp = { ...route }
26 | if (hasPermission(roles, tmp)) {
27 | if (tmp.children) {
28 | tmp.children = filterAsyncRoutes(tmp.children, roles)
29 | }
30 | res.push(tmp)
31 | }
32 | })
33 |
34 | return res
35 | }
36 |
37 | const state = {
38 | routes: [],
39 | addRoutes: []
40 | }
41 |
42 | const mutations = {
43 | SET_ROUTES: (state, routes) => {
44 | state.addRoutes = routes
45 | state.routes = constantRoutes.concat(routes)
46 | }
47 | }
48 |
49 | const actions = {
50 | generateRoutes({ commit }, roles) {
51 | return new Promise(resolve => {
52 | let accessedRoutes
53 | if (roles.includes('admin')) {
54 | accessedRoutes = asyncRoutes || []
55 | } else {
56 | accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
57 | }
58 | commit('SET_ROUTES', accessedRoutes)
59 | resolve(accessedRoutes)
60 | })
61 | }
62 | }
63 |
64 | export default {
65 | namespaced: true,
66 | state,
67 | mutations,
68 | actions
69 | }
70 |
--------------------------------------------------------------------------------
/src/store/modules/settings.js:
--------------------------------------------------------------------------------
1 | import variables from '@/styles/element-variables.scss'
2 | import defaultSettings from '@/settings'
3 |
4 | const { showSettings, tagsView, fixedHeader, sidebarLogo } = defaultSettings
5 |
6 | const state = {
7 | theme: variables.theme,
8 | showSettings: showSettings,
9 | tagsView: tagsView,
10 | fixedHeader: fixedHeader,
11 | sidebarLogo: sidebarLogo
12 | }
13 |
14 | const mutations = {
15 | CHANGE_SETTING: (state, { key, value }) => {
16 | // eslint-disable-next-line no-prototype-builtins
17 | if (state.hasOwnProperty(key)) {
18 | state[key] = value
19 | }
20 | }
21 | }
22 |
23 | const actions = {
24 | changeSetting({ commit }, data) {
25 | commit('CHANGE_SETTING', data)
26 | }
27 | }
28 |
29 | export default {
30 | namespaced: true,
31 | state,
32 | mutations,
33 | actions
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/src/styles/btn.scss:
--------------------------------------------------------------------------------
1 | @import './variables.scss';
2 |
3 | @mixin colorBtn($color) {
4 | background: $color;
5 |
6 | &:hover {
7 | color: $color;
8 |
9 | &:before,
10 | &:after {
11 | background: $color;
12 | }
13 | }
14 | }
15 |
16 | .blue-btn {
17 | @include colorBtn($blue)
18 | }
19 |
20 | .light-blue-btn {
21 | @include colorBtn($light-blue)
22 | }
23 |
24 | .red-btn {
25 | @include colorBtn($red)
26 | }
27 |
28 | .pink-btn {
29 | @include colorBtn($pink)
30 | }
31 |
32 | .green-btn {
33 | @include colorBtn($green)
34 | }
35 |
36 | .tiffany-btn {
37 | @include colorBtn($tiffany)
38 | }
39 |
40 | .yellow-btn {
41 | @include colorBtn($yellow)
42 | }
43 |
44 | .pan-btn {
45 | font-size: 14px;
46 | color: #fff;
47 | padding: 14px 36px;
48 | border-radius: 8px;
49 | border: none;
50 | outline: none;
51 | transition: 600ms ease all;
52 | position: relative;
53 | display: inline-block;
54 |
55 | &:hover {
56 | background: #fff;
57 |
58 | &:before,
59 | &:after {
60 | width: 100%;
61 | transition: 600ms ease all;
62 | }
63 | }
64 |
65 | &:before,
66 | &:after {
67 | content: '';
68 | position: absolute;
69 | top: 0;
70 | right: 0;
71 | height: 2px;
72 | width: 0;
73 | transition: 400ms ease all;
74 | }
75 |
76 | &::after {
77 | right: inherit;
78 | top: inherit;
79 | left: 0;
80 | bottom: 0;
81 | }
82 | }
83 |
84 | .custom-button {
85 | display: inline-block;
86 | line-height: 1;
87 | white-space: nowrap;
88 | cursor: pointer;
89 | background: #fff;
90 | color: #fff;
91 | -webkit-appearance: none;
92 | text-align: center;
93 | box-sizing: border-box;
94 | outline: 0;
95 | margin: 0;
96 | padding: 10px 15px;
97 | font-size: 14px;
98 | border-radius: 4px;
99 | }
100 |
--------------------------------------------------------------------------------
/src/styles/element-ui.scss:
--------------------------------------------------------------------------------
1 | // cover some element-ui styles
2 |
3 | .el-breadcrumb__inner,
4 | .el-breadcrumb__inner a {
5 | font-weight: 400 !important;
6 | }
7 |
8 | .el-upload {
9 | input[type="file"] {
10 | display: none !important;
11 | }
12 | }
13 |
14 | .el-upload__input {
15 | display: none;
16 | }
17 |
18 | .cell {
19 | .el-tag {
20 | margin-right: 0px;
21 | }
22 | }
23 |
24 | .small-padding {
25 | .cell {
26 | padding-left: 5px;
27 | padding-right: 5px;
28 | }
29 | }
30 |
31 | .fixed-width {
32 | .el-button--mini {
33 | padding: 7px 10px;
34 | min-width: 60px;
35 | }
36 | }
37 |
38 | .status-col {
39 | .cell {
40 | padding: 0 10px;
41 | text-align: center;
42 |
43 | .el-tag {
44 | margin-right: 0px;
45 | }
46 | }
47 | }
48 |
49 | // to fixed https://github.com/ElemeFE/element/issues/2461
50 | .el-dialog {
51 | transform: none;
52 | left: 0;
53 | position: relative;
54 | margin: 0 auto;
55 | }
56 |
57 | // refine element ui upload
58 | .upload-container {
59 | .el-upload {
60 | width: 100%;
61 |
62 | .el-upload-dragger {
63 | width: 100%;
64 | height: 200px;
65 | }
66 | }
67 | }
68 |
69 | // dropdown
70 | .el-dropdown-menu {
71 | a {
72 | display: block
73 | }
74 | }
75 |
76 | // fix date-picker ui bug in filter-item
77 | .el-range-editor.el-input__inner {
78 | display: inline-flex !important;
79 | }
80 |
81 | // to fix el-date-picker css style
82 | .el-range-separator {
83 | box-sizing: content-box;
84 | }
85 |
--------------------------------------------------------------------------------
/src/styles/element-variables.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * I think element-ui's default theme color is too light for long-term use.
3 | * So I modified the default color and you can modify it to your liking.
4 | **/
5 |
6 | /* theme color */
7 | $--color-primary: #1890ff;
8 | $--color-success: #13ce66;
9 | $--color-warning: #ffba00;
10 | $--color-danger: #ff4949;
11 | // $--color-info: #1E1E1E;
12 |
13 | $--button-font-weight: 400;
14 |
15 | // $--color-text-regular: #1f2d3d;
16 |
17 | $--border-color-light: #dfe4ed;
18 | $--border-color-lighter: #e6ebf5;
19 |
20 | $--table-border: 1px solid #dfe6ec;
21 |
22 | /* icon font path, required */
23 | $--font-path: "~element-plus/lib/theme-chalk/fonts";
24 |
25 | @import "~element-plus/packages/theme-chalk/src/index";
26 |
27 | // the :export directive is the magic sauce for webpack
28 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
29 | :export {
30 | theme: $--color-primary;
31 | }
32 |
--------------------------------------------------------------------------------
/src/styles/mixin.scss:
--------------------------------------------------------------------------------
1 | @mixin clearfix {
2 | &:after {
3 | content: "";
4 | display: table;
5 | clear: both;
6 | }
7 | }
8 |
9 | @mixin scrollBar {
10 | &::-webkit-scrollbar-track-piece {
11 | background: #d3dce6;
12 | }
13 |
14 | &::-webkit-scrollbar {
15 | width: 6px;
16 | }
17 |
18 | &::-webkit-scrollbar-thumb {
19 | background: #99a9bf;
20 | border-radius: 20px;
21 | }
22 | }
23 |
24 | @mixin relative {
25 | position: relative;
26 | width: 100%;
27 | height: 100%;
28 | }
29 |
30 | @mixin pct($pct) {
31 | width: #{$pct};
32 | position: relative;
33 | margin: 0 auto;
34 | }
35 |
36 | @mixin triangle($width, $height, $color, $direction) {
37 | $width: $width/2;
38 | $color-border-style: $height solid $color;
39 | $transparent-border-style: $width solid transparent;
40 | height: 0;
41 | width: 0;
42 |
43 | @if $direction==up {
44 | border-bottom: $color-border-style;
45 | border-left: $transparent-border-style;
46 | border-right: $transparent-border-style;
47 | }
48 |
49 | @else if $direction==right {
50 | border-left: $color-border-style;
51 | border-top: $transparent-border-style;
52 | border-bottom: $transparent-border-style;
53 | }
54 |
55 | @else if $direction==down {
56 | border-top: $color-border-style;
57 | border-left: $transparent-border-style;
58 | border-right: $transparent-border-style;
59 | }
60 |
61 | @else if $direction==left {
62 | border-right: $color-border-style;
63 | border-top: $transparent-border-style;
64 | border-bottom: $transparent-border-style;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/styles/transition.scss:
--------------------------------------------------------------------------------
1 | // global transition css
2 |
3 | /* fade */
4 | .fade-enter-active,
5 | .fade-leave-active {
6 | transition: opacity 0.28s;
7 | }
8 |
9 | .fade-enter,
10 | .fade-leave-active {
11 | opacity: 0;
12 | }
13 |
14 | /* fade-transform */
15 | .fade-transform-leave-active,
16 | .fade-transform-enter-active {
17 | transition: all .5s;
18 | }
19 |
20 | .fade-transform-enter {
21 | opacity: 0;
22 | transform: translateX(-30px);
23 | }
24 |
25 | .fade-transform-leave-to {
26 | opacity: 0;
27 | transform: translateX(30px);
28 | }
29 |
30 | /* breadcrumb transition */
31 | .breadcrumb-enter-active,
32 | .breadcrumb-leave-active {
33 | transition: all .5s;
34 | }
35 |
36 | .breadcrumb-enter,
37 | .breadcrumb-leave-active {
38 | opacity: 0;
39 | transform: translateX(20px);
40 | }
41 |
42 | .breadcrumb-move {
43 | transition: all .5s;
44 | }
45 |
46 | .breadcrumb-leave-active {
47 | position: absolute;
48 | }
49 |
--------------------------------------------------------------------------------
/src/styles/variables.scss:
--------------------------------------------------------------------------------
1 | // base color
2 | $blue:#324157;
3 | $light-blue:#3A71A8;
4 | $red:#C03639;
5 | $pink: #E65D6E;
6 | $green: #30B08F;
7 | $tiffany: #4AB7BD;
8 | $yellow:#FEC171;
9 | $panGreen: #30B08F;
10 |
11 | // sidebar
12 | $menuText:#bfcbd9;
13 | $menuActiveText:#409EFF;
14 | $subMenuActiveText:#f4f4f5; // https://github.com/ElemeFE/element/issues/12951
15 |
16 | $menuBg:#304156;
17 | $menuHover:#263445;
18 |
19 | $subMenuBg:#1f2d3d;
20 | $subMenuHover:#001528;
21 |
22 | $sideBarWidth: 210px;
23 |
24 | // the :export directive is the magic sauce for webpack
25 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
26 | :export {
27 | menuText: $menuText;
28 | menuActiveText: $menuActiveText;
29 | subMenuActiveText: $subMenuActiveText;
30 | menuBg: $menuBg;
31 | menuHover: $menuHover;
32 | subMenuBg: $subMenuBg;
33 | subMenuHover: $subMenuHover;
34 | sideBarWidth: $sideBarWidth;
35 | }
36 |
--------------------------------------------------------------------------------
/src/utils/auth.js:
--------------------------------------------------------------------------------
1 | import Cookies from 'js-cookie'
2 |
3 | const TokenKey = 'Admin-Token'
4 |
5 | export function getToken() {
6 | return Cookies.get(TokenKey)
7 | }
8 |
9 | export function setToken(token) {
10 | return Cookies.set(TokenKey, token)
11 | }
12 |
13 | export function removeToken() {
14 | return Cookies.remove(TokenKey)
15 | }
16 |
--------------------------------------------------------------------------------
/src/utils/clipboard.js:
--------------------------------------------------------------------------------
1 | import app from '@/main'
2 | import Clipboard from 'clipboard'
3 |
4 | function clipboardSuccess() {
5 | app.config.globalProperties.$message({
6 | message: 'Copy successfully',
7 | type: 'success',
8 | duration: 1500
9 | })
10 | }
11 |
12 | function clipboardError() {
13 | app.config.globalProperties.$message({
14 | message: 'Copy failed',
15 | type: 'error'
16 | })
17 | }
18 |
19 | export default function handleClipboard(text, event) {
20 | const clipboard = new Clipboard(event.target, {
21 | text: () => text
22 | })
23 | clipboard.on('success', () => {
24 | clipboardSuccess()
25 | clipboard.destroy()
26 | })
27 | clipboard.on('error', () => {
28 | clipboardError()
29 | clipboard.destroy()
30 | })
31 | clipboard.onClick(event)
32 | }
33 |
--------------------------------------------------------------------------------
/src/utils/error-log.js:
--------------------------------------------------------------------------------
1 | import app from '@/main'
2 | import store from '@/store'
3 | import { isString, isArray } from '@/utils/validate'
4 | import settings from '@/settings'
5 |
6 | // you can set in settings.js
7 | // errorLog:'production' | ['production', 'development']
8 | const { errorLog: needErrorLog } = settings
9 |
10 | function checkNeed() {
11 | const env = process.env.NODE_ENV
12 | if (isString(needErrorLog)) {
13 | return env === needErrorLog
14 | }
15 | if (isArray(needErrorLog)) {
16 | return needErrorLog.includes(env)
17 | }
18 | return false
19 | }
20 |
21 | if (checkNeed()) {
22 | app.config.errorHandler = function(err, vm, info, a) {
23 | // Don't ask me why I use Vue.nextTick, it just a hack.
24 | // detail see https://forum.vuejs.org/t/dispatch-in-vue-config-errorhandler-has-some-problem/23500
25 | app.nextTick(() => {
26 | store.dispatch('errorLog/addErrorLog', {
27 | err,
28 | vm,
29 | info,
30 | url: window.location.href
31 | })
32 | console.error(err, info)
33 | })
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/utils/get-page-title.js:
--------------------------------------------------------------------------------
1 | import defaultSettings from '@/settings'
2 |
3 | const title = defaultSettings.title || 'Vue Element Admin'
4 |
5 | export default function getPageTitle(pageTitle) {
6 | if (pageTitle) {
7 | return `${pageTitle} - ${title}`
8 | }
9 | return `${title}`
10 | }
11 |
--------------------------------------------------------------------------------
/src/utils/open-window.js:
--------------------------------------------------------------------------------
1 | /**
2 | *Created by PanJiaChen on 16/11/29.
3 | * @param {Sting} url
4 | * @param {Sting} title
5 | * @param {Number} w
6 | * @param {Number} h
7 | */
8 | export default function openWindow(url, title, w, h) {
9 | // Fixes dual-screen position Most browsers Firefox
10 | const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left
11 | const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top
12 |
13 | const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width
14 | const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height
15 |
16 | const left = ((width / 2) - (w / 2)) + dualScreenLeft
17 | const top = ((height / 2) - (h / 2)) + dualScreenTop
18 | const newWindow = window.open(url, title, 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left)
19 |
20 | // Puts focus on the newWindow
21 | if (window.focus) {
22 | newWindow.focus()
23 | }
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/src/utils/permission.js:
--------------------------------------------------------------------------------
1 | import store from '@/store'
2 |
3 | /**
4 | * @param {Array} value
5 | * @returns {Boolean}
6 | * @example see @/views/permission/directive.vue
7 | */
8 | export default function checkPermission(value) {
9 | if (value && value instanceof Array && value.length > 0) {
10 | const roles = store.getters && store.getters.roles
11 | const permissionRoles = value
12 |
13 | const hasPermission = roles.some(role => {
14 | return permissionRoles.includes(role)
15 | })
16 | return hasPermission
17 | } else {
18 | console.error(`need roles! Like v-permission="['admin','editor']"`)
19 | return false
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/utils/scroll-to.js:
--------------------------------------------------------------------------------
1 | Math.easeInOutQuad = function(t, b, c, d) {
2 | t /= d / 2
3 | if (t < 1) {
4 | return c / 2 * t * t + b
5 | }
6 | t--
7 | return -c / 2 * (t * (t - 2) - 1) + b
8 | }
9 |
10 | // requestAnimationFrame for Smart Animating http://goo.gl/sx5sts
11 | var requestAnimFrame = (function() {
12 | return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) }
13 | })()
14 |
15 | /**
16 | * Because it's so fucking difficult to detect the scrolling element, just move them all
17 | * @param {number} amount
18 | */
19 | function move(amount) {
20 | document.documentElement.scrollTop = amount
21 | document.body.parentNode.scrollTop = amount
22 | document.body.scrollTop = amount
23 | }
24 |
25 | function position() {
26 | return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop
27 | }
28 |
29 | /**
30 | * @param {number} to
31 | * @param {number} duration
32 | * @param {Function} callback
33 | */
34 | export function scrollTo(to, duration, callback) {
35 | const start = position()
36 | const change = to - start
37 | const increment = 20
38 | let currentTime = 0
39 | duration = (typeof (duration) === 'undefined') ? 500 : duration
40 | var animateScroll = function() {
41 | // increment the time
42 | currentTime += increment
43 | // find the value with the quadratic in-out easing function
44 | var val = Math.easeInOutQuad(currentTime, start, change, duration)
45 | // move the document.body
46 | move(val)
47 | // do the animation unless its over
48 | if (currentTime < duration) {
49 | requestAnimFrame(animateScroll)
50 | } else {
51 | if (callback && typeof (callback) === 'function') {
52 | // the animation is done so lets callback
53 | callback()
54 | }
55 | }
56 | }
57 | animateScroll()
58 | }
59 |
--------------------------------------------------------------------------------
/src/utils/validate.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by PanJiaChen on 16/11/18.
3 | */
4 |
5 | /**
6 | * @param {string} path
7 | * @returns {Boolean}
8 | */
9 | export function isExternal(path) {
10 | return /^(https?:|mailto:|tel:)/.test(path)
11 | }
12 |
13 | /**
14 | * @param {string} str
15 | * @returns {Boolean}
16 | */
17 | export function validUsername(str) {
18 | const valid_map = ['admin', 'editor']
19 | return valid_map.indexOf(str.trim()) >= 0
20 | }
21 |
22 | /**
23 | * @param {string} url
24 | * @returns {Boolean}
25 | */
26 | export function validURL(url) {
27 | 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.,?'\\+&%$#=~_-]+))*$/
28 | return reg.test(url)
29 | }
30 |
31 | /**
32 | * @param {string} str
33 | * @returns {Boolean}
34 | */
35 | export function validLowerCase(str) {
36 | const reg = /^[a-z]+$/
37 | return reg.test(str)
38 | }
39 |
40 | /**
41 | * @param {string} str
42 | * @returns {Boolean}
43 | */
44 | export function validUpperCase(str) {
45 | const reg = /^[A-Z]+$/
46 | return reg.test(str)
47 | }
48 |
49 | /**
50 | * @param {string} str
51 | * @returns {Boolean}
52 | */
53 | export function validAlphabets(str) {
54 | const reg = /^[A-Za-z]+$/
55 | return reg.test(str)
56 | }
57 |
58 | /**
59 | * @param {string} email
60 | * @returns {Boolean}
61 | */
62 | export function validEmail(email) {
63 | 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,}))$/
64 | return reg.test(email)
65 | }
66 |
67 | /**
68 | * @param {string} str
69 | * @returns {Boolean}
70 | */
71 | export function isString(str) {
72 | if (typeof str === 'string' || str instanceof String) {
73 | return true
74 | }
75 | return false
76 | }
77 |
78 | /**
79 | * @param {Array} arg
80 | * @returns {Boolean}
81 | */
82 | export function isArray(arg) {
83 | if (typeof Array.isArray === 'undefined') {
84 | return Object.prototype.toString.call(arg) === '[object Array]'
85 | }
86 | return Array.isArray(arg)
87 | }
88 |
--------------------------------------------------------------------------------
/src/vendor/Export2Zip.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import { saveAs } from 'file-saver'
3 | import JSZip from 'jszip'
4 |
5 | export function export_txt_to_zip(th, jsonData, txtName, zipName) {
6 | const zip = new JSZip()
7 | const txt_name = txtName || 'file'
8 | const zip_name = zipName || 'file'
9 | const data = jsonData
10 | let txtData = `${th}\r\n`
11 | data.forEach((row) => {
12 | let tempStr = ''
13 | tempStr = row.toString()
14 | txtData += `${tempStr}\r\n`
15 | })
16 | zip.file(`${txt_name}.txt`, txtData)
17 | zip.generateAsync({
18 | type: "blob"
19 | }).then((blob) => {
20 | saveAs(blob, `${zip_name}.zip`)
21 | }, (err) => {
22 | alert('导出失败')
23 | })
24 | }
25 |
--------------------------------------------------------------------------------
/src/views/charts/keyboard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 |
16 |
23 |
24 |
--------------------------------------------------------------------------------
/src/views/charts/line.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 |
16 |
23 |
24 |
--------------------------------------------------------------------------------
/src/views/charts/mix-chart.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 |
16 |
23 |
24 |
--------------------------------------------------------------------------------
/src/views/clipboard/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | copy
8 |
9 |
10 |
11 |
12 |
13 | copy
14 |
15 |
16 |
17 |
18 |
19 |
20 |
49 |
50 |
--------------------------------------------------------------------------------
/src/views/components-demo/avatar-upload.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 | Change Avatar
12 |
13 |
14 |
24 |
25 |
26 |
27 |
53 |
54 |
61 |
62 |
--------------------------------------------------------------------------------
/src/views/components-demo/dnd-list.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
39 |
40 |
--------------------------------------------------------------------------------
/src/views/components-demo/drag-dialog.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | open a Drag Dialog
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
62 |
--------------------------------------------------------------------------------
/src/views/components-demo/drag-kanban.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
39 |
66 |
67 |
--------------------------------------------------------------------------------
/src/views/components-demo/drag-select.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{ item }}
10 |
11 |
12 |
13 |
14 |
15 |
44 |
--------------------------------------------------------------------------------
/src/views/components-demo/dropzone.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
31 |
32 |
--------------------------------------------------------------------------------
/src/views/components-demo/json-editor.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
29 |
30 |
36 |
37 |
--------------------------------------------------------------------------------
/src/views/components-demo/split-pane.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
39 |
40 |
68 |
--------------------------------------------------------------------------------
/src/views/components-demo/tinymce.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
30 |
31 |
36 |
37 |
--------------------------------------------------------------------------------
/src/views/dashboard/admin/components/PieChart.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
80 |
--------------------------------------------------------------------------------
/src/views/dashboard/admin/components/TodoList/Todo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
22 |
23 |
24 |
25 |
82 |
--------------------------------------------------------------------------------
/src/views/dashboard/admin/components/TransactionTable.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ orderNoFilter(scope.row.order_no) }}
6 |
7 |
8 |
9 |
10 | ¥{{ toThousandFilter(scope.row.price) }}
11 |
12 |
13 |
14 |
15 |
16 | {{ row.status }}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
57 |
--------------------------------------------------------------------------------
/src/views/dashboard/admin/components/mixins/resize.js:
--------------------------------------------------------------------------------
1 | import { debounce } from '@/utils'
2 |
3 | export default {
4 | data() {
5 | return {
6 | $_sidebarElm: null,
7 | $_resizeHandler: null
8 | }
9 | },
10 | mounted() {
11 | this.$_resizeHandler = debounce(() => {
12 | if (this.chart) {
13 | this.chart.resize()
14 | }
15 | }, 100)
16 | this.$_initResizeEvent()
17 | this.$_initSidebarResizeEvent()
18 | },
19 | beforeUnmount() {
20 | this.$_destroyResizeEvent()
21 | this.$_destroySidebarResizeEvent()
22 | },
23 | // to fixed bug when cached by keep-alive
24 | // https://github.com/PanJiaChen/vue-element-admin/issues/2116
25 | activated() {
26 | this.$_initResizeEvent()
27 | this.$_initSidebarResizeEvent()
28 | },
29 | deactivated() {
30 | this.$_destroyResizeEvent()
31 | this.$_destroySidebarResizeEvent()
32 | },
33 | methods: {
34 | // use $_ for mixins properties
35 | // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
36 | $_initResizeEvent() {
37 | window.addEventListener('resize', this.$_resizeHandler)
38 | },
39 | $_destroyResizeEvent() {
40 | window.removeEventListener('resize', this.$_resizeHandler)
41 | },
42 | $_sidebarResizeHandler(e) {
43 | if (e.propertyName === 'width') {
44 | this.$_resizeHandler()
45 | }
46 | },
47 | $_initSidebarResizeEvent() {
48 | this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0]
49 | this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)
50 | },
51 | $_destroySidebarResizeEvent() {
52 | this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/views/dashboard/editor/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Your roles:
6 | {{ item }}
7 |
8 |
9 |
10 | {{ name }}
11 | Editor's Dashboard
12 |
13 |
14 |
15 |
![]()
16 |
17 |
18 |
19 |
20 |
42 |
43 |
75 |
--------------------------------------------------------------------------------
/src/views/dashboard/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
32 |
--------------------------------------------------------------------------------
/src/views/error-log/components/ErrorTestA.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ a }}
5 |
6 |
7 |
8 |
9 |
14 |
--------------------------------------------------------------------------------
/src/views/error-log/components/ErrorTestB.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
--------------------------------------------------------------------------------
/src/views/error-log/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Please click the bug icon in the upper right corner
6 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
27 |
28 |
33 |
--------------------------------------------------------------------------------
/src/views/example/components/Dropdown/Comment.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ !comment_disabled ? "Comment: opened" : "Comment: closed" }}
5 |
6 |
7 |
8 |
9 |
10 |
11 | Close comment
12 | Open comment
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
40 |
--------------------------------------------------------------------------------
/src/views/example/components/Dropdown/Platform.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Platfroms({{ platforms.length }})
5 |
6 |
7 |
8 |
9 |
10 | {{ item.name }}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
47 |
--------------------------------------------------------------------------------
/src/views/example/components/Dropdown/SourceUrl.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Link
5 |
6 |
7 |
8 |
9 |
10 |
11 | URL
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
39 |
--------------------------------------------------------------------------------
/src/views/example/components/Dropdown/index.js:
--------------------------------------------------------------------------------
1 | export { default as CommentDropdown } from './Comment'
2 | export { default as PlatformDropdown } from './Platform'
3 | export { default as SourceUrlDropdown } from './SourceUrl'
4 |
--------------------------------------------------------------------------------
/src/views/example/components/Warning.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/views/example/create.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 |
--------------------------------------------------------------------------------
/src/views/example/edit.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 |
--------------------------------------------------------------------------------
/src/views/excel/components/AutoWidthOption.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | True
7 |
8 |
9 | False
10 |
11 |
12 |
13 |
14 |
15 |
35 |
--------------------------------------------------------------------------------
/src/views/excel/components/BookTypeOption.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
40 |
--------------------------------------------------------------------------------
/src/views/excel/components/FilenameOption.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
28 |
--------------------------------------------------------------------------------
/src/views/excel/upload-excel.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
43 |
--------------------------------------------------------------------------------
/src/views/guide/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 | Show Guide
10 |
11 |
12 |
13 |
14 |
37 |
--------------------------------------------------------------------------------
/src/views/guide/steps.js:
--------------------------------------------------------------------------------
1 | const steps = [
2 | {
3 | element: '#hamburger-container',
4 | popover: {
5 | title: 'Hamburger',
6 | description: 'Open && Close sidebar',
7 | position: 'bottom'
8 | }
9 | },
10 | {
11 | element: '#breadcrumb-container',
12 | popover: {
13 | title: 'Breadcrumb',
14 | description: 'Indicate the current page location',
15 | position: 'bottom'
16 | }
17 | },
18 | {
19 | element: '#header-search',
20 | popover: {
21 | title: 'Page Search',
22 | description: 'Page search, quick navigation',
23 | position: 'left'
24 | }
25 | },
26 | {
27 | element: '#screenfull',
28 | popover: {
29 | title: 'Screenfull',
30 | description: 'Set the page into fullscreen',
31 | position: 'left'
32 | }
33 | },
34 | {
35 | element: '#size-select',
36 | popover: {
37 | title: 'Switch Size',
38 | description: 'Switch the system size',
39 | position: 'left'
40 | }
41 | },
42 | {
43 | element: '#tags-view-container',
44 | popover: {
45 | title: 'Tags view',
46 | description: 'The history of the page you visited',
47 | position: 'bottom'
48 | },
49 | padding: 0
50 | }
51 | ]
52 |
53 | export default steps
54 |
--------------------------------------------------------------------------------
/src/views/icons/svg-icons.js:
--------------------------------------------------------------------------------
1 | const req = require.context('../../icons/svg', false, /\.svg$/)
2 | const requireAll = requireContext => requireContext.keys()
3 |
4 | const re = /\.\/(.*)\.svg/
5 |
6 | const svgIcons = requireAll(req).map(i => {
7 | return i.match(re)[1]
8 | })
9 |
10 | export default svgIcons
11 |
--------------------------------------------------------------------------------
/src/views/login/auth-redirect.vue:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/src/views/nested/menu1/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/views/nested/menu1/menu1-1/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/views/nested/menu1/menu1-2/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/views/nested/menu1/menu1-2/menu1-2-1/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/views/nested/menu1/menu1-2/menu1-2-2/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/views/nested/menu1/menu1-3/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/views/nested/menu2/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/views/pdf/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 | Click to download PDF
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/views/permission/components/SwitchRoles.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Your roles: {{ roles }}
5 |
6 | Switch roles:
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
33 |
--------------------------------------------------------------------------------
/src/views/permission/page.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
20 |
--------------------------------------------------------------------------------
/src/views/profile/components/Account.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Update
11 |
12 |
13 |
14 |
15 |
39 |
--------------------------------------------------------------------------------
/src/views/profile/components/Timeline.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ item.title }}
7 | {{ item.content }}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
44 |
--------------------------------------------------------------------------------
/src/views/profile/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
69 |
--------------------------------------------------------------------------------
/src/views/qiniu/upload.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 将文件拖到此处,或点击上传
6 |
7 |
8 |
9 |
10 |
42 |
--------------------------------------------------------------------------------
/src/views/redirect/index.vue:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/src/views/tab/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | mounted times :{{ createdTimes }}
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
52 |
53 |
58 |
--------------------------------------------------------------------------------
/src/views/table/dynamic-table/components/FixedThead.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | apple
7 |
8 |
9 | banana
10 |
11 |
12 | orange
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | {{ scope.row[fruit] }}
22 |
23 |
24 |
25 |
26 |
27 |
28 |
62 |
63 |
--------------------------------------------------------------------------------
/src/views/table/dynamic-table/components/UnfixedThead.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | apple
7 |
8 |
9 | banana
10 |
11 |
12 | orange
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | {{ scope.row[fruit] }}
22 |
23 |
24 |
25 |
26 |
27 |
28 |
51 |
--------------------------------------------------------------------------------
/src/views/table/dynamic-table/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Fixed header, sorted by header order,
5 |
6 |
7 |
8 |
9 | Not fixed header, sorted by click order
10 |
11 |
12 |
13 |
14 |
15 |
24 |
25 |
--------------------------------------------------------------------------------
/tests/unit/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | jest: true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/tests/unit/components/Hamburger.spec.js:
--------------------------------------------------------------------------------
1 | import { shallowMount } from '@vue/test-utils'
2 | import Hamburger from '@/components/Hamburger/index.vue'
3 | describe('Hamburger.vue', () => {
4 | it('toggle click', () => {
5 | const wrapper = shallowMount(Hamburger)
6 | const mockFn = jest.fn()
7 | wrapper.vm.$on('toggleClick', mockFn)
8 | wrapper.find('.hamburger').trigger('click')
9 | expect(mockFn).toBeCalled()
10 | })
11 | it('prop isActive', () => {
12 | const wrapper = shallowMount(Hamburger)
13 | wrapper.setProps({ isActive: true })
14 | expect(wrapper.contains('.is-active')).toBe(true)
15 | wrapper.setProps({ isActive: false })
16 | expect(wrapper.contains('.is-active')).toBe(false)
17 | })
18 | })
19 |
--------------------------------------------------------------------------------
/tests/unit/components/SvgIcon.spec.js:
--------------------------------------------------------------------------------
1 | import { shallowMount } from '@vue/test-utils'
2 | import SvgIcon from '@/components/SvgIcon/index.vue'
3 | describe('SvgIcon.vue', () => {
4 | it('iconClass', () => {
5 | const wrapper = shallowMount(SvgIcon, {
6 | propsData: {
7 | iconClass: 'test'
8 | }
9 | })
10 | expect(wrapper.find('use').attributes().href).toBe('#icon-test')
11 | })
12 | it('className', () => {
13 | const wrapper = shallowMount(SvgIcon, {
14 | propsData: {
15 | iconClass: 'test'
16 | }
17 | })
18 | expect(wrapper.classes().length).toBe(1)
19 | wrapper.setProps({ className: 'test' })
20 | expect(wrapper.classes().includes('test')).toBe(true)
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/tests/unit/utils/formatTime.spec.js:
--------------------------------------------------------------------------------
1 | import { formatTime } from '@/utils/index.js'
2 | describe('Utils:formatTime', () => {
3 | const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01"
4 | const retrofit = 5 * 1000
5 |
6 | it('ten digits timestamp', () => {
7 | expect(formatTime((d / 1000).toFixed(0))).toBe('7月13日17时54分')
8 | })
9 | it('test now', () => {
10 | expect(formatTime(+new Date() - 1)).toBe('刚刚')
11 | })
12 | it('less two minute', () => {
13 | expect(formatTime(+new Date() - 60 * 2 * 1000 + retrofit)).toBe('2分钟前')
14 | })
15 | it('less two hour', () => {
16 | expect(formatTime(+new Date() - 60 * 60 * 2 * 1000 + retrofit)).toBe('2小时前')
17 | })
18 | it('less one day', () => {
19 | expect(formatTime(+new Date() - 60 * 60 * 24 * 1 * 1000)).toBe('1天前')
20 | })
21 | it('more than one day', () => {
22 | expect(formatTime(d)).toBe('7月13日17时54分')
23 | })
24 | it('format', () => {
25 | expect(formatTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54')
26 | expect(formatTime(d, '{y}-{m}-{d}')).toBe('2018-07-13')
27 | expect(formatTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54')
28 | })
29 | })
30 |
--------------------------------------------------------------------------------
/tests/unit/utils/param2Obj.spec.js:
--------------------------------------------------------------------------------
1 | import { param2Obj } from '@/utils/index.js'
2 | describe('Utils:param2Obj', () => {
3 | const url = 'https://github.com/PanJiaChen/vue-element-admin?name=bill&age=29&sex=1&field=dGVzdA==&key=%E6%B5%8B%E8%AF%95'
4 |
5 | it('param2Obj test', () => {
6 | expect(param2Obj(url)).toEqual({
7 | name: 'bill',
8 | age: '29',
9 | sex: '1',
10 | field: window.btoa('test'),
11 | key: '测试'
12 | })
13 | })
14 | })
15 |
--------------------------------------------------------------------------------
/tests/unit/utils/parseTime.spec.js:
--------------------------------------------------------------------------------
1 | import { parseTime } from '@/utils/index.js'
2 |
3 | describe('Utils:parseTime', () => {
4 | const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01"
5 | it('timestamp', () => {
6 | expect(parseTime(d)).toBe('2018-07-13 17:54:01')
7 | })
8 |
9 | it('timestamp string', () => {
10 | expect(parseTime((d + ''))).toBe('2018-07-13 17:54:01')
11 | })
12 |
13 | it('ten digits timestamp', () => {
14 | expect(parseTime((d / 1000).toFixed(0))).toBe('2018-07-13 17:54:01')
15 | })
16 | it('new Date', () => {
17 | expect(parseTime(new Date(d))).toBe('2018-07-13 17:54:01')
18 | })
19 | it('format', () => {
20 | expect(parseTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54')
21 | expect(parseTime(d, '{y}-{m}-{d}')).toBe('2018-07-13')
22 | expect(parseTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54')
23 | })
24 | it('get the day of the week', () => {
25 | expect(parseTime(d, '{a}')).toBe('五') // 星期五
26 | })
27 | it('get the day of the week', () => {
28 | expect(parseTime(+d + 1000 * 60 * 60 * 24 * 2, '{a}')).toBe('日') // 星期日
29 | })
30 | it('empty argument', () => {
31 | expect(parseTime()).toBeNull()
32 | })
33 |
34 | it('null', () => {
35 | expect(parseTime(null)).toBeNull()
36 | })
37 | })
38 |
--------------------------------------------------------------------------------
/tests/unit/utils/validate.spec.js:
--------------------------------------------------------------------------------
1 | import { validUsername, validURL, validLowerCase, validUpperCase, validAlphabets } from '@/utils/validate.js'
2 | describe('Utils:validate', () => {
3 | it('validUsername', () => {
4 | expect(validUsername('admin')).toBe(true)
5 | expect(validUsername('editor')).toBe(true)
6 | expect(validUsername('xxxx')).toBe(false)
7 | })
8 | it('validURL', () => {
9 | expect(validURL('https://github.com/PanJiaChen/vue-element-admin')).toBe(true)
10 | expect(validURL('http://github.com/PanJiaChen/vue-element-admin')).toBe(true)
11 | expect(validURL('github.com/PanJiaChen/vue-element-admin')).toBe(false)
12 | })
13 | it('validLowerCase', () => {
14 | expect(validLowerCase('abc')).toBe(true)
15 | expect(validLowerCase('Abc')).toBe(false)
16 | expect(validLowerCase('123abc')).toBe(false)
17 | })
18 | it('validUpperCase', () => {
19 | expect(validUpperCase('ABC')).toBe(true)
20 | expect(validUpperCase('Abc')).toBe(false)
21 | expect(validUpperCase('123ABC')).toBe(false)
22 | })
23 | it('validAlphabets', () => {
24 | expect(validAlphabets('ABC')).toBe(true)
25 | expect(validAlphabets('Abc')).toBe(true)
26 | expect(validAlphabets('123aBC')).toBe(false)
27 | })
28 | })
29 |
--------------------------------------------------------------------------------