#
Junjie
6 小时以前 14cc8925be94a6c07e8e48278afc8f2d4aa284f1
src/main/webapp/static/js/dashboard/dashboard.js
@@ -11,8 +11,10 @@
    el: "#app",
    data: function () {
      return {
        rawPayload: null,
        loading: true,
        refreshing: false,
        switchingSystem: false,
        countdown: REFRESH_SECONDS,
        countdownTimer: null,
        resizeHandler: null,
@@ -20,6 +22,7 @@
          taskDirection: null,
          taskStage: null,
          deviceType: null,
          networkStatus: null,
          aiRoute: null
        },
        overview: {
@@ -56,6 +59,26 @@
          },
          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,
@@ -74,6 +97,12 @@
    },
    mounted: function () {
      var self = this;
      this.refreshDocumentTitle();
      if (window.WCS_I18N && typeof window.WCS_I18N.onReady === "function") {
        window.WCS_I18N.onReady(function () {
          self.refreshLocalizedState();
        });
      }
      this.$nextTick(function () {
        self.initCharts();
        self.loadDashboard(false);
@@ -87,6 +116,48 @@
      this.disposeCharts();
    },
    methods: {
      formatMessage: function (text, params) {
        var list = Array.isArray(params) ? params : [params];
        return String(text == null ? "" : text).replace(/\{(\d+)\}/g, function (match, index) {
          return list[index] == null ? "" : list[index];
        });
      },
      i18n: function (key, fallback, params) {
        if (window.WCS_I18N && typeof window.WCS_I18N.t === "function") {
          var translated = window.WCS_I18N.t(key, params);
          if (translated && translated !== key) {
            return translated;
          }
        }
        return this.formatMessage(fallback || key, params);
      },
      translateLegacyText: function (text) {
        if (text == null || text === "") {
          return text;
        }
        if (window.WCS_I18N && typeof window.WCS_I18N.tl === "function") {
          return window.WCS_I18N.tl(String(text));
        }
        return text;
      },
      getCurrentLocale: function () {
        if (window.WCS_I18N && typeof window.WCS_I18N.getLocale === "function") {
          return window.WCS_I18N.getLocale();
        }
        return "zh-CN";
      },
      refreshDocumentTitle: function () {
        document.title = this.i18n("dashboard.title", "系统仪表盘");
      },
      refreshLocalizedState: function () {
        this.refreshDocumentTitle();
        if (this.rawPayload) {
          this.applyData(this.rawPayload);
          return;
        }
        this.$forceUpdate();
        this.updateCharts();
      },
      loadDashboard: function (manual) {
        var self = this;
        if (this.refreshing) {
@@ -103,11 +174,11 @@
              self.countdown = REFRESH_SECONDS;
              return;
            }
            self.$message.error((res && res.msg) || "仪表盘数据加载失败");
            self.$message.error((res && res.msg) || self.i18n("dashboard.loadFailed", "仪表盘数据加载失败"));
          },
          error: function () {
            if (manual) {
              self.$message.error("仪表盘数据加载失败,请检查接口状态");
              self.$message.error(self.i18n("dashboard.loadFailedDetail", "仪表盘数据加载失败,请检查接口状态"));
            }
          },
          complete: function () {
@@ -117,27 +188,183 @@
        });
      },
      applyData: function (payload) {
        this.overview = payload.overview || this.overview;
        this.tasks = payload.tasks || this.tasks;
        this.devices = payload.devices || this.devices;
        this.ai = payload.ai || this.ai;
        var tasks = payload && payload.tasks ? payload.tasks : {};
        var devices = payload && payload.devices ? payload.devices : {};
        var network = payload && payload.network ? payload.network : {};
        var ai = payload && payload.ai ? payload.ai : {};
        this.rawPayload = payload || {};
        this.refreshDocumentTitle();
        this.overview = payload && payload.overview ? payload.overview : this.overview;
        this.tasks = {
          overview: tasks.overview || this.tasks.overview,
          directionStats: this.normalizeTaskDirectionMetrics(tasks.directionStats),
          stageStats: this.normalizeTaskStageMetrics(tasks.stageStats),
          statusStats: this.normalizeMetricList(tasks.statusStats),
          recentTasks: this.normalizeRecentTasks(tasks.recentTasks)
        };
        this.devices = {
          overview: devices.overview || this.devices.overview,
          typeStats: this.normalizeDeviceTypeStats(devices.typeStats)
        };
        this.network = {
          overview: network.overview || this.network.overview,
          samplingConfig: network.samplingConfig || this.network.samplingConfig,
          statusStats: this.normalizeNetworkStatusMetrics(network.statusStats),
          focusDevices: this.normalizeFocusDevices(network.focusDevices)
        };
        this.ai = {
          overview: ai.overview || this.ai.overview,
          routeStats: this.normalizeAiRouteStats(ai.routeStats),
          routeList: this.normalizeAiRouteList(ai.routeList)
        };
        this.updateCharts();
      },
      normalizeTaskDirectionMetrics: function (list) {
        var self = this;
        return cloneMetricList(list).map(function (item) {
          var result = Object.assign({}, item);
          if (item && item.name === "入库任务") {
            result.name = self.i18n("dashboard.taskDirectionInbound", "入库任务");
          } else if (item && item.name === "出库任务") {
            result.name = self.i18n("dashboard.taskDirectionOutbound", "出库任务");
          } else if (item && item.name === "移库任务") {
            result.name = self.i18n("dashboard.taskDirectionMove", "移库任务");
          } else {
            result.name = self.translateLegacyText(item && item.name);
          }
          return result;
        });
      },
      normalizeTaskStageMetrics: function (list) {
        var self = this;
        return cloneMetricList(list).map(function (item) {
          var result = Object.assign({}, item);
          if (item && item.name === "执行中") {
            result.name = self.i18n("dashboard.taskRunningLabel", "执行中");
          } else if (item && item.name === "待人工") {
            result.name = self.i18n("dashboard.taskManualLabel", "待人工");
          } else if (item && item.name === "已完成") {
            result.name = self.i18n("dashboard.taskCompletedLabel", "已完成");
          } else if (item && item.name === "新建") {
            result.name = self.i18n("dashboard.taskNewLabel", "新建");
          } else {
            result.name = self.translateLegacyText(item && item.name);
          }
          return result;
        });
      },
      normalizeMetricList: function (list) {
        var self = this;
        return cloneMetricList(list).map(function (item) {
          var result = Object.assign({}, item);
          result.name = self.translateLegacyText(item && item.name);
          return result;
        });
      },
      normalizeRecentTasks: function (list) {
        var self = this;
        return cloneMetricList(list).map(function (item) {
          var result = Object.assign({}, item);
          result.taskType = self.translateLegacyText(item && item.taskType);
          result.status = self.translateLegacyText(item && item.status);
          result.source = self.translateLegacyText(item && item.source);
          result.target = self.translateLegacyText(item && item.target);
          result.device = self.translateLegacyText(item && item.device);
          return result;
        });
      },
      normalizeAiRouteStats: function (list) {
        var self = this;
        return cloneMetricList(list).map(function (item) {
          var result = Object.assign({}, item);
          if (item && item.name === "可用") {
            result.name = self.i18n("dashboard.aiRouteStatusAvailable", "可用");
          } else if (item && item.name === "冷却中") {
            result.name = self.i18n("dashboard.aiRouteStatusCooling", "冷却中");
          } else if (item && item.name === "已禁用") {
            result.name = self.i18n("dashboard.aiRouteStatusDisabled", "已禁用");
          } else {
            result.name = self.translateLegacyText(item && item.name);
          }
          return result;
        });
      },
      normalizeNetworkStatusMetrics: function (list) {
        var self = this;
        return cloneMetricList(list).map(function (item) {
          var result = Object.assign({}, item);
          if (item && item.name === "正常") {
            result.name = self.i18n("dashboard.networkOkLabel", "正常");
          } else if (item && item.name === "波动") {
            result.name = self.i18n("dashboard.networkUnstableLabel", "波动");
          } else if (item && item.name === "超时/异常") {
            result.name = self.i18n("dashboard.networkOfflineLabel", "超时/异常");
          } else if (item && item.name === "暂无数据") {
            result.name = self.i18n("dashboard.networkNoDataLabel", "暂无数据");
          } else {
            result.name = self.translateLegacyText(item && item.name);
          }
          return result;
        });
      },
      normalizeDeviceTypeStats: function (list) {
        var self = this;
        return cloneMetricList(list).map(function (item) {
          var result = Object.assign({}, item);
          result.name = self.translateLegacyText(item && item.name);
          return result;
        });
      },
      normalizeAiRouteList: function (list) {
        var self = this;
        return cloneMetricList(list).map(function (item) {
          var result = Object.assign({}, item);
          if (result.statusType === "success") {
            result.statusText = self.i18n("dashboard.aiRouteStatusAvailable", "可用");
          } else if (result.statusType === "warning") {
            result.statusText = self.i18n("dashboard.aiRouteStatusCooling", "冷却中");
          } else {
            result.statusText = self.i18n("dashboard.aiRouteStatusDisabled", "已禁用");
          }
          result.lastError = self.translateLegacyText(item && item.lastError);
          return result;
        });
      },
      normalizeFocusDevices: function (list) {
        var self = this;
        return cloneMetricList(list).map(function (item) {
          var result = Object.assign({}, item);
          if (result.statusType === "success") {
            result.statusText = self.i18n("dashboard.networkOkLabel", "正常");
          } else if (result.statusType === "warning") {
            result.statusText = self.i18n("dashboard.networkUnstableLabel", "波动");
          } else if (result.statusType === "danger") {
            result.statusText = self.i18n("dashboard.networkOfflineLabel", "超时/异常");
          } else {
            result.statusText = self.i18n("dashboard.networkNoDataLabel", "暂无数据");
          }
          result.message = self.translateLegacyText(item && item.message);
          return result;
        });
      },
      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.aiRoute) {
        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();
      },
@@ -223,6 +450,8 @@
      },
      buildDeviceTypeOption: function () {
        var data = cloneMetricList(this.devices.typeStats);
        var onlineText = this.i18n("dashboard.deviceOnlineLegend", "在线");
        var offlineText = this.i18n("dashboard.deviceOfflineLegend", "离线");
        return {
          color: ["#2fa38e", "#d8e2ec"],
          legend: {
@@ -231,7 +460,7 @@
            itemWidth: 10,
            itemHeight: 10,
            textStyle: { color: "#60778d", fontSize: 12 },
            data: ["在线", "离线"]
            data: [onlineText, offlineText]
          },
          grid: {
            left: 76,
@@ -258,7 +487,7 @@
            axisLabel: { color: "#60778d", fontSize: 12 }
          },
          series: [{
            name: "在线",
            name: onlineText,
            type: "bar",
            stack: "device",
            barWidth: 18,
@@ -267,7 +496,7 @@
              borderRadius: [9, 0, 0, 9]
            }
          }, {
            name: "离线",
            name: offlineText,
            type: "bar",
            stack: "device",
            barWidth: 18,
@@ -301,7 +530,7 @@
            left: "center",
            top: "55%",
            style: {
              text: "可用路由",
              text: this.i18n("dashboard.chartCenter.availableRoutes", "可用路由"),
              fill: "#7c8fa4",
              fontSize: 12
            }
@@ -323,15 +552,104 @@
          }]
        };
      },
      buildNetworkStatusOption: function () {
        var data = cloneMetricList(this.network.statusStats);
        return {
          color: ["#2fa38e", "#f59a4a", "#de5c5c", "#c8d4e1"],
          tooltip: {
            trigger: "item",
            formatter: "{b}<br/>{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: this.i18n("dashboard.chartCenter.attentionDevices", "需关注设备"),
              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");
        return num.toLocaleString(this.getCurrentLocale());
      },
      formatLatency: function (value) {
        var num;
        if (value == null || value === "") {
          return "--";
        }
        num = Number(value);
        if (!isFinite(num)) {
          return "--";
        }
        return num.toLocaleString(this.getCurrentLocale(), { maximumFractionDigits: 2 }) + " ms";
      },
      formatPercentValue: function (value) {
        var num = Number(value);
        if (!isFinite(num)) {
          return "--";
        }
        return num.toLocaleString(this.getCurrentLocale(), { maximumFractionDigits: 2 }) + "%";
      },
      formatPacketSize: function (value) {
        var num = Number(value);
        if (!isFinite(num) || num < 0) {
          return this.i18n("dashboard.systemDefaultPacketSize", "系统默认");
        }
        return num.toLocaleString(this.getCurrentLocale()) + " B";
      },
      displayText: function (value, fallback) {
        return value == null || value === "" ? (fallback || "") : value;
      },
      showMessage: function (message, type) {
        if (this.$message && typeof this.$message === "function") {
          this.$message({
            message: message,
            type: type || "info"
          });
          return;
        }
        console[type === "error" ? "error" : "log"](message);
      },
      networkSamplingText: function () {
        var config = this.network && this.network.samplingConfig ? this.network.samplingConfig : {};
        return this.i18n("dashboard.networkSampling", "采样 {0} ms / 超时 {1} ms / 每样本 {2} 次 / 包大小 {3}", [
          this.displayText(config.intervalMs, 0),
          this.displayText(config.timeoutMs, 0),
          this.displayText(config.probeCount, 0),
          this.formatPacketSize(config.packetSize)
        ]);
      },
      startAutoRefresh: function () {
        var self = this;
@@ -381,15 +699,218 @@
          }
        }
      },
      openMonitor: function () {
      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: "/views/watch/console.html",
            menuName: "监控工作台"
            menuPath: menuPath,
            menuName: menuName
          });
          return;
        }
        window.open(baseUrl + "/views/watch/console.html", "_blank");
        window.open(targetMenu && targetMenu.url ? targetMenu.url : this.resolveAbsoluteViewPath(targetPath), "_blank");
      },
      openMonitor: function () {
        this.openParentMenuView(
          "/views/watch/console.html",
          this.i18n("dashboard.monitorView", "监控画面"),
          this.translateLegacyText("监控画面"),
          this.translateLegacyText("监控系统")
        );
      },
      syncParentSystemRunning: function (status) {
        try {
          if (window.parent && window.parent !== window) {
            window.parent.systemRunning = !!status;
          }
        } catch (e) {
        }
      },
      requestSystemSwitch: function (operatorType, password) {
        var self = this;
        this.switchingSystem = true;
        $.ajax({
          url: baseUrl + "/console/system/switch",
          headers: {
            token: localStorage.getItem("token")
          },
          data: {
            operatorType: operatorType,
            password: password || ""
          },
          method: "POST",
          success: function (res) {
            var status;
            if (res && res.code === 200) {
              status = !!(res.data && res.data.status);
              self.overview = Object.assign({}, self.overview, {
                systemRunning: status
              });
              self.syncParentSystemRunning(status);
              self.showMessage(
                status
                  ? self.i18n("dashboard.systemStarted", "系统已启动")
                  : self.i18n("dashboard.systemStopped", "系统已停止"),
                "success"
              );
              self.loadDashboard(false);
              return;
            }
            if (res && res.code === 403) {
              window.location.href = baseUrl + "/login";
              return;
            }
            self.showMessage(
              (res && res.msg) || self.i18n("dashboard.systemSwitchFailed", "系统状态切换失败"),
              "error"
            );
          },
          error: function () {
            self.showMessage(
              self.i18n("dashboard.systemSwitchFailedDetail", "系统状态切换失败,请检查接口状态"),
              "error"
            );
          },
          complete: function () {
            self.switchingSystem = false;
          }
        });
      },
      startSystem: function () {
        if (this.overview.systemRunning || this.switchingSystem) {
          return;
        }
        this.requestSystemSwitch(1);
      },
      maskStopSystemPromptInput: function () {
        setTimeout(function () {
          var input = document.querySelector(".el-message-box__wrapper .el-message-box__input input");
          if (!input) {
            return;
          }
          input.setAttribute("type", "text");
          input.setAttribute("name", "dashboard-stop-code");
          input.setAttribute("autocomplete", "new-password");
          input.setAttribute("autocapitalize", "off");
          input.setAttribute("autocorrect", "off");
          input.setAttribute("spellcheck", "false");
          input.setAttribute("data-form-type", "other");
          input.style.webkitTextSecurity = "disc";
        }, 30);
      },
      stopSystem: function () {
        var self = this;
        var proceed = function (password) {
          var cleanPassword = String(password == null ? "" : password).trim();
          if (!cleanPassword) {
            self.showMessage(self.i18n("dashboard.stopSystemPasswordRequired", "请输入停止系统口令"), "warning");
            return;
          }
          self.requestSystemSwitch(0, cleanPassword);
        };
        if (!this.overview.systemRunning || this.switchingSystem) {
          return;
        }
        if (typeof this.$prompt === "function") {
          this.$prompt(
            this.i18n("dashboard.stopSystemPrompt", "请输入口令,并停止 WCS 系统"),
            this.i18n("dashboard.stopSystemTitle", "停止系统"),
            {
              confirmButtonText: this.i18n("common.confirm", "确定"),
              cancelButtonText: this.i18n("common.cancel", "取消"),
              inputType: "text",
              inputPattern: /\S+/,
              inputErrorMessage: this.i18n("dashboard.stopSystemPasswordRequired", "请输入停止系统口令")
            }
          ).then(function (result) {
            proceed(result && result.value);
          }).catch(function () {
          });
          this.maskStopSystemPromptInput();
          return;
        }
        proceed(window.prompt(this.i18n("dashboard.stopSystemPrompt", "请输入口令,并停止 WCS 系统"), ""));
      },
      toggleSystem: function () {
        if (this.overview.systemRunning) {
          this.stopSystem();
          return;
        }
        this.startSystem();
      },
      openDevicePingAnalysis: function () {
        this.openParentMenuView(
          "/views/devicePingLog/devicePingLog.html",
          this.i18n("devicePingLog.title", "设备网络分析"),
          this.translateLegacyText("设备网络分析")
        );
      }
    }
  });