You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
iot/projects/IoTCenter/Views/Home/Index.cshtml

332 lines
13 KiB

@model List<Node>
@inject IConfiguration cfg
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title>@cfg["name"]</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, minimal-ui, viewport-fit=cover">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="theme-color" content="#2196f3">
<link rel="stylesheet" href="~/lib/framework7/css/framework7.ios.min.css">
<style>
img.font {
display: inline-block;
height: 2em;
vertical-align: middle;
}
img.icon {
display: block;
height: 60px;
max-width: 160px;
margin: 0 auto;
}
img.btn {
min-width: 20px;
width: 30px;
max-width: 60px;
}
.row .col-auto > div {
text-align: center;
}
.row .col-auto > div > div {
text-align: left;
}
div.btn {
padding: 0 .5em;
}
.col-auto {
padding: 1em;
}
canvas {
margin: 0 auto;
max-width: 100%;
}
</style>
</head>
<body>
<div id="app">
<div class="view view-main view-init">
<div class="page">
<div class="navbar">
<div class="navbar-inner sliding">
<div class="left">
<a href="/Admin" class="link external" data-panel="left">管理</a>
</div>
<div class="title">@(cfg["name"])/{{model.length}}</div>
<div class="right">
@if (User.Identity.IsAuthenticated)
{
<a href="/Account/Logout" class="link external">退出</a>
}
else
{
<a href="/Account/Login" class="link external">登录</a>
}
</div>
</div>
</div>
<div class="page-content">
<div class="card">
<div class="card-header">
<div class="left">数量统计:{{_.chain(model).flatMap('Devices').value().length}}</div>
</div>
<div class="card-content">
<div class="row">
<div class="col-auto">
<canvas id="DeviceTotal" height="300" width="300"></canvas>
</div>
<div class="col-auto">
<div>
<img src="/iot/10.png" class="icon">
</div>
<div>安防<span class="badge color-red">{{getDeviceTotal('安防')}}</span></div>
</div>
<div class="col-auto">
<div>
<img src="/iot/20.png" class="icon">
</div>
<div>电器<span class="badge color-orange">{{getDeviceTotal('电器')}}</span></div>
</div>
<div class="col-auto">
<div>
<img src="/iot/30.png" class="icon">
</div>
<div>照明<span class="badge color-yellow">{{getDeviceTotal('照明')}}</span></div>
</div>
<div class="col-auto">
<div>
<img src="/iot/40.png" class="icon">
</div>
<div>监测<span class="badge color-green">{{getDeviceTotal('监测')}}</span></div>
</div>
</div>
</div>
</div>
<div class="card" v-for="node in model">
<div class="card-header">
<div class="left">
{{node.Name}}/{{node.Devices.length}}
</div>
<div class="right">
<a :href="'/Home/Node/'+node.Id" class="link external">控制面板</a>
</div>
</div>
<div class="card-content">
<div class="row">
<div v-for="device in node.Devices" class="col-auto">
<div>
<img :src="'/iot/'+device.Icon+'.png'" class="icon">
</div>
<div><a :href="'/Device/Details/'+device.Id" class="link external">{{device.DisplayName}}</a></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<audio id="alarm" muted style="display:none;" src="~/iot/warning.wav"></audio>
<script src="~/lib/jquery/jquery.min.js"></script>
<script src="~/lib/lodash/lodash.min.js"></script>
<script src="~/lib/Chart.js/Chart.bundle.min.js"></script>
<script src="~/lib/framework7/js/framework7.min.js"></script>
<script src="~/lib/vue/vue.min.js"></script>
<script src="~/lib/signalr/signalr.min.js"></script>
<script>
const connection = new signalR.HubConnectionBuilder()
.withUrl("/hub?group=page")
.build();
connection.on("UpdateNode", (message) => {
var node = JSON.parse(message);
var oldNode = _.chain(vm.model).filter(o => o.Number === node.Number).first().value();
oldNode.Name = node.Name;
console.log('update:' + node.Name);
});
connection.on("UpdateDevice", (message) => {
var device = JSON.parse(message);
device.Data = null;
device.Apis = null;
var update = false;
var node = _.chain(vm.model).filter(o => o.Number === device.Node.Number).first().value();
for (var i = 0; i < node.Devices.length; i++) {
if (node.Devices[i].Number == device.Number) {
update = true;
break;
}
}
if (update) {
node.Devices.splice(i, 1, device);
}
else {
node.Devices.push(device);
}
console.log('update:' + device.DisplayName);
});
connection.on("DeleteDevice", (message) => {
var number = message;
for (var i = 0; i < vm.model.length; i++) {
var node = vm.model[i];
for (var j = 0; j < node.Devices.length; j++) {
if (node.Devices[j].Number == number) {
node.Devices.splice(j, 1);
break;
}
}
}
console.log('delete:' + number);
});
connection.onclose(function (err) {
console.error(err.toString());
setTimeout(connect, 15 * 1000);
});
function connect() {
console.log('start connect to server:' + Date());
connection.start().then(function () {
}).catch(function (err) {
console.error(err.toString());
setTimeout(connect, 15 * 1000);
});
}
</script>
<script>
var app;
var deviceTotalChart;
var vm = new Vue({
el: '#app',
data: {
model: []
},
mounted: function () {
this.init();
connect();
var theme = 'ios';
var plugin = {
params: {
theme: theme,
root: '#app',
}
};
if (Framework7.use) {
Framework7.use(plugin);
}
else if (Framework7.Class && Framework7.Class.use) {
Framework7.Class.use(plugin);
}
app = new Framework7();
this.renderDeviceTotal(true);
},
updated: function () {
console.log('updated');
},
watch: {
'model': {
handler(val, oldVal) {
console.log('watch');
if (this.getDeviceCount(val, '安防') !== this.getDeviceCount(oldVal, '安防')
|| this.getDeviceCount(val, '电器') !== this.getDeviceCount(oldVal, '电器')
|| this.getDeviceCount(val, '照明') !== this.getDeviceCount(oldVal, '照明')
|| this.getDeviceCount(val, '监测') !== this.getDeviceCount(oldVal, '监测')) {
this.renderDeviceTotal(false);
}
},
deep: true
}
},
methods: {
init: function () {
$.getJSON('/Home/GetNodes', function (response) {
vm.model = response;
});
},
getDeviceTotal: function (categoryName) {
return _.chain(this.model).flatMap('Devices').filter(o => o.CategoryName === categoryName).value().length;
},
getDeviceCount: function (nodes, categoryName) {
return _.chain(nodes).flatMap('Devices').filter(o => o.CategoryName === categoryName).value().length;
},
getData: function (node, dataName) {
var data = _.chain(node.Devices).flatMap('Data').filter(o => o.Name === dataName).value()[0];
if (data != null) {
var result = data.Value;
if (data.Unit != null && data.Unit !== '') {
result += ' ' + data.Unit;
}
result += ' ' + data.Description;;
return result;
}
return '';
},
renderDeviceTotal: function (create) {
var datas = [
this.getDeviceTotal('安防'),
this.getDeviceTotal('电器'),
this.getDeviceTotal('照明'),
this.getDeviceTotal('监测'),
];
var total = 0;
datas.forEach(n => total += n);
var data = {
datasets: [{
data: datas,
backgroundColor: [
'red',
'orange',
'yellow',
'green',
],
label: 'Dataset 1'
}],
labels: [
'安防' + Number(datas[0] / total * 100).toFixed(2) + '%',
'电器' + Number(datas[1] / total * 100).toFixed(2) + '%',
'照明' + Number(datas[2] / total * 100).toFixed(2) + '%',
'监测' + Number(datas[3] / total * 100).toFixed(2) + '%',
]
};
if (create) {
var ctx = document.getElementById('DeviceTotal').getContext('2d');
var cfg = {
type: 'pie',
data: data,
options: {
responsive: true,
legend: {
position: 'right',
},
title: {
display: true,
text: '设备比例'
},
animation: {
animateScale: true,
animateRotate: true
}
}
};
deviceTotalChart = new Chart(ctx, cfg);
}
else {
chart = deviceTotalChart;
chart.data.labels.pop();
chart.data = data;
chart.update();
}
}
}
});
</script>
</body>
</html>