(function(){ const canvas = document.getElementById('unifiedCanvas'); const ctx = canvas.getContext('2d'); // 获取当前选中的屏幕尺寸 const screenSelect = document.getElementById('epdwh'); let W = 800, H = 480; function updateCanvasSize() { const [newW, newH] = screenSelect.value.split('x').map(Number); W = newW; H = newH; canvas.width = W; canvas.height = H; } // 初始化尺寸 updateCanvasSize(); let currentTemplate = 'schedule'; let globalTitle = "◉ 输入内容·页眉"; let presetLinesData = []; let hitAreas = []; let courseData = { days: ["周一", "周二", "周三", "周四", "周五"], periods: ["第1节", "第2节", "第3节", "第4节"], cells: {} }; function initDefaultCourse() { courseData.cells = {}; const defaults = { "0,0": "语文", "0,1": "数学", "0,2": "英语", "0,3": "体育", "1,0": "数学", "1,1": "语文", "1,2": "物理", "1,3": "历史", "2,0": "英语", "2,1": "化学", "2,2": "语文", "2,3": "自习", "3,0": "物理", "3,1": "英语", "3,2": "数学", "3,3": "政治", "4,0": "历史", "4,1": "体育", "4,2": "化学", "4,3": "班会" }; for(let k in defaults) courseData.cells[k] = defaults[k]; } initDefaultCourse(); let courseHitAreas = []; function applyCourseCustomSettings() { const dayCount = parseInt(document.getElementById('courseDaysCount').value) || 5; const periodCount = parseInt(document.getElementById('coursePeriodsCount').value) || 4; const dayNames = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]; courseData.days = dayNames.slice(0, dayCount); courseData.periods = []; for(let i=1; i<=periodCount; i++) { courseData.periods.push(`第${i}节`); } courseData.cells = {}; render(); } let qrText = "https://mspppclock.tech/"; let qrSize = 150; let qrCodeImage = null; let qrCaptionLines = []; let qrCaptionStyle = { fontSize: 14, fontFamily: "微软雅黑", baseColor: "#000000", invertMode: "none", alignment: "center" }; function syncCaptionFromTextarea() { const raw = document.getElementById('qrCaptionText').value; const lines = raw.split(/\r?\n/).filter(l => l.trim() !== "" || l === ""); qrCaptionLines = lines.length ? lines : ["(点击添加文字)"]; } function updateCaptionLinesToUI() { document.getElementById('qrCaptionText').value = qrCaptionLines.join("\n"); } async function generateQRCodeImage(text, size=90, colorDark="#000000", colorLight="#FFFFFF") { return new Promise((resolve) => { const tempDiv = document.createElement('div'); tempDiv.style.position = 'absolute'; tempDiv.style.left = '-9999px'; document.body.appendChild(tempDiv); try { const qrcode = new QRCode(tempDiv, { text: text, width: size, height: size, colorDark: colorDark, colorLight: colorLight, correctLevel: QRCode.CorrectLevel.M }); setTimeout(() => { const canvasQR = tempDiv.querySelector('canvas'); if (canvasQR) { const imgData = canvasQR.toDataURL(); resolve(imgData); } else resolve(null); document.body.removeChild(tempDiv); }, 60); } catch(e) { resolve(null); document.body.removeChild(tempDiv); } }); } async function refreshQRImage() { qrSize = parseInt(document.getElementById('qrSizeInput').value) || 84; const img = await generateQRCodeImage(qrText, qrSize, "#000", "#fff"); qrCodeImage = img; if(currentTemplate === 'qrcode') render(); } let customRawText = "小雨在放学路上捡到一枚【闪闪发亮的硬币】。\n她买了两颗【橘子味的糖果】,一颗塞进嘴里,\n甜得她眯起了眼睛;另一颗放在路边的长椅上。\n一只【唱歌的小鸟】飞过来,歪着脑袋看了看\n,叼起糖果飞走了。小雨有点失落,\n但还是继续往家走。第二天早上,她推开窗户,\n发现窗台上放着一朵【金黄色的小野花】,花瓣上还挂着露珠。小雨捧着花笑了:\n“原来快乐会【越分越多】呀。”\n从那天起,她每天都往长椅上放一颗糖。"; let customStyle = { fontSize: 17, fontFamily: "宋体", baseColor: "#000000", invertMode: "none", alignment: "center" }; const chineseFonts = ["宋体","微软雅黑","黑体","楷体","思源黑体","苹方"]; const invertModes = [ { value: "none", label: "无" }, { value: "red-black", label: "红字黑底" }, { value: "red-white", label: "红字白底" }, { value: "black-red", label: "黑字红底" }, { value: "black-white", label: "黑字白底" }, { value: "white-red", label: "白字红底" }, { value: "white-black", label: "白字黑底" } ]; function applyInvertStyle(baseColor, invertMode) { if (invertMode === "none") return { bgColor: null, textColor: baseColor }; if (invertMode === "red-black") return { bgColor: "#000000", textColor: "#FF0000" }; if (invertMode === "red-white") return { bgColor: "#FFFFFF", textColor: "#FF0000" }; if (invertMode === "black-red") return { bgColor: "#FF0000", textColor: "#000000" }; if (invertMode === "black-white") return { bgColor: "#FFFFFF", textColor: "#000000" }; if (invertMode === "white-red") return { bgColor: "#FF0000", textColor: "#FFFFFF" }; if (invertMode === "white-black") return { bgColor: "#000000", textColor: "#FFFFFF" }; return { bgColor: null, textColor: baseColor }; } const templatePresets = { schedule: [ { text: "✅ 今日核心议程", fontSize: 24, fontFamily: "微软雅黑", baseColor: "#FF0000", invertMode: "none", alignment: "center" }, { text: "【09:30】 敏捷站会 · 【需求对齐】", fontSize: 18,fontFamily: "微软雅黑", baseColor: "#000000", invertMode: "none", alignment: "left" }, { text: "11:00 前端架构评审会议", fontSize: 18, fontFamily: "黑体", baseColor: "#000000", invertMode: "black-red", alignment: "left" }, { text: "14:30 跨部门协作同步会", fontSize: 18, fontFamily: "微软雅黑", baseColor: "#FF0000", invertMode: "red-white", alignment: "left" }, { text: "16:45 代码审查 + 项目复盘", fontSize: 18, fontFamily: "楷体", baseColor: "#000000", invertMode: "none", alignment: "left" } ], businesscard: [ { text: "✦ 名 片 精 选 ✦", fontSize: 24, fontFamily: "微软雅黑", baseColor: "#FF0000", invertMode: "none", alignment: "center" }, { text: "欧阳 静澜", fontSize: 26, fontFamily: "黑体", baseColor: "#000000", invertMode: "black-white", alignment: "center" }, { text: "创意设计总监 / 合伙人", fontSize: 18, fontFamily: "微软雅黑", baseColor: "#000000", invertMode: "none", alignment: "center" }, { text: "jinglan@art.cn", fontSize: 17, fontFamily: "宋体", baseColor: "#FF0000", invertMode: "red-black", alignment: "center" }, { text: "三色哲学 · 红黑白", fontSize: 16, fontFamily: "微软雅黑", baseColor: "#000000", invertMode: "none", alignment: "center" } ], memo: [ { text: "📋 紧 急 备 忘 录", fontSize: 28, fontFamily: "微软雅黑", baseColor: "#FF0000", invertMode: "none", alignment: "center" }, { text: "• 季度总结报告 (周五截止)", fontSize: 24, fontFamily: "微软雅黑", baseColor: "#000000", invertMode: "black-red", alignment: "left" }, { text: "• 设计评审会议 14:00", fontSize: 24, fontFamily: "宋体", baseColor: "#000000", invertMode: "none", alignment: "left" }, { text: "• 前端组件库升级计划", fontSize: 24, fontFamily: "黑体", baseColor: "#FF0000", invertMode: "red-white", alignment: "left" }, { text: "• 团队建设活动策划", fontSize: 23, fontFamily: "微软雅黑", baseColor: "#000000", invertMode: "none", alignment: "left" } ] }; function loadPresetData(templateId) { if(templatePresets[templateId]) presetLinesData = JSON.parse(JSON.stringify(templatePresets[templateId])); else presetLinesData = [{ text: "示例行", fontSize: 16, fontFamily: "微软雅黑", baseColor: "#000000", invertMode: "none", alignment: "left" }]; } function renderCourseTable() { ctx.fillStyle = "#FFFFFF"; ctx.fillRect(0, 0, W, H); const isSmallScreen = (W === 296 && H === 128); const isLargeScreen = (W === 800 && H === 480); if (!isSmallScreen) { // 显示页眉 const headerFontSize = isLargeScreen ? 36 : 18; ctx.strokeStyle = "#000000"; ctx.lineWidth = 2; ctx.strokeRect(4, 4, W-8, H-8); ctx.fillStyle = "#FF0000"; ctx.fillRect(8, 8, 26, 4); ctx.fillStyle = "#000000"; ctx.fillRect(W-54, 12, 42, 4); let title = globalTitle.trim() || "输入学校班级学期等信息"; // 页眉标题 ctx.font = `bold ${headerFontSize}px '微软雅黑'`; ctx.fillStyle = "#FF0000"; ctx.fillText(title, 20, headerFontSize + 12); ctx.beginPath(); ctx.strokeStyle = "#FF0000"; ctx.moveTo(12, headerFontSize + 22); ctx.lineTo(W-12, headerFontSize + 22); ctx.stroke(); var startY = headerFontSize + 30; } else { ctx.strokeStyle = "#FF0000"; ctx.lineWidth = 2; ctx.strokeRect(4, 4, W-8, H-8); var startY = 8; } const dayCount = courseData.days.length; const periodCount = courseData.periods.length; const startX = 12; const cellW = (W - 24) / (dayCount + 1); const cellH = (H - startY - 8) / (periodCount + 1); const headerTextFontSize = isLargeScreen ? 30 : 15; const cellTextFontSize = isLargeScreen ? 28 : (isSmallScreen ? 11 : 14); // 绘制节次表头 ctx.font = `bold ${headerTextFontSize}px '微软雅黑'`; ctx.fillStyle = "#000000"; ctx.fillRect(startX, startY, cellW, cellH); ctx.fillStyle = "#FF0000"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText("节次", startX + cellW/2, startY + cellH/2); // 绘制星期表头 for(let d=0; d maxChars ? courseName.slice(0, maxChars-1) + "…" : courseName; // 如果文字仍然太宽,进一步缩小字体 let tempFontSize = finalFontSize; let textWidth = ctx.measureText(displayName).width; while (textWidth > cellW - 8 && tempFontSize > 10) { tempFontSize--; ctx.font = `${tempFontSize}px "${cellStyle.fontFamily}"`; textWidth = ctx.measureText(displayName).width; } ctx.fillText(displayName, x + cellW/2, y + cellH/2); courseHitAreas.push({ row: p, col: d, x, y, w: cellW, h: cellH, cellKey, currentName: courseName }); } } if (!isSmallScreen) { // 底部装饰线 ctx.fillStyle = "#000000"; ctx.fillRect(16, H-11, 42, 2); ctx.fillStyle = "#FF0000"; ctx.fillRect(66, H-13, 26, 2); } ctx.textAlign = "left"; ctx.textBaseline = "alphabetic"; ctx.beginPath(); ctx.strokeStyle = "#ff0000"; ctx.lineWidth = 1; ctx.setLineDash([]); for(let d = 1; d <= dayCount; d++){ ctx.beginPath(); ctx.moveTo(startX + d * cellW, startY); ctx.lineTo(startX + d * cellW, startY + (periodCount + 1) * cellH); ctx.stroke(); } for(let p = 1; p <= periodCount; p++){ ctx.beginPath(); ctx.moveTo(startX, startY + p * cellH); ctx.lineTo(startX + (dayCount + 1) * cellW, startY + p * cellH); ctx.stroke(); } } function editCourseCell(row, col, currentName) { const cellKey = `${col},${row}`; const existingStyle = courseData.cellStyles?.[cellKey] || { fontSize: 16, fontFamily: "微软雅黑", color: "#000000" }; const modal = document.createElement('div'); modal.className = 'modal-mask'; modal.innerHTML = ` `; document.body.appendChild(modal); modal.querySelector('#confirmBtn').onclick = () => { const newName = modal.querySelector('#courseNameInput').value.trim() || "自习"; const newFontSize = parseInt(modal.querySelector('#fontSizeInput').value) || 16; const newFontFamily = modal.querySelector('#fontFamilyInput').value; const newColor = modal.querySelector('#colorInput').value; courseData.cells[cellKey] = newName; if (!courseData.cellStyles) courseData.cellStyles = {}; courseData.cellStyles[cellKey] = { fontSize: Math.min(48, Math.max(10, newFontSize)), fontFamily: newFontFamily, color: newColor }; modal.remove(); render(); }; modal.querySelector('.cancel').onclick = () => modal.remove(); modal.onclick = e => e.target === modal && modal.remove(); } function renderPresetTemplate() { ctx.fillStyle = "#ffffffff"; ctx.fillRect(0, 0, W, H); const isSmallScreen = (W === 296 && H === 128); const isLargeScreen = (W === 800 && H === 480); if (!isSmallScreen) { ctx.strokeStyle = "#FF0000"; ctx.lineWidth = 2; ctx.strokeRect(4, 4, W-8, H-8); ctx.fillStyle = "#FF0000"; ctx.fillRect(8, 8, 26, 4); ctx.fillStyle = "#000000"; ctx.fillRect(W-54, 12, 42, 4); let titleText = globalTitle.trim() || "输入内容·页眉"; let headerFontSize = isLargeScreen ? 52 : 26; ctx.font = `bold ${headerFontSize}px '微软雅黑'`; let titleWidth = ctx.measureText(titleText.slice(0,24)).width; let bgX = (W - (titleWidth+40))/2; ctx.fillStyle = "#FF0000"; ctx.fillRect(bgX, 7, titleWidth+40, headerFontSize + 10); ctx.fillStyle = "#FFF"; ctx.fillText(titleText.slice(0,24), (W-titleWidth)/2, headerFontSize + 5); ctx.beginPath(); ctx.strokeStyle = "#FF0000"; ctx.moveTo(12, headerFontSize + 20); ctx.lineTo(W-12, headerFontSize + 20); ctx.stroke(); var startY = headerFontSize + 50; } else { ctx.strokeStyle = "#FF0000"; ctx.lineWidth = 2; ctx.strokeRect(4, 4, W-8, H-8); var startY = 10; } const lineHeightBase = 26; let currentY = startY; hitAreas = []; for(let i=0; i { ctx.font = `${line.fontSize}px "${line.fontFamily}"`; totalWidth += ctx.measureText(seg.text).width; }); let startX = line.alignment === 'center' ? (W - totalWidth) / 2 : (line.alignment === 'right' ? W - totalWidth - 18 : 22); let currentX = startX; segments.forEach(seg => { ctx.font = `${line.fontSize}px "${line.fontFamily}"`; let textWidth = ctx.measureText(seg.text).width; if(bgColor) { ctx.fillStyle = bgColor; ctx.fillRect(currentX, currentY-line.fontSize, textWidth+2, line.fontSize+4); } if(seg.isRed) { ctx.fillStyle = "#FF0000"; } else { ctx.fillStyle = textColor; } ctx.fillText(seg.text, currentX, currentY); currentX += textWidth; }); hitAreas.push({ index:i, yTop: currentY-20, yBottom: currentY+10 }); currentY += line.fontSize + 8; if(currentY > H-20) break; } ctx.fillStyle = "#000"; ctx.fillRect(16, H-11, 42, 2); ctx.fillStyle = "#FF0000"; ctx.fillRect(66, H-13, 26, 2); } function parseColoredText(text) { const segments = []; let lastIndex = 0; const regex = /【(.*?)】/g; let match; while ((match = regex.exec(text)) !== null) { if (match.index > lastIndex) { segments.push({ text: text.substring(lastIndex, match.index), isRed: false }); } segments.push({ text: match[1], isRed: true }); lastIndex = match.index + match[0].length; } if (lastIndex < text.length) { segments.push({ text: text.substring(lastIndex), isRed: false }); } return segments.length ? segments : [{ text: text, isRed: false }]; } async function renderQRCodeMode() { ctx.fillStyle = "#ffffffff"; ctx.fillRect(0, 0, W, H); const isSmallScreen = (W === 296 && H === 128); const isLargeScreen = (W === 800 && H === 480); if (!isSmallScreen) { ctx.strokeStyle = "#FF0000"; ctx.lineWidth = 2; ctx.strokeRect(4, 4, W-8, H-8); ctx.fillStyle = "#FF0000"; ctx.fillRect(8, 8, 26, 4); ctx.fillStyle = "#000"; ctx.fillRect(W-54, 12, 42, 4); const headerFontSize = isLargeScreen ? 36 : 18; ctx.font = `bold ${headerFontSize}px '微软雅黑'`; ctx.fillStyle = "#FF0000"; ctx.fillText(globalTitle.slice(0, 20), 33, headerFontSize + 10); ctx.beginPath(); ctx.moveTo(12, headerFontSize + 20); ctx.lineTo(W-12, headerFontSize + 20); ctx.stroke(); var qrY = headerFontSize + 24; var captionStartY = headerFontSize + qrSize + 30; } else { ctx.strokeStyle = "#FF0000"; ctx.lineWidth = 2; ctx.strokeRect(4, 4, W-8, H-8); var qrY = 8; var captionStartY = qrSize + 12; } if(qrCodeImage) { const img = new Image(); img.src = qrCodeImage; await new Promise(r => { img.onload = r; img.onerror = r; }); let drawSize = qrSize; if (isSmallScreen && drawSize > 90) { drawSize = 80; } ctx.drawImage(img, (W-drawSize)/2, qrY, drawSize, drawSize); } let y = captionStartY; const captionFontSize = isLargeScreen ? 32 : (isSmallScreen ? 12 : 18); for(let line of qrCaptionLines) { if(!line.trim()) { y += captionFontSize + 4; continue; } ctx.font = `${captionFontSize}px '微软雅黑'`; let tw = ctx.measureText(line).width; ctx.fillStyle = "#f00"; ctx.fillText(line, (W-tw)/2, y+(H/20)); y += captionFontSize + 6; } if (!isSmallScreen) { ctx.fillStyle = "#000"; ctx.fillRect(16, H-11, 42, 2); ctx.fillStyle = "#FF0000"; ctx.fillRect(66, H-13, 26, 2); } } function renderCustomMode() { ctx.fillStyle = "#ffffffff"; ctx.fillRect(0, 0, W, H); const isSmallScreen = (W === 296 && H === 128); const isLargeScreen = (W === 800 && H === 480); if (!isSmallScreen) { ctx.strokeStyle = "#FF0000"; ctx.lineWidth = 2; ctx.strokeRect(4, 4, W-8, H-8); const headerFontSize = isLargeScreen ? 40 : 20; const headerText = globalTitle.slice(0, 24); ctx.font = `bold ${headerFontSize}px '微软雅黑'`; const textWidth = ctx.measureText(headerText).width; const boxX = 14; const boxY = 8; const boxWidth = Math.max(textWidth + 30, 150); const boxHeight = headerFontSize + 10; ctx.fillStyle = "#000"; ctx.fillRect(boxX, boxY, boxWidth, boxHeight); ctx.fillStyle = "#FF0000"; ctx.fillText(headerText, boxX + 15, boxY + headerFontSize + 3); ctx.fillStyle = "#FF0000"; ctx.fillRect(8, 8, 26, 4); ctx.fillStyle = "#000"; ctx.fillRect(W-54, 12, 42, 4); ctx.beginPath(); ctx.moveTo(12, headerFontSize + 30); ctx.lineTo(W-12, headerFontSize + 30); ctx.stroke(); var startY = headerFontSize + 48; } else { ctx.strokeStyle = "#FF0000"; ctx.lineWidth = 2; ctx.strokeRect(4, 4, W-8, H-8); var startY = 8; } const { fontSize, fontFamily, baseColor, invertMode, alignment } = customStyle; const { bgColor, textColor } = applyInvertStyle(baseColor, invertMode); ctx.font = `${fontSize}px "${fontFamily}"`; let rawLines = customRawText.split('\n'); let allLines = []; for(let line of rawLines) { let segments = parseColoredText(line); let currentLineSegments = []; let currentLineWidth = 0; for(let seg of segments) { ctx.font = `${fontSize}px "${fontFamily}"`; let segWidth = ctx.measureText(seg.text).width; if(currentLineWidth + segWidth > W - 44 && currentLineSegments.length > 0) { allLines.push([...currentLineSegments]); currentLineSegments = []; currentLineWidth = 0; } currentLineSegments.push(seg); currentLineWidth += segWidth; } if(currentLineSegments.length > 0) { allLines.push(currentLineSegments); } } let y = startY; for(let lineSegments of allLines) { if(!lineSegments.length) { y += fontSize + 6; continue; } let totalWidth = 0; for(let seg of lineSegments) { ctx.font = `${fontSize}px "${fontFamily}"`; totalWidth += ctx.measureText(seg.text).width; } let startX = alignment === 'center' ? (W - totalWidth) / 2 : (alignment === 'right' ? W - totalWidth - 18 : 22); let currentX = startX; for(let seg of lineSegments) { ctx.font = `${fontSize}px "${fontFamily}"`; let textWidth = ctx.measureText(seg.text).width; const textHeight = fontSize; if(bgColor) { ctx.fillStyle = bgColor; ctx.fillRect(currentX-2, y + fontSize/2 - textHeight+1, textWidth+4, textHeight + 4); } if(seg.isRed) { ctx.fillStyle = "#FF0000"; } else { ctx.fillStyle = textColor; } ctx.fillText(seg.text, currentX, y + fontSize/2); currentX += textWidth; } y += fontSize + 6; if(y > H-30) break; } if (!isSmallScreen) { ctx.fillStyle = "#000"; ctx.fillRect(16, H-11, 42, 2); ctx.fillStyle = "#FF0000"; ctx.fillRect(66, H-13, 26, 2); } } function render() { if(currentTemplate === 'course') renderCourseTable(); else if(currentTemplate === 'qrcode') renderQRCodeMode(); else if(currentTemplate === 'customText') renderCustomMode(); else renderPresetTemplate(); const canvas = document.getElementById('unifiedCanvas'); applyImageAdjustmentsmb(canvas) } function applyImageAdjustmentsmb(canvas) { const brightness = 0; const contrast = 100; const saturation = 100; const diffusion = 0; const ctx = canvas.getContext('2d'); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; if (brightness !== 0) { const brightnessFactor = brightness / 100; for (let i = 0; i < data.length; i += 4) { data[i] = clamp(data[i] + 255 * brightnessFactor, 0, 255); data[i + 1] = clamp(data[i + 1] + 255 * brightnessFactor, 0, 255); data[i + 2] = clamp(data[i + 2] + 255 * brightnessFactor, 0, 255); } } if (contrast !== 0) { const contrastFactor = (contrast + 100) / 100; const contrastAdjust = (1 - contrastFactor) * 128; for (let i = 0; i < data.length; i += 4) { data[i] = clamp(data[i] * contrastFactor + contrastAdjust, 0, 255); data[i + 1] = clamp(data[i + 1] * contrastFactor + contrastAdjust, 0, 255); data[i + 2] = clamp(data[i + 2] * contrastFactor + contrastAdjust, 0, 255); } } if (saturation !== 100) { const saturationFactor = saturation / 100; for (let i = 0; i < data.length; i += 4) { const gray = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2]; data[i] = clamp(gray + (data[i] - gray) * saturationFactor, 0, 255); data[i + 1] = clamp(gray + (data[i + 1] - gray) * saturationFactor, 0, 255); data[i + 2] = clamp(gray + (data[i + 2] - gray) * saturationFactor, 0, 255); } } ctx.putImageData(imageData, 0, 0); return diffusion; } function handleCanvasClick(e) { if(currentTemplate === 'course') { const r = canvas.getBoundingClientRect(); const x = (e.clientX-r.left)*(W/r.width); const y = (e.clientY-r.top)*(H/r.height); for(let a of courseHitAreas) { if(x>=a.x&&x<=a.x+a.w&&y>=a.y&&y<=a.y+a.h) { editCourseCell(a.row,a.col,a.currentName); return; } } return; } if(currentTemplate === 'qrcode') { const r = canvas.getBoundingClientRect(); const y = (e.clientY-r.top)*(H/r.height); if(y>130) { const m = document.createElement('div'); m.className='modal-mask'; m.innerHTML=``; document.body.appendChild(m); m.querySelector('.ok').onclick=()=>{ qrCaptionLines = m.querySelector('textarea').value.split('\n'); updateCaptionLinesToUI(); render(); m.remove(); }; m.querySelector('.cancel').onclick=()=>m.remove(); } return; } if(currentTemplate !== 'customText') { const r = canvas.getBoundingClientRect(); const y = (e.clientY-r.top)*(H/r.height); for(let a of hitAreas) { if(y>=a.yTop&&y<=a.yBottom) { editPresetModal(a.index); return; } } addPresetNewRow(); } } function addPresetNewRow() { const modal = document.createElement('div'); modal.className = 'modal-mask'; modal.innerHTML = ` `; document.body.appendChild(modal); const confirm = () => { const newRow = { text: modal.querySelector('#modalText').value, fontSize: Math.min(52, Math.max(10, parseInt(modal.querySelector('#modalFontSize').value) || 14)), fontFamily: modal.querySelector('#modalFontFamily').value, baseColor: modal.querySelector('#modalBaseColor').value, invertMode: modal.querySelector('#modalInvert').value, alignment: modal.querySelector('#modalAlign').value }; presetLinesData.push(newRow); renderPresetTemplate(); modal.remove(); }; modal.querySelector('#confirmBtn').onclick = confirm; modal.querySelector('#cancelBtn').onclick = () => modal.remove(); modal.onclick = (e) => { if(e.target === modal) modal.remove(); }; } function editPresetModal(idx) { const line = presetLinesData[idx]; const modal = document.createElement('div'); modal.className = 'modal-mask'; modal.innerHTML = ` `; document.body.appendChild(modal); const confirm = () => { presetLinesData[idx] = { text: modal.querySelector('#modalText').value, fontSize: Math.min(52, Math.max(10, parseInt(modal.querySelector('#modalFontSize').value) || 14)), fontFamily: modal.querySelector('#modalFontFamily').value, baseColor: modal.querySelector('#modalBaseColor').value, invertMode: modal.querySelector('#modalInvert').value, alignment: modal.querySelector('#modalAlign').value }; renderPresetTemplate(); modal.remove(); }; modal.querySelector('#confirmBtn').onclick = confirm; modal.querySelector('#cancelBtn').onclick = () => modal.remove(); modal.onclick = (e) => { if(e.target === modal) modal.remove(); }; } function switchTemplate(tpl) { currentTemplate = tpl; document.getElementById('customBlock').style.display = tpl==='customText'?'block':'none'; document.getElementById('qrcodeBlock').style.display = tpl==='qrcode'?'block':'none'; document.getElementById('courseBlock').style.display = tpl==='course'?'block':'none'; document.getElementById('presetHintBlock').style.display = (tpl!=='customText'&&tpl!=='qrcode')?'block':'none'; if(tpl === 'course') { loadPresetData('schedule'); render(); } else if(tpl === 'qrcode') { syncCaptionFromTextarea(); refreshQRImage(); } else if(tpl === 'customText') { render(); } else { loadPresetData(tpl); render(); } } function resetCurrent() { if(currentTemplate === 'customText') { customRawText = "小雨在放学路上捡到一枚【闪闪发亮的硬币】。\n她买了两颗【橘子味的糖果】,一颗塞进嘴里,\n甜得她眯起了眼睛;另一颗放在路边的长椅上。\n一只【唱歌的小鸟】飞过来,歪着脑袋看了看\n,叼起糖果飞走了。小雨有点失落,\n但还是继续往家走。第二天早上,她推开窗户,\n发现窗台上放着一朵【金黄色的小野花】,花瓣上还挂着露珠。小雨捧着花笑了:\n“原来快乐会【越分越多】呀。”\n从那天起,她每天都往长椅上放一颗糖。"; document.getElementById('freeTextArea').value = customRawText; } else if(currentTemplate === 'qrcode') { qrText = "https://mspppclock.tech/"; document.getElementById('qrContentInput').value = qrText; qrCaptionLines = ["扫码访问官网"]; updateCaptionLinesToUI(); refreshQRImage(); } else if(currentTemplate === 'course') { initDefaultCourse(); } else { loadPresetData(currentTemplate); } render(); } function escapeHtml(s) { return String(s).replace(/&/g,'&').replace(//g,'>'); } function init() { const fsel = document.getElementById('customFontFamily'); chineseFonts.forEach(f=>{ let o=document.createElement('option');o.value=f;o.textContent=f;fsel.appendChild(o); }); const isel = document.getElementById('customInvertMode'); invertModes.forEach(m=>{ let o=document.createElement('option');o.value=m.value;o.textContent=m.label;isel.appendChild(o); }); document.getElementById('freeTextArea').value = customRawText; document.getElementById('freeTextArea').oninput = e=>{ customRawText=e.target.value; render(); }; document.querySelectorAll('#customFontSize,#customFontFamily,#customBaseColor,#customInvertMode,#customAlign').forEach(i=>{ i.onchange=()=>{ customStyle.fontSize = +document.getElementById('customFontSize').value; customStyle.fontFamily = document.getElementById('customFontFamily').value; customStyle.baseColor = document.getElementById('customBaseColor').value; customStyle.invertMode = document.getElementById('customInvertMode').value; customStyle.alignment = document.getElementById('customAlign').value; render(); }; }); document.getElementById('globalTitleInput').oninput = e=>{ globalTitle=e.target.value; render(); }; document.getElementById('forceRefreshBtn').onclick = render; document.getElementById('resetCurrentBtn').onclick = resetCurrent; document.getElementById('generateQRBtn').onclick = ()=>{ qrText=document.getElementById('qrContentInput').value; refreshQRImage(); }; document.getElementById('qrCaptionText').oninput = ()=>{ syncCaptionFromTextarea(); render(); }; document.getElementById('applyCourseSettingBtn').onclick = applyCourseCustomSettings; canvas.onclick = handleCanvasClick; document.querySelectorAll('.temp-btn').forEach(b=>{ b.onclick=()=>{ document.querySelectorAll('.temp-btn').forEach(x=>x.classList.remove('active')); b.classList.add('active'); switchTemplate(b.dataset.template); }; }); document.getElementById('epdwh').addEventListener('change', function() { const [newW, newH] = this.value.split('x').map(Number); canvas.width = newW; canvas.height = newH; render(); }); // 监听屏幕尺寸变化 document.getElementById('epdwh').addEventListener('change', function() { updateCanvasSize(); if (currentTemplate === 'qrcode') { refreshQRImage(); // 二维码模式需要重新生成 } else { render(); } }); switchTemplate('schedule'); } init(); })();