| | |
| | | <template> |
| | | <div class="title"></div> |
| | | <div class="container"> |
| | | <!-- 左侧åºå - ä»
ä¿çä¿¡å·ç¯+æå/é¶ä»¶åºå --> |
| | | <div class="left-area"> |
| | | <div class="left-top"> |
| | | <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 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)" |
| | | :disabled="checkLoading[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 input-submit-row"> |
| | | <span class="label">å½å
¥æ¡ï¼</span> |
| | | <input type="text" class="input-box" v-model="rightTopInput" /> |
| | | <button class="btn save-btn submit-input-btn" @click="saveData"> |
| | | <i class="icon icon-submit"></i>æäº¤ |
| | | </button> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="right-bottom"> |
| | | <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 { |
| | | // åºç¡æ°æ®å®ä¹ |
| | | const finishedProduct = ref(""); // 左侧æåç¼å·ï¼æ¥å£è¿åï¼ |
| | | const finishedProductId = ref(""); // 左侧æåIDï¼æ¥å£è¿åï¼ |
| | | const rightTopInput = ref(""); // å³ä¾§æ ¸å¿å½å
¥æ¡ |
| | | const leftPartCodes = ref(Array(10).fill("")); // 左侧é¶ä»¶ç¼å·æ°ç» |
| | | const rightPartCodes = ref(Array(10).fill("")); // å³ä¾§é¶ä»¶ç¼å·æ°ç» |
| | | const leftPartChecked = ref(Array(10).fill(false)); // 左侧é¶ä»¶å¾éç¶æ |
| | | const leftPartIds = ref(Array(10).fill("")); // 左侧é¶ä»¶IDï¼æ¥å£è¿åï¼ |
| | | const finishedProductCode = ref(""); // å³ä¾§æåç¼å·è¾å
¥æ¡ |
| | | const fillIndex = ref(-1); // å½å
¥æ¡èªå¨å¡«å
ç´¢å¼ |
| | | const checkLoading = ref(Array(10).fill(false)); // é¶ä»¶å¾éå è½½é |
| | | |
| | | } |
| | | } |
| | | } |
| | | // ä¿¡å·ç¯ç¸å
³ |
| | | const signalStates = ref([false, false, false, false, false]); |
| | | const signalLabels = ref([ |
| | | "å¿è·³ä¿¡å·", |
| | | "æ¥åä¿¡å·", |
| | | "èªå¨è¿è¡ä¿¡å·", |
| | | "å¨çº¿æ¨¡å¼ä¿¡å·", |
| | | "æ
éä¿¡å·", |
| | | ]); |
| | | |
| | | // 宿¶/鲿ç¸å
³åé |
| | | let pollingTimer = null; |
| | | const pollingInterval = 5000; // ä¿¡å·ç¯è½®è¯¢é´é5ç§ |
| | | let checkDebounceTimer = null; |
| | | let destroyDelayTimer = null; |
| | | const destroyDelayTime = 500; // å½å
¥æ¡å¡«å
å»¶è¿500ms |
| | | let autoSubmitDebounceTimer = null; |
| | | let submitLock = ref(false); // æäº¤éï¼é²æ¢éå¤æäº¤ |
| | | |
| | | // 计ç®å±æ§ - ç»è®¡ææå¾é/å¡«å
æ°éï¼è¿æ»¤ç©ºå¼ï¼ |
| | | const checkedCount = computed(() => { |
| | | return leftPartChecked.value.filter((checked) => checked === true).length; |
| | | }); |
| | | const filledPartCount = computed(() => { |
| | | return rightPartCodes.value.filter((code) => code.trim() !== "").length; |
| | | }); |
| | | |
| | | // è·å左侧åå§æ°æ®ï¼æå+é¶ä»¶+å¾éç¶æï¼ |
| | | const fetchLeftInitialData = async () => { |
| | | try { |
| | | console.log("æ£å¨è·å左侧åå§æ°æ®..."); |
| | | 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); |
| | | } |
| | | }; |
| | | |
| | | // è·åä¿¡å·ç¯ç¶æï¼å®æ¶è½®è¯¢ï¼ |
| | | 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; |
| | | } |
| | | } catch (error) { |
| | | console.error("è·åä¿¡å·ç¶æå¤±è´¥ï¼", error); |
| | | } |
| | | }; |
| | | |
| | | // å¯å¨/åæ¢ä¿¡å·ç¯è½®è¯¢ |
| | | const startPolling = () => { |
| | | if (pollingTimer) clearInterval(pollingTimer); |
| | | fetchSignalAndPLCStates(); |
| | | pollingTimer = setInterval(fetchSignalAndPLCStates, pollingInterval); |
| | | }; |
| | | const stopPolling = () => { |
| | | if (pollingTimer) clearInterval(pollingTimer); |
| | | pollingTimer = null; |
| | | }; |
| | | |
| | | // 左侧é¶ä»¶å¾éç¶æåæ´å¤çï¼æ´æ°åç«¯ç¶æï¼ |
| | | 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}æ°æ®åºIDä¸åå¨ï¼æ æ³æ´æ°æ«ç ç¶æï¼`); |
| | | leftPartChecked.value[index] = !isChecked; |
| | | return; |
| | | } |
| | | if (!partCode.trim()) { |
| | | alert(`é¶ä»¶${index + 1}ç¼å·ä¸ºç©ºï¼æ æ³æ´æ°æ«ç ç¶æï¼`); |
| | | leftPartChecked.value[index] = !isChecked; |
| | | return; |
| | | } |
| | | |
| | | checkLoading.value[index] = true; |
| | | 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}æ«ç ç¶ææ´æ°æå`); |
| | | } 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 { |
| | | checkLoading.value[index] = false; |
| | | if (checkDebounceTimer) clearTimeout(checkDebounceTimer); |
| | | checkDebounceTimer = null; |
| | | } |
| | | }; |
| | | |
| | | // å³ä¾§å½å
¥æ¡å»¶è¿å¡«å
é»è¾ï¼æåâé¶ä»¶ä¾æ¬¡å¡«å
ï¼ |
| | | const fillContent = () => { |
| | | if (!rightTopInput.value.trim()) return; |
| | | const inputValue = rightTopInput.value.trim(); |
| | | |
| | | if (destroyDelayTimer) clearTimeout(destroyDelayTimer); |
| | | destroyDelayTimer = setTimeout(() => { |
| | | 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 } |
| | | ); |
| | | |
| | | // å³ä¾§è¾å
¥æ¡æ¸
餿¹æ³ |
| | | const clearRightPart = (index) => (rightPartCodes.value[index] = ""); |
| | | const clearFinishedProductCode = () => (finishedProductCode.value = ""); |
| | | |
| | | // æ ¸å¿ä¿®æ¹ï¼æ¢å¤æåç¼å·å¿
å¡«æ ¡éªï¼æªå¡«åç´æ¥æç¤ºå¹¶ç»æ¢æäº¤ |
| | | const saveData = async () => { |
| | | // 1. æåç¼å·å¿
å¡«æ ¡éªãæ ¸å¿æ°å¢ã |
| | | const productCode = finishedProductCode.value.trim(); |
| | | if (!productCode) { |
| | | alert("请å
å¡«åæåç¼å·ï¼æåç¼å·ä¸ºå¿
填项ï¼"); |
| | | return; |
| | | } |
| | | // 2. æäº¤éï¼é²æ¢éå¤ç¹å» |
| | | if (submitLock.value) return; |
| | | submitLock.value = true; |
| | | |
| | | try { |
| | | // 3. ä¼ åéé
ï¼å端Dtoæ¯å¤§é©¼å³°å段ï¼å¿
é¡»ä¸¥æ ¼å¹é
ï¼FinishedProductCode/PartsListï¼ |
| | | const submitData = { |
| | | FinishedProductCode: productCode, // ç´æ¥ä½¿ç¨å·²æ ¡éªçéç©ºå¼ |
| | | PartsList: rightPartCodes.value.map((item) => item.trim()), |
| | | }; |
| | | console.log("ð¤ æäº¤å°SaveToolingBoardNoçåæ°ï¼å¹é
å端Dtoï¼ï¼", submitData); |
| | | |
| | | // 4. è°ç¨å端æ¥å£ï¼å»¶é¿è¶
æ¶æ¶é´ï¼å端æPLC交äºï¼500msä¼ç ï¼è®¾ä¸º10ç§ï¼ |
| | | const response = await axios.post( |
| | | "/api/boxingDetail/SaveToolingBoardNo", |
| | | submitData, |
| | | { timeout: 10000 } |
| | | ); |
| | | const resData = response.data; |
| | | console.log("ð¥ SaveToolingBoardNoæ¥å£è¿åï¼", resData); |
| | | |
| | | // 5. éé
å端è¿åæ ¼å¼ï¼status为true表示æåï¼å¦ååmessageéè¯¯ä¿¡æ¯ |
| | | if (resData.status === true) { |
| | | finishedProductCode.value = ""; |
| | | rightPartCodes.value = Array(10).fill(""); |
| | | rightTopInput.value = ""; |
| | | fillIndex.value = -1; |
| | | } else { |
| | | // ä¸å¡é误ï¼ç´æ¥å±ç¤ºå端è¿åçmessageï¼å¦ç©æéè¯¯ãæ é
æ¹ï¼ |
| | | const errorMsg = resData.message || "æäº¤å¤±è´¥ï¼æªç¥ä¸å¡é误"; |
| | | alert(`æäº¤å¤±è´¥ï¼${errorMsg}`); |
| | | } |
| | | } catch (error) { |
| | | // 6. å¼å¸¸æè·ï¼å
¼å®¹åç«¯æªæè·å¼å¸¸ï¼å¦PLCé讯å¼å¸¸ãæ°ç»è¶çã500éè¯¯ï¼ |
| | | let errorMsg = "æäº¤è¯·æ±å¼å¸¸ï¼"; |
| | | if (error.code === "ECONNABORTED") { |
| | | errorMsg = "æäº¤è¯·æ±è¶
æ¶ï¼å端PLC交äº/æ°æ®åºæä½èæ¶è¿é¿"; |
| | | } else if (error.response) { |
| | | // æå¡å¨500é误ï¼å端æåºæªæè·å¼å¸¸ï¼å¦PLCæªè¿æ¥ãé¶ä»¶ä¸å¹é
ï¼ |
| | | errorMsg = `æå¡å¨é误ï¼${error.response.status} - ${error.response.statusText}ï¼è¯·æ£æ¥PLCé讯æé
æ¹é
ç½®`; |
| | | console.error("â å端æå¡å¨é误详æ
ï¼", error.response.data); |
| | | } else if (error.request) { |
| | | errorMsg = "ç½ç»å¼å¸¸ï¼æªæ¶å°å端ååºï¼è¯·æ£æ¥æ¥å£å°ååç½ç»"; |
| | | } else { |
| | | errorMsg = `请æ±é误ï¼${error.message}`; |
| | | } |
| | | alert(errorMsg); |
| | | console.error("â æäº¤æ¥å£å¼å¸¸è¯¦æ
ï¼", error); |
| | | } finally { |
| | | // 7. éæ¾æäº¤éï¼æ 论æå/失败é½è¦éæ¾ |
| | | submitLock.value = false; |
| | | } |
| | | }; |
| | | |
| | | // æ ¸å¿ä¿®æ¹ï¼èªå¨æäº¤é»è¾åæ¥å¢å æåç¼å·å¿
å¡«æ ¡éªï¼æ æåç¼å·ä¸è§¦åèªå¨æäº¤ï¼ |
| | | const checkAutoSubmit = () => { |
| | | if (autoSubmitDebounceTimer) clearTimeout(autoSubmitDebounceTimer); |
| | | autoSubmitDebounceTimer = setTimeout(() => { |
| | | const needCheckNum = checkedCount.value; // 左侧å¾éé¶ä»¶æ° |
| | | const filledNum = filledPartCount.value; // å³ä¾§ææå¡«å
é¶ä»¶æ° |
| | | const productCode = finishedProductCode.value.trim(); // æåç¼å·ï¼å»ç©ºæ ¼ï¼ |
| | | const hasProductCode = !!productCode; // æåç¼å·æ¯å¦ææãä¿çåé»è¾ï¼å®é
æ¯å¿
å¡«æ ¡éªã |
| | | |
| | | console.log( |
| | | `â
èªå¨æäº¤æ ¡éªï¼å¾é${needCheckNum}个 | å¡«å
${filledNum}个 | æå已填${hasProductCode}` |
| | | ); |
| | | |
| | | // åç½®æ»æ ¡éªï¼æåç¼å·æªå¡«åï¼ç´æ¥ä¸è§¦åä»»ä½èªå¨æäº¤ãæ ¸å¿å¼ºåã |
| | | if (!hasProductCode) { |
| | | console.log("â ï¸ æåç¼å·æªå¡«åï¼è·³è¿èªå¨æäº¤"); |
| | | autoSubmitDebounceTimer = null; |
| | | return; |
| | | } |
| | | |
| | | // åºæ¯1ï¼å·¦ä¾§æ å¾é â ä»
æåç¼å·ææå³æäº¤ï¼å·²æ»¡è¶³hasProductCodeï¼ |
| | | if (needCheckNum === 0) { |
| | | if (!submitLock.value) { |
| | | console.log("â
æ é¶ä»¶å¾éï¼æåå·²å¡«ï¼æ§è¡èªå¨æäº¤ï¼"); |
| | | saveData(); |
| | | } |
| | | } |
| | | // åºæ¯2ï¼å·¦ä¾§æå¾é â æåææ + å¡«å
æ°=å¾éæ° ææäº¤ï¼å·²æ»¡è¶³hasProductCodeï¼ |
| | | else { |
| | | if (filledNum === needCheckNum && !submitLock.value) { |
| | | console.log("â
é¶ä»¶æ°éå¹é
ï¼æåå·²å¡«ï¼æ§è¡èªå¨æäº¤ï¼"); |
| | | saveData(); |
| | | } |
| | | } |
| | | autoSubmitDebounceTimer = null; |
| | | }, 300); // 300ms鲿ï¼é¿å
è¾å
¥é¢ç¹è§¦å |
| | | }; |
| | | |
| | | // çå¬èªå¨æäº¤ç¸å
³æ°æ®ååï¼è§¦åæ ¡éª |
| | | watch([checkedCount, filledPartCount, finishedProductCode], () => checkAutoSubmit(), { |
| | | deep: true, |
| | | immediate: false, |
| | | }); |
| | | |
| | | // èªå¨æ£æµå¡«å
ç´¢å¼ï¼æåå¡«å®åï¼æå第ä¸ä¸ªç©ºé¶ä»¶æ¡ï¼ |
| | | const detectFillIndex = () => { |
| | | if (!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([finishedProductCode, () => [...rightPartCodes.value]], detectFillIndex, { |
| | | immediate: true, |
| | | deep: true, |
| | | }); |
| | | |
| | | // çå½å¨æï¼æè½½æ¶å è½½åå§æ°æ®+å¯å¨è½®è¯¢ |
| | | onMounted(async () => { |
| | | await fetchLeftInitialData(); |
| | | startPolling(); |
| | | detectFillIndex(); |
| | | }); |
| | | |
| | | // çå½å¨æï¼å¸è½½æ¶æ¸
餿æå®æ¶å¨/鲿ï¼é²æ¢å
åæ³æ¼ |
| | | onUnmounted(() => { |
| | | stopPolling(); |
| | | [checkDebounceTimer, destroyDelayTimer, autoSubmitDebounceTimer].forEach( |
| | | (t) => t && clearTimeout(t) |
| | | ); |
| | | }); |
| | | |
| | | // æ´é²æ¨¡æ¿æé屿§/æ¹æ³ |
| | | return { |
| | | finishedProduct, |
| | | rightTopInput, |
| | | leftPartCodes, |
| | | rightPartCodes, |
| | | leftPartChecked, |
| | | signalStates, |
| | | signalLabels, |
| | | finishedProductCode, |
| | | checkLoading, |
| | | handlePartCheck, |
| | | saveData, |
| | | 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", sans-serif; |
| | | scrollbar-width: none; |
| | | -ms-overflow-style: none; |
| | | } |
| | | </style> |
| | | *::-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; |
| | | 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); |
| | | } |
| | | |
| | | /* 左侧顶é¨-ä¿¡å·ç¯åºå */ |
| | | .left-top { |
| | | background: #f8fafc; |
| | | padding: 20px 15px; |
| | | border-radius: 12px; |
| | | flex-shrink: 0; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | .signal-status { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | gap: 25px; |
| | | 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%; |
| | | } |
| | | .input-submit-row { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | 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; |
| | | } |
| | | |
| | | /* æåç¼å·è¡æ ·å¼ */ |
| | | .right-bottom .finished-product-row { |
| | | margin-top: 16px; |
| | | background: #eff6ff; |
| | | border-left: 4px solid #3b82f6; |
| | | padding-top: 2px; |
| | | padding-bottom: 2px; |
| | | } |
| | | .left-bottom .finished-product-row { |
| | | background: #eff6ff; |
| | | border-left: 4px solid #3b82f6; |
| | | } |
| | | |
| | | /* é¶ä»¶é¡¹æ ·å¼ */ |
| | | .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; |
| | | } |
| | | |
| | | /* è¾å
¥æ¡æ ·å¼ */ |
| | | .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; |
| | | } |
| | | .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; |
| | | } |
| | | .submit-input-btn { |
| | | width: 110px !important; |
| | | height: 42px !important; |
| | | flex: none !important; |
| | | padding: 0 15px !important; |
| | | font-size: 15px !important; |
| | | } |
| | | .submit-input-btn .icon { |
| | | width: 20px !important; |
| | | height: 20px !important; |
| | | } |
| | | .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-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"); |
| | | } |
| | | |
| | | /* æé®ä¸»é¢æ ·å¼ */ |
| | | .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; |
| | | 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; |
| | | } |
| | | .checkbox-container:has(.part-checkbox:disabled) { |
| | | opacity: 0.6; |
| | | cursor: not-allowed; |
| | | } |
| | | .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; |
| | | } |
| | | .signal-status { |
| | | flex-wrap: wrap; |
| | | justify-content: center; |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .form-row, |
| | | .input-submit-row { |
| | | flex-direction: column; |
| | | align-items: flex-start; |
| | | height: auto; |
| | | line-height: normal; |
| | | } |
| | | .label { |
| | | width: 100%; |
| | | text-align: left; |
| | | margin-bottom: 6px; |
| | | } |
| | | .input-box { |
| | | width: 100%; |
| | | } |
| | | .btn, |
| | | .submit-input-btn { |
| | | width: 100% !important; |
| | | margin-top: 6px; |
| | | } |
| | | .right-bottom .finished-product-row { |
| | | margin-top: 0 !important; |
| | | } |
| | | } |
| | | </style> |