// ParkingRecorder 小组件(巨大字体 WebUI 弹窗版 - 修复 API 报错) // 数据源: // 未停车 → {"key":"ParkingRecorder","val":"null"} // 已停车 → {"key":"ParkingRecorder","val":"YYYY-MM-DD HH:mm:ss"} const BOXJS_URL = "https://boxjs.com/query/data/ParkingRecorder"; // ⏱ 把后台刷新时间拉长到 120 分钟 (2小时),极限省电 const REFRESH_INTERVAL_MINUTES = 120; // Theme Colors const THEME = { bg1: "#1a1a1a", bg2: "#000000", idle: "#00F0FF", active: "#28A745", textMain: "#FFFFFF", textDim: "#888888", danger: "#FF453A" }; async function fetchParkingState() { try { const req = new Request(BOXJS_URL); req.timeoutInterval = 5; const json = await req.loadJSON(); const val = json?.val ?? "null"; if (val === "null") return createEmptyState(); if (typeof val !== "string" || val.length !== 19) return createEmptyState(); const parsed = parseTimeString(val); if (!parsed) return createEmptyState(); const durationText = buildDurationText(parsed, new Date()); return { isParked: true, rawTime: val, startDate: parsed, durationText, lastUpdated: new Date(), }; } catch (e) { console.log("Fetch error:", e); return createEmptyState(); } } function createEmptyState() { return { isParked: false, rawTime: "", startDate: null, durationText: "", lastUpdated: new Date(), }; } // 解析 "YYYY-MM-DD HH:mm:ss" 为 Date function parseTimeString(str) { try { const [datePart, timePart] = str.split(" "); if (!datePart || !timePart) return null; const [y, m, d] = datePart.split("-").map(Number); const [hh, mm, ss] = timePart.split(":").map(Number); if ([y, m, d, hh, mm, ss].some(n => Number.isNaN(n))) return null; return new Date(y, m - 1, d, hh, mm, ss); } catch (e) { return null; } } // 生成“已停车 X天X小时X分” function buildDurationText(start, now) { let diffMs = now - start; if (diffMs < 0) diffMs = 0; const minuteMs = 60 * 1000; const hourMs = 60 * minuteMs; const dayMs = 24 * hourMs; const days = Math.floor(diffMs / dayMs); diffMs %= dayMs; const hours = Math.floor(diffMs / hourMs); diffMs %= hourMs; const mins = Math.floor(diffMs / minuteMs); let parts = []; if (days > 0) parts.push(`${days}天 `); if (hours > 0) parts.push(`${hours}小时 `); parts.push(`${mins}分`); return parts.join(""); } // 渐变背景 function makeBackground(widget, isParked) { const gradient = new LinearGradient(); gradient.colors = isParked ? [new Color("#0f2027"), new Color("#203a43"), new Color("#2c5364")] : [new Color("#232526"), new Color("#414345")]; gradient.locations = [0, 1]; widget.backgroundGradient = gradient; } // 小组件 function buildSmallWidget(state) { const w = new ListWidget(); w.setPadding(12, 12, 12, 12); makeBackground(w, state.isParked); const mainStack = w.addStack(); mainStack.layoutVertically(); mainStack.centerAlignContent(); const status = mainStack.addText(state.isParked ? "停车中" : "未停车"); status.font = Font.boldSystemFont(18); status.textColor = new Color(THEME.textMain); w.addSpacer(8); if (state.isParked) { const timeText = w.addText("已停车 " + state.durationText.replace(/\s/g, '')); timeText.font = Font.boldSystemFont(22); timeText.textColor = new Color(THEME.active); } else { const idleMsg = w.addText("当前没有停车记录"); idleMsg.font = Font.mediumSystemFont(12); idleMsg.textColor = new Color(THEME.textDim); } w.addSpacer(8); const lastUpdated = w.addText(`点击查看精准时间`); lastUpdated.font = Font.italicSystemFont(10); lastUpdated.textColor = new Color("#00F0FF"); w.refreshAfterDate = new Date(Date.now() + REFRESH_INTERVAL_MINUTES * 60 * 1000); return w; } // 中号组件 function buildMediumWidget(state) { const w = new ListWidget(); w.setPadding(16, 16, 16, 16); makeBackground(w, state.isParked); const mainStack = w.addStack(); mainStack.layoutHorizontally(); const left = mainStack.addStack(); left.layoutVertically(); left.centerAlignContent(); const header = left.addText(state.isParked ? "停车中" : "未停车"); header.font = Font.boldSystemFont(20); header.textColor = new Color(THEME.textMain); left.addSpacer(8); if (state.isParked) { const timeText = left.addText("已停车 " + state.durationText.replace(/\s/g, '')); timeText.font = Font.boldSystemFont(28); timeText.textColor = new Color(THEME.active); left.addSpacer(8); const startLabel = left.addText(`开始:${state.rawTime}`); startLabel.font = Font.regularSystemFont(10); startLabel.textColor = new Color(THEME.textDim); } else { const idleMsg = left.addText("当前没有停车记录"); idleMsg.font = Font.mediumSystemFont(12); idleMsg.textColor = new Color(THEME.textDim); } const right = mainStack.addStack(); right.layoutVertically(); right.size = new Size(80, 0); right.centerAlignContent(); const iconStack = right.addStack(); iconStack.setPadding(10, 10, 10, 10); iconStack.cornerRadius = 30; iconStack.backgroundColor = new Color( state.isParked ? THEME.active : THEME.idle, 0.1 ); const icon = iconStack.addText(state.isParked ? "⏱" : "P"); icon.font = Font.systemFont(30); right.addSpacer(12); const refTime = right.addText(`点击看实况`); refTime.font = Font.italicSystemFont(10); refTime.textColor = new Color("#00F0FF"); return w; } // ========================================== // 🎨 用 HTML/CSS 渲染巨大字体的弹窗 // ========================================== async function presentHugeTextUI(state) { let html = `