From bf64e8016283b18c04d5392dd9c002b921021af2 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期六, 07 三月 2026 09:47:04 +0800
Subject: [PATCH] #
---
src/main/webapp/components/WatchRgvCard.js | 386 ++--
src/main/java/com/zy/asrs/controller/WatchStationColorController.java | 152 +
src/main/webapp/components/MapCanvas.js | 562 ++++++-
src/main/webapp/views/watch/console.html | 794 ++++++++-
src/main/webapp/views/config/config.html | 2
src/main/java/com/zy/core/utils/WmsOperateUtils.java | 2
src/main/webapp/components/WatchDualCrnCard.js | 576 +++----
src/main/webapp/components/MonitorWorkbench.js | 661 ++++++++
src/main/java/com/zy/core/plugin/NormalProcess.java | 2
src/main/java/com/zy/core/plugin/XiaosongProcess.java | 2
src/main/webapp/components/MonitorCardKit.js | 147 +
src/main/webapp/views/watch/stationColorConfig.html | 290 +++
src/main/webapp/components/DevpCard.js | 645 ++++----
src/main/webapp/components/WatchCrnCard.js | 419 ++--
src/main/webapp/static/js/watch/stationColorConfig.js | 127 +
src/main/java/com/zy/core/enums/RedisKeyType.java | 1
src/main/webapp/views/deviceLogs/deviceLogs.html | 3
src/main/resources/application.yml | 2
src/main/java/com/zy/core/plugin/FakeProcess.java | 2
19 files changed, 3,566 insertions(+), 1,209 deletions(-)
diff --git a/src/main/java/com/zy/asrs/controller/WatchStationColorController.java b/src/main/java/com/zy/asrs/controller/WatchStationColorController.java
new file mode 100644
index 0000000..e8bd67b
--- /dev/null
+++ b/src/main/java/com/zy/asrs/controller/WatchStationColorController.java
@@ -0,0 +1,152 @@
+package com.zy.asrs.controller;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.core.annotations.ManagerAuth;
+import com.core.common.R;
+import com.zy.common.utils.RedisUtil;
+import com.zy.core.enums.RedisKeyType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.*;
+
+@RestController
+@RequestMapping("/watch/stationColor")
+public class WatchStationColorController {
+
+ @Autowired
+ private RedisUtil redisUtil;
+
+ @GetMapping("/config/auth")
+ @ManagerAuth
+ public R getConfig() {
+ return R.ok(buildResponseData(loadStoredColorMap()));
+ }
+
+ @PostMapping("/config/save/auth")
+ @ManagerAuth
+ public R saveConfig(@RequestBody Map<String, Object> payload) {
+ Map<String, String> storedMap = loadStoredColorMap();
+ Map<String, String> mergedMap = new LinkedHashMap<>(storedMap);
+
+ Object itemsObj = payload == null ? null : payload.get("items");
+ if (!(itemsObj instanceof List)) {
+ return R.error("璇蜂紶鍏ラ鑹查厤缃垪琛�");
+ }
+
+ List<?> items = (List<?>) itemsObj;
+ Set<String> allowedStatuses = getDefaultColorMap().keySet();
+ for (Object obj : items) {
+ if (!(obj instanceof Map)) {
+ continue;
+ }
+ Map<?, ?> item = (Map<?, ?>) obj;
+ String status = item.get("status") == null ? null : String.valueOf(item.get("status")).trim();
+ String color = item.get("color") == null ? null : String.valueOf(item.get("color")).trim();
+ if (status == null || status.isEmpty() || !allowedStatuses.contains(status)) {
+ continue;
+ }
+ mergedMap.put(status, normalizeColor(color, getDefaultColorMap().get(status)));
+ }
+
+ redisUtil.set(RedisKeyType.WATCH_STATION_COLOR_CONFIG.key, JSON.toJSONString(mergedMap));
+ return R.ok(buildResponseData(mergedMap));
+ }
+
+ private Map<String, Object> buildResponseData(Map<String, String> storedMap) {
+ Map<String, String> defaults = getDefaultColorMap();
+ List<Map<String, String>> items = new ArrayList<>();
+ for (Map<String, String> meta : getColorMetaList()) {
+ String status = meta.get("status");
+ Map<String, String> item = new LinkedHashMap<>(meta);
+ item.put("defaultColor", defaults.get(status));
+ item.put("color", normalizeColor(storedMap.get(status), defaults.get(status)));
+ items.add(item);
+ }
+ Map<String, Object> data = new LinkedHashMap<>();
+ data.put("items", items);
+ data.put("defaults", defaults);
+ return data;
+ }
+
+ private Map<String, String> loadStoredColorMap() {
+ Map<String, String> result = new LinkedHashMap<>(getDefaultColorMap());
+ Object object = redisUtil.get(RedisKeyType.WATCH_STATION_COLOR_CONFIG.key);
+ if (object == null) {
+ return result;
+ }
+ try {
+ JSONObject jsonObject;
+ if (object instanceof JSONObject) {
+ jsonObject = (JSONObject) object;
+ } else {
+ jsonObject = JSON.parseObject(String.valueOf(object));
+ }
+ if (jsonObject == null) {
+ return result;
+ }
+ for (String status : getDefaultColorMap().keySet()) {
+ String color = jsonObject.getString(status);
+ if (color != null) {
+ result.put(status, normalizeColor(color, getDefaultColorMap().get(status)));
+ }
+ }
+ } catch (Exception ignore) {
+ }
+ return result;
+ }
+
+ private Map<String, String> getDefaultColorMap() {
+ Map<String, String> defaults = new LinkedHashMap<>();
+ defaults.put("site-auto", "#78FF81");
+ defaults.put("site-auto-run", "#FA51F6");
+ defaults.put("site-auto-id", "#C4C400");
+ defaults.put("site-auto-run-id", "#30BFFC");
+ defaults.put("site-enable-in", "#18C7B8");
+ defaults.put("site-unauto", "#B8B8B8");
+ defaults.put("machine-pakin", "#30BFFC");
+ defaults.put("machine-pakout", "#97B400");
+ defaults.put("site-run-block", "#E69138");
+ return defaults;
+ }
+
+ private List<Map<String, String>> getColorMetaList() {
+ List<Map<String, String>> list = new ArrayList<>();
+ list.add(buildMeta("site-auto", "鑷姩", "绔欑偣鑷姩寰呭懡鏃剁殑棰滆壊銆�"));
+ list.add(buildMeta("site-auto-run", "鑷姩 + 鏈夌墿", "绔欑偣鑷姩杩愯涓旀湁鐗╋紝浣嗚繕娌℃湁宸ヤ綔鍙锋椂鐨勯鑹层��"));
+ list.add(buildMeta("site-auto-id", "鑷姩 + 宸ヤ綔鍙�", "绔欑偣鑷姩涓斿瓨鍦ㄥ伐浣滃彿锛屼絾褰撳墠鏃犵墿鏃剁殑棰滆壊銆�"));
+ list.add(buildMeta("site-auto-run-id", "鑷姩 + 鏈夌墿 + 宸ヤ綔鍙�", "鏅�氳繍琛屼腑鐨勭珯鐐归鑹诧紝鏈懡涓叆搴�/鍑哄簱鑼冨洿鏃朵娇鐢ㄣ��"));
+ list.add(buildMeta("site-enable-in", "鍚姩鍏ュ簱", "宸ヤ綔鍙蜂负 9998 鎴栫珯鐐瑰甫鍚姩鍏ュ簱鏍囪鏃剁殑棰滆壊銆�"));
+ list.add(buildMeta("machine-pakin", "鍏ュ簱浠诲姟", "宸ヤ綔鍙峰懡涓叆搴撹寖鍥存椂鐨勯鑹层��"));
+ list.add(buildMeta("machine-pakout", "鍑哄簱浠诲姟", "宸ヤ綔鍙峰懡涓嚭搴撹寖鍥存椂鐨勯鑹层��"));
+ list.add(buildMeta("site-run-block", "杩愯鍫靛", "绔欑偣澶勪簬杩愯鍫靛鏃剁殑棰滆壊銆�"));
+ list.add(buildMeta("site-unauto", "闈炶嚜鍔�", "绔欑偣闈炶嚜鍔ㄦ椂鐨勯鑹层��"));
+ return list;
+ }
+
+ private Map<String, String> buildMeta(String status, String name, String desc) {
+ Map<String, String> map = new LinkedHashMap<>();
+ map.put("status", status);
+ map.put("name", name);
+ map.put("desc", desc);
+ return map;
+ }
+
+ private String normalizeColor(String color, String fallback) {
+ if (color == null) {
+ return fallback;
+ }
+ String value = color.trim();
+ if (value.matches("^#[0-9a-fA-F]{6}$")) {
+ return value.toUpperCase();
+ }
+ if (value.matches("^#[0-9a-fA-F]{3}$")) {
+ return ("#" + value.charAt(1) + value.charAt(1) + value.charAt(2) + value.charAt(2) + value.charAt(3) + value.charAt(3)).toUpperCase();
+ }
+ if (value.matches("^0x[0-9a-fA-F]{6}$")) {
+ return ("#" + value.substring(2)).toUpperCase();
+ }
+ return fallback;
+ }
+}
diff --git a/src/main/java/com/zy/core/enums/RedisKeyType.java b/src/main/java/com/zy/core/enums/RedisKeyType.java
index 916a7bb..caf05c3 100644
--- a/src/main/java/com/zy/core/enums/RedisKeyType.java
+++ b/src/main/java/com/zy/core/enums/RedisKeyType.java
@@ -55,6 +55,7 @@
CRN_OUT_TASK_COMPLETE_STATION_INFO("crn_out_task_complete_station_info_"),
WATCH_CIRCLE_STATION_("watch_circle_station_"),
+ WATCH_STATION_COLOR_CONFIG("watch_station_color_config"),
STATION_CYCLE_LOAD_RESERVE("station_cycle_load_reserve"),
CURRENT_CIRCLE_TASK_CRN_NO("current_circle_task_crn_no_"),
diff --git a/src/main/java/com/zy/core/plugin/FakeProcess.java b/src/main/java/com/zy/core/plugin/FakeProcess.java
index 6ef204c..d03023b 100644
--- a/src/main/java/com/zy/core/plugin/FakeProcess.java
+++ b/src/main/java/com/zy/core/plugin/FakeProcess.java
@@ -496,7 +496,7 @@
// 1. 棣栧厛鏌ヨ鏄惁鏈夊凡瀹屾垚鐨勫紓姝ュ搷搴�
String response = wmsOperateUtils.queryAsyncInTaskResponse(barcode, stationIdVal);
- if (response != null) {
+ if (!Cools.isEmpty(response)) {
// 2. 鏈夊搷搴旂粨鏋滐紝澶勭悊鍝嶅簲
if (response.equals("FAILED") || response.startsWith("ERROR:")) {
// 璇锋眰澶辫触锛岄噸鏂板彂璧峰紓姝ヨ姹�
diff --git a/src/main/java/com/zy/core/plugin/NormalProcess.java b/src/main/java/com/zy/core/plugin/NormalProcess.java
index 047b07e..92f770b 100644
--- a/src/main/java/com/zy/core/plugin/NormalProcess.java
+++ b/src/main/java/com/zy/core/plugin/NormalProcess.java
@@ -149,7 +149,7 @@
// 1. 棣栧厛鏌ヨ鏄惁鏈夊凡瀹屾垚鐨勫紓姝ュ搷搴�
String response = wmsOperateUtils.queryAsyncInTaskResponse(barcode, stationIdVal);
- if (response != null) {
+ if (!Cools.isEmpty(response)) {
// 2. 鏈夊搷搴旂粨鏋滐紝澶勭悊鍝嶅簲
if (response.equals("FAILED") || response.startsWith("ERROR:")) {
// 璇锋眰澶辫触锛岄噸鏂板彂璧峰紓姝ヨ姹�
diff --git a/src/main/java/com/zy/core/plugin/XiaosongProcess.java b/src/main/java/com/zy/core/plugin/XiaosongProcess.java
index 6750c53..2fdf863 100644
--- a/src/main/java/com/zy/core/plugin/XiaosongProcess.java
+++ b/src/main/java/com/zy/core/plugin/XiaosongProcess.java
@@ -176,7 +176,7 @@
// 1. 棣栧厛鏌ヨ鏄惁鏈夊凡瀹屾垚鐨勫紓姝ュ搷搴�
String response = wmsOperateUtils.queryAsyncInTaskResponse(barcode, stationIdVal);
- if (response != null) {
+ if (!Cools.isEmpty(response)) {
// 2. 鏈夊搷搴旂粨鏋滐紝澶勭悊鍝嶅簲
if (response.equals("FAILED") || response.startsWith("ERROR:")) {
// 璇锋眰澶辫触锛岄噸鏂板彂璧峰紓姝ヨ姹�
diff --git a/src/main/java/com/zy/core/utils/WmsOperateUtils.java b/src/main/java/com/zy/core/utils/WmsOperateUtils.java
index 4ef1979..27b19bd 100644
--- a/src/main/java/com/zy/core/utils/WmsOperateUtils.java
+++ b/src/main/java/com/zy/core/utils/WmsOperateUtils.java
@@ -103,7 +103,7 @@
.setTimeout(30, TimeUnit.SECONDS)
.build()
.doPost();
- if (response != null) {
+ if (!Cools.isEmpty(response)) {
JSONObject jsonObject = JSON.parseObject(response);
if (jsonObject.getInteger("code") == 200) {
result = 1;
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 6e76f8b..6d9b66c 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -1,6 +1,6 @@
# 绯荤粺鐗堟湰淇℃伅
app:
- version: 1.0.4.7
+ version: 1.0.4.8
version-type: dev # prd 鎴� dev
server:
diff --git a/src/main/webapp/components/DevpCard.js b/src/main/webapp/components/DevpCard.js
index 39e69eb..d699cfc 100644
--- a/src/main/webapp/components/DevpCard.js
+++ b/src/main/webapp/components/DevpCard.js
@@ -1,49 +1,63 @@
Vue.component("devp-card", {
template: `
- <div>
- <div style="display: flex;margin-bottom: 10px;">
- <div style="width: 100%;">杈撻�佺洃鎺�</div>
- <div style="width: 100%;text-align: right;display: flex;"><el-input size="mini" v-model="searchStationId" placeholder="璇疯緭鍏ョ珯鍙�"></el-input><el-button @click="getDevpStateInfo" size="mini">鏌ヨ</el-button></div>
+ <div class="mc-root">
+ <div class="mc-toolbar">
+ <div class="mc-title">杈撻�佺洃鎺�</div>
+ <div class="mc-search">
+ <input class="mc-input" v-model="searchStationId" placeholder="璇疯緭鍏ョ珯鍙�" />
+ <button type="button" class="mc-btn mc-btn-ghost" @click="getDevpStateInfo">鏌ヨ</button>
</div>
- <div style="margin-bottom: 10px;" v-if="!readOnly">
- <div style="margin-bottom: 5px;">
- <el-button v-if="showControl" @click="openControl" size="mini">鍏抽棴鎺у埗涓績</el-button>
- <el-button v-else @click="openControl" size="mini">鎵撳紑鎺у埗涓績</el-button>
- </div>
- <div v-if="showControl" style="display: flex;justify-content: space-between;flex-wrap: wrap;">
- <div style="margin-bottom: 10px;width: 33%;"><el-input size="mini" v-model="controlParam.stationId" placeholder="绔欏彿"></el-input></div>
- <div style="margin-bottom: 10px;width: 33%;"><el-input size="mini" v-model="controlParam.taskNo" placeholder="宸ヤ綔鍙�"></el-input></div>
- <div style="margin-bottom: 10px;width: 33%;"><el-input size="mini" v-model="controlParam.targetStationId" placeholder="鐩爣绔�"></el-input></div>
- <div style="margin-bottom: 10px;"><el-button @click="controlCommand()" size="mini">涓嬪彂</el-button></div>
- <div style="margin-bottom: 10px;"><el-button @click="resetCommand()" size="mini">澶嶄綅</el-button></div>
- </div>
+ </div>
+
+ <div v-if="!readOnly" class="mc-control-toggle">
+ <button type="button" class="mc-btn mc-btn-ghost" @click="openControl">
+ {{ showControl ? '鏀惰捣鎺у埗涓績' : '鎵撳紑鎺у埗涓績' }}
+ </button>
+ </div>
+
+ <div v-if="showControl" class="mc-control">
+ <div class="mc-control-grid">
+ <label class="mc-field">
+ <span class="mc-field-label">绔欏彿</span>
+ <input class="mc-input" v-model="controlParam.stationId" placeholder="渚嬪 101" />
+ </label>
+ <label class="mc-field">
+ <span class="mc-field-label">宸ヤ綔鍙�</span>
+ <input class="mc-input" v-model="controlParam.taskNo" placeholder="杈撳叆宸ヤ綔鍙�" />
+ </label>
+ <label class="mc-field mc-span-2">
+ <span class="mc-field-label">鐩爣绔�</span>
+ <input class="mc-input" v-model="controlParam.targetStationId" placeholder="杈撳叆鐩爣绔欏彿" />
+ </label>
+ <div class="mc-action-row">
+ <button type="button" class="mc-btn" @click="controlCommand">涓嬪彂</button>
+ <button type="button" class="mc-btn mc-btn-soft" @click="resetCommand">澶嶄綅</button>
+ </div>
</div>
- <div style="max-height: 55vh; overflow:auto;">
- <el-collapse v-model="activeNames" accordion>
- <el-collapse-item v-for="(item) in displayStationList" :name="item.stationId">
- <template slot="title">
- <div style="width: 100%;display: flex;">
- <div style="width: 50%;">{{ item.stationId }}绔�</div>
- <div style="width: 50%;text-align: right;">
- <el-tag v-if="item.autoing" type="success" size="small">鑷姩</el-tag>
- <el-tag v-else type="warning" size="small">鎵嬪姩</el-tag>
- </div>
- </div>
- </template>
- <el-descriptions border direction="vertical">
- <el-descriptions-item label="缂栧彿">{{ item.stationId }}</el-descriptions-item>
- <el-descriptions-item label="宸ヤ綔鍙�">{{ item.taskNo }}</el-descriptions-item>
- <el-descriptions-item label="鐩爣绔�">{{ item.targetStaNo }}</el-descriptions-item>
- <el-descriptions-item label="妯″紡">{{ item.autoing ? '鑷姩' : '鎵嬪姩' }}</el-descriptions-item>
- <el-descriptions-item label="鏈夌墿">{{ item.loading ? '鏈�' : '鏃�' }}</el-descriptions-item>
- <el-descriptions-item label="鍙叆">{{ item.inEnable ? 'Y' : 'N' }}</el-descriptions-item>
- <el-descriptions-item label="鍙嚭">{{ item.outEnable ? 'Y' : 'N' }}</el-descriptions-item>
- <el-descriptions-item label="绌烘澘淇″彿">{{ item.emptyMk ? 'Y' : 'N' }}</el-descriptions-item>
- <el-descriptions-item label="婊℃澘淇″彿">{{ item.fullPlt ? 'Y' : 'N' }}</el-descriptions-item>
- <el-descriptions-item label="杩愯闃诲">{{ item.runBlock ? 'Y' : 'N' }}</el-descriptions-item>
- <el-descriptions-item label="鍚姩鍏ュ簱">{{ item.enableIn ? 'Y' : 'N' }}</el-descriptions-item>
- <el-descriptions-item label="鎵樼洏楂樺害">{{ item.palletHeight }}</el-descriptions-item>
- <el-descriptions-item label="鏉$爜">
+ </div>
+
+ <div class="mc-collapse">
+ <div
+ v-for="item in displayStationList"
+ :key="item.stationId"
+ :class="['mc-item', { 'is-open': isActive(item.stationId) }]"
+ >
+ <button type="button" class="mc-head" @click="toggleItem(item)">
+ <div class="mc-head-main">
+ <div class="mc-head-title">{{ item.stationId }}绔�</div>
+ <div class="mc-head-subtitle">浠诲姟 {{ orDash(item.taskNo) }} | 鐩爣绔� {{ orDash(item.targetStaNo) }}</div>
+ </div>
+ <div class="mc-head-right">
+ <span :class="['mc-badge', 'is-' + getStatusTone(item)]">{{ getStatusLabel(item) }}</span>
+ <span class="mc-chevron">{{ isActive(item.stationId) ? '鈻�' : '鈻�' }}</span>
+ </div>
+ </button>
+
+ <div v-if="isActive(item.stationId)" class="mc-body">
+ <div class="mc-detail-grid">
+ <div v-for="entry in buildDetailEntries(item)" :key="entry.label" class="mc-detail-cell">
+ <div class="mc-detail-label">{{ entry.label }}</div>
+ <div v-if="entry.type === 'barcode'" class="mc-detail-value mc-code">
<el-popover v-if="item.barcode" placement="top" width="460" trigger="hover">
<div style="text-align: center;">
<img
@@ -53,135 +67,287 @@
/>
<div style="margin-top: 4px; font-size: 12px; word-break: break-all;">{{ item.barcode }}</div>
</div>
- <span slot="reference" @click.stop="handleBarcodeClick(item)" style="cursor: pointer; color: #409EFF;">{{ item.barcode }}</span>
+ <span
+ slot="reference"
+ @click.stop="handleBarcodeClick(item)"
+ style="cursor: pointer; color: #4677a4; font-weight: 600;"
+ >{{ entry.value }}</span>
</el-popover>
- <span v-else @click.stop="handleBarcodeClick(item)" style="cursor: pointer; color: #409EFF;">-</span>
- </el-descriptions-item>
- <el-descriptions-item label="閲嶉噺">{{ item.weight }}</el-descriptions-item>
- <el-descriptions-item label="浠诲姟鍙啓鍖�">{{ item.taskWriteIdx }}</el-descriptions-item>
- <el-descriptions-item label="鏁呴殰浠g爜">{{ item.error }}</el-descriptions-item>
- <el-descriptions-item label="鏁呴殰淇℃伅">{{ item.errorMsg }}</el-descriptions-item>
- <el-descriptions-item label="鎵╁睍鏁版嵁">{{ item.extend }}</el-descriptions-item>
- </el-descriptions>
- </el-collapse-item>
- </el-collapse>
+ <span
+ v-else
+ @click.stop="handleBarcodeClick(item)"
+ style="cursor: pointer; color: #4677a4; font-weight: 600;"
+ >{{ entry.value }}</span>
+ </div>
+ <div v-else class="mc-detail-value" :class="{ 'mc-code': entry.code }">{{ entry.value }}</div>
+ </div>
+ </div>
+ </div>
</div>
- <div style="display:flex; justify-content:flex-end; margin-top:8px;">
- <el-pagination
- small
- @current-change="handlePageChange"
- @size-change="handleSizeChange"
- :current-page="currentPage"
- :page-size="pageSize"
- :page-sizes="[10,20,50,100]"
- layout="total, prev, pager, next"
- :total="stationList.length">
- </el-pagination>
- </div>
+
+ <div v-if="displayStationList.length === 0" class="mc-empty">褰撳墠娌℃湁鍙睍绀虹殑绔欑偣鏁版嵁</div>
+ </div>
+
+ <div class="mc-footer">
+ <button type="button" class="mc-page-btn" :disabled="currentPage <= 1" @click="handlePageChange(currentPage - 1)">涓婁竴椤�</button>
+ <span>{{ currentPage }} / {{ totalPages }}</span>
+ <button type="button" class="mc-page-btn" :disabled="currentPage >= totalPages" @click="handlePageChange(currentPage + 1)">涓嬩竴椤�</button>
+ </div>
</div>
- `,
+ `,
props: {
- param: {
- type: Object,
- default: () => ({})
- },
- autoRefresh: {
- type: Boolean,
- default: true
- },
- readOnly: {
- type: Boolean,
- default: false
- }
+ param: { type: Object, default: function () { return {}; } },
+ items: { type: Array, default: null },
+ autoRefresh: { type: Boolean, default: true },
+ readOnly: { type: Boolean, default: false }
},
- data() {
+ data: function () {
return {
stationList: [],
- fullStationList: [],
activeNames: "",
searchStationId: "",
showControl: false,
controlParam: {
stationId: "",
taskNo: "",
- targetStationId: "",
+ targetStationId: ""
},
barcodePreviewCache: {},
- pageSize: 25,
+ pageSize: 12,
currentPage: 1,
timer: null
};
},
- created() {
- if (this.autoRefresh) {
- this.timer = setInterval(() => {
- this.getDevpStateInfo();
- }, 1000);
- }
- },
- beforeDestroy() {
- if (this.timer) {
- clearInterval(this.timer);
- }
- },
computed: {
- displayStationList() {
- const start = (this.currentPage - 1) * this.pageSize;
- const end = start + this.pageSize;
- return this.stationList.slice(start, end);
+ sourceList: function () {
+ return Array.isArray(this.items) ? this.items : this.stationList;
+ },
+ filteredStationList: function () {
+ var keyword = String(this.searchStationId || "").trim();
+ if (!keyword) {
+ return this.sourceList;
+ }
+ return this.sourceList.filter(function (item) {
+ return String(item.stationId) === keyword;
+ });
+ },
+ displayStationList: function () {
+ var start = (this.currentPage - 1) * this.pageSize;
+ return this.filteredStationList.slice(start, start + this.pageSize);
+ },
+ totalPages: function () {
+ return Math.max(1, Math.ceil(this.filteredStationList.length / this.pageSize) || 1);
}
},
watch: {
- param: {
- handler(newVal, oldVal) {
- if (newVal && newVal.stationId && newVal.stationId != 0) {
- this.activeNames = newVal.stationId;
- this.searchStationId = newVal.stationId;
- }
- },
- deep: true, // 娣卞害鐩戝惉宓屽灞炴��
- immediate: true, // 绔嬪嵆瑙﹀彂涓�娆★紙鍙�夛級
+ items: function () {
+ this.afterDataRefresh();
},
+ param: {
+ deep: true,
+ immediate: true,
+ handler: function (newVal) {
+ if (newVal && newVal.stationId && newVal.stationId !== 0) {
+ this.focusStation(newVal.stationId);
+ }
+ }
+ }
+ },
+ created: function () {
+ MonitorCardKit.ensureStyles();
+ if (this.autoRefresh) {
+ this.timer = setInterval(this.getDevpStateInfo, 1000);
+ }
+ },
+ beforeDestroy: function () {
+ if (this.timer) {
+ clearInterval(this.timer);
+ this.timer = null;
+ }
},
methods: {
- handlePageChange(page) {
+ orDash: function (value) {
+ return MonitorCardKit.orDash(value);
+ },
+ getStatusLabel: function (item) {
+ return item && item.autoing ? "鑷姩" : "鎵嬪姩";
+ },
+ getStatusTone: function (item) {
+ return MonitorCardKit.statusTone(this.getStatusLabel(item));
+ },
+ isActive: function (stationId) {
+ return String(this.activeNames) === String(stationId);
+ },
+ toggleItem: function (item) {
+ var next = String(item.stationId);
+ this.activeNames = this.activeNames === next ? "" : next;
+ },
+ focusStation: function (stationId) {
+ this.searchStationId = String(stationId);
+ var index = this.filteredStationList.findIndex(function (item) {
+ return String(item.stationId) === String(stationId);
+ });
+ this.currentPage = index >= 0 ? Math.floor(index / this.pageSize) + 1 : 1;
+ this.activeNames = String(stationId);
+ },
+ afterDataRefresh: function () {
+ if (this.currentPage > this.totalPages) {
+ this.currentPage = this.totalPages;
+ }
+ if (this.activeNames) {
+ var exists = this.filteredStationList.some(function (item) {
+ return String(item.stationId) === String(this.activeNames);
+ }, this);
+ if (!exists) {
+ this.activeNames = "";
+ }
+ }
+ },
+ handlePageChange: function (page) {
+ if (page < 1 || page > this.totalPages) {
+ return;
+ }
this.currentPage = page;
},
- handleSizeChange(size) {
- this.pageSize = size;
- this.currentPage = 1;
+ getDevpStateInfo: function () {
+ if (this.$root && this.$root.sendWs) {
+ this.$root.sendWs(JSON.stringify({
+ url: "/console/latest/data/station",
+ data: {}
+ }));
+ }
},
- getBarcodePreview(barcode) {
- const value = String(barcode || "").trim();
+ setStationList: function (res) {
+ if (res && res.code === 200) {
+ this.stationList = res.data || [];
+ this.afterDataRefresh();
+ }
+ },
+ openControl: function () {
+ this.showControl = !this.showControl;
+ },
+ buildDetailEntries: function (item) {
+ return [
+ { label: "缂栧彿", value: this.orDash(item.stationId) },
+ { label: "宸ヤ綔鍙�", value: this.orDash(item.taskNo) },
+ { label: "鐩爣绔�", value: this.orDash(item.targetStaNo) },
+ { label: "妯″紡", value: item.autoing ? "鑷姩" : "鎵嬪姩" },
+ { label: "鏈夌墿", value: MonitorCardKit.yesNo(item.loading) },
+ { label: "鍙叆", value: MonitorCardKit.yesNo(item.inEnable) },
+ { label: "鍙嚭", value: MonitorCardKit.yesNo(item.outEnable) },
+ { label: "绌烘澘淇″彿", value: MonitorCardKit.yesNo(item.emptyMk) },
+ { label: "婊℃澘淇″彿", value: MonitorCardKit.yesNo(item.fullPlt) },
+ { label: "杩愯闃诲", value: MonitorCardKit.yesNo(item.runBlock) },
+ { label: "鍚姩鍏ュ簱", value: MonitorCardKit.yesNo(item.enableIn) },
+ { label: "鎵樼洏楂樺害", value: this.orDash(item.palletHeight) },
+ { label: "鏉$爜", value: this.orDash(item.barcode), code: true, type: "barcode" },
+ { label: "閲嶉噺", value: this.orDash(item.weight) },
+ { label: "浠诲姟鍙啓鍖�", value: this.orDash(item.taskWriteIdx) },
+ { label: "鏁呴殰浠g爜", value: this.orDash(item.error) },
+ { label: "鏁呴殰淇℃伅", value: this.orDash(item.errorMsg) },
+ { label: "鎵╁睍鏁版嵁", value: this.orDash(item.extend) }
+ ];
+ },
+ postControl: function (url, payload) {
+ $.ajax({
+ url: baseUrl + url,
+ headers: {
+ token: localStorage.getItem("token")
+ },
+ contentType: "application/json",
+ method: "post",
+ data: JSON.stringify(payload),
+ success: function (res) {
+ if (res && res.code === 200) {
+ MonitorCardKit.showMessage(this, res.msg || "鎿嶄綔鎴愬姛", "success");
+ } else {
+ MonitorCardKit.showMessage(this, (res && res.msg) || "鎿嶄綔澶辫触", "warning");
+ }
+ }.bind(this)
+ });
+ },
+ handleBarcodeClick: function (item) {
+ if (this.readOnly || !item || item.stationId == null) {
+ return;
+ }
+ this.$prompt("璇疯緭鍏ユ柊鐨勬潯鐮佸�硷紙鍙暀绌烘竻绌猴級", "淇敼鏉$爜", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ inputValue: item.barcode || "",
+ inputPlaceholder: "璇疯緭鍏ユ潯鐮�"
+ }).then(function (result) {
+ this.updateStationBarcode(item.stationId, result && result.value == null ? "" : String(result.value).trim());
+ }.bind(this)).catch(function () {});
+ },
+ updateStationBarcode: function (stationId, barcode) {
+ $.ajax({
+ url: baseUrl + "/station/command/barcode",
+ headers: {
+ token: localStorage.getItem("token")
+ },
+ contentType: "application/json",
+ method: "post",
+ data: JSON.stringify({
+ stationId: stationId,
+ barcode: barcode
+ }),
+ success: function (res) {
+ if (res && res.code === 200) {
+ this.syncLocalBarcode(stationId, barcode);
+ MonitorCardKit.showMessage(this, "鏉$爜淇敼鎴愬姛", "success");
+ } else {
+ MonitorCardKit.showMessage(this, (res && res.msg) || "鏉$爜淇敼澶辫触", "warning");
+ }
+ }.bind(this)
+ });
+ },
+ syncLocalBarcode: function (stationId, barcode) {
+ var update = function (list) {
+ if (!list || !list.length) {
+ return;
+ }
+ list.forEach(function (item) {
+ if (item.stationId == stationId) {
+ item.barcode = barcode;
+ }
+ });
+ };
+ update(this.stationList);
+ if (Array.isArray(this.items)) {
+ update(this.items);
+ }
+ },
+ getBarcodePreview: function (barcode) {
+ var value = String(barcode || "").trim();
if (!value) {
return "";
}
if (this.barcodePreviewCache[value]) {
return this.barcodePreviewCache[value];
}
- const encodeResult = this.encodeCode128(value);
+ var encodeResult = this.encodeCode128(value);
if (!encodeResult) {
return "";
}
- const svg = this.buildCode128Svg(encodeResult, value);
- const dataUrl = "data:image/svg+xml;charset=UTF-8," + encodeURIComponent(svg);
+ var svg = this.buildCode128Svg(encodeResult, value);
+ var dataUrl = "data:image/svg+xml;charset=UTF-8," + encodeURIComponent(svg);
this.$set(this.barcodePreviewCache, value, dataUrl);
return dataUrl;
},
- encodeCode128(value) {
+ encodeCode128: function (value) {
if (!value) {
return null;
}
- const isNumeric = /^\d+$/.test(value);
+ var isNumeric = /^\d+$/.test(value);
if (isNumeric && value.length % 2 === 0) {
return this.encodeCode128C(value);
}
return this.encodeCode128B(value);
},
- encodeCode128B(value) {
- const codes = [104];
- for (let i = 0; i < value.length; i++) {
- const code = value.charCodeAt(i) - 32;
+ encodeCode128B: function (value) {
+ var codes = [104];
+ for (var i = 0; i < value.length; i++) {
+ var code = value.charCodeAt(i) - 32;
if (code < 0 || code > 94) {
return null;
}
@@ -189,64 +355,62 @@
}
return this.buildCode128Pattern(codes);
},
- encodeCode128C(value) {
+ encodeCode128C: function (value) {
if (value.length % 2 !== 0) {
return null;
}
- const codes = [105];
- for (let i = 0; i < value.length; i += 2) {
+ var codes = [105];
+ for (var i = 0; i < value.length; i += 2) {
codes.push(parseInt(value.substring(i, i + 2), 10));
}
return this.buildCode128Pattern(codes);
},
- buildCode128Pattern(codes) {
- const patterns = this.getCode128Patterns();
- let checksum = codes[0];
- for (let i = 1; i < codes.length; i++) {
+ buildCode128Pattern: function (codes) {
+ var patterns = this.getCode128Patterns();
+ var checksum = codes[0];
+ for (var i = 1; i < codes.length; i++) {
checksum += codes[i] * i;
}
- const checkCode = checksum % 103;
- const fullCodes = codes.concat([checkCode, 106]);
- let bars = "";
- for (let i = 0; i < fullCodes.length; i++) {
- const code = fullCodes[i];
- if (patterns[code] == null) {
+ var checkCode = checksum % 103;
+ var fullCodes = codes.concat([checkCode, 106]);
+ var bars = "";
+ for (var j = 0; j < fullCodes.length; j++) {
+ if (patterns[fullCodes[j]] == null) {
return null;
}
- bars += patterns[code];
+ bars += patterns[fullCodes[j]];
}
bars += "11";
return bars;
},
- buildCode128Svg(bars, text) {
- const quietModules = 20;
- const modules = quietModules * 2 + bars.split("").reduce((sum, n) => sum + parseInt(n, 10), 0);
- const moduleWidth = modules > 300 ? 1 : 2;
- const width = modules * moduleWidth;
- const barTop = 10;
- const barHeight = 110;
- let x = quietModules * moduleWidth;
- let black = true;
- let rects = "";
- for (let i = 0; i < bars.length; i++) {
- const w = parseInt(bars[i], 10) * moduleWidth;
+ buildCode128Svg: function (bars, text) {
+ var quietModules = 20;
+ var modules = quietModules * 2 + bars.split("").reduce(function (sum, n) {
+ return sum + parseInt(n, 10);
+ }, 0);
+ var moduleWidth = modules > 300 ? 1 : 2;
+ var width = modules * moduleWidth;
+ var barTop = 10;
+ var barHeight = 110;
+ var x = quietModules * moduleWidth;
+ var black = true;
+ var rects = "";
+ for (var i = 0; i < bars.length; i++) {
+ var w = parseInt(bars[i], 10) * moduleWidth;
if (black) {
rects += '<rect x="' + x + '" y="' + barTop + '" width="' + w + '" height="' + barHeight + '" fill="#000" shape-rendering="crispEdges" />';
}
x += w;
black = !black;
}
- return (
- '<svg xmlns="http://www.w3.org/2000/svg" width="' + width + '" height="145" viewBox="0 0 ' + width + ' 145">' +
+ return '<svg xmlns="http://www.w3.org/2000/svg" width="' + width + '" height="145" viewBox="0 0 ' + width + ' 145">' +
'<rect width="100%" height="100%" fill="#fff" />' +
rects +
'<text x="' + (width / 2) + '" y="136" text-anchor="middle" font-family="monospace" font-size="14" fill="#111">' +
this.escapeXml(text) +
- "</text>" +
- "</svg>"
- );
+ "</text></svg>";
},
- getCode128Patterns() {
+ getCode128Patterns: function () {
return [
"212222", "222122", "222221", "121223", "121322", "131222", "122213", "122312", "132212", "221213",
"221312", "231212", "112232", "122132", "122231", "113222", "123122", "123221", "223211", "221132",
@@ -261,7 +425,7 @@
"114131", "311141", "411131", "211412", "211214", "211232", "2331112"
];
},
- escapeXml(text) {
+ escapeXml: function (text) {
return String(text)
.replace(/&/g, "&")
.replace(/</g, "<")
@@ -269,170 +433,11 @@
.replace(/"/g, """)
.replace(/'/g, "'");
},
- getDevpStateInfo() {
- if (this.readOnly) {
- // Frontend filtering for readOnly mode
- if (this.searchStationId == "") {
- this.stationList = this.fullStationList;
- } else {
- this.stationList = this.fullStationList.filter(item => item.stationId == this.searchStationId);
- this.currentPage = 1;
- }
- } else if (this.$root.sendWs) {
- this.$root.sendWs(JSON.stringify({
- "url": "/console/latest/data/station",
- "data": {}
- }));
- }
+ controlCommand: function () {
+ this.postControl("/station/command/move", this.controlParam);
},
- setStationList(res) {
- let that = this;
- if (res.code == 200) {
- let list = res.data;
- that.fullStationList = list;
- if (that.searchStationId == "") {
- that.stationList = list;
- } else {
- let tmp = [];
- list.forEach((item) => {
- if (item.stationId == that.searchStationId) {
- tmp.push(item);
- }
- });
- that.stationList = tmp;
- that.currentPage = 1;
- }
- }
- },
- handleBarcodeClick(item) {
- if (this.readOnly || !item || item.stationId == null) {
- return;
- }
-
- let that = this;
- $.ajax({
- url: baseUrl + "/openapi/getFakeSystemRunStatus",
- headers: {
- token: localStorage.getItem("token"),
- },
- method: "get",
- success: (res) => {
- if (res.code !== 200 || !res.data || !res.data.isFake || !res.data.running) {
- that.$message({
- message: "浠呬豢鐪熸ā寮忚繍琛屼腑鍙慨鏀规潯鐮�",
- type: "warning",
- });
- return;
- }
-
- that.$prompt("璇疯緭鍏ユ柊鐨勬潯鐮佸�硷紙鍙暀绌烘竻绌猴級", "淇敼鏉$爜", {
- confirmButtonText: "纭畾",
- cancelButtonText: "鍙栨秷",
- inputValue: item.barcode || "",
- inputPlaceholder: "璇疯緭鍏ユ潯鐮�",
- }).then(({ value }) => {
- that.updateStationBarcode(item.stationId, value == null ? "" : String(value).trim());
- }).catch(() => {});
- },
- });
- },
- updateStationBarcode(stationId, barcode) {
- let that = this;
- $.ajax({
- url: baseUrl + "/station/command/barcode",
- headers: {
- token: localStorage.getItem("token"),
- },
- contentType: "application/json",
- method: "post",
- data: JSON.stringify({
- stationId: stationId,
- barcode: barcode,
- }),
- success: (res) => {
- if (res.code == 200) {
- that.syncLocalBarcode(stationId, barcode);
- that.$message({
- message: "鏉$爜淇敼鎴愬姛",
- type: "success",
- });
- } else {
- that.$message({
- message: res.msg || "鏉$爜淇敼澶辫触",
- type: "warning",
- });
- }
- },
- });
- },
- syncLocalBarcode(stationId, barcode) {
- let updateFn = (list) => {
- if (!list || list.length === 0) {
- return;
- }
- list.forEach((row) => {
- if (row.stationId == stationId) {
- row.barcode = barcode;
- }
- });
- };
- updateFn(this.stationList);
- updateFn(this.fullStationList);
- },
- openControl() {
- this.showControl = !this.showControl;
- },
- controlCommand() {
- let that = this;
- //涓嬪彂鍛戒护
- $.ajax({
- url: baseUrl + "/station/command/move",
- headers: {
- token: localStorage.getItem("token"),
- },
- contentType: "application/json",
- method: "post",
- data: JSON.stringify(that.controlParam),
- success: (res) => {
- if (res.code == 200) {
- that.$message({
- message: res.msg,
- type: "success",
- });
- } else {
- that.$message({
- message: res.msg,
- type: "warning",
- });
- }
- },
- });
- },
- resetCommand() {
- let that = this;
- //涓嬪彂鍛戒护
- $.ajax({
- url: baseUrl + "/station/command/reset",
- headers: {
- token: localStorage.getItem("token"),
- },
- contentType: "application/json",
- method: "post",
- data: JSON.stringify(that.controlParam),
- success: (res) => {
- if (res.code == 200) {
- that.$message({
- message: res.msg,
- type: "success",
- });
- } else {
- that.$message({
- message: res.msg,
- type: "warning",
- });
- }
- },
- });
- },
- },
+ resetCommand: function () {
+ this.postControl("/station/command/reset", this.controlParam);
+ }
+ }
});
diff --git a/src/main/webapp/components/MapCanvas.js b/src/main/webapp/components/MapCanvas.js
index 276827b..05d703b 100644
--- a/src/main/webapp/components/MapCanvas.js
+++ b/src/main/webapp/components/MapCanvas.js
@@ -2,7 +2,7 @@
template: `
<div style="width: 100%; height: 100%; position: relative;">
<div ref="pixiView" style="position: absolute; inset: 0;"></div>
- <div style="position: absolute; top: 12px; left: 14px; z-index: 30; pointer-events: none; max-width: 52%;">
+ <div :style="cycleCapacityPanelStyle()">
<div style="display: flex; flex-direction: column; gap: 6px; align-items: flex-start;">
<div v-for="item in cycleCapacity.loopList"
:key="'loop-' + item.loopNo"
@@ -24,14 +24,31 @@
<div :style="mapToolFpsStyle()">FPS {{ mapFps }}</div>
<button type="button" @click="toggleMapToolPanel" :style="mapToolToggleStyle(showMapToolPanel)">{{ showMapToolPanel ? '鏀惰捣鎿嶄綔' : '鍦板浘鎿嶄綔' }}</button>
<div v-show="showMapToolPanel" :style="mapToolBarStyle()">
- <button type="button" @click="toggleStationDirection" :style="mapToolButtonStyle(showStationDirection)">{{ showStationDirection ? '闅愯棌绔欑偣鏂瑰悜' : '鏄剧ず绔欑偣鏂瑰悜' }}</button>
- <button type="button" @click="rotateMap" :style="mapToolButtonStyle(false)">鏃嬭浆</button>
- <button type="button" @click="toggleMirror" :style="mapToolButtonStyle(mapMirrorX)">{{ mapMirrorX ? '鍙栨秷闀滃儚' : '闀滃儚' }}</button>
+ <div :style="mapToolRowStyle()">
+ <button type="button" @click="toggleStationDirection" :style="mapToolButtonStyle(showStationDirection)">{{ showStationDirection ? '闅愯棌绔欑偣鏂瑰悜' : '鏄剧ず绔欑偣鏂瑰悜' }}</button>
+ <button type="button" @click="rotateMap" :style="mapToolButtonStyle(false)">鏃嬭浆</button>
+ <button type="button" @click="toggleMirror" :style="mapToolButtonStyle(mapMirrorX)">{{ mapMirrorX ? '鍙栨秷闀滃儚' : '闀滃儚' }}</button>
+ </div>
+ <div :style="mapToolRowStyle()">
+ <button type="button" @click="openStationColorConfigPage" :style="mapToolButtonStyle(false)">绔欑偣棰滆壊</button>
+ </div>
+ <div v-if="levList && levList.length > 1" :style="mapToolFloorSectionStyle()">
+ <div :style="mapToolSectionLabelStyle()">妤煎眰</div>
+ <div :style="mapToolFloorListStyle()">
+ <button
+ v-for="floor in levList"
+ :key="'tool-floor-' + floor"
+ type="button"
+ @click="selectFloorFromTool(floor)"
+ :style="mapToolFloorButtonStyle(currentLev == floor)"
+ >{{ floor }}F</button>
+ </div>
+ </div>
</div>
</div>
</div>
`,
- props: ['lev', 'crnParam', 'rgvParam', 'devpParam', 'highlightOnParamChange'],
+ props: ['lev', 'levList', 'crnParam', 'rgvParam', 'devpParam', 'stationTaskRange', 'highlightOnParamChange', 'viewportPadding', 'hudPadding'],
data() {
return {
map: [],
@@ -63,6 +80,10 @@
pixiDevpTextureMap: new Map(),
pixiCrnColorTextureMap: new Map(),
pixiRgvColorTextureMap: new Map(),
+ shelfChunkList: [],
+ shelfChunkSize: 2048,
+ shelfCullPadding: 160,
+ shelfCullRaf: null,
crnList: [],
dualCrnList: [],
rgvList: [],
@@ -111,7 +132,18 @@
hoverLoopNo: null,
hoverLoopStationIdSet: new Set(),
loopHighlightColor: 0xfff34d,
- stationDirectionColor: 0xff5a36
+ stationDirectionColor: 0xff5a36,
+ stationStatusColors: {
+ 'site-auto': 0x78ff81,
+ 'site-auto-run': 0xfa51f6,
+ 'site-auto-id': 0xc4c400,
+ 'site-auto-run-id': 0x30bffc,
+ 'site-enable-in': 0x18c7b8,
+ 'site-unauto': 0xb8b8b8,
+ 'machine-pakin': 0x30bffc,
+ 'machine-pakout': 0x97b400,
+ 'site-run-block': 0xe69138
+ }
}
},
mounted() {
@@ -119,6 +151,7 @@
this.createMap();
this.startContainerResizeObserve();
this.loadMapTransformConfig();
+ this.loadStationColorConfig();
this.loadLocList();
this.connectWs();
@@ -138,6 +171,8 @@
if (this.timer) { clearInterval(this.timer); }
if (this.hoverRaf) { cancelAnimationFrame(this.hoverRaf); this.hoverRaf = null; }
+ if (this.shelfCullRaf) { cancelAnimationFrame(this.shelfCullRaf); this.shelfCullRaf = null; }
+ if (window.gsap && this.pixiApp && this.pixiApp.stage) { window.gsap.killTweensOf(this.pixiApp.stage.position); }
if (this.pixiApp) { this.pixiApp.destroy(true, { children: true }); }
if (this.containerResizeObserver) { this.containerResizeObserver.disconnect(); this.containerResizeObserver = null; }
window.removeEventListener('resize', this.resizeToContainer);
@@ -147,6 +182,14 @@
watch: {
lev(newLev) {
if (newLev != null) { this.changeFloor(newLev); }
+ },
+ viewportPadding: {
+ deep: true,
+ handler(newVal, oldVal) {
+ if (this.mapContentSize && this.mapContentSize.width > 0 && this.mapContentSize.height > 0) {
+ this.adjustStageForViewportPadding(oldVal, newVal);
+ }
+ }
},
crnParam: {
deep: true,
@@ -192,17 +235,65 @@
}
},
methods: {
+ cycleCapacityPanelStyle() {
+ const hud = this.hudPadding || {};
+ const left = Math.max(14, Number(hud.left) || 0);
+ const rightReserve = 220;
+ return {
+ position: 'absolute',
+ top: '12px',
+ left: left + 'px',
+ zIndex: 30,
+ pointerEvents: 'none',
+ maxWidth: 'calc(100% - ' + (left + rightReserve) + 'px)'
+ };
+ },
mapToolBarStyle() {
return {
display: 'flex',
+ flexDirection: 'column',
gap: '8px',
- alignItems: 'center',
+ alignItems: 'stretch',
padding: '7px',
borderRadius: '14px',
background: 'rgba(255, 255, 255, 0.72)',
border: '1px solid rgba(160, 180, 205, 0.3)',
boxShadow: '0 8px 20px rgba(37, 64, 97, 0.08)',
backdropFilter: 'blur(4px)'
+ };
+ },
+ mapToolRowStyle() {
+ return {
+ display: 'flex',
+ gap: '8px',
+ alignItems: 'center',
+ justifyContent: 'flex-end',
+ flexWrap: 'wrap'
+ };
+ },
+ mapToolFloorSectionStyle() {
+ return {
+ display: 'flex',
+ flexDirection: 'column',
+ gap: '4px',
+ paddingTop: '6px',
+ borderTop: '1px solid rgba(160, 180, 205, 0.22)'
+ };
+ },
+ mapToolSectionLabelStyle() {
+ return {
+ color: '#6a7f95',
+ fontSize: '10px',
+ lineHeight: '14px',
+ textAlign: 'right'
+ };
+ },
+ mapToolFloorListStyle() {
+ return {
+ display: 'flex',
+ flexDirection: 'column',
+ gap: '4px',
+ alignItems: 'stretch'
};
},
mapToolFpsStyle() {
@@ -252,8 +343,30 @@
whiteSpace: 'nowrap'
};
},
+ mapToolFloorButtonStyle(active) {
+ return {
+ appearance: 'none',
+ border: '1px solid ' + (active ? 'rgba(96, 132, 170, 0.36)' : 'rgba(160, 180, 205, 0.3)'),
+ background: active ? 'rgba(235, 243, 251, 0.96)' : 'rgba(255, 255, 255, 0.88)',
+ color: active ? '#27425c' : '#4d647d',
+ minWidth: '44px',
+ height: '26px',
+ padding: '0 10px',
+ borderRadius: '8px',
+ fontSize: '11px',
+ lineHeight: '26px',
+ cursor: 'pointer',
+ fontWeight: '700',
+ boxShadow: active ? '0 4px 12px rgba(37, 64, 97, 0.08)' : 'none',
+ whiteSpace: 'nowrap'
+ };
+ },
toggleMapToolPanel() {
this.showMapToolPanel = !this.showMapToolPanel;
+ },
+ selectFloorFromTool(lev) {
+ if (lev == null || lev === this.currentLev) { return; }
+ this.$emit('switch-lev', lev);
},
createMap() {
this.pixiApp = new PIXI.Application({ backgroundColor: 0xF5F7F9, antialias: false, powerPreference: 'high-performance', autoDensity: true, resolution: Math.min(window.devicePixelRatio || 1, 2) });
@@ -270,9 +383,8 @@
this.objectsContainer2 = new PIXI.Container();
this.tracksContainer = new PIXI.ParticleContainer(10000, { scale: true, position: true, rotation: false, uvs: false, alpha: false });
this.tracksGraphics = new PIXI.Graphics();
- this.shelvesContainer = new PIXI.ParticleContainer(10000, { scale: true, position: true, rotation: false, uvs: false, alpha: false });
+ this.shelvesContainer = new PIXI.Container();
this.tracksContainer.autoResize = true;
- this.shelvesContainer.autoResize = true;
this.mapRoot = new PIXI.Container();
this.pixiApp.stage.addChild(this.mapRoot);
this.mapRoot.addChild(this.tracksGraphics);
@@ -320,6 +432,7 @@
const dx = globalPos.x - mouseDownPoint[0];
const dy = globalPos.y - mouseDownPoint[1];
this.pixiApp.stage.position.set(stageOriginalPos[0] + dx, stageOriginalPos[1] + dy);
+ this.scheduleShelfChunkCulling();
}
});
this.pixiApp.renderer.plugins.interaction.on('pointerup', () => { touchBlank = false; });
@@ -345,7 +458,8 @@
const newPosX = sx - worldX * newZoomX;
const newPosY = sy - worldY * newZoomY;
this.pixiApp.stage.setTransform(newPosX, newPosY, newZoomX, newZoomY, 0, 0, 0, 0, 0);
- this.scheduleAdjustLabels();
+ this.scheduleAdjustLabels();
+ this.scheduleShelfChunkCulling();
});
//*******************缂╂斁鐢诲竷*******************
@@ -388,6 +502,67 @@
const rect = this.pixiApp.view ? this.pixiApp.view.getBoundingClientRect() : null;
return { width: rect ? rect.width : 0, height: rect ? rect.height : 0 };
},
+ getViewportPadding() {
+ return this.normalizeViewportPadding(this.viewportPadding);
+ },
+ normalizeViewportPadding(padding) {
+ const source = padding || {};
+ const normalize = (value) => {
+ const num = Number(value);
+ return isFinite(num) && num > 0 ? num : 0;
+ };
+ return {
+ top: normalize(source.top),
+ right: normalize(source.right),
+ bottom: normalize(source.bottom),
+ left: normalize(source.left)
+ };
+ },
+ getViewportCenter(viewport, padding) {
+ const normalized = this.normalizeViewportPadding(padding);
+ const availableW = Math.max(1, viewport.width - normalized.left - normalized.right);
+ const availableH = Math.max(1, viewport.height - normalized.top - normalized.bottom);
+ return {
+ x: normalized.left + availableW / 2,
+ y: normalized.top + availableH / 2
+ };
+ },
+ adjustStageForViewportPadding(oldPadding, newPadding) {
+ if (!this.pixiApp || !this.pixiApp.stage) { return; }
+ const viewport = this.getViewportSize();
+ if (viewport.width <= 0 || viewport.height <= 0) { return; }
+ const prevCenter = this.getViewportCenter(viewport, oldPadding);
+ const nextCenter = this.getViewportCenter(viewport, newPadding);
+ const deltaX = nextCenter.x - prevCenter.x;
+ const deltaY = nextCenter.y - prevCenter.y;
+ if (Math.abs(deltaX) < 0.5 && Math.abs(deltaY) < 0.5) {
+ return;
+ }
+ const targetX = this.pixiApp.stage.position.x + deltaX;
+ const targetY = this.pixiApp.stage.position.y + deltaY;
+ if (window.gsap) {
+ window.gsap.killTweensOf(this.pixiApp.stage.position);
+ window.gsap.to(this.pixiApp.stage.position, {
+ x: targetX,
+ y: targetY,
+ duration: 0.18,
+ ease: 'power1.out',
+ onUpdate: () => {
+ this.scheduleAdjustLabels();
+ this.scheduleShelfChunkCulling();
+ },
+ onComplete: () => {
+ this.scheduleAdjustLabels();
+ this.scheduleShelfChunkCulling();
+ }
+ });
+ return;
+ }
+ this.pixiApp.stage.position.x = targetX;
+ this.pixiApp.stage.position.y = targetY;
+ this.scheduleAdjustLabels();
+ this.scheduleShelfChunkCulling();
+ },
resizeToContainer() {
const w = this.$el.clientWidth || 0;
const h = this.$el.clientHeight || 0;
@@ -419,7 +594,7 @@
this.objectsContainer2.removeChildren();
if (this.tracksContainer) { this.tracksContainer.removeChildren(); }
if (this.tracksGraphics) { this.tracksGraphics.clear(); }
- if (this.shelvesContainer) { this.shelvesContainer.removeChildren(); }
+ this.clearShelfChunks();
this.crnList = [];
this.dualCrnList = [];
this.rgvList = [];
@@ -448,7 +623,7 @@
this.objectsContainer2.removeChildren();
if (this.tracksContainer) { this.tracksContainer.removeChildren(); }
if (this.tracksGraphics) { this.tracksGraphics.clear(); }
- if (this.shelvesContainer) { this.shelvesContainer.removeChildren(); }
+ this.clearShelfChunks();
this.crnList = [];
this.dualCrnList = [];
this.rgvList = [];
@@ -573,15 +748,12 @@
this.collectTrackItem(val);
continue;
}
+ if (val.type === 'shelf') { continue; }
let sprite = this.getSprite(val, (e) => {
//鍥炶皟
});
if (sprite == null) { continue; }
- if (sprite._kind === 'shelf') {
- this.shelvesContainer.addChild(sprite);
- } else {
- this.objectsContainer.addChild(sprite);
- }
+ this.objectsContainer.addChild(sprite);
this.pixiStageList[index][idx] = sprite;
}
});
@@ -698,6 +870,7 @@
}
}
this.mapContentSize = { width: contentW, height: contentH };
+ this.buildShelfChunks(map, contentW, contentH);
this.applyMapTransform(true);
this.map = map;
this.isSwitchingFloor = false;
@@ -743,7 +916,6 @@
if (!sites) { return; }
sites.forEach((item) => {
let id = item.siteId != null ? item.siteId : item.stationId;
- let status = item.siteStatus != null ? item.siteStatus : item.stationStatus;
let workNo = item.workNo != null ? item.workNo : item.taskNo;
if (id == null) { return; }
let sta = this.pixiStaMap.get(parseInt(id));
@@ -754,7 +926,7 @@
sta.statusObj = null;
if (sta.textObj.parent !== sta) { sta.addChild(sta.textObj); sta.textObj.position.set(sta.width / 2, sta.height / 2); }
}
- this.setStationBaseColor(sta, this.getStationStatusColor(status));
+ this.setStationBaseColor(sta, this.getStationStatusColor(this.resolveStationStatus(item)));
});
},
getCrnInfo() {
@@ -1285,15 +1457,47 @@
return brightness > 150 ? '#000000' : '#ffffff';
},
getStationStatusColor(status) {
- if (status === "site-auto") { return 0x78ff81; }
- if (status === "site-auto-run") { return 0xfa51f6; }
- if (status === "site-auto-id") { return 0xc4c400; }
- if (status === "site-auto-run-id") { return 0x30bffc; }
- if (status === "site-unauto") { return 0xb8b8b8; }
- if (status === "machine-pakin") { return 0x30bffc; }
- if (status === "machine-pakout") { return 0x97b400; }
- if (status === "site-run-block") { return 0xe69138; }
- return 0xb8b8b8;
+ const colorMap = this.stationStatusColors || this.getDefaultStationStatusColors();
+ if (status && colorMap[status] != null) { return colorMap[status]; }
+ return colorMap['site-unauto'] != null ? colorMap['site-unauto'] : 0xb8b8b8;
+ },
+ resolveStationStatus(item) {
+ const status = item && (item.siteStatus != null ? item.siteStatus : item.stationStatus);
+ const taskNo = this.parseStationTaskNo(item && (item.workNo != null ? item.workNo : item.taskNo));
+ const autoing = !!(item && item.autoing);
+ const loading = !!(item && item.loading);
+ const runBlock = !!(item && item.runBlock);
+ const enableIn = !!(item && item.enableIn);
+ if (taskNo === 9998 || enableIn) { return 'site-enable-in'; }
+ if (autoing && loading && taskNo > 0 && !runBlock) {
+ const taskClass = this.getStationTaskClass(taskNo);
+ if (taskClass) { return taskClass; }
+ }
+ if (status) { return status; }
+ if (autoing && loading && taskNo > 0 && runBlock) { return 'site-run-block'; }
+ if (autoing && loading && taskNo > 0) { return 'site-auto-run-id'; }
+ if (autoing && loading) { return 'site-auto-run'; }
+ if (autoing && taskNo > 0) { return 'site-auto-id'; }
+ if (autoing) { return 'site-auto'; }
+ return 'site-unauto';
+ },
+ parseStationTaskNo(value) {
+ const taskNo = parseInt(value, 10);
+ return isNaN(taskNo) ? 0 : taskNo;
+ },
+ getStationTaskClass(taskNo) {
+ if (!(taskNo > 0)) { return null; }
+ const range = this.stationTaskRange || {};
+ if (this.isTaskNoInRange(taskNo, range.inbound)) { return 'machine-pakin'; }
+ if (this.isTaskNoInRange(taskNo, range.outbound)) { return 'machine-pakout'; }
+ return null;
+ },
+ isTaskNoInRange(taskNo, range) {
+ if (!range) { return false; }
+ const start = parseInt(range.start, 10);
+ const end = parseInt(range.end, 10);
+ if (isNaN(start) || isNaN(end)) { return false; }
+ return taskNo >= start && taskNo <= end;
},
getCrnStatusColor(status) {
if (status === "machine-auto") { return 0x21BA45; }
@@ -1837,6 +2041,135 @@
}
}
},
+ clearShelfChunks() {
+ if (this.shelfCullRaf) {
+ cancelAnimationFrame(this.shelfCullRaf);
+ this.shelfCullRaf = null;
+ }
+ this.shelfChunkList = [];
+ if (!this.shelvesContainer) { return; }
+ const children = this.shelvesContainer.removeChildren();
+ children.forEach((child) => {
+ if (child && typeof child.destroy === 'function') {
+ child.destroy({ children: true, texture: true, baseTexture: true });
+ }
+ });
+ },
+ buildShelfChunks(map, contentW, contentH) {
+ this.clearShelfChunks();
+ if (!this.pixiApp || !this.pixiApp.renderer || !this.shelvesContainer || !Array.isArray(map)) { return; }
+ const chunkSize = Math.max(256, parseInt(this.shelfChunkSize, 10) || 2048);
+ const chunkMap = new Map();
+ for (let r = 0; r < map.length; r++) {
+ const row = map[r];
+ if (!row) { continue; }
+ for (let c = 0; c < row.length; c++) {
+ const cell = row[c];
+ if (!cell || cell.type !== 'shelf' || cell.type === 'merge') { continue; }
+ const startChunkX = Math.floor(cell.posX / chunkSize);
+ const endChunkX = Math.floor((cell.posX + Math.max(1, cell.width) - 0.01) / chunkSize);
+ const startChunkY = Math.floor(cell.posY / chunkSize);
+ const endChunkY = Math.floor((cell.posY + Math.max(1, cell.height) - 0.01) / chunkSize);
+ for (let chunkY = startChunkY; chunkY <= endChunkY; chunkY++) {
+ for (let chunkX = startChunkX; chunkX <= endChunkX; chunkX++) {
+ const key = chunkX + ',' + chunkY;
+ let list = chunkMap.get(key);
+ if (!list) {
+ list = [];
+ chunkMap.set(key, list);
+ }
+ list.push(cell);
+ }
+ }
+ }
+ }
+
+ const chunkList = [];
+ chunkMap.forEach((cells, key) => {
+ const keyParts = key.split(',');
+ const chunkX = parseInt(keyParts[0], 10) || 0;
+ const chunkY = parseInt(keyParts[1], 10) || 0;
+ const chunkLeft = chunkX * chunkSize;
+ const chunkTop = chunkY * chunkSize;
+ const chunkWidth = Math.max(1, Math.min(chunkSize, contentW - chunkLeft));
+ const chunkHeight = Math.max(1, Math.min(chunkSize, contentH - chunkTop));
+ const graphics = new PIXI.Graphics();
+ graphics.beginFill(0xb6e2e2);
+ graphics.lineStyle(1, 0xffffff, 1);
+ for (let i = 0; i < cells.length; i++) {
+ const cell = cells[i];
+ graphics.drawRect(cell.posX - chunkLeft, cell.posY - chunkTop, cell.width, cell.height);
+ }
+ graphics.endFill();
+ const texture = this.pixiApp.renderer.generateTexture(
+ graphics,
+ PIXI.SCALE_MODES.LINEAR,
+ 1,
+ new PIXI.Rectangle(0, 0, chunkWidth, chunkHeight)
+ );
+ graphics.destroy(true);
+ const sprite = new PIXI.Sprite(texture);
+ sprite.position.set(chunkLeft, chunkTop);
+ sprite._chunkBounds = {
+ x: chunkLeft,
+ y: chunkTop,
+ width: chunkWidth,
+ height: chunkHeight
+ };
+ this.shelvesContainer.addChild(sprite);
+ chunkList.push(sprite);
+ });
+ this.shelfChunkList = chunkList;
+ this.updateVisibleShelfChunks();
+ },
+ getViewportLocalBounds(padding) {
+ if (!this.mapRoot || !this.pixiApp) { return null; }
+ const viewport = this.getViewportSize();
+ const pad = Math.max(0, Number(padding) || 0);
+ const points = [
+ new PIXI.Point(-pad, -pad),
+ new PIXI.Point(viewport.width + pad, -pad),
+ new PIXI.Point(-pad, viewport.height + pad),
+ new PIXI.Point(viewport.width + pad, viewport.height + pad)
+ ];
+ let minX = Infinity;
+ let minY = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+ points.forEach((point) => {
+ const local = this.mapRoot.toLocal(point);
+ if (local.x < minX) { minX = local.x; }
+ if (local.y < minY) { minY = local.y; }
+ if (local.x > maxX) { maxX = local.x; }
+ if (local.y > maxY) { maxY = local.y; }
+ });
+ if (!isFinite(minX) || !isFinite(minY) || !isFinite(maxX) || !isFinite(maxY)) { return null; }
+ return { minX: minX, minY: minY, maxX: maxX, maxY: maxY };
+ },
+ updateVisibleShelfChunks() {
+ if (!this.shelfChunkList || this.shelfChunkList.length === 0) { return; }
+ const localBounds = this.getViewportLocalBounds(this.shelfCullPadding);
+ if (!localBounds) { return; }
+ for (let i = 0; i < this.shelfChunkList.length; i++) {
+ const sprite = this.shelfChunkList[i];
+ const bounds = sprite && sprite._chunkBounds;
+ if (!bounds) { continue; }
+ const visible = bounds.x < localBounds.maxX &&
+ bounds.x + bounds.width > localBounds.minX &&
+ bounds.y < localBounds.maxY &&
+ bounds.y + bounds.height > localBounds.minY;
+ if (sprite.visible !== visible) {
+ sprite.visible = visible;
+ }
+ }
+ },
+ scheduleShelfChunkCulling() {
+ if (this.shelfCullRaf) { return; }
+ this.shelfCullRaf = requestAnimationFrame(() => {
+ this.shelfCullRaf = null;
+ this.updateVisibleShelfChunks();
+ });
+ },
findIndexByOffsets(offsets, sizes, value) {
if (!offsets || !sizes || offsets.length === 0) { return -1; }
for (let i = 0; i < offsets.length; i++) {
@@ -2084,6 +2417,23 @@
this.applyMapTransform(true);
this.saveMapTransformConfig();
},
+ openStationColorConfigPage() {
+ if (typeof window === 'undefined') { return; }
+ const url = (typeof baseUrl !== 'undefined' ? baseUrl : '') + '/views/watch/stationColorConfig.html';
+ const layerInstance = (window.top && window.top.layer) || window.layer;
+ if (layerInstance && typeof layerInstance.open === 'function') {
+ layerInstance.open({
+ type: 2,
+ title: '绔欑偣棰滆壊閰嶇疆',
+ maxmin: true,
+ area: ['980px', '760px'],
+ shadeClose: false,
+ content: url
+ });
+ return;
+ }
+ window.open(url, '_blank');
+ },
parseRotation(value) {
const num = parseInt(value, 10);
if (!isFinite(num)) { return 0; }
@@ -2095,6 +2445,98 @@
if (value == null) { return false; }
const str = String(value).toLowerCase();
return str === '1' || str === 'true' || str === 'y';
+ },
+ getDefaultStationStatusColors() {
+ return {
+ 'site-auto': 0x78ff81,
+ 'site-auto-run': 0xfa51f6,
+ 'site-auto-id': 0xc4c400,
+ 'site-auto-run-id': 0x30bffc,
+ 'site-enable-in': 0x18c7b8,
+ 'site-unauto': 0xb8b8b8,
+ 'machine-pakin': 0x30bffc,
+ 'machine-pakout': 0x97b400,
+ 'site-run-block': 0xe69138
+ };
+ },
+ parseColorConfigValue(value, fallback) {
+ if (typeof value === 'number' && isFinite(value)) {
+ return value;
+ }
+ const str = String(value == null ? '' : value).trim();
+ if (!str) { return fallback; }
+ if (/^#[0-9a-fA-F]{6}$/.test(str)) { return parseInt(str.slice(1), 16); }
+ if (/^#[0-9a-fA-F]{3}$/.test(str)) {
+ const expanded = str.charAt(1) + str.charAt(1) + str.charAt(2) + str.charAt(2) + str.charAt(3) + str.charAt(3);
+ return parseInt(expanded, 16);
+ }
+ if (/^0x[0-9a-fA-F]{6}$/i.test(str)) { return parseInt(str.slice(2), 16); }
+ if (/^[0-9]+$/.test(str)) {
+ const num = parseInt(str, 10);
+ return isNaN(num) ? fallback : num;
+ }
+ return fallback;
+ },
+ loadStationColorConfig() {
+ if (!window.$ || typeof baseUrl === 'undefined') { return; }
+ $.ajax({
+ url: baseUrl + "/watch/stationColor/config/auth",
+ headers: { 'token': localStorage.getItem('token') },
+ dataType: 'json',
+ method: 'GET',
+ success: (res) => {
+ if (!res || res.code !== 200 || !res.data) {
+ if (res && res.code === 403) { parent.location.href = baseUrl + "/login"; }
+ return;
+ }
+ this.applyStationColorConfigPayload(res.data);
+ }
+ });
+ },
+ applyStationColorConfigPayload(data) {
+ const defaults = this.getDefaultStationStatusColors();
+ const nextColors = Object.assign({}, defaults);
+ const items = Array.isArray(data.items) ? data.items : [];
+ items.forEach((item) => {
+ if (!item || !item.status || defaults[item.status] == null) { return; }
+ nextColors[item.status] = this.parseColorConfigValue(item.color, defaults[item.status]);
+ });
+ this.stationStatusColors = nextColors;
+ },
+ buildMissingMapConfigList(byCode) {
+ const createList = [];
+ if (!byCode[this.mapConfigCodes.rotate]) {
+ createList.push({
+ name: '鍦板浘鏃嬭浆',
+ code: this.mapConfigCodes.rotate,
+ value: String(this.mapRotation || 0),
+ type: 1,
+ status: 1,
+ selectType: 'map'
+ });
+ }
+ if (!byCode[this.mapConfigCodes.mirror]) {
+ createList.push({
+ name: '鍦板浘闀滃儚',
+ code: this.mapConfigCodes.mirror,
+ value: this.mapMirrorX ? '1' : '0',
+ type: 1,
+ status: 1,
+ selectType: 'map'
+ });
+ }
+ return createList;
+ },
+ createMapConfigs(createList) {
+ if (!window.$ || typeof baseUrl === 'undefined' || !Array.isArray(createList) || createList.length === 0) { return; }
+ createList.forEach((cfg) => {
+ $.ajax({
+ url: baseUrl + "/config/add/auth",
+ headers: { 'token': localStorage.getItem('token') },
+ method: 'POST',
+ data: cfg
+ });
+ });
},
loadMapTransformConfig() {
if (!window.$ || typeof baseUrl === 'undefined') { return; }
@@ -2120,45 +2562,11 @@
if (mirrorCfg && mirrorCfg.value != null) {
this.mapMirrorX = this.parseMirror(mirrorCfg.value);
}
- if (rotateCfg == null || mirrorCfg == null) {
- this.createMapTransformConfigIfMissing(rotateCfg, mirrorCfg);
- }
+ this.createMapConfigs(this.buildMissingMapConfigList(byCode));
if (this.mapContentSize && this.mapContentSize.width > 0) {
this.applyMapTransform(true);
}
}
- });
- },
- createMapTransformConfigIfMissing(rotateCfg, mirrorCfg) {
- if (!window.$ || typeof baseUrl === 'undefined') { return; }
- const createList = [];
- if (!rotateCfg) {
- createList.push({
- name: '鍦板浘鏃嬭浆',
- code: this.mapConfigCodes.rotate,
- value: String(this.mapRotation || 0),
- type: 1,
- status: 1,
- selectType: 'map'
- });
- }
- if (!mirrorCfg) {
- createList.push({
- name: '鍦板浘闀滃儚',
- code: this.mapConfigCodes.mirror,
- value: this.mapMirrorX ? '1' : '0',
- type: 1,
- status: 1,
- selectType: 'map'
- });
- }
- createList.forEach((cfg) => {
- $.ajax({
- url: baseUrl + "/config/add/auth",
- headers: { 'token': localStorage.getItem('token') },
- method: 'POST',
- data: cfg
- });
});
},
saveMapTransformConfig() {
@@ -2193,15 +2601,20 @@
const viewport = this.getViewportSize();
const vw = viewport.width;
const vh = viewport.height;
- let scale = Math.min(vw / contentW, vh / contentH) * 0.95;
+ const padding = this.getViewportPadding();
+ const availableW = Math.max(1, vw - padding.left - padding.right);
+ const availableH = Math.max(1, vh - padding.top - padding.bottom);
+ let scale = Math.min(availableW / contentW, availableH / contentH) * 0.95;
if (!isFinite(scale) || scale <= 0) { scale = 1; }
const baseW = this.mapContentSize.width || contentW;
const baseH = this.mapContentSize.height || contentH;
const mirrorX = this.mapMirrorX ? -1 : 1;
const scaleX = scale * mirrorX;
const scaleY = scale;
- const posX = (vw / 2) - (baseW / 2) * scaleX;
- const posY = (vh / 2) - (baseH / 2) * scaleY;
+ const centerX = padding.left + availableW / 2;
+ const centerY = padding.top + availableH / 2;
+ const posX = centerX - (baseW / 2) * scaleX;
+ const posY = centerY - (baseH / 2) * scaleY;
this.pixiApp.stage.setTransform(posX, posY, scaleX, scaleY, 0, 0, 0, 0, 0);
},
applyMapTransform(fitToView) {
@@ -2215,6 +2628,7 @@
this.mapRoot.scale.set(1, 1);
if (fitToView) { this.fitStageToContent(); }
this.scheduleAdjustLabels();
+ this.scheduleShelfChunkCulling();
},
scheduleAdjustLabels() {
if (this.adjustLabelTimer) { clearTimeout(this.adjustLabelTimer); }
@@ -2226,22 +2640,6 @@
}
}
});
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/main/webapp/components/MonitorCardKit.js b/src/main/webapp/components/MonitorCardKit.js
new file mode 100644
index 0000000..6d893ad
--- /dev/null
+++ b/src/main/webapp/components/MonitorCardKit.js
@@ -0,0 +1,147 @@
+(function (global) {
+ if (global.MonitorCardKit) {
+ return;
+ }
+
+ function ensureStyles() {
+ if (document.getElementById("monitor-card-kit-style")) {
+ return;
+ }
+ var style = document.createElement("style");
+ style.id = "monitor-card-kit-style";
+ style.textContent = [
+ "watch-crn-card,watch-dual-crn-card,devp-card,watch-rgv-card{display:block;width:100%;min-width:0;min-height:0;flex:1 1 auto;}",
+ ".mc-root{display:flex;flex-direction:column;width:100%;height:100%;min-width:0;min-height:0;color:#375067;font-size:12px;}",
+ ".mc-toolbar{display:flex;align-items:center;gap:10px;margin-bottom:10px;}",
+ ".mc-title{flex:1;font-size:14px;font-weight:700;color:#22384f;}",
+ ".mc-search{display:flex;gap:8px;flex:1;}",
+ ".mc-input,.mc-select{width:100%;height:34px;padding:0 11px;border-radius:10px;border:1px solid rgba(219,228,236,.96);background:rgba(255,255,255,.84);box-sizing:border-box;color:#334155;outline:none;}",
+ ".mc-input:focus,.mc-select:focus{border-color:rgba(112,148,190,.62);box-shadow:0 0 0 3px rgba(112,148,190,.1);}",
+ ".mc-btn{height:34px;padding:0 12px;border-radius:10px;border:none;background:#6f95bd;color:#fff;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap;}",
+ ".mc-btn.mc-btn-ghost{background:rgba(255,255,255,.88);color:#46607a;border:1px solid rgba(219,228,236,.96);}",
+ ".mc-btn.mc-btn-soft{background:rgba(233,239,245,.92);color:#46607a;border:1px solid rgba(210,220,231,.98);}",
+ ".mc-control-toggle{margin-bottom:8px;}",
+ ".mc-control{margin-bottom:10px;padding:12px;border:1px solid rgba(223,232,240,.94);border-radius:14px;background:rgba(248,251,253,.88);}",
+ ".mc-control-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;}",
+ ".mc-field{display:flex;flex-direction:column;gap:5px;}",
+ ".mc-field.mc-span-2{grid-column:1 / -1;}",
+ ".mc-field-label{font-size:11px;font-weight:700;color:#72859a;}",
+ ".mc-action-row{display:flex;flex-wrap:wrap;gap:8px;grid-column:1 / -1;padding-top:8px;margin-top:2px;border-top:1px dashed rgba(216,226,235,.92);}",
+ ".mc-collapse{flex:1;min-height:0;overflow:auto;padding-right:2px;}",
+ ".mc-item{border:1px solid rgba(223,232,240,.92);border-radius:14px;background:rgba(255,255,255,.72);margin-bottom:10px;overflow:hidden;}",
+ ".mc-item.is-open{border-color:rgba(132,166,201,.48);box-shadow:0 10px 18px rgba(148,163,184,.08);}",
+ ".mc-head{width:100%;display:flex;align-items:center;justify-content:space-between;gap:10px;padding:12px 14px;border:none;background:transparent;cursor:pointer;text-align:left;color:inherit;}",
+ ".mc-head-main{min-width:0;flex:1;}",
+ ".mc-head-title{font-size:13px;font-weight:700;color:#27425c;line-height:1.35;}",
+ ".mc-head-subtitle{margin-top:3px;font-size:11px;color:#7c8d9f;line-height:1.35;}",
+ ".mc-head-right{display:flex;align-items:center;gap:8px;flex-shrink:0;}",
+ ".mc-badge{padding:4px 8px;border-radius:999px;font-size:10px;font-weight:700;}",
+ ".mc-badge.is-success{background:rgba(82,177,126,.12);color:#2d7650;}",
+ ".mc-badge.is-working{background:rgba(111,149,189,.12);color:#3f6286;}",
+ ".mc-badge.is-warning{background:rgba(214,162,94,.14);color:#9b6a24;}",
+ ".mc-badge.is-danger{background:rgba(207,126,120,.14);color:#a14e4a;}",
+ ".mc-badge.is-muted{background:rgba(148,163,184,.14);color:#748397;}",
+ ".mc-chevron{font-size:14px;color:#6f8194;line-height:1;}",
+ ".mc-body{padding:0 14px 14px;border-top:1px solid rgba(232,238,244,.92);}",
+ ".mc-detail-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin-top:12px;}",
+ ".mc-detail-cell{padding:10px 12px;border-radius:12px;background:rgba(247,250,252,.9);border:1px solid rgba(232,238,244,.96);}",
+ ".mc-detail-label{font-size:11px;color:#7c8d9f;}",
+ ".mc-detail-value{margin-top:4px;font-size:12px;color:#31485f;line-height:1.45;word-break:break-all;}",
+ ".mc-inline-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px;}",
+ ".mc-link{padding:0;border:none;background:transparent;color:#4677a4;font-size:12px;font-weight:600;cursor:pointer;}",
+ ".mc-empty{padding:24px 10px;text-align:center;color:#8b9aad;}",
+ ".mc-footer{display:flex;align-items:center;justify-content:flex-end;gap:10px;margin-top:10px;color:#708396;font-size:11px;}",
+ ".mc-page-btn{height:30px;padding:0 10px;border-radius:8px;border:1px solid rgba(219,228,236,.96);background:rgba(255,255,255,.86);color:#46607a;cursor:pointer;}",
+ ".mc-page-btn[disabled]{opacity:.45;cursor:not-allowed;}",
+ ".mc-code{font-family:monospace;}",
+ "@media (max-width: 1100px){.mc-control-grid{grid-template-columns:1fr;}.mc-field.mc-span-2{grid-column:auto;}.mc-toolbar{flex-direction:column;align-items:stretch;}.mc-search{width:100%;}.mc-detail-grid{grid-template-columns:1fr;}}"
+ ].join("");
+ document.head.appendChild(style);
+ }
+
+ function showMessage(vm, message, type) {
+ if (!message) {
+ return;
+ }
+ if (vm && typeof vm.$message === "function") {
+ vm.$message({
+ message: message,
+ type: type === "danger" ? "error" : (type || "info")
+ });
+ return;
+ }
+ if (vm && vm.$root && typeof vm.$root.showPageMessage === "function") {
+ vm.$root.showPageMessage(message, type === "danger" ? "error" : type);
+ return;
+ }
+ if (global.ELEMENT && typeof global.ELEMENT.Message === "function") {
+ global.ELEMENT.Message({
+ message: message,
+ type: type === "danger" ? "error" : (type || "info")
+ });
+ return;
+ }
+ if (global.layer && typeof global.layer.msg === "function") {
+ var iconMap = { success: 1, danger: 2, warning: 0 };
+ global.layer.msg(message, {
+ icon: iconMap[type] != null ? iconMap[type] : 0,
+ time: 1800
+ });
+ return;
+ }
+ console[type === "danger" ? "error" : "log"](message);
+ }
+
+ function orDash(value) {
+ return value == null || value === "" ? "-" : String(value);
+ }
+
+ function yesNo(value) {
+ if (value === true || value === "Y" || value === "y" || value === 1 || value === "1") {
+ return "Y";
+ }
+ if (value === false || value === "N" || value === "n" || value === 0 || value === "0") {
+ return "N";
+ }
+ return value ? "Y" : "N";
+ }
+
+ function deviceStatusLabel(status, fallbackManual) {
+ var normalized = String(status || "").toUpperCase();
+ if (normalized === "AUTO") {
+ return "鑷姩";
+ }
+ if (normalized === "WORKING") {
+ return "浣滀笟涓�";
+ }
+ if (normalized === "ERROR") {
+ return "鏁呴殰";
+ }
+ return fallbackManual || "绂荤嚎";
+ }
+
+ function statusTone(label) {
+ if (label === "鑷姩") {
+ return "success";
+ }
+ if (label === "浣滀笟涓�") {
+ return "working";
+ }
+ if (label === "鎵嬪姩") {
+ return "warning";
+ }
+ if (label === "鏁呴殰" || label === "鎶ヨ") {
+ return "danger";
+ }
+ return "muted";
+ }
+
+ global.MonitorCardKit = {
+ ensureStyles: ensureStyles,
+ showMessage: showMessage,
+ orDash: orDash,
+ yesNo: yesNo,
+ deviceStatusLabel: deviceStatusLabel,
+ statusTone: statusTone
+ };
+})(window);
diff --git a/src/main/webapp/components/MonitorWorkbench.js b/src/main/webapp/components/MonitorWorkbench.js
new file mode 100644
index 0000000..a532d1f
--- /dev/null
+++ b/src/main/webapp/components/MonitorWorkbench.js
@@ -0,0 +1,661 @@
+Vue.component("monitor-workbench", {
+ template: `
+ <div class="wb-root">
+ <div class="wb-tabs" role="tablist">
+ <button
+ v-for="tab in tabs"
+ :key="tab.key"
+ type="button"
+ :class="['wb-tab', { 'is-active': activeCard === tab.key }]"
+ @click="changeTab(tab.key)"
+ >{{ tab.label }}</button>
+ </div>
+
+ <div class="wb-toolbar">
+ <input
+ class="wb-input"
+ :value="currentSearch"
+ :placeholder="currentSearchPlaceholder()"
+ @input="updateSearch($event.target.value)"
+ />
+ <div class="wb-toolbar-actions">
+ <button type="button" class="wb-btn wb-btn-ghost" @click="refreshCurrent">鍒锋柊</button>
+ <button type="button" class="wb-btn" @click="toggleControl">
+ {{ currentShowControl ? '鏀惰捣鎿嶄綔' : '灞曞紑鎿嶄綔' }}
+ </button>
+ </div>
+ </div>
+
+ <div class="wb-main">
+ <div class="wb-side">
+ <div class="wb-list-card">
+ <div class="wb-side-title">璁惧閫夋嫨</div>
+ <div class="wb-list">
+ <button
+ v-for="item in filteredList"
+ :key="activeCard + '-' + getItemId(activeCard, item)"
+ type="button"
+ :class="['wb-list-item', { 'is-active': isSelected(activeCard, item) }]"
+ @click="selectItem(activeCard, item)"
+ >
+ <span :class="['wb-badge', 'is-' + getStatusTone(activeCard, item)]">{{ getStatusLabel(activeCard, item) }}</span>
+ <div class="wb-list-main">
+ <div class="wb-list-title">{{ getItemTitle(activeCard, item) }}</div>
+ <div class="wb-list-meta">{{ getItemMeta(activeCard, item) }}</div>
+ </div>
+ </button>
+ <div v-if="filteredList.length === 0" class="wb-empty">褰撳墠娌℃湁鍙睍绀虹殑鏁版嵁</div>
+ </div>
+ </div>
+
+ <div v-if="currentShowControl" class="wb-control-card">
+ <div class="wb-side-title">蹇嵎鎿嶄綔</div>
+ <div class="wb-control-target">{{ controlTargetText }}</div>
+ <div class="wb-control-subtitle">{{ controlPanelHint }}</div>
+
+ <div v-if="activeCard === 'crn'" class="wb-form-grid">
+ <label class="wb-field">
+ <span class="wb-field-label">鍫嗗灈鏈哄彿</span>
+ <input class="wb-input" v-model="controlForms.crn.crnNo" placeholder="1" />
+ </label>
+ <label class="wb-field">
+ <span class="wb-field-label">婧愬簱浣�</span>
+ <input class="wb-input" v-model="controlForms.crn.sourceLocNo" placeholder="婧愮偣" />
+ </label>
+ <label class="wb-field wb-field-span-2">
+ <span class="wb-field-label">鐩爣搴撲綅</span>
+ <input class="wb-input" v-model="controlForms.crn.targetLocNo" placeholder="鐩爣鐐�" />
+ </label>
+ <div class="wb-action-row">
+ <button type="button" class="wb-btn wb-btn-primary" @click="submitControl('transport')">鍙栨斁璐�</button>
+ <button type="button" class="wb-btn wb-btn-ghost" @click="submitControl('move')">绉诲姩</button>
+ <button type="button" class="wb-btn wb-btn-soft" @click="submitControl('taskComplete')">瀹屾垚</button>
+ </div>
+ </div>
+
+ <div v-else-if="activeCard === 'dualCrn'" class="wb-form-grid">
+ <label class="wb-field">
+ <span class="wb-field-label">鍫嗗灈鏈哄彿</span>
+ <input class="wb-input" v-model="controlForms.dualCrn.crnNo" placeholder="2" />
+ </label>
+ <label class="wb-field">
+ <span class="wb-field-label">宸ヤ綅</span>
+ <select class="wb-select" v-model="controlForms.dualCrn.station">
+ <option :value="1">宸ヤ綅1</option>
+ <option :value="2">宸ヤ綅2</option>
+ </select>
+ </label>
+ <label class="wb-field">
+ <span class="wb-field-label">婧愬簱浣�</span>
+ <input class="wb-input" v-model="controlForms.dualCrn.sourceLocNo" placeholder="婧愮偣" />
+ </label>
+ <label class="wb-field">
+ <span class="wb-field-label">鐩爣搴撲綅</span>
+ <input class="wb-input" v-model="controlForms.dualCrn.targetLocNo" placeholder="鐩爣鐐�" />
+ </label>
+ <div class="wb-action-row">
+ <button type="button" class="wb-btn wb-btn-primary" @click="submitControl('transport')">鍙栨斁璐�</button>
+ <button type="button" class="wb-btn wb-btn-ghost" @click="submitControl('pickup')">鍙栬揣</button>
+ <button type="button" class="wb-btn wb-btn-ghost" @click="submitControl('putdown')">鏀捐揣</button>
+ <button type="button" class="wb-btn wb-btn-ghost" @click="submitControl('move')">绉诲姩</button>
+ <button type="button" class="wb-btn wb-btn-soft" @click="submitControl('taskComplete')">瀹屾垚</button>
+ </div>
+ </div>
+
+ <div v-else-if="activeCard === 'devp'" class="wb-form-grid">
+ <label class="wb-field">
+ <span class="wb-field-label">绔欏彿</span>
+ <input class="wb-input" v-model="controlForms.devp.stationId" placeholder="101" />
+ </label>
+ <label class="wb-field">
+ <span class="wb-field-label">宸ヤ綔鍙�</span>
+ <input class="wb-input" v-model="controlForms.devp.taskNo" placeholder="宸ヤ綔鍙�" />
+ </label>
+ <label class="wb-field wb-field-span-2">
+ <span class="wb-field-label">鐩爣绔�</span>
+ <input class="wb-input" v-model="controlForms.devp.targetStationId" placeholder="鐩爣绔欏彿" />
+ </label>
+ <div class="wb-action-row">
+ <button type="button" class="wb-btn wb-btn-primary" @click="submitControl('move')">涓嬪彂</button>
+ <button type="button" class="wb-btn wb-btn-soft" @click="submitControl('reset')">澶嶄綅</button>
+ <button
+ v-if="selectedItem"
+ type="button"
+ class="wb-btn wb-btn-ghost"
+ @click="editBarcode"
+ >鏉$爜</button>
+ </div>
+ </div>
+
+ <div v-else-if="activeCard === 'rgv'" class="wb-form-grid">
+ <label class="wb-field">
+ <span class="wb-field-label">RGV鍙�</span>
+ <input class="wb-input" v-model="controlForms.rgv.rgvNo" placeholder="1" />
+ </label>
+ <label class="wb-field">
+ <span class="wb-field-label">婧愮偣</span>
+ <input class="wb-input" v-model="controlForms.rgv.sourcePos" placeholder="婧愮偣" />
+ </label>
+ <label class="wb-field wb-field-span-2">
+ <span class="wb-field-label">鐩爣鐐�</span>
+ <input class="wb-input" v-model="controlForms.rgv.targetPos" placeholder="鐩爣鐐�" />
+ </label>
+ <div class="wb-action-row">
+ <button type="button" class="wb-btn wb-btn-primary" @click="submitControl('transport')">鍙栨斁璐�</button>
+ <button type="button" class="wb-btn wb-btn-ghost" @click="submitControl('move')">绉诲姩</button>
+ <button type="button" class="wb-btn wb-btn-soft" @click="submitControl('taskComplete')">瀹屾垚</button>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="wb-detail-panel">
+ <div class="wb-detail" v-if="selectedItem">
+ <div class="wb-detail-header">
+ <div>
+ <div class="wb-section-title">{{ getItemTitle(activeCard, selectedItem) }}</div>
+ <div class="wb-detail-subtitle">{{ getItemMeta(activeCard, selectedItem) }}</div>
+ </div>
+ <div class="wb-detail-actions">
+ <button
+ v-if="activeCard === 'dualCrn'"
+ type="button"
+ class="wb-link"
+ @click="editDualTask(1)"
+ >宸ヤ綅1浠诲姟鍙�</button>
+ <button
+ v-if="activeCard === 'dualCrn'"
+ type="button"
+ class="wb-link"
+ @click="editDualTask(2)"
+ >宸ヤ綅2浠诲姟鍙�</button>
+ </div>
+ </div>
+
+ <div class="wb-detail-grid">
+ <div v-for="entry in detailEntries" :key="entry.label" class="wb-detail-cell">
+ <div class="wb-detail-label">{{ entry.label }}</div>
+ <div class="wb-detail-value">{{ entry.value }}</div>
+ </div>
+ </div>
+ </div>
+ <div class="wb-detail wb-detail-empty" v-else>
+ <div class="wb-empty">璇峰厛浠庡乏渚ч�夋嫨涓�涓澶�</div>
+ </div>
+ </div>
+ </div>
+
+ <div v-if="noticeMessage" :class="['wb-notice', 'is-' + noticeType]">{{ noticeMessage }}</div>
+ </div>
+ `,
+ props: {
+ activeCard: { type: String, default: "crn" },
+ crnParam: { type: Object, default: function () { return {}; } },
+ dualCrnParam: { type: Object, default: function () { return {}; } },
+ devpParam: { type: Object, default: function () { return {}; } },
+ rgvParam: { type: Object, default: function () { return {}; } },
+ crnList: { type: Array, default: function () { return []; } },
+ dualCrnList: { type: Array, default: function () { return []; } },
+ stationList: { type: Array, default: function () { return []; } },
+ rgvList: { type: Array, default: function () { return []; } }
+ },
+ data() {
+ return {
+ tabs: [
+ { key: "crn", label: "鍫嗗灈鏈�" },
+ { key: "dualCrn", label: "鍙屽伐浣�" },
+ { key: "devp", label: "杈撻�佺珯" },
+ { key: "rgv", label: "RGV" }
+ ],
+ searchMap: {
+ crn: "",
+ dualCrn: "",
+ devp: "",
+ rgv: ""
+ },
+ selectedIdMap: {
+ crn: null,
+ dualCrn: null,
+ devp: null,
+ rgv: null
+ },
+ showControlMap: {
+ crn: false,
+ dualCrn: false,
+ devp: false,
+ rgv: false
+ },
+ controlForms: {
+ crn: { crnNo: "", sourceLocNo: "", targetLocNo: "" },
+ dualCrn: { crnNo: "", sourceLocNo: "", targetLocNo: "", station: 1 },
+ devp: { stationId: "", taskNo: "", targetStationId: "" },
+ rgv: { rgvNo: "", sourcePos: "", targetPos: "" }
+ },
+ noticeMessage: "",
+ noticeType: "info",
+ noticeTimer: null
+ };
+ },
+ computed: {
+ currentSearch() {
+ return this.searchMap[this.activeCard] || "";
+ },
+ currentShowControl() {
+ return !!this.showControlMap[this.activeCard];
+ },
+ currentList() {
+ return this.getListByType(this.activeCard);
+ },
+ filteredList() {
+ const keyword = String(this.currentSearch || "").trim().toLowerCase();
+ if (!keyword) { return this.currentList; }
+ return this.currentList.filter((item) => this.matchesKeyword(this.activeCard, item, keyword));
+ },
+ selectedItem() {
+ return this.getSelectedItem(this.activeCard);
+ },
+ detailEntries() {
+ return this.buildDetailEntries(this.activeCard, this.selectedItem);
+ },
+ controlPanelTitle() {
+ if (this.activeCard === "crn") { return "鍫嗗灈鏈烘帶鍒�"; }
+ if (this.activeCard === "dualCrn") { return "鍙屽伐浣嶆帶鍒�"; }
+ if (this.activeCard === "devp") { return "杈撻�佺珯鎺у埗"; }
+ if (this.activeCard === "rgv") { return "RGV鎺у埗"; }
+ return "鎺у埗鎿嶄綔";
+ },
+ controlPanelHint() {
+ if (this.activeCard === "crn") { return "鍏堢‘璁よ澶囧彿锛屽啀濉啓婧愬簱浣嶅拰鐩爣搴撲綅銆�"; }
+ if (this.activeCard === "dualCrn") { return "鍏堥�夋嫨宸ヤ綅锛屽啀涓嬪彂鍙栬揣銆佹斁璐ф垨绉诲姩鎸囦护銆�"; }
+ if (this.activeCard === "devp") { return "鐢ㄤ簬绔欑偣涓嬪彂銆佸浣嶅拰鏉$爜缁存姢銆�"; }
+ if (this.activeCard === "rgv") { return "鐢ㄤ簬杞ㄩ亾杞﹀彇鏀捐揣銆佺Щ鍔ㄥ拰浠诲姟瀹屾垚銆�"; }
+ return "";
+ },
+ controlTargetText() {
+ if (!this.selectedItem) { return "鏈�変腑璁惧"; }
+ return "褰撳墠鐩爣: " + this.getItemTitle(this.activeCard, this.selectedItem);
+ }
+ },
+ watch: {
+ activeCard: {
+ immediate: true,
+ handler(type) {
+ this.ensureSelection(type);
+ }
+ },
+ crnList() { this.ensureSelection("crn"); },
+ dualCrnList() { this.ensureSelection("dualCrn"); },
+ stationList() { this.ensureSelection("devp"); },
+ rgvList() { this.ensureSelection("rgv"); },
+ crnParam: {
+ deep: true,
+ handler(v) { this.applyExternalFocus("crn", v && v.crnNo); }
+ },
+ dualCrnParam: {
+ deep: true,
+ handler(v) { this.applyExternalFocus("dualCrn", v && v.crnNo); }
+ },
+ devpParam: {
+ deep: true,
+ handler(v) { this.applyExternalFocus("devp", v && v.stationId); }
+ },
+ rgvParam: {
+ deep: true,
+ handler(v) { this.applyExternalFocus("rgv", v && v.rgvNo); }
+ }
+ },
+ beforeDestroy() {
+ if (this.noticeTimer) {
+ clearTimeout(this.noticeTimer);
+ this.noticeTimer = null;
+ }
+ },
+ methods: {
+ changeTab(type) {
+ if (type === this.activeCard) { return; }
+ this.$emit("change-tab", type);
+ },
+ updateSearch(value) {
+ this.$set(this.searchMap, this.activeCard, value);
+ this.ensureSelection(this.activeCard);
+ },
+ refreshCurrent() {
+ this.$emit("refresh-request", this.activeCard);
+ },
+ toggleControl() {
+ this.$set(this.showControlMap, this.activeCard, !this.currentShowControl);
+ if (this.currentShowControl && this.selectedItem) {
+ this.hydrateControlForm(this.activeCard, this.selectedItem);
+ }
+ },
+ getListByType(type) {
+ if (type === "crn") { return this.crnList || []; }
+ if (type === "dualCrn") { return this.dualCrnList || []; }
+ if (type === "devp") { return this.stationList || []; }
+ if (type === "rgv") { return this.rgvList || []; }
+ return [];
+ },
+ getItemId(type, item) {
+ if (!item) { return null; }
+ if (type === "crn" || type === "dualCrn") { return item.crnNo; }
+ if (type === "devp") { return item.stationId; }
+ if (type === "rgv") { return item.rgvNo; }
+ return null;
+ },
+ getSelectedItem(type) {
+ const list = this.filteredListForType(type);
+ if (!list.length) { return null; }
+ const selectedId = this.selectedIdMap[type];
+ for (let i = 0; i < list.length; i++) {
+ if (String(this.getItemId(type, list[i])) === String(selectedId)) {
+ return list[i];
+ }
+ }
+ return list[0];
+ },
+ filteredListForType(type) {
+ const keyword = String(this.searchMap[type] || "").trim().toLowerCase();
+ const list = this.getListByType(type);
+ if (!keyword) { return list; }
+ return list.filter((item) => this.matchesKeyword(type, item, keyword));
+ },
+ ensureSelection(type) {
+ const list = this.filteredListForType(type);
+ if (!list.length) {
+ this.$set(this.selectedIdMap, type, null);
+ return;
+ }
+ const currentId = this.selectedIdMap[type];
+ const exists = list.some((item) => String(this.getItemId(type, item)) === String(currentId));
+ if (!exists) {
+ this.$set(this.selectedIdMap, type, this.getItemId(type, list[0]));
+ }
+ },
+ applyExternalFocus(type, rawId) {
+ if (rawId == null || rawId === "" || rawId === 0) { return; }
+ this.$set(this.selectedIdMap, type, rawId);
+ if (this.activeCard === type) {
+ const item = this.getSelectedItem(type);
+ if (item) { this.hydrateControlForm(type, item); }
+ }
+ },
+ selectItem(type, item) {
+ this.$set(this.selectedIdMap, type, this.getItemId(type, item));
+ this.hydrateControlForm(type, item);
+ },
+ hydrateControlForm(type, item) {
+ if (!item) { return; }
+ if (type === "crn") {
+ this.controlForms.crn.crnNo = this.orEmpty(item.crnNo);
+ } else if (type === "dualCrn") {
+ this.controlForms.dualCrn.crnNo = this.orEmpty(item.crnNo);
+ } else if (type === "devp") {
+ this.controlForms.devp.stationId = this.orEmpty(item.stationId);
+ this.controlForms.devp.taskNo = this.orEmpty(item.taskNo);
+ this.controlForms.devp.targetStationId = this.orEmpty(item.targetStaNo);
+ } else if (type === "rgv") {
+ this.controlForms.rgv.rgvNo = this.orEmpty(item.rgvNo);
+ }
+ },
+ matchesKeyword(type, item, keyword) {
+ const fields = [];
+ if (type === "crn" || type === "dualCrn") {
+ fields.push(item.crnNo, item.taskNo, item.taskNoTwo, item.locNo, item.sourceLocNo, item.status, item.mode);
+ } else if (type === "devp") {
+ fields.push(item.stationId, item.taskNo, item.targetStaNo, item.barcode, item.errorMsg, item.extend);
+ } else if (type === "rgv") {
+ fields.push(item.rgvNo, item.taskNo, item.trackSiteNo, item.status, item.mode, item.alarm);
+ }
+ return fields.some((field) => String(field == null ? "" : field).toLowerCase().indexOf(keyword) !== -1);
+ },
+ currentSearchPlaceholder() {
+ if (this.activeCard === "crn") { return "鎼滅储鍫嗗灈鏈哄彿"; }
+ if (this.activeCard === "dualCrn") { return "鎼滅储鍙屽伐浣嶅爢鍨涙満鍙�"; }
+ if (this.activeCard === "devp") { return "鎼滅储绔欏彿 / 鏉$爜"; }
+ if (this.activeCard === "rgv") { return "鎼滅储RGV鍙�"; }
+ return "鎼滅储";
+ },
+ getItemTitle(type, item) {
+ if (!item) { return "-"; }
+ if (type === "crn") { return item.crnNo + "鍙峰爢鍨涙満"; }
+ if (type === "dualCrn") { return item.crnNo + "鍙峰弻宸ヤ綅鍫嗗灈鏈�"; }
+ if (type === "devp") { return item.stationId + "鍙风珯鐐�"; }
+ if (type === "rgv") { return item.rgvNo + "鍙稲GV"; }
+ return "-";
+ },
+ getItemMeta(type, item) {
+ if (!item) { return "-"; }
+ if (type === "crn") { return "浠诲姟 " + this.orDash(item.workNo) + " | 鐩爣 " + this.orDash(item.locNo); }
+ if (type === "dualCrn") { return "宸ヤ綅1 " + this.orDash(item.taskNo) + " | 宸ヤ綅2 " + this.orDash(item.taskNoTwo); }
+ if (type === "devp") { return "浠诲姟 " + this.orDash(item.taskNo) + " | 鐩爣绔� " + this.orDash(item.targetStaNo); }
+ if (type === "rgv") { return "杞ㄩ亾浣� " + this.orDash(item.trackSiteNo) + " | 浠诲姟 " + this.orDash(item.taskNo); }
+ return "-";
+ },
+ getStatusLabel(type, item) {
+ if (!item) { return "鏈煡"; }
+ if (type === "devp") { return item.autoing ? "鑷姩" : "鎵嬪姩"; }
+ const status = String(item.deviceStatus || "").toUpperCase();
+ if (status === "AUTO") { return "鑷姩"; }
+ if (status === "WORKING") { return "浣滀笟涓�"; }
+ if (status === "ERROR") { return "鏁呴殰"; }
+ return "绂荤嚎";
+ },
+ getStatusTone(type, item) {
+ const label = this.getStatusLabel(type, item);
+ if (label === "鑷姩") { return "success"; }
+ if (label === "浣滀笟涓�") { return "working"; }
+ if (label === "鎵嬪姩") { return "warning"; }
+ if (label === "鏁呴殰") { return "danger"; }
+ return "muted";
+ },
+ isSelected(type, item) {
+ return String(this.getItemId(type, item)) === String(this.selectedIdMap[type]);
+ },
+ buildDetailEntries(type, item) {
+ if (!item) { return []; }
+ if (type === "crn") {
+ return [
+ { label: "缂栧彿", value: this.orDash(item.crnNo) },
+ { label: "宸ヤ綔鍙�", value: this.orDash(item.workNo) },
+ { label: "妯″紡", value: this.orDash(item.mode) },
+ { label: "鐘舵��", value: this.orDash(item.status) },
+ { label: "婧愬簱浣�", value: this.orDash(item.sourceLocNo) },
+ { label: "鐩爣搴撲綅", value: this.orDash(item.locNo) },
+ { label: "鏄惁鏈夌墿", value: this.yesNo(item.loading) },
+ { label: "浠诲姟鎺ユ敹", value: this.orDash(item.taskReceive) },
+ { label: "鍒�", value: this.orDash(item.bay) },
+ { label: "灞�", value: this.orDash(item.lev) },
+ { label: "璐у弶瀹氫綅", value: this.orDash(item.forkOffset) },
+ { label: "杞借揣鍙板畾浣�", value: this.orDash(item.liftPos) },
+ { label: "璧拌瀹氫綅", value: this.orDash(item.walkPos) },
+ { label: "璧拌閫熷害", value: this.orDash(item.xspeed) },
+ { label: "鍗囬檷閫熷害", value: this.orDash(item.yspeed) },
+ { label: "鍙夌墮閫熷害", value: this.orDash(item.zspeed) },
+ { label: "绉伴噸鏁版嵁", value: this.orDash(item.weight) },
+ { label: "鏉$爜鏁版嵁", value: this.orDash(item.barcode) },
+ { label: "鏁呴殰浠g爜", value: this.orDash(item.warnCode) },
+ { label: "鏁呴殰鎻忚堪", value: this.orDash(item.alarm) }
+ ];
+ }
+ if (type === "dualCrn") {
+ return [
+ { label: "妯″紡", value: this.orDash(item.mode) },
+ { label: "寮傚父鐮�", value: this.orDash(item.warnCode) },
+ { label: "宸ヤ綅1浠诲姟鍙�", value: this.orDash(item.taskNo) },
+ { label: "宸ヤ綅2浠诲姟鍙�", value: this.orDash(item.taskNoTwo) },
+ { label: "宸ヤ綅1鐘舵��", value: this.orDash(item.status) },
+ { label: "宸ヤ綅2鐘舵��", value: this.orDash(item.statusTwo) },
+ { label: "宸ヤ綅1鏈夌墿", value: this.yesNo(item.loading) },
+ { label: "宸ヤ綅2鏈夌墿", value: this.yesNo(item.loadingTwo) },
+ { label: "鍒�", value: this.orDash(item.bay) },
+ { label: "灞�", value: this.orDash(item.lev) },
+ { label: "杞借揣鍙板畾浣�", value: this.orDash(item.liftPos) },
+ { label: "璧拌瀹氫綅", value: this.orDash(item.walkPos) },
+ { label: "璧拌閫熷害", value: this.orDash(item.xspeed) },
+ { label: "鍗囬檷閫熷害", value: this.orDash(item.yspeed) },
+ { label: "鍙夌墮閫熷害", value: this.orDash(item.zspeed) },
+ { label: "鎵╁睍鏁版嵁", value: this.orDash(item.extend) }
+ ];
+ }
+ if (type === "devp") {
+ return [
+ { label: "缂栧彿", value: this.orDash(item.stationId) },
+ { label: "宸ヤ綔鍙�", value: this.orDash(item.taskNo) },
+ { label: "鐩爣绔�", value: this.orDash(item.targetStaNo) },
+ { label: "妯″紡", value: item.autoing ? "鑷姩" : "鎵嬪姩" },
+ { label: "鏈夌墿", value: this.yesNo(item.loading) },
+ { label: "鍙叆", value: this.yesNo(item.inEnable) },
+ { label: "鍙嚭", value: this.yesNo(item.outEnable) },
+ { label: "绌烘澘淇″彿", value: this.yesNo(item.emptyMk) },
+ { label: "婊℃澘淇″彿", value: this.yesNo(item.fullPlt) },
+ { label: "杩愯闃诲", value: this.yesNo(item.runBlock) },
+ { label: "鍚姩鍏ュ簱", value: this.yesNo(item.enableIn) },
+ { label: "鎵樼洏楂樺害", value: this.orDash(item.palletHeight) },
+ { label: "鏉$爜", value: this.orDash(item.barcode) },
+ { label: "閲嶉噺", value: this.orDash(item.weight) },
+ { label: "浠诲姟鍙啓鍖�", value: this.orDash(item.taskWriteIdx) },
+ { label: "鏁呴殰浠g爜", value: this.orDash(item.error) },
+ { label: "鏁呴殰淇℃伅", value: this.orDash(item.errorMsg) }
+ ];
+ }
+ if (type === "rgv") {
+ return [
+ { label: "缂栧彿", value: this.orDash(item.rgvNo) },
+ { label: "宸ヤ綔鍙�", value: this.orDash(item.taskNo) },
+ { label: "妯″紡", value: this.orDash(item.mode) },
+ { label: "鐘舵��", value: this.orDash(item.status) },
+ { label: "杞ㄩ亾浣�", value: this.orDash(item.trackSiteNo) },
+ { label: "鏄惁鏈夌墿", value: this.yesNo(item.loading) },
+ { label: "鏁呴殰浠g爜", value: this.orDash(item.warnCode) },
+ { label: "鏁呴殰鎻忚堪", value: this.orDash(item.alarm) },
+ { label: "鎵╁睍鏁版嵁", value: this.orDash(item.extend) }
+ ];
+ }
+ return [];
+ },
+ submitControl(action) {
+ const config = this.getControlConfig(this.activeCard, action);
+ if (!config) { return; }
+ $.ajax({
+ url: baseUrl + config.url,
+ headers: { token: localStorage.getItem("token") },
+ contentType: "application/json",
+ method: "post",
+ data: JSON.stringify(config.payload),
+ success: (res) => {
+ if (res && res.code === 200) {
+ this.showNotice(res.msg || "鎿嶄綔鎴愬姛", "success");
+ this.$emit("refresh-request", this.activeCard);
+ } else {
+ this.showNotice((res && res.msg) || "鎿嶄綔澶辫触", "warning");
+ }
+ },
+ error: () => {
+ this.showNotice("璇锋眰澶辫触", "danger");
+ }
+ });
+ },
+ getControlConfig(type, action) {
+ if (type === "crn") {
+ return {
+ url: { transport: "/crn/command/take", move: "/crn/command/move", taskComplete: "/crn/command/taskComplete" }[action],
+ payload: this.controlForms.crn
+ };
+ }
+ if (type === "dualCrn") {
+ return {
+ url: {
+ transport: "/dualcrn/command/take",
+ pickup: "/dualcrn/command/pick",
+ putdown: "/dualcrn/command/put",
+ move: "/dualcrn/command/move",
+ taskComplete: "/dualcrn/command/taskComplete"
+ }[action],
+ payload: this.controlForms.dualCrn
+ };
+ }
+ if (type === "devp") {
+ return {
+ url: { move: "/station/command/move", reset: "/station/command/reset" }[action],
+ payload: this.controlForms.devp
+ };
+ }
+ if (type === "rgv") {
+ return {
+ url: { transport: "/rgv/command/transport", move: "/rgv/command/move", taskComplete: "/rgv/command/taskComplete" }[action],
+ payload: this.controlForms.rgv
+ };
+ }
+ return null;
+ },
+ editBarcode() {
+ const item = this.selectedItem;
+ if (!item || item.stationId == null) { return; }
+ const barcode = window.prompt("璇疯緭鍏ユ柊鐨勬潯鐮佸�硷紙鍙暀绌烘竻绌猴級", item.barcode || "");
+ if (barcode === null) { return; }
+ $.ajax({
+ url: baseUrl + "/station/command/barcode",
+ headers: { token: localStorage.getItem("token") },
+ contentType: "application/json",
+ method: "post",
+ data: JSON.stringify({ stationId: item.stationId, barcode: String(barcode).trim() }),
+ success: (res) => {
+ if (res && res.code === 200) {
+ this.showNotice("鏉$爜淇敼鎴愬姛", "success");
+ this.$emit("refresh-request", "devp");
+ } else {
+ this.showNotice((res && res.msg) || "鏉$爜淇敼澶辫触", "warning");
+ }
+ },
+ error: () => { this.showNotice("鏉$爜淇敼澶辫触", "danger"); }
+ });
+ },
+ editDualTask(station) {
+ const item = this.selectedItem;
+ if (!item || item.crnNo == null) { return; }
+ const currentValue = station === 1 ? item.taskNo : item.taskNoTwo;
+ const value = window.prompt("璇疯緭鍏ュ伐浣�" + station + "浠诲姟鍙�", currentValue == null ? "" : String(currentValue));
+ if (value === null) { return; }
+ if (!/^\d+$/.test(String(value).trim())) {
+ this.showNotice("浠诲姟鍙峰繀椤绘槸闈炶礋鏁存暟", "warning");
+ return;
+ }
+ $.ajax({
+ url: baseUrl + "/dualcrn/command/updateTaskNo",
+ headers: { token: localStorage.getItem("token") },
+ contentType: "application/json",
+ method: "post",
+ data: JSON.stringify({ crnNo: item.crnNo, station: station, taskNo: Number(value) }),
+ success: (res) => {
+ if (res && res.code === 200) {
+ this.showNotice("浠诲姟鍙锋洿鏂版垚鍔�", "success");
+ this.$emit("refresh-request", "dualCrn");
+ } else {
+ this.showNotice((res && res.msg) || "浠诲姟鍙锋洿鏂板け璐�", "warning");
+ }
+ },
+ error: () => { this.showNotice("浠诲姟鍙锋洿鏂板け璐�", "danger"); }
+ });
+ },
+ showNotice(message, type) {
+ this.noticeMessage = message;
+ this.noticeType = type || "info";
+ if (this.noticeTimer) { clearTimeout(this.noticeTimer); }
+ this.noticeTimer = setTimeout(() => {
+ this.noticeMessage = "";
+ this.noticeTimer = null;
+ }, 2200);
+ },
+ yesNo(value) {
+ if (value === true || value === "Y" || value === "y" || value === 1 || value === "1") { return "Y"; }
+ if (value === false || value === 0 || value === "0") { return "N"; }
+ return value ? "Y" : "N";
+ },
+ orDash(value) {
+ return value == null || value === "" ? "-" : String(value);
+ },
+ orEmpty(value) {
+ return value == null ? "" : String(value);
+ }
+ }
+});
diff --git a/src/main/webapp/components/WatchCrnCard.js b/src/main/webapp/components/WatchCrnCard.js
index 53c90a2..0d7e292 100644
--- a/src/main/webapp/components/WatchCrnCard.js
+++ b/src/main/webapp/components/WatchCrnCard.js
@@ -1,96 +1,86 @@
Vue.component("watch-crn-card", {
template: `
- <div>
- <div style="display: flex;margin-bottom: 10px;">
- <div style="width: 100%;">鍫嗗灈鏈虹洃鎺�</div>
- <div style="width: 100%;text-align: right;display: flex;"><el-input size="mini" v-model="searchCrnNo" placeholder="璇疯緭鍏ュ爢鍨涙満鍙�"></el-input><el-button @click="getCrnStateInfo" size="mini">鏌ヨ</el-button></div>
+ <div class="mc-root">
+ <div class="mc-toolbar">
+ <div class="mc-title">鍫嗗灈鏈虹洃鎺�</div>
+ <div class="mc-search">
+ <input class="mc-input" v-model="searchCrnNo" placeholder="璇疯緭鍏ュ爢鍨涙満鍙�" />
+ <button type="button" class="mc-btn mc-btn-ghost" @click="getCrnStateInfo">鏌ヨ</button>
</div>
- <div style="margin-bottom: 10px;" v-if="!readOnly">
- <div style="margin-bottom: 5px;">
- <el-button v-if="showControl" @click="openControl" size="mini">鍏抽棴鎺у埗涓績</el-button>
- <el-button v-else @click="openControl" size="mini">鎵撳紑鎺у埗涓績</el-button>
+ </div>
+
+ <div v-if="!readOnly" class="mc-control-toggle">
+ <button type="button" class="mc-btn mc-btn-ghost" @click="openControl">
+ {{ showControl ? '鏀惰捣鎺у埗涓績' : '鎵撳紑鎺у埗涓績' }}
+ </button>
+ </div>
+
+ <div v-if="showControl" class="mc-control">
+ <div class="mc-control-grid">
+ <label class="mc-field">
+ <span class="mc-field-label">鍫嗗灈鏈哄彿</span>
+ <input class="mc-input" v-model="controlParam.crnNo" placeholder="渚嬪 1" />
+ </label>
+ <label class="mc-field">
+ <span class="mc-field-label">婧愬簱浣�</span>
+ <input class="mc-input" v-model="controlParam.sourceLocNo" placeholder="杈撳叆婧愮偣" />
+ </label>
+ <label class="mc-field mc-span-2">
+ <span class="mc-field-label">鐩爣搴撲綅</span>
+ <input class="mc-input" v-model="controlParam.targetLocNo" placeholder="杈撳叆鐩爣鐐�" />
+ </label>
+ <div class="mc-action-row">
+ <button type="button" class="mc-btn" @click="controlCommandTransport">鍙栨斁璐�</button>
+ <button type="button" class="mc-btn mc-btn-ghost" @click="controlCommandMove">绉诲姩</button>
+ <button type="button" class="mc-btn mc-btn-soft" @click="controlCommandTaskComplete">浠诲姟瀹屾垚</button>
+ </div>
+ </div>
+ </div>
+
+ <div class="mc-collapse">
+ <div
+ v-for="item in displayCrnList"
+ :key="item.crnNo"
+ :class="['mc-item', { 'is-open': isActive(item.crnNo) }]"
+ >
+ <button type="button" class="mc-head" @click="toggleItem(item)">
+ <div class="mc-head-main">
+ <div class="mc-head-title">{{ item.crnNo }}鍙峰爢鍨涙満</div>
+ <div class="mc-head-subtitle">宸ヤ綔鍙� {{ orDash(item.workNo) }} | 鐩爣 {{ orDash(item.locNo) }}</div>
</div>
- <div v-if="showControl" style="display: flex;justify-content: space-between;flex-wrap: wrap;">
- <div style="margin-bottom: 10px;width: 33%;"><el-input size="mini" v-model="controlParam.crnNo" placeholder="鍫嗗灈鏈哄彿"></el-input></div>
- <div style="margin-bottom: 10px;width: 33%;"><el-input size="mini" v-model="controlParam.sourceLocNo" placeholder="婧愮偣"></el-input></div>
- <div style="margin-bottom: 10px;width: 33%;"><el-input size="mini" v-model="controlParam.targetLocNo" placeholder="鐩爣鐐�"></el-input></div>
- <div style="margin-bottom: 10px;"><el-button @click="controlCommandTransport()" size="mini">鍙栨斁璐�</el-button></div>
- <div style="margin-bottom: 10px;"><el-button @click="controlCommandMove()" size="mini">绉诲姩</el-button></div>
- <div style="margin-bottom: 10px;"><el-button @click="controlCommandTaskComplete()" size="mini">浠诲姟瀹屾垚</el-button></div>
+ <div class="mc-head-right">
+ <span :class="['mc-badge', 'is-' + getStatusTone(item)]">{{ getStatusLabel(item) }}</span>
+ <span class="mc-chevron">{{ isActive(item.crnNo) ? '鈻�' : '鈻�' }}</span>
</div>
+ </button>
+
+ <div v-if="isActive(item.crnNo)" class="mc-body">
+ <div class="mc-detail-grid">
+ <div v-for="entry in buildDetailEntries(item)" :key="entry.label" class="mc-detail-cell">
+ <div class="mc-detail-label">{{ entry.label }}</div>
+ <div class="mc-detail-value">{{ entry.value }}</div>
+ </div>
+ </div>
+ </div>
</div>
- <div style="max-height: 55vh; overflow:auto;">
- <el-collapse v-model="activeNames" accordion>
- <el-collapse-item v-for="(item) in displayCrnList" :name="item.crnNo">
- <template slot="title">
- <div style="width: 100%;display: flex;">
- <div style="width: 50%;">{{ item.crnNo }}鍙峰爢鍨涙満</div>
- <div style="width: 50%;text-align: right;">
- <el-tag v-if="item.deviceStatus == 'AUTO'" type="success" size="small">鑷姩</el-tag>
- <el-tag v-else-if="item.deviceStatus == 'WORKING'" size="small">浣滀笟涓�</el-tag>
- <el-tag v-else-if="item.deviceStatus == 'ERROR'" type="danger" size="small">鏁呴殰</el-tag>
- <el-tag v-else type="warning" size="small">绂荤嚎</el-tag>
- </div>
- </div>
- </template>
- <el-descriptions border direction="vertical">
- <el-descriptions-item label="缂栧彿">{{ item.crnNo }}</el-descriptions-item>
- <el-descriptions-item label="宸ヤ綔鍙�">{{ item.workNo }}</el-descriptions-item>
- <el-descriptions-item label="妯″紡">{{ item.mode }}</el-descriptions-item>
- <el-descriptions-item label="鐘舵��">{{ item.status }}</el-descriptions-item>
- <el-descriptions-item label="婧愬簱浣�">{{ item.sourceLocNo }}</el-descriptions-item>
- <el-descriptions-item label="鐩爣搴撲綅">{{ item.locNo }}</el-descriptions-item>
- <el-descriptions-item label="鏄惁鏈夌墿">{{ item.loading }}</el-descriptions-item>
- <el-descriptions-item label="浠诲姟鎺ユ敹">{{ item.taskReceive }}</el-descriptions-item>
- <el-descriptions-item label="鍒�">{{ item.bay }}</el-descriptions-item>
- <el-descriptions-item label="灞�">{{ item.lev }}</el-descriptions-item>
- <el-descriptions-item label="璐у弶瀹氫綅">{{ item.forkOffset }}</el-descriptions-item>
- <el-descriptions-item label="杞借揣鍙板畾浣�">{{ item.liftPos }}</el-descriptions-item>
- <el-descriptions-item label="璧拌鍦ㄥ畾浣�">{{ item.walkPos }}</el-descriptions-item>
- <el-descriptions-item label="璧拌閫熷害锛坢/min)">{{ item.xspeed }}</el-descriptions-item>
- <el-descriptions-item label="鍗囬檷閫熷害锛坢/min)">{{ item.yspeed }}</el-descriptions-item>
- <el-descriptions-item label="鍙夌墮閫熷害锛坢/min)">{{ item.zspeed }}</el-descriptions-item>
- <el-descriptions-item label="璧拌璺濈(Km)">{{ item.xdistance }}</el-descriptions-item>
- <el-descriptions-item label="鍗囬檷璺濈(Km)">{{ item.ydistance }}</el-descriptions-item>
- <el-descriptions-item label="璧拌鏃堕暱(H)">{{ item.xduration }}</el-descriptions-item>
- <el-descriptions-item label="鍗囬檷鏃堕暱(H)">{{ item.yduration }}</el-descriptions-item>
- <el-descriptions-item label="绉伴噸鏁版嵁">{{ item.weight }}</el-descriptions-item>
- <el-descriptions-item label="鏉$爜鏁版嵁">{{ item.barcode }}</el-descriptions-item>
- <el-descriptions-item label="鏁呴殰浠g爜">{{ item.warnCode }}</el-descriptions-item>
- <el-descriptions-item label="鏁呴殰鎻忚堪">{{ item.alarm }}</el-descriptions-item>
- <el-descriptions-item label="鎵╁睍鏁版嵁">{{ item.extend }}</el-descriptions-item>
- </el-descriptions>
- </el-collapse-item>
- </el-collapse>
- </div>
- <div style="display:flex; justify-content:flex-end; margin-top:8px;">
- <el-pagination
- @current-change="handlePageChange"
- @size-change="handleSizeChange"
- :current-page="currentPage"
- :page-size="pageSize"
- :page-sizes="[10,20,50,100]"
- layout="total, prev, pager, next"
- :total="crnList.length">
- </el-pagination>
- </div>
+
+ <div v-if="displayCrnList.length === 0" class="mc-empty">褰撳墠娌℃湁鍙睍绀虹殑鍫嗗灈鏈烘暟鎹�</div>
+ </div>
+
+ <div class="mc-footer">
+ <button type="button" class="mc-page-btn" :disabled="currentPage <= 1" @click="handlePageChange(currentPage - 1)">涓婁竴椤�</button>
+ <span>{{ currentPage }} / {{ totalPages }}</span>
+ <button type="button" class="mc-page-btn" :disabled="currentPage >= totalPages" @click="handlePageChange(currentPage + 1)">涓嬩竴椤�</button>
+ </div>
</div>
- `,
+ `,
props: {
- param: {
- type: Object,
- default: () => ({})
- },
- autoRefresh: {
- type: Boolean,
- default: true
- },
- readOnly: {
- type: Boolean,
- default: false
- }
+ param: { type: Object, default: function () { return {}; } },
+ items: { type: Array, default: null },
+ autoRefresh: { type: Boolean, default: true },
+ readOnly: { type: Boolean, default: false }
},
- data() {
+ data: function () {
return {
crnList: [],
activeNames: "",
@@ -99,159 +89,176 @@
controlParam: {
crnNo: "",
sourceLocNo: "",
- targetLocNo: "",
+ targetLocNo: ""
},
- pageSize: 25,
+ pageSize: 12,
currentPage: 1,
timer: null
};
},
- created() {
- if (this.autoRefresh) {
- this.timer = setInterval(() => {
- this.getCrnStateInfo();
- }, 1000);
- }
- },
- beforeDestroy() {
- if (this.timer) {
- clearInterval(this.timer);
- }
- },
computed: {
- displayCrnList() {
- const start = (this.currentPage - 1) * this.pageSize;
- const end = start + this.pageSize;
- return this.crnList.slice(start, end);
+ sourceList: function () {
+ return Array.isArray(this.items) ? this.items : this.crnList;
+ },
+ filteredCrnList: function () {
+ var keyword = String(this.searchCrnNo || "").trim();
+ if (!keyword) {
+ return this.sourceList;
+ }
+ return this.sourceList.filter(function (item) {
+ return String(item.crnNo) === keyword;
+ });
+ },
+ displayCrnList: function () {
+ var start = (this.currentPage - 1) * this.pageSize;
+ return this.filteredCrnList.slice(start, start + this.pageSize);
+ },
+ totalPages: function () {
+ return Math.max(1, Math.ceil(this.filteredCrnList.length / this.pageSize) || 1);
}
},
watch: {
- param: {
- handler(newVal, oldVal) {
- if (newVal && newVal.crnNo && newVal.crnNo != 0) {
- this.activeNames = newVal.crnNo;
- this.searchCrnNo = newVal.crnNo;
- const idx = this.crnList.findIndex(i => i.crnNo == newVal.crnNo);
- if (idx >= 0) { this.currentPage = Math.floor(idx / this.pageSize) + 1; }
- }
- },
- deep: true, // 娣卞害鐩戝惉宓屽灞炴��
- immediate: true, // 绔嬪嵆瑙﹀彂涓�娆★紙鍙�夛級
+ items: function () {
+ this.afterDataRefresh();
},
+ param: {
+ deep: true,
+ immediate: true,
+ handler: function (newVal) {
+ if (newVal && newVal.crnNo && newVal.crnNo !== 0) {
+ this.focusCrn(newVal.crnNo);
+ }
+ }
+ }
+ },
+ created: function () {
+ MonitorCardKit.ensureStyles();
+ if (this.autoRefresh) {
+ this.timer = setInterval(this.getCrnStateInfo, 1000);
+ }
+ },
+ beforeDestroy: function () {
+ if (this.timer) {
+ clearInterval(this.timer);
+ this.timer = null;
+ }
},
methods: {
- handlePageChange(page) {
+ orDash: function (value) {
+ return MonitorCardKit.orDash(value);
+ },
+ getStatusLabel: function (item) {
+ return MonitorCardKit.deviceStatusLabel(item && item.deviceStatus);
+ },
+ getStatusTone: function (item) {
+ return MonitorCardKit.statusTone(this.getStatusLabel(item));
+ },
+ isActive: function (crnNo) {
+ return String(this.activeNames) === String(crnNo);
+ },
+ toggleItem: function (item) {
+ var next = String(item.crnNo);
+ this.activeNames = this.activeNames === next ? "" : next;
+ },
+ focusCrn: function (crnNo) {
+ this.searchCrnNo = String(crnNo);
+ var index = this.filteredCrnList.findIndex(function (item) {
+ return String(item.crnNo) === String(crnNo);
+ });
+ if (index >= 0) {
+ this.currentPage = Math.floor(index / this.pageSize) + 1;
+ } else {
+ this.currentPage = 1;
+ }
+ this.activeNames = String(crnNo);
+ },
+ afterDataRefresh: function () {
+ if (this.currentPage > this.totalPages) {
+ this.currentPage = this.totalPages;
+ }
+ if (this.activeNames) {
+ var exists = this.filteredCrnList.some(function (item) {
+ return String(item.crnNo) === String(this.activeNames);
+ }, this);
+ if (!exists) {
+ this.activeNames = "";
+ }
+ }
+ },
+ handlePageChange: function (page) {
+ if (page < 1 || page > this.totalPages) {
+ return;
+ }
this.currentPage = page;
},
- handleSizeChange(size) {
- this.pageSize = size;
- this.currentPage = 1;
- },
- getCrnStateInfo() {
- if (this.$root.sendWs) {
+ getCrnStateInfo: function () {
+ if (this.$root && this.$root.sendWs) {
this.$root.sendWs(JSON.stringify({
- "url": "/crn/table/crn/state",
- "data": {}
+ url: "/crn/table/crn/state",
+ data: {}
}));
}
},
- setCrnList(res) {
- let that = this;
- if (res.code == 200) {
- let list = res.data;
-
- if (that.searchCrnNo == "") {
- that.crnList = list;
- } else {
- let tmp = [];
- list.forEach((item) => {
- if (item.crnNo == that.searchCrnNo) {
- tmp.push(item);
- }
- });
- that.crnList = tmp;
- that.currentPage = 1;
- }
+ setCrnList: function (res) {
+ if (res && res.code === 200) {
+ this.crnList = res.data || [];
+ this.afterDataRefresh();
}
},
- openControl() {
+ openControl: function () {
this.showControl = !this.showControl;
},
- controlCommandTransport() {
- let that = this;
- //鍙栨斁璐�
+ buildDetailEntries: function (item) {
+ return [
+ { label: "缂栧彿", value: this.orDash(item.crnNo) },
+ { label: "宸ヤ綔鍙�", value: this.orDash(item.workNo) },
+ { label: "妯″紡", value: this.orDash(item.mode) },
+ { label: "鐘舵��", value: this.orDash(item.status) },
+ { label: "婧愬簱浣�", value: this.orDash(item.sourceLocNo) },
+ { label: "鐩爣搴撲綅", value: this.orDash(item.locNo) },
+ { label: "鏄惁鏈夌墿", value: MonitorCardKit.yesNo(item.loading) },
+ { label: "浠诲姟鎺ユ敹", value: this.orDash(item.taskReceive) },
+ { label: "鍒�", value: this.orDash(item.bay) },
+ { label: "灞�", value: this.orDash(item.lev) },
+ { label: "璐у弶瀹氫綅", value: this.orDash(item.forkOffset) },
+ { label: "杞借揣鍙板畾浣�", value: this.orDash(item.liftPos) },
+ { label: "璧拌瀹氫綅", value: this.orDash(item.walkPos) },
+ { label: "璧拌閫熷害", value: this.orDash(item.xspeed) },
+ { label: "鍗囬檷閫熷害", value: this.orDash(item.yspeed) },
+ { label: "鍙夌墮閫熷害", value: this.orDash(item.zspeed) },
+ { label: "绉伴噸鏁版嵁", value: this.orDash(item.weight) },
+ { label: "鏉$爜鏁版嵁", value: this.orDash(item.barcode) },
+ { label: "鏁呴殰浠g爜", value: this.orDash(item.warnCode) },
+ { label: "鏁呴殰鎻忚堪", value: this.orDash(item.alarm) },
+ { label: "鎵╁睍鏁版嵁", value: this.orDash(item.extend) }
+ ];
+ },
+ postControl: function (url, payload) {
$.ajax({
- url: baseUrl + "/crn/command/take",
+ url: baseUrl + url,
headers: {
- token: localStorage.getItem("token"),
+ token: localStorage.getItem("token")
},
contentType: "application/json",
method: "post",
- data: JSON.stringify(that.controlParam),
- success: (res) => {
- if (res.code == 200) {
- that.$message({
- message: res.msg,
- type: "success",
- });
+ data: JSON.stringify(payload),
+ success: function (res) {
+ if (res && res.code === 200) {
+ MonitorCardKit.showMessage(this, res.msg || "鎿嶄綔鎴愬姛", "success");
} else {
- that.$message({
- message: res.msg,
- type: "warning",
- });
+ MonitorCardKit.showMessage(this, (res && res.msg) || "鎿嶄綔澶辫触", "warning");
}
- },
+ }.bind(this)
});
},
- controlCommandMove() {
- let that = this;
- $.ajax({
- url: baseUrl + "/crn/command/move",
- headers: {
- token: localStorage.getItem("token"),
- },
- contentType: "application/json",
- method: "post",
- data: JSON.stringify(that.controlParam),
- success: (res) => {
- if (res.code == 200) {
- that.$message({
- message: res.msg,
- type: "success",
- });
- } else {
- that.$message({
- message: res.msg,
- type: "warning",
- });
- }
- },
- });
+ controlCommandTransport: function () {
+ this.postControl("/crn/command/take", this.controlParam);
},
- controlCommandTaskComplete() {
- let that = this;
- $.ajax({
- url: baseUrl + "/crn/command/taskComplete",
- headers: {
- token: localStorage.getItem("token"),
- },
- contentType: "application/json",
- method: "post",
- data: JSON.stringify(that.controlParam),
- success: (res) => {
- if (res.code == 200) {
- that.$message({
- message: res.msg,
- type: "success",
- });
- } else {
- that.$message({
- message: res.msg,
- type: "warning",
- });
- }
- },
- });
+ controlCommandMove: function () {
+ this.postControl("/crn/command/move", this.controlParam);
},
- },
+ controlCommandTaskComplete: function () {
+ this.postControl("/crn/command/taskComplete", this.controlParam);
+ }
+ }
});
diff --git a/src/main/webapp/components/WatchDualCrnCard.js b/src/main/webapp/components/WatchDualCrnCard.js
index 6845338..886db1e 100644
--- a/src/main/webapp/components/WatchDualCrnCard.js
+++ b/src/main/webapp/components/WatchDualCrnCard.js
@@ -1,128 +1,99 @@
Vue.component("watch-dual-crn-card", {
template: `
- <div>
- <div style="display: flex;margin-bottom: 10px;">
- <div style="width: 100%;">鍙屽伐浣嶅爢鍨涙満鐩戞帶</div>
- <div style="width: 100%;text-align: right;display: flex;">
- <el-input size="mini" v-model="searchCrnNo" placeholder="璇疯緭鍏ュ爢鍨涙満鍙�"></el-input>
- <el-button @click="getDualCrnStateInfo" size="mini">鏌ヨ</el-button>
- </div>
+ <div class="mc-root">
+ <div class="mc-toolbar">
+ <div class="mc-title">鍙屽伐浣嶅爢鍨涙満鐩戞帶</div>
+ <div class="mc-search">
+ <input class="mc-input" v-model="searchCrnNo" placeholder="璇疯緭鍏ュ爢鍨涙満鍙�" />
+ <button type="button" class="mc-btn mc-btn-ghost" @click="getDualCrnStateInfo">鏌ヨ</button>
</div>
- <div style="margin-bottom: 10px;" v-if="!readOnly">
- <div style="margin-bottom: 5px;">
- <el-button v-if="showControl" @click="openControl" size="mini">鍏抽棴鎺у埗涓績</el-button>
- <el-button v-else @click="openControl" size="mini">鎵撳紑鎺у埗涓績</el-button>
- </div>
- <div v-if="showControl" style="display: flex;justify-content: space-between;flex-wrap: wrap;">
- <div style="margin-bottom: 10px;width: 33%;"><el-input size="mini" v-model="controlParam.crnNo" placeholder="鍫嗗灈鏈哄彿"></el-input></div>
- <div style="margin-bottom: 10px;width: 33%;"><el-input size="mini" v-model="controlParam.sourceLocNo" placeholder="婧愮偣"></el-input></div>
- <div style="margin-bottom: 10px;width: 33%;"><el-input size="mini" v-model="controlParam.targetLocNo" placeholder="鐩爣鐐�"></el-input></div>
- <div style="margin-bottom: 10px;width: 33%;">
- <el-select size="mini" v-model="controlParam.station" placeholder="宸ヤ綅">
- <el-option :label="'宸ヤ綅1'" :value="1"></el-option>
- <el-option :label="'宸ヤ綅2'" :value="2"></el-option>
- </el-select>
- </div>
- <div style="margin-bottom: 10px;"><el-button @click="controlCommandTransport()" size="mini">鍙栨斁璐�</el-button></div>
- <div style="margin-bottom: 10px;"><el-button @click="controlCommandPickup()" size="mini">鍙栬揣</el-button></div>
- <div style="margin-bottom: 10px;"><el-button @click="controlCommandPutdown()" size="mini">鏀捐揣</el-button></div>
- <div style="margin-bottom: 10px;"><el-button @click="controlCommandMove()" size="mini">绉诲姩</el-button></div>
- <div style="margin-bottom: 10px;"><el-button @click="controlCommandTaskComplete()" size="mini">浠诲姟瀹屾垚</el-button></div>
+ </div>
+
+ <div v-if="!readOnly" class="mc-control-toggle">
+ <button type="button" class="mc-btn mc-btn-ghost" @click="openControl">
+ {{ showControl ? '鏀惰捣鎺у埗涓績' : '鎵撳紑鎺у埗涓績' }}
+ </button>
+ </div>
+
+ <div v-if="showControl" class="mc-control">
+ <div class="mc-control-grid">
+ <label class="mc-field">
+ <span class="mc-field-label">鍫嗗灈鏈哄彿</span>
+ <input class="mc-input" v-model="controlParam.crnNo" placeholder="渚嬪 2" />
+ </label>
+ <label class="mc-field">
+ <span class="mc-field-label">宸ヤ綅</span>
+ <select class="mc-select" v-model="controlParam.station">
+ <option :value="1">宸ヤ綅1</option>
+ <option :value="2">宸ヤ綅2</option>
+ </select>
+ </label>
+ <label class="mc-field">
+ <span class="mc-field-label">婧愬簱浣�</span>
+ <input class="mc-input" v-model="controlParam.sourceLocNo" placeholder="杈撳叆婧愮偣" />
+ </label>
+ <label class="mc-field">
+ <span class="mc-field-label">鐩爣搴撲綅</span>
+ <input class="mc-input" v-model="controlParam.targetLocNo" placeholder="杈撳叆鐩爣鐐�" />
+ </label>
+ <div class="mc-action-row">
+ <button type="button" class="mc-btn" @click="controlCommandTransport">鍙栨斁璐�</button>
+ <button type="button" class="mc-btn mc-btn-ghost" @click="controlCommandPickup">鍙栬揣</button>
+ <button type="button" class="mc-btn mc-btn-ghost" @click="controlCommandPutdown">鏀捐揣</button>
+ <button type="button" class="mc-btn mc-btn-ghost" @click="controlCommandMove">绉诲姩</button>
+ <button type="button" class="mc-btn mc-btn-soft" @click="controlCommandTaskComplete">浠诲姟瀹屾垚</button>
</div>
</div>
- <div style="max-height: 55vh; overflow:auto;">
- <el-collapse v-model="activeNames" accordion>
- <el-collapse-item v-for="(item) in displayCrnList" :name="item.crnNo">
- <template slot="title">
- <div style="width: 100%;display: flex;">
- <div style="width: 50%;">{{ item.crnNo }}鍙峰弻宸ヤ綅鍫嗗灈鏈�</div>
- <div style="width: 50%;text-align: right;">
- <el-tag v-if="item.deviceStatus == 'AUTO'" type="success" size="small">鑷姩</el-tag>
- <el-tag v-else-if="item.deviceStatus == 'WORKING'" size="small">浣滀笟涓�</el-tag>
- <el-tag v-else-if="item.deviceStatus == 'ERROR'" type="danger" size="small">鏁呴殰</el-tag>
- <el-tag v-else type="warning" size="small">绂荤嚎</el-tag>
- </div>
- </div>
- </template>
- <el-descriptions border direction="vertical">
- <el-descriptions-item label="妯″紡">{{ item.mode }}</el-descriptions-item>
- <el-descriptions-item label="寮傚父鐮�">{{ item.warnCode }}</el-descriptions-item>
- <el-descriptions-item label="宸ヤ綅1浠诲姟鍙�">
- <span v-if="readOnly">{{ item.taskNo }}</span>
- <el-button
- v-else
- type="text"
- size="mini"
- style="padding:0;"
- @click.stop="editTaskNo(item, 1)"
- >{{ item.taskNo }}</el-button>
- </el-descriptions-item>
- <el-descriptions-item label="宸ヤ綅2浠诲姟鍙�">
- <span v-if="readOnly">{{ item.taskNoTwo }}</span>
- <el-button
- v-else
- type="text"
- size="mini"
- style="padding:0;"
- @click.stop="editTaskNo(item, 2)"
- >{{ item.taskNoTwo }}</el-button>
- </el-descriptions-item>
- <el-descriptions-item label="璁惧宸ヤ綅1浠诲姟鍙�">{{ item.deviceTaskNo }}</el-descriptions-item>
- <el-descriptions-item label="璁惧宸ヤ綅2浠诲姟鍙�">{{ item.deviceTaskNoTwo }}</el-descriptions-item>
- <el-descriptions-item label="宸ヤ綅1鐘舵��">{{ item.status }}</el-descriptions-item>
- <el-descriptions-item label="宸ヤ綅2鐘舵��">{{ item.statusTwo }}</el-descriptions-item>
- <el-descriptions-item label="宸ヤ綅1鏄惁鏈夌墿">{{ item.loading }}</el-descriptions-item>
- <el-descriptions-item label="宸ヤ綅2鏄惁鏈夌墿">{{ item.loadingTwo }}</el-descriptions-item>
- <el-descriptions-item label="宸ヤ綅1璐у弶瀹氫綅">{{ item.forkOffset }}</el-descriptions-item>
- <el-descriptions-item label="宸ヤ綅2璐у弶瀹氫綅">{{ item.forkOffsetTwo }}</el-descriptions-item>
- <el-descriptions-item label="宸ヤ綅1浠诲姟鎺ユ敹">{{ item.taskReceive }}</el-descriptions-item>
- <el-descriptions-item label="宸ヤ綅2浠诲姟鎺ユ敹">{{ item.taskReceiveTwo }}</el-descriptions-item>
- <el-descriptions-item label="宸ヤ綅1涓嬪彂鏁版嵁">{{ item.taskSend }}</el-descriptions-item>
- <el-descriptions-item label="宸ヤ綅2涓嬪彂鏁版嵁">{{ item.taskSendTwo }}</el-descriptions-item>
- <el-descriptions-item label="鍒�">{{ item.bay }}</el-descriptions-item>
- <el-descriptions-item label="灞�">{{ item.lev }}</el-descriptions-item>
- <el-descriptions-item label="杞借揣鍙板畾浣�">{{ item.liftPos }}</el-descriptions-item>
- <el-descriptions-item label="璧拌鍦ㄥ畾浣�">{{ item.walkPos }}</el-descriptions-item>
- <el-descriptions-item label="璧拌閫熷害锛坢/min)">{{ item.xspeed }}</el-descriptions-item>
- <el-descriptions-item label="鍗囬檷閫熷害锛坢/min)">{{ item.yspeed }}</el-descriptions-item>
- <el-descriptions-item label="鍙夌墮閫熷害锛坢/min)">{{ item.zspeed }}</el-descriptions-item>
- <el-descriptions-item label="璧拌璺濈(Km)">{{ item.xdistance }}</el-descriptions-item>
- <el-descriptions-item label="鍗囬檷璺濈(Km)">{{ item.ydistance }}</el-descriptions-item>
- <el-descriptions-item label="璧拌鏃堕暱(H)">{{ item.xduration }}</el-descriptions-item>
- <el-descriptions-item label="鍗囬檷鏃堕暱(H)">{{ item.yduration }}</el-descriptions-item>
- <el-descriptions-item label="鎵╁睍鏁版嵁">{{ item.extend }}</el-descriptions-item>
- </el-descriptions>
- </el-collapse-item>
- </el-collapse>
+ </div>
+
+ <div class="mc-collapse">
+ <div
+ v-for="item in displayCrnList"
+ :key="item.crnNo"
+ :class="['mc-item', { 'is-open': isActive(item.crnNo) }]"
+ >
+ <button type="button" class="mc-head" @click="toggleItem(item)">
+ <div class="mc-head-main">
+ <div class="mc-head-title">{{ item.crnNo }}鍙峰弻宸ヤ綅鍫嗗灈鏈�</div>
+ <div class="mc-head-subtitle">宸ヤ綅1 {{ orDash(item.taskNo) }} | 宸ヤ綅2 {{ orDash(item.taskNoTwo) }}</div>
+ </div>
+ <div class="mc-head-right">
+ <span :class="['mc-badge', 'is-' + getStatusTone(item)]">{{ getStatusLabel(item) }}</span>
+ <span class="mc-chevron">{{ isActive(item.crnNo) ? '鈻�' : '鈻�' }}</span>
+ </div>
+ </button>
+
+ <div v-if="isActive(item.crnNo)" class="mc-body">
+ <div class="mc-inline-actions" v-if="!readOnly">
+ <button type="button" class="mc-link" @click.stop="editTaskNo(item, 1)">缂栬緫宸ヤ綅1浠诲姟鍙�</button>
+ <button type="button" class="mc-link" @click.stop="editTaskNo(item, 2)">缂栬緫宸ヤ綅2浠诲姟鍙�</button>
+ </div>
+ <div class="mc-detail-grid">
+ <div v-for="entry in buildDetailEntries(item)" :key="entry.label" class="mc-detail-cell">
+ <div class="mc-detail-label">{{ entry.label }}</div>
+ <div class="mc-detail-value">{{ entry.value }}</div>
+ </div>
+ </div>
+ </div>
</div>
- <div style="display:flex; justify-content:flex-end; margin-top:8px;">
- <el-pagination
- @current-change="handlePageChange"
- @size-change="handleSizeChange"
- :current-page="currentPage"
- :page-size="pageSize"
- :page-sizes="[10,20,50,100]"
- layout="total, prev, pager, next"
- :total="crnList.length">
- </el-pagination>
- </div>
+
+ <div v-if="displayCrnList.length === 0" class="mc-empty">褰撳墠娌℃湁鍙睍绀虹殑鍙屽伐浣嶅爢鍨涙満鏁版嵁</div>
+ </div>
+
+ <div class="mc-footer">
+ <button type="button" class="mc-page-btn" :disabled="currentPage <= 1" @click="handlePageChange(currentPage - 1)">涓婁竴椤�</button>
+ <span>{{ currentPage }} / {{ totalPages }}</span>
+ <button type="button" class="mc-page-btn" :disabled="currentPage >= totalPages" @click="handlePageChange(currentPage + 1)">涓嬩竴椤�</button>
+ </div>
</div>
`,
props: {
- param: {
- type: Object,
- default: () => ({})
- },
- autoRefresh: {
- type: Boolean,
- default: true
- },
- readOnly: {
- type: Boolean,
- default: false
- }
+ param: { type: Object, default: function () { return {}; } },
+ items: { type: Array, default: null },
+ autoRefresh: { type: Boolean, default: true },
+ readOnly: { type: Boolean, default: false }
},
- data() {
+ data: function () {
return {
crnList: [],
activeNames: "",
@@ -132,251 +103,212 @@
crnNo: "",
sourceLocNo: "",
targetLocNo: "",
- station: 1,
+ station: 1
},
- pageSize: 25,
+ pageSize: 12,
currentPage: 1,
timer: null
};
},
- created() {
- if (this.autoRefresh) {
- this.timer = setInterval(() => {
- this.getDualCrnStateInfo();
- }, 1000);
- }
- },
- beforeDestroy() {
- if (this.timer) {
- clearInterval(this.timer);
- }
- },
computed: {
- displayCrnList() {
- const start = (this.currentPage - 1) * this.pageSize;
- const end = start + this.pageSize;
- return this.crnList.slice(start, end);
+ sourceList: function () {
+ return Array.isArray(this.items) ? this.items : this.crnList;
+ },
+ filteredCrnList: function () {
+ var keyword = String(this.searchCrnNo || "").trim();
+ if (!keyword) {
+ return this.sourceList;
+ }
+ return this.sourceList.filter(function (item) {
+ return String(item.crnNo) === keyword;
+ });
+ },
+ displayCrnList: function () {
+ var start = (this.currentPage - 1) * this.pageSize;
+ return this.filteredCrnList.slice(start, start + this.pageSize);
+ },
+ totalPages: function () {
+ return Math.max(1, Math.ceil(this.filteredCrnList.length / this.pageSize) || 1);
}
},
watch: {
+ items: function () {
+ this.afterDataRefresh();
+ },
param: {
- handler(newVal) {
- if (newVal && newVal.crnNo && newVal.crnNo != 0) {
- this.activeNames = newVal.crnNo;
- this.searchCrnNo = newVal.crnNo;
- const idx = this.crnList.findIndex(i => i.crnNo == newVal.crnNo);
- if (idx >= 0) { this.currentPage = Math.floor(idx / this.pageSize) + 1; }
- }
- },
deep: true,
immediate: true,
- },
+ handler: function (newVal) {
+ if (newVal && newVal.crnNo && newVal.crnNo !== 0) {
+ this.focusCrn(newVal.crnNo);
+ }
+ }
+ }
+ },
+ created: function () {
+ MonitorCardKit.ensureStyles();
+ if (this.autoRefresh) {
+ this.timer = setInterval(this.getDualCrnStateInfo, 1000);
+ }
+ },
+ beforeDestroy: function () {
+ if (this.timer) {
+ clearInterval(this.timer);
+ this.timer = null;
+ }
},
methods: {
- handlePageChange(page) {
+ orDash: function (value) {
+ return MonitorCardKit.orDash(value);
+ },
+ getStatusLabel: function (item) {
+ return MonitorCardKit.deviceStatusLabel(item && item.deviceStatus);
+ },
+ getStatusTone: function (item) {
+ return MonitorCardKit.statusTone(this.getStatusLabel(item));
+ },
+ isActive: function (crnNo) {
+ return String(this.activeNames) === String(crnNo);
+ },
+ toggleItem: function (item) {
+ var next = String(item.crnNo);
+ this.activeNames = this.activeNames === next ? "" : next;
+ },
+ focusCrn: function (crnNo) {
+ this.searchCrnNo = String(crnNo);
+ var index = this.filteredCrnList.findIndex(function (item) {
+ return String(item.crnNo) === String(crnNo);
+ });
+ this.currentPage = index >= 0 ? Math.floor(index / this.pageSize) + 1 : 1;
+ this.activeNames = String(crnNo);
+ },
+ afterDataRefresh: function () {
+ if (this.currentPage > this.totalPages) {
+ this.currentPage = this.totalPages;
+ }
+ if (this.activeNames) {
+ var exists = this.filteredCrnList.some(function (item) {
+ return String(item.crnNo) === String(this.activeNames);
+ }, this);
+ if (!exists) {
+ this.activeNames = "";
+ }
+ }
+ },
+ handlePageChange: function (page) {
+ if (page < 1 || page > this.totalPages) {
+ return;
+ }
this.currentPage = page;
},
- handleSizeChange(size) {
- this.pageSize = size;
- this.currentPage = 1;
+ getDualCrnStateInfo: function () {
+ if (this.$root && this.$root.sendWs) {
+ this.$root.sendWs(JSON.stringify({
+ url: "/dualcrn/table/crn/state",
+ data: {}
+ }));
+ }
},
- openControl() {
+ setDualCrnList: function (res) {
+ if (res && res.code === 200) {
+ this.crnList = res.data || [];
+ this.afterDataRefresh();
+ }
+ },
+ openControl: function () {
this.showControl = !this.showControl;
},
- editTaskNo(item, station) {
- let that = this;
- const isStationOne = station === 1;
- const fieldName = isStationOne ? "taskNo" : "taskNoTwo";
- const stationName = isStationOne ? "宸ヤ綅1" : "宸ヤ綅2";
- const currentTaskNo = item[fieldName] == null ? "" : String(item[fieldName]);
- that.$prompt("璇疯緭鍏�" + stationName + "浠诲姟鍙�", "缂栬緫浠诲姟鍙�", {
+ buildDetailEntries: function (item) {
+ return [
+ { label: "妯″紡", value: this.orDash(item.mode) },
+ { label: "寮傚父鐮�", value: this.orDash(item.warnCode) },
+ { label: "宸ヤ綅1浠诲姟鍙�", value: this.orDash(item.taskNo) },
+ { label: "宸ヤ綅2浠诲姟鍙�", value: this.orDash(item.taskNoTwo) },
+ { label: "璁惧宸ヤ綅1浠诲姟鍙�", value: this.orDash(item.deviceTaskNo) },
+ { label: "璁惧宸ヤ綅2浠诲姟鍙�", value: this.orDash(item.deviceTaskNoTwo) },
+ { label: "宸ヤ綅1鐘舵��", value: this.orDash(item.status) },
+ { label: "宸ヤ綅2鐘舵��", value: this.orDash(item.statusTwo) },
+ { label: "宸ヤ綅1鏄惁鏈夌墿", value: MonitorCardKit.yesNo(item.loading) },
+ { label: "宸ヤ綅2鏄惁鏈夌墿", value: MonitorCardKit.yesNo(item.loadingTwo) },
+ { label: "宸ヤ綅1璐у弶瀹氫綅", value: this.orDash(item.forkOffset) },
+ { label: "宸ヤ綅2璐у弶瀹氫綅", value: this.orDash(item.forkOffsetTwo) },
+ { label: "宸ヤ綅1浠诲姟鎺ユ敹", value: this.orDash(item.taskReceive) },
+ { label: "宸ヤ綅2浠诲姟鎺ユ敹", value: this.orDash(item.taskReceiveTwo) },
+ { label: "宸ヤ綅1涓嬪彂鏁版嵁", value: this.orDash(item.taskSend) },
+ { label: "宸ヤ綅2涓嬪彂鏁版嵁", value: this.orDash(item.taskSendTwo) },
+ { label: "鍒�", value: this.orDash(item.bay) },
+ { label: "灞�", value: this.orDash(item.lev) },
+ { label: "杞借揣鍙板畾浣�", value: this.orDash(item.liftPos) },
+ { label: "璧拌瀹氫綅", value: this.orDash(item.walkPos) },
+ { label: "璧拌閫熷害", value: this.orDash(item.xspeed) },
+ { label: "鍗囬檷閫熷害", value: this.orDash(item.yspeed) },
+ { label: "鍙夌墮閫熷害", value: this.orDash(item.zspeed) },
+ { label: "鎵╁睍鏁版嵁", value: this.orDash(item.extend) }
+ ];
+ },
+ postControl: function (url, payload) {
+ $.ajax({
+ url: baseUrl + url,
+ headers: {
+ token: localStorage.getItem("token")
+ },
+ contentType: "application/json",
+ method: "post",
+ data: JSON.stringify(payload),
+ success: function (res) {
+ if (res && res.code === 200) {
+ MonitorCardKit.showMessage(this, res.msg || "鎿嶄綔鎴愬姛", "success");
+ } else {
+ MonitorCardKit.showMessage(this, (res && res.msg) || "鎿嶄綔澶辫触", "warning");
+ }
+ }.bind(this)
+ });
+ },
+ editTaskNo: function (item, station) {
+ var currentValue = station === 1 ? item.taskNo : item.taskNoTwo;
+ this.$prompt("璇疯緭鍏ュ伐浣�" + station + "浠诲姟鍙�", "缂栬緫浠诲姟鍙�", {
confirmButtonText: "纭畾",
cancelButtonText: "鍙栨秷",
- inputValue: currentTaskNo,
+ inputValue: currentValue == null ? "" : String(currentValue),
inputPattern: /^\d+$/,
- inputErrorMessage: "浠诲姟鍙峰繀椤绘槸闈炶礋鏁存暟",
- }).then(({ value }) => {
- const taskNo = Number(value);
+ inputErrorMessage: "浠诲姟鍙峰繀椤绘槸闈炶礋鏁存暟"
+ }).then(function (result) {
$.ajax({
url: baseUrl + "/dualcrn/command/updateTaskNo",
headers: {
- token: localStorage.getItem("token"),
+ token: localStorage.getItem("token")
},
contentType: "application/json",
method: "post",
data: JSON.stringify({
crnNo: item.crnNo,
station: station,
- taskNo: taskNo,
+ taskNo: Number(result.value)
}),
- success: (res) => {
- if (res.code == 200) {
- item[fieldName] = taskNo;
- that.$message({
- message: stationName + "浠诲姟鍙锋洿鏂版垚鍔�",
- type: "success",
- });
- that.getDualCrnStateInfo();
+ success: function (res) {
+ if (res && res.code === 200) {
+ MonitorCardKit.showMessage(this, "浠诲姟鍙锋洿鏂版垚鍔�", "success");
} else {
- that.$message({
- message: res.msg,
- type: "warning",
- });
+ MonitorCardKit.showMessage(this, (res && res.msg) || "浠诲姟鍙锋洿鏂板け璐�", "warning");
}
- },
+ }.bind(this)
});
- }).catch(() => {});
+ }.bind(this)).catch(function () {});
},
- getDualCrnStateInfo() {
- if (this.$root.sendWs) {
- this.$root.sendWs(JSON.stringify({
- "url": "/dualcrn/table/crn/state",
- "data": {}
- }));
- }
+ controlCommandTransport: function () {
+ this.postControl("/dualcrn/command/take", this.controlParam);
},
- controlCommandTransport() {
- let that = this;
- $.ajax({
- url: baseUrl + "/dualcrn/command/take",
- headers: {
- token: localStorage.getItem("token"),
- },
- contentType: "application/json",
- method: "post",
- data: JSON.stringify(that.controlParam),
- success: (res) => {
- if (res.code == 200) {
- that.$message({
- message: res.msg,
- type: "success",
- });
- } else {
- that.$message({
- message: res.msg,
- type: "warning",
- });
- }
- },
- });
+ controlCommandPickup: function () {
+ this.postControl("/dualcrn/command/pick", this.controlParam);
},
- controlCommandPickup() {
- let that = this;
- $.ajax({
- url: baseUrl + "/dualcrn/command/pick",
- headers: {
- token: localStorage.getItem("token"),
- },
- contentType: "application/json",
- method: "post",
- data: JSON.stringify(that.controlParam),
- success: (res) => {
- if (res.code == 200) {
- that.$message({
- message: res.msg,
- type: "success",
- });
- } else {
- that.$message({
- message: res.msg,
- type: "warning",
- });
- }
- },
- });
+ controlCommandPutdown: function () {
+ this.postControl("/dualcrn/command/put", this.controlParam);
},
- controlCommandPutdown() {
- let that = this;
- $.ajax({
- url: baseUrl + "/dualcrn/command/put",
- headers: {
- token: localStorage.getItem("token"),
- },
- contentType: "application/json",
- method: "post",
- data: JSON.stringify(that.controlParam),
- success: (res) => {
- if (res.code == 200) {
- that.$message({
- message: res.msg,
- type: "success",
- });
- } else {
- that.$message({
- message: res.msg,
- type: "warning",
- });
- }
- },
- });
+ controlCommandMove: function () {
+ this.postControl("/dualcrn/command/move", this.controlParam);
},
- controlCommandMove() {
- let that = this;
- $.ajax({
- url: baseUrl + "/dualcrn/command/move",
- headers: {
- token: localStorage.getItem("token"),
- },
- contentType: "application/json",
- method: "post",
- data: JSON.stringify(that.controlParam),
- success: (res) => {
- if (res.code == 200) {
- that.$message({
- message: res.msg,
- type: "success",
- });
- } else {
- that.$message({
- message: res.msg,
- type: "warning",
- });
- }
- },
- });
- },
- controlCommandTaskComplete() {
- let that = this;
- $.ajax({
- url: baseUrl + "/dualcrn/command/taskComplete",
- headers: {
- token: localStorage.getItem("token"),
- },
- contentType: "application/json",
- method: "post",
- data: JSON.stringify(that.controlParam),
- success: (res) => {
- if (res.code == 200) {
- that.$message({
- message: res.msg,
- type: "success",
- });
- } else {
- that.$message({
- message: res.msg,
- type: "warning",
- });
- }
- },
- });
- },
- setDualCrnList(res) {
- let that = this;
- if (res.code == 200) {
- let list = res.data;
- if (that.searchCrnNo == "") {
- that.crnList = list;
- } else {
- let tmp = [];
- list.forEach((item) => {
- if (item.crnNo == that.searchCrnNo) {
- tmp.push(item);
- }
- });
- that.crnList = tmp;
- that.currentPage = 1;
- }
- }
- },
- },
+ controlCommandTaskComplete: function () {
+ this.postControl("/dualcrn/command/taskComplete", this.controlParam);
+ }
+ }
});
diff --git a/src/main/webapp/components/WatchRgvCard.js b/src/main/webapp/components/WatchRgvCard.js
index 5c00b7d..3c19e76 100644
--- a/src/main/webapp/components/WatchRgvCard.js
+++ b/src/main/webapp/components/WatchRgvCard.js
@@ -1,83 +1,86 @@
Vue.component("watch-rgv-card", {
template: `
- <div>
- <div style="display: flex;margin-bottom: 10px;">
- <div style="width: 100%;">RGV鐩戞帶</div>
- <div style="width: 100%;text-align: right;display: flex;">
- <el-input size="mini" v-model="searchRgvNo" placeholder="璇疯緭鍏GV鍙�"></el-input>
- <el-button @click="getRgvStateInfo" size="mini">鏌ヨ</el-button>
+ <div class="mc-root">
+ <div class="mc-toolbar">
+ <div class="mc-title">RGV鐩戞帶</div>
+ <div class="mc-search">
+ <input class="mc-input" v-model="searchRgvNo" placeholder="璇疯緭鍏GV鍙�" />
+ <button type="button" class="mc-btn mc-btn-ghost" @click="getRgvStateInfo">鏌ヨ</button>
+ </div>
+ </div>
+
+ <div v-if="!readOnly" class="mc-control-toggle">
+ <button type="button" class="mc-btn mc-btn-ghost" @click="openControl">
+ {{ showControl ? '鏀惰捣鎺у埗涓績' : '鎵撳紑鎺у埗涓績' }}
+ </button>
+ </div>
+
+ <div v-if="showControl" class="mc-control">
+ <div class="mc-control-grid">
+ <label class="mc-field">
+ <span class="mc-field-label">RGV鍙�</span>
+ <input class="mc-input" v-model="controlParam.rgvNo" placeholder="渚嬪 1" />
+ </label>
+ <label class="mc-field">
+ <span class="mc-field-label">婧愮偣</span>
+ <input class="mc-input" v-model="controlParam.sourcePos" placeholder="杈撳叆婧愮偣" />
+ </label>
+ <label class="mc-field mc-span-2">
+ <span class="mc-field-label">鐩爣鐐�</span>
+ <input class="mc-input" v-model="controlParam.targetPos" placeholder="杈撳叆鐩爣鐐�" />
+ </label>
+ <div class="mc-action-row">
+ <button type="button" class="mc-btn" @click="controlCommandTransport">鍙栨斁璐�</button>
+ <button type="button" class="mc-btn mc-btn-ghost" @click="controlCommandMove">绉诲姩</button>
+ <button type="button" class="mc-btn mc-btn-soft" @click="controlCommandTaskComplete">浠诲姟瀹屾垚</button>
+ </div>
+ </div>
+ </div>
+
+ <div class="mc-collapse">
+ <div
+ v-for="item in displayRgvList"
+ :key="item.rgvNo"
+ :class="['mc-item', { 'is-open': isActive(item.rgvNo) }]"
+ >
+ <button type="button" class="mc-head" @click="toggleItem(item)">
+ <div class="mc-head-main">
+ <div class="mc-head-title">{{ item.rgvNo }}鍙稲GV</div>
+ <div class="mc-head-subtitle">杞ㄩ亾浣� {{ orDash(item.trackSiteNo) }} | 浠诲姟 {{ orDash(item.taskNo) }}</div>
</div>
- </div>
- <div style="margin-bottom: 10px;" v-if="!readOnly">
- <div style="margin-bottom: 5px;">
- <el-button v-if="showControl" @click="openControl" size="mini">鍏抽棴鎺у埗涓績</el-button>
- <el-button v-else @click="openControl" size="mini">鎵撳紑鎺у埗涓績</el-button>
+ <div class="mc-head-right">
+ <span :class="['mc-badge', 'is-' + getStatusTone(item)]">{{ getStatusLabel(item) }}</span>
+ <span class="mc-chevron">{{ isActive(item.rgvNo) ? '鈻�' : '鈻�' }}</span>
</div>
- <div v-if="showControl" style="display: flex;justify-content: space-between;flex-wrap: wrap;">
- <div style="margin-bottom: 10px;width: 33%;"><el-input size="mini" v-model="controlParam.rgvNo" placeholder="RGV鍙�"></el-input></div>
- <div style="margin-bottom: 10px;width: 33%;"><el-input size="mini" v-model="controlParam.sourcePos" placeholder="婧愮偣"></el-input></div>
- <div style="margin-bottom: 10px;width: 33%;"><el-input size="mini" v-model="controlParam.targetPos" placeholder="鐩爣鐐�"></el-input></div>
- <div style="margin-bottom: 10px;"><el-button @click="controlCommandTransport()" size="mini">鍙栨斁璐�</el-button></div>
- <div style="margin-bottom: 10px;"><el-button @click="controlCommandMove()" size="mini">绉诲姩</el-button></div>
- <div style="margin-bottom: 10px;"><el-button @click="controlCommandTaskComplete()" size="mini">浠诲姟瀹屾垚</el-button></div>
+ </button>
+
+ <div v-if="isActive(item.rgvNo)" class="mc-body">
+ <div class="mc-detail-grid">
+ <div v-for="entry in buildDetailEntries(item)" :key="entry.label" class="mc-detail-cell">
+ <div class="mc-detail-label">{{ entry.label }}</div>
+ <div class="mc-detail-value">{{ entry.value }}</div>
+ </div>
</div>
+ </div>
</div>
- <div style="max-height: 55vh; overflow:auto;">
- <el-collapse v-model="activeNames" accordion>
- <el-collapse-item v-for="(item) in displayRgvList" :name="item.rgvNo">
- <template slot="title">
- <div style="width: 100%;display: flex;">
- <div style="width: 50%;">{{ item.rgvNo }}鍙稲GV</div>
- <div style="width: 50%;text-align: right;">
- <el-tag v-if="item.deviceStatus === 'AUTO'" type="success" size="small">鑷姩</el-tag>
- <el-tag v-else-if="item.deviceStatus === 'WORKING'" size="small">浣滀笟涓�</el-tag>
- <el-tag v-else-if="item.deviceStatus === 'ERROR'" type="danger" size="small">鎶ヨ</el-tag>
- <el-tag v-else type="warning" size="small">绂荤嚎</el-tag>
- </div>
- </div>
- </template>
- <el-descriptions border direction="vertical">
- <el-descriptions-item label="缂栧彿">{{ item.rgvNo }}</el-descriptions-item>
- <el-descriptions-item label="宸ヤ綔鍙�">{{ item.taskNo }}</el-descriptions-item>
- <el-descriptions-item label="妯″紡">{{ item.mode }}</el-descriptions-item>
- <el-descriptions-item label="鐘舵��">{{ item.status }}</el-descriptions-item>
- <el-descriptions-item label="杞ㄩ亾浣�">{{ item.trackSiteNo }}</el-descriptions-item>
- <el-descriptions-item label="鏄惁鏈夌墿">{{ item.loading }}</el-descriptions-item>
- <el-descriptions-item label="鏁呴殰浠g爜">{{ item.warnCode }}</el-descriptions-item>
- <el-descriptions-item label="鏁呴殰鎻忚堪">{{ item.alarm }}</el-descriptions-item>
- <el-descriptions-item label="鎵╁睍鏁版嵁">{{ item.extend }}</el-descriptions-item>
- </el-descriptions>
- </el-collapse-item>
- </el-collapse>
- </div>
- <div style="display:flex; justify-content:flex-end; margin-top:8px;">
- <el-pagination
- @current-change="handlePageChange"
- @size-change="handleSizeChange"
- :current-page="currentPage"
- :page-size="pageSize"
- :page-sizes="[10,20,50,100]"
- layout="total, prev, pager, next"
- :total="rgvList.length">
- </el-pagination>
- </div>
+
+ <div v-if="displayRgvList.length === 0" class="mc-empty">褰撳墠娌℃湁鍙睍绀虹殑RGV鏁版嵁</div>
+ </div>
+
+ <div class="mc-footer">
+ <button type="button" class="mc-page-btn" :disabled="currentPage <= 1" @click="handlePageChange(currentPage - 1)">涓婁竴椤�</button>
+ <span>{{ currentPage }} / {{ totalPages }}</span>
+ <button type="button" class="mc-page-btn" :disabled="currentPage >= totalPages" @click="handlePageChange(currentPage + 1)">涓嬩竴椤�</button>
+ </div>
</div>
- `,
+ `,
props: {
- param: {
- type: Object,
- default: () => ({})
- },
- autoRefresh: {
- type: Boolean,
- default: true
- },
- readOnly: {
- type: Boolean,
- default: false
- }
+ param: { type: Object, default: function () { return {}; } },
+ items: { type: Array, default: null },
+ autoRefresh: { type: Boolean, default: true },
+ readOnly: { type: Boolean, default: false }
},
- data() {
+ data: function () {
return {
rgvList: [],
activeNames: "",
@@ -88,155 +91,158 @@
sourcePos: "",
targetPos: ""
},
- pageSize: 25,
+ pageSize: 12,
currentPage: 1,
timer: null
};
},
- created() {
- if (this.autoRefresh) {
- this.timer = setInterval(() => {
- this.getRgvStateInfo();
- }, 1000);
- }
- },
- beforeDestroy() {
- if (this.timer) {
- clearInterval(this.timer);
- }
- },
computed: {
- displayRgvList() {
- const start = (this.currentPage - 1) * this.pageSize;
- const end = start + this.pageSize;
- return this.rgvList.slice(start, end);
+ sourceList: function () {
+ return Array.isArray(this.items) ? this.items : this.rgvList;
+ },
+ filteredRgvList: function () {
+ var keyword = String(this.searchRgvNo || "").trim();
+ if (!keyword) {
+ return this.sourceList;
+ }
+ return this.sourceList.filter(function (item) {
+ return String(item.rgvNo) === keyword;
+ });
+ },
+ displayRgvList: function () {
+ var start = (this.currentPage - 1) * this.pageSize;
+ return this.filteredRgvList.slice(start, start + this.pageSize);
+ },
+ totalPages: function () {
+ return Math.max(1, Math.ceil(this.filteredRgvList.length / this.pageSize) || 1);
}
},
watch: {
- param: {
- handler(newVal) {
- if (newVal && newVal.rgvNo && newVal.rgvNo != 0) {
- this.activeNames = newVal.rgvNo;
- this.searchRgvNo = newVal.rgvNo;
- const idx = this.rgvList.findIndex(i => i.rgvNo == newVal.rgvNo);
- if (idx >= 0) { this.currentPage = Math.floor(idx / this.pageSize) + 1; }
- }
- },
- deep: true,
- immediate: true
+ items: function () {
+ this.afterDataRefresh();
},
+ param: {
+ deep: true,
+ immediate: true,
+ handler: function (newVal) {
+ if (newVal && newVal.rgvNo && newVal.rgvNo !== 0) {
+ this.focusRgv(newVal.rgvNo);
+ }
+ }
+ }
+ },
+ created: function () {
+ MonitorCardKit.ensureStyles();
+ if (this.autoRefresh) {
+ this.timer = setInterval(this.getRgvStateInfo, 1000);
+ }
+ },
+ beforeDestroy: function () {
+ if (this.timer) {
+ clearInterval(this.timer);
+ this.timer = null;
+ }
},
methods: {
- handlePageChange(page) {
+ orDash: function (value) {
+ return MonitorCardKit.orDash(value);
+ },
+ getStatusLabel: function (item) {
+ return MonitorCardKit.deviceStatusLabel(item && item.deviceStatus);
+ },
+ getStatusTone: function (item) {
+ return MonitorCardKit.statusTone(this.getStatusLabel(item));
+ },
+ isActive: function (rgvNo) {
+ return String(this.activeNames) === String(rgvNo);
+ },
+ toggleItem: function (item) {
+ var next = String(item.rgvNo);
+ this.activeNames = this.activeNames === next ? "" : next;
+ },
+ focusRgv: function (rgvNo) {
+ this.searchRgvNo = String(rgvNo);
+ var index = this.filteredRgvList.findIndex(function (item) {
+ return String(item.rgvNo) === String(rgvNo);
+ });
+ this.currentPage = index >= 0 ? Math.floor(index / this.pageSize) + 1 : 1;
+ this.activeNames = String(rgvNo);
+ },
+ afterDataRefresh: function () {
+ if (this.currentPage > this.totalPages) {
+ this.currentPage = this.totalPages;
+ }
+ if (this.activeNames) {
+ var exists = this.filteredRgvList.some(function (item) {
+ return String(item.rgvNo) === String(this.activeNames);
+ }, this);
+ if (!exists) {
+ this.activeNames = "";
+ }
+ }
+ },
+ handlePageChange: function (page) {
+ if (page < 1 || page > this.totalPages) {
+ return;
+ }
this.currentPage = page;
},
- handleSizeChange(size) {
- this.pageSize = size;
- this.currentPage = 1;
- },
- getRgvStateInfo() {
- if (this.$root.sendWs) {
+ getRgvStateInfo: function () {
+ if (this.$root && this.$root.sendWs) {
this.$root.sendWs(JSON.stringify({
- "url": "/rgv/table/rgv/state",
- "data": {}
+ url: "/rgv/table/rgv/state",
+ data: {}
}));
}
},
- setRgvList(res) {
- let that = this;
- if (res.code == 200) {
- let list = res.data || [];
- if (that.searchRgvNo == "") {
- that.rgvList = list;
- } else {
- let tmp = [];
- list.forEach((item) => {
- if (item.rgvNo == that.searchRgvNo) {
- tmp.push(item);
- }
- });
- that.rgvList = tmp;
- that.currentPage = 1;
- }
+ setRgvList: function (res) {
+ if (res && res.code === 200) {
+ this.rgvList = res.data || [];
+ this.afterDataRefresh();
}
},
- openControl() {
+ openControl: function () {
this.showControl = !this.showControl;
},
- controlCommandTransport() {
- let that = this;
+ buildDetailEntries: function (item) {
+ return [
+ { label: "缂栧彿", value: this.orDash(item.rgvNo) },
+ { label: "宸ヤ綔鍙�", value: this.orDash(item.taskNo) },
+ { label: "妯″紡", value: this.orDash(item.mode) },
+ { label: "鐘舵��", value: this.orDash(item.status) },
+ { label: "杞ㄩ亾浣�", value: this.orDash(item.trackSiteNo) },
+ { label: "鏄惁鏈夌墿", value: MonitorCardKit.yesNo(item.loading) },
+ { label: "鏁呴殰浠g爜", value: this.orDash(item.warnCode) },
+ { label: "鏁呴殰鎻忚堪", value: this.orDash(item.alarm) },
+ { label: "鎵╁睍鏁版嵁", value: this.orDash(item.extend) }
+ ];
+ },
+ postControl: function (url, payload) {
$.ajax({
- url: baseUrl + "/rgv/command/transport",
+ url: baseUrl + url,
headers: {
- token: localStorage.getItem("token"),
+ token: localStorage.getItem("token")
},
contentType: "application/json",
method: "post",
- data: JSON.stringify(that.controlParam),
- success: (res) => {
- if (res.code == 200) {
- that.$message({
- message: res.msg,
- type: "success",
- });
+ data: JSON.stringify(payload),
+ success: function (res) {
+ if (res && res.code === 200) {
+ MonitorCardKit.showMessage(this, res.msg || "鎿嶄綔鎴愬姛", "success");
} else {
- that.$message({
- message: res.msg,
- type: "warning",
- });
+ MonitorCardKit.showMessage(this, (res && res.msg) || "鎿嶄綔澶辫触", "warning");
}
- },
+ }.bind(this)
});
},
- controlCommandMove() {
- let that = this;
- $.ajax({
- url: baseUrl + "/rgv/command/move",
- headers: {
- token: localStorage.getItem("token"),
- },
- contentType: "application/json",
- method: "post",
- data: JSON.stringify(that.controlParam),
- success: (res) => {
- if (res.code == 200) {
- that.$message({
- message: res.msg,
- type: "success",
- });
- } else {
- that.$message({
- message: res.msg,
- type: "warning",
- });
- }
- },
- });
+ controlCommandTransport: function () {
+ this.postControl("/rgv/command/transport", this.controlParam);
},
- controlCommandTaskComplete() {
- let that = this;
- $.ajax({
- url: baseUrl + "/rgv/command/taskComplete",
- headers: {
- token: localStorage.getItem("token"),
- },
- contentType: "application/json",
- method: "post",
- data: JSON.stringify(that.controlParam),
- success: (res) => {
- if (res.code == 200) {
- that.$message({
- message: res.msg,
- type: "success",
- });
- } else {
- that.$message({
- message: res.msg,
- type: "warning",
- });
- }
- },
- });
+ controlCommandMove: function () {
+ this.postControl("/rgv/command/move", this.controlParam);
},
- },
+ controlCommandTaskComplete: function () {
+ this.postControl("/rgv/command/taskComplete", this.controlParam);
+ }
+ }
});
diff --git a/src/main/webapp/static/js/watch/stationColorConfig.js b/src/main/webapp/static/js/watch/stationColorConfig.js
new file mode 100644
index 0000000..d2450ea
--- /dev/null
+++ b/src/main/webapp/static/js/watch/stationColorConfig.js
@@ -0,0 +1,127 @@
+var app = new Vue({
+ el: '#app',
+ data: {
+ loading: false,
+ saving: false,
+ items: [],
+ predefineColors: [
+ '#78FF81',
+ '#FA51F6',
+ '#C4C400',
+ '#30BFFC',
+ '#18C7B8',
+ '#97B400',
+ '#E69138',
+ '#B8B8B8',
+ '#FF6B6B',
+ '#FFD166',
+ '#06D6A0',
+ '#118AB2'
+ ]
+ },
+ mounted: function () {
+ this.reloadData();
+ },
+ methods: {
+ reloadData: function () {
+ var that = this;
+ this.loading = true;
+ $.ajax({
+ url: baseUrl + '/watch/stationColor/config/auth',
+ headers: { token: localStorage.getItem('token') },
+ method: 'GET',
+ success: function (res) {
+ that.loading = false;
+ if (res.code === 200) {
+ var items = (res.data && res.data.items) ? res.data.items : [];
+ that.items = items.map(function (item) {
+ return {
+ status: item.status,
+ name: item.name,
+ desc: item.desc,
+ color: that.normalizeColor(item.color || item.defaultColor),
+ defaultColor: that.normalizeColor(item.defaultColor)
+ };
+ });
+ } else if (res.code === 403) {
+ top.location.href = baseUrl + '/';
+ } else {
+ that.$message.error(res.msg || '鍔犺浇绔欑偣棰滆壊閰嶇疆澶辫触');
+ }
+ },
+ error: function () {
+ that.loading = false;
+ that.$message.error('鍔犺浇绔欑偣棰滆壊閰嶇疆澶辫触');
+ }
+ });
+ },
+ resetDefaults: function () {
+ this.items = this.items.map(function (item) {
+ return Object.assign({}, item, {
+ color: item.defaultColor
+ });
+ });
+ this.$message.success('宸叉仮澶嶉粯璁ら鑹�');
+ },
+ applyDefaultColor: function (item) {
+ item.color = item.defaultColor;
+ },
+ handleColorInput: function (item) {
+ var normalized = this.normalizeColor(item.color);
+ if (normalized !== String(item.color || '').trim().toUpperCase()) {
+ this.$message.warning('棰滆壊鏍煎紡宸茶嚜鍔ㄤ慨姝d负鍗佸叚杩涘埗');
+ }
+ item.color = normalized;
+ },
+ saveConfig: function () {
+ var that = this;
+ this.saving = true;
+ $.ajax({
+ url: baseUrl + '/watch/stationColor/config/save/auth',
+ headers: { token: localStorage.getItem('token') },
+ method: 'POST',
+ contentType: 'application/json;charset=UTF-8',
+ dataType: 'json',
+ data: JSON.stringify({
+ items: this.items.map(function (item) {
+ return {
+ status: item.status,
+ color: that.normalizeColor(item.color)
+ };
+ })
+ }),
+ success: function (res) {
+ that.saving = false;
+ if (res.code === 200) {
+ that.$message.success('绔欑偣棰滆壊閰嶇疆宸蹭繚瀛�');
+ that.reloadData();
+ } else if (res.code === 403) {
+ top.location.href = baseUrl + '/';
+ } else {
+ that.$message.error(res.msg || '淇濆瓨绔欑偣棰滆壊閰嶇疆澶辫触');
+ }
+ },
+ error: function () {
+ that.saving = false;
+ that.$message.error('淇濆瓨绔欑偣棰滆壊閰嶇疆澶辫触');
+ }
+ });
+ },
+ normalizeColor: function (color) {
+ var value = String(color || '').trim();
+ if (!value) {
+ return '#B8B8B8';
+ }
+ if (/^#[0-9a-fA-F]{6}$/.test(value)) {
+ return value.toUpperCase();
+ }
+ if (/^#[0-9a-fA-F]{3}$/.test(value)) {
+ return ('#' + value.charAt(1) + value.charAt(1) + value.charAt(2) + value.charAt(2) + value.charAt(3) + value.charAt(3)).toUpperCase();
+ }
+ if (/^0x[0-9a-fA-F]{6}$/.test(value)) {
+ return ('#' + value.substring(2)).toUpperCase();
+ }
+ return '#B8B8B8';
+ }
+ }
+});
diff --git a/src/main/webapp/views/config/config.html b/src/main/webapp/views/config/config.html
index 3479aff..a0d92fb 100644
--- a/src/main/webapp/views/config/config.html
+++ b/src/main/webapp/views/config/config.html
@@ -67,4 +67,4 @@
</body>
-</html>
\ No newline at end of file
+</html>
diff --git a/src/main/webapp/views/deviceLogs/deviceLogs.html b/src/main/webapp/views/deviceLogs/deviceLogs.html
index 56f8d78..ed5c392 100644
--- a/src/main/webapp/views/deviceLogs/deviceLogs.html
+++ b/src/main/webapp/views/deviceLogs/deviceLogs.html
@@ -206,10 +206,11 @@
<script type="text/javascript" src="../../static/js/common.js" charset="utf-8"></script>
<script src="../../static/vue/js/vue.min.js"></script>
<script src="../../static/vue/element/element.js"></script>
+<script src="../../components/MonitorCardKit.js"></script>
<script src="../../components/WatchCrnCard.js"></script>
<script src="../../components/WatchRgvCard.js"></script>
<script src="../../components/WatchDualCrnCard.js"></script>
<script src="../../components/DevpCard.js"></script>
<script type="text/javascript" src="../../static/js/deviceLogs/deviceLogs.js" charset="utf-8"></script>
</body>
-</html>
\ No newline at end of file
+</html>
diff --git a/src/main/webapp/views/watch/console.html b/src/main/webapp/views/watch/console.html
index 3dbcc3c..a1229ae 100644
--- a/src/main/webapp/views/watch/console.html
+++ b/src/main/webapp/views/watch/console.html
@@ -4,8 +4,8 @@
<meta charset="UTF-8">
<title>WCS鎺у埗涓績</title>
<link rel="stylesheet" href="../../static/css/animate.min.css">
- <link rel="stylesheet" href="../../static/vue/element/element.css">
<link rel="stylesheet" href="../../static/css/watch/console_vue.css">
+ <link rel="stylesheet" href="../../static/vue/element/element.css">
<style>
html, body, #app {
width: 100%;
@@ -13,11 +13,503 @@
margin: 0;
overflow: hidden;
}
+ body {
+ background: linear-gradient(180deg, #eef4f8 0%, #e7edf4 100%);
+ }
+ #app {
+ position: relative;
+ }
+ .monitor-shell {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ }
+ .monitor-map {
+ width: 100%;
+ height: 100%;
+ }
+ .monitor-panel-wrap {
+ position: absolute;
+ top: 18px;
+ left: 18px;
+ bottom: 18px;
+ z-index: 40;
+ pointer-events: none;
+ }
+ .monitor-panel {
+ width: min(max(360px, 30vw), calc(100vw - 92px));
+ max-width: calc(100vw - 92px);
+ height: calc(100vh - 36px);
+ display: flex;
+ flex-direction: column;
+ border-radius: 20px;
+ border: 1px solid rgba(255, 255, 255, 0.42);
+ background: rgba(248, 251, 253, 0.94);
+ box-shadow: 0 10px 24px rgba(88, 110, 136, 0.08);
+ overflow: hidden;
+ pointer-events: auto;
+ transform-origin: left center;
+ will-change: transform, opacity;
+ backface-visibility: hidden;
+ contain: layout paint style;
+ transition: transform .26s cubic-bezier(0.22, 1, 0.36, 1), opacity .2s ease, box-shadow .2s ease, border-color .2s ease;
+ }
+ .monitor-panel.is-collapsed {
+ opacity: 0;
+ transform: translate3d(calc(-100% - 14px), 0, 0);
+ border-color: transparent;
+ box-shadow: 0 6px 16px rgba(88, 110, 136, 0.02);
+ pointer-events: none;
+ }
+ .monitor-panel-header {
+ padding: 14px 16px 10px;
+ border-bottom: 1px solid rgba(226, 232, 240, 0.72);
+ background: rgba(255, 255, 255, 0.24);
+ }
+ .monitor-panel-title {
+ font-size: 15px;
+ font-weight: 600;
+ color: #243447;
+ line-height: 1.2;
+ }
+ .monitor-panel-desc {
+ margin-top: 4px;
+ font-size: 12px;
+ color: #6b7b8d;
+ }
+ .monitor-panel-body {
+ flex: 1;
+ min-height: 0;
+ padding: 12px;
+ overflow: hidden;
+ }
+ .monitor-panel-body {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ }
+ .monitor-card-host {
+ flex: 1;
+ min-height: 0;
+ display: flex;
+ align-items: stretch;
+ width: 100%;
+ }
+ .wb-root {
+ position: relative;
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ gap: 10px;
+ color: #395066;
+ }
+ .wb-main {
+ flex: 1;
+ min-height: 0;
+ display: flex;
+ gap: 10px;
+ }
+ .wb-side {
+ flex: 0 0 38%;
+ min-width: 220px;
+ max-width: 44%;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ min-height: 0;
+ }
+ .wb-side-title {
+ font-size: 11px;
+ font-weight: 700;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: #7d8fa2;
+ margin-bottom: 8px;
+ }
+ .wb-list-card,
+ .wb-detail-panel {
+ min-height: 0;
+ display: flex;
+ flex-direction: column;
+ }
+ .wb-detail-panel {
+ flex: 1 1 62%;
+ min-width: 0;
+ }
+ .wb-tabs {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 6px;
+ padding: 6px;
+ border-radius: 14px;
+ background: rgba(242, 246, 250, 0.78);
+ border: 1px solid rgba(224, 232, 239, 0.9);
+ }
+ .wb-tab {
+ height: 34px;
+ border: none;
+ border-radius: 10px;
+ background: transparent;
+ color: #73869b;
+ font-size: 12px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all .16s ease;
+ }
+ .wb-tab.is-active {
+ background: rgba(255, 255, 255, 0.92);
+ color: #24405c;
+ box-shadow: 0 8px 16px rgba(148, 163, 184, 0.08);
+ }
+ .wb-toolbar {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+ .wb-toolbar-actions {
+ display: flex;
+ gap: 8px;
+ flex-shrink: 0;
+ }
+ .wb-input,
+ .wb-select {
+ width: 100%;
+ height: 36px;
+ padding: 0 12px;
+ border-radius: 10px;
+ border: 1px solid rgba(224, 232, 239, 0.96);
+ background: rgba(255, 255, 255, 0.76);
+ color: #334155;
+ box-sizing: border-box;
+ outline: none;
+ transition: border-color .16s ease, box-shadow .16s ease, background .16s ease;
+ }
+ .wb-input:focus,
+ .wb-select:focus {
+ border-color: rgba(128, 168, 208, 0.66);
+ box-shadow: 0 0 0 3px rgba(128, 168, 208, 0.1);
+ background: rgba(255, 255, 255, 0.92);
+ }
+ .wb-btn {
+ height: 36px;
+ padding: 0 14px;
+ border: none;
+ border-radius: 10px;
+ background: #6f95bd;
+ color: #fff;
+ font-size: 12px;
+ font-weight: 600;
+ cursor: pointer;
+ box-shadow: 0 6px 14px rgba(111, 149, 189, 0.18);
+ transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease, background .16s ease;
+ }
+ .wb-btn:hover {
+ transform: translateY(-1px);
+ }
+ .wb-btn-primary {
+ background: linear-gradient(135deg, #5e89b4 0%, #6f95bd 100%);
+ box-shadow: 0 10px 20px rgba(111, 149, 189, 0.22);
+ }
+ .wb-btn.wb-btn-ghost {
+ background: rgba(255, 255, 255, 0.76);
+ color: #4c6177;
+ border: 1px solid rgba(224, 232, 239, 0.96);
+ box-shadow: none;
+ }
+ .wb-btn.wb-btn-soft {
+ background: rgba(230, 237, 244, 0.92);
+ color: #4c6177;
+ border: 1px solid rgba(210, 221, 232, 0.98);
+ box-shadow: none;
+ }
+ .wb-control-card,
+ .wb-list-card,
+ .wb-detail {
+ border-radius: 16px;
+ border: 1px solid rgba(224, 232, 239, 0.92);
+ background: rgba(255, 255, 255, 0.62);
+ box-shadow: 0 8px 18px rgba(148, 163, 184, 0.06);
+ }
+ .wb-list-card {
+ flex: 1;
+ padding: 10px 8px 8px;
+ }
+ .wb-control-card {
+ padding: 14px;
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.82) 0%, rgba(246, 250, 253, 0.78) 100%);
+ overflow: auto;
+ }
+ .wb-control-subtitle {
+ margin-top: 8px;
+ margin-bottom: 10px;
+ font-size: 11px;
+ line-height: 1.45;
+ color: #6f8194;
+ }
+ .wb-control-target {
+ padding: 7px 9px;
+ border-radius: 12px;
+ background: rgba(234, 241, 247, 0.96);
+ border: 1px solid rgba(214, 224, 234, 0.96);
+ color: #43607c;
+ font-size: 11px;
+ font-weight: 700;
+ line-height: 1.4;
+ }
+ .wb-form-grid {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 8px;
+ }
+ .wb-field {
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+ }
+ .wb-field-label {
+ font-size: 11px;
+ font-weight: 700;
+ letter-spacing: 0.02em;
+ color: #6d8197;
+ }
+ .wb-action-row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-top: 2px;
+ padding-top: 8px;
+ border-top: 1px dashed rgba(216, 226, 235, 0.92);
+ }
+ .wb-section-title {
+ font-size: 13px;
+ font-weight: 700;
+ color: #28425d;
+ margin-bottom: 10px;
+ }
+ .wb-list {
+ flex: 1;
+ min-height: 0;
+ padding: 6px;
+ overflow: auto;
+ }
+ .wb-list-item {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: flex-start;
+ gap: 7px;
+ padding: 10px;
+ margin-bottom: 8px;
+ border: 1px solid transparent;
+ border-radius: 12px;
+ background: rgba(248, 250, 252, 0.72);
+ cursor: pointer;
+ text-align: left;
+ color: inherit;
+ }
+ .wb-list-item:last-child {
+ margin-bottom: 0;
+ }
+ .wb-list-item.is-active {
+ border-color: rgba(135, 166, 198, 0.38);
+ background: rgba(236, 243, 249, 0.94);
+ }
+ .wb-list-main {
+ min-width: 0;
+ }
+ .wb-list-title {
+ font-size: 12px;
+ font-weight: 700;
+ color: #27425c;
+ line-height: 1.35;
+ }
+ .wb-list-meta {
+ margin-top: 4px;
+ font-size: 11px;
+ color: #7b8b9c;
+ line-height: 1.4;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ }
+ .wb-badge {
+ flex-shrink: 0;
+ padding: 3px 8px;
+ border-radius: 999px;
+ font-size: 10px;
+ font-weight: 700;
+ }
+ .wb-badge.is-success {
+ background: rgba(82, 177, 126, 0.12);
+ color: #2d7650;
+ }
+ .wb-badge.is-working {
+ background: rgba(111, 149, 189, 0.12);
+ color: #3f6286;
+ }
+ .wb-badge.is-warning {
+ background: rgba(214, 162, 94, 0.14);
+ color: #9b6a24;
+ }
+ .wb-badge.is-danger {
+ background: rgba(207, 126, 120, 0.14);
+ color: #a14e4a;
+ }
+ .wb-badge.is-muted {
+ background: rgba(148, 163, 184, 0.14);
+ color: #748397;
+ }
+ .wb-empty {
+ padding: 28px 12px;
+ text-align: center;
+ color: #8b9aad;
+ font-size: 12px;
+ }
+ .wb-detail {
+ flex: 1;
+ min-height: 0;
+ padding: 12px;
+ overflow: auto;
+ }
+ .wb-detail-empty {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+ .wb-detail-header {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 10px;
+ margin-bottom: 12px;
+ }
+ .wb-detail-subtitle {
+ margin-top: 4px;
+ font-size: 12px;
+ color: #7c8c9d;
+ }
+ .wb-detail-actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ }
+ .wb-link {
+ padding: 0;
+ border: none;
+ background: transparent;
+ color: #4677a4;
+ font-size: 12px;
+ cursor: pointer;
+ }
+ .wb-detail-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
+ gap: 8px;
+ }
+ .wb-detail-cell {
+ padding: 10px 12px;
+ border-radius: 12px;
+ background: rgba(247, 250, 252, 0.86);
+ border: 1px solid rgba(233, 239, 244, 0.96);
+ }
+ .wb-detail-label {
+ font-size: 11px;
+ color: #8090a2;
+ }
+ .wb-detail-value {
+ margin-top: 5px;
+ font-size: 13px;
+ color: #31485f;
+ word-break: break-all;
+ }
+ .wb-notice {
+ position: absolute;
+ right: 18px;
+ bottom: 18px;
+ padding: 10px 14px;
+ border-radius: 12px;
+ font-size: 12px;
+ font-weight: 600;
+ color: #fff;
+ box-shadow: 0 12px 24px rgba(15, 23, 42, 0.12);
+ }
+ .wb-notice.is-success {
+ background: rgba(82, 177, 126, 0.92);
+ }
+ .wb-notice.is-warning {
+ background: rgba(214, 162, 94, 0.92);
+ }
+ .wb-notice.is-danger {
+ background: rgba(207, 126, 120, 0.92);
+ }
+ .floor-switch-button {
+ min-width: 44px;
+ height: 30px;
+ padding: 0 12px;
+ border: 1px solid rgba(185, 197, 210, 0.84);
+ border-radius: 999px;
+ background: rgba(255, 255, 255, 0.82);
+ color: #4b6177;
+ font-size: 12px;
+ font-weight: 700;
+ cursor: pointer;
+ }
+ .floor-switch-button.is-active {
+ border-color: rgba(111, 149, 189, 0.4);
+ background: rgba(236, 243, 249, 0.94);
+ color: #27425c;
+ }
+ .monitor-panel-toggle {
+ position: absolute;
+ left: 0;
+ top: 50%;
+ margin-left: 0;
+ transform: translateY(-50%);
+ width: 30px;
+ min-height: 108px;
+ padding: 10px 4px;
+ border: 1px solid rgba(148, 163, 184, 0.22);
+ border-left: none;
+ border-radius: 0 14px 14px 0;
+ background: rgba(255, 255, 255, 0.96);
+ color: #3e5974;
+ box-shadow: 0 8px 18px rgba(15, 23, 42, 0.08);
+ cursor: pointer;
+ pointer-events: auto;
+ display: inline-flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ font-size: 12px;
+ line-height: 1;
+ white-space: nowrap;
+ backface-visibility: hidden;
+ transition: left .26s cubic-bezier(0.22, 1, 0.36, 1), transform .26s cubic-bezier(0.22, 1, 0.36, 1), box-shadow .18s ease, background .18s ease;
+ }
+ .monitor-panel-toggle.is-panel-open {
+ left: calc(100% + 10px);
+ }
+ .monitor-panel-toggle i {
+ font-size: 14px;
+ }
+ .monitor-panel-toggle span {
+ writing-mode: vertical-rl;
+ text-orientation: mixed;
+ letter-spacing: 0.08em;
+ user-select: none;
+ }
</style>
<script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="../../static/layui/layui.js"></script>
<script type="text/javascript" src="../../static/js/handlebars/handlebars-v4.5.3.js"></script>
- <script type="text/javascript" src="../../static/js/common.js"></script>
+ <script type="text/javascript" src="../../static/js/common.js"></script>
<script type="text/javascript" src="../../static/vue/js/vue.min.js"></script>
<script type="text/javascript" src="../../static/vue/element/element.js"></script>
<script src="../../static/js/gsap.min.js"></script>
@@ -25,42 +517,43 @@
</head>
<body>
<div id="app">
- <div style="display: flex;margin-left: 20px;">
- <div style="width: 20%;height: 60vh;margin-right: 20px;margin-top: 30px;">
- <el-tabs type="border-card" v-model="activateCard" @tab-click="handleCardClick">
- <el-tab-pane label="鍫嗗灈鏈�" name="crn">
- <watch-crn-card ref="watchCrnCard" :param="crnParam"></watch-crn-card>
- </el-tab-pane>
- <el-tab-pane label="鍙屽伐浣嶅爢鍨涙満" name="dualCrn">
- <watch-dual-crn-card ref="watchDualCrnCard" :param="dualCrnParam"></watch-dual-crn-card>
- </el-tab-pane>
- <el-tab-pane label="杈撻�佺珯" name="devp">
- <devp-card ref="devpCard" :param="devpParam"></devp-card>
- </el-tab-pane>
- <el-tab-pane label="RGV" name="rgv">
- <watch-rgv-card ref="watchRgvCard" :param="rgvParam"></watch-rgv-card>
- </el-tab-pane>
- <!-- <el-tab-pane label="鍦板浘閰嶇疆" name="mapSetting">
- <map-setting-card :param="mapSettingParam"></map-setting-card>
- </el-tab-pane> -->
- </el-tabs>
- </div>
+ <div class="monitor-shell" ref="monitorShell">
+ <map-canvas :lev="currentLev" :lev-list="levList" :crn-param="crnParam" :rgv-param="rgvParam" :devp-param="devpParam" :station-task-range="stationTaskRange" :viewport-padding="mapViewportPadding" :hud-padding="mapHudPadding" @switch-lev="switchLev" @crn-click="openCrn" @dual-crn-click="openDualCrn" @station-click="openSite" @rgv-click="openRgv" class="monitor-map"></map-canvas>
- <map-canvas :lev="currentLev" :crn-param="crnParam" :rgv-param="rgvParam" :devp-param="devpParam" @crn-click="openCrn" @dual-crn-click="openDualCrn" @station-click="openSite" @rgv-click="openRgv" style="width: 80%; height: 100vh;"></map-canvas>
-
- <div style="position: absolute;top: 15px;left: 50%;display: flex;">
- <div v-if="levList.length > 1" v-for="(lev,index) in levList" :key="index" style="margin-right: 10px;">
- <el-button :type="currentLev == lev ? 'primary' : ''" @click="switchLev(lev)" size="mini">{{ lev }}F</el-button>
+ <div class="monitor-panel-wrap" ref="monitorPanelWrap">
+ <div class="monitor-panel" ref="monitorPanel" :class="{ 'is-collapsed': panelCollapsed }">
+ <div class="monitor-panel-header">
+ <div class="monitor-panel-title">鐩戞帶宸ヤ綔鍙�</div>
+ <div class="monitor-panel-desc">鍥寸粫鍦板浘鍋氭搷浣滐紝璁惧鐐瑰嚮鍚庤嚜鍔ㄥ垏鎹㈠埌瀵瑰簲闈㈡澘</div>
+ </div>
+ <div class="monitor-panel-body">
+ <div class="wb-tabs" role="tablist">
+ <button type="button" :class="['wb-tab', { 'is-active': activateCard === 'crn' }]" @click="handleWorkbenchTabChange('crn')">鍫嗗灈鏈�</button>
+ <button type="button" :class="['wb-tab', { 'is-active': activateCard === 'dualCrn' }]" @click="handleWorkbenchTabChange('dualCrn')">鍙屽伐浣�</button>
+ <button type="button" :class="['wb-tab', { 'is-active': activateCard === 'devp' }]" @click="handleWorkbenchTabChange('devp')">杈撻�佺珯</button>
+ <button type="button" :class="['wb-tab', { 'is-active': activateCard === 'rgv' }]" @click="handleWorkbenchTabChange('rgv')">RGV</button>
+ </div>
+ <div class="monitor-card-host">
+ <watch-crn-card v-if="activateCard === 'crn'" ref="watchCrnCard" :param="crnParam" :items="crnStateList" :auto-refresh="false"></watch-crn-card>
+ <watch-dual-crn-card v-else-if="activateCard === 'dualCrn'" ref="watchDualCrnCard" :param="dualCrnParam" :items="dualCrnStateList" :auto-refresh="false"></watch-dual-crn-card>
+ <devp-card v-else-if="activateCard === 'devp'" ref="devpCard" :param="devpParam" :items="stationStateList" :auto-refresh="false"></devp-card>
+ <watch-rgv-card v-else ref="watchRgvCard" :param="rgvParam" :items="rgvStateList" :auto-refresh="false"></watch-rgv-card>
+ </div>
+ </div>
</div>
+ <button class="monitor-panel-toggle" :class="{ 'is-panel-open': !panelCollapsed }" ref="monitorToggle" @click="toggleMonitorPanel">
+ <i>{{ panelCollapsed ? '>' : '<' }}</i>
+ <span>{{ panelCollapsed ? '灞曞紑闈㈡澘' : '鏀惰捣闈㈡澘' }}</span>
+ </button>
</div>
</div>
</div>
+ <script src="../../components/MonitorCardKit.js"></script>
<script src="../../components/WatchCrnCard.js"></script>
<script src="../../components/WatchDualCrnCard.js"></script>
<script src="../../components/DevpCard.js"></script>
- <script src="../../components/MapSettingCard.js"></script>
<script src="../../components/WatchRgvCard.js"></script>
<script src="../../components/MapCanvas.js"></script>
<script>
@@ -74,6 +567,22 @@
systemStatus: true,//绯荤粺杩愯鐘舵��
consoleInterval: null,//瀹氭椂鍣ㄥ瓨鍌ㄥ彉閲�
rgvPosition: [],
+ panelCollapsed: false,
+ mapViewportPadding: {
+ top: 0,
+ right: 0,
+ bottom: 0,
+ left: 0
+ },
+ mapHudPadding: {
+ left: 14
+ },
+ stationTaskRange: {
+ inbound: null,
+ outbound: null
+ },
+ panelTransitionTimer: null,
+ panelPollTimer: null,
activateCard: 'crn',
crnParam: {
crnNo: 0
@@ -81,15 +590,16 @@
dualCrnParam: {
crnNo: 0
},
- mapSettingParam: {
- zoom: 70
- },
devpParam: {
stationId: 0
},
rgvParam: {
rgvNo: 0
},
+ crnStateList: [],
+ dualCrnStateList: [],
+ stationStateList: [],
+ rgvStateList: [],
locMastData: [],//搴撲綅鏁版嵁
wsReconnectTimer: null,
wsReconnectAttempts: 0,
@@ -100,10 +610,20 @@
this.init()
},
mounted() {
+ this.$nextTick(() => {
+ this.updateMapViewportPadding();
+ });
+ window.addEventListener('resize', this.updateMapViewportPadding);
+ this.panelPollTimer = setInterval(() => {
+ this.refreshWorkbench(this.activateCard);
+ }, 1000);
},
beforeDestroy() {
if (this.wsReconnectTimer) { clearTimeout(this.wsReconnectTimer); this.wsReconnectTimer = null; }
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) { try { ws.close(); } catch (e) {} }
+ window.removeEventListener('resize', this.updateMapViewportPadding);
+ if (this.panelTransitionTimer) { clearTimeout(this.panelTransitionTimer); this.panelTransitionTimer = null; }
+ if (this.panelPollTimer) { clearInterval(this.panelPollTimer); this.panelPollTimer = null; }
},
watch: {
@@ -114,12 +634,13 @@
ws.send(data);
}
},
- webSocketOnOpen() {
- console.log("WebSocket杩炴帴鎴愬姛");
- if (this.wsReconnectTimer) { clearTimeout(this.wsReconnectTimer); this.wsReconnectTimer = null; }
- this.wsReconnectAttempts = 0;
- this.getMap();
- },
+ webSocketOnOpen() {
+ console.log("WebSocket杩炴帴鎴愬姛");
+ if (this.wsReconnectTimer) { clearTimeout(this.wsReconnectTimer); this.wsReconnectTimer = null; }
+ this.wsReconnectAttempts = 0;
+ this.getMap();
+ this.refreshWorkbench(this.activateCard);
+ },
webSocketOnError() {
console.log("WebSocket杩炴帴鍙戠敓閿欒");
this.scheduleWsReconnect();
@@ -128,27 +649,23 @@
console.log("WebSocket杩炴帴鍏抽棴");
this.scheduleWsReconnect();
},
- webSocketOnMessage(e) {
- const result = JSON.parse(e.data);
- if (result.url == "/crn/table/crn/state") {
- if(this.$refs.watchCrnCard) {
- this.$refs.watchCrnCard.setCrnList(JSON.parse(result.data));
- }
- } else if (result.url == "/dualcrn/table/crn/state") {
- if(this.$refs.watchDualCrnCard) {
- this.$refs.watchDualCrnCard.setDualCrnList(JSON.parse(result.data));
- }
- } else if (result.url == "/console/latest/data/station") {
- if(this.$refs.devpCard) {
- this.$refs.devpCard.setStationList(JSON.parse(result.data));
- }
- } else if (result.url == "/rgv/table/rgv/state") {
- if(this.$refs.watchRgvCard) {
- this.$refs.watchRgvCard.setRgvList(JSON.parse(result.data));
- }
- } else if (result.url == "/basMap/lev/" + this.currentLev + "/auth") {
- // 鍦板浘鏁版嵁
- let res = JSON.parse(result.data);
+ webSocketOnMessage(e) {
+ const result = JSON.parse(e.data);
+ if (result.url == "/crn/table/crn/state") {
+ const res = JSON.parse(result.data);
+ this.crnStateList = res && res.code === 200 ? (res.data || []) : [];
+ } else if (result.url == "/dualcrn/table/crn/state") {
+ const res = JSON.parse(result.data);
+ this.dualCrnStateList = res && res.code === 200 ? (res.data || []) : [];
+ } else if (result.url == "/console/latest/data/station") {
+ const res = JSON.parse(result.data);
+ this.stationStateList = res && res.code === 200 ? (res.data || []) : [];
+ } else if (result.url == "/rgv/table/rgv/state") {
+ const res = JSON.parse(result.data);
+ this.rgvStateList = res && res.code === 200 ? (res.data || []) : [];
+ } else if (result.url == "/basMap/lev/" + this.currentLev + "/auth") {
+ // 鍦板浘鏁版嵁
+ let res = JSON.parse(result.data);
if (res.code === 200) {
this.map = res.data;
}
@@ -165,7 +682,31 @@
this.getSystemRunningStatus() //鑾峰彇绯荤粺杩愯鐘舵��
this.getLevList() //鑾峰彇鍦板浘灞傜骇鍒楄〃
+ this.getStationTaskRange() // 鑾峰彇鍏ュ簱/鍑哄簱宸ヤ綔鍙疯寖鍥�
this.getLocMastData() //鑾峰彇搴撲綅鏁版嵁
+ },
+ getStationTaskRange() {
+ this.fetchWrkLastnoRange(1, 'inbound');
+ this.fetchWrkLastnoRange(101, 'outbound');
+ },
+ fetchWrkLastnoRange(id, key) {
+ $.ajax({
+ url: baseUrl + "/wrkLastno/" + id + "/auth",
+ headers: {
+ 'token': localStorage.getItem('token')
+ },
+ method: "get",
+ success: (res) => {
+ if (!res || res.code !== 200 || !res.data) { return; }
+ const data = res.data;
+ this.stationTaskRange = Object.assign({}, this.stationTaskRange, {
+ [key]: {
+ start: data.sNo,
+ end: data.eNo
+ }
+ });
+ }
+ });
},
connectWs() {
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) { return; }
@@ -204,40 +745,138 @@
this.currentLev = lev;
this.getMap()
this.getLocMastData()
+ this.refreshWorkbench(this.activateCard)
+ },
+ handleWorkbenchTabChange(type) {
+ this.activateCard = type;
+ this.refreshWorkbench(type);
+ },
+ refreshWorkbench(type) {
+ if (!type) { return; }
+ if (type === 'crn') {
+ this.sendWs(JSON.stringify({ url: "/crn/table/crn/state", data: {} }));
+ } else if (type === 'dualCrn') {
+ this.sendWs(JSON.stringify({ url: "/dualcrn/table/crn/state", data: {} }));
+ } else if (type === 'devp') {
+ this.sendWs(JSON.stringify({ url: "/console/latest/data/station", data: {} }));
+ } else if (type === 'rgv') {
+ this.sendWs(JSON.stringify({ url: "/rgv/table/rgv/state", data: {} }));
+ }
+ },
+ updateMapViewportPadding() {
+ const shell = this.$refs.monitorShell;
+ const panelWrap = this.$refs.monitorPanelWrap;
+ if (!shell) { return; }
+ const shellRect = shell.getBoundingClientRect();
+ let leftPadding = 0;
+ let hudLeft = 14;
+ if (!this.panelCollapsed && this.$refs.monitorPanel && panelWrap) {
+ const wrapRect = panelWrap.getBoundingClientRect();
+ const panelWidth = this.$refs.monitorPanel.offsetWidth || this.$refs.monitorPanel.getBoundingClientRect().width || 0;
+ const panelLeft = Math.max(0, Math.ceil(wrapRect.left - shellRect.left));
+ const panelBaseRight = panelLeft + Math.ceil(panelWidth);
+ const overlapCompensation = Math.min(56, panelWidth * 0.18);
+ leftPadding = Math.max(0, Math.ceil(panelBaseRight - overlapCompensation));
+ hudLeft = Math.max(14, Math.ceil(panelBaseRight + 34));
+ } else {
+ leftPadding = 0;
+ hudLeft = 14;
+ }
+ this.mapViewportPadding = {
+ top: 0,
+ right: 0,
+ bottom: 0,
+ left: leftPadding
+ };
+ this.mapHudPadding = {
+ left: hudLeft
+ };
+ },
+ scheduleMapViewportPaddingUpdate(delay) {
+ if (this.panelTransitionTimer) {
+ clearTimeout(this.panelTransitionTimer);
+ this.panelTransitionTimer = null;
+ }
+ this.panelTransitionTimer = setTimeout(() => {
+ this.panelTransitionTimer = null;
+ this.updateMapViewportPadding();
+ }, delay == null ? 0 : delay);
+ },
+ toggleMonitorPanel() {
+ this.panelCollapsed = !this.panelCollapsed;
+ this.scheduleMapViewportPaddingUpdate(0);
},
openCrn(id) {
+ this.panelCollapsed = false;
this.crnParam.crnNo = id;
this.activateCard = 'crn';
+ this.scheduleMapViewportPaddingUpdate(0);
+ this.refreshWorkbench('crn');
},
openDualCrn(id) {
+ this.panelCollapsed = false;
this.dualCrnParam.crnNo = id;
this.activateCard = 'dualCrn';
+ this.scheduleMapViewportPaddingUpdate(0);
+ this.refreshWorkbench('dualCrn');
},
openRgv(id) {
+ this.panelCollapsed = false;
this.rgvParam.rgvNo = id;
this.activateCard = 'rgv';
+ this.scheduleMapViewportPaddingUpdate(0);
+ this.refreshWorkbench('rgv');
},
openSite(id) {
+ this.panelCollapsed = false;
this.devpParam.stationId = id;
this.activateCard = 'devp';
+ this.scheduleMapViewportPaddingUpdate(0);
+ this.refreshWorkbench('devp');
},
systemSwitch() {
// 绯荤粺寮�鍏�
- let that = this
if (this.systemStatus) {
- this.$prompt('璇疯緭鍏ュ彛浠わ紝骞跺仠姝CS绯荤粺', '鎻愮ず', {
- confirmButtonText: '纭畾',
- cancelButtonText: '鍙栨秷',
- }).then(({
- value
- }) => {
- that.doSwitch(0, value)
- }).catch(() => {
-
- });
+ const password = window.prompt('璇疯緭鍏ュ彛浠わ紝骞跺仠姝CS绯荤粺', '');
+ if (password === null) {
+ return;
+ }
+ this.doSwitch(0, password);
} else {
this.doSwitch(1)
}
+ },
+ showPageMessage(message, type) {
+ if (!message) {
+ return;
+ }
+ if (typeof this.$message === 'function') {
+ this.$message({
+ message: message,
+ type: type || 'info'
+ });
+ return;
+ }
+ if (window.ELEMENT && typeof window.ELEMENT.Message === 'function') {
+ window.ELEMENT.Message({
+ message: message,
+ type: type || 'info'
+ });
+ return;
+ }
+ if (window.layer && typeof window.layer.msg === 'function') {
+ const iconMap = {
+ success: 1,
+ error: 2,
+ warning: 0
+ };
+ window.layer.msg(message, {
+ icon: iconMap[type] != null ? iconMap[type] : 0,
+ time: 1800
+ });
+ return;
+ }
+ console[type === 'error' ? 'error' : 'log'](message);
},
doSwitch(operatorType, password) {
let that = this
@@ -267,10 +906,7 @@
} else if (res.code === 403) {
parent.location.href = baseUrl + "/login";
} else {
- that.$message({
- message: res.msg,
- type: 'error'
- });
+ that.showPageMessage(res.msg, 'error');
}
}
});
@@ -300,10 +936,7 @@
} else if (res.code === 403) {
parent.location.href = baseUrl + "/login";
} else {
- that.$message({
- message: res.msg,
- type: 'error'
- });
+ that.showPageMessage(res.msg, 'error');
}
}
});
@@ -381,9 +1014,6 @@
return false;
}
},
- handleCardClick(tab, event) {
-
- },
//鑾峰彇搴撲綅鏁版嵁
getLocMastData() {
let that = this;
@@ -422,8 +1052,8 @@
return locInfo.row1 + '-' + locInfo.bay1;
}
return '';
- },
- }
+ },
+ }
})
</script>
</body>
diff --git a/src/main/webapp/views/watch/stationColorConfig.html b/src/main/webapp/views/watch/stationColorConfig.html
new file mode 100644
index 0000000..ae24781
--- /dev/null
+++ b/src/main/webapp/views/watch/stationColorConfig.html
@@ -0,0 +1,290 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+ <meta charset="utf-8">
+ <title>绔欑偣棰滆壊閰嶇疆</title>
+ <meta name="renderer" content="webkit">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+ <link rel="stylesheet" href="../../static/vue/element/element.css">
+ <link rel="stylesheet" href="../../static/css/cool.css">
+ <style>
+ html, body {
+ height: 100%;
+ margin: 0;
+ background:
+ radial-gradient(circle at top left, rgba(70, 136, 214, 0.14), transparent 34%),
+ radial-gradient(circle at bottom right, rgba(37, 198, 178, 0.12), transparent 28%),
+ linear-gradient(180deg, #eef4fa 0%, #e8eef5 100%);
+ }
+ body {
+ font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
+ }
+ [v-cloak] {
+ display: none;
+ }
+ #app {
+ height: 100%;
+ padding: 22px;
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ }
+ .page-hero {
+ padding: 22px 24px;
+ border-radius: 20px;
+ border: 1px solid rgba(255, 255, 255, 0.78);
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.92), rgba(244, 249, 255, 0.82));
+ box-shadow: 0 18px 50px rgba(48, 74, 104, 0.08);
+ }
+ .page-title {
+ font-size: 26px;
+ font-weight: 700;
+ color: #223548;
+ letter-spacing: 0.01em;
+ }
+ .page-subtitle {
+ margin-top: 8px;
+ max-width: 860px;
+ font-size: 13px;
+ line-height: 1.8;
+ color: #66788c;
+ }
+ .page-panel {
+ flex: 1;
+ min-height: 0;
+ display: flex;
+ flex-direction: column;
+ border-radius: 22px;
+ border: 1px solid rgba(221, 231, 242, 0.92);
+ background: rgba(251, 253, 255, 0.88);
+ box-shadow: 0 20px 48px rgba(49, 76, 106, 0.08);
+ overflow: hidden;
+ }
+ .panel-toolbar {
+ padding: 18px 22px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ border-bottom: 1px solid rgba(224, 233, 244, 0.9);
+ background: rgba(255, 255, 255, 0.72);
+ }
+ .panel-tip {
+ font-size: 12px;
+ color: #74879b;
+ line-height: 1.8;
+ }
+ .color-grid {
+ flex: 1;
+ min-height: 0;
+ padding: 20px 22px 24px;
+ overflow: auto;
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(290px, 1fr));
+ grid-auto-rows: max-content;
+ align-content: start;
+ gap: 16px;
+ }
+ .color-card {
+ position: relative;
+ border-radius: 18px;
+ border: 1px solid rgba(223, 232, 242, 0.94);
+ background: linear-gradient(180deg, rgba(255,255,255,0.96), rgba(246,250,255,0.92));
+ box-shadow: 0 12px 28px rgba(76, 101, 130, 0.07);
+ padding: 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ overflow: visible;
+ }
+ .color-card::after {
+ content: "";
+ position: absolute;
+ top: -30px;
+ right: -20px;
+ width: 120px;
+ height: 120px;
+ border-radius: 50%;
+ background: rgba(255,255,255,0.42);
+ pointer-events: none;
+ }
+ .color-card-head {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 14px;
+ }
+ .color-name {
+ font-size: 16px;
+ font-weight: 700;
+ color: #24374a;
+ }
+ .color-status {
+ margin-top: 4px;
+ font-size: 12px;
+ color: #7a8da2;
+ word-break: break-all;
+ }
+ .color-chip {
+ width: 58px;
+ height: 58px;
+ border-radius: 18px;
+ border: 1px solid rgba(89, 109, 134, 0.18);
+ box-shadow: inset 0 0 0 1px rgba(255,255,255,0.46);
+ flex-shrink: 0;
+ }
+ .color-desc {
+ font-size: 12px;
+ line-height: 1.8;
+ color: #66798d;
+ }
+ .color-editor {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ }
+ .color-picker-inline {
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 0 10px;
+ height: 40px;
+ border-radius: 12px;
+ border: 1px solid rgba(214, 225, 238, 0.95);
+ background: rgba(247, 250, 255, 0.9);
+ color: #567089;
+ font-size: 12px;
+ font-weight: 600;
+ }
+ .color-actions {
+ display: flex;
+ justify-content: flex-end;
+ }
+ .color-default {
+ font-size: 12px;
+ color: #7890a8;
+ }
+ .color-meta {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 10px;
+ }
+ .empty-state {
+ padding: 60px 20px;
+ text-align: center;
+ color: #7b8c9f;
+ font-size: 14px;
+ }
+ .footer-note {
+ padding: 0 22px 18px;
+ font-size: 12px;
+ color: #8696a8;
+ }
+ .el-color-picker__trigger {
+ width: 52px;
+ height: 40px;
+ padding: 0;
+ border-radius: 12px;
+ border-color: rgba(211, 223, 237, 0.95);
+ background: #fff;
+ }
+ .el-input__inner {
+ border-radius: 12px;
+ height: 40px;
+ line-height: 40px;
+ }
+ .el-button + .el-button {
+ margin-left: 10px;
+ }
+ @media (max-width: 900px) {
+ #app {
+ padding: 14px;
+ }
+ .page-hero,
+ .panel-toolbar,
+ .color-grid,
+ .footer-note {
+ padding-left: 16px;
+ padding-right: 16px;
+ }
+ .color-editor,
+ .color-meta {
+ flex-wrap: wrap;
+ }
+ }
+ </style>
+</head>
+<body>
+<div id="app" v-cloak>
+ <div class="page-hero">
+ <div class="page-title">绔欑偣棰滆壊閰嶇疆</div>
+ <div class="page-subtitle">
+ 鍗曠嫭缁存姢鐩戞帶鍦板浘閲岀珯鐐圭姸鎬佺殑鏄剧ず棰滆壊銆傞鑹查�氳繃璋冭壊鐩樹换鎰忛�夋嫨锛岄厤缃繚瀛樺湪 Redis 涓紱
+ 鏈厤缃椂榛樿浣跨敤褰撳墠鍦板浘浠g爜閲岀殑棰滆壊鍊笺��
+ </div>
+ </div>
+
+ <div class="page-panel">
+ <div class="panel-toolbar">
+ <div class="panel-tip">
+ 寤鸿璁┾�滃惎鍔ㄥ叆搴� / 鍏ュ簱浠诲姟 / 鍑哄簱浠诲姟 / 鍫靛鈥濅繚鎸佹槑鏄惧尯鍒嗭紝閬垮厤鐜板満鍊煎畧鏃惰鍒ゃ��
+ </div>
+ <div>
+ <el-button size="small" @click="reloadData" :loading="loading">鍒锋柊</el-button>
+ <el-button size="small" @click="resetDefaults">鎭㈠榛樿</el-button>
+ <el-button type="primary" size="small" @click="saveConfig" :loading="saving">淇濆瓨閰嶇疆</el-button>
+ </div>
+ </div>
+
+ <div v-if="items.length" class="color-grid">
+ <div v-for="item in items" :key="item.status" class="color-card">
+ <div class="color-card-head">
+ <div>
+ <div class="color-name">{{ item.name }}</div>
+ <div class="color-status">{{ item.status }}</div>
+ </div>
+ <div class="color-chip" :style="{ backgroundColor: item.color }"></div>
+ </div>
+
+ <div class="color-desc">{{ item.desc }}</div>
+
+ <div class="color-editor">
+ <div class="color-picker-inline">
+ <el-color-picker
+ v-model="item.color"
+ :predefine="predefineColors"
+ ></el-color-picker>
+ <span>璋冭壊鐩�</span>
+ </div>
+ <el-input
+ v-model="item.color"
+ @change="handleColorInput(item)"
+ placeholder="#FFFFFF"
+ ></el-input>
+ </div>
+
+ <div class="color-meta">
+ <div class="color-default">榛樿鍊硷細{{ item.defaultColor }}</div>
+ <div class="color-actions">
+ <el-button size="mini" @click="applyDefaultColor(item)">鎭㈠榛樿</el-button>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div v-else class="empty-state">鏆傛棤绔欑偣棰滆壊閰嶇疆椤�</div>
+
+ <div class="footer-note">淇濆瓨鍚庯紝鏂版墦寮�鐨勭洃鎺у湴鍥句細鐩存帴璇诲彇 Redis 閰嶇疆锛涘凡鎵撳紑椤甸潰鍒锋柊鍚庡嵆鍙敓鏁堛��</div>
+ </div>
+</div>
+
+<script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
+<script type="text/javascript" src="../../static/vue/js/vue.min.js"></script>
+<script type="text/javascript" src="../../static/vue/element/element.js"></script>
+<script type="text/javascript" src="../../static/js/common.js" charset="utf-8"></script>
+<script type="text/javascript" src="../../static/js/watch/stationColorConfig.js" charset="utf-8"></script>
+</body>
+</html>
--
Gitblit v1.9.1