// ==UserScript== // @name 小米路由器(mini)PC端设备实时网速 // @namespace http://tampermonkey.net/ // @version 1.04 // @description 小米路由器(mini)PC端后台管理界面设备实时网速 // @author 过去终究是个回忆 // @license MIT // @match http://192.168.31.1/cgi-bin/luci/*/web/home* // @require https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/dayjs/1.10.8/dayjs.min.js // @require https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/2.6.14/vue.min.js // @require https://unpkg.com/ajax-hook@2.1.3/dist/ajaxhook.min.js // @require https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/element-ui/2.15.7/index.min.js // @require https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/echarts/5.3.0-rc.1/echarts.min.js // @grant GM_getResourceText // @grant GM_addStyle // @grant GM_addStyle // @grant unsafeWindow // @resource element-ui https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/element-ui/2.15.7/theme-chalk/index.min.css // @run-at document-start // @downloadURL none // ==/UserScript== (function () { 'use strict'; GM_addStyle(GM_getResourceText('element-ui')) GM_addStyle(` @font-face{font-family:element-icons;src:url(https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/element-ui/2.15.7/theme-chalk/fonts/element-icons.woff) format("woff"),url(https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/element-ui/2.15.7/theme-chalk/fonts/element-icons.ttf) format("truetype");font-weight:400;font-display:"auto";font-style:normal} #net-speed { float: left; margin-top: 40px; width: 100%; } .demo-table-expand { font-size: 0; } .speed-line { display: flex; justify-content: space-between; align-items: center; } .speed-line div[role="progressbar"] { width: 300px; } #net-speed-charts { height: 600px; } `) const maxEachDataStorage = 100 const updateDevDataEvent = new Event('updateDevData') const initData = { history: {}, tableData: [] } window.ah.proxy({ //请求成功后进入 onResponse: (response, handler) => { if (response.config.url.includes('/api/misystem/status')) { const data = JSON.parse(response.response) initData.tableData = data.dev.map(item => ({ ...item, download: Number(item.download), downspeed: Number(item.downspeed), maxdownloadspeed: Number(item.maxdownloadspeed), maxuploadspeed: Number(item.maxuploadspeed), online: Number(item.online), upload: Number(item.upload), upspeed: Number(item.upspeed), dateSecond: Date.now(), combinedSpeed: Number(item.upspeed) + Number(item.downspeed), })) data.dev.forEach(item => { if (!initData.history[item.mac]) { initData.history[item.mac] = [] } initData.history[item.mac].push({ ...item, dateSecond: Date.now(), }) if (initData.history[item.mac].length > maxEachDataStorage) { initData.history[item.mac].shift() } }) document.dispatchEvent(updateDevDataEvent) } handler.next(response) } }, unsafeWindow) window.onload = function () { const container = document.createElement('div') container.id = 'net-speed' console.log('net-speed starting...') const target = document.querySelector('#bd>.mod-routerstatus.nav-tab-content') target.appendChild(container) new Vue({ el: container, data() { return { dialogTableTitle: 'dialogTableTitle', dialogTableVisible: false, currentRow: {}, history: {}, tableData: [], dataSource: 1, stok: '', totalDownloadSpeed: 0, totalUploadSpeed: 0, myChart: null, } }, async created() { this.stok = location.pathname.match(/\/;stok=(.*?)\//)?.[1] this.history = initData.history this.tableData = initData.tableData document.addEventListener("updateDevData", () => { if (this.dataSource === 1) { this.history = initData.history this.tableData = initData.tableData if (this.myChart) { const $row = this.history[this.currentRow.mac] this.myChart.setOption({ xAxis: { data: $row.map(item => this.dateTimeFormatter(item.dateSecond)) }, series: [ { name: '上传', data: $row.map(item => item.upspeed) }, { name: '下载', data: $row.map(item => item.downspeed) }, ] }); } console.log(this.tableData, 'this.tableData') console.log(this.history, 'this.history') } }) const res = await this.getBbandwidth() this.totalDownloadSpeed = res.bandwidth / 8 * 1024 * 1024 this.totalUploadSpeed = res.bandwidth2 / 8 * 1024 * 1024 window.addEventListener('resize', () => { this.myChart && this.myChart.resize(); }); }, watch: { dataSource(prev, next) { if (prev !== next) { this.history = [] this.tableData = [] if (next === 2) { } } }, }, methods: { byteFormat: byteFormat, secondToDate: secondToDate, speedFormat(number, precision, isarray) { return this.byteFormat(number, precision, isarray) + '/S' }, speedFormatter(row, column, cellValue) { return this.speedFormat(cellValue, 100) }, dataFormatter(row, column, cellValue) { return this.byteFormat(cellValue, 100) }, secondFormatter(row, column, cellValue) { return this.secondToDate(cellValue) }, dateTimeFormatter(cellValue) { return dayjs(cellValue).format('YYYY-MM-DD HH:mm:ss') }, showHistoryData(row) { this.dialogTableVisible = true this.dialogTableTitle = `${row.devname}(${row.mac}) 历史数据` this.currentRow = row const $row = this.history[this.currentRow.mac] this.$nextTick(() => { this.myChart = echarts.init(document.getElementById('net-speed-charts')); // 绘制图表 this.myChart.setOption({ tooltip: { trigger: 'axis', axisPointer: { type: 'cross', label: { backgroundColor: '#6a7985' } } }, dataZoom: [ { show: true, realtime: true, start: 0, end: 100 }, { type: 'inside', realtime: true, start: 0, end: 100 }, ], color: ['#2673bf', '#33cc33'], legend: { data: ['上传', '下载'] }, xAxis: [ { type: 'category', boundaryGap: false, data: $row.map(item => this.dateTimeFormatter(item.dateSecond)), } ], yAxis: [ { type: 'value', axisLabel: { formatter: (value) => this.speedFormat(value, 100), }, } ], series: [ { name: '上传', type: 'line', areaStyle: {}, tooltip: { valueFormatter: (value) => this.speedFormat(value, 100) }, emphasis: { focus: 'series' }, data: $row.map(item => item.upspeed) }, { name: '下载', type: 'line', areaStyle: {}, tooltip: { valueFormatter: (value) => this.speedFormat(value, 100) }, emphasis: { focus: 'series' }, data: $row.map(item => item.downspeed) }, ] }); }) }, beforeDialogTableClose(done) { this.myChart.dispose() this.myChart = null done() }, async getBbandwidth() { const res = await fetch(`/cgi-bin/luci/;stok=${this.stok}/api/misystem/bandwidth_test?history=1`).then(result => result.json()) if (res.code === 0) { return res } else { throw res; } }, async updateNetSpeed() { const res = await fetch(`/cgi-bin/luci/;stok=${this.stok}/api/misystem/devicelist`).then(result => result.json()) if (res.code === 0) { const devList = res.data.map(item => ({ ...item, ...item.statistics, ip: item.ip[0].ip })) this.tableData = devList } } }, template: `
数据来源(切换会清空历史记录): 请求代理 主动请求
` }) } })();