(function () {
"use strict";
var REFRESH_SECONDS = 30;
function cloneMetricList(source) {
return Array.isArray(source) ? source : [];
}
new Vue({
el: "#app",
data: function () {
return {
loading: true,
refreshing: false,
countdown: REFRESH_SECONDS,
countdownTimer: null,
resizeHandler: null,
charts: {
taskDirection: null,
taskStage: null,
deviceType: null,
networkStatus: null,
aiRoute: null
},
overview: {
systemRunning: false,
generatedAt: "",
taskTotal: 0,
taskRunning: 0,
deviceTotal: 0,
deviceOnline: 0,
deviceAlarm: 0,
aiTokenTotal: 0,
aiCallTotal: 0
},
tasks: {
overview: {
running: 0,
manual: 0,
completed: 0,
newCreated: 0
},
directionStats: [],
stageStats: [],
statusStats: [],
recentTasks: []
},
devices: {
overview: {
total: 0,
online: 0,
offline: 0,
busy: 0,
alarm: 0,
onlineRate: 0
},
typeStats: []
},
network: {
overview: {
totalDevices: 0,
okDevices: 0,
unstableDevices: 0,
offlineDevices: 0,
noDataDevices: 0,
attentionDevices: 0,
avgLatencyMs: null,
maxLatencyMs: null
},
samplingConfig: {
intervalMs: 1000,
timeoutMs: 800,
probeCount: 3,
packetSize: -1
},
statusStats: [],
focusDevices: []
},
ai: {
overview: {
tokenTotal: 0,
askCount: 0,
llmCallTotal: 0,
successCallTotal: 0,
failCallTotal: 0,
sessionCount: 0,
availableRouteCount: 0,
lastCallTime: ""
},
routeStats: [],
routeList: []
}
};
},
mounted: function () {
var self = this;
this.$nextTick(function () {
self.initCharts();
self.loadDashboard(false);
self.startAutoRefresh();
self.bindResize();
});
},
beforeDestroy: function () {
this.clearTimers();
this.unbindResize();
this.disposeCharts();
},
methods: {
loadDashboard: function (manual) {
var self = this;
if (this.refreshing) {
return;
}
this.refreshing = true;
$.ajax({
url: baseUrl + "/dashboard/summary/auth",
method: "GET",
headers: { token: localStorage.getItem("token") },
success: function (res) {
if (res && res.code === 200) {
self.applyData(res.data || {});
self.countdown = REFRESH_SECONDS;
return;
}
self.$message.error((res && res.msg) || "仪表盘数据加载失败");
},
error: function () {
if (manual) {
self.$message.error("仪表盘数据加载失败,请检查接口状态");
}
},
complete: function () {
self.loading = false;
self.refreshing = false;
}
});
},
applyData: function (payload) {
this.overview = payload.overview || this.overview;
this.tasks = payload.tasks || this.tasks;
this.devices = payload.devices || this.devices;
this.network = payload.network || this.network;
this.ai = payload.ai || this.ai;
this.updateCharts();
},
initCharts: function () {
this.disposeCharts();
this.charts.taskDirection = echarts.init(this.$refs.taskDirectionChart);
this.charts.taskStage = echarts.init(this.$refs.taskStageChart);
this.charts.deviceType = echarts.init(this.$refs.deviceTypeChart);
this.charts.networkStatus = echarts.init(this.$refs.networkStatusChart);
this.charts.aiRoute = echarts.init(this.$refs.aiRouteChart);
},
updateCharts: function () {
if (!this.charts.taskDirection || !this.charts.taskStage || !this.charts.deviceType || !this.charts.networkStatus || !this.charts.aiRoute) {
return;
}
this.charts.taskDirection.setOption(this.buildTaskDirectionOption());
this.charts.taskStage.setOption(this.buildTaskStageOption());
this.charts.deviceType.setOption(this.buildDeviceTypeOption());
this.charts.networkStatus.setOption(this.buildNetworkStatusOption());
this.charts.aiRoute.setOption(this.buildAiRouteOption());
this.resizeCharts();
},
buildTaskDirectionOption: function () {
var data = cloneMetricList(this.tasks.directionStats);
return {
color: ["#1f6fb2", "#f59a4a", "#2fa38e"],
tooltip: {
trigger: "item",
formatter: "{b}
{c} ({d}%)"
},
legend: {
bottom: 0,
itemWidth: 10,
itemHeight: 10,
textStyle: { color: "#60778d", fontSize: 12 }
},
series: [{
type: "pie",
radius: ["48%", "72%"],
center: ["50%", "46%"],
data: data,
label: {
color: "#40596f",
formatter: "{b}\n{c}"
},
labelLine: {
lineStyle: { color: "#c6d5e3" }
}
}]
};
},
buildTaskStageOption: function () {
var data = cloneMetricList(this.tasks.stageStats);
return {
color: ["#1f6fb2"],
grid: {
left: 48,
right: 18,
top: 24,
bottom: 28
},
tooltip: {
trigger: "axis",
axisPointer: { type: "shadow" }
},
xAxis: {
type: "category",
data: data.map(function (item) { return item.name; }),
axisLine: { lineStyle: { color: "#d4deea" } },
axisTick: { show: false },
axisLabel: { color: "#63798f", fontSize: 12 }
},
yAxis: {
type: "value",
splitLine: { lineStyle: { color: "#edf2f7" } },
axisLine: { show: false },
axisTick: { show: false },
axisLabel: { color: "#8699ad", fontSize: 12 }
},
series: [{
type: "bar",
barWidth: 32,
data: data.map(function (item) { return item.value; }),
itemStyle: {
borderRadius: [8, 8, 0, 0],
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: "#3388d2"
}, {
offset: 1,
color: "#1f6fb2"
}])
},
label: {
show: true,
position: "top",
color: "#36506d",
fontWeight: 600
}
}]
};
},
buildDeviceTypeOption: function () {
var data = cloneMetricList(this.devices.typeStats);
return {
color: ["#2fa38e", "#d8e2ec"],
legend: {
right: 0,
top: 0,
itemWidth: 10,
itemHeight: 10,
textStyle: { color: "#60778d", fontSize: 12 },
data: ["在线", "离线"]
},
grid: {
left: 76,
right: 12,
top: 42,
bottom: 18
},
tooltip: {
trigger: "axis",
axisPointer: { type: "shadow" }
},
xAxis: {
type: "value",
splitLine: { lineStyle: { color: "#edf2f7" } },
axisLine: { show: false },
axisTick: { show: false },
axisLabel: { color: "#8398ab", fontSize: 12 }
},
yAxis: {
type: "category",
data: data.map(function (item) { return item.name; }),
axisLine: { show: false },
axisTick: { show: false },
axisLabel: { color: "#60778d", fontSize: 12 }
},
series: [{
name: "在线",
type: "bar",
stack: "device",
barWidth: 18,
data: data.map(function (item) { return item.online; }),
itemStyle: {
borderRadius: [9, 0, 0, 9]
}
}, {
name: "离线",
type: "bar",
stack: "device",
barWidth: 18,
data: data.map(function (item) { return item.offline; }),
itemStyle: {
borderRadius: [0, 9, 9, 0]
}
}]
};
},
buildAiRouteOption: function () {
var data = cloneMetricList(this.ai.routeStats);
return {
color: ["#2fa38e", "#f59a4a", "#c8d4e1"],
tooltip: {
trigger: "item",
formatter: "{b}
{c} ({d}%)"
},
graphic: [{
type: "text",
left: "center",
top: "39%",
style: {
text: this.formatNumber(this.ai.overview.availableRouteCount || 0),
fill: "#1f3142",
fontSize: 28,
fontWeight: 700
}
}, {
type: "text",
left: "center",
top: "55%",
style: {
text: "可用路由",
fill: "#7c8fa4",
fontSize: 12
}
}],
legend: {
bottom: 0,
itemWidth: 10,
itemHeight: 10,
textStyle: { color: "#60778d", fontSize: 12 }
},
series: [{
type: "pie",
radius: ["56%", "76%"],
center: ["50%", "43%"],
avoidLabelOverlap: false,
label: { show: false },
labelLine: { show: false },
data: data
}]
};
},
buildNetworkStatusOption: function () {
var data = cloneMetricList(this.network.statusStats);
return {
color: ["#2fa38e", "#f59a4a", "#de5c5c", "#c8d4e1"],
tooltip: {
trigger: "item",
formatter: "{b}
{c} ({d}%)"
},
graphic: [{
type: "text",
left: "center",
top: "38%",
style: {
text: this.formatNumber(this.network.overview.attentionDevices || 0),
fill: "#1f3142",
fontSize: 26,
fontWeight: 700
}
}, {
type: "text",
left: "center",
top: "54%",
style: {
text: "需关注设备",
fill: "#7c8fa4",
fontSize: 12
}
}],
legend: {
bottom: 0,
itemWidth: 10,
itemHeight: 10,
textStyle: { color: "#60778d", fontSize: 12 }
},
series: [{
type: "pie",
radius: ["55%", "75%"],
center: ["50%", "42%"],
avoidLabelOverlap: false,
label: { show: false },
labelLine: { show: false },
data: data
}]
};
},
formatNumber: function (value) {
var num = Number(value || 0);
if (!isFinite(num)) {
return "0";
}
return num.toLocaleString("zh-CN");
},
formatLatency: function (value) {
var num;
if (value == null || value === "") {
return "--";
}
num = Number(value);
if (!isFinite(num)) {
return "--";
}
return num.toLocaleString("zh-CN", { maximumFractionDigits: 2 }) + " ms";
},
formatPacketSize: function (value) {
var num = Number(value);
if (!isFinite(num) || num < 0) {
return "系统默认";
}
return num + " B";
},
displayText: function (value, fallback) {
return value == null || value === "" ? (fallback || "") : value;
},
networkSamplingText: function () {
var config = this.network && this.network.samplingConfig ? this.network.samplingConfig : {};
return "采样 " + this.displayText(config.intervalMs, 0) + " ms / 超时 " + this.displayText(config.timeoutMs, 0) +
" ms / 每样本 " + this.displayText(config.probeCount, 0) + " 次 / 包大小 " + this.formatPacketSize(config.packetSize);
},
startAutoRefresh: function () {
var self = this;
this.clearTimers();
this.countdownTimer = setInterval(function () {
if (self.countdown <= 1) {
self.countdown = REFRESH_SECONDS;
self.loadDashboard(false);
return;
}
self.countdown -= 1;
}, 1000);
},
clearTimers: function () {
if (this.countdownTimer) {
clearInterval(this.countdownTimer);
this.countdownTimer = null;
}
},
bindResize: function () {
var self = this;
this.resizeHandler = function () {
self.resizeCharts();
};
window.addEventListener("resize", this.resizeHandler);
},
unbindResize: function () {
if (this.resizeHandler) {
window.removeEventListener("resize", this.resizeHandler);
this.resizeHandler = null;
}
},
resizeCharts: function () {
var key;
for (key in this.charts) {
if (this.charts.hasOwnProperty(key) && this.charts[key]) {
this.charts[key].resize();
}
}
},
disposeCharts: function () {
var key;
for (key in this.charts) {
if (this.charts.hasOwnProperty(key) && this.charts[key]) {
this.charts[key].dispose();
this.charts[key] = null;
}
}
},
resolveParentMenuApp: function () {
var parentDocument;
var parentRoot;
try {
if (!window.parent || window.parent === window || !window.parent.document) {
return null;
}
parentDocument = window.parent.document;
parentRoot = parentDocument.getElementById("app");
return parentRoot && parentRoot.__vue__ ? parentRoot.__vue__ : null;
} catch (e) {
return null;
}
},
resolveAbsoluteViewPath: function (path) {
if (!path) {
return "";
}
if (/^https?:\/\//.test(path) || path.indexOf(baseUrl) === 0) {
return path;
}
if (path.indexOf("/views/") === 0) {
return baseUrl + path;
}
if (path.indexOf("views/") === 0) {
return baseUrl + "/" + path;
}
return baseUrl + "/views/" + path.replace(/^\/+/, "");
},
findParentMenuByPath: function (targetPath, preferredName, preferredGroup) {
var parentApp = this.resolveParentMenuApp();
var targetBasePath = this.resolveAbsoluteViewPath(targetPath).split("#")[0].split("?")[0];
var fallback = null;
var i;
var j;
var group;
var item;
var itemBasePath;
if (!parentApp || !Array.isArray(parentApp.menus)) {
return null;
}
for (i = 0; i < parentApp.menus.length; i++) {
group = parentApp.menus[i];
for (j = 0; j < (group.subMenu || []).length; j++) {
item = group.subMenu[j];
if (!item || !item.url) {
continue;
}
itemBasePath = item.url.split("#")[0].split("?")[0];
if (!fallback && ((preferredName && item.name === preferredName) || itemBasePath === targetBasePath)) {
fallback = item;
}
if ((!preferredGroup || group.menu === preferredGroup) && itemBasePath === targetBasePath) {
return item;
}
}
}
return fallback;
},
openParentMenuView: function (targetPath, fallbackName, preferredName, preferredGroup) {
var targetMenu = this.findParentMenuByPath(targetPath, preferredName || fallbackName, preferredGroup);
var menuPath = targetMenu && targetMenu.url ? targetMenu.url : targetPath;
var menuName = targetMenu && targetMenu.name ? targetMenu.name : (fallbackName || preferredName || "");
if (window.parent && window.parent.index && typeof window.parent.index.loadView === "function") {
window.parent.index.loadView({
menuPath: menuPath,
menuName: menuName
});
return;
}
window.open(targetMenu && targetMenu.url ? targetMenu.url : this.resolveAbsoluteViewPath(targetPath), "_blank");
},
openMonitor: function () {
this.openParentMenuView("/views/watch/console.html", "监控画面", "监控画面", "监控系统");
},
openDevicePingAnalysis: function () {
this.openParentMenuView("/views/devicePingLog/devicePingLog.html", "设备网络分析", "设备网络分析");
}
}
});
}());