├── .editorconfig
├── .env.development
├── .env.production
├── .env.staging
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── LICENSE
├── README.md
├── README.zh-CN.md
├── babel.config.js
├── build
└── index.js
├── deploy-vue.sh
├── install.sh
├── jsconfig.json
├── package.json
├── plopfile.js
├── postcss.config.js
├── public
├── favicon.ico
└── index.html
├── src
├── App.vue
├── api
│ ├── system
│ │ ├── sys-department-api.js
│ │ ├── sys-permission-api.js
│ │ ├── sys-role-api.js
│ │ ├── sys-user-api.js
│ │ └── upload-api.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
├── components
│ ├── BackToTop
│ │ └── index.vue
│ ├── Breadcrumb
│ │ └── index.vue
│ ├── Charts
│ │ ├── Keyboard.vue
│ │ ├── LineMarker.vue
│ │ ├── MixChart.vue
│ │ └── mixins
│ │ │ └── resize.js
│ ├── DatePickerRange
│ │ └── index.vue
│ ├── 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
│ ├── TreeSelect
│ │ └── index.vue
│ ├── 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
├── 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
├── 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
│ ├── 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
│ ├── icons
│ ├── element-icons.js
│ ├── index.vue
│ └── svg-icons.js
│ ├── login
│ ├── auth-redirect.vue
│ ├── components
│ │ └── SocialSignin.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
│ ├── redirect
│ └── index.vue
│ └── system
│ ├── sys-menu
│ ├── components
│ │ └── sys-menu.vue
│ └── sys-menu-list.vue
│ ├── sys-role
│ ├── components
│ │ └── sys-role.vue
│ ├── sys-role-list.vue
│ └── sys-role-menu.vue
│ └── sys-user
│ ├── components
│ ├── sys-user-head.vue
│ ├── sys-user-password.vue
│ └── sys-user.vue
│ └── sys-user-list.vue
└── 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 = 'http://localhost:8888/api'
6 |
7 | # vue-cli uses the VUE_CLI_BABEL_TRANSPILE_MODULES environment variable,
8 | # to control whether the babel-plugin-dynamic-import-node plugin is enabled.
9 | # It only does one thing by converting all import() to require().
10 | # This configuration can significantly increase the speed of hot updates,
11 | # when you have a large number of pages.
12 | # Detail: https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/babel-preset-app/index.js
13 |
14 | VUE_CLI_BABEL_TRANSPILE_MODULES = true
15 |
--------------------------------------------------------------------------------
/.env.production:
--------------------------------------------------------------------------------
1 | # just a flag
2 | ENV = 'production'
3 |
4 | # base api
5 | VUE_APP_BASE_API = 'http://47.105.159.10:8888/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/assets
3 | public
4 | dist
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | **/*.log
8 |
9 | tests/**/coverage/
10 | tests/e2e/reports
11 | selenium-debug.log
12 |
13 | # Editor directories and files
14 | .idea
15 | .vscode
16 | *.suo
17 | *.ntvs*
18 | *.njsproj
19 | *.sln
20 | *.local
21 |
22 | package-lock.json
23 | yarn.lock
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # spring-boot-plus-vue
2 |
3 | ## 管理系统主页
4 | [http://47.105.159.10/](http://47.105.159.10/)
5 |
6 | ### 设置nodejs镜像
7 | ```text
8 | npm config set registry https://registry.npm.taobao.org
9 | ```
10 |
11 | ### npm install
12 | ```text
13 | npm install
14 | ```
15 |
16 | ### npm启动应用
17 | ```text
18 | npm start
19 | ```
20 |
21 | ### npm build
22 | > 打包后的目录:dist
23 | ```text
24 | npm run-script build
25 | ```
26 |
--------------------------------------------------------------------------------
/README.zh-CN.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geekidea/spring-boot-plus-vue/67ccf280067aedb564e76c6a2a99beeab31c3104/README.zh-CN.md
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/app'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/deploy-vue.sh:
--------------------------------------------------------------------------------
1 | #! /bin/shell
2 |
3 | # Copyright 2019-2029 geekidea(https://github.com/geekidea)
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | #======================================================================
18 | # 1. 下载或更新spring-boot-plus-vue版本库
19 | # 2. npm打包
20 | # 3. 将dist目录复制到Nginx静态目录中
21 | # 4. 备份dist目录
22 | # 5. 运行spring-boot-plus-vue
23 | # author: geekidea
24 | # date: 2020-03-06
25 | #======================================================================
26 |
27 | NOW=$(date --date='0 days ago' "+%Y-%m-%d-%H-%M-%S")
28 | echo "${NOW}"
29 |
30 | if [ ! -d "spring-boot-plus-vue" ]; then
31 | git clone https://gitee.com/geekidea/spring-boot-plus-vue.git
32 | fi
33 |
34 | cd spring-boot-plus-vue
35 | rm -rf package-lock.json
36 |
37 | git checkout master
38 | git branch
39 | git pull
40 |
41 | rm -rf dist
42 | npm install
43 | npx vue-cli-service build
44 |
45 | cd ..
46 | if [ ! -d "spring-boot-plus-vue-back" ]; then
47 | mkdir spring-boot-plus-vue-back
48 | fi
49 |
50 | cp -r -f spring-boot-plus-vue/dist spring-boot-plus-vue-back/spring-boot-plus-vue-back-"${NOW}"
51 | echo "back spring-boot-plus-vue success"
52 |
53 | rm -rf /home/www/vue
54 | nginx -s reload
55 | mv spring-boot-plus-vue/dist /home/www/vue
56 | nginx -s reload
57 | echo "Deploy spring-boot-plus-vue success"
58 |
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | #! /bin/shell
2 |
3 | # Copyright 2019-2029 geekidea(https://github.com/geekidea)
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | #======================================================================
18 | # 1. 下载nodejs源
19 | # 2. yum安装nodejs
20 | # 3. 设置nodejs镜像
21 | # 4. 安装最新稳定版本
22 | # 5. 查看版本
23 | # 6. 全局安装 vue-cli
24 | # author: geekidea
25 | # date: 2020-03-25
26 | #======================================================================
27 |
28 | curl --silent --location https://rpm.nodesource.com/setup_10.x | sudo bash
29 | yum -y install nodejs
30 | npm config set registry https://registry.npm.taobao.org
31 | npm install -g n stable
32 | node -v
33 | npm -v
34 | npm install --global vue-cli
35 |
36 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "./",
4 | "paths": {
5 | "@/*": ["src/*"]
6 | }
7 | },
8 | "exclude": ["node_modules", "dist"]
9 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "spring-boot-plus-vue",
3 | "version": "4.2.1",
4 | "description": "基于vue-element-admin开发的,对应spring-boot-plus接口的前端项目",
5 | "author": "geekidea@qq.com",
6 | "license": "Apache License",
7 | "scripts": {
8 | "dev": "vue-cli-service serve",
9 | "build:prod": "vue-cli-service build",
10 | "build:stage": "vue-cli-service build --mode staging",
11 | "preview": "node build/index.js --preview",
12 | "lint": "eslint --ext .js,.vue src",
13 | "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml",
14 | "new": "plop"
15 | },
16 | "husky": {
17 | "hooks": {
18 | "pre-commit": "lint-staged"
19 | }
20 | },
21 | "lint-staged": {
22 | "src/**/*.{js,vue}": [
23 | "eslint --fix",
24 | "git add"
25 | ]
26 | },
27 | "keywords": [
28 | "vue",
29 | "admin",
30 | "dashboard",
31 | "element-ui",
32 | "boilerplate",
33 | "admin-template",
34 | "management-system"
35 | ],
36 | "repository": {
37 | "type": "git",
38 | "url": "git+https://github.com/PanJiaChen/vue-element-admin.git"
39 | },
40 | "bugs": {
41 | "url": "https://github.com/PanJiaChen/vue-element-admin.git/issues"
42 | },
43 | "dependencies": {
44 | "axios": "0.18.1",
45 | "clipboard": "2.0.4",
46 | "codemirror": "5.45.0",
47 | "driver.js": "0.9.5",
48 | "dropzone": "5.5.1",
49 | "echarts": "4.2.1",
50 | "element-ui": "2.13.0",
51 | "file-saver": "2.0.1",
52 | "fuse.js": "3.4.4",
53 | "js-cookie": "2.2.0",
54 | "jsonlint": "1.6.3",
55 | "jszip": "3.2.1",
56 | "normalize.css": "7.0.0",
57 | "nprogress": "0.2.0",
58 | "path-to-regexp": "2.4.0",
59 | "screenfull": "4.2.0",
60 | "script-loader": "0.7.2",
61 | "showdown": "1.9.0",
62 | "sortablejs": "1.8.4",
63 | "tui-editor": "1.3.3",
64 | "vue": "2.6.10",
65 | "vue-count-to": "1.0.13",
66 | "vue-router": "3.0.2",
67 | "vue-splitpane": "1.0.4",
68 | "vuedraggable": "2.20.0",
69 | "vuex": "3.1.0",
70 | "xlsx": "0.14.1"
71 | },
72 | "devDependencies": {
73 | "@babel/core": "7.0.0",
74 | "@babel/register": "7.0.0",
75 | "@vue/cli-plugin-babel": "3.5.3",
76 | "@vue/cli-plugin-eslint": "^3.9.1",
77 | "@vue/cli-plugin-unit-jest": "3.5.3",
78 | "@vue/cli-service": "3.5.3",
79 | "autoprefixer": "^9.5.1",
80 | "babel-core": "7.0.0-bridge.0",
81 | "babel-eslint": "10.0.1",
82 | "babel-jest": "23.6.0",
83 | "chalk": "2.4.2",
84 | "chokidar": "2.1.5",
85 | "connect": "3.6.6",
86 | "eslint": "5.15.3",
87 | "eslint-plugin-vue": "5.2.2",
88 | "html-webpack-plugin": "3.2.0",
89 | "husky": "1.3.1",
90 | "lint-staged": "8.1.5",
91 | "node-sass": "^4.9.0",
92 | "plop": "2.3.0",
93 | "runjs": "^4.3.2",
94 | "sass-loader": "^7.1.0",
95 | "script-ext-html-webpack-plugin": "2.1.3",
96 | "serve-static": "^1.13.2",
97 | "svg-sprite-loader": "4.1.3",
98 | "svgo": "1.2.0",
99 | "vue-template-compiler": "2.6.10"
100 | },
101 | "engines": {
102 | "node": ">=8.9",
103 | "npm": ">= 3.0.0"
104 | },
105 | "browserslist": [
106 | "> 1%",
107 | "last 2 versions"
108 | ]
109 | }
110 |
--------------------------------------------------------------------------------
/plopfile.js:
--------------------------------------------------------------------------------
1 | const viewGenerator = require('./plop-templates/view/prompt')
2 | const componentGenerator = require('./plop-templates/component/prompt')
3 | const storeGenerator = require('./plop-templates/store/prompt.js')
4 |
5 | module.exports = function(plop) {
6 | plop.setGenerator('view', viewGenerator)
7 | plop.setGenerator('component', componentGenerator)
8 | plop.setGenerator('store', storeGenerator)
9 | }
10 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | autoprefixer: {}
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geekidea/spring-boot-plus-vue/67ccf280067aedb564e76c6a2a99beeab31c3104/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | <%= webpackConfig.name %>
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/src/api/system/sys-department-api.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | const sysDepartmentApi = {
4 |
5 | }
6 |
7 | /**
8 | * 获取系统部门列表
9 | * @param data
10 | * @returns {AxiosPromise}
11 | */
12 | sysDepartmentApi.getList = data => {
13 | return request({
14 | url: '/sysDepartment/getList',
15 | method: 'post',
16 | data
17 | })
18 | }
19 |
20 | /**
21 | * 获取系统部门列表
22 | * @param data
23 | * @returns {AxiosPromise}
24 | */
25 | sysDepartmentApi.getDepartmentTree = data => {
26 | return request({
27 | url: '/sysDepartment/getDepartmentTree',
28 | method: 'post',
29 | data
30 | })
31 | }
32 |
33 | export default sysDepartmentApi
34 |
35 |
--------------------------------------------------------------------------------
/src/api/system/sys-permission-api.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | const sysPermissionApi = {
4 |
5 | }
6 |
7 | /**
8 | * 获取系统所有树形菜单
9 | * @param data
10 | * @returns {AxiosPromise}
11 | */
12 | sysPermissionApi.getAllMenuTree = () => {
13 | return request({
14 | url: '/sysPermission/getAllMenuTree',
15 | method: 'post'
16 | })
17 | }
18 |
19 | /**
20 | * 获取系统部门列表
21 | * @param data
22 | * @returns {AxiosPromise}
23 | */
24 | sysPermissionApi.detail = id => {
25 | return request({
26 | url: '/sysPermission/info/' + id,
27 | method: 'get'
28 | })
29 | }
30 |
31 | /**
32 | * 根据角色id获取该对应的所有三级权限ID
33 | * @param data
34 | * @returns {AxiosPromise}
35 | */
36 | sysPermissionApi.getMenuIdsByRoleId = roleId => {
37 | return request({
38 | url: '/sysPermission/getThreeLevelPermissionIdsByRoleId/' + roleId,
39 | method: 'get'
40 | })
41 | }
42 |
43 | /**
44 | * 获取所有导航菜单(一级/二级菜单)
45 | * @param data
46 | * @returns {AxiosPromise}
47 | */
48 | sysPermissionApi.getNavMenuTree = () => {
49 | return request({
50 | url: '/sysPermission/getNavMenuTree',
51 | method: 'post'
52 | })
53 | }
54 |
55 | /**
56 | * 添加菜单
57 | * @param data
58 | * @returns {AxiosPromise}
59 | */
60 | sysPermissionApi.add = data => {
61 | return request({
62 | url: '/sysPermission/add',
63 | method: 'post',
64 | data
65 | })
66 | }
67 |
68 | /**
69 | * 修改菜单
70 | * @param data
71 | * @returns {AxiosPromise}
72 | */
73 | sysPermissionApi.update = data => {
74 | return request({
75 | url: '/sysPermission/update',
76 | method: 'post',
77 | data
78 | })
79 | }
80 |
81 | /**
82 | * 删除菜单
83 | * @param data
84 | * @returns {AxiosPromise}
85 | */
86 | sysPermissionApi.delete = id => {
87 | return request({
88 | url: '/sysPermission/delete/' + id,
89 | method: 'post'
90 | })
91 | }
92 |
93 | export default sysPermissionApi
94 |
95 |
--------------------------------------------------------------------------------
/src/api/system/sys-role-api.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | const sysRoleApi = {
4 |
5 | }
6 |
7 | /**
8 | * 获取系统部门列表
9 | * @param data
10 | * @returns {AxiosPromise}
11 | */
12 | sysRoleApi.getList = data => {
13 | return request({
14 | url: '/sysRole/getList',
15 | method: 'post',
16 | data
17 | })
18 | }
19 |
20 | /**
21 | * 获取系统部门列表
22 | * @param data
23 | * @returns {AxiosPromise}
24 | */
25 | sysRoleApi.getPageList = data => {
26 | return request({
27 | url: '/sysRole/getPageList',
28 | method: 'post',
29 | data
30 | })
31 | }
32 |
33 | /**
34 | * 获取系统部门列表
35 | * @param data
36 | * @returns {AxiosPromise}
37 | */
38 | sysRoleApi.detail = id => {
39 | return request({
40 | url: '/sysRole/info/' + id,
41 | method: 'get'
42 | })
43 | }
44 |
45 | /**
46 | * 添加角色
47 | * @param data
48 | * @returns {AxiosPromise}
49 | */
50 | sysRoleApi.add = data => {
51 | return request({
52 | url: '/sysRole/add',
53 | method: 'post',
54 | data
55 | })
56 | }
57 |
58 | /**
59 | * 修改角色
60 | * @param data
61 | * @returns {AxiosPromise}
62 | */
63 | sysRoleApi.update = data => {
64 | return request({
65 | url: '/sysRole/update',
66 | method: 'post',
67 | data
68 | })
69 | }
70 |
71 | /**
72 | * 删除角色
73 | * @param data
74 | * @returns {AxiosPromise}
75 | */
76 | sysRoleApi.delete = id => {
77 | return request({
78 | url: '/sysRole/delete/' + id,
79 | method: 'post'
80 | })
81 | }
82 |
83 |
84 | /**
85 | * 修改系统角色权限
86 | * @param data
87 | * @returns {AxiosPromise}
88 | */
89 | sysRoleApi.updateSysRolePermission = data => {
90 | return request({
91 | url: '/sysRole/updateSysRolePermission',
92 | method: 'post',
93 | data
94 | })
95 | }
96 |
97 | export default sysRoleApi
98 |
99 |
--------------------------------------------------------------------------------
/src/api/system/sys-user-api.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | const sysUserApi = {
4 |
5 | }
6 |
7 | /**
8 | * 获取系统用户分页列表
9 | * @param data
10 | * @returns {AxiosPromise}
11 | */
12 | sysUserApi.getPageList = data => {
13 | return request({
14 | url: '/sysUser/getPageList',
15 | method: 'post',
16 | data
17 | })
18 | }
19 |
20 | /**
21 | * 系统用户详情
22 | * @param data
23 | * @returns {AxiosPromise}
24 | */
25 | sysUserApi.detail = id => {
26 | return request({
27 | url: '/sysUser/info/' + id,
28 | method: 'get'
29 | })
30 | }
31 |
32 | /**
33 | * 新增系统用户
34 | * @param data
35 | * @returns {AxiosPromise}
36 | */
37 | sysUserApi.add = data => {
38 | return request({
39 | url: '/sysUser/add',
40 | method: 'post',
41 | data
42 | })
43 | }
44 |
45 | /**
46 | * 修改系统用户
47 | * @param data
48 | * @returns {AxiosPromise}
49 | */
50 | sysUserApi.update = data => {
51 | return request({
52 | url: '/sysUser/update',
53 | method: 'post',
54 | data
55 | })
56 | }
57 |
58 | /**
59 | * 修改系统用户
60 | * @param data
61 | * @returns {AxiosPromise}
62 | */
63 | sysUserApi.delete = id => {
64 | return request({
65 | url: '/sysUser/delete/' + id,
66 | method: 'post'
67 | })
68 | }
69 |
70 | /**
71 | * 上传用户头像
72 | * @param data
73 | * @returns {AxiosPromise}
74 | */
75 | sysUserApi.uploadHead = data => {
76 | return request({
77 | url: '/sysUser/uploadHead',
78 | method: 'post',
79 | data
80 | })
81 | }
82 |
83 | /**
84 | * 管理员重置用户密码
85 | * @param data
86 | * @returns {AxiosPromise}
87 | */
88 | sysUserApi.resetPassword = data => {
89 | return request({
90 | url: '/sysUser/resetPassword',
91 | method: 'post',
92 | data
93 | })
94 | }
95 |
96 | export default sysUserApi
97 |
--------------------------------------------------------------------------------
/src/api/system/upload-api.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | const uploadApi = {
4 |
5 | }
6 |
7 | /**
8 | * 上传文件
9 | * @param data
10 | * @returns {AxiosPromise}
11 | */
12 | uploadApi.upload = data => {
13 | return request({
14 | url: '/upload',
15 | method: 'post',
16 | data,
17 | headers: { 'Content-Type': 'multipart/form-data' }
18 | })
19 | }
20 |
21 | export default uploadApi
22 |
--------------------------------------------------------------------------------
/src/api/user.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function login(data) {
4 | return request({
5 | url: '/login',
6 | method: 'post',
7 | data
8 | })
9 | }
10 |
11 | export function getInfo(token) {
12 | return request({
13 | url: '/getSysUserInfo',
14 | method: 'get'
15 | })
16 | }
17 |
18 | export function logout() {
19 | return request({
20 | url: '/logout',
21 | method: 'post'
22 | })
23 | }
24 |
--------------------------------------------------------------------------------
/src/assets/401_images/401.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geekidea/spring-boot-plus-vue/67ccf280067aedb564e76c6a2a99beeab31c3104/src/assets/401_images/401.gif
--------------------------------------------------------------------------------
/src/assets/404_images/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geekidea/spring-boot-plus-vue/67ccf280067aedb564e76c6a2a99beeab31c3104/src/assets/404_images/404.png
--------------------------------------------------------------------------------
/src/assets/404_images/404_cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geekidea/spring-boot-plus-vue/67ccf280067aedb564e76c6a2a99beeab31c3104/src/assets/404_images/404_cloud.png
--------------------------------------------------------------------------------
/src/assets/custom-theme/fonts/element-icons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geekidea/spring-boot-plus-vue/67ccf280067aedb564e76c6a2a99beeab31c3104/src/assets/custom-theme/fonts/element-icons.ttf
--------------------------------------------------------------------------------
/src/assets/custom-theme/fonts/element-icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geekidea/spring-boot-plus-vue/67ccf280067aedb564e76c6a2a99beeab31c3104/src/assets/custom-theme/fonts/element-icons.woff
--------------------------------------------------------------------------------
/src/components/BackToTop/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
84 |
85 |
112 |
--------------------------------------------------------------------------------
/src/components/Breadcrumb/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ item.meta.title }}
6 | {{ item.meta.title }}
7 |
8 |
9 |
10 |
11 |
12 |
69 |
70 |
83 |
--------------------------------------------------------------------------------
/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 | beforeDestroy() {
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 |
62 |
--------------------------------------------------------------------------------
/src/components/ErrorLog/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Error Log
12 | Clear All
13 |
14 |
15 |
16 |
17 |
18 | Msg:
19 |
20 | {{ row.err.message }}
21 |
22 |
23 |
24 |
25 | Info:
26 |
27 | {{ row.vm.$vnode.tag }} error in {{ row.info }}
28 |
29 |
30 |
31 |
32 | Url:
33 |
34 | {{ row.url }}
35 |
36 |
37 |
38 |
39 |
40 |
41 | {{ scope.row.err.stack }}
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
70 |
71 |
79 |
--------------------------------------------------------------------------------
/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 |
73 |
--------------------------------------------------------------------------------
/src/components/Kanban/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ headerText }}
5 |
6 |
12 |
13 | {{ element.name }} {{ element.id }}
14 |
15 |
16 |
17 |
18 |
19 |
54 |
99 |
100 |
--------------------------------------------------------------------------------
/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/MarkdownEditor/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
119 |
--------------------------------------------------------------------------------
/src/components/Pagination/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
92 |
93 |
98 |
--------------------------------------------------------------------------------
/src/components/Screenfull/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
50 |
51 |
61 |
--------------------------------------------------------------------------------
/src/components/Share/DropdownMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
39 |
40 |
104 |
--------------------------------------------------------------------------------
/src/components/SizeSelect/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{
9 | item.label }}
10 |
11 |
12 |
13 |
14 |
15 |
58 |
--------------------------------------------------------------------------------
/src/components/Sticky/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
92 |
--------------------------------------------------------------------------------
/src/components/SvgIcon/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
47 |
48 |
63 |
--------------------------------------------------------------------------------
/src/components/TextHoverEffect/Mallki.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ text }}
4 |
5 |
6 |
7 |
8 |
9 |
23 |
24 |
114 |
--------------------------------------------------------------------------------
/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/components/Upload/SingleImage2.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
14 | Drag或点击上传
15 |
16 |
17 |
18 |
19 |
![]()
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
78 |
79 |
131 |
--------------------------------------------------------------------------------
/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/drag.js:
--------------------------------------------------------------------------------
1 | export default {
2 | bind(el, binding, vnode) {
3 | const dialogHeaderEl = el.querySelector('.el-dialog__header')
4 | const dragDom = el.querySelector('.el-dialog')
5 | dialogHeaderEl.style.cssText += ';cursor:move;'
6 | dragDom.style.cssText += ';top:0px;'
7 |
8 | // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
9 | const getStyle = (function() {
10 | if (window.document.currentStyle) {
11 | return (dom, attr) => dom.currentStyle[attr]
12 | } else {
13 | return (dom, attr) => getComputedStyle(dom, false)[attr]
14 | }
15 | })()
16 |
17 | dialogHeaderEl.onmousedown = (e) => {
18 | // 鼠标按下,计算当前元素距离可视区的距离
19 | const disX = e.clientX - dialogHeaderEl.offsetLeft
20 | const disY = e.clientY - dialogHeaderEl.offsetTop
21 |
22 | const dragDomWidth = dragDom.offsetWidth
23 | const dragDomHeight = dragDom.offsetHeight
24 |
25 | const screenWidth = document.body.clientWidth
26 | const screenHeight = document.body.clientHeight
27 |
28 | const minDragDomLeft = dragDom.offsetLeft
29 | const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth
30 |
31 | const minDragDomTop = dragDom.offsetTop
32 | const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomHeight
33 |
34 | // 获取到的值带px 正则匹配替换
35 | let styL = getStyle(dragDom, 'left')
36 | let styT = getStyle(dragDom, 'top')
37 |
38 | if (styL.includes('%')) {
39 | styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100)
40 | styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100)
41 | } else {
42 | styL = +styL.replace(/\px/g, '')
43 | styT = +styT.replace(/\px/g, '')
44 | }
45 |
46 | document.onmousemove = function(e) {
47 | // 通过事件委托,计算移动的距离
48 | let left = e.clientX - disX
49 | let top = e.clientY - disY
50 |
51 | // 边界处理
52 | if (-(left) > minDragDomLeft) {
53 | left = -minDragDomLeft
54 | } else if (left > maxDragDomLeft) {
55 | left = maxDragDomLeft
56 | }
57 |
58 | if (-(top) > minDragDomTop) {
59 | top = -minDragDomTop
60 | } else if (top > maxDragDomTop) {
61 | top = maxDragDomTop
62 | }
63 |
64 | // 移动当前元素
65 | dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`
66 |
67 | // emit onDrag event
68 | vnode.child.$emit('dragDialog')
69 | }
70 |
71 | document.onmouseup = function(e) {
72 | document.onmousemove = null
73 | document.onmouseup = null
74 | }
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/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 | export default {
4 | inserted(el, binding, vnode) {
5 | const { value } = binding
6 | const roles = store.getters && store.getters.roles
7 |
8 | if (value && value instanceof Array && 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 | } else {
19 | throw new Error(`need roles! Like v-permission="['admin','editor']"`)
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/directive/sticky.js:
--------------------------------------------------------------------------------
1 | const vueSticky = {}
2 | let listenAction
3 | vueSticky.install = Vue => {
4 | Vue.directive('sticky', {
5 | inserted(el, binding) {
6 | const params = binding.value || {}
7 | const stickyTop = params.stickyTop || 0
8 | const zIndex = params.zIndex || 1000
9 | const elStyle = el.style
10 |
11 | elStyle.position = '-webkit-sticky'
12 | elStyle.position = 'sticky'
13 | // if the browser support css sticky(Currently Safari, Firefox and Chrome Canary)
14 | // if (~elStyle.position.indexOf('sticky')) {
15 | // elStyle.top = `${stickyTop}px`;
16 | // elStyle.zIndex = zIndex;
17 | // return
18 | // }
19 | const elHeight = el.getBoundingClientRect().height
20 | const elWidth = el.getBoundingClientRect().width
21 | elStyle.cssText = `top: ${stickyTop}px; z-index: ${zIndex}`
22 |
23 | const parentElm = el.parentNode || document.documentElement
24 | const placeholder = document.createElement('div')
25 | placeholder.style.display = 'none'
26 | placeholder.style.width = `${elWidth}px`
27 | placeholder.style.height = `${elHeight}px`
28 | parentElm.insertBefore(placeholder, el)
29 |
30 | let active = false
31 |
32 | const getScroll = (target, top) => {
33 | const prop = top ? 'pageYOffset' : 'pageXOffset'
34 | const method = top ? 'scrollTop' : 'scrollLeft'
35 | let ret = target[prop]
36 | if (typeof ret !== 'number') {
37 | ret = window.document.documentElement[method]
38 | }
39 | return ret
40 | }
41 |
42 | const sticky = () => {
43 | if (active) {
44 | return
45 | }
46 | if (!elStyle.height) {
47 | elStyle.height = `${el.offsetHeight}px`
48 | }
49 |
50 | elStyle.position = 'fixed'
51 | elStyle.width = `${elWidth}px`
52 | placeholder.style.display = 'inline-block'
53 | active = true
54 | }
55 |
56 | const reset = () => {
57 | if (!active) {
58 | return
59 | }
60 |
61 | elStyle.position = ''
62 | placeholder.style.display = 'none'
63 | active = false
64 | }
65 |
66 | const check = () => {
67 | const scrollTop = getScroll(window, true)
68 | const offsetTop = el.getBoundingClientRect().top
69 | if (offsetTop < stickyTop) {
70 | sticky()
71 | } else {
72 | if (scrollTop < elHeight + stickyTop) {
73 | reset()
74 | }
75 | }
76 | }
77 | listenAction = () => {
78 | check()
79 | }
80 |
81 | window.addEventListener('scroll', listenAction)
82 | },
83 |
84 | unbind() {
85 | window.removeEventListener('scroll', listenAction)
86 | }
87 | })
88 | }
89 |
90 | export default vueSticky
91 |
92 |
--------------------------------------------------------------------------------
/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/directive/waves/waves.js:
--------------------------------------------------------------------------------
1 | import './waves.css'
2 |
3 | const context = '@@wavesContext'
4 |
5 | function handleClick(el, binding) {
6 | function handle(e) {
7 | const customOpts = Object.assign({}, binding.value)
8 | const opts = Object.assign({
9 | ele: el, // 波纹作用元素
10 | type: 'hit', // hit 点击位置扩散 center中心点扩展
11 | color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
12 | },
13 | customOpts
14 | )
15 | const target = opts.ele
16 | if (target) {
17 | target.style.position = 'relative'
18 | target.style.overflow = 'hidden'
19 | const rect = target.getBoundingClientRect()
20 | let ripple = target.querySelector('.waves-ripple')
21 | if (!ripple) {
22 | ripple = document.createElement('span')
23 | ripple.className = 'waves-ripple'
24 | ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'
25 | target.appendChild(ripple)
26 | } else {
27 | ripple.className = 'waves-ripple'
28 | }
29 | switch (opts.type) {
30 | case 'center':
31 | ripple.style.top = rect.height / 2 - ripple.offsetHeight / 2 + 'px'
32 | ripple.style.left = rect.width / 2 - ripple.offsetWidth / 2 + 'px'
33 | break
34 | default:
35 | ripple.style.top =
36 | (e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop ||
37 | document.body.scrollTop) + 'px'
38 | ripple.style.left =
39 | (e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft ||
40 | document.body.scrollLeft) + 'px'
41 | }
42 | ripple.style.backgroundColor = opts.color
43 | ripple.className = 'waves-ripple z-active'
44 | return false
45 | }
46 | }
47 |
48 | if (!el[context]) {
49 | el[context] = {
50 | removeHandle: handle
51 | }
52 | } else {
53 | el[context].removeHandle = handle
54 | }
55 |
56 | return handle
57 | }
58 |
59 | export default {
60 | bind(el, binding) {
61 | el.addEventListener('click', handleClick(el, binding), false)
62 | },
63 | update(el, binding) {
64 | el.removeEventListener('click', el[context].removeHandle, false)
65 | el.addEventListener('click', handleClick(el, binding), false)
66 | },
67 | unbind(el) {
68 | el.removeEventListener('click', el[context].removeHandle, false)
69 | el[context] = null
70 | delete el[context]
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/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 | import Vue from 'vue'
2 | import SvgIcon from '@/components/SvgIcon'// svg component
3 |
4 | // register globally
5 | Vue.component('svg-icon', SvgIcon)
6 |
7 | const req = require.context('./svg', false, /\.svg$/)
8 | const requireAll = requireContext => requireContext.keys().map(requireContext)
9 | requireAll(req)
10 |
--------------------------------------------------------------------------------
/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/form.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/layout/components/AppMain.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
24 |
25 |
49 |
50 |
58 |
--------------------------------------------------------------------------------
/src/layout/components/Settings/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Page style setting
5 |
6 |
7 | Theme Color
8 |
9 |
10 |
11 |
12 | Open Tags-View
13 |
14 |
15 |
16 |
17 | Fixed Header
18 |
19 |
20 |
21 |
22 | Sidebar Logo
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
83 |
84 |
109 |
--------------------------------------------------------------------------------
/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 |
30 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/Link.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
37 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/Logo.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
33 |
34 |
83 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/SidebarItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
23 |
24 |
25 |
26 |
27 |
96 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
55 |
--------------------------------------------------------------------------------
/src/layout/components/TagsView/ScrollPane.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
69 |
70 |
86 |
--------------------------------------------------------------------------------
/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/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
59 |
60 |
103 |
--------------------------------------------------------------------------------
/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 | beforeDestroy() {
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 Vue from 'vue'
2 |
3 | import Cookies from 'js-cookie'
4 |
5 | import 'normalize.css/normalize.css' // a modern alternative to CSS resets
6 |
7 | import Element from 'element-ui'
8 | import './styles/element-variables.scss'
9 |
10 | import '@/styles/index.scss' // global css
11 |
12 | import App from './App'
13 | import store from './store'
14 | import router from './router'
15 |
16 | import './icons' // icon
17 | import './permission' // permission control
18 | import './utils/error-log' // error log
19 |
20 | import * as filters from './filters' // global filters
21 |
22 | /**
23 | * If you don't want to use mock-server
24 | * you want to use MockJs for mock api
25 | * you can execute: mockXHR()
26 | *
27 | * Currently MockJs will be used in the production environment,
28 | * please remove it before going online ! ! !
29 | */
30 | // if (process.env.NODE_ENV === 'production') {
31 | //
32 | // }
33 |
34 | Vue.use(Element, {
35 | size: Cookies.get('size') || 'medium' // set element-ui default size
36 | })
37 |
38 | // register global utility filters
39 | Object.keys(filters).forEach(key => {
40 | Vue.filter(key, filters[key])
41 | })
42 |
43 | Vue.config.productionTip = false
44 |
45 | new Vue({
46 | el: '#app',
47 | router,
48 | store,
49 | render: h => h(App)
50 | })
51 |
--------------------------------------------------------------------------------
/src/permission.js:
--------------------------------------------------------------------------------
1 | import router from './router'
2 | import store from './store'
3 | import { Message } from 'element-ui'
4 | import NProgress from 'nprogress' // progress bar
5 | import 'nprogress/nprogress.css' // progress bar style
6 | import { getToken } from '@/utils/auth' // get token from cookie
7 | import getPageTitle from '@/utils/get-page-title'
8 |
9 | NProgress.configure({ showSpinner: false }) // NProgress Configuration
10 |
11 | const whiteList = ['/login', '/auth-redirect'] // no redirect whitelist
12 |
13 | router.beforeEach(async(to, from, next) => {
14 | // start progress bar
15 | NProgress.start()
16 |
17 | // set page title
18 | document.title = getPageTitle(to.meta.title)
19 |
20 | // determine whether the user has logged in
21 | const hasToken = getToken()
22 |
23 | if (hasToken) {
24 | if (to.path === '/login') {
25 | // if is logged in, redirect to the home page
26 | next({ path: '/' })
27 | NProgress.done()
28 | } else {
29 | // determine whether the user has obtained his permission roles through getInfo
30 | const hasRoles = store.getters.roles && store.getters.roles.length > 0
31 | if (hasRoles) {
32 | next()
33 | } else {
34 | try {
35 | // get user info
36 | // note: roles must be a object array! such as: ['admin'] or ,['developer','editor']
37 | const { roles } = await store.dispatch('user/getInfo')
38 |
39 | // generate accessible routes map based on roles
40 | const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
41 |
42 | // dynamically add accessible routes
43 | router.addRoutes(accessRoutes)
44 |
45 | // hack method to ensure that addRoutes is complete
46 | // set the replace: true, so the navigation will not leave a history record
47 | next({ ...to, replace: true })
48 | } catch (error) {
49 | // remove token and go to login page to re-login
50 | await store.dispatch('user/resetToken')
51 | Message.error(error || 'Has Error')
52 | next(`/login?redirect=${to.path}`)
53 | NProgress.done()
54 | }
55 | }
56 | }
57 | } else {
58 | /* has no token*/
59 |
60 | if (whiteList.indexOf(to.path) !== -1) {
61 | // in the free login whitelist, go directly
62 | next()
63 | } else {
64 | // other pages that do not have permission to access are redirected to the login page.
65 | next(`/login?redirect=${to.path}`)
66 | NProgress.done()
67 | }
68 | }
69 | })
70 |
71 | router.afterEach(() => {
72 | // finish progress bar
73 | NProgress.done()
74 | })
75 |
--------------------------------------------------------------------------------
/src/settings.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | title: 'Spring Boot Plus Vue',
3 |
4 | /**
5 | * @type {boolean} true | false
6 | * @description Whether show the settings right-panel
7 | */
8 | showSettings: false,
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: true,
21 |
22 | /**
23 | * @type {boolean} true | false
24 | * @description Whether show the logo in sidebar
25 | */
26 | sidebarLogo: true,
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 Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import getters from './getters'
4 |
5 | Vue.use(Vuex)
6 |
7 | // https://webpack.js.org/guides/dependency-management/#requirecontext
8 | const modulesFiles = require.context('./modules', true, /\.js$/)
9 |
10 | // you do not need `import app from './modules/app'`
11 | // it will auto require all vuex module from modules file
12 | const modules = modulesFiles.keys().reduce((modules, modulePath) => {
13 | // set './app.js' => 'app'
14 | const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
15 | const value = modulesFiles(modulePath)
16 | modules[moduleName] = value.default
17 | return modules
18 | }, {})
19 |
20 | const store = new Vuex.Store({
21 | modules,
22 | getters
23 | })
24 |
25 | export default store
26 |
--------------------------------------------------------------------------------
/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 | if (state.hasOwnProperty(key)) {
17 | state[key] = value
18 | }
19 | }
20 | }
21 |
22 | const actions = {
23 | changeSetting({ commit }, data) {
24 | commit('CHANGE_SETTING', data)
25 | }
26 | }
27 |
28 | export default {
29 | namespaced: true,
30 | state,
31 | mutations,
32 | actions
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/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 | border: 1px solid red;
29 | }
30 | }
31 |
32 | .fixed-width {
33 | .el-button--mini {
34 | padding: 7px 10px;
35 | width: 60px;
36 | }
37 | }
38 |
39 | .status-col {
40 | .cell {
41 | padding: 0 10px;
42 | text-align: center;
43 |
44 | .el-tag {
45 | margin-right: 0px;
46 | }
47 | }
48 | }
49 |
50 | // to fixed https://github.com/ElemeFE/element/issues/2461
51 | .el-dialog {
52 | transform: none;
53 | left: 0;
54 | position: relative;
55 | margin: 0 auto;
56 | }
57 |
58 | // refine element ui upload
59 | .upload-container {
60 | .el-upload {
61 | width: 100%;
62 |
63 | .el-upload-dragger {
64 | width: 100%;
65 | height: 200px;
66 | }
67 | }
68 | }
69 |
70 | // dropdown
71 | .el-dropdown-menu {
72 | a {
73 | display: block
74 | }
75 | }
76 |
77 | // fix date-picker ui bug in filter-item
78 | .el-range-editor.el-input__inner {
79 | display: inline-flex !important;
80 | }
81 |
82 | // to fix el-date-picker css style
83 | .el-range-separator {
84 | box-sizing: content-box;
85 | }
86 |
87 |
88 | .el-table .el-table__header .cell{
89 | color: #333333;
90 | font-weight: 500;
91 | }
92 |
93 | .el-table .el-table__body .operation{
94 | .cell{
95 | .el-link{
96 | margin: 0px 2px !important;
97 | font-size: 13px;
98 | font-weight: normal;
99 | }
100 | }
101 | }
102 |
103 | .el-input__inner{
104 | height: 36px;
105 | }
106 |
107 | .el-button{
108 | padding: 10px 20px;
109 | }
110 |
111 | .el-table .cell .el-tag{
112 | height: 28px;
113 | line-height: 28px;
114 | }
115 |
--------------------------------------------------------------------------------
/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-ui/lib/theme-chalk/fonts';
24 |
25 | @import "~element-ui/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:#1A1C29;
17 | $menuHover:#12121C;
18 |
19 | $subMenuBg:#1A1C29;
20 | $subMenuHover:#12121C;
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 Vue from 'vue'
2 | import Clipboard from 'clipboard'
3 |
4 | function clipboardSuccess() {
5 | Vue.prototype.$message({
6 | message: 'Copy successfully',
7 | type: 'success',
8 | duration: 1500
9 | })
10 | }
11 |
12 | function clipboardError() {
13 | Vue.prototype.$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 Vue from 'vue'
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 | Vue.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 | Vue.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 || 'Spring Boot Plus Vue'
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 |
17 | if (!hasPermission) {
18 | return false
19 | }
20 | return true
21 | } else {
22 | console.error(`need roles! Like v-permission="['admin','editor']"`)
23 | return false
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/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/dashboard/admin/components/BarChart.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
103 |
--------------------------------------------------------------------------------
/src/views/dashboard/admin/components/BoxCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 | Vue
11 |
12 |
13 |
14 | JavaScript
15 |
16 |
17 |
18 | Css
19 |
20 |
21 |
22 | ESLint
23 |
24 |
25 |
26 |
27 |
28 |
29 |
63 |
64 |
71 |
119 |
--------------------------------------------------------------------------------
/src/views/dashboard/admin/components/PieChart.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
80 |
--------------------------------------------------------------------------------
/src/views/dashboard/admin/components/RaddarChart.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
117 |
--------------------------------------------------------------------------------
/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 | {{ scope.row.order_no | orderNoFilter }}
6 |
7 |
8 |
9 |
10 | ¥{{ scope.row.price | toThousandFilter }}
11 |
12 |
13 |
14 |
15 |
16 | {{ row.status }}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
53 |
--------------------------------------------------------------------------------
/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 | beforeDestroy() {
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/documentation/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
20 |
21 |
43 |
--------------------------------------------------------------------------------
/src/views/error-log/components/ErrorTestA.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ a.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/error-page/401.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 返回
5 |
6 |
7 |
8 |
9 | Oops!
10 |
11 | gif来源airbnb 页面
12 | 你没有权限去该页面
13 | 如有不满请联系你领导
14 |
15 | - 或者你可以去:
16 | -
17 |
18 | 回首页
19 |
20 |
21 | -
22 | 随便看看
23 |
24 | - 点我看图
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
60 |
61 |
100 |
--------------------------------------------------------------------------------
/src/views/icons/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | {{ generateIconCode(item) }}
14 |
15 |
16 |
17 | {{ item }}
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | {{ generateElementIconCode(item) }}
29 |
30 |
31 |
32 | {{ item }}
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
68 |
69 |
102 |
--------------------------------------------------------------------------------
/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/login/components/SocialSignin.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | WeChat
6 |
7 |
8 |
9 | QQ
10 |
11 |
12 |
13 |
14 |
39 |
40 |
73 |
--------------------------------------------------------------------------------
/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/redirect/index.vue:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------