|
|
@@ -0,0 +1,356 @@
|
|
|
+(function ($, window) {
|
|
|
+ "use strict";
|
|
|
+
|
|
|
+ var config = window.kpiScreenMConfig || {};
|
|
|
+ var charts = {};
|
|
|
+ var refreshTimer = null;
|
|
|
+ var TEXT = "#5b6b7c";
|
|
|
+ var AXIS = "#c5d0dc";
|
|
|
+
|
|
|
+ function pad(num) {
|
|
|
+ return num < 10 ? "0" + num : String(num);
|
|
|
+ }
|
|
|
+
|
|
|
+ function updateClock() {
|
|
|
+ var now = new Date();
|
|
|
+ $("#screenTime").text(pad(now.getHours()) + ":" + pad(now.getMinutes()));
|
|
|
+ }
|
|
|
+
|
|
|
+ function escapeHtml(text) {
|
|
|
+ return String(text == null ? "" : text)
|
|
|
+ .replace(/&/g, "&")
|
|
|
+ .replace(/</g, "<")
|
|
|
+ .replace(/>/g, ">")
|
|
|
+ .replace(/"/g, """);
|
|
|
+ }
|
|
|
+
|
|
|
+ function num(value) {
|
|
|
+ var n = Number(value);
|
|
|
+ return isNaN(n) ? 0 : n;
|
|
|
+ }
|
|
|
+
|
|
|
+ function rateText(value) {
|
|
|
+ return num(value).toFixed(1) + "%";
|
|
|
+ }
|
|
|
+
|
|
|
+ function rateClass(value) {
|
|
|
+ var n = num(value);
|
|
|
+ if (n >= 95) {
|
|
|
+ return "is-good";
|
|
|
+ }
|
|
|
+ if (n >= 85) {
|
|
|
+ return "is-ok";
|
|
|
+ }
|
|
|
+ if (n >= 70) {
|
|
|
+ return "is-warn";
|
|
|
+ }
|
|
|
+ return "is-bad";
|
|
|
+ }
|
|
|
+
|
|
|
+ function rateColor(value) {
|
|
|
+ var level = rateClass(value);
|
|
|
+ if (level === "is-good") {
|
|
|
+ return "#1f9d57";
|
|
|
+ }
|
|
|
+ if (level === "is-ok") {
|
|
|
+ return "#2f7cff";
|
|
|
+ }
|
|
|
+ if (level === "is-warn") {
|
|
|
+ return "#ef9b1a";
|
|
|
+ }
|
|
|
+ return "#e35b5b";
|
|
|
+ }
|
|
|
+
|
|
|
+ function chartOf(id) {
|
|
|
+ if (!charts[id]) {
|
|
|
+ charts[id] = echarts.init(document.getElementById(id));
|
|
|
+ }
|
|
|
+ return charts[id];
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderGauge(id, value, neutral) {
|
|
|
+ var raw = num(value);
|
|
|
+ var ratio = Math.max(0, Math.min(raw, 100)) / 100;
|
|
|
+ var color = neutral ? "#9aa8b8" : rateColor(raw);
|
|
|
+ chartOf(id).setOption({
|
|
|
+ series: [{
|
|
|
+ type: "gauge",
|
|
|
+ min: 0,
|
|
|
+ max: 100,
|
|
|
+ startAngle: 210,
|
|
|
+ endAngle: -30,
|
|
|
+ radius: "96%",
|
|
|
+ center: ["50%", "58%"],
|
|
|
+ pointer: { show: false },
|
|
|
+ axisTick: { show: false },
|
|
|
+ splitLine: { show: false },
|
|
|
+ axisLabel: { show: false },
|
|
|
+ axisLine: {
|
|
|
+ lineStyle: {
|
|
|
+ width: 10,
|
|
|
+ color: [[ratio, color], [1, "rgba(30, 50, 80, 0.08)"]]
|
|
|
+ }
|
|
|
+ },
|
|
|
+ detail: {
|
|
|
+ offsetCenter: [0, "8%"],
|
|
|
+ color: "#1f2a37",
|
|
|
+ fontSize: 16,
|
|
|
+ fontWeight: 700,
|
|
|
+ formatter: function () {
|
|
|
+ return raw.toFixed(1) + "%";
|
|
|
+ }
|
|
|
+ },
|
|
|
+ data: [{ value: raw }]
|
|
|
+ }]
|
|
|
+ }, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderSummary(data) {
|
|
|
+ var summary = data.summary || {};
|
|
|
+ var kpi = data.kpi || {};
|
|
|
+ $("#statDay").text("统计日期 " + (data.day || "--"));
|
|
|
+ $("#statWindow").text(data.window || "08:00~次日08:00");
|
|
|
+ $("#planCount").text(summary.plan || 0);
|
|
|
+ $("#actualCount").text(summary.actual || 0);
|
|
|
+ $("#okCount").text(summary.ok || 0);
|
|
|
+ $("#ngCount").text(summary.ng || 0);
|
|
|
+ $("#repairCount").text(summary.repair || 0);
|
|
|
+ $("#shiftTitle").text(data.shiftTitle || "未排班");
|
|
|
+ $("#achieveExtra").text(summary.plan ? ((summary.actual || 0) + " / " + summary.plan) : "未排班次计划");
|
|
|
+ $("#fpyExtra").text("合格 " + (summary.ok || 0) + " / 检验 " + ((summary.ok || 0) + (summary.ng || 0)));
|
|
|
+ $("#availabilityRate").text(rateText(kpi.availability));
|
|
|
+ $("#performanceRate").text(rateText(kpi.performance));
|
|
|
+ $("#qualityRate").text(rateText(kpi.quality));
|
|
|
+ renderGauge("achieveGauge", kpi.achieve, !summary.plan);
|
|
|
+ renderGauge("fpyGauge", kpi.fpy, !(summary.ok || summary.ng || summary.actual));
|
|
|
+ renderGauge("oeeGauge", kpi.oee, !summary.plan && !(summary.ok || summary.ng));
|
|
|
+ }
|
|
|
+
|
|
|
+ function lineSeries(name, data, color) {
|
|
|
+ return {
|
|
|
+ name: name,
|
|
|
+ type: "line",
|
|
|
+ smooth: true,
|
|
|
+ symbol: "circle",
|
|
|
+ symbolSize: 6,
|
|
|
+ data: data || [],
|
|
|
+ itemStyle: { color: color },
|
|
|
+ lineStyle: { width: 2, color: color }
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderTrend(trend) {
|
|
|
+ trend = trend || {};
|
|
|
+ chartOf("trendChart").setOption({
|
|
|
+ tooltip: {
|
|
|
+ trigger: "axis",
|
|
|
+ confine: true,
|
|
|
+ backgroundColor: "rgba(255,255,255,0.96)",
|
|
|
+ borderColor: "#d7e2ef",
|
|
|
+ textStyle: { color: "#1f2a37", fontSize: 12 }
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ top: 0,
|
|
|
+ right: 0,
|
|
|
+ itemWidth: 10,
|
|
|
+ itemHeight: 8,
|
|
|
+ textStyle: { color: TEXT, fontSize: 11 },
|
|
|
+ data: ["达成率", "直通率", "OEE"]
|
|
|
+ },
|
|
|
+ grid: { left: 36, right: 10, top: 30, bottom: 28 },
|
|
|
+ xAxis: {
|
|
|
+ type: "category",
|
|
|
+ data: trend.labels || [],
|
|
|
+ axisLine: { lineStyle: { color: AXIS } },
|
|
|
+ axisTick: { show: false },
|
|
|
+ axisLabel: { color: TEXT, fontSize: 11 }
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: "value",
|
|
|
+ min: 0,
|
|
|
+ max: 100,
|
|
|
+ axisLine: { show: false },
|
|
|
+ axisTick: { show: false },
|
|
|
+ axisLabel: { color: TEXT, formatter: "{value}%", fontSize: 11 },
|
|
|
+ splitLine: { lineStyle: { color: "#eef2f7" } }
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ lineSeries("达成率", trend.achieve, "#2f7cff"),
|
|
|
+ lineSeries("直通率", trend.fpy, "#1f9d57"),
|
|
|
+ lineSeries("OEE", trend.oee, "#ef9b1a")
|
|
|
+ ]
|
|
|
+ }, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderHours(hours) {
|
|
|
+ hours = hours || {};
|
|
|
+ chartOf("hourChart").setOption({
|
|
|
+ tooltip: {
|
|
|
+ trigger: "axis",
|
|
|
+ confine: true,
|
|
|
+ backgroundColor: "rgba(255,255,255,0.96)",
|
|
|
+ borderColor: "#d7e2ef",
|
|
|
+ textStyle: { color: "#1f2a37", fontSize: 12 }
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ top: 0,
|
|
|
+ right: 0,
|
|
|
+ itemWidth: 10,
|
|
|
+ itemHeight: 8,
|
|
|
+ textStyle: { color: TEXT, fontSize: 11 },
|
|
|
+ data: ["完工", "节拍目标"]
|
|
|
+ },
|
|
|
+ grid: { left: 34, right: 10, top: 30, bottom: 28 },
|
|
|
+ xAxis: {
|
|
|
+ type: "category",
|
|
|
+ data: hours.labels || [],
|
|
|
+ axisLine: { lineStyle: { color: AXIS } },
|
|
|
+ axisTick: { show: false },
|
|
|
+ axisLabel: { color: TEXT, fontSize: 10, rotate: 30 }
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: "value",
|
|
|
+ minInterval: 1,
|
|
|
+ axisLine: { show: false },
|
|
|
+ axisTick: { show: false },
|
|
|
+ axisLabel: { color: TEXT, fontSize: 11 },
|
|
|
+ splitLine: { lineStyle: { color: "#eef2f7" } }
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: "完工",
|
|
|
+ type: "bar",
|
|
|
+ barWidth: 12,
|
|
|
+ data: hours.actual || [],
|
|
|
+ itemStyle: {
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
+ { offset: 0, color: "#5aa4ff" },
|
|
|
+ { offset: 1, color: "#2f7cff" }
|
|
|
+ ]),
|
|
|
+ barBorderRadius: [3, 3, 0, 0]
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "节拍目标",
|
|
|
+ type: "line",
|
|
|
+ data: hours.plan || [],
|
|
|
+ symbol: "none",
|
|
|
+ itemStyle: { color: "#ef9b1a" },
|
|
|
+ lineStyle: { type: "dashed", width: 2, color: "#ef9b1a" }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderShifts(rows) {
|
|
|
+ var html = "";
|
|
|
+ if (!rows || !rows.length) {
|
|
|
+ html = '<tr class="empty-row"><td colspan="9">暂无班次报工</td></tr>';
|
|
|
+ } else {
|
|
|
+ rows.forEach(function (row) {
|
|
|
+ html += '<tr class="' + (row.current ? "is-current" : "") + '">' +
|
|
|
+ "<td>" + escapeHtml(row.title) + (row.current ? " · 当前" : "") + "</td>" +
|
|
|
+ "<td>" + escapeHtml(row.range) + "</td>" +
|
|
|
+ "<td>" + num(row.plan) + "</td>" +
|
|
|
+ "<td>" + num(row.actual) + "</td>" +
|
|
|
+ "<td>" + num(row.ok) + "</td>" +
|
|
|
+ "<td>" + num(row.ng) + "</td>" +
|
|
|
+ "<td>" + num(row.repair) + "</td>" +
|
|
|
+ '<td class="' + rateClass(row.achieve) + '">' + rateText(row.achieve) + "</td>" +
|
|
|
+ '<td class="' + rateClass(row.fpy) + '">' + rateText(row.fpy) + "</td>" +
|
|
|
+ "</tr>";
|
|
|
+ });
|
|
|
+ }
|
|
|
+ $("#shiftBody").html(html);
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderProducts(rows) {
|
|
|
+ var html = "";
|
|
|
+ if (!rows || !rows.length) {
|
|
|
+ html = '<tr class="empty-row"><td colspan="4">暂无产品计划</td></tr>';
|
|
|
+ } else {
|
|
|
+ rows.forEach(function (row) {
|
|
|
+ html += "<tr>" +
|
|
|
+ "<td>" + escapeHtml(row.title) + "</td>" +
|
|
|
+ "<td>" + num(row.plan) + "</td>" +
|
|
|
+ "<td>" + num(row.actual) + "</td>" +
|
|
|
+ '<td class="' + rateClass(row.achieve) + '">' + rateText(row.achieve) + "</td>" +
|
|
|
+ "</tr>";
|
|
|
+ });
|
|
|
+ }
|
|
|
+ $("#productBody").html(html);
|
|
|
+ }
|
|
|
+
|
|
|
+ function applyData(data) {
|
|
|
+ renderSummary(data);
|
|
|
+ renderTrend(data.trend);
|
|
|
+ renderHours(data.hours);
|
|
|
+ renderShifts(data.shifts);
|
|
|
+ renderProducts(data.products);
|
|
|
+ resizeCharts();
|
|
|
+ }
|
|
|
+
|
|
|
+ function loadData() {
|
|
|
+ $.ajax({
|
|
|
+ url: config.apiUrl,
|
|
|
+ type: "GET",
|
|
|
+ dataType: "json",
|
|
|
+ timeout: 20000
|
|
|
+ }).done(function (res) {
|
|
|
+ if (res && (res.result === "true" || res.result === true) && res.data) {
|
|
|
+ applyData(res.data);
|
|
|
+ scheduleRefresh(config.refreshInterval || 60000);
|
|
|
+ } else {
|
|
|
+ scheduleRefresh(5000);
|
|
|
+ }
|
|
|
+ }).fail(function () {
|
|
|
+ scheduleRefresh(5000);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function scheduleRefresh(delay) {
|
|
|
+ if (refreshTimer) {
|
|
|
+ clearTimeout(refreshTimer);
|
|
|
+ }
|
|
|
+ refreshTimer = setTimeout(loadData, delay || 60000);
|
|
|
+ }
|
|
|
+
|
|
|
+ function resizeCharts() {
|
|
|
+ Object.keys(charts).forEach(function (key) {
|
|
|
+ charts[key].resize();
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function tryNativeBack() {
|
|
|
+ try {
|
|
|
+ if (window.AndroidBridge && typeof window.AndroidBridge.closePage === "function") {
|
|
|
+ window.AndroidBridge.closePage();
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ if (window.Android && typeof window.Android.closePage === "function") {
|
|
|
+ window.Android.closePage();
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ /* ignore */
|
|
|
+ }
|
|
|
+ if (window.history.length > 1) {
|
|
|
+ window.history.back();
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ $(function () {
|
|
|
+ $("#screenTitle").text(config.title || "生产运营看板");
|
|
|
+ updateClock();
|
|
|
+ setInterval(updateClock, 1000);
|
|
|
+ $("#btnBack").on("click", function () {
|
|
|
+ tryNativeBack();
|
|
|
+ });
|
|
|
+ loadData();
|
|
|
+ $(window).on("resize orientationchange", function () {
|
|
|
+ setTimeout(resizeCharts, 120);
|
|
|
+ });
|
|
|
+ });
|
|
|
+})(jQuery, window);
|