├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── .travis.yml ├── LICENSE ├── README.md ├── cypress.json ├── package.json ├── public ├── ico.png └── index.html ├── src ├── App.vue ├── assets │ ├── icons │ │ ├── iconfont.css │ │ ├── iconfont.eot │ │ ├── iconfont.svg │ │ ├── iconfont.ttf │ │ └── iconfont.woff │ ├── images │ │ ├── error-page │ │ │ ├── error-401.svg │ │ │ ├── error-404.svg │ │ │ └── error-500.svg │ │ ├── ico.png │ │ ├── login-bg.jpg │ │ ├── logo-header.png │ │ ├── logo-min.jpg │ │ ├── logo.jpg │ │ ├── logo.png │ │ ├── qq-fance.jpg │ │ └── talkingdata.png │ └── styles │ │ └── common.less ├── components │ ├── common-icon │ │ ├── common-icon.vue │ │ └── index.js │ ├── common │ │ ├── common.less │ │ └── util.js │ └── icons │ │ ├── icons.vue │ │ └── index.js ├── config │ └── index.js ├── directive │ ├── directives.js │ └── index.js ├── index.less ├── libs │ ├── axios-cfg.js │ ├── error │ │ └── ResError.js │ ├── iview-cfg.js │ ├── tools.js │ └── util.js ├── locale │ ├── index.js │ └── lang │ │ ├── en-US.js │ │ ├── zh-CN.js │ │ └── zh-TW.js ├── main.js ├── mock │ ├── data.js │ ├── index.js │ └── login.js ├── router │ ├── index.js │ └── routers.js ├── store │ ├── index.js │ └── module │ │ ├── app.js │ │ └── user.js └── view │ ├── error-page │ ├── 401.vue │ ├── 404.vue │ ├── 500.vue │ ├── back-btn-group.vue │ ├── error-content.vue │ └── error.less │ ├── home │ └── index.vue │ ├── login │ ├── components │ │ ├── page-header.vue │ │ └── service.vue │ └── index.vue │ ├── main │ ├── components │ │ ├── fullscreen │ │ │ ├── fullscreen.vue │ │ │ └── index.js │ │ ├── header-bar │ │ │ ├── custom-bread-crumb │ │ │ │ ├── custom-bread-crumb.less │ │ │ │ ├── custom-bread-crumb.vue │ │ │ │ └── index.js │ │ │ ├── header-bar.less │ │ │ ├── header-bar.vue │ │ │ ├── index.js │ │ │ └── sider-trigger │ │ │ │ ├── index.js │ │ │ │ ├── sider-trigger.less │ │ │ │ └── sider-trigger.vue │ │ ├── language │ │ │ ├── index.js │ │ │ └── language.vue │ │ ├── side-menu │ │ │ ├── collapsed-menu.vue │ │ │ ├── index.js │ │ │ ├── item-mixin.js │ │ │ ├── mixin.js │ │ │ ├── side-menu-item.vue │ │ │ ├── side-menu.less │ │ │ └── side-menu.vue │ │ ├── tags-nav │ │ │ ├── index.js │ │ │ ├── tags-nav.less │ │ │ └── tags-nav.vue │ │ └── user │ │ │ ├── index.js │ │ │ ├── user.less │ │ │ └── user.vue │ ├── index.js │ ├── main.less │ └── main.vue │ ├── single-page │ └── home │ │ ├── example.vue │ │ ├── home.vue │ │ └── index.js │ └── system │ ├── log │ └── index.vue │ ├── resource │ └── index.vue │ ├── role │ ├── components │ │ ├── add.vue │ │ └── update.vue │ └── index.vue │ └── user │ ├── components │ ├── add.vue │ ├── reset-password.vue │ └── update.vue │ └── index.vue ├── vue.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@vue/app" 4 | ], 5 | "plugins": [ 6 | "@babel/plugin-syntax-dynamic-import" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = false 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | routers.js 2 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: false, 3 | 'extends': [ 4 | 'plugin:vue/essential', 5 | '@vue/standard' 6 | ], 7 | rules: { 8 | // allow async-await 9 | 'generator-star-spacing': 'off', 10 | // allow debugger during development 11 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 12 | 'vue/no-parsing-error': [2, { 'x-invalid-end-tag': false }], 13 | 'no-undef': 'off' 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | /tests/e2e/videos/ 6 | /tests/e2e/screenshots/ 7 | 8 | # local env files 9 | .env.local 10 | .env.*.local 11 | 12 | # Log files 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | 17 | # Editor directories and files 18 | .idea 19 | .vscode 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw* 25 | 26 | build/env.js 27 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: stable 3 | script: npm run lint 4 | notifications: 5 | email: false 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 iView 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | `watchDog-framework-web`是[watchDog-framework](https://github.com/Licoy/watchDog-framework)的前端项目工程,本项目基于Vue + iView-Admin + Vue-Router + Vuex + Axios开发。 3 | # 使用 4 | > 使用前请先安装[NodeJs](https://nodejs.org/zh-cn/)(推荐使用8.x版本),国内用户推荐使用[淘宝NPM镜像源](http://npm.taobao.org/)。 5 | - 克隆到本地 6 | ```git 7 | git clone git@github.com:watchdog-framework/watchdog-framework-web.git 8 | ``` 9 | - 安装依赖 10 | ```shell 11 | npm install 12 | ``` 13 | - 运行 14 | ```shell 15 | npm run dev 16 | ``` 17 | - 打包 18 | ```shell 19 | npm run build 20 | ``` 21 | # 配置修改 22 | - 请求地址 23 | 24 | 将`config/index.js`中`baseUrl`属性中的`dev(开发时)`以及`pro(上线时)`改成自己的baseUrl即可。 25 | - 端口修改 26 | 27 | 更改根目录下的`package.json`文件中的`scripts.dev`的`--port`的值为你所需要的端口即可,例如使用2000端口即为:`"dev": "vue-cli-service serve --open --port=2000"` 28 | # 声明 29 | 若要将此项目用于商业用途请自行更换资源内的不可商用或商用需要授权的文件,例如LOGO、背景图等,若因未更换造成的损失均由使用者自行承担。 30 | 31 | # 版本支持 32 | - 0.2.x 33 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginsFile": "tests/e2e/plugins/index.js" 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "watch-dog", 3 | "version": "0.2.0", 4 | "description": "WatchDog", 5 | "author": "Licoy ", 6 | "license": "MIT", 7 | "private": false, 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/Licoy/watchDog-framework" 11 | }, 12 | "scripts": { 13 | "dev": "vue-cli-service serve --open --port=1003", 14 | "build": "vue-cli-service build", 15 | "lint": "vue-cli-service lint", 16 | "test:unit": "vue-cli-service test:unit", 17 | "test:e2e": "vue-cli-service test:e2e" 18 | }, 19 | "dependencies": { 20 | "axios": "^0.18.1", 21 | "dayjs": "^1.7.5", 22 | "iview": "^3.0.0", 23 | "js-cookie": "^2.2.0", 24 | "js-md5": "^0.7.3", 25 | "string-format": "^2.0.0", 26 | "vue": "^2.5.10", 27 | "vue-i18n": "^7.8.0", 28 | "vue-router": "^3.0.1", 29 | "vue-table-with-tree-grid": "^0.2.4", 30 | "vuedraggable": "^2.16.0", 31 | "vuex": "^3.0.1", 32 | "xlsx": "^0.13.3" 33 | }, 34 | "devDependencies": { 35 | "@babel/plugin-syntax-dynamic-import": "^7.0.0-rc.1", 36 | "@vue/cli-plugin-babel": "^3.0.1", 37 | "@vue/cli-plugin-eslint": "^3.0.1", 38 | "@vue/cli-plugin-unit-mocha": "^3.0.1", 39 | "@vue/cli-service": "^3.0.1", 40 | "@vue/eslint-config-standard": "^3.0.0-beta.10", 41 | "@vue/test-utils": "^1.0.0-beta.10", 42 | "chai": "^4.1.2", 43 | "eslint-plugin-cypress": "^2.0.1", 44 | "less": "^2.7.3", 45 | "less-loader": "^4.0.5", 46 | "lint-staged": "^6.0.0", 47 | "mockjs": "^1.0.1-beta3", 48 | "vue-template-compiler": "^2.5.13" 49 | }, 50 | "browserslist": [ 51 | "> 1%", 52 | "last 2 versions", 53 | "not ie <= 8" 54 | ], 55 | "lint-staged": { 56 | "*.js": [ 57 | "vue-cli-service lint", 58 | "git add" 59 | ], 60 | "*.vue": [ 61 | "vue-cli-service lint", 62 | "git add" 63 | ] 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /public/ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Licoy/watchdog-framework-web/dcf98dccaab16458727e9080ca02e2bb89f5fd36/public/ico.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | WatchDog 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 28 | -------------------------------------------------------------------------------- /src/assets/icons/iconfont.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face {font-family: "iconfont"; 3 | src: url('iconfont.eot?t=1530874958372'); /* IE9*/ 4 | src: url('iconfont.eot?t=1530874958372#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAjEAAsAAAAADmwAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFZW8UnVY21hcAAAAYAAAACeAAACID1NtZpnbHlmAAACIAAABFoAAAdYtnrc/mhlYWQAAAZ8AAAAMQAAADYR6R6WaGhlYQAABrAAAAAgAAAAJAfdA4tobXR4AAAG0AAAABkAAAAoJ+n//2xvY2EAAAbsAAAAFgAAABYKcgh8bWF4cAAABwQAAAAdAAAAIAEbAG5uYW1lAAAHJAAAAUUAAAJtPlT+fXBvc3QAAAhsAAAAVwAAAG1+6rtfeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2Bk/sc4gYGVgYOpk+kMAwNDP4RmfM1gxMjBwMDEwMrMgBUEpLmmMDgwVDx/ytzwv4EhhrmBoRkozAiSAwA2fA1beJzFkTsOwjAQRJ/JB4woKDgGp6NKgaJIuQcVN8lNIsV7jDDOpiFKDWM9Sx7Lu9YsUAGFuIsSwptA1ktuWPyC8+KXPHS+cZVT0iRStNY6622w0aZ51u2+u1VQle3K7kF1K2qO6nAiyqp3Xv9I4X+tv3VZ9ud6Uio0K/piwlF2St7Jc7TWyfO1zlGyWO8oY2xw8vxtdJQ7NjnED8roN6wAAHictVTNbxtFFJ+3u7O7cex1vZ/xx/rbu27cbJzdtdeqg5OapkmBVglJJBwSpAhVtSrBCdEeoDISqBEgvnpBSIWqICGVP6FVKUgoN5RDJaSAVKFy48KRQ+wwayeQFMEhiN3Z997szDz9fu83MwgjtPszfZceQRIqogl0Gs0jBGwJsgKlQ8Z0LaoESgYrmizQZs7McLmsRT8BWpaVVbvqGhrLsWEQIAlOxq6aFmVCxW1QdbBVHSAajy2KhYRIfwCBETP5Vu8p6hYoqVwi3BjrnT0xJdtpib8cFMWoKL7LsxjzFMWEBXhJU4fwUIDtfYHDMeVu6jiVgmDUjD3TCqXj4vqG+7Je0IYAOh2Q4mnhy6lILELaazFVEqPcsRA/Egvl8jJc/mV4RArqxiNEHry7u/spg+gXUQFV0Sx6Dq0TrjKnKrJAZS2q4nqGj92xVa0BfePYSQrsBkXKQAoi/1sH26rMZg23yiDLmn/nzoM7by9YVmJ8Ol+a06wL7QvWyGypMDWe2Pk+EBE4TohIYojnQyLUK2tnisUza5cGDi4GxBDHhUQpIvC8EOn9UKzV5ms16qtZp9Vev311bu7q7fV2y5k1mk6ikIpIUiRVSDjNXl0plL1yXlHyxBWU3oP42OTM5Fh8z8GJw+NpWZckXUYU2QMdUpcOGkaKrz7iVKRVkWcg2uFMJ5LztByFtnsPMYbM9jZkMO49/GS605n2P7pDetsHRrszRJZOp+PXHEju1+mfSO4mQhKrcgJwrKqpVU31X9bvNKCiOp5me5rqNcC0wGyAV92Pq4ZvSSMrBbhOM0A3FAkSeomhsVKkRhW5vvbqIs+EglSgXnn2TV0MO5COBtiJeFCWNA8oCoYUoL/98KNvaDgGv/K6EVpwneeVYIsZDtbGcCyVX2CYCQbIk0kwkbl0MgX5WhqvBPnz556uB5NFHOWYG1sMs3UjwPi8OMLrPn2fnibxEAqQ05NCNXSOEM4lwT8XWRb/GdEEehIIT88CiVDO+nQM1/Np2hoZUWWOLZBJsuZUvf7WU0lUsQDmyxAvxkmD9/ejN+51Me7e69uzbnPm45knLyZ0PdH+K+x912xhvswBUzlfYYAr87gFP8r+6rg8cLCwn4PYbsPRD2cYhF9Ty6c4ANawbYMF4E4t/zP30/+Bu5lhOUXVMlWvYhyR8ue9LYKUA0IajkL1M1IlP0GZ+x+0NXMs2btZwzRIpAymu0cleljWJlH6SNIeVJXyde7fA7/TGzRP7scJn21fFIezIDe44sh1V3EN0/P/EqlcAwZnlRASgL60gwHwzU2jVT7ZXiyVFtuvEJfUNwHXMER6mZUrFHVlpW9pvv9zU0/uTSPuZLllbN70c+yEIt3J/anEEh18bL/R12kJHUcVgs00wv2PTcHf8WnqFFTHiQCH8L03yrKrSywuMczyxuMgry2x7CjDLK0y3d4BnNQjMnttmWHI2qVrj6Pd2BtYZbsvHIb7B37maBAAAHicY2BkYGAA4sCb7Pfj+W2+MnCzMIDA9dRcPxj9////WhYG5gYgl4OBCSQKAED2C7oAAAB4nGNgZGBgbvjfwBDDwvD/PwMDCwMDUAQFcAEAdeEEcXicY2FgYGB+ycDAwvD/PwsDiMaOAV4UAw8AAAAAAAAAAHYBAgEwAZwCGgKKAwYDVAOsAAB4nGNgZGBg4GJIYmBnAAEmMI8LSP4H8xkAE4sBigAAAHicZY9NTsMwEIVf+gekEqqoYIfkBWIBKP0Rq25YVGr3XXTfpk6bKokjx63UA3AejsAJOALcgDvwSCebNpbH37x5Y08A3OAHHo7fLfeRPVwyO3INF7gXrlN/EG6QX4SbaONVuEX9TdjHM6bCbXRheYPXuGL2hHdhDx18CNdwjU/hOvUv4Qb5W7iJO/wKt9Dx6sI+5l5XuI1HL/bHVi+cXqnlQcWhySKTOb+CmV7vkoWt0uqca1vEJlODoF9JU51pW91T7NdD5yIVWZOqCas6SYzKrdnq0AUb5/JRrxeJHoQm5Vhj/rbGAo5xBYUlDowxQhhkiMro6DtVZvSvsUPCXntWPc3ndFsU1P9zhQEC9M9cU7qy0nk6T4E9XxtSdXQrbsuelDSRXs1JErJCXta2VELqATZlV44RelzRiT8oZ0j/AAlabsgAAAB4nG3ESQqAMBAEwOksLvHgS3xUlBEDWUgiKr5ewat1KBL0MfTPQEBCQaNBiw49DAbCpWa2ZSxc3c3TwWV3i/UiZ72WdEYZeNM1OM8y2KjP9E70ABXpEngA') format('woff'), 6 | url('iconfont.ttf?t=1530874958372') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 7 | url('iconfont.svg?t=1530874958372#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family:"iconfont" !important; 12 | font-size:16px; 13 | font-style:normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-bear:before { content: "\e600"; } 19 | 20 | .icon-resize-vertical:before { content: "\e7c3"; } 21 | 22 | .icon-qq:before { content: "\e609"; } 23 | 24 | .icon-frown:before { content: "\e77e"; } 25 | 26 | .icon-meh:before { content: "\e780"; } 27 | 28 | .icon-smile:before { content: "\e783"; } 29 | 30 | .icon-man:before { content: "\e7e2"; } 31 | 32 | .icon-woman:before { content: "\e7e5"; } 33 | 34 | -------------------------------------------------------------------------------- /src/assets/icons/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Licoy/watchdog-framework-web/dcf98dccaab16458727e9080ca02e2bb89f5fd36/src/assets/icons/iconfont.eot -------------------------------------------------------------------------------- /src/assets/icons/iconfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by iconfont 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/assets/icons/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Licoy/watchdog-framework-web/dcf98dccaab16458727e9080ca02e2bb89f5fd36/src/assets/icons/iconfont.ttf -------------------------------------------------------------------------------- /src/assets/icons/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Licoy/watchdog-framework-web/dcf98dccaab16458727e9080ca02e2bb89f5fd36/src/assets/icons/iconfont.woff -------------------------------------------------------------------------------- /src/assets/images/error-page/error-404.svg: -------------------------------------------------------------------------------- 1 | drone_delivery -------------------------------------------------------------------------------- /src/assets/images/ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Licoy/watchdog-framework-web/dcf98dccaab16458727e9080ca02e2bb89f5fd36/src/assets/images/ico.png -------------------------------------------------------------------------------- /src/assets/images/login-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Licoy/watchdog-framework-web/dcf98dccaab16458727e9080ca02e2bb89f5fd36/src/assets/images/login-bg.jpg -------------------------------------------------------------------------------- /src/assets/images/logo-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Licoy/watchdog-framework-web/dcf98dccaab16458727e9080ca02e2bb89f5fd36/src/assets/images/logo-header.png -------------------------------------------------------------------------------- /src/assets/images/logo-min.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Licoy/watchdog-framework-web/dcf98dccaab16458727e9080ca02e2bb89f5fd36/src/assets/images/logo-min.jpg -------------------------------------------------------------------------------- /src/assets/images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Licoy/watchdog-framework-web/dcf98dccaab16458727e9080ca02e2bb89f5fd36/src/assets/images/logo.jpg -------------------------------------------------------------------------------- /src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Licoy/watchdog-framework-web/dcf98dccaab16458727e9080ca02e2bb89f5fd36/src/assets/images/logo.png -------------------------------------------------------------------------------- /src/assets/images/qq-fance.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Licoy/watchdog-framework-web/dcf98dccaab16458727e9080ca02e2bb89f5fd36/src/assets/images/qq-fance.jpg -------------------------------------------------------------------------------- /src/assets/images/talkingdata.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Licoy/watchdog-framework-web/dcf98dccaab16458727e9080ca02e2bb89f5fd36/src/assets/images/talkingdata.png -------------------------------------------------------------------------------- /src/assets/styles/common.less: -------------------------------------------------------------------------------- 1 | .margin-top-8{ 2 | margin-top: 8px; 3 | } 4 | .margin-top-10{ 5 | margin-top: 10px; 6 | } 7 | .margin-top-20{ 8 | margin-top: 20px; 9 | } 10 | .margin-left-10{ 11 | margin-left: 10px; 12 | } 13 | .margin-bottom-10{ 14 | margin-bottom: 10px; 15 | } 16 | .margin-bottom-100{ 17 | margin-bottom: 100px; 18 | } 19 | .margin-right-10{ 20 | margin-right: 10px; 21 | } 22 | .padding-left-6{ 23 | padding-left: 6px; 24 | } 25 | .padding-left-8{ 26 | padding-left: 5px; 27 | } 28 | .padding-left-10{ 29 | padding-left: 10px; 30 | } 31 | .padding-left-20{ 32 | padding-left: 20px; 33 | } 34 | .height-100{ 35 | height: 100%; 36 | } 37 | .height-120px{ 38 | height: 100px; 39 | } 40 | .height-200px{ 41 | height: 200px; 42 | } 43 | .height-492px{ 44 | height: 492px; 45 | } 46 | .height-460px{ 47 | height: 460px; 48 | } 49 | .line-gray{ 50 | height: 0; 51 | border-bottom: 2px solid #dcdcdc; 52 | } 53 | .notwrap{ 54 | word-break:keep-all; 55 | white-space:nowrap; 56 | overflow: hidden; 57 | text-overflow: ellipsis; 58 | } 59 | .padding-left-5{ 60 | padding-left: 10px; 61 | } 62 | [v-cloak]{ 63 | display: none; 64 | } 65 | .tl{text-align:left} 66 | .tc{text-align:center} 67 | .tr{text-align:right} 68 | .ivu-btn{margin-right: 5px !important} -------------------------------------------------------------------------------- /src/components/common-icon/common-icon.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 39 | 40 | 43 | -------------------------------------------------------------------------------- /src/components/common-icon/index.js: -------------------------------------------------------------------------------- 1 | import CommonIcon from './common-icon.vue' 2 | export default CommonIcon 3 | -------------------------------------------------------------------------------- /src/components/common/common.less: -------------------------------------------------------------------------------- 1 | .no-select{ 2 | -webkit-touch-callout: none; 3 | -webkit-user-select: none; 4 | -khtml-user-select: none; 5 | -moz-user-select: none; 6 | -ms-user-select: none; 7 | user-select: none; 8 | } 9 | -------------------------------------------------------------------------------- /src/components/common/util.js: -------------------------------------------------------------------------------- 1 | export const showTitle = (item, vm) => { 2 | return vm.$config.useI18n ? vm.$t(item.name) : ((item.meta && item.meta.title) || item.name) 3 | } 4 | -------------------------------------------------------------------------------- /src/components/icons/icons.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 32 | 33 | 36 | -------------------------------------------------------------------------------- /src/components/icons/index.js: -------------------------------------------------------------------------------- 1 | import Icons from './icons.vue' 2 | export default Icons 3 | -------------------------------------------------------------------------------- /src/config/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | /** 3 | * @description token在Cookie中存储的天数,默认1天 4 | */ 5 | cookieExpires: 1, 6 | /** 7 | * @description 是否使用国际化,默认为false 8 | * 如果不使用,则需要在路由中给需要在菜单中展示的路由设置meta: {title: 'xxx'} 9 | * 用来在菜单中显示文字 10 | */ 11 | useI18n: false, 12 | /** 13 | * @description api请求基础路径 14 | */ 15 | baseUrl: { 16 | dev: 'http://localhost:1000', 17 | pro: 'http://localhost:1000' 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/directive/directives.js: -------------------------------------------------------------------------------- 1 | import { on } from '@/libs/tools' 2 | const directives = { 3 | draggable: { 4 | inserted: (el, binding, vnode) => { 5 | let triggerDom = document.querySelector(binding.value.trigger) 6 | triggerDom.style.cursor = 'move' 7 | let bodyDom = document.querySelector(binding.value.body) 8 | let pageX = 0 9 | let pageY = 0 10 | let transformX = 0 11 | let transformY = 0 12 | let canMove = false 13 | const handleMousedown = e => { 14 | let transform = /\(.*\)/.exec(bodyDom.style.transform) 15 | if (transform) { 16 | transform = transform[0].slice(1, transform[0].length - 1) 17 | let splitxy = transform.split('px, ') 18 | transformX = parseFloat(splitxy[0]) 19 | transformY = parseFloat(splitxy[1].split('px')[0]) 20 | } 21 | pageX = e.pageX 22 | pageY = e.pageY 23 | canMove = true 24 | } 25 | const handleMousemove = e => { 26 | let xOffset = e.pageX - pageX + transformX 27 | let yOffset = e.pageY - pageY + transformY 28 | if (canMove) bodyDom.style.transform = `translate(${xOffset}px, ${yOffset}px)` 29 | } 30 | const handleMouseup = e => { 31 | canMove = false 32 | } 33 | on(triggerDom, 'mousedown', handleMousedown) 34 | on(document, 'mousemove', handleMousemove) 35 | on(document, 'mouseup', handleMouseup) 36 | }, 37 | update: (el, binding, vnode) => { 38 | if (!binding.value.recover) return 39 | let bodyDom = document.querySelector(binding.value.body) 40 | bodyDom.style.transform = '' 41 | } 42 | } 43 | } 44 | 45 | export default directives 46 | -------------------------------------------------------------------------------- /src/directive/index.js: -------------------------------------------------------------------------------- 1 | import directive from './directives' 2 | 3 | const importDirective = Vue => { 4 | /** 5 | * 拖拽指令 v-draggable="options" 6 | * options = { 7 | * trigger: /这里传入作为拖拽触发器的CSS选择器/, 8 | * body: /这里传入需要移动容器的CSS选择器/, 9 | * recover: /拖动结束之后是否恢复到原来的位置/ 10 | * } 11 | */ 12 | Vue.directive('draggable', directive.draggable) 13 | } 14 | 15 | export default importDirective 16 | -------------------------------------------------------------------------------- /src/index.less: -------------------------------------------------------------------------------- 1 | @import '~iview/src/styles/index.less'; 2 | 3 | @menu-dark-title: #001529; 4 | @menu-dark-active-bg: #000c17; 5 | @layout-sider-background: #001529; 6 | -------------------------------------------------------------------------------- /src/libs/axios-cfg.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import iView from 'iview' 3 | import { ResError } from './error/ResError' 4 | import sf from 'string-format' 5 | import store from '@/store' 6 | import { router } from '@/router/index' 7 | import config from '../config/index' 8 | 9 | const baseRequestUrl = process.env.NODE_ENV === 'development' ? config.baseUrl.dev : config.baseUrl.pro 10 | 11 | const axiosInstance = axios.create({ 12 | baseURL: baseRequestUrl, 13 | timeout: 3000 14 | // withCredentials: true 15 | }) 16 | axiosInstance.interceptors.request.use(function (config) { 17 | iView.LoadingBar.start() 18 | if (localStorage.getItem('csrf-token')) { 19 | config.headers.Authorization = localStorage.getItem('csrf-token') 20 | } 21 | return config 22 | }, function (error) { 23 | iView.LoadingBar.finish() 24 | return Promise.reject(error) 25 | }) 26 | axiosInstance.interceptors.response.use(res => { 27 | iView.LoadingBar.finish() 28 | // -6表明身份异常或未登录 29 | if (res.data.code == -6) { 30 | store.commit('setToken', '') 31 | store.commit('setAccess', []) 32 | localStorage.clear() 33 | router.push({ 34 | name: 'login' 35 | }) 36 | } 37 | // 状态码小于0属于异常情况 38 | if (res.data.status < 0) { 39 | throw new ResError(res.data.msg) 40 | } 41 | return res.data 42 | }, error => { 43 | iView.LoadingBar.finish() 44 | console.error(error) 45 | throw new ResError('请求服务器失败,请检查服务是否正常!') 46 | return error 47 | }) 48 | 49 | export const baseUrl = baseRequestUrl 50 | 51 | export const get = (url, params, pathVariable = null) => { 52 | if (params == null) { 53 | params = { axios_timestamp_current: new Date().getTime() } 54 | } else { 55 | params.axios_timestamp_current = new Date().getTime() 56 | } 57 | return axiosInstance.get(sf(url, pathVariable), { params: params }) 58 | } 59 | 60 | export const post = (url, params, pathVariable = null) => axiosInstance.post(sf(url, pathVariable), params) 61 | 62 | export const put = (url, params, pathVariable = null) => axiosInstance.put(sf(url, pathVariable), params) 63 | 64 | export const patch = (url, params, pathVariable = null) => axiosInstance.patch(sf(url, pathVariable), params) 65 | 66 | export const del = (url, params, pathVariable = null) => axiosInstance.delete(sf(url, pathVariable), { params: params }) 67 | -------------------------------------------------------------------------------- /src/libs/error/ResError.js: -------------------------------------------------------------------------------- 1 | function ExtendableBuiltin(cls) { 2 | function ExtendableBuiltin() { 3 | cls.apply(this, arguments); 4 | } 5 | ExtendableBuiltin.prototype = Object.create(cls.prototype); 6 | Object.setPrototypeOf(ExtendableBuiltin, cls); 7 | 8 | return ExtendableBuiltin; 9 | } 10 | 11 | //自定义请求异常错误 12 | export class ResError extends ExtendableBuiltin(Error){ 13 | constructor(message) { 14 | super(message); 15 | this.message = message; 16 | this.name = "ResError"; 17 | } 18 | } -------------------------------------------------------------------------------- /src/libs/iview-cfg.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import iView from 'iview'; 3 | import { ResError } from '@/libs//error/ResError'; 4 | import ZkTable from 'vue-table-with-tree-grid' 5 | 6 | iView.Message.config({ 7 | duration: 4 8 | }); 9 | //全局引入 10 | Vue.use(iView) 11 | Vue.use(ZkTable) 12 | 13 | export const errorHandler = (error, vm)=>{ 14 | iView.Message.destroy() 15 | if(!(error instanceof ResError)){ 16 | //iView.Message.error("系统出了点小差,请联系管理员修复一下~"); 17 | console.error(error) 18 | }else{ 19 | iView.Message.error(error.message); 20 | } 21 | } 22 | 23 | Vue.config.errorHandler = errorHandler; 24 | Vue.prototype.$throw = (error)=> errorHandler(error,this); 25 | -------------------------------------------------------------------------------- /src/libs/tools.js: -------------------------------------------------------------------------------- 1 | export const forEach = (arr, fn) => { 2 | if (!arr.length || !fn) return 3 | let i = -1 4 | let len = arr.length 5 | while (++i < len) { 6 | let item = arr[i] 7 | fn(item, i, arr) 8 | } 9 | } 10 | 11 | /** 12 | * @param {Array} arr1 13 | * @param {Array} arr2 14 | * @description 得到两个数组的交集, 两个数组的元素为数值或字符串 15 | */ 16 | export const getIntersection = (arr1, arr2) => { 17 | let len = Math.min(arr1.length, arr2.length) 18 | let i = -1 19 | let res = [] 20 | while (++i < len) { 21 | const item = arr2[i] 22 | if (arr1.indexOf(item) > -1) res.push(item) 23 | } 24 | return res 25 | } 26 | 27 | /** 28 | * @param {Array} arr1 29 | * @param {Array} arr2 30 | * @description 得到两个数组的并集, 两个数组的元素为数值或字符串 31 | */ 32 | export const getUnion = (arr1, arr2) => { 33 | return Array.from(new Set([...arr1, ...arr2])) 34 | } 35 | 36 | /** 37 | * @param {Array} target 目标数组 38 | * @param {Array} arr 需要查询的数组 39 | * @description 判断要查询的数组是否至少有一个元素包含在目标数组中 40 | */ 41 | export const hasOneOf = (targetarr, arr) => { 42 | return targetarr.some(_ => arr.indexOf(_) > -1) 43 | } 44 | 45 | /** 46 | * @param {String|Number} value 要验证的字符串或数值 47 | * @param {*} validList 用来验证的列表 48 | */ 49 | export function oneOf (value, validList) { 50 | for (let i = 0; i < validList.length; i++) { 51 | if (value === validList[i]) { 52 | return true 53 | } 54 | } 55 | return false 56 | } 57 | 58 | /** 59 | * @param {Number} timeStamp 判断时间戳格式是否是毫秒 60 | * @returns {Boolean} 61 | */ 62 | const isMillisecond = timeStamp => { 63 | const timeStr = String(timeStamp) 64 | return timeStr.length > 10 65 | } 66 | 67 | /** 68 | * @param {Number} timeStamp 传入的时间戳 69 | * @param {Number} currentTime 当前时间时间戳 70 | * @returns {Boolean} 传入的时间戳是否早于当前时间戳 71 | */ 72 | const isEarly = (timeStamp, currentTime) => { 73 | return timeStamp < currentTime 74 | } 75 | 76 | /** 77 | * @param {Number} num 数值 78 | * @returns {String} 处理后的字符串 79 | * @description 如果传入的数值小于10,即位数只有1位,则在前面补充0 80 | */ 81 | const getHandledValue = num => { 82 | return num < 10 ? '0' + num : num 83 | } 84 | 85 | /** 86 | * @param {Number} timeStamp 传入的时间戳 87 | * @param {Number} startType 要返回的时间字符串的格式类型,传入'year'则返回年开头的完整时间 88 | */ 89 | const getDate = (timeStamp, startType) => { 90 | const d = new Date(timeStamp * 1000) 91 | const year = d.getFullYear() 92 | const month = getHandledValue(d.getMonth() + 1) 93 | const date = getHandledValue(d.getDate()) 94 | const hours = getHandledValue(d.getHours()) 95 | const minutes = getHandledValue(d.getMinutes()) 96 | const second = getHandledValue(d.getSeconds()) 97 | let resStr = '' 98 | if (startType === 'year') resStr = year + '-' + month + '-' + date + ' ' + hours + ':' + minutes + ':' + second 99 | else resStr = month + '-' + date + ' ' + hours + ':' + minutes 100 | return resStr 101 | } 102 | 103 | /** 104 | * @param {String|Number} timeStamp 时间戳 105 | * @returns {String} 相对时间字符串 106 | */ 107 | export const getRelativeTime = timeStamp => { 108 | // 判断当前传入的时间戳是秒格式还是毫秒 109 | const IS_MILLISECOND = isMillisecond(timeStamp) 110 | // 如果是毫秒格式则转为秒格式 111 | if (IS_MILLISECOND) Math.floor(timeStamp /= 1000) 112 | // 传入的时间戳可以是数值或字符串类型,这里统一转为数值类型 113 | timeStamp = Number(timeStamp) 114 | // 获取当前时间时间戳 115 | const currentTime = Math.floor(Date.parse(new Date()) / 1000) 116 | // 判断传入时间戳是否早于当前时间戳 117 | const IS_EARLY = isEarly(timeStamp, currentTime) 118 | // 获取两个时间戳差值 119 | let diff = currentTime - timeStamp 120 | // 如果IS_EARLY为false则差值取反 121 | if (!IS_EARLY) diff = -diff 122 | let resStr = '' 123 | const dirStr = IS_EARLY ? '前' : '后' 124 | // 少于等于59秒 125 | if (diff <= 59) resStr = diff + '秒' + dirStr 126 | // 多于59秒,少于等于59分钟59秒 127 | else if (diff > 59 && diff <= 3599) resStr = Math.floor(diff / 60) + '分钟' + dirStr 128 | // 多于59分钟59秒,少于等于23小时59分钟59秒 129 | else if (diff > 3599 && diff <= 86399) resStr = Math.floor(diff / 3600) + '小时' + dirStr 130 | // 多于23小时59分钟59秒,少于等于29天59分钟59秒 131 | else if (diff > 86399 && diff <= 2623859) resStr = Math.floor(diff / 86400) + '天' + dirStr 132 | // 多于29天59分钟59秒,少于364天23小时59分钟59秒,且传入的时间戳早于当前 133 | else if (diff > 2623859 && diff <= 31567859 && IS_EARLY) resStr = getDate(timeStamp) 134 | else resStr = getDate(timeStamp, 'year') 135 | return resStr 136 | } 137 | 138 | /** 139 | * @returns {String} 当前浏览器名称 140 | */ 141 | export const getExplorer = () => { 142 | const ua = window.navigator.userAgent 143 | const isExplorer = (exp) => { 144 | return ua.indexOf(exp) > -1 145 | } 146 | if (isExplorer('MSIE')) return 'IE' 147 | else if (isExplorer('Firefox')) return 'Firefox' 148 | else if (isExplorer('Chrome')) return 'Chrome' 149 | else if (isExplorer('Opera')) return 'Opera' 150 | else if (isExplorer('Safari')) return 'Safari' 151 | } 152 | 153 | /** 154 | * @description 绑定事件 on(element, event, handler) 155 | */ 156 | export const on = (function () { 157 | if (document.addEventListener) { 158 | return function (element, event, handler) { 159 | if (element && event && handler) { 160 | element.addEventListener(event, handler, false) 161 | } 162 | } 163 | } else { 164 | return function (element, event, handler) { 165 | if (element && event && handler) { 166 | element.attachEvent('on' + event, handler) 167 | } 168 | } 169 | } 170 | })() 171 | 172 | /** 173 | * @description 解绑事件 off(element, event, handler) 174 | */ 175 | export const off = (function () { 176 | if (document.removeEventListener) { 177 | return function (element, event, handler) { 178 | if (element && event) { 179 | element.removeEventListener(event, handler, false) 180 | } 181 | } 182 | } else { 183 | return function (element, event, handler) { 184 | if (element && event) { 185 | element.detachEvent('on' + event, handler) 186 | } 187 | } 188 | } 189 | })() 190 | 191 | /** 192 | * 判断一个对象是否存在key,如果传入第二个参数key,则是判断这个obj对象是否存在key这个属性 193 | * 如果没有传入key这个参数,则判断obj对象是否有键值对 194 | */ 195 | export const hasKey = (obj, key) => { 196 | if (key) return key in obj 197 | else { 198 | let keysArr = Object.keys(obj) 199 | return keysArr.length 200 | } 201 | } 202 | 203 | /** 204 | * @param {*} obj1 对象 205 | * @param {*} obj2 对象 206 | * @description 判断两个对象是否相等,这两个对象的值只能是数字或字符串 207 | */ 208 | export const objEqual = (obj1, obj2) => { 209 | const keysArr1 = Object.keys(obj1) 210 | const keysArr2 = Object.keys(obj2) 211 | if (keysArr1.length !== keysArr2.length) return false 212 | else if (keysArr1.length === 0 && keysArr2.length === 0) return true 213 | /* eslint-disable-next-line */ 214 | else return !keysArr1.some(key => obj1[key] != obj2[key]) 215 | } 216 | -------------------------------------------------------------------------------- /src/libs/util.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | // cookie保存的天数 3 | import config from '@/config' 4 | import { forEach, hasOneOf, objEqual } from '@/libs/tools' 5 | 6 | export const TOKEN_KEY = 'token' 7 | 8 | export const setToken = (token) => { 9 | Cookies.set(TOKEN_KEY, token, {expires: config.cookieExpires || 1}) 10 | } 11 | 12 | export const getToken = () => { 13 | const token = Cookies.get(TOKEN_KEY) 14 | if (token) return token 15 | else return false 16 | } 17 | 18 | export const hasChild = (item) => { 19 | return item.children && item.children.length !== 0 20 | } 21 | 22 | const showThisMenuEle = (item, access) => { 23 | if (item.meta && item.meta.access && item.meta.access.length) { 24 | if (hasOneOf(item.meta.access, access)) return true 25 | else return false 26 | } else return true 27 | } 28 | /** 29 | * @param {Array} list 通过路由列表得到菜单列表 30 | * @returns {Array} 31 | */ 32 | export const getMenuByRouter = (list, access) => { 33 | let res = [] 34 | forEach(list, item => { 35 | if (!item.meta || (item.meta && !item.meta.hideInMenu)) { 36 | let obj = { 37 | icon: (item.meta && item.meta.icon) || '', 38 | name: item.name, 39 | meta: item.meta 40 | } 41 | if ((hasChild(item) || (item.meta && item.meta.showAlways)) && showThisMenuEle(item, access)) { 42 | obj.children = getMenuByRouter(item.children, access) 43 | } 44 | if (item.meta && item.meta.href) obj.href = item.meta.href 45 | if (showThisMenuEle(item, access)) res.push(obj) 46 | } 47 | }) 48 | return res 49 | } 50 | 51 | /** 52 | * @param {Array} routeMetched 当前路由metched 53 | * @returns {Array} 54 | */ 55 | export const getBreadCrumbList = (routeMetched, homeRoute) => { 56 | let res = routeMetched.filter(item => { 57 | return item.meta === undefined || !item.meta.hide 58 | }).map(item => { 59 | let obj = { 60 | icon: (item.meta && item.meta.icon) || '', 61 | name: item.name, 62 | meta: item.meta 63 | } 64 | return obj 65 | }) 66 | res = res.filter(item => { 67 | return !item.meta.hideInMenu 68 | }) 69 | return [Object.assign(homeRoute, { to: homeRoute.path }), ...res] 70 | } 71 | 72 | export const showTitle = (item, vm) => vm.$config.useI18n ? vm.$t(item.name) : ((item.meta && item.meta.title) || item.name) 73 | 74 | /** 75 | * @description 本地存储和获取标签导航列表 76 | */ 77 | export const setTagNavListInLocalstorage = list => { 78 | localStorage.tagNaveList = JSON.stringify(list) 79 | } 80 | /** 81 | * @returns {Array} 其中的每个元素只包含路由原信息中的name, path, meta三项 82 | */ 83 | export const getTagNavListFromLocalstorage = () => { 84 | const list = localStorage.tagNaveList 85 | return list ? JSON.parse(list) : [] 86 | } 87 | 88 | /** 89 | * @param {Array} routers 路由列表数组 90 | * @description 用于找到路由列表中name为home的对象 91 | */ 92 | export const getHomeRoute = routers => { 93 | let i = -1 94 | let len = routers.length 95 | let homeRoute = {} 96 | while (++i < len) { 97 | let item = routers[i] 98 | if (item.children && item.children.length) { 99 | let res = getHomeRoute(item.children) 100 | if (res.name) return res 101 | } else { 102 | if (item.name === 'home') homeRoute = item 103 | } 104 | } 105 | return homeRoute 106 | } 107 | 108 | /** 109 | * @param {*} list 现有标签导航列表 110 | * @param {*} newRoute 新添加的路由原信息对象 111 | * @description 如果该newRoute已经存在则不再添加 112 | */ 113 | export const getNewTagList = (list, newRoute) => { 114 | const { name, path, meta } = newRoute 115 | let newList = [...list] 116 | if (newList.findIndex(item => item.name === name) >= 0) return newList 117 | else newList.push({ name, path, meta }) 118 | return newList 119 | } 120 | 121 | /** 122 | * @param {*} access 用户权限数组,如 ['super_admin', 'admin'] 123 | * @param {*} route 路由列表 124 | */ 125 | const hasAccess = (access, route) => { 126 | if (route.meta && route.meta.access) return hasOneOf(access, route.meta.access) 127 | else return true 128 | } 129 | 130 | /** 131 | * 权鉴 132 | * @param {*} name 即将跳转的路由name 133 | * @param {*} access 用户权限数组 134 | * @param {*} routes 路由列表 135 | * @description 用户是否可跳转到该页 136 | */ 137 | export const canTurnTo = (name, access, routes) => { 138 | const routePermissionJudge = (list) => { 139 | return list.some(item => { 140 | if (item.children && item.children.length) { 141 | return routePermissionJudge(item.children) 142 | } else if (item.name === name) { 143 | return hasAccess(access, item) 144 | } 145 | }) 146 | } 147 | 148 | return routePermissionJudge(routes) 149 | } 150 | 151 | /** 152 | * @param {String} url 153 | * @description 从URL中解析参数 154 | */ 155 | export const getParams = url => { 156 | const keyValueArr = url.split('?')[1].split('&') 157 | let paramObj = {} 158 | keyValueArr.forEach(item => { 159 | const keyValue = item.split('=') 160 | paramObj[keyValue[0]] = keyValue[1] 161 | }) 162 | return paramObj 163 | } 164 | 165 | /** 166 | * @param {Array} list 标签列表 167 | * @param {String} name 当前关闭的标签的name 168 | */ 169 | export const getNextRoute = (list, route) => { 170 | let res = {} 171 | if (list.length === 2) { 172 | res = getHomeRoute(list) 173 | } else { 174 | const index = list.findIndex(item => routeEqual(item, route)) 175 | if (index === list.length - 1) res = list[list.length - 2] 176 | else res = list[index + 1] 177 | } 178 | return res 179 | } 180 | 181 | /** 182 | * @param {Number} times 回调函数需要执行的次数 183 | * @param {Function} callback 回调函数 184 | */ 185 | export const doCustomTimes = (times, callback) => { 186 | let i = -1 187 | while (++i < times) { 188 | callback(i) 189 | } 190 | } 191 | 192 | /** 193 | * @param {Object} file 从上传组件得到的文件对象 194 | * @returns {Promise} resolve参数是解析后的二维数组 195 | * @description 从Csv文件中解析出表格,解析成二维数组 196 | */ 197 | export const getArrayFromFile = (file) => { 198 | let nameSplit = file.name.split('.') 199 | let format = nameSplit[nameSplit.length - 1] 200 | return new Promise((resolve, reject) => { 201 | let reader = new FileReader() 202 | reader.readAsText(file) // 以文本格式读取 203 | let arr = [] 204 | reader.onload = function (evt) { 205 | let data = evt.target.result // 读到的数据 206 | let pasteData = data.trim() 207 | arr = pasteData.split((/[\n\u0085\u2028\u2029]|\r\n?/g)).map(row => { 208 | return row.split('\t') 209 | }).map(item => { 210 | return item[0].split(',') 211 | }) 212 | if (format === 'csv') resolve(arr) 213 | else reject(new Error('[Format Error]:你上传的不是Csv文件')) 214 | } 215 | }) 216 | } 217 | 218 | /** 219 | * @param {Array} array 表格数据二维数组 220 | * @returns {Object} { columns, tableData } 221 | * @description 从二维数组中获取表头和表格数据,将第一行作为表头,用于在iView的表格中展示数据 222 | */ 223 | export const getTableDataFromArray = (array) => { 224 | let columns = [] 225 | let tableData = [] 226 | if (array.length > 1) { 227 | let titles = array.shift() 228 | columns = titles.map(item => { 229 | return { 230 | title: item, 231 | key: item 232 | } 233 | }) 234 | tableData = array.map(item => { 235 | let res = {} 236 | item.forEach((col, i) => { 237 | res[titles[i]] = col 238 | }) 239 | return res 240 | }) 241 | } 242 | return { 243 | columns, 244 | tableData 245 | } 246 | } 247 | 248 | export const findNodeUpper = (ele, tag) => { 249 | if (ele.parentNode) { 250 | if (ele.parentNode.tagName === tag.toUpperCase()) { 251 | return ele.parentNode 252 | } else { 253 | return findNodeUpper(ele.parentNode, tag) 254 | } 255 | } 256 | } 257 | 258 | export const findNodeDownward = (ele, tag) => { 259 | const tagName = tag.toUpperCase() 260 | if (ele.childNodes.length) { 261 | let i = -1 262 | let len = ele.childNodes.length 263 | while (++i < len) { 264 | let child = ele.childNodes[i] 265 | if (child.tagName === tagName) return child 266 | else return findNodeDownward(child, tag) 267 | } 268 | } 269 | } 270 | 271 | export const showByAccess = (access, canViewAccess) => { 272 | return hasOneOf(canViewAccess, access) 273 | } 274 | 275 | /** 276 | * @description 根据name/params/query判断两个路由对象是否相等 277 | * @param {*} route1 路由对象 278 | * @param {*} route2 路由对象 279 | */ 280 | export const routeEqual = (route1, route2) => { 281 | const params1 = route1.params || {} 282 | const params2 = route2.params || {} 283 | const query1 = route1.query || {} 284 | const query2 = route2.query || {} 285 | return (route1.name === route2.name) && objEqual(params1, params2) && objEqual(query1, query2) 286 | } 287 | 288 | /** 289 | * 判断打开的标签列表里是否已存在这个新添加的路由对象 290 | */ 291 | export const routeHasExist = (tagNavList, routeItem) => { 292 | let len = tagNavList.length 293 | let res = false 294 | doCustomTimes(len, (index) => { 295 | if (routeEqual(tagNavList[index], routeItem)) res = true 296 | }) 297 | return res 298 | } 299 | -------------------------------------------------------------------------------- /src/locale/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueI18n from 'vue-i18n' 3 | import customZhCn from './lang/zh-CN' 4 | import customZhTw from './lang/zh-TW' 5 | import customEnUs from './lang/en-US' 6 | import zhCnLocale from 'iview/src/locale/lang/zh-CN' 7 | import enUsLocale from 'iview/src/locale/lang/en-US' 8 | import zhTwLocale from 'iview/src/locale/lang/zh-TW' 9 | 10 | Vue.use(VueI18n) 11 | 12 | // 自动根据浏览器系统语言设置语言 13 | const navLang = navigator.language 14 | const localLang = (navLang === 'zh-CN' || navLang === 'en-US') ? navLang : false 15 | let lang = window.localStorage.lang || localLang || 'zh-CN' 16 | 17 | Vue.config.lang = lang 18 | 19 | // vue-i18n 6.x+写法 20 | Vue.locale = () => {} 21 | const messages = { 22 | 'zh-CN': Object.assign(zhCnLocale, customZhCn), 23 | 'zh-TW': Object.assign(zhTwLocale, customZhTw), 24 | 'en-US': Object.assign(enUsLocale, customEnUs) 25 | } 26 | const i18n = new VueI18n({ 27 | locale: lang, 28 | messages 29 | }) 30 | 31 | export default i18n 32 | 33 | // vue-i18n 5.x写法 34 | // Vue.locale('zh-CN', Object.assign(zhCnLocale, customZhCn)) 35 | // Vue.locale('en-US', Object.assign(zhTwLocale, customZhTw)) 36 | // Vue.locale('zh-TW', Object.assign(enUsLocale, customEnUs)) 37 | -------------------------------------------------------------------------------- /src/locale/lang/en-US.js: -------------------------------------------------------------------------------- 1 | export default { 2 | components: 'Components', 3 | count_to_page: 'Count-to', 4 | tables_page: 'Table', 5 | split_pane_page: 'Split-pane', 6 | markdown_page: 'Markdown-editor', 7 | editor_page: 'Rich-Text-Editor', 8 | icons_page: 'Custom-icon', 9 | img_cropper_page: 'Image-editor', 10 | update: 'Update', 11 | doc: 'Document', 12 | join_page: 'QQ Group', 13 | update_table_page: 'Update .CSV', 14 | update_paste_page: 'Paste Table Data', 15 | multilevel: 'multilevel', 16 | directive_page: 'Directive', 17 | level_1: 'level-1', 18 | level_2: 'level-2', 19 | level_2_1: 'level-2-1' 20 | } 21 | -------------------------------------------------------------------------------- /src/locale/lang/zh-CN.js: -------------------------------------------------------------------------------- 1 | export default { 2 | components: '组件', 3 | count_to_page: '数字渐变', 4 | tables_page: '多功能表格', 5 | split_pane_page: '分割窗口', 6 | markdown_page: 'Markdown编辑器', 7 | editor_page: '富文本编辑器', 8 | icons_page: '自定义图标', 9 | img_cropper_page: '图片编辑器', 10 | update: '上传数据', 11 | join_page: 'QQ群', 12 | doc: '文档', 13 | update_table_page: '上传CSV文件', 14 | update_paste_page: '粘贴表格数据', 15 | multilevel: '多级菜单', 16 | directive_page: '指令', 17 | level_1: 'level-1', 18 | level_2: 'level-2', 19 | level_2_1: 'level-2-1' 20 | } 21 | -------------------------------------------------------------------------------- /src/locale/lang/zh-TW.js: -------------------------------------------------------------------------------- 1 | export default { 2 | components: '组件', 3 | count_to_page: '数字渐变', 4 | tables_page: '多功能表格', 5 | split_pane_page: '分割窗口', 6 | markdown_page: 'Markdown編輯器', 7 | editor_page: '富文本編輯器', 8 | icons_page: '自定義圖標', 9 | img_cropper_page: '圖片編輯器', 10 | update: '上傳數據', 11 | join_page: 'QQ群', 12 | doc: '文檔', 13 | update_table_page: '上傳CSV文件', 14 | update_paste_page: '粘貼表格數據', 15 | multilevel: '多级菜单', 16 | directive_page: '指令', 17 | level_1: 'level-1', 18 | level_2: 'level-2', 19 | level_2_1: 'level-2-1' 20 | } 21 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import App from './App' 5 | import router from './router' 6 | import store from './store' 7 | import iView from 'iview' 8 | import config from '@/config' 9 | import importDirective from '@/directive' 10 | import 'iview/dist/styles/iview.css' 11 | import './index.less' 12 | import '@/assets/icons/iconfont.css' 13 | import './libs/iview-cfg' 14 | // import '@/mock' 15 | // 实际打包时应该不引入mock 16 | /* eslint-disable */ 17 | if (process.env.NODE_ENV !== 'production') require('@/mock') 18 | 19 | Vue.use(iView) 20 | Vue.config.productionTip = false 21 | /** 22 | * @description 全局注册应用配置 23 | */ 24 | Vue.prototype.$config = config 25 | /** 26 | * 注册指令 27 | */ 28 | importDirective(Vue) 29 | 30 | /* eslint-disable no-new */ 31 | new Vue({ 32 | el: '#app', 33 | router, 34 | store, 35 | render: h => h(App) 36 | }) 37 | -------------------------------------------------------------------------------- /src/mock/data.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs' 2 | import { doCustomTimes } from '@/libs/util' 3 | const Random = Mock.Random 4 | 5 | export const getTableData = req => { 6 | let tableData = [] 7 | doCustomTimes(5, () => { 8 | tableData.push(Mock.mock({ 9 | name: '@name', 10 | email: '@email', 11 | createTime: '@date' 12 | })) 13 | }) 14 | return tableData 15 | } 16 | 17 | export const getDragList = req => { 18 | let dragList = [] 19 | doCustomTimes(5, () => { 20 | dragList.push(Mock.mock({ 21 | name: Random.csentence(10, 13), 22 | id: Random.increment(10) 23 | })) 24 | }) 25 | return dragList 26 | } 27 | -------------------------------------------------------------------------------- /src/mock/index.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs' 2 | import { login, logout, getUserInfo } from './login' 3 | import { getTableData, getDragList } from './data' 4 | 5 | // 登录相关和获取用户信息 6 | Mock.mock(/\/login/, login) 7 | Mock.mock(/\/get_info/, getUserInfo) 8 | Mock.mock(/\/logout/, logout) 9 | Mock.mock(/\/get_table_data/, getTableData) 10 | Mock.mock(/\/get_drag_list/, getDragList) 11 | 12 | export default Mock 13 | -------------------------------------------------------------------------------- /src/mock/login.js: -------------------------------------------------------------------------------- 1 | import { getParams } from '@/libs/util' 2 | const USER_MAP = { 3 | super_admin: { 4 | name: 'super_admin', 5 | user_id: '1', 6 | access: ['super_admin', 'admin'], 7 | token: 'super_admin', 8 | avator: 'https://file.iviewui.com/dist/a0e88e83800f138b94d2414621bd9704.png' 9 | }, 10 | admin: { 11 | name: 'admin', 12 | user_id: '2', 13 | access: ['admin'], 14 | token: 'admin', 15 | avator: 'https://avatars0.githubusercontent.com/u/20942571?s=460&v=4' 16 | } 17 | } 18 | 19 | export const login = req => { 20 | req = JSON.parse(req.body) 21 | return {token: USER_MAP[req.userName].token} 22 | } 23 | 24 | export const getUserInfo = req => { 25 | const params = getParams(req.url) 26 | return USER_MAP[params.token] 27 | } 28 | 29 | export const logout = req => { 30 | return null 31 | } 32 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import routes from './routers' 4 | import store from '@/store' 5 | import iView from 'iview' 6 | import { getToken, canTurnTo } from '@/libs/util' 7 | 8 | Vue.use(Router) 9 | const router = new Router({ 10 | routes, 11 | mode: 'history' 12 | }) 13 | const LOGIN_PAGE_NAME = 'login' 14 | 15 | router.beforeEach((to, from, next) => { 16 | try{ 17 | document.title = to.meta.title; 18 | }catch(e){console.warn('load title warning!')} 19 | iView.LoadingBar.start() 20 | const token = getToken() 21 | if (!token && to.name !== LOGIN_PAGE_NAME) { 22 | // 未登录且要跳转的页面不是登录页 23 | next({ 24 | name: LOGIN_PAGE_NAME // 跳转到登录页 25 | }) 26 | } else if (!token && to.name === LOGIN_PAGE_NAME) { 27 | // 未登陆且要跳转的页面是登录页 28 | next() // 跳转 29 | } else if (token && to.name === LOGIN_PAGE_NAME) { 30 | // 已登录且要跳转的页面是登录页 31 | next({ 32 | name: 'home' // 跳转到home页 33 | }) 34 | } else { 35 | store.dispatch('getUserInfo').then(user => { 36 | // 拉取用户信息,通过用户权限和跳转的页面的name来判断是否有权限访问;access必须是一个数组,如:['super_admin'] ['super_admin', 'admin'] 37 | if (canTurnTo(to.name, user.access, routes)) next() // 有权限,可访问 38 | else next({ replace: true, name: 'error_401' }) // 无权限,重定向到401页面 39 | }) 40 | } 41 | }) 42 | 43 | router.afterEach(to => { 44 | iView.LoadingBar.finish() 45 | window.scrollTo(0, 0) 46 | }) 47 | 48 | export default router 49 | -------------------------------------------------------------------------------- /src/router/routers.js: -------------------------------------------------------------------------------- 1 | import Main from '@/view/main' 2 | 3 | /** 4 | * iview-admin中meta除了原生参数外可配置的参数: 5 | * meta: { 6 | * hideInMenu: (false) 设为true后在左侧菜单不会显示该页面选项 7 | * notCache: (false) 设为true后页面不会缓存 8 | * access: (null) 可访问该页面的权限数组,当前路由设置的权限会影响子路由 9 | * icon: (-) 该页面在左侧菜单、面包屑和标签导航处显示的图标,如果是自定义图标,需要在图标名称前加下划线'_' 10 | * } 11 | */ 12 | 13 | export default [ 14 | { 15 | path: '/login', 16 | name: 'login', 17 | meta: { 18 | title: '登录', 19 | hideInMenu: true 20 | }, 21 | component: () => import('@/view/login') 22 | }, 23 | { 24 | path: '/', 25 | name: '_home', 26 | redirect: '/home', 27 | component: Main, 28 | meta: { 29 | hideInMenu: true, 30 | notCache: true 31 | }, 32 | children: [ 33 | { 34 | path: '/home', 35 | name: 'home', 36 | meta: { 37 | hideInMenu: true, 38 | title: '首页', 39 | notCache: true 40 | }, 41 | component: () => import('@/view/home/index') 42 | } 43 | ] 44 | }, 45 | { 46 | path: '/system', 47 | name: 'doc', 48 | component: Main, 49 | meta: { 50 | title: '系统设置', 51 | icon:'ios-cog' 52 | }, 53 | children:[ 54 | { path: 'user',meta:{icon: 'md-people', title: '用户管理',access: ['system:user:list']}, name: 'system_user', component: () => import('@/view/system/user') }, 55 | { path: 'person-stalker',meta:{icon: 'md-body',title: '角色管理',}, name: 'system_role', component: () => import('@/view/system/role') }, 56 | { path: 'resource',meta:{icon: 'ios-lock',title: '资源管理',access: ['system:resource:list']}, name: 'system_resource', component: () => import('@/view/system/resource') }, 57 | { path: 'log',meta:{icon: 'ios-aperture',title: '系统日志',access: ['system:log:list']}, name: 'system_log', component: () => import('@/view/system/log') }, 58 | ] 59 | }, 60 | { 61 | path: '/401', 62 | name: 'error_401', 63 | meta: { 64 | hideInMenu: true 65 | }, 66 | component: () => import('@/view/error-page/401.vue') 67 | }, 68 | { 69 | path: '/500', 70 | name: 'error_500', 71 | meta: { 72 | hideInMenu: true 73 | }, 74 | component: () => import('@/view/error-page/500.vue') 75 | }, 76 | { 77 | path: '*', 78 | name: 'error_404', 79 | meta: { 80 | hideInMenu: true 81 | }, 82 | component: () => import('@/view/error-page/404.vue') 83 | } 84 | ] 85 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | import user from './module/user' 5 | import app from './module/app' 6 | 7 | Vue.use(Vuex) 8 | 9 | export default new Vuex.Store({ 10 | state: { 11 | // 12 | }, 13 | mutations: { 14 | // 15 | }, 16 | actions: { 17 | // 18 | }, 19 | modules: { 20 | user, 21 | app 22 | } 23 | }) 24 | -------------------------------------------------------------------------------- /src/store/module/app.js: -------------------------------------------------------------------------------- 1 | import { getBreadCrumbList, setTagNavListInLocalstorage, getMenuByRouter, getTagNavListFromLocalstorage, getHomeRoute, routeHasExist } from '@/libs/util' 2 | import routers from '@/router/routers' 3 | export default { 4 | state: { 5 | breadCrumbList: [], 6 | tagNavList: [], 7 | homeRoute: getHomeRoute(routers), 8 | local: '' 9 | }, 10 | getters: { 11 | menuList: (state, getters, rootState) => getMenuByRouter(routers, rootState.user.access) 12 | }, 13 | mutations: { 14 | setBreadCrumb (state, routeMetched) { 15 | state.breadCrumbList = getBreadCrumbList(routeMetched, state.homeRoute) 16 | }, 17 | setTagNavList (state, list) { 18 | if (list) { 19 | state.tagNavList = [...list] 20 | setTagNavListInLocalstorage([...list]) 21 | } else state.tagNavList = getTagNavListFromLocalstorage() 22 | }, 23 | addTag (state, { route, type = 'unshift' }) { 24 | if (!routeHasExist(state.tagNavList, route)) { 25 | if (type === 'push') state.tagNavList.push(route) 26 | else { 27 | if (route.name === 'home') state.tagNavList.unshift(route) 28 | else state.tagNavList.splice(1, 0, route) 29 | } 30 | setTagNavListInLocalstorage([...state.tagNavList]) 31 | } 32 | }, 33 | setLocal (state, lang) { 34 | state.local = lang 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/store/module/user.js: -------------------------------------------------------------------------------- 1 | import { setToken, getToken } from '@/libs/util' 2 | import { post } from '@/libs/axios-cfg' 3 | import {errorHandler} from '@/libs/iview-cfg' 4 | export default { 5 | state: { 6 | userName: '', 7 | userId: '', 8 | avatorImgPath: '', 9 | token: getToken(), 10 | access: '' 11 | }, 12 | mutations: { 13 | setAvator (state, avatorPath) { 14 | state.avatorImgPath = avatorPath 15 | }, 16 | setUserId (state, id) { 17 | state.userId = id 18 | }, 19 | setUserName (state, name) { 20 | state.userName = name 21 | }, 22 | setAccess (state, access) { 23 | state.access = access 24 | }, 25 | setToken (state, token) { 26 | state.token = token 27 | setToken(token) 28 | } 29 | }, 30 | actions: { 31 | // 退出登录 32 | handleLogOut ({ state, commit }) { 33 | commit('setToken', '') 34 | commit('setAccess', []) 35 | localStorage.clear() 36 | }, 37 | // 获取用户相关信息 38 | async getUserInfo ({ state, commit }) { 39 | try { 40 | let res = await post('/account/all-permission-tag') 41 | commit('setAccess', res.data) 42 | return {access:res.data} 43 | } catch (error) { 44 | errorHandler(error) 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/view/error-page/401.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 20 | -------------------------------------------------------------------------------- /src/view/error-page/404.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 20 | -------------------------------------------------------------------------------- /src/view/error-page/500.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 20 | -------------------------------------------------------------------------------- /src/view/error-page/back-btn-group.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 39 | -------------------------------------------------------------------------------- /src/view/error-page/error-content.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 29 | -------------------------------------------------------------------------------- /src/view/error-page/error.less: -------------------------------------------------------------------------------- 1 | .error-page{ 2 | width: 100%; 3 | height: 100%; 4 | position: relative; 5 | background: #f8f8f9; 6 | .content-con{ 7 | width: 700px; 8 | height: 600px; 9 | position: absolute; 10 | left: 50%; 11 | top: 50%; 12 | transform: translate(-50%, -60%); 13 | img{ 14 | display: block; 15 | width: 100%; 16 | height: 100%; 17 | } 18 | .text-con{ 19 | position: absolute; 20 | left: 0px; 21 | top: 0px; 22 | h4{ 23 | position: absolute; 24 | left: 0px; 25 | top: 0px; 26 | font-size: 80px; 27 | font-weight: 700; 28 | color: #348EED; 29 | } 30 | h5{ 31 | position: absolute; 32 | width: 700px; 33 | left: 0px; 34 | top: 100px; 35 | font-size: 20px; 36 | font-weight: 700; 37 | color: #67647D; 38 | } 39 | } 40 | .back-btn-group{ 41 | position: absolute; 42 | right: 0px; 43 | bottom: 20px; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/view/home/index.vue: -------------------------------------------------------------------------------- 1 | 50 | 54 | 55 | -------------------------------------------------------------------------------- /src/view/login/components/page-header.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/view/login/components/service.vue: -------------------------------------------------------------------------------- 1 | 34 | 94 | -------------------------------------------------------------------------------- /src/view/login/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 21 | 22 | -------------------------------------------------------------------------------- /src/view/main/components/fullscreen/fullscreen.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 75 | 76 | 85 | -------------------------------------------------------------------------------- /src/view/main/components/fullscreen/index.js: -------------------------------------------------------------------------------- 1 | import Fullscreen from './fullscreen.vue' 2 | export default Fullscreen 3 | -------------------------------------------------------------------------------- /src/view/main/components/header-bar/custom-bread-crumb/custom-bread-crumb.less: -------------------------------------------------------------------------------- 1 | .custom-bread-crumb{ 2 | display: inline-block; 3 | vertical-align: top; 4 | } 5 | -------------------------------------------------------------------------------- /src/view/main/components/header-bar/custom-bread-crumb/custom-bread-crumb.vue: -------------------------------------------------------------------------------- 1 | 11 | 47 | -------------------------------------------------------------------------------- /src/view/main/components/header-bar/custom-bread-crumb/index.js: -------------------------------------------------------------------------------- 1 | import customBreadCrumb from './custom-bread-crumb.vue' 2 | export default customBreadCrumb 3 | -------------------------------------------------------------------------------- /src/view/main/components/header-bar/header-bar.less: -------------------------------------------------------------------------------- 1 | .header-bar{ 2 | width: 100%; 3 | height: 100%; 4 | position: relative; 5 | .custom-content-con{ 6 | float: right; 7 | height: auto; 8 | padding-right: 20px; 9 | line-height: 64px; 10 | & > *{ 11 | float: right; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/view/main/components/header-bar/header-bar.vue: -------------------------------------------------------------------------------- 1 | 10 | 35 | -------------------------------------------------------------------------------- /src/view/main/components/header-bar/index.js: -------------------------------------------------------------------------------- 1 | import HeaderBar from './header-bar' 2 | export default HeaderBar 3 | -------------------------------------------------------------------------------- /src/view/main/components/header-bar/sider-trigger/index.js: -------------------------------------------------------------------------------- 1 | import siderTrigger from './sider-trigger.vue' 2 | export default siderTrigger 3 | -------------------------------------------------------------------------------- /src/view/main/components/header-bar/sider-trigger/sider-trigger.less: -------------------------------------------------------------------------------- 1 | .trans{ 2 | transition: transform .2s ease; 3 | } 4 | @size: 40px; 5 | .sider-trigger-a{ 6 | padding: 6px; 7 | width: @size; 8 | height: @size; 9 | display: inline-block; 10 | text-align: center; 11 | color: #5c6b77; 12 | margin-top: 12px; 13 | i{ 14 | .trans; 15 | vertical-align: top; 16 | } 17 | &.collapsed i{ 18 | transform: rotateZ(90deg); 19 | .trans; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/view/main/components/header-bar/sider-trigger/sider-trigger.vue: -------------------------------------------------------------------------------- 1 | 4 | 25 | 28 | -------------------------------------------------------------------------------- /src/view/main/components/language/index.js: -------------------------------------------------------------------------------- 1 | import Language from './language.vue' 2 | export default Language 3 | -------------------------------------------------------------------------------- /src/view/main/components/language/language.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 52 | -------------------------------------------------------------------------------- /src/view/main/components/side-menu/collapsed-menu.vue: -------------------------------------------------------------------------------- 1 | 12 | 47 | -------------------------------------------------------------------------------- /src/view/main/components/side-menu/index.js: -------------------------------------------------------------------------------- 1 | import SideMenu from './side-menu.vue' 2 | export default SideMenu 3 | -------------------------------------------------------------------------------- /src/view/main/components/side-menu/item-mixin.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | parentItem: { 4 | type: Object, 5 | default: () => {} 6 | }, 7 | theme: String, 8 | iconSize: Number 9 | }, 10 | computed: { 11 | parentName () { 12 | return this.parentItem.name 13 | }, 14 | children () { 15 | return this.parentItem.children 16 | }, 17 | textColor () { 18 | return this.theme === 'dark' ? '#fff' : '#495060' 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/view/main/components/side-menu/mixin.js: -------------------------------------------------------------------------------- 1 | import CommonIcon from '_c/common-icon' 2 | export default { 3 | components: { 4 | CommonIcon 5 | }, 6 | methods: { 7 | showTitle (item) { 8 | return this.$config.useI18n ? this.$t(item.name) : ((item.meta && item.meta.title) || item.name) 9 | }, 10 | showChildren (item) { 11 | return item.children && (item.children.length > 1 || (item.meta && item.meta.showAlways)) 12 | }, 13 | getNameOrHref (item, children0) { 14 | return item.href ? `isTurnByHref_${item.href}` : (children0 ? item.children[0].name : item.name) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/view/main/components/side-menu/side-menu-item.vue: -------------------------------------------------------------------------------- 1 | 19 | 27 | -------------------------------------------------------------------------------- /src/view/main/components/side-menu/side-menu.less: -------------------------------------------------------------------------------- 1 | .side-menu-wrapper{ 2 | user-select: none; 3 | .menu-collapsed{ 4 | padding-top: 10px; 5 | 6 | .ivu-dropdown{ 7 | width: 100%; 8 | .ivu-dropdown-rel a{ 9 | width: 100%; 10 | } 11 | } 12 | .ivu-tooltip{ 13 | width: 100%; 14 | .ivu-tooltip-rel{ 15 | width: 100%; 16 | } 17 | .ivu-tooltip-popper .ivu-tooltip-content{ 18 | .ivu-tooltip-arrow{ 19 | border-right-color: #fff; 20 | } 21 | .ivu-tooltip-inner{ 22 | background: #fff; 23 | color: #495060; 24 | } 25 | } 26 | } 27 | 28 | 29 | } 30 | a.drop-menu-a{ 31 | display: inline-block; 32 | padding: 6px 15px; 33 | width: 100%; 34 | text-align: center; 35 | color: #495060; 36 | } 37 | } 38 | .menu-title{ 39 | padding-left: 6px; 40 | } 41 | -------------------------------------------------------------------------------- /src/view/main/components/side-menu/side-menu.vue: -------------------------------------------------------------------------------- 1 | 26 | 112 | 115 | -------------------------------------------------------------------------------- /src/view/main/components/tags-nav/index.js: -------------------------------------------------------------------------------- 1 | import TagsNav from './tags-nav.vue' 2 | export default TagsNav 3 | -------------------------------------------------------------------------------- /src/view/main/components/tags-nav/tags-nav.less: -------------------------------------------------------------------------------- 1 | .no-select{ 2 | -webkit-touch-callout: none; 3 | -webkit-user-select: none; 4 | -khtml-user-select: none; 5 | -moz-user-select: none; 6 | -ms-user-select: none; 7 | user-select: none; 8 | } 9 | .size{ 10 | width: 100%; 11 | height: 100%; 12 | } 13 | .tags-nav{ 14 | position: relative; 15 | border-top: 1px solid #F0F0F0; 16 | border-bottom: 1px solid #F0F0F0; 17 | .no-select; 18 | .size; 19 | .close-con{ 20 | position: absolute; 21 | right: 0; 22 | top: 0; 23 | height: 100%; 24 | width: 32px; 25 | background: #fff; 26 | text-align: center; 27 | z-index: 10; 28 | } 29 | .btn-con{ 30 | position: absolute; 31 | top: 0px; 32 | height: 100%; 33 | background: #fff; 34 | padding-top: 3px; 35 | z-index: 10; 36 | button{ 37 | padding: 6px 4px; 38 | line-height: 14px; 39 | text-align: center; 40 | } 41 | &.left-btn{ 42 | left: 0px; 43 | } 44 | &.right-btn{ 45 | right: 0px; 46 | border-right: 1px solid #F0F0F0; 47 | } 48 | } 49 | .scroll-outer{ 50 | position: absolute; 51 | left: 28px; 52 | right: 28px; 53 | top: 0; 54 | bottom: 0; 55 | box-shadow: 0px 0 3px 2px rgba(100,100,100,.1) inset; 56 | .scroll-body{ 57 | height: ~"calc(100% - 1px)"; 58 | display: inline-block; 59 | padding: 1px 4px 0; 60 | position: absolute; 61 | overflow: visible; 62 | white-space: nowrap; 63 | transition: left .3s ease; 64 | .ivu-tag-dot-inner{ 65 | transition: background .2s ease; 66 | } 67 | } 68 | } 69 | .contextmenu { 70 | position: absolute; 71 | margin: 0; 72 | padding: 5px 0; 73 | background: #fff; 74 | z-index: 100; 75 | list-style-type: none; 76 | border-radius: 4px; 77 | box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3); 78 | li { 79 | margin: 0; 80 | padding: 5px 15px; 81 | cursor: pointer; 82 | &:hover { 83 | background: #eee; 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/view/main/components/tags-nav/tags-nav.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 182 | 183 | 186 | -------------------------------------------------------------------------------- /src/view/main/components/user/index.js: -------------------------------------------------------------------------------- 1 | import User from './user.vue' 2 | export default User 3 | -------------------------------------------------------------------------------- /src/view/main/components/user/user.less: -------------------------------------------------------------------------------- 1 | .user{ 2 | &-avator-dropdown{ 3 | cursor: pointer; 4 | display: inline-block; 5 | // height: 64px; 6 | vertical-align: middle; 7 | // line-height: 64px; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/view/main/components/user/user.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 42 | -------------------------------------------------------------------------------- /src/view/main/index.js: -------------------------------------------------------------------------------- 1 | import Main from './main.vue' 2 | export default Main 3 | -------------------------------------------------------------------------------- /src/view/main/main.less: -------------------------------------------------------------------------------- 1 | .main{ 2 | .logo-con{ 3 | height: 64px; 4 | padding: 10px; 5 | img{ 6 | height: 44px; 7 | width: auto; 8 | display: block; 9 | margin: 0 auto; 10 | } 11 | } 12 | .header-con{ 13 | background: #fff; 14 | padding: 0 20px; 15 | width: 100%; 16 | } 17 | .main-layout-con{ 18 | height: 100%; 19 | overflow: hidden; 20 | } 21 | .main-content-con{ 22 | height: ~"calc(100% - 60px)"; 23 | overflow: hidden; 24 | } 25 | .tag-nav-wrapper{ 26 | padding: 0; 27 | height:40px; 28 | background:#F0F0F0; 29 | } 30 | .content-wrapper{ 31 | padding: 18px; 32 | height: ~"calc(100% - 80px)"; 33 | overflow: auto; 34 | } 35 | .left-sider{ 36 | .ivu-layout-sider-children{ 37 | overflow-y: scroll; 38 | margin-right: -18px; 39 | } 40 | } 41 | } 42 | .ivu-menu-item > i{ 43 | margin-right: 12px !important; 44 | } 45 | .ivu-menu-submenu > .ivu-menu > .ivu-menu-item > i { 46 | margin-right: 8px !important; 47 | } 48 | .ivu-select-dropdown.ivu-dropdown-transfer{ 49 | max-height: 1000px; 50 | & div.ivu-dropdown{ 51 | width: 100%; 52 | margin: 0; 53 | line-height: normal; 54 | padding: 7px 0 6px 16px; 55 | clear: both; 56 | font-size: 12px !important; 57 | white-space: nowrap; 58 | list-style: none; 59 | cursor: pointer; 60 | transition: background 0.2s ease-in-out; 61 | &:hover{ 62 | background: rgba(100, 100, 100, 0.1); 63 | } 64 | & * { 65 | color: #515a6e; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/view/main/main.vue: -------------------------------------------------------------------------------- 1 | 34 | 37 | 158 | -------------------------------------------------------------------------------- /src/view/single-page/home/example.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 115 | -------------------------------------------------------------------------------- /src/view/single-page/home/home.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 78 | 79 | 84 | -------------------------------------------------------------------------------- /src/view/single-page/home/index.js: -------------------------------------------------------------------------------- 1 | import home from './home.vue' 2 | export default home 3 | -------------------------------------------------------------------------------- /src/view/system/log/index.vue: -------------------------------------------------------------------------------- 1 | 38 | -------------------------------------------------------------------------------- /src/view/system/resource/index.vue: -------------------------------------------------------------------------------- 1 | 127 | -------------------------------------------------------------------------------- /src/view/system/role/components/add.vue: -------------------------------------------------------------------------------- 1 | 22 | 99 | 100 | -------------------------------------------------------------------------------- /src/view/system/role/components/update.vue: -------------------------------------------------------------------------------- 1 | 22 | 121 | 122 | -------------------------------------------------------------------------------- /src/view/system/role/index.vue: -------------------------------------------------------------------------------- 1 | 45 | -------------------------------------------------------------------------------- /src/view/system/user/components/add.vue: -------------------------------------------------------------------------------- 1 | 38 | 125 | 126 | -------------------------------------------------------------------------------- /src/view/system/user/components/reset-password.vue: -------------------------------------------------------------------------------- 1 | 21 | 90 | -------------------------------------------------------------------------------- /src/view/system/user/components/update.vue: -------------------------------------------------------------------------------- 1 | 35 | 141 | 142 | -------------------------------------------------------------------------------- /src/view/system/user/index.vue: -------------------------------------------------------------------------------- 1 | 46 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const resolve = dir => { 4 | return path.join(__dirname, dir) 5 | } 6 | 7 | // 项目部署基础 8 | // 默认情况下,我们假设你的应用将被部署在域的根目录下, 9 | // 例如:https://www.my-app.com/ 10 | // 默认:'/' 11 | // 如果您的应用程序部署在子路径中,则需要在这指定子路径 12 | // 例如:https://www.foobar.com/my-app/ 13 | // 需要将它改为'/my-app/' 14 | const BASE_URL = process.env.NODE_ENV === 'production' 15 | ? '/iview-admin/' 16 | : '/' 17 | 18 | module.exports = { 19 | // Project deployment base 20 | // By default we assume your app will be deployed at the root of a domain, 21 | // e.g. https://www.my-app.com/ 22 | // If your app is deployed at a sub-path, you will need to specify that 23 | // sub-path here. For example, if your app is deployed at 24 | // https://www.foobar.com/my-app/ 25 | // then change this to '/my-app/' 26 | baseUrl: BASE_URL, 27 | // tweak internal webpack configuration. 28 | // see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md 29 | chainWebpack: config => { 30 | config.resolve.alias 31 | .set('@', resolve('src')) // key,value自行定义,比如.set('@@', resolve('src/components')) 32 | .set('_c', resolve('src/components')) 33 | .set('_conf', resolve('config')) 34 | }, 35 | // 打包时不生成.map文件 36 | productionSourceMap: false 37 | // 这里写你调用接口的基础路径,来解决跨域,如果设置了代理,那你本地开发环境的axios的baseUrl要写为 '' ,即空字符串 38 | // devServer: { 39 | // proxy: 'localhost:3000' 40 | // } 41 | } 42 | --------------------------------------------------------------------------------