<template> <div class="app-container"> <div style="margin-top: 10px"> <grid-layout :layout.sync="layout" :col-num="12" :row-height="30" :is-draggable="draggable" :is-resizable="resizable" :vertical-compact="true" :use-css-transforms="true" @layout-created="layoutCreatedEvent" @layout-before-mount="layoutBeforeMountEvent" @layout-mounted="layoutMountedEvent" @layout-ready="layoutReadyEvent" @layout-updated="layoutUpdatedEvent" > <grid-item :x="0" :y="0" :w="6" :h="5" i="1" @resize="resizeEvent" @move="moveEvent" @resized="resizedEvent" @container-resized="containerResizedEvent" @moved="movedEvent" > <p class="item-title">DEVICE</p> <div style="padding: 15px"> <a-select style="width: 200px;" size="large" v-model="device" @change="onSelectChange" placeholder="请选择设备" > <a-option v-for="(item, index) in deviceList" :key="index" :value="item" >{{ item }}</a-option> </a-select> </div> </grid-item> <grid-item :x="6" :y="0" :w="6" :h="5" i="2" @resize="resizeEvent" @move="moveEvent" @resized="resizedEvent" @container-resized="containerResizedEvent" @moved="movedEvent" > <p class="item-title">SYSTEM</p> <div class="grid-container"> <div class="grid-item"> <h3>host</h3> <p>{{ system.host }}</p> </div> <div class="grid-item"> <h3>timestamp</h3> <p>{{ system.timestamp }}</p> </div> <div class="grid-item"> <h3>imei</h3> <p>{{ system.imei }}</p> </div> <div class="grid-item"> <h3>free_size</h3> <p>{{ system.free_size }}</p> </div> </div> </grid-item> <grid-item :x="0" :y="5" :w="6" :h="5" i="3" @resize="resizeEvent" @move="moveEvent" @resized="resizedEvent" @container-resized="containerResizedEvent" @moved="movedEvent" > <p class="item-title">EVM</p> <div class="grid-container"> <div class="grid-item"> <h3>heap_map_size</h3> <p>{{ evm.heap_map_size }}</p> </div> <div class="grid-item"> <h3>heap_total_size</h3> <p>{{ evm.heap_total_size }}</p> </div> <div class="grid-item"> <h3>heap_used_size</h3> <p>{{ evm.heap_used_size }}</p> </div> <div class="grid-item"> <h3>stack_total_size</h3> <p>{{ evm.stack_total_size }}</p> </div> <div class="grid-item"> <h3>stack_used_size</h3> <p>{{ evm.stack_used_size }}</p> </div> </div> </grid-item> <grid-item :x="6" :y="5" :w="6" :h="5" i="4" @resize="resizeEvent" @move="moveEvent" @resized="resizedEvent" @container-resized="containerResizedEvent" @moved="movedEvent" > <p class="item-title">LVGL</p> <div class="grid-container"> <div class="grid-item"> <h3>total_size</h3> <p>{{ lvgl.total_size }}</p> </div> <div class="grid-item"> <h3>free_cnt</h3> <p>{{ lvgl.free_cnt }}</p> </div> <div class="grid-item"> <h3>free_size</h3> <p>{{ lvgl.free_size }}</p> </div> <div class="grid-item"> <h3>free_biggest_size</h3> <p>{{ lvgl.free_biggest_size }}</p> </div> <div class="grid-item"> <h3>used_cnt</h3> <p>{{ lvgl.used_cnt }}</p> </div> <div class="grid-item"> <h3>used_pct</h3> <p>{{ lvgl.used_pct }}</p> </div> <div class="grid-item"> <h3>frag_pct</h3> <p>{{ lvgl.frag_pct }}</p> </div> </div> </grid-item> <grid-item :x="0" :y="10" :w="12" :h="10" i="5" @resize="resizeEvent" @move="moveEvent" @resized="resizedEvent" @container-resized="containerResizedEvent" @moved="movedEvent" > <div style="width: 100%; height: 100%; overflow-y: auto"> <p class="item-title">APP</p> <div class="grid-container"> <div class="grid-item"> <h3>uri</h3> </div> <div class="grid-item"> <h3>length</h3> </div> <div class="grid-item"> <h3>png_file_size</h3> </div> <div class="grid-item"> <h3>png_total_count</h3> </div> <div class="grid-item"> <h3>png_uncompressed_size</h3> </div> </div> <div class="grid-container" v-for="(item, index) in imageList" :key="index"> <div class="grid-item"><p>{{ item.uri }}</p></div> <div class="grid-item"><p>{{ item.length }}</p></div> <div class="grid-item"><p>{{ item.png_file_size }}</p></div> <div class="grid-item"><p>{{ item.png_total_count }}</p></div> <div class="grid-item"><p>{{ item.png_uncompressed_size }}</p></div> </div> </div> </grid-item> <grid-item :x="0" :y="20" :w="12" :h="7" i="6" @resize="resizeEvent" @move="moveEvent" @resized="resizedEvent" @container-resized="containerResizedEvent" @moved="movedEvent" > <SystemChart :chartData="system"></SystemChart> </grid-item> <grid-item :x="0" :y="27" :w="12" :h="7" i="7" @resize="resizeEvent" @move="moveEvent" @resized="resizedEvent" @container-resized="containerResizedEvent" @moved="movedEvent" > <EvmChart :chartData="evm"></EvmChart> </grid-item> <grid-item :x="0" :y="34" :w="12" :h="7" i="8" @resize="resizeEvent" @move="moveEvent" @resized="resizedEvent" @container-resized="containerResizedEvent" @moved="movedEvent" > <LvglChart :chartData="lvgl"></LvglChart> </grid-item> </grid-layout> </div> </div> </template> <script> import { Avatar, Row, Col, Card, List, Button, Form, Icon, Table, Divider, Dropdown, Input, Select, DatePicker, message, } from "ant-design-vue"; import PageHeaderWrapper from "@/components/PageHeaderWrapper"; import DescriptionItem from "@/components/DescriptionItem"; import { getWatchList, getMonitorData } from "@/api/openapi"; import EvmChart from "./components/EvmChart"; import LvglChart from "./components/LvglChart"; import SystemChart from "./components/SystemChart"; import { GridLayout, GridItem } from "vue-grid-layout"; import { wsNotify } from "@/utils/notify.js"; export default { name: "Monitor", data() { return { watchs: [], globalData: null, device: null, devices: {}, deviceList: [], systemList: [], system: { host: null, imei: null, timestamp: null, }, evm: { heap_map_size: null, heap_total_size: null, heap_used_size: null, stack_total_size: null, stack_used_size: null, }, evmList: [], lvgl: { total_size: null, free_cnt: null, free_size: null, free_biggest_size: null, used_cnt: null, used_pct: null, frag_pct: null }, lvglList: [], image: {}, imageList: [], socket: null, form: { system: ["free_size", "free_space_size", "used_space_size"], lvgl: ["total_size", "free_size", "free_biggest_size"], evm: [ "total_size", "free_size", "heap_map_size", "heap_total_size", "heap_used_size", "stack_total_size", "stack_used_size", ], image: ["png_uncompressed_size", "png_file_size", "length"], }, layout: [ { x: 0, y: 0, w: 6, h: 5, i: "1", static: false }, { x: 6, y: 0, w: 6, h: 5, i: "2", static: true }, { x: 0, y: 5, w: 6, h: 5, i: "3", static: false }, { x: 6, y: 5, w: 6, h: 5, i: "4", static: false }, { x: 0, y: 10, w: 12, h: 10, i: "5", static: false }, { x: 0, y: 20, w: 12, h: 7, i: "6", static: false }, { x: 0, y: 27, w: 12, h: 7, i: "7", static: false }, { x: 0, y: 34, w: 12, h: 7, i: "8", static: false }, ], draggable: true, resizable: true, }; }, components: { GridLayout, GridItem, EvmChart, LvglChart, SystemChart, APageHeaderWrapper: PageHeaderWrapper, AAvatar: Avatar, ARow: Row, ACol: Col, ACard: Card, ACardGrid: Card.Grid, ACardMeta: Card.Meta, AList: List, AButton: Button, AForm: Form, AFormItem: Form.Item, AIcon: Icon, ATable: Table, ADescriptionItem: DescriptionItem, ADivider: Divider, ADropdown: Dropdown, AInput: Input, ASelect: Select, AOption: Select.Option, ARangePicker: DatePicker.RangePicker, }, methods: { moveEvent(i, newX, newY) { const msg = "MOVE i=" + i + ", X=" + newX + ", Y=" + newY; console.log(msg); }, movedEvent(i, newX, newY) { const msg = "MOVED i=" + i + ", X=" + newX + ", Y=" + newY; console.log(msg); }, resizeEvent(i, newH, newW, newHPx, newWPx) { const msg = "RESIZE i=" + i + ", H=" + newH + ", W=" + newW + ", H(px)=" + newHPx + ", W(px)=" + newWPx; console.log(msg); }, resizedEvent(i, newX, newY, newHPx, newWPx) { const msg = "RESIZED i=" + i + ", X=" + newX + ", Y=" + newY + ", H(px)=" + newHPx + ", W(px)=" + newWPx; console.log(msg); }, containerResizedEvent(i, newH, newW, newHPx, newWPx) { const msg = "CONTAINER RESIZED i=" + i + ", H=" + newH + ", W=" + newW + ", H(px)=" + newHPx + ", W(px)=" + newWPx; console.log(msg); }, layoutCreatedEvent(newLayout) { console.log("Created layout: ", newLayout); }, layoutBeforeMountEvent(newLayout) { console.log("beforeMount layout: ", newLayout); }, layoutMountedEvent(newLayout) { console.log("Mounted layout: ", newLayout); }, layoutReadyEvent(newLayout) { console.log("Ready layout: ", newLayout); }, layoutUpdatedEvent(newLayout) { console.log("Updated layout: ", newLayout); }, fetchData() { this.isLoading = true; getWatchList() .then((res) => { if (res.code == 200) this.watchs = res.data; }) .catch((err) => { this.$message.warning(err.msg); }) .finally(() => { this.isLoading = false; }); }, queryData() { getMonitorData({ watch: this.device, }) .then((res) => { if (res.type == "object") { this.evmList = res.data.evm; this.lvglList = res.data.lvgl; this.imageList = res.data.image; } }) .catch((err) => { this.$message.warning(err.msg); }); }, onChange(res) { if (!res) return null; var t = this.watchs.find((item) => { return item.id == res; }); if (t) this.device = t.imei; // 清空之前数据 this.resetData(); }, onSubmit() { this.queryData(); }, onReset(formName) { this.$refs[formName].resetFields(); this.fetchData(); }, sendMsg() { let message = JSON.stringify({ type: "auth", token: window.sessionStorage.getItem("Authorization"), }); this.socket.send(message); }, handleMessage(msg) { if (msg.code == 401) { window.sessionStorage.removeItem("Authorization") this.$router.push({ path: "/user/login" }) message.error(msg.msg) return null; } if (msg.type !== "report" || !msg.imei) return null; // 如果接收到的数据不是当前选中的设备,那么则直接丢弃 // 这里可以优化,将所有数据,保存到indexed datebase中 if (this.device && msg.imei != this.device) return null; if (!this.deviceList) { this.deviceList = []; } if (!this.deviceList.includes(msg.imei)) { this.deviceList.push(msg.imei); } if (!this.device) { if (this.deviceList && this.deviceList.length) this.device = this.deviceList[0]; else this.device = msg.imei; } // 处理单位 this.processData(msg); // this.devices[msg.imei] = msg; this.globalData = msg; this.resetData(); }, processData(msg) { if (!msg) return null; Object.keys(msg).forEach((item) => { if (this.form[item]) { var keys = this.form[item]; for (var i = 0; i < keys.length; i++) { var k = keys[i]; if (item == "image") { for (var j = 0; j < msg[item].length; j++) { msg[item][j][k] = Math.ceil(msg[item][j][k] / 1024); } } else { msg[item][k] = Math.ceil(msg[item][k] / 1024); } } } }); }, onSelectChange(res) { this.device = res; // this.processData(this.devices[this.device]); // this.resetData(); console.log(res); }, resetData() { wsNotify.eventBus.$emit("resize"); this.evmList = [{ ...this.globalData.evm }]; this.lvglList = [{ ...this.globalData.lvgl }]; this.systemList = [ { imei: this.globalData.imei, ...this.globalData.system, }, ]; // 这里需要特殊处理下,先判断uri是否存在,不存在则添加,存在则更新 let uris = []; this.imageList.forEach((item) => { item.highlight = false; uris.push(item.uri); }); this.globalData.image && this.globalData.image.forEach((item) => { if (item.png_uncompressed_size > 0) { item.highlight = true; } else { item.highlight = false; } const target = this.imageList.find((img) => img.uri === item.uri); if (target) { target.length = item.length; target.png_total_count = item.png_total_count; target.highlight = false; if ( item.png_uncompressed_size && item.png_uncompressed_size !== target.png_uncompressed_size ) { target.highlight = true; target.png_uncompressed_size = item.png_uncompressed_size; } if ( item.png_file_size && item.png_file_size !== target.png_file_size ) target.png_file_size = item.png_file_size; } else { this.imageList.push(item); } }); if (this.globalData) { if (this.globalData.evm) this.evm = this.globalData.evm; if (this.globalData.lvgl) this.lvgl = this.globalData.lvgl; if (this.globalData.image) this.image = this.globalData.image; if (this.globalData.system) this.system = this.globalData.system; } }, }, mounted() {}, created() { this.socket = wsNotify; wsNotify.eventBus.$on("open", (message) => { this.sendMsg(); this.$nextTick(() => { console.log(message); }); // 这里启动一个定时器,10秒钟后,如果没有消息进来,说明当前没有在线设备 }); wsNotify.eventBus.$on("close", (message) => { this.$nextTick(() => { console.log(message); }); }); wsNotify.eventBus.$on("message", (message) => { console.log(message) this.$nextTick(() => { this.handleMessage(message); }); }); }, }; </script> <style lang="scss" scoped> .app-container { & > div.page-wrapper { margin: 10px 0px; } } .grid-container { display: flex; flex-direction: row; & > .grid-item { flex: 1; & > h3, p { text-align: center; } } } .vue-grid-layout { background: none; } .vue-grid-item:not(.vue-grid-placeholder) { background: #fff; border: 0px solid #eee; } .vue-grid-item .resizing { opacity: 0.9; } .vue-grid-item .static { background: #cce; } .vue-grid-item .text { font-size: 24px; text-align: center; position: absolute; top: 0; bottom: 0; left: 0; right: 0; margin: auto; height: 100%; width: 100%; } .vue-grid-item .item-title { margin: 15px; } .vue-grid-item .no-drag { height: 100%; width: 100%; } .vue-grid-item .minMax { font-size: 12px; } .vue-grid-item .add { cursor: pointer; } .vue-draggable-handle { position: absolute; width: 20px; height: 20px; top: 0; left: 0; background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'><circle cx='5' cy='5' r='5' fill='#999999'/></svg>") no-repeat; background-position: bottom right; padding: 0 8px 8px 0; background-repeat: no-repeat; background-origin: content-box; box-sizing: border-box; cursor: pointer; } </style>