├── static ├── .gitkeep └── icon │ └── admin-avatar.png ├── .eslintignore ├── favicon.ico ├── src ├── styles │ ├── variables.scss │ ├── mixin.scss │ ├── element-ui.scss │ ├── transition.scss │ ├── index.scss │ ├── sidebar.scss │ └── iconfont │ │ └── iconfont.css ├── assets │ ├── 401_images │ │ └── 401.gif │ └── 404_images │ │ ├── 404.png │ │ └── 404_cloud.png ├── api │ ├── tcp.js │ ├── server.js │ ├── feedbackMsg.js │ ├── login.js │ ├── msgsending.js │ └── data.js ├── views │ ├── layout │ │ ├── components │ │ │ ├── index.js │ │ │ ├── AppMain.vue │ │ │ ├── Sidebar │ │ │ │ ├── index.vue │ │ │ │ └── SidebarItem.vue │ │ │ └── Navbar.vue │ │ ├── mixin │ │ │ └── ResizeHandler.js │ │ └── Layout.vue │ ├── msgsending │ │ ├── testWidget.vue │ │ ├── widget │ │ │ ├── deviceProgressWidget.vue │ │ │ ├── groupProgressWidget.vue │ │ │ └── msgSendingWidget.vue │ │ ├── msgSendingSingleDevice.vue │ │ ├── msgSendingSingleGroup.vue │ │ └── msgSendingOutline.vue │ ├── login │ │ ├── index.vue │ │ └── loginWidget.vue │ ├── tcp │ │ ├── TcpPressureDetail.vue │ │ └── TcpPressureOutline.vue │ ├── page404 │ │ ├── 2.vue │ │ └── 1.vue │ ├── dataprocess │ │ ├── DataProcessPressureDetail.vue │ │ └── DataProcessPressureOutline.vue │ ├── msg │ │ └── FeedbackList.vue │ ├── devicegroup │ │ ├── GroupDeviceOutline.vue │ │ └── SingleGroupOutline.vue │ └── dashboard │ │ └── index.vue ├── App.vue ├── store │ ├── getters.js │ ├── index.js │ └── modules │ │ ├── timer.js │ │ ├── selectedDevice.js │ │ ├── app.js │ │ └── user.js ├── icons │ ├── index.js │ └── svg │ │ ├── table.svg │ │ ├── user.svg │ │ ├── example.svg │ │ ├── password.svg │ │ ├── form.svg │ │ ├── eye.svg │ │ └── tree.svg ├── utils │ ├── auth.js │ ├── kyUtil.js │ ├── timer.js │ ├── validate.js │ ├── index.js │ ├── storeUtil.js │ ├── request.js │ └── chooseElegantSentences.js ├── main.js ├── components │ ├── SvgIcon │ │ └── index.vue │ ├── Breadcrumb │ │ └── index.vue │ ├── ScrollBar │ │ └── index.vue │ └── Hamburger │ │ └── index.vue ├── permission.js └── router │ └── index.js ├── mdphoto ├── main11.jpg ├── main12.jpg ├── main13.jpg ├── main14.jpg └── main19.png ├── config ├── prod.env.js ├── dev.env.js └── index.js ├── .babelrc ├── .postcssrc.js ├── .travis.yml ├── .gitignore ├── .editorconfig ├── index.html ├── LICENSE ├── package.json ├── README.md └── .eslintrc.js /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | src/assets 4 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitkylin/ClusterDeviceControlPlatform-Web/HEAD/favicon.ico -------------------------------------------------------------------------------- /src/styles/variables.scss: -------------------------------------------------------------------------------- 1 | //sidebar 2 | $menuBg:#304156; 3 | $subMenuBg:#1f2d3d; 4 | $menuHover:#001528; 5 | -------------------------------------------------------------------------------- /mdphoto/main11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitkylin/ClusterDeviceControlPlatform-Web/HEAD/mdphoto/main11.jpg -------------------------------------------------------------------------------- /mdphoto/main12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitkylin/ClusterDeviceControlPlatform-Web/HEAD/mdphoto/main12.jpg -------------------------------------------------------------------------------- /mdphoto/main13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitkylin/ClusterDeviceControlPlatform-Web/HEAD/mdphoto/main13.jpg -------------------------------------------------------------------------------- /mdphoto/main14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitkylin/ClusterDeviceControlPlatform-Web/HEAD/mdphoto/main14.jpg -------------------------------------------------------------------------------- /mdphoto/main19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitkylin/ClusterDeviceControlPlatform-Web/HEAD/mdphoto/main19.png -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"', 4 | BASE_API: undefined 5 | } 6 | -------------------------------------------------------------------------------- /static/icon/admin-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitkylin/ClusterDeviceControlPlatform-Web/HEAD/static/icon/admin-avatar.png -------------------------------------------------------------------------------- /src/assets/401_images/401.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitkylin/ClusterDeviceControlPlatform-Web/HEAD/src/assets/401_images/401.gif -------------------------------------------------------------------------------- /src/assets/404_images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitkylin/ClusterDeviceControlPlatform-Web/HEAD/src/assets/404_images/404.png -------------------------------------------------------------------------------- /src/assets/404_images/404_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitkylin/ClusterDeviceControlPlatform-Web/HEAD/src/assets/404_images/404_cloud.png -------------------------------------------------------------------------------- /src/api/tcp.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function tcpOutline() { 4 | return request({ 5 | url: '/tcp/outline', 6 | method: 'get' 7 | }) 8 | } 9 | -------------------------------------------------------------------------------- /src/views/layout/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Navbar } from './Navbar' 2 | export { default as Sidebar } from './Sidebar' 3 | export { default as AppMain } from './AppMain' 4 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"', 7 | BASE_API: '"http://localhost:8080/"' 8 | }) 9 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-vue-jsx", "transform-runtime"] 12 | } 13 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | # nodejs版本 3 | node_js: 4 | - 'node' 5 | # Travis-CI Caching 6 | cache: 7 | directories: 8 | - node_modules 9 | # S: Build Lifecycle 10 | install: 11 | - npm install 12 | before_script: 13 | # 无其他依赖项所以执行npm run build 构建就行了 14 | script: 15 | - npm run build 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | node_modulesWin/ 4 | node_modulesLinux/ 5 | dist/ 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | package-lock.json 10 | 11 | # Editor directories and files 12 | .idea 13 | .vscode 14 | *.suo 15 | *.ntvs* 16 | *.njsproj 17 | *.sln 18 | static/cover/ 19 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://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 | -------------------------------------------------------------------------------- /src/store/getters.js: -------------------------------------------------------------------------------- 1 | const getters = { 2 | sidebar: state => state.app.sidebar, 3 | device: state => state.app.device, 4 | token: state => state.user.token, 5 | avatar: state => state.user.avatar, 6 | name: state => state.user.name, 7 | roles: state => state.user.roles 8 | } 9 | export default getters 10 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 集群设备监控平台 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/api/server.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function serverInfoOutline() { 4 | return request({ 5 | url: '/inner/outline', 6 | method: 'get' 7 | }) 8 | } 9 | 10 | export function serverAttach() { 11 | return request({ 12 | url: '/inner/attach', 13 | method: 'get' 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /src/icons/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import SvgIcon from '@/components/SvgIcon'// svg组件 3 | 4 | // register globally 5 | Vue.component('svg-icon', SvgIcon) 6 | 7 | const requireAll = requireContext => requireContext.keys().map(requireContext) 8 | const req = require.context('./svg', false, /\.svg$/) 9 | requireAll(req) 10 | -------------------------------------------------------------------------------- /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/views/msgsending/testWidget.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | 20 | 23 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import app from './modules/app' 4 | import user from './modules/user' 5 | import getters from './getters' 6 | import timer from './modules/timer' 7 | import selectedDevice from './modules/selectedDevice' 8 | 9 | Vue.use(Vuex) 10 | 11 | const store = new Vuex.Store({ 12 | modules: { 13 | app, 14 | user, 15 | timer, 16 | selectedDevice 17 | }, 18 | getters 19 | }) 20 | 21 | export default store 22 | -------------------------------------------------------------------------------- /src/api/feedbackMsg.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function getFeedbackItemsException() { 4 | return request({ 5 | url: '/feedback/items/exception', 6 | method: 'get' 7 | }) 8 | } 9 | 10 | export function getFeedbackItemsTimeout() { 11 | return request({ 12 | url: '/feedback/items/timeout', 13 | method: 'get' 14 | }) 15 | } 16 | 17 | export function clearFeedbackItems() { 18 | return request({ 19 | url: '/feedback/items/clear', 20 | method: 'get' 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /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 | &::-webkit-scrollbar { 14 | width: 6px; 15 | } 16 | &::-webkit-scrollbar-thumb { 17 | background: #99a9bf; 18 | border-radius: 20px; 19 | } 20 | } 21 | 22 | @mixin relative { 23 | position: relative; 24 | width: 100%; 25 | height: 100%; 26 | } 27 | 28 | -------------------------------------------------------------------------------- /src/icons/svg/table.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/layout/components/AppMain.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 20 | -------------------------------------------------------------------------------- /src/api/login.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function login(username, password) { 4 | return request({ 5 | url: '/user/login', 6 | method: 'post', 7 | data: { 8 | username, 9 | password 10 | } 11 | }) 12 | } 13 | 14 | export function getInfo(token) { 15 | return request({ 16 | url: '/user/info', 17 | method: 'get', 18 | params: { token } 19 | }) 20 | } 21 | 22 | export function logout() { 23 | return request({ 24 | url: '/user/logout', 25 | method: 'post' 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /src/styles/element-ui.scss: -------------------------------------------------------------------------------- 1 | //to reset element-ui default css 2 | .el-upload { 3 | input[type="file"] { 4 | display: none !important; 5 | } 6 | } 7 | 8 | .el-upload__input { 9 | display: none; 10 | } 11 | 12 | //暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461 13 | .el-dialog { 14 | transform: none; 15 | left: 0; 16 | position: relative; 17 | margin: 0 auto; 18 | } 19 | 20 | //element ui upload 21 | .upload-container { 22 | .el-upload { 23 | width: 100%; 24 | .el-upload-dragger { 25 | width: 100%; 26 | height: 200px; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/styles/transition.scss: -------------------------------------------------------------------------------- 1 | //globl 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*/ 15 | .breadcrumb-enter-active, 16 | .breadcrumb-leave-active { 17 | transition: all .5s; 18 | } 19 | 20 | .breadcrumb-enter, 21 | .breadcrumb-leave-active { 22 | opacity: 0; 23 | transform: translateX(20px); 24 | } 25 | 26 | .breadcrumb-move { 27 | transition: all .5s; 28 | } 29 | 30 | .breadcrumb-leave-active { 31 | position: absolute; 32 | } 33 | -------------------------------------------------------------------------------- /src/utils/kyUtil.js: -------------------------------------------------------------------------------- 1 | export function floatToFixed(msgSendingCount, msgCount) { 2 | if (msgCount === 0) { 3 | return 100 4 | } else { 5 | return Math.round((msgCount - msgSendingCount) * 10000 / msgCount) / 100 6 | } 7 | } 8 | 9 | export function countMsgSum(msgCountOld, msgCount) { 10 | if (!msgCountOld) return msgCount 11 | if (msgCount === 0) return 0 12 | if (msgCount > msgCountOld) { 13 | return msgCount 14 | } else { 15 | return msgCountOld 16 | } 17 | } 18 | 19 | // 根据设备号和设备组号生成独立的ID 20 | export function createDeviceFlag(groupId, deviceId, detail) { 21 | return groupId * 1000 + deviceId + detail 22 | } 23 | -------------------------------------------------------------------------------- /src/utils/timer.js: -------------------------------------------------------------------------------- 1 | import store from '../store' 2 | 3 | // 设置定时器,经过指定的时间time循环执行call函数 4 | export function setTimer(call, time) { 5 | store.dispatch('setTimer', { 6 | call: call, 7 | time: time 8 | }) 9 | } 10 | 11 | // 停止定时器 12 | export function stopTimer() { 13 | store.dispatch('stopTimer') 14 | } 15 | 16 | // 通信或发生其他异常,设定10秒后再试 17 | export function touchError(vm, call, error) { 18 | // console.log(store.state.timer.canToastError) 19 | // if (store.state.timer.canToastError) { 20 | // Message.error('错误信息:' + error + '\n请检查网络连接') 21 | // } 22 | // store.commit('enableMsgToast') 23 | stopTimer() 24 | vm.$router.push({ path: '/disconnect' }) 25 | } 26 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | import 'normalize.css/normalize.css'// A modern alternative to CSS resets 4 | 5 | import ElementUI from 'element-ui' 6 | import 'element-ui/lib/theme-chalk/index.css' 7 | import locale from 'element-ui/lib/locale/lang/zh-CN' // lang i18n 8 | 9 | import '@/styles/index.scss' // global css 10 | 11 | import App from './App' 12 | import router from './router' 13 | import store from './store' 14 | 15 | import '@/icons' // icon 16 | import '@/permission' // permission control 17 | 18 | Vue.use(ElementUI, { locale }) 19 | 20 | Vue.config.productionTip = false 21 | 22 | new Vue({ 23 | el: '#app', 24 | router, 25 | store, 26 | template: '', 27 | components: { App } 28 | }) 29 | -------------------------------------------------------------------------------- /src/icons/svg/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/example.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/api/msgsending.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function getMsgSendingByCoordinate(groupId, deviceId, isFlat) { 4 | return request({ 5 | url: '/tcp/msg/sending/' + groupId + '/' + deviceId + '?msgflat=' + isFlat + '&msglimit=500', 6 | method: 'get' 7 | }) 8 | } 9 | 10 | export function getMsgSendingGrouped(groupId, isFlat) { 11 | return request({ 12 | url: '/tcp/msg/sending/' + groupId + '/255?msgflat=' + isFlat + '&msglimit=500', 13 | method: 'get' 14 | }) 15 | } 16 | 17 | export function getMsgSendingOutline(isFlat) { 18 | return request({ 19 | url: '/tcp/msg/sending/255/255?msgflat=' + isFlat + '&msglimit=500', 20 | method: 'get' 21 | }) 22 | } 23 | 24 | export function clearMsgSendingOutline() { 25 | return request({ 26 | url: '/tcp/msg/sending/clear', 27 | method: 'get' 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /src/components/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 33 | 34 | 43 | -------------------------------------------------------------------------------- /src/api/data.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 服务器数据处理模块 4 | export function deviceGroupDataOutline() { 5 | return request({ 6 | url: '/dataprocess/devicegroup/outline', 7 | method: 'get' 8 | }) 9 | } 10 | 11 | export function deviceGroupCount() { 12 | return request({ 13 | url: '/dataprocess/devicegroup/maxgroupcount', 14 | method: 'get' 15 | }) 16 | } 17 | 18 | export function singleGroupDeviceCount() { 19 | return request({ 20 | url: '/dataprocess/devicegroup/maxdevicecount', 21 | method: 'get' 22 | }) 23 | } 24 | 25 | export function singleGroupDeviceData(groupId) { 26 | return request({ 27 | url: '/dataprocess/devicegroup/detail/' + groupId, 28 | method: 'get' 29 | }) 30 | } 31 | 32 | export function dataProcessOutline() { 33 | return request({ 34 | url: '/dataprocess/devicegroup/pressure', 35 | method: 'get' 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /src/views/layout/components/Sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 37 | -------------------------------------------------------------------------------- /src/store/modules/timer.js: -------------------------------------------------------------------------------- 1 | const timer = { 2 | state: { 3 | timerInterval: null, 4 | errorToastTime: 0, 5 | canToastError: true 6 | }, 7 | 8 | mutations: { 9 | setTimerCommit(state, save) { 10 | state.timerInterval = save 11 | }, 12 | stopTimerCommit(state) { 13 | if (state.timerInterval != null) { 14 | clearInterval(state.timerInterval) 15 | } 16 | } 17 | }, 18 | 19 | actions: { 20 | // 设置轮询定时器,整个应用仅维护一个轮询定时器,设置新的定时器时,旧的定时器即被取代 21 | setTimer: ({ commit, state }, timer) => { 22 | if (state.timerInterval != null) { 23 | clearInterval(state.timerInterval) 24 | } 25 | timer.call() 26 | const save = setInterval(timer.call, timer.time) 27 | commit('setTimerCommit', save) 28 | }, 29 | stopTimer({ commit, state }) { 30 | if (state.timerInterval != null) { 31 | clearInterval(state.timerInterval) 32 | } 33 | } 34 | } 35 | } 36 | 37 | export default timer 38 | -------------------------------------------------------------------------------- /src/store/modules/selectedDevice.js: -------------------------------------------------------------------------------- 1 | const selectedDevice = { 2 | state: { 3 | selectedGroupId: 1, 4 | selectedDeviceId: 1, 5 | itemIsFlat: false, 6 | deviceMsgCount: [], 7 | groupMsgCount: [], 8 | outlineMsgCount: 0 9 | }, 10 | 11 | mutations: { 12 | saveGroupId(state, id) { 13 | state.selectedGroupId = id 14 | }, 15 | 16 | saveDeviceId(state, id) { 17 | state.selectedDeviceId = id 18 | }, 19 | 20 | saveItemIsFlat(state, itemIsFlat) { 21 | state.itemIsFlat = itemIsFlat 22 | }, 23 | saveDeviceMsgCount(state, item) { 24 | const groupItem = state.deviceMsgCount[item.groupId] 25 | if (!groupItem) state.deviceMsgCount[item.groupId] = [] 26 | state.deviceMsgCount[item.groupId][item.deviceId] = item.msgCount 27 | }, 28 | saveGroupMsgCount(state, item) { 29 | state.groupMsgCount[item.groupId] = item.msgCount 30 | }, 31 | saveOutlineMsgCount(state, msgCount) { 32 | state.outlineMsgCount = msgCount 33 | } 34 | } 35 | } 36 | 37 | export default selectedDevice 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 123lml123 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 | -------------------------------------------------------------------------------- /src/views/layout/mixin/ResizeHandler.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | 3 | const { body } = document 4 | const WIDTH = 1024 5 | const RATIO = 3 6 | 7 | export default { 8 | watch: { 9 | $route(route) { 10 | if (this.device === 'mobile' && this.sidebar.opened) { 11 | store.dispatch('CloseSideBar', { withoutAnimation: false }) 12 | } 13 | } 14 | }, 15 | beforeMount() { 16 | window.addEventListener('resize', this.resizeHandler) 17 | }, 18 | mounted() { 19 | const isMobile = this.isMobile() 20 | if (isMobile) { 21 | store.dispatch('ToggleDevice', 'mobile') 22 | store.dispatch('CloseSideBar', { withoutAnimation: true }) 23 | } 24 | }, 25 | methods: { 26 | isMobile() { 27 | const rect = body.getBoundingClientRect() 28 | return rect.width - RATIO < WIDTH 29 | }, 30 | resizeHandler() { 31 | if (!document.hidden) { 32 | const isMobile = this.isMobile() 33 | store.dispatch('ToggleDevice', isMobile ? 'mobile' : 'desktop') 34 | 35 | if (isMobile) { 36 | store.dispatch('CloseSideBar', { withoutAnimation: true }) 37 | } 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const app = { 4 | state: { 5 | sidebar: { 6 | opened: !+Cookies.get('sidebarStatus'), 7 | withoutAnimation: false 8 | }, 9 | device: 'desktop' 10 | }, 11 | mutations: { 12 | TOGGLE_SIDEBAR: state => { 13 | if (state.sidebar.opened) { 14 | Cookies.set('sidebarStatus', 1) 15 | } else { 16 | Cookies.set('sidebarStatus', 0) 17 | } 18 | state.sidebar.opened = !state.sidebar.opened 19 | }, 20 | CLOSE_SIDEBAR: (state, withoutAnimation) => { 21 | Cookies.set('sidebarStatus', 1) 22 | state.sidebar.opened = false 23 | state.sidebar.withoutAnimation = withoutAnimation 24 | }, 25 | TOGGLE_DEVICE: (state, device) => { 26 | state.device = device 27 | } 28 | }, 29 | actions: { 30 | ToggleSideBar: ({ commit }) => { 31 | commit('TOGGLE_SIDEBAR') 32 | }, 33 | CloseSideBar({ commit }, { withoutAnimation }) { 34 | commit('CLOSE_SIDEBAR', withoutAnimation) 35 | }, 36 | ToggleDevice({ commit }, device) { 37 | commit('TOGGLE_DEVICE', device) 38 | } 39 | } 40 | } 41 | 42 | export default app 43 | -------------------------------------------------------------------------------- /src/utils/validate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jiachenpan on 16/11/18. 3 | */ 4 | 5 | export function isvalidUsernameOrPassword(str) { 6 | const valid_map = /^[a-z0-9]{1,20}$/i 7 | return valid_map.test(str) 8 | } 9 | 10 | export function isvalidUsername(str) { 11 | const valid_map = ['admin', 'editor'] 12 | return valid_map.indexOf(str.trim()) >= 0 13 | } 14 | 15 | /* 合法uri*/ 16 | export function validateURL(textval) { 17 | const urlregex = /^(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.,?'\\+&%$#=~_-]+))*$/ 18 | return urlregex.test(textval) 19 | } 20 | 21 | /* 小写字母*/ 22 | export function validateLowerCase(str) { 23 | const reg = /^[a-z]+$/ 24 | return reg.test(str) 25 | } 26 | 27 | /* 大写字母*/ 28 | export function validateUpperCase(str) { 29 | const reg = /^[A-Z]+$/ 30 | return reg.test(str) 31 | } 32 | 33 | /* 大小写字母*/ 34 | export function validatAlphabets(str) { 35 | const reg = /^[A-Za-z]+$/ 36 | return reg.test(str) 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/icons/svg/password.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/layout/Layout.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 40 | 41 | 51 | -------------------------------------------------------------------------------- /src/icons/svg/form.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import './variables.scss'; 2 | @import './mixin.scss'; 3 | @import './transition.scss'; 4 | @import './element-ui.scss'; 5 | @import './sidebar.scss'; 6 | @import './iconfont/iconfont.css'; 7 | 8 | body { 9 | -moz-osx-font-smoothing: grayscale; 10 | -webkit-font-smoothing: antialiased; 11 | text-rendering: optimizeLegibility; 12 | //font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; 13 | font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif; 14 | } 15 | 16 | html { 17 | box-sizing: border-box; 18 | } 19 | 20 | *, 21 | *:before, 22 | *:after { 23 | box-sizing: inherit; 24 | } 25 | 26 | div:focus { 27 | outline: none; 28 | } 29 | 30 | a:focus, 31 | a:active { 32 | outline: none; 33 | } 34 | 35 | a, 36 | a:focus, 37 | a:hover { 38 | cursor: pointer; 39 | color: inherit; 40 | text-decoration: none; 41 | } 42 | 43 | .clearfix { 44 | &:after { 45 | visibility: hidden; 46 | display: block; 47 | font-size: 0; 48 | content: " "; 49 | clear: both; 50 | height: 0; 51 | } 52 | } 53 | 54 | //main-container全局样式 55 | .app-main { 56 | min-height: 100% 57 | } 58 | 59 | .app-container { 60 | padding: 20px; 61 | } 62 | -------------------------------------------------------------------------------- /src/permission.js: -------------------------------------------------------------------------------- 1 | import router from './router' 2 | import store from './store' 3 | import NProgress from 'nprogress' // Progress 进度条 4 | import 'nprogress/nprogress.css'// Progress 进度条样式 5 | import { Message } from 'element-ui' 6 | import { getToken } from '@/utils/auth' // 验权 7 | 8 | const whiteList = [ 9 | '/login', 10 | '/404', 11 | '/disconnect', 12 | '/solo/msg/feedbacklist', 13 | '/solo/devicegroup/outline', 14 | '/solo/devicegroup/single' 15 | ] // 不重定向白名单 16 | router.beforeEach((to, from, next) => { 17 | NProgress.start() 18 | if (getToken()) { 19 | if (to.path === '/login') { 20 | next({ path: '/' }) 21 | NProgress.done() // if current page is dashboard will not trigger afterEach hook, so manually handle it 22 | } else { 23 | if (store.getters.roles.length === 0) { 24 | store.dispatch('GetInfo').then(res => { // 拉取用户信息 25 | next() 26 | }).catch((err) => { 27 | store.dispatch('FedLogOut').then(() => { 28 | Message.error(err || '验证失败,请重新登录') 29 | next({ path: '/' }) 30 | }) 31 | }) 32 | } else { 33 | next() 34 | } 35 | } 36 | } else { 37 | if (whiteList.indexOf(to.path) !== -1) { 38 | next() 39 | } else { 40 | next('/login') 41 | NProgress.done() 42 | } 43 | } 44 | }) 45 | 46 | router.afterEach(() => { 47 | NProgress.done() // 结束Progress 48 | }) 49 | -------------------------------------------------------------------------------- /src/components/Breadcrumb/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 43 | 44 | 56 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jiachenpan on 16/11/18. 3 | */ 4 | 5 | export function parseTime(time, cFormat) { 6 | if (arguments.length === 0) { 7 | return null 8 | } 9 | const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}' 10 | let date 11 | if (typeof time === 'object') { 12 | date = time 13 | } else { 14 | if (('' + time).length === 10) time = parseInt(time) * 1000 15 | date = new Date(time) 16 | } 17 | const formatObj = { 18 | y: date.getFullYear(), 19 | m: date.getMonth() + 1, 20 | d: date.getDate(), 21 | h: date.getHours(), 22 | i: date.getMinutes(), 23 | s: date.getSeconds(), 24 | a: date.getDay() 25 | } 26 | const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { 27 | let value = formatObj[key] 28 | if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1] 29 | if (result.length > 0 && value < 10) { 30 | value = '0' + value 31 | } 32 | return value || 0 33 | }) 34 | return time_str 35 | } 36 | 37 | export function formatTime(time, option) { 38 | time = +time * 1000 39 | const d = new Date(time) 40 | const now = Date.now() 41 | 42 | const diff = (now - d) / 1000 43 | 44 | if (diff < 30) { 45 | return '刚刚' 46 | } else if (diff < 3600) { // less 1 hour 47 | return Math.ceil(diff / 60) + '分钟前' 48 | } else if (diff < 3600 * 24) { 49 | return Math.ceil(diff / 3600) + '小时前' 50 | } else if (diff < 3600 * 24 * 2) { 51 | return '1天前' 52 | } 53 | if (option) { 54 | return parseTime(time, option) 55 | } else { 56 | return d.getMonth() + 1 + '月' + d.getDate() + '日' + d.getHours() + '时' + d.getMinutes() + '分' 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/components/ScrollBar/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 43 | 44 | 58 | -------------------------------------------------------------------------------- /src/icons/svg/eye.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/storeUtil.js: -------------------------------------------------------------------------------- 1 | import store from '../store' 2 | 3 | // 保存被选中的 GroupId 4 | export function saveGroupId(id) { 5 | store.commit('saveGroupId', id) 6 | } 7 | 8 | // 保存被选中的 DeviceId 9 | export function saveDeviceId(id) { 10 | store.commit('saveDeviceId', id) 11 | } 12 | 13 | // 保存是否扁平化显示所有消息对象 14 | export function saveItemIsFlat(itemIsFlat) { 15 | store.commit('saveItemIsFlat', itemIsFlat) 16 | } 17 | 18 | // 从 Store 中读取 GroupId 19 | export function getGroupId() { 20 | return store.state.selectedDevice.selectedGroupId 21 | } 22 | 23 | // 从 Store 中读取 DeviceId 24 | export function getDeviceId() { 25 | return store.state.selectedDevice.selectedDeviceId 26 | } 27 | 28 | // 从 Store 中读取「是否扁平化显示所有消息对象」 29 | export function getItemIsFlat() { 30 | return store.state.selectedDevice.itemIsFlat 31 | } 32 | 33 | // 保存单个设备的发送消息总数 34 | export function saveDeviceMsgCount(groupId, deviceId, msgCount) { 35 | store.commit('saveDeviceMsgCount', { groupId: groupId, deviceId: deviceId, msgCount: msgCount }) 36 | } 37 | 38 | // 保存单组的发送消息总数 39 | export function saveGroupMsgCount(groupId, msgCount) { 40 | store.commit('saveGroupMsgCount', { groupId: groupId, msgCount: msgCount }) 41 | } 42 | 43 | // 保存整体的发送消息总数 44 | export function saveOutlineMsgCount(msgCount) { 45 | store.commit('saveOutlineMsgCount', msgCount) 46 | } 47 | 48 | // 从 Store 中读取:单个设备的发送消息总数 49 | export function getDeviceMsgCount(groupId, deviceId) { 50 | const groupItem = store.state.selectedDevice.deviceMsgCount[groupId] 51 | if (groupItem) { 52 | return groupItem[deviceId] 53 | } 54 | } 55 | 56 | // 从 Store 中读取:单个组的发送消息总数 57 | export function getGroupMsgCount(groupId) { 58 | return store.state.selectedDevice.groupMsgCount[groupId] 59 | } 60 | 61 | // 从 Store 中读取:整体的发送消息总数 62 | export function getOutlineMsgCount() { 63 | return store.state.selectedDevice.outlineMsgCount 64 | } 65 | -------------------------------------------------------------------------------- /src/utils/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { Message, MessageBox } from 'element-ui' 3 | import store from '../store' 4 | import { getToken } from '@/utils/auth' 5 | 6 | // 创建axios实例 7 | const service = axios.create({ 8 | baseURL: process.env.BASE_API, // api的base_url 9 | timeout: 3000 // 请求超时时间 10 | }) 11 | 12 | // request拦截器 13 | service.interceptors.request.use(config => { 14 | config.url = '/server' + config.url 15 | if (store.getters.token) { 16 | config.headers['X-Token'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 17 | } 18 | return config 19 | }, error => { 20 | // Do something with request error 21 | console.log(error) // for debug 22 | Promise.reject(error) 23 | }) 24 | 25 | // respone拦截器 26 | service.interceptors.response.use( 27 | response => { 28 | /** 29 | * code为非20000是抛错 可结合自己业务进行修改 30 | */ 31 | const res = response.data 32 | if (res.code !== 20000) { 33 | Message({ 34 | message: res.message, 35 | type: 'error', 36 | duration: 5 * 1000 37 | }) 38 | 39 | // 50008:非法的token; 50012:其他客户端登录了; 50014:Token 过期了; 40 | if (res.code === 50008 || res.code === 50012 || res.code === 50014) { 41 | MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', { 42 | confirmButtonText: '重新登录', 43 | cancelButtonText: '取消', 44 | type: 'warning' 45 | }).then(() => { 46 | store.dispatch('FedLogOut').then(() => { 47 | location.reload()// 为了重新实例化vue-router对象 避免bug 48 | }) 49 | }) 50 | } 51 | return Promise.reject('error') 52 | } else { 53 | return response.data 54 | } 55 | }, 56 | error => { 57 | console.log('err' + error)// for debug 58 | Message({ 59 | message: error.message, 60 | type: 'error', 61 | duration: 5 * 1000 62 | }) 63 | return Promise.reject(error) 64 | } 65 | ) 66 | 67 | export default service 68 | -------------------------------------------------------------------------------- /src/views/msgsending/widget/deviceProgressWidget.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 29 | 30 | 75 | -------------------------------------------------------------------------------- /src/views/msgsending/widget/groupProgressWidget.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 29 | 30 | 75 | -------------------------------------------------------------------------------- /src/components/Hamburger/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 35 | 36 | 51 | -------------------------------------------------------------------------------- /src/icons/svg/tree.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/layout/components/Sidebar/SidebarItem.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 60 | -------------------------------------------------------------------------------- /src/store/modules/user.js: -------------------------------------------------------------------------------- 1 | import { login, logout, getInfo } from '@/api/login' 2 | import { getToken, setToken, removeToken } from '@/utils/auth' 3 | 4 | const user = { 5 | state: { 6 | token: getToken(), 7 | name: '', 8 | avatar: '', 9 | roles: [] 10 | }, 11 | 12 | mutations: { 13 | SET_TOKEN: (state, token) => { 14 | state.token = token 15 | }, 16 | SET_NAME: (state, name) => { 17 | state.name = name 18 | }, 19 | SET_AVATAR: (state, avatar) => { 20 | state.avatar = avatar 21 | }, 22 | SET_ROLES: (state, roles) => { 23 | state.roles = roles 24 | } 25 | }, 26 | 27 | actions: { 28 | // 登录 29 | Login({ commit }, userInfo) { 30 | const username = userInfo.username.trim() 31 | return new Promise((resolve, reject) => { 32 | login(username, userInfo.password).then(response => { 33 | const data = response.data 34 | setToken(data.token) 35 | commit('SET_TOKEN', data.token) 36 | resolve() 37 | }).catch(error => { 38 | reject(error) 39 | }) 40 | }) 41 | }, 42 | 43 | // 获取用户信息 44 | GetInfo({ commit, state }) { 45 | return new Promise((resolve, reject) => { 46 | getInfo(state.token).then(response => { 47 | const data = response.data 48 | console.log('data', data) 49 | if (data.role && data.role.length > 0) { // 验证返回的roles是否是一个非空数组 50 | commit('SET_ROLES', data.role) 51 | } else { 52 | reject('getInfo: roles haha must be a non-null array !') 53 | } 54 | commit('SET_NAME', data.name) 55 | commit('SET_AVATAR', data.avatar) 56 | resolve(response) 57 | }).catch(error => { 58 | reject(error) 59 | }) 60 | }) 61 | }, 62 | 63 | // 登出 64 | LogOut({ commit, state }) { 65 | return new Promise((resolve, reject) => { 66 | logout(state.token).then(() => { 67 | commit('SET_TOKEN', '') 68 | commit('SET_ROLES', []) 69 | removeToken() 70 | resolve() 71 | }).catch(error => { 72 | reject(error) 73 | }) 74 | }) 75 | }, 76 | 77 | // 前端 登出 78 | FedLogOut({ commit }) { 79 | return new Promise(resolve => { 80 | commit('SET_TOKEN', '') 81 | removeToken() 82 | resolve() 83 | }) 84 | } 85 | } 86 | } 87 | 88 | export default user 89 | -------------------------------------------------------------------------------- /src/utils/chooseElegantSentences.js: -------------------------------------------------------------------------------- 1 | const sentences404 = [ 2 | '漫漫人生路,一直在迷路。', 3 | '若来世有归途,但愿爱不迷路。', 4 | '别再自己摸索,问路才不会迷路。', 5 | '旅行是为了迷路,迷路是为了遇上美好。', 6 | '牵着我的手,闭着眼睛走你也不会迷路。', 7 | '迷路不是因为没有方向,而是方向太多。', 8 | '如果你不在乎身处何地,你就不会迷路。', 9 | '在这个世界上,人只需要闭上眼睛,转个向,就会迷路。', 10 | '前方总是会出现一个又一个的十字路口,我已经习惯了迷路。', 11 | '常常告诫自己不要在一棵树上吊死,结果在树林里迷路了。', 12 | '地球之所以是圆的,是因为上帝想让那些走失或者迷路的人能够重新相遇。', 13 | '有时候疯疯癫癫,有时候放空一切,只因这一秒,我在自己的世界里迷路了。'] 14 | const sentencesLogin = [ 15 | { content: '本来无望的事,大胆尝试,往往能成功。', name: '莎士比亚' }, 16 | { content: '最不会利用时间的人,最会抱怨时间不够。', name: '拉布吕耶尔' }, 17 | { content: '对于不屈不挠的人来说,没有失败这回事。', name: '俾斯麦' }, 18 | { content: '骐骥一跃,不能十步;驽马十驾,功以不舍。', name: '荀况' }, 19 | { content: '锲而舍之,朽木不折;锲而不舍,金石可镂。', name: '荀况' }, 20 | { content: '失败也是我需要的,它和成功对我一样有价值。', name: '爱迪生' }, 21 | { content: '天才是百分之一的灵感加上百分之九十九的汗水。', name: '爱迪生' }, 22 | { content: '执着追求并从中得到最大快乐的人,才是成功者。', name: '梭罗' }, 23 | { content: '只要朝着一个方向努力,一切都会变得得心应手。', name: '勃朗宁' }, 24 | { content: '不登高山,不知天之大;不临深谷,不知地之厚也。', name: '荀况' }, 25 | { content: '伟大的人物都走过了荒沙大漠,才登上光荣的高峰。', name: '巴尔扎克' }, 26 | { content: '胜利者往往是从坚持最后五分钟的时间中得来成功。', name: '牛顿' }, 27 | { content: '盛年不重来,一日难再晨。及时当勉励,岁月不待人。', name: '陶渊明' }, 28 | { content: '真正的人生,只有在经过艰难卓绝的斗争之后才能实现。', name: '塞涅卡' }, 29 | { content: '只要持续地努力,不懈地奋斗,就没有征服不了的东西。', name: '塞内加' }, 30 | { content: '古之立大事者,不惟有超世之才,亦必有坚忍不拔之志。', name: '苏轼' }, 31 | { content: '哪里有天才,我是把别人喝咖啡的功夫,都用在工作上的。', name: '鲁迅' }, 32 | { content: '只有把抱怨环境的心情,化为上进的力量,才是成功的保证。', name: '罗曼·罗兰' }, 33 | { content: '让自己的内心藏着一条巨龙,既是一种苦刑,也是一种乐趣。', name: '雨果' }, 34 | { content: '不安于小成,然后足以成大器;不诱于小利,然后可以立远功。', name: '方孝孺' }, 35 | { content: '每一个人要有做一代豪杰的雄心斗志!应当做个开创一代的人。', name: '周恩来' }, 36 | { content: '你热爱生命吗?那么别浪费时间,因为时间是构成生命的材料。', name: '富兰克林' }, 37 | { content: '没有人事先了解自己到底有多大的力量,直到他试过以后才知道。', name: '歌德' }, 38 | { content: '那脑袋里的智慧,就像打火石里的火花一样,不去打它是不肯出来的。', name: '莎士比亚' }, 39 | { content: '君子不镜于水,而镜于人。镜于水,见面之容,镜于人,则知吉与凶。', name: '墨子' }, 40 | { content: '灵感并不是在逻辑思考的延长线上产生,而是在破除逻辑或常识的地方才有灵感。', name: '爱因斯坦' }, 41 | { content: '一个人如果不到最高峰,他就没有片刻的安宁,他也就不会感到生命的恬静和光荣。', name: '肖伯纳' }, 42 | { content: '成功者与失败者之间的区别,常在于成功者能由错误中获益,并以不同的方式再尝试。', name: '爱默生' }, 43 | { content: '人生是一场无休、无歇的战斗,凡是要做个够得上称为人的人,都得时时向无形的敌人作战。', name: '罗曼·罗兰' } 44 | ] 45 | 46 | // 从数组中选取一句优雅的句子 47 | export function chooseElegantSentences404() { 48 | return sentences404[Math.floor(Math.random() * sentences404.length)] 49 | } 50 | 51 | // 从数组中选取一句优雅的句子 52 | export function chooseElegantSentencesLogin() { 53 | return sentencesLogin[Math.floor(Math.random() * sentencesLogin.length)] 54 | } 55 | -------------------------------------------------------------------------------- /src/views/layout/components/Navbar.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 50 | 51 | 92 | 93 | -------------------------------------------------------------------------------- /src/styles/sidebar.scss: -------------------------------------------------------------------------------- 1 | #app { 2 | 3 | // 主体区域 4 | .main-container { 5 | min-height: 100%; 6 | transition: margin-left .28s; 7 | margin-left: 180px; 8 | } 9 | 10 | // 侧边栏 11 | .sidebar-container { 12 | .horizontal-collapse-transition { 13 | transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out; 14 | } 15 | transition: width .28s; 16 | width: 180px !important; 17 | height: 100%; 18 | position: fixed; 19 | font-size: 0px; 20 | top: 0; 21 | bottom: 0; 22 | left: 0; 23 | z-index: 1001; 24 | overflow: hidden; 25 | a { 26 | display: inline-block; 27 | width: 100%; 28 | } 29 | .svg-icon { 30 | margin-right: 16px; 31 | } 32 | .el-menu { 33 | border: none; 34 | width: 100% !important; 35 | } 36 | } 37 | 38 | .hideSidebar { 39 | .sidebar-container { 40 | width: 36px !important; 41 | } 42 | .main-container { 43 | margin-left: 36px; 44 | } 45 | .submenu-title-noDropdown { 46 | padding-left: 10px !important; 47 | position: relative; 48 | .el-tooltip { 49 | padding: 0 10px !important; 50 | } 51 | } 52 | .el-submenu { 53 | &>.el-submenu__title { 54 | padding-left: 10px !important; 55 | &>span { 56 | height: 0; 57 | width: 0; 58 | overflow: hidden; 59 | visibility: hidden; 60 | display: inline-block; 61 | } 62 | .el-submenu__icon-arrow { 63 | display: none; 64 | } 65 | } 66 | } 67 | } 68 | 69 | .sidebar-container .nest-menu .el-submenu>.el-submenu__title, 70 | .sidebar-container .el-submenu .el-menu-item { 71 | min-width: 180px !important; 72 | background-color: $subMenuBg !important; 73 | &:hover { 74 | background-color: $menuHover !important; 75 | } 76 | } 77 | .el-menu--collapse .el-menu .el-submenu { 78 | min-width: 180px !important; 79 | } 80 | 81 | //适配移动端 82 | .mobile { 83 | .main-container { 84 | margin-left: 0px; 85 | } 86 | .sidebar-container { 87 | top: 50px; 88 | transition: transform .28s; 89 | width: 180px !important; 90 | } 91 | &.hideSidebar { 92 | .sidebar-container { 93 | transition-duration: 0.3s; 94 | transform: translate3d(-180px, 0, 0); 95 | } 96 | } 97 | } 98 | 99 | .withoutAnimation { 100 | .main-container, 101 | .sidebar-container { 102 | transition: none; 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/views/msgsending/widget/msgSendingWidget.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 38 | 39 | 98 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cluster-device-platform-web", 3 | "version": "1.11.0", 4 | "license": "MIT", 5 | "description": "Cluster Device Platform for Web", 6 | "author": "bitkylin ", 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "build": "node build/build.js", 11 | "build:report": "npm_config_report=true node build/build.js", 12 | "lint": "eslint --ext .js,.vue src", 13 | "test": "npm run lint" 14 | }, 15 | "dependencies": { 16 | "axios": "^0.18.0", 17 | "echarts": "^4.1.0", 18 | "element-ui": "^2.3.7", 19 | "js-cookie": "^2.2.0", 20 | "normalize.css": "7.0.0", 21 | "nprogress": "0.2.0", 22 | "vue": "^2.5.16", 23 | "vue-router": "^3.0.1", 24 | "vuex": "^3.0.1" 25 | }, 26 | "devDependencies": { 27 | "autoprefixer": "7.2.3", 28 | "babel-core": "6.26.0", 29 | "babel-eslint": "8.0.3", 30 | "babel-helper-vue-jsx-merge-props": "2.0.3", 31 | "babel-loader": "7.1.2", 32 | "babel-plugin-syntax-jsx": "6.18.0", 33 | "babel-plugin-transform-runtime": "6.23.0", 34 | "babel-plugin-transform-vue-jsx": "3.5.0", 35 | "babel-preset-env": "1.6.1", 36 | "babel-preset-stage-2": "6.24.1", 37 | "chalk": "2.3.0", 38 | "copy-webpack-plugin": "4.2.3", 39 | "css-loader": "0.28.7", 40 | "eslint": "4.13.1", 41 | "eslint-friendly-formatter": "3.0.0", 42 | "eslint-loader": "1.9.0", 43 | "eslint-plugin-html": "4.0.1", 44 | "eventsource-polyfill": "0.9.6", 45 | "extract-text-webpack-plugin": "3.0.2", 46 | "file-loader": "1.1.5", 47 | "friendly-errors-webpack-plugin": "1.6.1", 48 | "html-webpack-plugin": "2.30.1", 49 | "node-notifier": "5.1.2", 50 | "node-sass": "^4.7.2", 51 | "optimize-css-assets-webpack-plugin": "3.2.0", 52 | "ora": "1.3.0", 53 | "portfinder": "1.0.13", 54 | "postcss-import": "11.0.0", 55 | "postcss-loader": "2.0.9", 56 | "postcss-url": "7.3.0", 57 | "rimraf": "2.6.2", 58 | "sass-loader": "6.0.6", 59 | "semver": "5.4.1", 60 | "shelljs": "0.7.8", 61 | "svg-sprite-loader": "3.5.2", 62 | "uglifyjs-webpack-plugin": "1.1.3", 63 | "url-loader": "0.6.2", 64 | "vue-loader": "14.2.2", 65 | "vue-style-loader": "^3.0.3", 66 | "vue-template-compiler": "^2.5.16", 67 | "webpack": "3.10.0", 68 | "webpack-bundle-analyzer": "2.9.1", 69 | "webpack-dev-server": "2.11.2", 70 | "webpack-merge": "4.1.1" 71 | }, 72 | "engines": { 73 | "node": ">= 4.0.0", 74 | "npm": ">= 3.0.0" 75 | }, 76 | "browserslist": [ 77 | "> 1%", 78 | "last 2 versions", 79 | "not ie <= 8" 80 | ] 81 | } 82 | -------------------------------------------------------------------------------- /src/views/login/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 66 | 67 | 68 | 88 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.2.6 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | proxyTable: {}, 14 | 15 | // Various Dev Server settings 16 | host: 'localhost', // can be overwritten by process.env.HOST 17 | port: 9000, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 18 | autoOpenBrowser: true, 19 | errorOverlay: true, 20 | notifyOnErrors: false, 21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 22 | 23 | // Use Eslint Loader? 24 | // If true, your code will be linted during bundling and 25 | // linting errors and warnings will be shown in the console. 26 | useEslint: true, 27 | // If true, eslint errors and warnings will also be shown in the error overlay 28 | // in the browser. 29 | showEslintErrorsInOverlay: false, 30 | 31 | /** 32 | * Source Maps 33 | */ 34 | 35 | // https://webpack.js.org/configuration/devtool/#development 36 | devtool: 'cheap-source-map', 37 | 38 | // If you have problems debugging vue-files in devtools, 39 | // set this to false - it *may* help 40 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 41 | cacheBusting: true, 42 | 43 | // CSS Sourcemaps off by default because relative paths are "buggy" 44 | // with this option, according to the CSS-Loader README 45 | // (https://github.com/webpack/css-loader#sourcemaps) 46 | // In our experience, they generally work as expected, 47 | // just be aware of this issue when enabling this option. 48 | cssSourceMap: false, 49 | }, 50 | 51 | build: { 52 | // Template for index.html 53 | index: path.resolve(__dirname, '../dist/index.html'), 54 | 55 | // Paths 56 | assetsRoot: path.resolve(__dirname, '../dist'), 57 | assetsSubDirectory: 'static', 58 | 59 | // you can set by youself according to actual condition 60 | assetsPublicPath: './', 61 | 62 | /** 63 | * Source Maps 64 | */ 65 | 66 | productionSourceMap: false, 67 | // https://webpack.js.org/configuration/devtool/#production 68 | devtool: '#source-map', 69 | 70 | // Gzip off by default as many popular static hosts such as 71 | // Surge or Netlify already gzip all static assets for you. 72 | // Before setting to `true`, make sure to: 73 | // npm install --save-dev compression-webpack-plugin 74 | productionGzip: false, 75 | productionGzipExtensions: ['js', 'css'], 76 | 77 | // Run the build command with an extra argument to 78 | // View the bundle analyzer report after build finishes: 79 | // `npm run build --report` 80 | // Set to `true` or `false` to always turn it on or off 81 | bundleAnalyzerReport: process.env.npm_config_report 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/views/tcp/TcpPressureDetail.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 122 | 123 | 128 | -------------------------------------------------------------------------------- /src/views/page404/2.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 88 | 89 | 128 | -------------------------------------------------------------------------------- /src/views/dataprocess/DataProcessPressureDetail.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 125 | 126 | 131 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 基于 Vue.js 2.0 & Element 2.0 的集群设备管理云平台 2 | 3 | [![GitHub stars](https://img.shields.io/github/stars/bitkylin/ClusterDeviceControlPlatform-Web.svg)](https://github.com/bitkylin/ClusterDeviceControlPlatform-Web/stargazers) 4 | [![Build Status](https://travis-ci.org/bitkylin/ClusterDeviceControlPlatform-Web.svg?branch=master)](https://travis-ci.org/bitkylin/ClusterDeviceControlPlatform-Web) 5 | [![Dependency Status](https://david-dm.org/bitkylin/ClusterDeviceControlPlatform-Web.svg)](https://david-dm.org/bitkylin/ClusterDeviceControlPlatform-Web) 6 | [![许可证](https://img.shields.io/badge/许可证-MIT-blue.svg)](https://github.com/bitkylin/ClusterDeviceControlPlatform-Web/blob/master/LICENSE) 7 | ![技术](https://img.shields.io/badge/%E6%8A%80%E6%9C%AF-Vue.js_2.0%7CElement_2.0%7CRESTful_API-brightgreen.svg) 8 | 9 | ## 项目描述 10 | 11 | Java & Vue.js 全栈项目,大规模集群设备管理云平台,由以下几部分组成:**Java 后端服务器、基于 Vue.js 的 Web 前端「SPA」单页应用程序、JavaFX 集群设备模拟客户端、辅助工具组件「Python 脚本、基于 C# & WPF 的可视化配置工具」**,简要介绍如下: 12 | 13 | - **Java 后端服务器**:使用 Spring 作为基础框架,使用 Netty 搭建 TCP 服务器与**上万台设备**组成的集群通信,采用自定义帧格式。 14 | 15 | - **基于 Vue.js 的 Web 前端「SPA」单页应用程序**:绚丽的现代化 SPA 应用程序,可视化展现服务器内部的各项数据,包括服务器消息队列、通信压力、实时通信信息等。 16 | 17 | - **JavaFX 集群设备模拟客户端**:使用基于 JavaFX 的图形界面应用程序模拟上万台设备的行为,并可对服务器进行压力测试。 18 | 19 | - **辅助工具组件**:Python 编写的数据库初始化及测试脚本、C# 编写的基于 WPF 的可视化服务器配置工具。 20 | 21 | **注意:[本项目为系统的 Web 部分,系统后端部分为单独的项目,点击此处跳转](https://github.com/bitkylin/ClusterDeviceControlPlatform)** 22 | 23 | ## 运行方法 24 | 25 | 进入[配套的 Java 后端项目](https://github.com/bitkylin/ClusterDeviceControlPlatform),根据该项目的说明,即可成功执行工程「clusterdeviceplatform-demo」,而后构建本项目并运行即可,方法如下: 26 | 27 | 下载并进入该仓库的根目录,在命令行下,安装依赖: 28 | 29 | ```shell 30 | npm install 31 | ``` 32 | 33 | 依赖安装完毕后,执行如下命令进行调试: 34 | 35 | ```shell 36 | npm run start 37 | ``` 38 | 39 | 或执行如下命令进行构建: 40 | 41 | ```shell 42 | npm run build 43 | ``` 44 | 45 | **当然,以上运行说明并不完善,详细的构建方法及方便的一键构建脚本,可参考如下两篇文章:** 46 | 47 | 1. [使用 Linux 子系统部署 Node、Gradle 项目的构建工具](https://www.jianshu.com/p/f34d1f2e329c) 48 | 49 | 2. [Windows 10 用于 Linux 子系统的一键构建、打包脚本「 Node、Gradle 项目」](https://www.jianshu.com/p/6c78f35e228e) 50 | 51 | ## 项目图示 52 | 53 | 特别提示:如果无法查看后面的图片,可能需要科学上网。 54 | 55 | ### 1. 基于 Vue.js 的 Web 前端「SPA」单页应用程序 56 | 57 | **Web 登录页面** 58 | 59 | ![Web 登录页面](./mdphoto/main11.jpg) 60 | 61 | **设备组细节概览图示** 62 | 63 | ![设备组细节概览图示](./mdphoto/main14.jpg) 64 | 65 | **单设备组详细信息显示** 66 | 67 | ![单设备组详细信息显示](./mdphoto/main12.jpg) 68 | 69 | **服务器压力图示** 70 | 71 | ![服务器压力图示](./mdphoto/main13.jpg) 72 | 73 | 74 | 75 | ### 系统架构图示 76 | 77 | ![系统架构图示](./mdphoto/main19.png) 78 | 79 | ## 附录 80 | 81 | **本项目以[ vueAdmin-template ](https://github.com/PanJiaChen/vueAdmin-template)项目为基础框架开发而成,特别感谢该项目的作者!** 82 | 83 | 绚丽的现代化 SPA 应用程序,可视化展现服务器内部的各项数据,包括服务器消息队列、通信压力、实时通信信息等。 84 | 85 | 该应用开发时,设计经验总结可见以下文章: 86 | 87 | [基于 Vue.js 2.0 酷炫自适应背景视频登录页面的设计](https://www.jianshu.com/p/8097bb3d9d49) 88 | 89 | 文章会不时进行更新。 90 | 91 | ## [License](https://github.com/bitkylin/ClusterDeviceControlPlatform-Web/blob/master/LICENSE) 92 | 93 | > MIT License 94 | > 95 | > Copyright (c) 2018 123lml123 96 | > 97 | > Permission is hereby granted, free of charge, to any person obtaining a copy 98 | > of this software and associated documentation files (the "Software"), to deal 99 | > in the Software without restriction, including without limitation the rights 100 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 101 | > copies of the Software, and to permit persons to whom the Software is 102 | > furnished to do so, subject to the following conditions: 103 | > 104 | > The above copyright notice and this permission notice shall be included in all 105 | > copies or substantial portions of the Software. 106 | > 107 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 108 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 109 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 110 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 111 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 112 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 113 | > SOFTWARE. 114 | 115 | ## 关于我 116 | 117 | ### 1. 我的主页 118 | 119 | 名称|域名|原始地址 120 | ---|---|--- 121 | 主页|http://bitky.cc|https://bitkylin.github.io 122 | GitHub|http://github.bitky.cc|https://github.com/bitkylin 123 | 简书|http://js.bitky.cc|http://www.jianshu.com/u/bd2e386a6ea8 124 | 知乎|http://zhi.bitky.cc|https://www.zhihu.com/people/bitkylin 125 | 126 | 127 | ### 2. 其他 128 | 129 | - 兴趣方向: Java, Web, Android, Vue.js, C#, JavaScript, Node.js, Kotlin 等 130 | 131 | - Email: bitkylin@163.com 132 | -------------------------------------------------------------------------------- /src/views/msgsending/msgSendingSingleDevice.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 83 | 84 | 147 | -------------------------------------------------------------------------------- /src/views/msg/FeedbackList.vue: -------------------------------------------------------------------------------- 1 | 74 | 75 | 159 | 160 | 176 | -------------------------------------------------------------------------------- /src/styles/iconfont/iconfont.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face {font-family: "iconfont"; 3 | src: url('//at.alicdn.com/t/font_525177_qbplve16attymn29.eot?t=1525270083378'); /* IE9*/ 4 | src: url('//at.alicdn.com/t/font_525177_qbplve16attymn29.eot?t=1525270083378#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('data:application/x-font-woff;charset=utf-8;base64,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') format('woff'), 6 | url('//at.alicdn.com/t/font_525177_qbplve16attymn29.ttf?t=1525270083378') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 7 | url('//at.alicdn.com/t/font_525177_qbplve16attymn29.svg?t=1525270083378#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-mima:before { content: "\e6d1"; } 19 | 20 | .icon-xianshimima:before { content: "\e662"; } 21 | 22 | .icon-icon311:before { content: "\e6ab"; } 23 | 24 | .icon-cuowu:before { content: "\e627"; } 25 | 26 | .icon-Ankerwebicon-:before { content: "\e682"; } 27 | 28 | .icon-laodonggaizao:before { content: "\e601"; } 29 | 30 | .icon-shenfenxinxi:before { content: "\e62c"; } 31 | 32 | .icon-yincangmima:before { content: "\e6ad"; } 33 | 34 | .icon-yonghuming:before { content: "\e8fd"; } 35 | 36 | .icon-tupian-:before { content: "\e67b"; } 37 | 38 | .icon-xinxi:before { content: "\e618"; } 39 | 40 | .icon-kongxianzhong:before { content: "\e647"; } 41 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | // in development-env not use lazy-loading, because lazy-loading too many pages will cause webpack hot update too slow. so only in production use lazy-loading; 5 | // detail: https://panjiachen.github.io/vue-element-admin-site/#/lazy-loading 6 | 7 | Vue.use(Router) 8 | 9 | /* Layout */ 10 | import Layout from '../views/layout/Layout' 11 | 12 | /** 13 | * hidden: true if `hidden:true` will not show in the sidebar(default is false) 14 | * alwaysShow: true if set true, will always show the root menu, whatever its child routes length 15 | * if not set alwaysShow, only more than one route under the children 16 | * it will becomes nested mode, otherwise not show the root menu 17 | * redirect: noredirect if `redirect:noredirect` will no redirct in the breadcrumb 18 | * name:'router-name' the name is used by (must set!!!) 19 | * meta : { 20 | title: 'title' the name show in submenu and breadcrumb (recommend set) 21 | icon: 'svg-name' the icon show in the sidebar, 22 | } 23 | **/ 24 | export const constantRouterMap = [ 25 | { 26 | path: '/login', 27 | component: () => import('@/views/login/index'), 28 | hidden: true 29 | }, { 30 | path: '/404', 31 | component: () => import('@/views/page404/1'), 32 | hidden: true 33 | }, { 34 | path: '/disconnect', 35 | component: () => import('@/views/page404/2'), 36 | hidden: true 37 | }, { 38 | path: '/solo/msg/feedbacklist', 39 | component: () => import('@/views/msg/FeedbackList'), 40 | hidden: true 41 | }, { 42 | path: '/solo/devicegroup/outline', 43 | component: () => import('@/views/devicegroup/GroupDeviceOutline'), 44 | hidden: true 45 | }, { 46 | path: '/solo/devicegroup/single', 47 | component: () => import('@/views/devicegroup/SingleGroupOutline'), 48 | hidden: true 49 | }, { 50 | path: '', 51 | component: Layout, 52 | redirect: 'dashboard', 53 | name: 'dashboardTop', 54 | meta: { title: '', icon: 'example' }, 55 | children: [{ 56 | name: 'dashboard', 57 | path: 'dashboard', 58 | component: () => import('@/views/dashboard/index'), 59 | meta: { title: '首页', icon: 'table' } 60 | }] 61 | }, { 62 | path: '/devicegroup', 63 | component: Layout, 64 | redirect: '/devicegroup/outline', 65 | name: 'deviceGroup', 66 | meta: { title: '设备当前状态', icon: 'example' }, 67 | children: [{ 68 | path: 'outline', 69 | name: 'groupDeviceOutline', 70 | component: () => import('@/views/devicegroup/GroupDeviceOutline'), 71 | meta: { title: '概览', icon: 'table' } 72 | }, { 73 | path: 'single', 74 | name: 'singleDeviceOutline', 75 | component: () => import('@/views/devicegroup/SingleGroupOutline'), 76 | meta: { title: '单组', icon: 'table' } 77 | }] 78 | }, { 79 | path: '/dataprocess', 80 | component: Layout, 81 | redirect: '/dataprocess/outline', 82 | name: 'dataprocess', 83 | meta: { title: '数据处理负载', icon: 'example' }, 84 | children: [{ 85 | path: 'outline', 86 | name: 'dataProcessPressureOutline', 87 | component: () => import('@/views/dataprocess/DataProcessPressureOutline'), 88 | meta: { title: '概览', icon: 'table' } 89 | }, { 90 | path: 'single', 91 | name: 'dataProcessPressureDetail', 92 | component: () => import('@/views/dataprocess/DataProcessPressureDetail'), 93 | meta: { title: '详情', icon: 'table' } 94 | }] 95 | }, { 96 | path: '/tcp', 97 | component: Layout, 98 | redirect: '/tcp/outline', 99 | name: 'tcp', 100 | meta: { title: 'TCP接口负载', icon: 'example' }, 101 | children: [{ 102 | path: 'outline', 103 | name: 'tcpPressureOutline', 104 | component: () => import('@/views/tcp/TcpPressureOutline'), 105 | meta: { title: '概览', icon: 'table' } 106 | }, { 107 | path: 'detail', 108 | name: 'tcpPressureDetail', 109 | component: () => import('@/views/tcp/TcpPressureDetail'), 110 | meta: { title: '详情', icon: 'table' } 111 | }] 112 | }, { 113 | path: '/msgsending', 114 | component: Layout, 115 | redirect: '/msgsending/outline', 116 | name: 'msgsending', 117 | meta: { title: '消息发送进度', icon: 'example' }, 118 | children: [{ 119 | path: 'outline', 120 | name: 'msgSendingOutline', 121 | component: () => import('@/views/msgsending/msgSendingOutline'), 122 | meta: { title: '概览', icon: 'table' } 123 | }, { 124 | path: 'singlegroup', 125 | name: 'msgSendingSingleGroup', 126 | component: () => import('@/views/msgsending/msgSendingSingleGroup'), 127 | meta: { title: '单组', icon: 'table' } 128 | }, { 129 | path: 'singledevice', 130 | name: 'msgSendingSingleDevice', 131 | component: () => import('@/views/msgsending/msgSendingSingleDevice'), 132 | meta: { title: '单设备', icon: 'table' } 133 | }] 134 | }, { 135 | path: '/msg', 136 | component: Layout, 137 | redirect: '/msg/feedbacklist', 138 | name: 'msg', 139 | hidden: true, 140 | meta: { title: '消息', icon: 'example' }, 141 | children: [{ 142 | name: 'feedbackList', 143 | path: 'feedbacklist', 144 | component: () => import('@/views/msg/FeedbackList'), 145 | meta: { title: '反馈消息', icon: 'table' } 146 | }] 147 | }, { 148 | path: '*', 149 | redirect: '/404', 150 | hidden: true 151 | } 152 | ] 153 | 154 | export default new Router({ 155 | // mode: 'history', //后端支持可开 156 | scrollBehavior: () => ({ y: 0 }), 157 | routes: constantRouterMap 158 | }) 159 | 160 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | sourceType: 'module' 6 | }, 7 | env: { 8 | browser: true, 9 | node: true, 10 | es6: true, 11 | }, 12 | extends: 'eslint:recommended', 13 | // required to lint *.vue files 14 | plugins: [ 15 | 'html' 16 | ], 17 | // check if imports actually resolve 18 | 'settings': { 19 | 'import/resolver': { 20 | 'webpack': { 21 | 'config': 'build/webpack.base.conf.js' 22 | } 23 | } 24 | }, 25 | // add your custom rules here 26 | //it is base on https://github.com/vuejs/eslint-config-vue 27 | rules: { 28 | 'accessor-pairs': 2, 29 | 'arrow-spacing': [2, { 30 | 'before': true, 31 | 'after': true 32 | }], 33 | 'block-spacing': [2, 'always'], 34 | 'brace-style': [2, '1tbs', { 35 | 'allowSingleLine': true 36 | }], 37 | 'camelcase': [0, { 38 | 'properties': 'always' 39 | }], 40 | 'comma-dangle': [2, 'never'], 41 | 'comma-spacing': [2, { 42 | 'before': false, 43 | 'after': true 44 | }], 45 | 'comma-style': [2, 'last'], 46 | 'constructor-super': 2, 47 | 'curly': [2, 'multi-line'], 48 | 'dot-location': [2, 'property'], 49 | 'eol-last': 2, 50 | 'eqeqeq': [2, 'allow-null'], 51 | 'generator-star-spacing': [2, { 52 | 'before': true, 53 | 'after': true 54 | }], 55 | 'handle-callback-err': [2, '^(err|error)$'], 56 | 'indent': [2, 2, { 57 | 'SwitchCase': 1 58 | }], 59 | 'jsx-quotes': [2, 'prefer-single'], 60 | 'key-spacing': [2, { 61 | 'beforeColon': false, 62 | 'afterColon': true 63 | }], 64 | 'keyword-spacing': [2, { 65 | 'before': true, 66 | 'after': true 67 | }], 68 | 'new-cap': [2, { 69 | 'newIsCap': true, 70 | 'capIsNew': false 71 | }], 72 | 'new-parens': 2, 73 | 'no-array-constructor': 2, 74 | 'no-caller': 2, 75 | 'no-console': 'off', 76 | 'no-class-assign': 2, 77 | 'no-cond-assign': 2, 78 | 'no-const-assign': 2, 79 | 'no-control-regex': 2, 80 | 'no-delete-var': 2, 81 | 'no-dupe-args': 2, 82 | 'no-dupe-class-members': 2, 83 | 'no-dupe-keys': 2, 84 | 'no-duplicate-case': 2, 85 | 'no-empty-character-class': 2, 86 | 'no-empty-pattern': 2, 87 | 'no-eval': 2, 88 | 'no-ex-assign': 2, 89 | 'no-extend-native': 2, 90 | 'no-extra-bind': 2, 91 | 'no-extra-boolean-cast': 2, 92 | 'no-extra-parens': [2, 'functions'], 93 | 'no-fallthrough': 2, 94 | 'no-floating-decimal': 2, 95 | 'no-func-assign': 2, 96 | 'no-implied-eval': 2, 97 | 'no-inner-declarations': [2, 'functions'], 98 | 'no-invalid-regexp': 2, 99 | 'no-irregular-whitespace': 2, 100 | 'no-iterator': 2, 101 | 'no-label-var': 2, 102 | 'no-labels': [2, { 103 | 'allowLoop': false, 104 | 'allowSwitch': false 105 | }], 106 | 'no-lone-blocks': 2, 107 | 'no-mixed-spaces-and-tabs': 2, 108 | 'no-multi-spaces': 2, 109 | 'no-multi-str': 2, 110 | 'no-multiple-empty-lines': [2, { 111 | 'max': 1 112 | }], 113 | 'no-native-reassign': 2, 114 | 'no-negated-in-lhs': 2, 115 | 'no-new-object': 2, 116 | 'no-new-require': 2, 117 | 'no-new-symbol': 2, 118 | 'no-new-wrappers': 2, 119 | 'no-obj-calls': 2, 120 | 'no-octal': 2, 121 | 'no-octal-escape': 2, 122 | 'no-path-concat': 2, 123 | 'no-proto': 2, 124 | 'no-redeclare': 2, 125 | 'no-regex-spaces': 2, 126 | 'no-return-assign': [2, 'except-parens'], 127 | 'no-self-assign': 2, 128 | 'no-self-compare': 2, 129 | 'no-sequences': 2, 130 | 'no-shadow-restricted-names': 2, 131 | 'no-spaced-func': 2, 132 | 'no-sparse-arrays': 2, 133 | 'no-this-before-super': 2, 134 | 'no-throw-literal': 2, 135 | 'no-trailing-spaces': 2, 136 | 'no-undef': 2, 137 | 'no-undef-init': 2, 138 | 'no-unexpected-multiline': 2, 139 | 'no-unmodified-loop-condition': 2, 140 | 'no-unneeded-ternary': [2, { 141 | 'defaultAssignment': false 142 | }], 143 | 'no-unreachable': 2, 144 | 'no-unsafe-finally': 2, 145 | 'no-unused-vars': [2, { 146 | 'vars': 'all', 147 | 'args': 'none' 148 | }], 149 | 'no-useless-call': 2, 150 | 'no-useless-computed-key': 2, 151 | 'no-useless-constructor': 2, 152 | 'no-useless-escape': 0, 153 | 'no-whitespace-before-property': 2, 154 | 'no-with': 2, 155 | 'one-var': [2, { 156 | 'initialized': 'never' 157 | }], 158 | 'operator-linebreak': [2, 'after', { 159 | 'overrides': { 160 | '?': 'before', 161 | ':': 'before' 162 | } 163 | }], 164 | 'padded-blocks': [2, 'never'], 165 | 'quotes': [2, 'single', { 166 | 'avoidEscape': true, 167 | 'allowTemplateLiterals': true 168 | }], 169 | 'semi': [2, 'never'], 170 | 'semi-spacing': [2, { 171 | 'before': false, 172 | 'after': true 173 | }], 174 | 'space-before-blocks': [2, 'always'], 175 | 'space-before-function-paren': [2, 'never'], 176 | 'space-in-parens': [2, 'never'], 177 | 'space-infix-ops': 2, 178 | 'space-unary-ops': [2, { 179 | 'words': true, 180 | 'nonwords': false 181 | }], 182 | 'spaced-comment': [2, 'always', { 183 | 'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ','] 184 | }], 185 | 'template-curly-spacing': [2, 'never'], 186 | 'use-isnan': 2, 187 | 'valid-typeof': 2, 188 | 'wrap-iife': [2, 'any'], 189 | 'yield-star-spacing': [2, 'both'], 190 | 'yoda': [2, 'never'], 191 | 'prefer-const': 2, 192 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 193 | 'object-curly-spacing': [2, 'always', { 194 | objectsInObjects: false 195 | }], 196 | 'array-bracket-spacing': [2, 'never'] 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/views/login/loginWidget.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 45 | 136 | 137 | 193 | -------------------------------------------------------------------------------- /src/views/msgsending/msgSendingSingleGroup.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 130 | 131 | 194 | -------------------------------------------------------------------------------- /src/views/msgsending/msgSendingOutline.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 135 | 136 | 199 | -------------------------------------------------------------------------------- /src/views/page404/1.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 51 | 52 | 239 | -------------------------------------------------------------------------------- /src/views/devicegroup/GroupDeviceOutline.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 213 | 214 | 246 | -------------------------------------------------------------------------------- /src/views/devicegroup/SingleGroupOutline.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 171 | 172 | 305 | -------------------------------------------------------------------------------- /src/views/tcp/TcpPressureOutline.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 291 | 292 | 371 | -------------------------------------------------------------------------------- /src/views/dataprocess/DataProcessPressureOutline.vue: -------------------------------------------------------------------------------- 1 | 65 | 66 | 303 | 304 | 378 | -------------------------------------------------------------------------------- /src/views/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 257 | 258 | 356 | 357 | 467 | --------------------------------------------------------------------------------