网关管理模块开发
|
@ -22,6 +22,7 @@
|
|||
"html-loader": "^4.2.0",
|
||||
"js-cookie": "2.2.0",
|
||||
"markdown-loader": "^8.0.0",
|
||||
"mqtt": "^2.18.9",
|
||||
"normalize.css": "7.0.0",
|
||||
"nprogress": "0.2.0",
|
||||
"path-to-regexp": "2.4.0",
|
||||
|
|
|
@ -5,7 +5,226 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import {
|
||||
getGatewayList,
|
||||
updateGateway,
|
||||
updateDevice,
|
||||
deviceOffline,
|
||||
gatewayOffline
|
||||
} from '@/api/gateway/manage'
|
||||
import mqtt from 'mqtt'
|
||||
|
||||
export default {
|
||||
name: 'App'
|
||||
name: 'App',
|
||||
data() {
|
||||
return {
|
||||
connection: {
|
||||
protocol: 'ws',
|
||||
host: '8.140.53.225',
|
||||
// ws: 8083; wss: 8084
|
||||
port: 8083,
|
||||
endpoint: '/mqtt',
|
||||
// for more options, please refer to https://github.com/mqttjs/MQTT.js#mqttclientstreambuilder-options
|
||||
clean: true,
|
||||
connectTimeout: 30 * 1000, // ms
|
||||
reconnectPeriod: 4000, // ms
|
||||
clientId: 'emqx_vue',
|
||||
// auth
|
||||
username: 'xiuos',
|
||||
password: 'xiuos'
|
||||
},
|
||||
subscription: [
|
||||
{
|
||||
topic: '$SYS/brokers/+/clients/+/#',
|
||||
qos: 0
|
||||
},
|
||||
{
|
||||
topic: 'gateway',
|
||||
qos: 0
|
||||
},
|
||||
{
|
||||
topic: 'deviceStatus',
|
||||
qos: 0
|
||||
}
|
||||
],
|
||||
publish: {
|
||||
topic: 'topic/browser',
|
||||
qos: 0,
|
||||
payload: '{ "msg": "Hello, I am browser." }'
|
||||
},
|
||||
qosList: [0, 1, 2],
|
||||
subscribeSuccess: false,
|
||||
connecting: false,
|
||||
retryTimes: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['client', 'gatewayList', 'token'])
|
||||
},
|
||||
mounted() {
|
||||
this.createConnection()
|
||||
this.doSubscribe()
|
||||
this.doPublish()
|
||||
},
|
||||
methods: {
|
||||
formatDate(value) {
|
||||
var date = new Date(value)
|
||||
var y = date.getFullYear()
|
||||
var m = date.getMonth() + 1
|
||||
var d = date.getDate()
|
||||
var h = date.getHours()
|
||||
var i = date.getMinutes()
|
||||
var s = date.getSeconds()
|
||||
if (m < 10) { m = '0' + m }
|
||||
if (d < 10) { d = '0' + d }
|
||||
if (h < 10) { h = '0' + h }
|
||||
if (i < 10) { i = '0' + i }
|
||||
if (s < 10) { s = '0' + s }
|
||||
var t = y + '-' + m + '-' + d + ' ' + h + ':' + i + ':' + s
|
||||
return t
|
||||
},
|
||||
initData() {
|
||||
this.client = {
|
||||
connected: false
|
||||
}
|
||||
this.retryTimes = 0
|
||||
this.connecting = false
|
||||
this.subscribeSuccess = false
|
||||
},
|
||||
handleOnReConnect() {
|
||||
this.retryTimes += 1
|
||||
if (this.retryTimes > 5) {
|
||||
try {
|
||||
this.client.end()
|
||||
this.initData()
|
||||
this.$message.error('Connection maxReconnectTimes limit, stop retry')
|
||||
} catch (error) {
|
||||
this.$message.error(error.toString())
|
||||
}
|
||||
}
|
||||
},
|
||||
createConnection() {
|
||||
try {
|
||||
this.connecting = true
|
||||
const { protocol, host, port, endpoint, ...options } = this.connection
|
||||
const connectUrl = `${protocol}://${host}:${port}${endpoint}`
|
||||
// this.client = mqtt.connect(connectUrl, options)
|
||||
const temp = mqtt.connect(connectUrl, options)
|
||||
this.$store.dispatch('app/setClient', temp)
|
||||
// this.$myContent.setClient(temp)
|
||||
if (this.client.on) {
|
||||
this.client.on('connect', () => {
|
||||
this.connecting = false
|
||||
console.log('Connection succeeded!')
|
||||
})
|
||||
this.client.on('reconnect', this.handleOnReConnect)
|
||||
this.client.on('message', (topic, message) => {
|
||||
console.log(`Received message ${message} from topic ${topic}`)
|
||||
const res = JSON.parse(String().concat(message))
|
||||
const regxConnected = /\$SYS\/brokers\/.+\/connected/
|
||||
const regxDisconnected = /\$SYS\/brokers\/.+\/disconnected/
|
||||
if (this.token === 'success') {
|
||||
console.log('success')
|
||||
if (regxDisconnected.test(topic)) {
|
||||
console.log('topic disconnected')
|
||||
if (
|
||||
this.gatewayList.some((gateway) => gateway.gatewayid === res.clientid)
|
||||
) {
|
||||
updateGateway({ status: 2, gatewayid: res.clientid }).then(
|
||||
(res) => {
|
||||
this.getGatewayList()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
if (regxConnected.test(topic)) {
|
||||
console.log('topic connected')
|
||||
if (
|
||||
this.gatewayList.some((gateway) => gateway.gatewayid === res.clientid)
|
||||
) {
|
||||
const lastConnect = this.formatDate(res.connected_at)
|
||||
updateGateway({
|
||||
status: 1,
|
||||
gatewayid: res.clientid,
|
||||
lastConnect
|
||||
}).then((res) => {
|
||||
this.getGatewayList()
|
||||
})
|
||||
}
|
||||
}
|
||||
if (topic === 'gateway') {
|
||||
console.log('gateway')
|
||||
if (
|
||||
this.gatewayList.some((gateway) => gateway.gatewayid === res.gatewayId)
|
||||
) {
|
||||
res.deviceList.forEach((device) => {
|
||||
const { devEui, dataType, value } = device
|
||||
const content = JSON.stringify({ dataType, value })
|
||||
localStorage.setItem(devEui, content)
|
||||
})
|
||||
}
|
||||
}
|
||||
if (topic === 'deviceStatus') {
|
||||
console.log('deviceStatus')
|
||||
res.deviceList.forEach((device) => {
|
||||
updateDevice(device).then()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
this.client.on('error', (error) => {
|
||||
console.log('Connection failed', error)
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
this.connecting = false
|
||||
console.log('mqtt.connect error', error)
|
||||
}
|
||||
},
|
||||
doSubscribe() {
|
||||
this.subscription.forEach((subscription) => {
|
||||
const { topic, qos } = subscription
|
||||
this.client.subscribe(topic, { qos }, (error, res) => {
|
||||
if (error) {
|
||||
console.log(`Subscribe to topic ${topic} error`, error)
|
||||
return
|
||||
}
|
||||
this.subscribeSuccess = true
|
||||
console.log(`Subscribe to topic ${topic} res`, res)
|
||||
})
|
||||
})
|
||||
},
|
||||
doUnSubscribe() {
|
||||
this.subscription.forEach((subscription) => {
|
||||
const { topic } = subscription
|
||||
this.client.unsubscribe(topic, (error) => {
|
||||
if (error) {
|
||||
console.log('Unsubscribe error', error)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
doPublish() {
|
||||
const { topic, qos, payload } = this.publish
|
||||
this.client.publish(topic, payload, { qos }, (error) => {
|
||||
if (error) {
|
||||
console.log('Publish error', error)
|
||||
}
|
||||
})
|
||||
},
|
||||
destroyConnection() {
|
||||
if (this.client.connected) {
|
||||
try {
|
||||
this.client.end(false, () => {
|
||||
this.initData()
|
||||
console.log('Successfully disconnected!')
|
||||
})
|
||||
} catch (error) {
|
||||
console.log('Disconnect failed', error.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
export function getDeviceStatus(data) {
|
||||
return request({
|
||||
url: '/loraDevice/getStatus',
|
||||
method: 'get',
|
||||
params: data || {}
|
||||
})
|
||||
}
|
||||
|
||||
export function getGatewayStatus(data) {
|
||||
return request({
|
||||
url: '/loraGateway/getStatus',
|
||||
method: 'get',
|
||||
params: data || {}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
export function getList(data) {
|
||||
return request({
|
||||
url: '/loraDevice/select',
|
||||
method: 'get',
|
||||
params: data || {}
|
||||
})
|
||||
}
|
||||
|
||||
export function add(data) {
|
||||
return request({
|
||||
url: '/loraDevice/add',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function remove(data) {
|
||||
return request({
|
||||
url: '/loraDevice/delete',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
export function getGatewayList(data) {
|
||||
return request({
|
||||
url: '/loraGateway/select',
|
||||
method: 'get',
|
||||
params: data || {}
|
||||
})
|
||||
}
|
||||
|
||||
export function getDeviceList(data) {
|
||||
return request({
|
||||
url: '/loraDevice/select',
|
||||
method: 'get',
|
||||
params: data || {}
|
||||
})
|
||||
}
|
||||
|
||||
export function add(data) {
|
||||
return request({
|
||||
url: '/loraGateway/add',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateGateway(data) {
|
||||
return request({
|
||||
url: '/loraGateway/update',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateDevice(data) {
|
||||
return request({
|
||||
url: '/loraDevice/update',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function remove(data) {
|
||||
return request({
|
||||
url: '/loraGateway/delete',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function deviceOffline(data) {
|
||||
return request({
|
||||
url: '/loraDevice/offline',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function gatewayOffline(data) {
|
||||
return request({
|
||||
url: '/loraGateway/offline',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
After Width: | Height: | Size: 529 B |
After Width: | Height: | Size: 557 B |
After Width: | Height: | Size: 156 B |
After Width: | Height: | Size: 228 B |
After Width: | Height: | Size: 380 B |
After Width: | Height: | Size: 468 B |
After Width: | Height: | Size: 355 B |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 9.1 KiB |
After Width: | Height: | Size: 9.4 KiB |
After Width: | Height: | Size: 591 B |
After Width: | Height: | Size: 9.9 KiB |
After Width: | Height: | Size: 733 B |
After Width: | Height: | Size: 732 B |
After Width: | Height: | Size: 733 B |
After Width: | Height: | Size: 739 B |
|
@ -48,7 +48,22 @@ export default {
|
|||
}
|
||||
},
|
||||
showBreadcrumb() {
|
||||
const whiteList = ['overview', 'terminal/add', 'terminal/plc', 'terminal/stock', 'terminal/ota', 'data/value', 'configuration/development', 'dashboard', 'log/accessLog', 'log/systemLog', 'rule/manage', 'rule/dataTransfer']
|
||||
const whiteList = [
|
||||
'overview',
|
||||
'terminal/add',
|
||||
'terminal/plc',
|
||||
'terminal/stock',
|
||||
'terminal/ota',
|
||||
'data/value',
|
||||
'configuration/development',
|
||||
'dashboard',
|
||||
'log/accessLog',
|
||||
'log/systemLog',
|
||||
'rule/manage',
|
||||
'rule/dataTransfer',
|
||||
'gateway/device',
|
||||
'gateway/manage'
|
||||
]
|
||||
return whiteList.every((item) => this.$route.path.indexOf(item) === -1)
|
||||
}
|
||||
},
|
||||
|
|
|
@ -157,7 +157,7 @@ export const asyncRouter = [
|
|||
path: '',
|
||||
name: 'Dashboard',
|
||||
component: () => import('@/views/dashboard/index'),
|
||||
meta: { title: '设备看板', icon: 'dashboard', permission: ['dashengda', 'hangxiao', 'qianjiangdianqi'] }
|
||||
meta: { title: '看板', icon: 'dashboard', permission: ['dashengda', 'hangxiao', 'qianjiangdianqi'] }
|
||||
}]
|
||||
},
|
||||
{
|
||||
|
@ -229,6 +229,32 @@ export const asyncRouter = [
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/gateway',
|
||||
component: Layout,
|
||||
name: 'Gateway',
|
||||
meta: { title: '网关管理', icon: 'rule', permission: ['dashengda', 'hangxiao', 'qianjiangdianqi'] },
|
||||
children: [
|
||||
{
|
||||
path: 'dashboard',
|
||||
name: 'GatewayDashboard',
|
||||
component: () => import('@/views/gateway/dashboard/index'),
|
||||
meta: { title: '看板' }
|
||||
},
|
||||
{
|
||||
path: 'device',
|
||||
name: 'GatewayDevice',
|
||||
component: () => import('@/views/gateway/device/index'),
|
||||
meta: { title: '设备节点' }
|
||||
},
|
||||
{
|
||||
path: 'manage',
|
||||
name: 'GatewayManage',
|
||||
component: () => import('@/views/gateway/manage/index'),
|
||||
meta: { title: '网关' }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/rule',
|
||||
component: Layout,
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
const getters = {
|
||||
sidebar: state => state.app.sidebar,
|
||||
device: state => state.app.device,
|
||||
client: state => state.app.client,
|
||||
gatewayList: state => state.app.gatewayList,
|
||||
token: state => state.user.token,
|
||||
avatar: state => state.user.avatar,
|
||||
name: state => state.user.name,
|
||||
|
|
|
@ -5,7 +5,11 @@ const state = {
|
|||
opened: true,
|
||||
withoutAnimation: false
|
||||
},
|
||||
device: 'desktop'
|
||||
device: 'desktop',
|
||||
client: {
|
||||
connected: false
|
||||
},
|
||||
gatewayList: []
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
|
@ -25,6 +29,12 @@ const mutations = {
|
|||
},
|
||||
TOGGLE_DEVICE: (state, device) => {
|
||||
state.device = device
|
||||
},
|
||||
SET_CLIENT: (state, client) => {
|
||||
state.client = client
|
||||
},
|
||||
SET_GATEWAY_LIST: (state, gatewayList) => {
|
||||
state.gatewayList = gatewayList
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,6 +47,12 @@ const actions = {
|
|||
},
|
||||
toggleDevice({ commit }, device) {
|
||||
commit('TOGGLE_DEVICE', device)
|
||||
},
|
||||
setClient({ commit }, client) {
|
||||
commit('SET_CLIENT', client)
|
||||
},
|
||||
setGatewayList({ commit }, gatewayList) {
|
||||
commit('SET_GATEWAY_LIST', gatewayList)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* @Author: 龚祖望 573413756@qq.com
|
||||
* @Date: 2022-05-16 09:16:41
|
||||
* @LastEditors: 龚祖望 573413756@qq.com
|
||||
* @LastEditTime: 2022-11-17 11:15:19
|
||||
* @LastEditTime: 2023-11-23 15:07:17
|
||||
* @FilePath: \dashengda\src\store\modules\user.js
|
||||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||
*/
|
||||
|
@ -56,12 +56,12 @@ const mutations = {
|
|||
RESET_STATE: (state) => {
|
||||
Object.assign(state, getDefaultState())
|
||||
},
|
||||
SET_TOKEN: (state, name) => {
|
||||
state.name = name
|
||||
},
|
||||
SET_NAME: (state, token) => {
|
||||
SET_TOKEN: (state, token) => {
|
||||
state.token = token
|
||||
},
|
||||
SET_NAME: (state, name) => {
|
||||
state.name = name
|
||||
},
|
||||
SET_AVATAR: (state, avatar) => {
|
||||
state.avatar = avatar
|
||||
},
|
||||
|
|
|
@ -0,0 +1,255 @@
|
|||
<template>
|
||||
<div class="app-container" style="min-height: calc(100vh - 50px)">
|
||||
<el-row
|
||||
type="flex"
|
||||
justify="space-between"
|
||||
style="align-items: baseline; margin-bottom: 30px"
|
||||
>
|
||||
<div class="header">
|
||||
<h1 class="title">看板</h1>
|
||||
<span class="date">{{ date }}</span>
|
||||
</div>
|
||||
</el-row>
|
||||
<div class="box_container">
|
||||
<div class="chart_container">
|
||||
<div class="title bg_purple">设备节点</div>
|
||||
<div class="chart" />
|
||||
</div>
|
||||
<div class="divider" />
|
||||
<div class="chart_container">
|
||||
<div class="title bg_blue">网关</div>
|
||||
<div class="chart" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getDeviceStatus, getGatewayStatus } from '@/api/gateway/dashboard'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
date: '',
|
||||
myCharts: [],
|
||||
options: [
|
||||
{
|
||||
grid: {
|
||||
left: 0,
|
||||
right: 0
|
||||
},
|
||||
legend: {
|
||||
bottom: '10%',
|
||||
left: 'center',
|
||||
itemGap: 50
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
center: ['50%', '40%'],
|
||||
radius: ['30%', '50%'],
|
||||
clockwise: true,
|
||||
avoidLabelOverlap: true,
|
||||
color: ['#20BE0B', '#EF6F59', '#789AF3'],
|
||||
label: {
|
||||
show: true,
|
||||
position: 'outside',
|
||||
formatter: '{a|{b}:{c}}\n',
|
||||
rich: {
|
||||
a: {
|
||||
padding: [0, 0, -20, 0],
|
||||
fontSize: 14
|
||||
}
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
normal: {
|
||||
length: 10,
|
||||
length2: 10,
|
||||
lineStyle: {
|
||||
width: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
scaleSize: 10
|
||||
},
|
||||
data: [
|
||||
{
|
||||
name: '在线',
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
name: '离线',
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
name: '从未连接',
|
||||
value: 3
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
grid: {
|
||||
left: 0,
|
||||
right: 0
|
||||
},
|
||||
legend: {
|
||||
bottom: '10%',
|
||||
left: 'center',
|
||||
itemGap: 50
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
center: ['50%', '40%'],
|
||||
radius: ['30%', '50%'],
|
||||
clockwise: true,
|
||||
avoidLabelOverlap: true,
|
||||
color: ['#20BE0B', '#EF6F59', '#5CDBE5'],
|
||||
label: {
|
||||
show: true,
|
||||
position: 'outside',
|
||||
formatter: '{a|{b}:{c}}\n',
|
||||
rich: {
|
||||
a: {
|
||||
padding: [0, 0, -20, 0],
|
||||
fontSize: 14
|
||||
}
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
normal: {
|
||||
length: 10,
|
||||
length2: 10,
|
||||
lineStyle: {
|
||||
width: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
scaleSize: 10
|
||||
},
|
||||
data: [
|
||||
{
|
||||
name: '在线',
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
name: '离线',
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
name: '从未连接',
|
||||
value: 3
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.date = new Date().toLocaleDateString()
|
||||
const doms = document.getElementsByClassName('chart')
|
||||
Array.prototype.forEach.call(doms, (dom) => {
|
||||
this.myCharts.push(this.$echarts.init(dom))
|
||||
})
|
||||
this.getSum()
|
||||
window.addEventListener('resize', () => {
|
||||
this.myCharts.forEach(chart => { chart.resize() })
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
async getSum() {
|
||||
const device = await getDeviceStatus()
|
||||
const gateway = await getGatewayStatus()
|
||||
this.options[0].series[0].data[0].value = device.data.filter(item => item.status === 1)[0]?.count || 0
|
||||
this.options[0].series[0].data[1].value = device.data.filter(item => item.status === 2)[0]?.count || 0
|
||||
this.options[0].series[0].data[2].value = device.data.filter(item => item.status === 0)[0]?.count || 0
|
||||
this.options[1].series[0].data[0].value = gateway.data.filter(item => item.status === 1)[0]?.count || 0
|
||||
this.options[1].series[0].data[1].value = gateway.data.filter(item => item.status === 2)[0]?.count || 0
|
||||
this.options[1].series[0].data[2].value = gateway.data.filter(item => item.status === 0)[0]?.count || 0
|
||||
this.myCharts.forEach((chart, index) => {
|
||||
chart.setOption(this.options[index])
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.header {
|
||||
font-family: Lato;
|
||||
.title {
|
||||
color: #174a84;
|
||||
}
|
||||
.date {
|
||||
color: #a5c9ff;
|
||||
}
|
||||
}
|
||||
.box_container {
|
||||
background: #fff;
|
||||
width: 100%;
|
||||
box-shadow: rgba(0, 0, 0, 0.1) 0px 20px 25px -5px,
|
||||
rgba(0, 0, 0, 0.04) 0px 10px 10px -5px;
|
||||
padding: 20px 40px 70px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
.chart_container {
|
||||
width: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
.title {
|
||||
width: 90%;
|
||||
height: 100px;
|
||||
line-height:100px;
|
||||
text-align:center;
|
||||
color: #fff;
|
||||
font-size: 36px;
|
||||
font-family: Microsoft YaHei;
|
||||
font-weight: bold;
|
||||
}
|
||||
.chart {
|
||||
width: 100%;
|
||||
aspect-ratio: 1 / 0.6;
|
||||
}
|
||||
.legend {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
}
|
||||
.bg_purple {
|
||||
border: 2px solid transparent;
|
||||
background: linear-gradient(#789af3, #789af3) padding-box,
|
||||
repeating-linear-gradient(
|
||||
45deg,
|
||||
#789af3 0,
|
||||
#789af3 12px,
|
||||
#fff 12px,
|
||||
#fff 24px
|
||||
);
|
||||
}
|
||||
.bg_blue {
|
||||
border: 2px solid transparent;
|
||||
background: linear-gradient(#00ccf2, #00ccf2) padding-box,
|
||||
repeating-linear-gradient(
|
||||
45deg,
|
||||
#00ccf2 0,
|
||||
#00ccf2 12px,
|
||||
#fff 12px,
|
||||
#fff 24px
|
||||
);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,632 @@
|
|||
<template>
|
||||
<div class="app-container">
|
||||
<el-row
|
||||
type="flex"
|
||||
justify="space-between"
|
||||
style="align-items: baseline; margin-bottom: 30px"
|
||||
>
|
||||
<div class="header">
|
||||
<h1 class="title">设备节点</h1>
|
||||
<span class="date">{{ date }}</span>
|
||||
<span class="sum">{{ "共" + deviceSum + "个设备节点" }}</span>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-button
|
||||
class="add_btn"
|
||||
icon="el-icon-plus"
|
||||
@click="visible = true"
|
||||
>新增节点</el-button>
|
||||
</el-row>
|
||||
<div class="list_container">
|
||||
<div class="title_box">
|
||||
<p />
|
||||
<p>节点名称</p>
|
||||
<p>终端ID</p>
|
||||
<p>所属网关ID</p>
|
||||
<p>支持OTAA</p>
|
||||
<p>支持Class-C</p>
|
||||
<i />
|
||||
</div>
|
||||
<div v-for="(item, index) in list" :key="index" class="item_box">
|
||||
<img src="@/assets/images/geteway_device.png">
|
||||
<p>{{ item.name }}</p>
|
||||
<p>
|
||||
<a @click="showDataDialog(item.devEui, item.status)">{{
|
||||
item.devEui
|
||||
}}</a>
|
||||
</p>
|
||||
<p>{{ item.gatewayid }}</p>
|
||||
<p>{{ item.supportOtaa ? "是" : "否" }}</p>
|
||||
<p>{{ item.supportClassc ? "是" : "否" }}</p>
|
||||
<i
|
||||
type="danger"
|
||||
class="remove_icon el-icon-delete"
|
||||
@click="remove(item.name, item.devEui)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 设备采集数据弹窗 -->
|
||||
<div v-show="dataVisible" class="dialog_wrapper">
|
||||
<div class="dialog-container">
|
||||
<div class="dialog_header">
|
||||
<p class="ip">设备采集数据</p>
|
||||
<div class="remove_icon" @click="close">X</div>
|
||||
</div>
|
||||
<div class="dialog_content">
|
||||
<div class="title" style="margin-top: -40px">
|
||||
<img src="@/assets/images/gateway_device_status.png">
|
||||
<p class="label">设备在线状态</p>
|
||||
<p class="data">{{ transformStatus(currentDevice.status) }}</p>
|
||||
</div>
|
||||
<div class="line" />
|
||||
<div v-if="currentDevice.dataType === 1">
|
||||
<div class="title">
|
||||
<img src="@/assets/images/gateway_device_data_type.png">
|
||||
<p class="label">采集数据类型</p>
|
||||
<p class="data">{{ transformType(currentDevice.dataType) }}</p>
|
||||
</div>
|
||||
<div class="chart">
|
||||
<img src="@/assets/images/gateway_device_pm1.png">
|
||||
<p class="data_center">{{ currentDevice.value }}</p>
|
||||
<p class="unit">单位:ug/m³</p>
|
||||
</div>
|
||||
<p class="name blue">PM1.0</p>
|
||||
</div>
|
||||
<div v-else-if="currentDevice.dataType === 2">
|
||||
<div class="title">
|
||||
<img src="@/assets/images/gateway_device_data_type.png">
|
||||
<p class="label">采集数据类型</p>
|
||||
<p class="data">{{ transformType(currentDevice.dataType) }}</p>
|
||||
</div>
|
||||
<div class="chart">
|
||||
<img src="@/assets/images/gateway_device_pm2_5.png">
|
||||
<p class="data_center">{{ currentDevice.value }}</p>
|
||||
<p class="unit">单位:ug/m³</p>
|
||||
</div>
|
||||
<p class="name green">PM2.5</p>
|
||||
</div>
|
||||
<div v-else-if="currentDevice.dataType === 3">
|
||||
<div class="title">
|
||||
<img src="@/assets/images/gateway_device_data_type.png">
|
||||
<p class="label">采集数据类型</p>
|
||||
<p class="data">{{ transformType(currentDevice.dataType) }}</p>
|
||||
</div>
|
||||
<div class="chart">
|
||||
<img src="@/assets/images/gateway_device_pm10.png">
|
||||
<p class="data_center">{{ currentDevice.value }}</p>
|
||||
<p class="unit">单位:ug/m³</p>
|
||||
</div>
|
||||
<p class="name orange">PM10</p>
|
||||
</div>
|
||||
<div v-else-if="currentDevice.dataType === 4">
|
||||
<div class="title">
|
||||
<img src="@/assets/images/gateway_device_data_type.png">
|
||||
<p class="label">采集数据类型</p>
|
||||
<p class="data">{{ transformType(currentDevice.dataType) }}</p>
|
||||
</div>
|
||||
<div class="chart">
|
||||
<img src="@/assets/images/gateway_device_temperature.png">
|
||||
<p class="data">{{ currentDevice.value }}</p>
|
||||
<p class="label">当前温度</p>
|
||||
<p class="unit">单位:℃</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="currentDevice.dataType === 5">
|
||||
<div class="title">
|
||||
<img src="@/assets/images/gateway_device_data_type.png">
|
||||
<p class="label">采集数据类型</p>
|
||||
<p class="data">{{ transformType(currentDevice.dataType) }}</p>
|
||||
</div>
|
||||
<div class="chart">
|
||||
<img src="@/assets/images/gateway_device_humidity.png">
|
||||
<p class="data">{{ currentDevice.value }}</p>
|
||||
<p class="label">当前湿度</p>
|
||||
<p class="unit">单位:%RH</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div style="height: 200px; line-height: 200px; text-align: center">
|
||||
暂无数据
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 设备新增弹窗 -->
|
||||
<div v-show="visible" class="dialog_wrapper">
|
||||
<div class="dialog-container">
|
||||
<div class="dialog_header">
|
||||
<p class="ip">设备节点新增</p>
|
||||
<div class="remove_icon" @click="close">X</div>
|
||||
</div>
|
||||
<div class="dialog_content">
|
||||
<div class="form">
|
||||
<div class="form_content">
|
||||
<div class="item_title">
|
||||
<img src="@/assets/images/device_name.png">名称
|
||||
</div>
|
||||
<div class="item_content">
|
||||
<el-input v-model="form.name" />
|
||||
</div>
|
||||
<div class="item_title">
|
||||
<img src="@/assets/images/device_devEUI.png">终端<br>ID
|
||||
</div>
|
||||
<div class="item_content">
|
||||
<el-input v-model="form.devEui" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="line" />
|
||||
<div class="form_content">
|
||||
<div class="item_title">
|
||||
<img src="@/assets/images/device_joinEUI.png">所属网<br>关ID
|
||||
</div>
|
||||
<div class="item_content">
|
||||
<el-input v-model="form.gatewayid" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="line" />
|
||||
<div class="form_content">
|
||||
<div class="item_title">
|
||||
<img src="@/assets/images/device_otaa.png">支持<br>OTAA
|
||||
</div>
|
||||
<div class="item_content">
|
||||
<el-switch v-model="form.supportOtaa" active-color="#00CCF2" />
|
||||
</div>
|
||||
<div class="item_title">
|
||||
<img src="@/assets/images/device_classc.png">支持<br>Class-C
|
||||
</div>
|
||||
<div class="item_content">
|
||||
<el-switch
|
||||
v-model="form.supportClassc"
|
||||
active-color="#00CCF2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line" />
|
||||
</div>
|
||||
<div class="footer">
|
||||
<el-button
|
||||
size="medium"
|
||||
class="cancel_btn"
|
||||
@click="close"
|
||||
>取消</el-button>
|
||||
<el-button
|
||||
size="medium"
|
||||
class="save_btn"
|
||||
style="border-radius: 4px"
|
||||
type="primary"
|
||||
@click="save"
|
||||
>保存</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getList, add, remove } from '@/api/gateway/device'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
date: '',
|
||||
deviceSum: 0,
|
||||
list: [
|
||||
{
|
||||
name: '11',
|
||||
devEui: '12345689',
|
||||
gatewayid: '123',
|
||||
supportOtaa: true,
|
||||
supportClassc: false
|
||||
}
|
||||
],
|
||||
visible: false,
|
||||
dataVisible: false,
|
||||
form: {
|
||||
name: '',
|
||||
devEui: '',
|
||||
gatewayid: '',
|
||||
supportOtaa: true,
|
||||
supportClassc: true
|
||||
},
|
||||
currentDevice: {},
|
||||
interval: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.date = new Date().toLocaleDateString()
|
||||
this.getList()
|
||||
// localStorage.setItem('A00001', JSON.stringify({ dataType: 4, value: 25 }))
|
||||
// localStorage.setItem('A00002', JSON.stringify({ dataType: 5, value: 63 }))
|
||||
// localStorage.setItem('A00004', JSON.stringify({ dataType: 1, value: 25 }))
|
||||
// localStorage.setItem('A00005', JSON.stringify({ dataType: 2, value: 63 }))
|
||||
// localStorage.setItem('A00006', JSON.stringify({ dataType: 3, value: 63 }))
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
getList({ gatewayid: '' }).then((res) => {
|
||||
this.list = res.data
|
||||
this.deviceSum = this.list.length
|
||||
})
|
||||
},
|
||||
close() {
|
||||
this.visible = false
|
||||
this.dataVisible = false
|
||||
clearInterval(this.interval)
|
||||
},
|
||||
remove(name, devEui) {
|
||||
this.$confirm(`确认删除设备节点${name}?`, '提示', { type: 'warning' })
|
||||
.then(() => {
|
||||
remove({ devEui }).then((res) => {
|
||||
this.$message.success('删除成功')
|
||||
this.getList()
|
||||
})
|
||||
})
|
||||
.catch()
|
||||
},
|
||||
save() {
|
||||
console.log('form', this.form)
|
||||
add({ ...this.form, status: 0 }).then((res) => {
|
||||
this.$message.success('新增成功')
|
||||
this.getList()
|
||||
this.close()
|
||||
})
|
||||
},
|
||||
showDataDialog(devEui, status) {
|
||||
this.dataVisible = true
|
||||
this.getDeviceData(devEui, status)
|
||||
this.interval = setInterval(() => {
|
||||
this.getDeviceData(devEui, status)
|
||||
}, 1000)
|
||||
},
|
||||
getDeviceData(devEui, status) {
|
||||
const data = localStorage.getItem(devEui)
|
||||
? JSON.parse(localStorage.getItem(devEui))
|
||||
: {}
|
||||
this.currentDevice = {
|
||||
status,
|
||||
...data
|
||||
}
|
||||
},
|
||||
transformStatus(status) {
|
||||
switch (status) {
|
||||
case 0:
|
||||
return '从未连接'
|
||||
case 1:
|
||||
return '在线'
|
||||
case 2:
|
||||
return '离线'
|
||||
}
|
||||
},
|
||||
transformType(type) {
|
||||
switch (type) {
|
||||
case 1:
|
||||
return 'PM1.0'
|
||||
case 2:
|
||||
return 'PM2.5'
|
||||
case 3:
|
||||
return 'PM10'
|
||||
case 4:
|
||||
return '温度'
|
||||
case 5:
|
||||
return '湿度'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.header {
|
||||
font-family: Lato;
|
||||
.title {
|
||||
color: #174a84;
|
||||
}
|
||||
.date {
|
||||
color: #a5c9ff;
|
||||
}
|
||||
.sum {
|
||||
margin-left: 85px;
|
||||
color: #a5c9ff;
|
||||
}
|
||||
}
|
||||
.add_btn {
|
||||
width: 100%;
|
||||
color: #fff;
|
||||
border: 2px solid transparent;
|
||||
background: linear-gradient(#789af3, #789af3) padding-box,
|
||||
repeating-linear-gradient(
|
||||
45deg,
|
||||
#789af3 0,
|
||||
#789af3 12px,
|
||||
#fff 12px,
|
||||
#fff 24px
|
||||
);
|
||||
}
|
||||
.list_container {
|
||||
width: 100%;
|
||||
margin-top: 30px;
|
||||
.title_box {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
margin: 10px auto;
|
||||
display: inline-grid;
|
||||
grid-template-columns:
|
||||
80px minmax(105px, 1fr) 2fr 2fr 2fr minmax(130px, 2fr) minmax(130px, 2fr)
|
||||
100px;
|
||||
justify-items: center;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
p {
|
||||
line-height: 40px;
|
||||
font-size: 26px;
|
||||
font-family: Microsoft YaHei;
|
||||
color: #2e4765;
|
||||
}
|
||||
}
|
||||
.item_box {
|
||||
border: 1px solid rgba(0, 204, 242, 0.2);
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(0, 204, 242, 0.2) 0%,
|
||||
rgba(150, 230, 161, 0.2) 100%
|
||||
);
|
||||
border-radius: 10px;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
margin: 5px auto;
|
||||
display: inline-grid;
|
||||
grid-template-columns:
|
||||
80px 1fr 2fr 2fr 2fr minmax(130px, 2fr) minmax(130px, 2fr)
|
||||
100px;
|
||||
justify-items: center;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
img {
|
||||
width: 45px;
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
p {
|
||||
line-height: 100px;
|
||||
font-size: 26px;
|
||||
font-family: Microsoft YaHei;
|
||||
color: #2e4765;
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
.remove_icon {
|
||||
font-size: 26px;
|
||||
color: #f56c6c;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
.dialog_wrapper {
|
||||
z-index: 2;
|
||||
position: fixed;
|
||||
left: 230px;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: 0;
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -230px;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
z-index: 2;
|
||||
}
|
||||
.dialog-container {
|
||||
z-index: 10;
|
||||
width: 600px;
|
||||
transform: none;
|
||||
margin: 15vh auto;
|
||||
position: relative;
|
||||
border-radius: 8px;
|
||||
border: 2px solid #00ccf2;
|
||||
font-family: Microsoft YaHei;
|
||||
box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.16);
|
||||
.dialog_header {
|
||||
background-color: #00ccf2;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
padding: 5px;
|
||||
position: relative;
|
||||
p {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin: 5px auto;
|
||||
}
|
||||
.remove_icon {
|
||||
position: absolute;
|
||||
font-size: 18px;
|
||||
right: 15px;
|
||||
top: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.dialog_content {
|
||||
background-color: #fff;
|
||||
border-radius: 0 0 8px 8px;
|
||||
padding: 40px 0 20px;
|
||||
.form {
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
.form_content {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
div {
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
img {
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
.item_title {
|
||||
text-align: center;
|
||||
background: #c7f4fc;
|
||||
width: 110px;
|
||||
min-height: 90px;
|
||||
font-size: 20px;
|
||||
color: #00ccf2;
|
||||
}
|
||||
.item_content {
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
padding: 9px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
img {
|
||||
width: 18px;
|
||||
height: 22px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
p {
|
||||
font-size: 23px;
|
||||
font-family: Microsoft YaHei;
|
||||
}
|
||||
.label {
|
||||
color: #00ccf2;
|
||||
}
|
||||
.data {
|
||||
color: #5f6874;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
.line {
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-image: linear-gradient(
|
||||
to right,
|
||||
#00ccf2 0%,
|
||||
#00ccf2 50%,
|
||||
transparent 50%
|
||||
);
|
||||
background-size: 18px 100%;
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.chart {
|
||||
margin: auto;
|
||||
width: 180px;
|
||||
aspect-ratio: 1 / 1;
|
||||
position: relative;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
.data {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 0;
|
||||
transform: translateX(-50%);
|
||||
font-size: 45px;
|
||||
font-family: DINCond-Medium;
|
||||
color: #358caa;
|
||||
line-height: 63px;
|
||||
}
|
||||
.label {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 45px;
|
||||
transform: translateX(-50%);
|
||||
font-size: 25px;
|
||||
font-family: Microsoft YaHei;
|
||||
font-weight: bold;
|
||||
color: #358caa;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.unit {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
transform: translateX(100%);
|
||||
font-size: 15px;
|
||||
font-family: Segoe UI;
|
||||
color: #358caa;
|
||||
}
|
||||
.data_center {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 69px;
|
||||
font-family: DINCond-Medium;
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
.name {
|
||||
font-size: 35px;
|
||||
font-family: MicrosoftYaHei;
|
||||
line-height: 44px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
.orange {
|
||||
color: #ff4e00;
|
||||
}
|
||||
.green {
|
||||
color: #20be0b;
|
||||
}
|
||||
.blue {
|
||||
color: #00ccf2;
|
||||
}
|
||||
}
|
||||
|
||||
// .radio {
|
||||
// ::v-deep .el-radio-button__inner {
|
||||
// color: #8f59eb;
|
||||
// border-color: #8f59eb;
|
||||
// }
|
||||
// ::v-deep .el-radio-button__orig-radio:checked + .el-radio-button__inner {
|
||||
// background-color: #8f59eb;
|
||||
// color: #fff;
|
||||
// }
|
||||
// ::v-deep .el-radio-button:first-child .el-radio-button__inner {
|
||||
// border-radius: 9px 0 0 9px;
|
||||
// }
|
||||
// ::v-deep .el-radio-button:last-child .el-radio-button__inner {
|
||||
// border-radius: 0 9px 9px 0;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
.footer {
|
||||
text-align: center;
|
||||
margin: 40px auto 20px;
|
||||
.cancel_btn {
|
||||
border-color: #00ccf2;
|
||||
color: #00ccf2;
|
||||
&:hover {
|
||||
background-color: rgba(0, 204, 242, 0.1);
|
||||
}
|
||||
}
|
||||
.save_btn {
|
||||
border-color: #00ccf2;
|
||||
background-color: #00ccf2;
|
||||
border-radius: 2px;
|
||||
&:hover {
|
||||
background-color: #00ccf2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
::v-deep .el-input > .el-input__inner {
|
||||
border: none;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,553 @@
|
|||
<template>
|
||||
<div class="app-container">
|
||||
<el-row
|
||||
type="flex"
|
||||
justify="space-between"
|
||||
style="align-items: baseline; margin-bottom: 30px"
|
||||
>
|
||||
<div class="header">
|
||||
<h1 class="title">网关</h1>
|
||||
<span class="date">{{ date }}</span>
|
||||
<span class="sum">{{ "共" + deviceSum + "个网关" }}</span>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-button
|
||||
class="add_btn"
|
||||
icon="el-icon-plus"
|
||||
@click="visible = true"
|
||||
>新增网关</el-button>
|
||||
</el-row>
|
||||
<div class="list_container">
|
||||
<div class="title_box">
|
||||
<p />
|
||||
<p />
|
||||
<p>最后连接时间</p>
|
||||
<p>网关名称</p>
|
||||
<p>网关ID</p>
|
||||
<i />
|
||||
</div>
|
||||
<div
|
||||
v-for="(item, index) in gatewayList"
|
||||
:key="index"
|
||||
class="item_box"
|
||||
:class="
|
||||
item.status === 1
|
||||
? 'online'
|
||||
: item.status === 2
|
||||
? 'offline'
|
||||
: 'never_seen'
|
||||
"
|
||||
>
|
||||
<img :src="getStatusImg(item.status)">
|
||||
<p>
|
||||
{{
|
||||
item.status === 1 ? "在线" : item.status === 2 ? "离线" : "从未连接"
|
||||
}}
|
||||
</p>
|
||||
<p>{{ item.lastConnect ? item.lastConnect : "" }}</p>
|
||||
<p>{{ item.name }}</p>
|
||||
<p>
|
||||
<a @click="showDataDialog(item.gatewayid)">{{ item.gatewayid }}</a>
|
||||
</p>
|
||||
<i
|
||||
type="danger"
|
||||
class="remove_icon el-icon-delete"
|
||||
@click="remove(item.name, item.gatewayid)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 设备列表弹窗 -->
|
||||
<div v-show="dataVisible" class="dialog_wrapper">
|
||||
<div class="dialog-container">
|
||||
<div class="dialog_header">
|
||||
<p class="ip">包含设备列表</p>
|
||||
<div class="remove_icon" @click="close">X</div>
|
||||
</div>
|
||||
<div class="dialog_content">
|
||||
<div v-if="deviceList.length > 0" class="list_container">
|
||||
<div class="title_box">
|
||||
<p />
|
||||
<p>状态</p>
|
||||
<p>节点名称</p>
|
||||
<p>终端ID</p>
|
||||
</div>
|
||||
<div class="content_box">
|
||||
<div
|
||||
v-for="(item, index) in deviceList"
|
||||
:key="index"
|
||||
class="item_box"
|
||||
>
|
||||
<img src="@/assets/images/geteway_device.png">
|
||||
<p>
|
||||
{{
|
||||
item.status === 1 ? "在线" : item.status === 2 ? "离线" : "从未连接"
|
||||
}}
|
||||
</p>
|
||||
<p>{{ item.name }}</p>
|
||||
<p>{{ item.devEui }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="min-height:200px;text-align:center">
|
||||
暂无数据
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 设备新增弹窗 -->
|
||||
<div v-show="visible" class="dialog_wrapper">
|
||||
<div class="dialog-container">
|
||||
<div class="dialog_header">
|
||||
<p class="ip">网关新增</p>
|
||||
<div class="remove_icon" @click="close">X</div>
|
||||
</div>
|
||||
<div class="dialog_content">
|
||||
<div class="form">
|
||||
<div class="form_content">
|
||||
<div class="item_title">
|
||||
<img src="@/assets/images/stock_status.png">名称
|
||||
</div>
|
||||
<div class="item_content">
|
||||
<el-input v-model="form.name" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="line" />
|
||||
<div class="form_content">
|
||||
<div class="item_title">
|
||||
<img src="@/assets/images/stock_no.png">网关<br>ID
|
||||
</div>
|
||||
<div class="item_content">
|
||||
<el-input v-model="form.gatewayid" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="line" />
|
||||
</div>
|
||||
<div class="footer">
|
||||
<el-button
|
||||
size="medium"
|
||||
class="cancel_btn"
|
||||
@click="close"
|
||||
>取消</el-button>
|
||||
<el-button
|
||||
size="medium"
|
||||
class="save_btn"
|
||||
style="border-radius: 4px"
|
||||
type="primary"
|
||||
@click="save"
|
||||
>保存</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import {
|
||||
getGatewayList,
|
||||
getDeviceList,
|
||||
add,
|
||||
updateGateway,
|
||||
updateDevice,
|
||||
remove
|
||||
} from '@/api/gateway/manage'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
date: '',
|
||||
deviceSum: 10,
|
||||
// list: [
|
||||
// {
|
||||
// name: '11',
|
||||
// gatewayid: '1234568932131331',
|
||||
// lastConnect: '2023-10-11 12:00:00',
|
||||
// status: 1,
|
||||
// imgSrc: require('@/assets/images/gateway_online.png')
|
||||
// },
|
||||
// {
|
||||
// name: '11',
|
||||
// gatewayid: '1234568932131331',
|
||||
// lastConnect: '2023-10-11 12:00:00',
|
||||
// status: 2,
|
||||
// imgSrc: require('@/assets/images/gateway_offline.png')
|
||||
// },
|
||||
// {
|
||||
// name: '11',
|
||||
// gatewayid: '1234568932131331',
|
||||
// lastConnect: '2023-10-11 12:00:00',
|
||||
// status: 0,
|
||||
// imgSrc: require('@/assets/images/gateway_never_seen.png')
|
||||
// }
|
||||
// ],
|
||||
deviceList: [],
|
||||
visible: false,
|
||||
dataVisible: false,
|
||||
form: {
|
||||
name: '',
|
||||
gatewayid: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['gatewayList'])
|
||||
},
|
||||
mounted() {
|
||||
this.date = new Date().toLocaleDateString()
|
||||
this.getGatewayList()
|
||||
},
|
||||
methods: {
|
||||
getGatewayList() {
|
||||
getGatewayList().then((res) => {
|
||||
this.$store.dispatch('app/setGatewayList', res.data)
|
||||
this.deviceSum = this.gatewayList.length
|
||||
})
|
||||
},
|
||||
getStatusImg(status) {
|
||||
let imgSrc
|
||||
switch (status) {
|
||||
case 0:
|
||||
imgSrc = require('@/assets/images/gateway_never_seen.png')
|
||||
break
|
||||
case 1:
|
||||
imgSrc = require('@/assets/images/gateway_online.png')
|
||||
break
|
||||
case 2:
|
||||
imgSrc = require('@/assets/images/gateway_offline.png')
|
||||
break
|
||||
}
|
||||
return imgSrc
|
||||
},
|
||||
close() {
|
||||
this.visible = false
|
||||
this.dataVisible = false
|
||||
},
|
||||
remove(name, gatewayid) {
|
||||
this.$confirm(`确认删除网关${name}?`, '提示', { type: 'warning' })
|
||||
.then(() => {
|
||||
remove({ gatewayid }).then((res) => {
|
||||
this.$message.success('删除成功')
|
||||
this.getGatewayList()
|
||||
})
|
||||
})
|
||||
.catch()
|
||||
},
|
||||
save() {
|
||||
add({ ...this.form, status: 0 }).then((res) => {
|
||||
this.$message.success('新增成功')
|
||||
this.getGatewayList()
|
||||
this.close()
|
||||
})
|
||||
},
|
||||
showDataDialog(gatewayid) {
|
||||
this.getDeviceList(gatewayid)
|
||||
this.dataVisible = true
|
||||
},
|
||||
getDeviceList(gatewayid) {
|
||||
getDeviceList({ gatewayid }).then((res) => {
|
||||
if (res.data) {
|
||||
this.deviceList = res.data
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.header {
|
||||
font-family: Lato;
|
||||
.title {
|
||||
color: #174a84;
|
||||
}
|
||||
.date {
|
||||
color: #a5c9ff;
|
||||
}
|
||||
.sum {
|
||||
margin-left: 85px;
|
||||
color: #a5c9ff;
|
||||
}
|
||||
}
|
||||
.add_btn {
|
||||
width: 100%;
|
||||
color: #fff;
|
||||
border: 2px solid transparent;
|
||||
background: linear-gradient(#789af3, #789af3) padding-box,
|
||||
repeating-linear-gradient(
|
||||
45deg,
|
||||
#789af3 0,
|
||||
#789af3 12px,
|
||||
#fff 12px,
|
||||
#fff 24px
|
||||
);
|
||||
}
|
||||
.list_container {
|
||||
width: 100%;
|
||||
margin-top: 30px;
|
||||
.title_box {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
margin: 10px auto;
|
||||
display: inline-grid;
|
||||
grid-template-columns: 100px 1fr minmax(260px, 2fr) 1fr 2fr 100px;
|
||||
justify-items: center;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
p {
|
||||
line-height: 40px;
|
||||
font-size: 26px;
|
||||
font-family: Microsoft YaHei;
|
||||
color: #2e4765;
|
||||
}
|
||||
}
|
||||
.item_box {
|
||||
border-radius: 10px;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
margin: 5px auto;
|
||||
display: inline-grid;
|
||||
grid-template-columns: 100px 1fr minmax(260px, 2fr) 1fr 2fr 100px;
|
||||
justify-items: center;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
img {
|
||||
width: 45px;
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
p {
|
||||
line-height: 100px;
|
||||
font-size: 26px;
|
||||
font-family: Microsoft YaHei;
|
||||
color: #2e4765;
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
.remove_icon {
|
||||
font-size: 26px;
|
||||
color: #f56c6c;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.online {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(212, 252, 121, 0.2) 0%,
|
||||
rgba(150, 230, 161, 0.2) 100%
|
||||
);
|
||||
}
|
||||
.offline {
|
||||
background: linear-gradient(
|
||||
225deg,
|
||||
rgba(254, 243, 229, 0.2) 0%,
|
||||
rgba(254, 199, 168, 0.2) 0%,
|
||||
rgba(254, 180, 141, 0.2) 38%,
|
||||
rgba(255, 78, 0, 0.2) 100%
|
||||
);
|
||||
}
|
||||
.never_seen {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(0, 204, 242, 0.2) 0%,
|
||||
rgba(120, 154, 243, 0.2) 62%,
|
||||
rgba(173, 223, 232, 0.2) 100%
|
||||
);
|
||||
}
|
||||
}
|
||||
.dialog_wrapper {
|
||||
z-index: 2;
|
||||
position: fixed;
|
||||
left: 230px;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: 0;
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -230px;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
z-index: 2;
|
||||
}
|
||||
.dialog-container {
|
||||
z-index: 10;
|
||||
width: 600px;
|
||||
transform: none;
|
||||
margin: 15vh auto;
|
||||
position: relative;
|
||||
border-radius: 8px;
|
||||
border: 2px solid #20be0b;
|
||||
font-family: Microsoft YaHei;
|
||||
box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.16);
|
||||
.dialog_header {
|
||||
background-color: #20be0b;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
padding: 5px;
|
||||
position: relative;
|
||||
p {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin: 5px auto;
|
||||
}
|
||||
.remove_icon {
|
||||
position: absolute;
|
||||
font-size: 18px;
|
||||
right: 15px;
|
||||
top: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.dialog_content {
|
||||
background-color: #fff;
|
||||
border-radius: 0 0 8px 8px;
|
||||
padding: 40px 10px 20px;
|
||||
.form {
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
.form_content {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
div {
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
img {
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
.item_title {
|
||||
text-align: center;
|
||||
background: #d5f3d1;
|
||||
width: 110px;
|
||||
min-height: 90px;
|
||||
font-size: 20px;
|
||||
color: #20be0b;
|
||||
}
|
||||
.item_content {
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
padding: 9px;
|
||||
}
|
||||
}
|
||||
.line {
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-image: linear-gradient(
|
||||
to right,
|
||||
#20be0b 0%,
|
||||
#20be0b 50%,
|
||||
transparent 50%
|
||||
);
|
||||
background-size: 18px 100%;
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
}
|
||||
.list_container {
|
||||
width: 100%;
|
||||
margin-top: -20px;
|
||||
.title_box {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
margin: 10px auto;
|
||||
display: inline-grid;
|
||||
grid-template-columns: 80px 1fr 1fr 1fr;
|
||||
justify-items: center;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
p {
|
||||
line-height: 40px;
|
||||
font-size: 22px;
|
||||
font-family: Microsoft YaHei;
|
||||
color: #2e4765;
|
||||
}
|
||||
}
|
||||
.content_box {
|
||||
min-height: 200px;
|
||||
.item_box {
|
||||
border: 1px solid rgba(0, 204, 242, 0.2);
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(0, 204, 242, 0.2) 0%,
|
||||
rgba(150, 230, 161, 0.2) 100%
|
||||
);
|
||||
border-radius: 10px;
|
||||
width: 100%;
|
||||
height: 70px;
|
||||
margin: 5px auto;
|
||||
display: inline-grid;
|
||||
grid-template-columns: 80px 1fr 1fr 1fr;
|
||||
justify-items: center;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
img {
|
||||
width: 45px;
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
p {
|
||||
line-height: 100px;
|
||||
font-size: 22px;
|
||||
font-family: Microsoft YaHei;
|
||||
color: #2e4765;
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
.remove_icon {
|
||||
font-size: 26px;
|
||||
color: #f56c6c;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// .radio {
|
||||
// ::v-deep .el-radio-button__inner {
|
||||
// color: #8f59eb;
|
||||
// border-color: #8f59eb;
|
||||
// }
|
||||
// ::v-deep .el-radio-button__orig-radio:checked + .el-radio-button__inner {
|
||||
// background-color: #8f59eb;
|
||||
// color: #fff;
|
||||
// }
|
||||
// ::v-deep .el-radio-button:first-child .el-radio-button__inner {
|
||||
// border-radius: 9px 0 0 9px;
|
||||
// }
|
||||
// ::v-deep .el-radio-button:last-child .el-radio-button__inner {
|
||||
// border-radius: 0 9px 9px 0;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
.footer {
|
||||
text-align: center;
|
||||
margin: 40px auto 20px;
|
||||
.cancel_btn {
|
||||
border-color: #20be0b;
|
||||
color: #20be0b;
|
||||
&:hover {
|
||||
background-color: rgba(32, 190, 11, 0.1);
|
||||
}
|
||||
}
|
||||
.save_btn {
|
||||
border-color: #20be0b;
|
||||
background-color: #20be0b;
|
||||
border-radius: 2px;
|
||||
&:hover {
|
||||
background-color: #20be0b;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
::v-deep .el-input > .el-input__inner {
|
||||
border: none;
|
||||
}
|
||||
</style>
|
|
@ -91,7 +91,12 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { getGifCode } from '@/api/user'
|
||||
import { mapGetters } from 'vuex'
|
||||
import {
|
||||
getGatewayList,
|
||||
deviceOffline,
|
||||
gatewayOffline
|
||||
} from '@/api/gateway/manage'
|
||||
import SIdentify from './components/identify'
|
||||
|
||||
export default {
|
||||
|
@ -139,6 +144,9 @@ export default {
|
|||
height: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['gatewayList'])
|
||||
},
|
||||
watch: {
|
||||
$route: {
|
||||
handler: function(route) {
|
||||
|
@ -179,6 +187,9 @@ export default {
|
|||
// this.loading = false
|
||||
// })
|
||||
.then(() => {
|
||||
this.getGatewayList()
|
||||
this.offlineDevice()
|
||||
this.offlineGateway()
|
||||
this.$router.push({ path: this.redirect || '/overview' })
|
||||
this.loading = false
|
||||
})
|
||||
|
@ -268,6 +279,18 @@ export default {
|
|||
}
|
||||
bubble.y += bubble.vy
|
||||
this.draw(bubble)
|
||||
},
|
||||
offlineDevice() {
|
||||
deviceOffline().then()
|
||||
},
|
||||
offlineGateway() {
|
||||
gatewayOffline().then()
|
||||
},
|
||||
getGatewayList() {
|
||||
getGatewayList().then((res) => {
|
||||
this.$store.dispatch('app/setGatewayList', res.data)
|
||||
console.log('gatewayList on login page', this.gatewayList)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -574,6 +574,7 @@ export default {
|
|||
const temp = res.data.filter(
|
||||
(resTask) => task.id === resTask.id
|
||||
)[0]
|
||||
task.status = temp.status
|
||||
task.statusDesc = statusList[temp.status]
|
||||
task.ratio = Math.round(
|
||||
(temp.currentProcess / this.currentFileSize) * 100
|
||||
|
|