From ba8aa925e7901381ceb394adb53eca8723d1c4c5 Mon Sep 17 00:00:00 2001
From: leiqunqing <zhengqifeng@hnkhzn.com>
Date: 星期一, 19 一月 2026 11:05:49 +0800
Subject: [PATCH] 完善工位界面

---
 代码管理/WIDESEAWCS_Client/src/views/Home.vue | 1094 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 1,082 insertions(+), 12 deletions(-)

diff --git "a/\344\273\243\347\240\201\347\256\241\347\220\206/WIDESEAWCS_Client/src/views/Home.vue" "b/\344\273\243\347\240\201\347\256\241\347\220\206/WIDESEAWCS_Client/src/views/Home.vue"
index 820437a..b82d2f2 100644
--- "a/\344\273\243\347\240\201\347\256\241\347\220\206/WIDESEAWCS_Client/src/views/Home.vue"
+++ "b/\344\273\243\347\240\201\347\256\241\347\220\206/WIDESEAWCS_Client/src/views/Home.vue"
@@ -1,24 +1,1094 @@
 <template>
-  <div class="title"></div>
+  <div class="container">
+    <!-- 宸︿晶鍖哄煙 - 鏃犱换浣曟爣棰樻爣娉� -->
+    <div class="left-area">
+      <div class="left-top">
+        <!-- 鎸夐挳+淇″彿 妯悜鎺掑垪瀹瑰櫒 -->
+        <div class="btn-signal-group">
+          <!-- 鎸夐挳缁� - 淇敼涓轰笂涓嬫帓鍒� -->
+          <div class="btn-group">
+            <button
+              class="btn"
+              :class="isPLCStarted ? 'stop-btn' : 'start-btn'"
+              @click="handleToggle"
+            >
+              <i class="icon" :class="isPLCStarted ? 'icon-stop' : 'icon-start'"></i>
+              {{ isPLCStarted ? "鍏抽棴" : "鍚姩" }}
+            </button>
+            <button
+              class="btn"
+              :class="isPLCPaused ? 'resume-btn' : 'pause-btn'"
+              @click="handlePauseToggle"
+              :disabled="!isPLCStarted"
+            >
+              <i class="icon" :class="isPLCPaused ? 'icon-resume' : 'icon-pause'"></i>
+              {{ isPLCPaused ? "鎭㈠" : "鏆傚仠" }}
+            </button>
+          </div>
+
+          <!-- 淇″彿鐏粍 鍗犱袱涓寜閽搴� + 鏁翠綋鏀惧ぇ -->
+          <div class="signal-status">
+            <div class="signal-item" v-for="(signal, index) in signalStates" :key="index">
+              <div
+                class="signal-light"
+                :class="signal ? 'signal-active' : 'signal-inactive'"
+              >
+                <div class="signal-light-inner"></div>
+              </div>
+              <span class="signal-label">{{ signalLabels[index] }}</span>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <div class="left-bottom">
+        <div class="form-row finished-product-row">
+          <span class="label">鎴愬搧缂栧彿锛�</span>
+          <input type="text" class="input-box" v-model="finishedProduct" disabled />
+        </div>
+        <div class="parts-list">
+          <div
+            class="form-row part-item"
+            v-for="(item, index) in leftPartCodes"
+            :key="index"
+          >
+            <span class="label">闆朵欢{{ index + 1 }}锛�</span>
+            <input
+              type="text"
+              class="input-box"
+              v-model="leftPartCodes[index]"
+              disabled
+            />
+            <label class="checkbox-container">
+              <input
+                type="checkbox"
+                class="part-checkbox"
+                v-model="leftPartChecked[index]"
+                @change="handlePartCheck(index)"
+              />
+              <span class="checkmark"></span>
+              <span class="checkbox-label">{{
+                leftPartChecked[index] ? "鎵爜" : "涓嶆壂"
+              }}</span>
+            </label>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 鍙充晶鍖哄煙 - 鏃犱换浣曟爣棰樻爣娉� 銆愬凡鍒犻櫎娓呯┖+淇濆瓨鎸夐挳銆� -->
+    <div class="right-area">
+      <div class="right-top">
+        <div class="form-row">
+          <span class="label">褰曞叆妗嗭細</span>
+          <!-- 鉁� 鍙繚鐣欑函褰曞叆妗嗭紝娓呯┖/淇濆瓨鎸夐挳宸插垹闄� -->
+          <input type="text" class="input-box" v-model="rightTopInput" />
+        </div>
+      </div>
+
+      <div class="right-bottom">
+        <div class="form-row tooling-board-row">
+          <span class="short-label">宸ヨ鏉跨紪鍙凤細</span>
+          <input
+            type="text"
+            class="input-box short-input"
+            v-model="toolingBoardNo"
+            placeholder="璇疯緭鍏ュ伐瑁呮澘缂栧彿"
+          />
+          <button class="btn clear-btn" @click="clearToolingBoardNo">
+            <i class="icon icon-clear"></i>娓呴櫎
+          </button>
+          <button class="btn save-btn" @click="saveToolingBoardNo">
+            <i class="icon icon-submit"></i>鎻愪氦
+          </button>
+        </div>
+        <div class="parts-list">
+          <div class="form-row part-item finished-product-row">
+            <span class="label">鎴愬搧缂栧彿锛�</span>
+            <input
+              type="text"
+              class="input-box"
+              v-model="finishedProductCode"
+              placeholder="璇疯緭鍏ユ垚鍝佺紪鍙�"
+            />
+            <button class="btn clear-btn" @click="clearFinishedProductCode">
+              <i class="icon icon-clear"></i>娓呴櫎
+            </button>
+          </div>
+          <div
+            class="form-row part-item"
+            v-for="(item, index) in rightPartCodes"
+            :key="index"
+          >
+            <span class="label">闆朵欢{{ index + 1 }}锛�</span>
+            <input
+              type="text"
+              class="input-box"
+              v-model="rightPartCodes[index]"
+              placeholder="璇疯緭鍏ラ浂浠剁紪鍙�"
+            />
+            <button class="btn clear-btn" @click="clearRightPart(index)">
+              <i class="icon icon-clear"></i>娓呴櫎
+            </button>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
 </template>
 
 <script>
-import { ref, reactive } from 'vue'
+import { ref, onMounted, onUnmounted, watch, computed } from "vue";
+import axios from "axios";
 
 export default {
   setup() {
-    return {
+    // 鍩虹鏁版嵁瀹氫箟 - 1:1绮惧噯瀵规帴鍚庣 鎴愬搧缂栧彿+闆朵欢缂栧彿 鏃犲啑浣欏吋瀹�
+    const finishedProduct = ref(""); // 宸︿晶鎴愬搧缂栧彿锛圙etLeftInitialData鎺ュ彛杩斿洖锛�
+    const finishedProductId = ref("");
+    const rightTopInput = ref("");
+    const leftPartCodes = ref(Array(10).fill("")); // 宸︿晶闆朵欢缂栧彿鏁扮粍
+    const rightPartCodes = ref(Array(10).fill("")); // 鍙充晶闆朵欢缂栧彿鏁扮粍
+    const leftPartChecked = ref(Array(10).fill(false));
+    const toolingBoardNo = ref("");
+    const fillIndex = ref(-1);
+    const leftPartIds = ref(Array(10).fill(""));
+    const finishedProductCode = ref(""); // 鍙充晶鎴愬搧缂栧彿锛堝伐瑁呮澘鎺ュ彛杩斿洖锛�
 
-    }
-  }
-}
+    // PLC鐘舵��
+    const isPLCStarted = ref(false);
+    const isPLCPaused = ref(false);
+
+    // 淇″彿鐩稿叧
+    const signalStates = ref([false, false, false, false, false]);
+    const signalLabels = ref([
+      "蹇冭烦淇″彿",
+      "鎬ュ仠淇″彿",
+      "鑷姩杩愯淇″彿",
+      "鍦ㄧ嚎妯″紡淇″彿",
+      "鏁呴殰淇″彿",
+    ]);
+
+    // 瀹氭椂杞鏍稿績閰嶇疆
+    let pollingTimer = null;
+    const pollingInterval = 5000;
+    let checkDebounceTimer = null;
+    let destroyDelayTimer = null;
+    const destroyDelayTime = 500; // 鉁� 鏍稿績锛氬~鍏�+娓呯┖ 閮藉欢杩�500姣
+    let boardCodeDebounceTimer = null;
+    // 鉁� 鏂板锛氳嚜鍔ㄦ彁浜ら槻鎶栧畾鏃跺櫒锛岄槻姝㈤噸澶嶆彁浜�
+    let autoSubmitDebounceTimer = null;
+    // 鉁� 鏂板锛氭彁浜ら攣锛岄槻姝㈡棤鍕鹃�夋椂閲嶅瑙﹀彂鎻愪氦
+    let submitLock = ref(false);
+
+    // 鉁� 鉁� 鉁� 鏍稿績鏂板1锛氳绠楀睘鎬� - 瀹炴椂缁熻宸︿晶鍕鹃�夌殑澶嶉�夋鏁伴噺 (鑷姩鏇存柊)
+    const checkedCount = computed(() => {
+      // 缁熻leftPartChecked鏁扮粍涓负true鐨勬暟閲�
+      return leftPartChecked.value.filter((checked) => checked === true).length;
+    });
+
+    // 鉁� 鉁� 鉁� 鏍稿績鏂板2锛氳绠楀睘鎬� - 瀹炴椂缁熻鍙充晶宸插~鍏呯殑闆朵欢鏁伴噺 (鑷姩鏇存柊)
+    const filledPartCount = computed(() => {
+      // 缁熻rightPartCodes鏁扮粍涓湁鍊�(闈炵┖)鐨勯浂浠舵暟閲�
+      return rightPartCodes.value.filter((code) => code.trim() !== "").length;
+    });
+
+    // 鉁� 鑾峰彇宸︿晶鍒濆鏁版嵁 - 瀵规帴 /api/scanStation/GetLeftInitialData
+    const fetchLeftInitialData = async () => {
+      try {
+        console.log("姝e湪鑾峰彇宸︿晶鍒濆鏁版嵁锛堟垚鍝佺紪鍙�+闆朵欢缂栧彿+鍕鹃�夌姸鎬�+闆朵欢ID锛�...");
+        const response = await axios.get("/api/scanStation/GetLeftInitialData", {
+          timeout: 5000,
+        });
+        const resData = response.data;
+        const isSuccess = resData.Status === true || resData.status === true;
+        if (isSuccess) {
+          const data = resData.Data || resData.data || {};
+          if (data.finishedProductId) finishedProductId.value = data.finishedProductId;
+          if (data.finishedProduct) finishedProduct.value = data.finishedProduct;
+          // 璧嬪�煎乏渚ч浂浠剁紪鍙�
+          if (Array.isArray(data.leftPartCodes) && data.leftPartCodes.length >= 10) {
+            for (let i = 0; i < 10; i++) {
+              leftPartCodes.value[i] = data.leftPartCodes[i] || "";
+              leftPartIds.value[i] = data.leftPartIds?.[i] || "";
+            }
+          }
+          // 璧嬪�煎嬀閫夌姸鎬�
+          if (Array.isArray(data.leftPartChecked) && data.leftPartChecked.length >= 10) {
+            for (let i = 0; i < 10; i++) {
+              leftPartChecked.value[i] = !!data.leftPartChecked[i];
+            }
+          }
+        }
+      } catch (error) {
+        console.error("鑾峰彇宸︿晶鍒濆鏁版嵁澶辫触锛�", error);
+      }
+    };
+
+    // 鉁� 鑾峰彇淇″彿+PLC鐘舵��
+    const fetchSignalAndPLCStates = async () => {
+      try {
+        const response = await axios.get("/api/scanStation/GetSignalStates", {
+          timeout: 5000,
+        });
+        const resData = response.data;
+        const isSuccess = resData.Status === true || resData.status === true;
+        if (isSuccess) {
+          const data = resData.Data || resData.data || {};
+          const newSignalStates = data.signalStates || [];
+          for (let i = 0; i < 5; i++) signalStates.value[i] = newSignalStates[i] ?? false;
+          const plcStatus = data.plcStatus || data.plc_status || {};
+          isPLCStarted.value = plcStatus.isStarted ?? isPLCStarted.value;
+          // 鉁� 淇BUG锛氬師浠g爜鏄� isPLCStarted.value 瀵艰嚧鏆傚仠鐘舵�佽祴鍊奸敊璇�
+          isPLCPaused.value = plcStatus.isPaused ?? isPLCPaused.value;
+        }
+      } catch (error) {
+        console.error("鑾峰彇淇″彿鍜孭LC鐘舵�佸け璐ワ細", error);
+      }
+    };
+
+    // 鉁� 銆愭牳蹇冧慨鏀广�戝伐瑁呮澘鏌ヨ鎺ュ彛 - 鏈夋暟鎹氨濉厖锛屾棤鏁版嵁/澶辫触 瀹屽叏淇濈暀鍘熸湁鍐呭锛屼笉鍋氫换浣曟竻绌烘搷浣�
+    const fetchProductAndPartsByBoardCode = async (boardCode) => {
+      if (!boardCode.trim()) return;
+      try {
+        console.log(`宸ヨ鏉跨紪鍙峰彉鏇达紝璇锋眰鏁版嵁锛�${boardCode}`);
+        const response = await axios.get(
+          "/api/boxingDetail/GetProductAndPartsByBoardNo",
+          {
+            params: { palletCode: boardCode.trim() },
+            timeout: 5000,
+          }
+        );
+        const resData = response.data;
+        const isSuccess = resData.Status === true || resData.status === true;
+        // 鉁� 鍙湁銆愭帴鍙f垚鍔�+鏈夎繑鍥炴暟鎹�戠殑鏃跺�欙紝鎵嶆墽琛岃祴鍊艰鐩�
+        if (isSuccess) {
+          const data = resData.Data || resData.data || {};
+          // 鏈夋垚鍝佺紪鍙峰氨璧嬪�硷紝娌℃湁灏变笉鎿嶄綔
+          if (data.finishedProductCode) {
+            finishedProductCode.value = data.finishedProductCode;
+          }
+          // 鏈夐浂浠跺垪琛ㄥ氨濉厖锛屾病鏈夊氨涓嶆搷浣�
+          const partsList = Array.isArray(data.partsList) ? data.partsList : [];
+          if (partsList.length > 0) {
+            for (let i = 0; i < 10; i++) {
+              if (partsList[i]) {
+                rightPartCodes.value[i] = partsList[i];
+              }
+            }
+          }
+          console.log("鉁� 宸ヨ鏉挎煡璇㈡垚鍔燂紝鎴愬搧缂栧彿+闆朵欢缂栧彿濉厖瀹屾垚");
+        } else {
+          // 鉁� 鏃犲搴旀暟鎹細鍙脊鎻愮ず锛屼笉娓呯┖浠讳綍鍐呭
+          alert(
+            "鑾峰彇鏁版嵁澶辫触锛�" + (resData.Message || resData.message || "鏃犲搴斿伐瑁呮澘鏁版嵁")
+          );
+        }
+      } catch (error) {
+        // 鉁� 璇锋眰澶辫触锛氬彧寮规彁绀猴紝涓嶆竻绌轰换浣曞唴瀹�
+        alert("宸ヨ鏉挎暟鎹姹傚け璐ワ紝璇锋鏌ョ綉缁滄垨鎺ュ彛锛�");
+        console.error("宸ヨ鏉挎帴鍙h姹傚け璐ワ細", error);
+      } finally {
+        boardCodeDebounceTimer = null;
+      }
+    };
+
+    // 鍚姩/鍋滄瀹氭椂杞
+    const startPolling = () => {
+      if (pollingTimer) clearInterval(pollingTimer);
+      fetchSignalAndPLCStates();
+      pollingTimer = setInterval(fetchSignalAndPLCStates, pollingInterval);
+    };
+    const stopPolling = () => {
+      if (pollingTimer) clearInterval(pollingTimer);
+      pollingTimer = null;
+    };
+
+    // PLC鍚姩/鍏抽棴閫昏緫
+    const handleToggle = async () => {
+      try {
+        const response = await axios.get("/api/scanStation/StartPLC", {
+          params: { isStop: isPLCStarted.value },
+          timeout: 5000,
+        });
+        const resData = response.data;
+        const isSuccess = resData.Status === true || resData.status === true;
+        if (isSuccess) {
+          isPLCStarted.value = !isPLCStarted.value;
+          isPLCPaused.value = false;
+          fetchSignalAndPLCStates();
+        } else {
+          alert(
+            resData.Message ||
+              resData.message ||
+              (isPLCStarted.value ? "鍏抽棴澶辫触" : "鍚姩澶辫触")
+          );
+        }
+      } catch (error) {
+        alert(isPLCStarted.value ? "鍏抽棴PLC澶辫触" : "鍚姩PLC澶辫触");
+        console.error("PLC鍚仠澶辫触锛�", error);
+      }
+    };
+
+    // PLC鏆傚仠/鎭㈠閫昏緫
+    const handlePauseToggle = async () => {
+      try {
+        const response = await axios.get("/api/scanStation/PausePLC", {
+          params: { isPause: !isPLCPaused.value },
+          timeout: 5000,
+        });
+        const resData = response.data;
+        const isSuccess = resData.Status === true || resData.status === true;
+        if (isSuccess) {
+          isPLCPaused.value = !isPLCPaused.value;
+          fetchSignalAndPLCStates();
+        } else {
+          alert(
+            resData.Message ||
+              resData.message ||
+              (isPLCPaused.value ? "鎭㈠澶辫触" : "鏆傚仠澶辫触")
+          );
+        }
+      } catch (error) {
+        alert(isPLCPaused.value ? "鎭㈠PLC澶辫触" : "鏆傚仠PLC澶辫触");
+        console.error("PLC鏆傚仠鎭㈠澶辫触锛�", error);
+      }
+    };
+
+    // 闆朵欢鍕鹃�夌姸鎬佸彉鏇村鐞�
+    const handlePartCheck = async (index) => {
+      const isChecked = leftPartChecked.value[index];
+      const partCode = leftPartCodes.value[index];
+      const partId = leftPartIds.value[index];
+
+      if (!finishedProductId.value) {
+        alert("鎴愬搧ID涓嶅瓨鍦紝鏃犳硶鏇存柊闆朵欢鎵爜鐘舵�侊紒");
+        leftPartChecked.value[index] = !isChecked;
+        return;
+      }
+      if (!partId) {
+        alert(`闆朵欢${index + 1}鏁版嵁搴揑D涓嶅瓨鍦紝鏃犳硶鏇存柊鎵爜鐘舵�侊紒`);
+        leftPartChecked.value[index] = !isChecked;
+        return;
+      }
+      if (!partCode.trim()) {
+        alert(`闆朵欢${index + 1}缂栧彿涓虹┖锛屾棤娉曟洿鏂版壂鐮佺姸鎬侊紒`);
+        leftPartChecked.value[index] = !isChecked;
+        return;
+      }
+
+      if (checkDebounceTimer) clearTimeout(checkDebounceTimer);
+      checkDebounceTimer = setTimeout(async () => {
+        try {
+          const response = await axios.post(
+            "/api/scanStation/UpdatePartScannedStatus",
+            { Id: partId, IsScanned: isChecked ? 1 : 0 },
+            { timeout: 5000 }
+          );
+          const resData = response.data;
+          const isSuccess = resData.Status === true || resData.status === true;
+          if (isSuccess) {
+            console.log(`闆朵欢${index + 1}鎵爜鐘舵�佹洿鏂版垚鍔焋);
+            // 鉁� 鍕鹃�夌姸鎬佸彉鍖栨椂锛岄噸缃彁浜ら攣
+            submitLock.value = false;
+          } else {
+            leftPartChecked.value[index] = !isChecked;
+            alert(
+              `闆朵欢${index + 1}鐘舵�佹洿鏂板け璐ワ細${
+                resData.Message || resData.message || "鏈煡閿欒"
+              }`
+            );
+          }
+        } catch (error) {
+          leftPartChecked.value[index] = !isChecked;
+          alert(`闆朵欢${index + 1}鐘舵�佹洿鏂拌姹傚け璐ワ紝璇锋鏌ョ綉缁滄垨鎺ュ彛锛乣);
+          console.error(`鏇存柊闆朵欢${index + 1}鎵爜鐘舵�佸け璐ワ細`, error);
+        } finally {
+          checkDebounceTimer = null;
+        }
+      }, 500);
+    };
+
+    // 鉁� 鏍稿績淇敼锛氬欢杩�0.5绉掑~鍏� + 寤惰繜0.5绉掓竻绌� 鍚屾鎵ц 鏃犻渶鎵嬪姩鎸夐挳
+    const fillContent = () => {
+      if (!rightTopInput.value.trim()) return;
+      const inputValue = rightTopInput.value.trim();
+
+      // 娓呴櫎鏃у畾鏃跺櫒锛岄槻姝㈤噸澶嶆墽琛�
+      if (destroyDelayTimer) clearTimeout(destroyDelayTimer);
+      // 缁熶竴寤惰繜500ms鎵ц銆愬~鍏�+娓呯┖銆�
+      destroyDelayTimer = setTimeout(() => {
+        if (!toolingBoardNo.value.trim()) {
+          toolingBoardNo.value = inputValue;
+        } else if (!finishedProductCode.value.trim()) {
+          finishedProductCode.value = inputValue;
+        } else if (fillIndex.value < 10) {
+          rightPartCodes.value[fillIndex.value] = inputValue;
+          fillIndex.value++;
+        } else {
+          alert("宸ヨ鏉跨紪鍙枫�佹垚鍝佺紪鍙峰拰闆朵欢1-10宸插叏閮ㄥ~鍏呭畬鎴�,鏃犳硶缁х画褰曞叆!");
+          rightTopInput.value = "";
+          destroyDelayTimer = null;
+          return;
+        }
+        // 濉厖瀹屾垚鍚� 鑷姩娓呯┖褰曞叆妗�
+        rightTopInput.value = "";
+        destroyDelayTimer = null;
+      }, destroyDelayTime);
+    };
+
+    // 鉁� 鐩戝惉褰曞叆妗嗗唴瀹瑰彉鍖栬嚜鍔ㄨЕ鍙戝~鍏呴�昏緫
+    watch(
+      rightTopInput,
+      (newVal) => {
+        if (newVal.trim()) fillContent();
+      },
+      { immediate: false }
+    );
+
+    // 鐩戝惉宸ヨ鏉跨紪鍙峰彉鍖栨煡璇㈡暟鎹�
+    watch(
+      toolingBoardNo,
+      (newVal) => {
+        const boardCode = newVal.trim();
+        if (boardCode) {
+          if (boardCodeDebounceTimer) clearTimeout(boardCodeDebounceTimer);
+          boardCodeDebounceTimer = setTimeout(
+            () => fetchProductAndPartsByBoardCode(boardCode),
+            300
+          );
+        } else {
+          if (boardCodeDebounceTimer) clearTimeout(boardCodeDebounceTimer);
+        }
+      },
+      { immediate: false }
+    );
+
+    // 鍙充晶椤甸潰鎿嶄綔鏂规硶 (宸插垹闄ゆ竻绌�/淇濆瓨褰曞叆妗嗙殑鏂规硶锛屾棤鍐椾綑)
+    const clearToolingBoardNo = () => (toolingBoardNo.value = "");
+    const clearRightPart = (index) => (rightPartCodes.value[index] = "");
+    const clearFinishedProductCode = () => (finishedProductCode.value = "");
+
+    // 鎻愪氦宸ヨ鏉挎暟鎹埌鍚庣
+    const saveToolingBoardNo = async () => {
+      if (!toolingBoardNo.value.trim()) {
+        alert("宸ヨ鏉跨紪鍙蜂笉鑳戒负绌猴紝璇疯緭鍏ュ悗鍐嶆彁浜わ紒");
+        return;
+      }
+      if (!finishedProductCode.value.trim()) {
+        alert("鎴愬搧缂栧彿涓嶈兘涓虹┖锛岃杈撳叆鍚庡啀鎻愪氦锛�");
+        return;
+      }
+      try {
+        const submitData = {
+          toolingBoardNo: toolingBoardNo.value.trim(),
+          finishedProductCode: finishedProductCode.value.trim(),
+          partsList: rightPartCodes.value.map((item) => item.trim()),
+        };
+        console.log("鉁� 鎻愪氦宸ヨ鏉挎暟鎹細", submitData);
+        const response = await axios.post(
+          "/api/boxingDetail/SaveToolingBoardNo",
+          submitData,
+          { timeout: 5000 }
+        );
+        const resData = response.data;
+        const isSuccess = resData.Status === true || resData.status === true;
+        if (isSuccess) {
+          alert("鉁� 鎻愪氦鎴愬姛锛�");
+          toolingBoardNo.value = "";
+          finishedProductCode.value = "";
+          rightPartCodes.value = Array(10).fill("");
+          rightTopInput.value = "";
+          fillIndex.value = -1;
+          // 鉁� 鎻愪氦鎴愬姛鍚庯紝閲嶇疆鎻愪氦閿�
+          submitLock.value = false;
+        } else {
+          alert("鎻愪氦澶辫触锛�" + (resData.Message || resData.message || "鏈煡閿欒"));
+        }
+      } catch (error) {
+        alert("鎻愪氦璇锋眰澶辫触锛岃妫�鏌ョ綉缁滄垨鎺ュ彛锛�");
+        console.error("鎻愪氦鎺ュ彛澶辫触锛�", error);
+        // 鉁� 璇锋眰澶辫触涔熼噸缃攣
+        submitLock.value = false;
+      }
+    };
+
+    // 鉁� 鉁� 鉁� 鏍稿績鍗囩骇锛氳嚜鍔ㄦ彁浜ゅ垽鏂�昏緫 - 鏂板鏃犲嬀閫夋椂鎴愬搧缂栧彿濉厖鍗虫彁浜�
+    const checkAutoSubmit = () => {
+      // 闃叉姈锛氶槻姝㈢煭鏃堕棿鍐呭娆¤Е鍙戞彁浜�
+      if (autoSubmitDebounceTimer) clearTimeout(autoSubmitDebounceTimer);
+      autoSubmitDebounceTimer = setTimeout(() => {
+        const needCheckNum = checkedCount.value; // 宸︿晶鍕鹃�夌殑鏁伴噺
+        const filledNum = filledPartCount.value; // 鍙充晶濉厖鐨勯浂浠舵暟閲�
+        const hasBoardNo = toolingBoardNo.value.trim() !== ""; // 宸ヨ鏉挎湁鍊�
+        const hasProductCode = finishedProductCode.value.trim() !== ""; // 鎴愬搧鏈夊��
+
+        console.log(`鉁� 鑷姩鎻愪氦鏍¢獙锛氬乏渚у嬀閫�${needCheckNum}涓紝鍙充晶濉厖${filledNum}涓猔);
+
+        // 鍒嗘敮1锛氬乏渚ф湁鍕鹃�� 鈫� 鍘熸湁閫昏緫锛氶浂浠跺~鍏呮暟鈮ュ嬀閫夋暟 鎵嶆彁浜�
+        if (needCheckNum > 0) {
+          if (hasBoardNo && hasProductCode && filledNum >= needCheckNum) {
+            console.log("鉁� 婊¤冻鍕鹃�夋暟閲忔潯浠讹紝鎵ц鑷姩鎻愪氦锛�");
+            saveToolingBoardNo();
+          }
+        }
+        // 鍒嗘敮2锛氬乏渚ф棤鍕鹃�� 鈫� 鏂板閫昏緫锛氬伐瑁呮澘+鎴愬搧閮芥湁鍊� 灏辨彁浜� (鍔犻攣闃查噸澶�)
+        else {
+          if (hasBoardNo && hasProductCode && !submitLock.value) {
+            console.log("鉁� 宸︿晶鏃犲嬀閫夛紝鎴愬搧缂栧彿濉厖瀹屾垚锛屾墽琛岃嚜鍔ㄦ彁浜わ紒");
+            submitLock.value = true; // 鍔犻攣闃叉閲嶅鎻愪氦
+            saveToolingBoardNo();
+          }
+        }
+        autoSubmitDebounceTimer = null;
+      }, 300);
+    };
+
+    // 鉁� 鉁� 鉁� 鏍稿績鏂板4锛氱洃鍚叧閿暟鎹彉鍖栵紝瑙﹀彂鑷姩鎻愪氦鏍¢獙
+    watch(
+      [checkedCount, filledPartCount, toolingBoardNo, finishedProductCode],
+      () => {
+        checkAutoSubmit();
+      },
+      { deep: true, immediate: false }
+    );
+
+    // 鑷姩妫�娴嬪~鍏呯储寮曢�昏緫
+    const detectFillIndex = () => {
+      if (!toolingBoardNo.value.trim() || !finishedProductCode.value.trim()) {
+        fillIndex.value = -1;
+        return;
+      }
+      for (let i = 0; i < 10; i++) {
+        if (!rightPartCodes.value[i].trim()) {
+          fillIndex.value = i;
+          return;
+        }
+      }
+      fillIndex.value = 10;
+    };
+
+    watch(
+      [toolingBoardNo, finishedProductCode, () => [...rightPartCodes.value]],
+      detectFillIndex,
+      {
+        immediate: true,
+        deep: true,
+      }
+    );
+
+    // 椤甸潰鎸傝浇/鍗歌浇鐢熷懡鍛ㄦ湡
+    onMounted(async () => {
+      await fetchLeftInitialData();
+      startPolling();
+      detectFillIndex();
+    });
+
+    onUnmounted(() => {
+      stopPolling();
+      if (checkDebounceTimer) clearTimeout(checkDebounceTimer);
+      if (destroyDelayTimer) clearTimeout(destroyDelayTimer);
+      if (boardCodeDebounceTimer) clearTimeout(boardCodeDebounceTimer);
+      if (autoSubmitDebounceTimer) clearTimeout(autoSubmitDebounceTimer);
+    });
+
+    return {
+      finishedProduct,
+      finishedProductId,
+      rightTopInput,
+      leftPartCodes,
+      rightPartCodes,
+      leftPartChecked,
+      leftPartIds,
+      toolingBoardNo,
+      isPLCStarted,
+      isPLCPaused,
+      signalStates,
+      signalLabels,
+      finishedProductCode,
+      handleToggle,
+      handlePauseToggle,
+      handlePartCheck,
+      clearToolingBoardNo,
+      saveToolingBoardNo,
+      clearRightPart,
+      clearFinishedProductCode,
+    };
+  },
+};
 </script>
 
 <style scoped>
-.title {
-  line-height: 70vh;
-  text-align: center;
-  font-size: 28px;
-  color: orange;
+/* 鍩虹鏍峰紡閲嶇疆涓庡叏灞�鏍峰紡 */
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+  font-family: "Microsoft Yahei", "PingFang SC", "Inter", sans-serif;
+  scrollbar-width: none;
+  -ms-overflow-style: none;
 }
-</style>
\ No newline at end of file
+*::-webkit-scrollbar {
+  display: none;
+}
+
+body {
+  background: linear-gradient(135deg, #f0f4f8 0%, #e9ecef 100%);
+  min-height: 100vh;
+  overflow: hidden;
+  font-size: 14px;
+}
+
+/* 瀹瑰櫒鏍峰紡 - 鏀惧ぇ 闂磋窛鍔犲 */
+.container {
+  display: flex;
+  width: 100%;
+  height: 100vh;
+  margin: 0;
+  gap: 15px;
+  padding: 15px;
+  overflow: hidden;
+}
+
+/* 闈㈡澘閫氱敤鏍峰紡 - 缁熶竴鍐呰竟璺� 纭繚宸﹀彸瀵归綈 */
+.left-area,
+.right-area {
+  flex: 1;
+  width: 50%;
+  display: flex;
+  flex-direction: column;
+  gap: 15px;
+  padding: 18px;
+  background: #ffffff;
+  border: 1px solid #e2e8f0;
+  border-radius: 15px;
+  box-shadow: 0 6px 16px rgba(149, 157, 165, 0.15);
+  transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
+}
+
+/* 鎸夐挳+淇″彿 妯悜鎺掑垪瀹瑰櫒 - 鏍稿績甯冨眬 */
+.btn-signal-group {
+  display: flex;
+  align-items: center;
+  gap: 20px;
+  width: 100%;
+}
+.btn-group {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+  flex-shrink: 0;
+}
+.signal-status {
+  display: flex;
+  justify-content: flex-start;
+  align-items: center;
+  gap: 18px;
+  flex-shrink: 0;
+  padding: 0;
+}
+
+.left-top {
+  background: #f8fafc;
+  padding: 15px;
+  border-radius: 12px;
+  flex-shrink: 0;
+  width: 100%;
+}
+
+.left-bottom,
+.right-bottom {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  flex: 1;
+  overflow: hidden !important;
+}
+
+.right-top {
+  padding: 15px;
+  background: #f8fafc;
+  border-radius: 12px;
+  flex-shrink: 0;
+}
+
+/* 琛ㄥ崟琛屾牱寮� - 缁熶竴楂樺害鍜岄棿璺� 纭繚宸﹀彸瀵归綈 */
+.form-row {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  flex-wrap: nowrap;
+  padding: 6px 10px;
+  border-radius: 8px;
+  transition: all 0.2s ease;
+  height: 48px;
+  line-height: 48px;
+  flex-shrink: 0;
+  width: 100%;
+}
+
+.form-row:hover {
+  background: #f8fafc;
+}
+
+.finished-product-row {
+  background: #eff6ff;
+  border-left: 4px solid #3b82f6;
+}
+
+.tooling-board-row {
+  background: #f0fdf4;
+  border-left: 4px solid #22c55e;
+}
+
+.part-item {
+  border-bottom: 1px solid #f1f5f9;
+  margin-bottom: 3px;
+}
+
+.part-item:last-child {
+  border-bottom: none;
+}
+
+/* 鏍囩鏍峰紡 - 缁熶竴瀹藉害 纭繚宸﹀彸瀵归綈 */
+.label {
+  width: 90px;
+  text-align: right;
+  color: #334155;
+  font-size: 15px;
+  font-weight: 600;
+  flex-shrink: 0;
+}
+
+.short-label {
+  width: 110px;
+  text-align: right;
+  color: #334155;
+  font-size: 15px;
+  font-weight: 600;
+  flex-shrink: 0;
+}
+
+/* 杈撳叆妗嗘牱寮� - 缁熶竴灏哄 纭繚宸﹀彸瀵归綈 */
+.input-box {
+  flex: 1;
+  min-width: 100px;
+  height: 42px;
+  padding: 0 15px;
+  border: 1px solid #e2e8f0;
+  border-radius: 8px;
+  outline: none;
+  font-size: 15px;
+  transition: all 0.2s ease;
+  background-color: #ffffff;
+}
+
+.short-input {
+  width: 150px !important;
+  flex: none !important;
+}
+
+.input-box:focus {
+  border-color: #3b82f6;
+  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.12);
+}
+
+.input-box:disabled {
+  background-color: #f1f5f9;
+  color: #64748b;
+  cursor: not-allowed;
+}
+
+.input-box::placeholder {
+  color: #94a3b8;
+  font-size: 14px;
+}
+
+/* 鎸夐挳鏍峰紡 - 鍥哄畾瀹藉害 涓嶅彉 */
+.btn {
+  width: 120px;
+  height: 42px;
+  padding: 0 16px;
+  border: none;
+  border-radius: 8px;
+  cursor: pointer;
+  font-size: 14px;
+  font-weight: 600;
+  transition: all 0.2s ease;
+  flex-shrink: 0;
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+  position: relative;
+  overflow: hidden;
+}
+
+.btn:disabled {
+  opacity: 0.6;
+  cursor: not-allowed;
+  transform: none !important;
+  box-shadow: none !important;
+}
+
+.btn::after {
+  content: "";
+  position: absolute;
+  top: 0;
+  left: -100%;
+  width: 100%;
+  height: 100%;
+  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
+  transition: all 0.5s ease;
+}
+
+.btn:hover::after {
+  left: 100%;
+}
+
+/* 鍥炬爣鏍峰紡 - 鍥炬爣鏀惧ぇ */
+.icon {
+  display: inline-block;
+  width: 18px;
+  height: 18px;
+  background-size: contain;
+  background-repeat: no-repeat;
+  background-position: center;
+}
+
+.icon-start {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M8 5v14l11-7z'/%3E%3C/svg%3E");
+}
+
+.icon-stop {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M6 6h12v12H6z'/%3E%3C/svg%3E");
+}
+
+.icon-pause {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M6 19h4V5H6v14zm8-14v14h4V5h-4z'/%3E%3C/svg%3E");
+}
+
+.icon-resume {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M11 5L6 9H2v6h4l5 4V5zm7 0v14c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2z'/%3E%3C/svg%3E");
+}
+
+.icon-clear {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E");
+}
+
+.icon-submit {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M2.01 21L15 13.4 23 21V5H2.01V21zM17 15l-5-5-5 5V7h10v8z'/%3E%3C/svg%3E");
+}
+
+/* 鎸夐挳绫诲瀷鏍峰紡 - 涓嶅彉 */
+.start-btn {
+  background: linear-gradient(135deg, #10b981 0%, #059669 100%);
+  color: #fff;
+}
+.start-btn:hover:not(:disabled) {
+  transform: translateY(-1px);
+  box-shadow: 0 4px 8px rgba(16, 185, 129, 0.2);
+}
+
+.stop-btn {
+  background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
+  color: #fff;
+}
+.stop-btn:hover:not(:disabled) {
+  transform: translateY(-1px);
+  box-shadow: 0 4px 8px rgba(239, 68, 68, 0.2);
+}
+
+.pause-btn {
+  background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
+  color: #fff;
+}
+.pause-btn:hover:not(:disabled) {
+  transform: translateY(-1px);
+  box-shadow: 0 4px 8px rgba(245, 158, 11, 0.2);
+}
+
+.resume-btn {
+  background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
+  color: #fff;
+}
+.resume-btn:hover:not(:disabled) {
+  transform: translateY(-1px);
+  box-shadow: 0 4px 8px rgba(139, 92, 246, 0.2);
+}
+
+.clear-btn {
+  background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
+  color: #fff;
+  padding: 0 12px;
+}
+.clear-btn:hover:not(:disabled) {
+  transform: translateY(-1px);
+  box-shadow: 0 4px 8px rgba(239, 68, 68, 0.15);
+}
+
+.save-btn {
+  background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
+  color: #fff;
+  padding: 0 12px;
+}
+.save-btn:hover:not(:disabled) {
+  transform: translateY(-1px);
+  box-shadow: 0 4px 8px rgba(59, 130, 246, 0.15);
+}
+
+/* 淇″彿鐏牱寮� 閱掔洰鏀惧ぇ */
+.signal-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 5px;
+  min-width: 60px;
+}
+.signal-label {
+  font-size: 14px;
+  color: #334155;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  width: 100%;
+  text-align: center;
+  font-weight: 600;
+}
+.signal-light {
+  width: 32px;
+  height: 32px;
+  border-radius: 50%;
+  box-shadow: 0 3px 8px rgba(0, 0, 0, 0.15);
+  transition: all 0.3s ease;
+  position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.signal-light-inner {
+  width: 14px;
+  height: 14px;
+  border-radius: 50%;
+  background: white;
+  opacity: 0.9;
+  transition: all 0.3s ease;
+}
+.signal-active {
+  background: linear-gradient(135deg, #10b981 0%, #059669 100%);
+  box-shadow: 0 0 18px rgba(16, 185, 129, 0.7);
+  animation: pulse 2s infinite;
+}
+.signal-inactive {
+  background: linear-gradient(135deg, #94a3b8 0%, #64748b 100%);
+}
+@keyframes pulse {
+  0% {
+    box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.8);
+  }
+  70% {
+    box-shadow: 0 0 0 15px rgba(16, 185, 129, 0);
+  }
+  100% {
+    box-shadow: 0 0 0 0 rgba(16, 185, 129, 0);
+  }
+}
+
+/* 鑷畾涔夊閫夋鏍峰紡 - 鏀惧ぇ 鐐瑰嚮鍖哄煙鏇村ぇ */
+.checkbox-container {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  cursor: pointer;
+  font-size: 14px;
+  color: #334155;
+  flex-shrink: 0;
+}
+.part-checkbox {
+  display: none;
+}
+.checkmark {
+  width: 22px;
+  height: 22px;
+  background-color: #f1f5f9;
+  border: 1px solid #cbd5e1;
+  border-radius: 4px;
+  transition: all 0.2s ease;
+  position: relative;
+}
+.part-checkbox:checked ~ .checkmark {
+  background-color: #3b82f6;
+  border-color: #3b82f6;
+}
+.checkmark:after {
+  content: "";
+  position: absolute;
+  display: none;
+  left: 7px;
+  top: 2px;
+  width: 6px;
+  height: 12px;
+  border: solid white;
+  border-width: 0 3px 3px 0;
+  transform: rotate(45deg);
+}
+.part-checkbox:checked ~ .checkmark:after {
+  display: block;
+}
+.checkbox-label {
+  font-size: 13px;
+  color: #475569;
+  width: 50px;
+}
+
+/* 鍝嶅簲寮忛�傞厤 */
+@media (max-width: 1200px) {
+  .container {
+    flex-direction: column;
+    height: auto;
+  }
+  .left-area,
+  .right-area {
+    width: 100%;
+    flex: none;
+  }
+  .btn-signal-group {
+    flex-direction: column;
+    align-items: flex-start;
+  }
+  .signal-status {
+    width: 100%;
+    justify-content: flex-start;
+  }
+}
+
+@media (max-width: 768px) {
+  .form-row {
+    flex-direction: column;
+    align-items: flex-start;
+    height: auto;
+    line-height: normal;
+  }
+  .label,
+  .short-label {
+    width: 100%;
+    text-align: left;
+    margin-bottom: 6px;
+  }
+  .input-box {
+    width: 100%;
+  }
+  .short-input {
+    width: 100% !important;
+  }
+  .btn {
+    width: 100%;
+    margin-top: 6px;
+  }
+  .btn-group {
+    flex-direction: column;
+    gap: 10px;
+    width: 100%;
+  }
+}
+</style>

--
Gitblit v1.9.3