diff --git a/index.html b/index.html index b9726b8..ffe11f8 100644 --- a/index.html +++ b/index.html @@ -91,6 +91,7 @@ diff --git a/notification/audio/15min_left.mp3 b/notification/audio/15min_left.mp3 new file mode 100644 index 0000000..8922f81 Binary files /dev/null and b/notification/audio/15min_left.mp3 differ diff --git a/notification/audio/30min_left.mp3 b/notification/audio/30min_left.mp3 new file mode 100644 index 0000000..11ae416 Binary files /dev/null and b/notification/audio/30min_left.mp3 differ diff --git a/notification/audio/classstart.mp3 b/notification/audio/classstart.mp3 new file mode 100644 index 0000000..b7582d8 Binary files /dev/null and b/notification/audio/classstart.mp3 differ diff --git a/notification/audio/end.mp3 b/notification/audio/end.mp3 new file mode 100644 index 0000000..75df277 Binary files /dev/null and b/notification/audio/end.mp3 differ diff --git a/notification/audio/hedui_eng.mp3 b/notification/audio/hedui_eng.mp3 new file mode 100644 index 0000000..b173f1f Binary files /dev/null and b/notification/audio/hedui_eng.mp3 differ diff --git a/notification/audio/hedui_noeng.mp3 b/notification/audio/hedui_noeng.mp3 new file mode 100644 index 0000000..2d6eb8f Binary files /dev/null and b/notification/audio/hedui_noeng.mp3 differ diff --git a/notification/audio/jinchang.mp3 b/notification/audio/jinchang.mp3 new file mode 100644 index 0000000..3747b04 Binary files /dev/null and b/notification/audio/jinchang.mp3 differ diff --git a/notification/audio/start.mp3 b/notification/audio/start.mp3 new file mode 100644 index 0000000..999e054 Binary files /dev/null and b/notification/audio/start.mp3 differ diff --git a/notification/audio/zhanshikemudai.mp3 b/notification/audio/zhanshikemudai.mp3 new file mode 100644 index 0000000..54f4f98 Binary files /dev/null and b/notification/audio/zhanshikemudai.mp3 differ diff --git a/notification/audio_files.json b/notification/audio_files.json new file mode 100644 index 0000000..8f8ab8b --- /dev/null +++ b/notification/audio_files.json @@ -0,0 +1,10 @@ +{ + "上课铃": "audio/classstart.mp3", + "距离结束还有15分钟": "audio/15min_left.mp3", + "距离结束还有30分钟": "audio/30min_left.mp3", + "考试开始": "audio/start.mp3", + "考试结束": "audio/end.mp3", + "核对信息": "audio/hedui_noeng.mp3", + "检场": "audio/jinchang.mp3", + "展示科目袋": "audio/zhanshikemudai.mp3" +} diff --git a/notification/course_schedule.json b/notification/course_schedule.json new file mode 100644 index 0000000..08ac924 --- /dev/null +++ b/notification/course_schedule.json @@ -0,0 +1,62 @@ +[ + { + "name": "第一节课", + "start": "2025-03-09T08:00:00", + "end": "2025-03-09T08:50:00" + }, + { + "name": "第二节课", + "start": "2025-03-09T09:00:00", + "end": "2025-03-09T09:50:00" + }, + { + "name": "第三节课", + "start": "2025-03-09T10:10:00", + "end": "2025-03-09T11:00:00" + }, + { + "name": "第四节课", + "start": "2025-03-09T11:10:00", + "end": "2025-03-09T12:00:00" + }, + { + "name": "第一节课", + "start": "2025-03-09T13:30:00", + "end": "2025-03-09T14:20:00" + }, + { + "name": "第二节课", + "start": "2025-03-09T14:30:00", + "end": "2025-03-09T15:20:00" + }, + { + "name": "第三节课", + "start": "2025-03-09T15:40:00", + "end": "2025-03-09T16:30:00" + }, + { + "name": "第四节课", + "start": "2025-03-09T16:40:00", + "end": "2025-03-09T17:30:00" + }, + { + "name": "第一节课", + "start": "2025-03-09T18:00:00", + "end": "2025-03-09T18:50:00" + }, + { + "name": "第二节课", + "start": "2025-03-09T19:00:00", + "end": "2025-03-09T19:50:00" + }, + { + "name": "第三节课", + "start": "2025-03-09T20:10:00", + "end": "2025-03-09T21:00:00" + }, + { + "name": "第四节课", + "start": "2025-03-09T21:10:00", + "end": "2025-03-10T00:15:00" + } +] \ No newline at end of file diff --git a/notification/index.html b/notification/index.html new file mode 100644 index 0000000..ba25298 --- /dev/null +++ b/notification/index.html @@ -0,0 +1,69 @@ + + + + + 考试看板—广播适配 + + + +
+
+
+
+
+
+ +
+
+ + +
+ +
+
正在初始化...
+
--:--
+
+
+ +
+

系统设置

+
+ + +
+

提醒设置

+ + + + + + + + + + +
提醒条件时间(分钟)音频选择操作
+ +
+ + + + + + + +
科目时间段状态
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/notification/scripts/audioController.js b/notification/scripts/audioController.js new file mode 100644 index 0000000..d38e584 --- /dev/null +++ b/notification/scripts/audioController.js @@ -0,0 +1,109 @@ +var audioController = (function() { + var audioPool = []; + var maxPoolSize = 3; + var soundFiles = {}; + var audioSelectPopulated = false; + + function init() { + fetch('audio_files.json') + .then(response => response.json()) + .then(data => { + soundFiles = data; + Object.keys(soundFiles).forEach(function(type) { + for (var i = 0; i < 2; i++) { + createAudio(type); + } + }); + if (!audioSelectPopulated) { + populateAudioSelect(); + audioSelectPopulated = true; + } + removeInvalidAudioOptions(); + }) + .catch(e => errorSystem.show('音频文件加载失败: ' + e.message, 'error')); + } + + function createAudio(type) { + var audio = document.createElement('audio'); + audio.style.display = 'none'; + audio.preload = 'auto'; + audio.src = soundFiles[type]; + var retryCount = 0; + function loadAudio() { + try { + audio.load(); + } catch(e) { + if (retryCount++ < 3) { + setTimeout(loadAudio, 1000); + } + } + } + audio.addEventListener('error', function() { + if (retryCount++ < 3) { + setTimeout(loadAudio, 1000); + } + }); + document.body.appendChild(audio); + loadAudio(); + audioPool.push(audio); + return audio; + } + + function play(type) { + try { + var audio = audioPool.find(function(a) { return a.paused; }); + if (!audio) { + if (audioPool.length < maxPoolSize) { + audio = createAudio(type); + } else { + return errorSystem.show('系统繁忙,请稍后再试', 'error'); + } + } + audio.src = soundFiles[type]; + try { + audio.play(); + } catch(e) { + errorSystem.show('播放失败: ' + e.message, 'error'); + } + } catch(e) { + errorSystem.show('音频系统错误: ' + e.message, 'error'); + } + } + + function getAudioSrc(type) { + return soundFiles[type]; + } + + function populateAudioSelect() { + var selects = document.querySelectorAll('select[name="audioSelect"]'); + selects.forEach(select => { + Object.keys(soundFiles).forEach(function(type) { + var option = document.createElement('option'); + option.value = type; + option.textContent = type; + select.appendChild(option); + }); + }); + } + + function removeInvalidAudioOptions() { + var selects = document.querySelectorAll('select[name="audioSelect"]'); + selects.forEach(select => { + Array.from(select.options).forEach(option => { + if (!soundFiles[option.value]) { + option.remove(); + } + }); + }); + } + + return { + init: init, + play: play, + getAudioSrc: getAudioSrc, + populateAudioSelect: populateAudioSelect, + removeInvalidAudioOptions: removeInvalidAudioOptions + }; +})(); + +audioController.init(); diff --git a/notification/scripts/config.js b/notification/scripts/config.js new file mode 100644 index 0000000..f2729a7 --- /dev/null +++ b/notification/scripts/config.js @@ -0,0 +1,63 @@ +document.getElementById('importJson').addEventListener('change', function(event) { + var files = event.target.files; + if (files.length > 0) { + Array.from(files).forEach(file => { + var reader = new FileReader(); + reader.onload = function(e) { + try { + var config = JSON.parse(e.target.result); + applyConfig(config); + } catch (err) { + errorSystem.show('导入配置失败: ' + err.message, 'error'); + } + }; + reader.readAsText(file); + }); + } +}); + +function applyConfig(config) { + try { + if (config.examInfos) { + courseSchedule = config.examInfos.map(function(exam) { + return { + name: exam.name, + start: exam.start, + end: exam.end + }; + }); + updateScheduleTable(); + } + if (config.examName) { + document.title = config.examName; + } + if (config.message) { + document.getElementById('statusLabel').textContent = config.message; + } + if (config.room) { + document.getElementById('timeDescription').textContent = '考场: ' + config.room; + } + if (config.reminders) { + localStorage.setItem('reminders', JSON.stringify(config.reminders)); + } + } catch (err) { + errorSystem.show('应用配置失败: ' + err.message, 'error'); + } +} + +function exportConfig() { + var config = { + examInfos: courseSchedule, + examName: document.title, + message: document.getElementById('statusLabel').textContent, + room: document.getElementById('timeDescription').textContent.replace('考场: ', ''), + reminders: JSON.parse(localStorage.getItem('reminders') || '[]') + }; + var dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(config)); + var downloadAnchorNode = document.createElement('a'); + downloadAnchorNode.setAttribute("href", dataStr); + downloadAnchorNode.setAttribute("download", "exam_config.json"); + document.body.appendChild(downloadAnchorNode); + downloadAnchorNode.click(); + downloadAnchorNode.remove(); +} diff --git a/notification/scripts/courseSchedule.js b/notification/scripts/courseSchedule.js new file mode 100644 index 0000000..78e4e26 --- /dev/null +++ b/notification/scripts/courseSchedule.js @@ -0,0 +1,95 @@ +var courseSchedule = []; + +fetch('course_schedule.json') + .then(response => response.json()) + .then(data => { + courseSchedule = data; + updateScheduleTable(); + }) + .catch(error => errorSystem.show('加载课程表失败: ' + error.message, 'error')); + +function parseTime(timeStr) { + try { + return new Date(timeStr); + } catch (e) { + errorSystem.show('时间解析错误: ' + e.message, 'info', 'error'); + return new Date(); + } +} + +function updateCourseStatus() { + try { + var now = new Date(); + currentCourse = null; + for (var i = 0; i < courseSchedule.length; i++) { + var course = courseSchedule[i], + start = parseTime(course.start), + end = parseTime(course.end); + if (end < start) end.setDate(end.getDate() + 1); + if (now >= start && now <= end) { + currentCourse = course; + break; + } + } + if (currentCourse !== lastCourse) { + handleStatusChange(); + lastCourse = currentCourse; + } + } catch (e) { + errorSystem.show('课程状态更新失败: ' + e.message, 'error'); + } +} + +function handleStatusChange() { + // 处理状态变化的逻辑 + console.log('课程状态已更改:', currentCourse); +} + +function getNextCourse() { + try { + var now = new Date(); + for (var i = 0; i < courseSchedule.length; i++) { + var start = parseTime(courseSchedule[i].start); + if (start > now) return courseSchedule[i]; + } + return null; + } catch (e) { + errorSystem.show('获取下一节课失败: ' + e.message, 'error'); + return null; + } +} + +function updateScheduleTable() { + try { + var now = new Date(), + table = document.getElementById('scheduleTable'), + rows = table.querySelectorAll('tr:not(:first-child)'); + rows.forEach(row => row.remove()); // 清空现有行 + courseSchedule.forEach(function(course) { + var row = table.insertRow(-1); + row.innerHTML = '' + course.name + '' + + '' + formatDateTime(course.start) + ' - ' + formatDateTime(course.end) + '' + + ''; + }); + for (var i = 0; i < courseSchedule.length; i++) { + var course = courseSchedule[i]; + if (!course) continue; // 确保不会超出数组边界 + var start = parseTime(course.start), + end = parseTime(course.end), + row = table.rows[i + 1]; // 跳过表头行 + row.className = ''; + if (now >= start && now <= end) { + row.className = 'current-class'; + row.cells[2].textContent = '进行中'; + } else if (now < start) { + row.className = 'future-class'; + row.cells[2].textContent = '即将开始'; + } else { + row.className = 'past-class'; + row.cells[2].textContent = '已结束'; + } + } + } catch (e) { + errorSystem.show('课程表更新失败: ' + e.message, 'error'); + } +} diff --git a/notification/scripts/display.js b/notification/scripts/display.js new file mode 100644 index 0000000..cbe2d8e --- /dev/null +++ b/notification/scripts/display.js @@ -0,0 +1,53 @@ +function updateDisplay() { + try { + var now = new Date(), + timeDisplay = document.getElementById('timeDisplay'), + statusLabel = document.getElementById('statusLabel'), + timeDesc = document.getElementById('timeDescription'); + if (currentCourse) { + var endTime = parseTime(currentCourse.end), + remain = endTime - now; + statusLabel.textContent = currentCourse.name + ' 进行中'; + timeDisplay.textContent = formatTime(remain); + timeDesc.textContent = '剩余时间'; + } else { + statusLabel.textContent = '休息中'; + var nextCourse = getNextCourse(); + if (nextCourse) { + var startTime = parseTime(nextCourse.start), + remain = startTime - now; + timeDisplay.textContent = formatTime(remain); + timeDesc.textContent = '距离 ' + nextCourse.name; + } else { + timeDisplay.textContent = '00:00'; + timeDesc.textContent = '今日课程已结束'; + } + } + updateScheduleTable(); + } catch (e) { + errorSystem.show('界面更新失败: ' + e.message, 'error'); + } +} + +function formatTime(ms) { + try { + if (ms < 0) return '00:00'; + var totalSeconds = Math.floor(ms / 1000), + minutes = Math.floor(totalSeconds / 60), + seconds = totalSeconds % 60; + return (minutes < 10 ? '0' : '') + minutes + ':' + (seconds < 10 ? '0' : '') + seconds; + } catch (e) { + return '--:--'; + } +} + +function formatDateTime(dateTimeStr) { + var date = new Date(dateTimeStr); + return date.toLocaleString('zh-CN', { + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + }); +} diff --git a/notification/scripts/errorSystem.js b/notification/scripts/errorSystem.js new file mode 100644 index 0000000..8eb2a44 --- /dev/null +++ b/notification/scripts/errorSystem.js @@ -0,0 +1,17 @@ +var errorSystem = { + show: function(message, type = 'error') { + try { + var container = document.querySelector(type === 'info' ? '.info-container' : '.error-container'); + var content = document.getElementById(type === 'info' ? 'infoMessage' : 'errorMessage'); + content.textContent = message; + container.style.display = 'flex'; + setTimeout(() => this.hide(type), 5000); + } catch(e) { + console.error('错误提示系统异常:', e); + } + }, + hide: function(type = 'error') { + var container = document.querySelector(type === 'info' ? '.info-container' : '.error-container'); + if (container) container.style.display = 'none'; + } +}; diff --git a/notification/scripts/reminderQueue.js b/notification/scripts/reminderQueue.js new file mode 100644 index 0000000..3e07e74 --- /dev/null +++ b/notification/scripts/reminderQueue.js @@ -0,0 +1,45 @@ +var reminderQueue = (function() { + var queue = []; + var audioCache = {}; + + function addReminder(reminder) { + queue.push(reminder); + queue.sort(function(a, b) { + return a.time - b.time; + }); + preloadAudio(reminder.audio); + } + + function processQueue() { + var now = Date.now(); + while (queue.length > 0 && queue[0].time <= now) { + var reminder = queue.shift(); + executeReminder(reminder); + } + setTimeout(processQueue, 1000); + } + + function executeReminder(reminder) { + if (audioCache[reminder.audio]) { + audioCache[reminder.audio].play(); + } else if (audioController.getAudioSrc(reminder.audio)) { + audioController.play(reminder.audio); + } else { + errorSystem.show('音频文件不存在: ' + reminder.audio, 'error'); + } + } + + function preloadAudio(audioType) { + if (!audioCache[audioType] && audioController.getAudioSrc(audioType)) { + var audio = new Audio(audioController.getAudioSrc(audioType)); + audioCache[audioType] = audio; + } + } + + return { + addReminder: addReminder, + processQueue: processQueue + }; +})(); + +reminderQueue.processQueue(); diff --git a/notification/scripts/script.js b/notification/scripts/script.js new file mode 100644 index 0000000..421f20b --- /dev/null +++ b/notification/scripts/script.js @@ -0,0 +1,265 @@ +// 全局状态变量 +var isFullscreen = false, currentCourse = null, lastCourse = null, timer = null, lastUpdate = Date.now(); + +// 新增:安全更新循环函数 +function safeUpdate() { + try { + var now = Date.now(); + updateCourseStatus(); + updateDisplay(); + var nextTick = Math.max(1000 - (Date.now() - now), 50); + timer = setTimeout(safeUpdate, nextTick); + lastUpdate = now; + } catch (e) { + errorSystem.show('更新循环错误: ' + e.message, 'error'); + } +} + +// 修改:全屏切换函数 +function toggleFullscreen() { + try { + if (!document.fullscreenElement) { + document.documentElement.requestFullscreen().then(() => { + isFullscreen = true; + document.body.classList.add('fullscreen-mode'); + adjustFontSize(); + document.getElementById('exitFullscreenBtn').style.display = 'block'; + }); + } else { + document.exitFullscreen().then(() => { + isFullscreen = false; + document.body.classList.remove('fullscreen-mode'); + adjustFontSize(); + document.getElementById('exitFullscreenBtn').style.display = 'none'; + }); + } + } catch (e) { + errorSystem.show('全屏切换失败: ' + e.message, 'error'); + } +} + +function adjustFontSize() { + var elements = document.querySelectorAll('.time-display, .status-label'); + elements.forEach(element => { + if (isFullscreen) { + element.style.fontSize = '10vw'; + } else { + element.style.fontSize = ''; + } + }); + var countdownElement = document.getElementById('timeDisplay'); + if (isFullscreen) { + countdownElement.style.fontSize = '20vw'; + } else { + countdownElement.style.fontSize = ''; + } +} + +function addReminder() { + var table = document.getElementById('reminderTable'); + var row = table.insertRow(table.rows.length - 1); + row.draggable = true; + row.innerHTML = ` + + + + + + + + + ☰ + `; + row.cells[0].querySelector('select').addEventListener('change', function() { + row.cells[1].querySelector('input').disabled = this.value === 'start' || this.value === 'end'; + row.cells[1].querySelector('input').placeholder = this.value === 'start' || this.value === 'end' ? '-' : '分钟'; + }); + audioController.populateAudioSelect(); + addDragAndDropHandlers(row); +} + +function removeReminder(button) { + var row = button.parentNode.parentNode; + row.parentNode.removeChild(row); +} + +function saveConfig() { + var table = document.getElementById('reminderTable'); + var reminders = []; + for (var i = 1; i < table.rows.length - 1; i++) { + var row = table.rows[i]; + var condition = row.cells[0].querySelector('select').value; + var timeInput = row.cells[1].querySelector('input'); + var audioSelect = row.cells[2].querySelector('select'); + if (timeInput && audioSelect) { + var time = timeInput.value || 0; // 确保时间值不为空 + var audio = audioSelect.value || 'classStart'; // 确保音频选择不为空 + reminders.push({ condition: condition, time: time, audio: audio }); + } + } + var config = { + reminders: reminders, + examInfos: courseSchedule, + examName: document.title, + message: document.getElementById('statusLabel').textContent, + room: document.getElementById('timeDescription').textContent.replace('考场: ', '') + }; + localStorage.setItem('config', JSON.stringify(config)); + errorSystem.show('配置已保存', 'info'); + loadRemindersToQueue(reminders); +} + +function loadRemindersToQueue(reminders) { + var now = Date.now(); + reminders.forEach(function(reminder) { + var reminderTime; + if (currentCourse) { + switch (reminder.condition) { + case 'beforeStart': + reminderTime = new Date(currentCourse.start).getTime() - reminder.time * 60000; + break; + case 'beforeEnd': + reminderTime = new Date(currentCourse.end).getTime() - reminder.time * 60000; + break; + case 'afterEnd': + reminderTime = new Date(currentCourse.end).getTime() + reminder.time * 60000; + break; + case 'start': + reminderTime = new Date(currentCourse.start).getTime(); + break; + case 'end': + reminderTime = new Date(currentCourse.end).getTime(); + break; + default: + console.error('未知的提醒条件:', reminder.condition); + return; + } + } else { + var nextCourse = getNextCourse(); + if (nextCourse) { + switch (reminder.condition) { + case 'beforeStart': + reminderTime = new Date(nextCourse.start).getTime() - reminder.time * 60000; + break; + case 'start': + reminderTime = new Date(nextCourse.start).getTime(); + break; + default: + console.error('未知的提醒条件:', reminder.condition); + return; + } + } else { + errorSystem.show('当前没有课程信息', 'info'); + return; + } + } + if (reminderTime > now) { + reminderQueue.addReminder({ time: reminderTime, condition: reminder.condition, audio: reminder.audio }); + } + }); +} + +// 修改:系统初始化函数 +function init() { + try { + var table = document.getElementById('scheduleTable'); + courseSchedule.forEach(function(course) { + var row = table.insertRow(-1); + row.innerHTML = '' + course.name + '' + + '' + formatDateTime(course.start) + ' - ' + formatDateTime(course.end) + '' + + ''; + }); + // 初始化设置复选框 + var config = JSON.parse(localStorage.getItem('config') || '{}'); + // 加载提醒设置 + var reminders = config.reminders || []; + var table = document.getElementById('reminderTable'); + reminders.forEach(function(reminder) { + var row = table.insertRow(table.rows.length - 1); + row.draggable = true; + row.innerHTML = ` + + + + + + + + + ☰ + `; + row.cells[0].querySelector('select').addEventListener('change', function() { + row.cells[1].querySelector('input').disabled = this.value === 'start' || this.value === 'end'; + row.cells[1].querySelector('input').placeholder = this.value === 'start' || this.value === 'end' ? '-' : '分钟'; + }); + addDragAndDropHandlers(row); + }); + // 启动安全更新循环 + safeUpdate(); + // 移除或修改音频权限激活代码(用原音频系统无需特殊激活) + document.body.onclick = null; + // 加载设置从Cookies + loadSettingsFromCookies(); + // 加载提醒到队列 + loadRemindersToQueue(reminders); + } catch (e) { + errorSystem.show('系统初始化失败: ' + e.message); + } +} +window.onbeforeunload = function () { + if (timer) clearTimeout(timer); +}; +init(); + +function addDragAndDropHandlers(row) { + row.addEventListener('dragstart', function(e) { + e.dataTransfer.setData('text/plain', e.target.rowIndex); + e.target.classList.add('dragging'); + }); + + row.addEventListener('dragover', function(e) { + e.preventDefault(); + var draggingRow = document.querySelector('.dragging'); + if (draggingRow && draggingRow !== e.target) { + var table = document.getElementById('reminderTable'); + var rows = Array.from(table.rows).slice(1, -1); + var targetRow = rows.find(row => row === e.target || row.contains(e.target)); + if (targetRow && targetRow.parentNode === table) { + var targetIndex = targetRow.rowIndex; + var draggingIndex = draggingRow.rowIndex; + if (draggingIndex < targetIndex) { + table.insertBefore(draggingRow, targetRow.nextSibling); + } else { + table.insertBefore(draggingRow, targetRow); + } + } + } + }); + + row.addEventListener('drop', function(e) { + e.preventDefault(); + var draggingRow = document.querySelector('.dragging'); + if (draggingRow) { + draggingRow.classList.remove('dragging'); + } + }); + + row.addEventListener('dragend', function(e) { + e.target.classList.remove('dragging'); + }); +} \ No newline at end of file diff --git a/notification/scripts/settings.js b/notification/scripts/settings.js new file mode 100644 index 0000000..c397c8d --- /dev/null +++ b/notification/scripts/settings.js @@ -0,0 +1,55 @@ +function saveSettingsToCookies() { + var table = document.getElementById('reminderTable'); + var reminders = []; + for (var i = 1; i < table.rows.length - 1; i++) { + var row = table.rows[i]; + var condition = row.cells[0].querySelector('select').value; + var timeInput = row.cells[1].querySelector('input'); + var audioSelect = row.cells[2].querySelector('select'); + if (timeInput && audioSelect) { + var time = timeInput.value || 0; // 确保时间值不为空 + var audio = audioSelect.value || 'classStart'; // 确保音频选择不为空 + reminders.push({ condition: condition, time: time, audio: audio }); + } + } + document.cookie = "reminders=" + JSON.stringify(reminders); +} + +function loadSettingsFromCookies() { + var cookies = document.cookie.split(';'); + cookies.forEach(function(cookie) { + var parts = cookie.split('='); + var name = parts[0].trim(); + var value = parts[1].trim(); + if (name === 'reminders') { + var reminders = JSON.parse(value); + var table = document.getElementById('reminderTable'); + reminders.forEach(function(reminder) { + var row = table.insertRow(table.rows.length - 1); + row.innerHTML = ` + + + + + + + + + `; + row.cells[0].querySelector('select').addEventListener('change', function() { + row.cells[1].querySelector('input').disabled = this.value === 'start' || this.value === 'end'; + row.cells[1].querySelector('input').placeholder = this.value === 'start' || this.value === 'end' ? '-' : '分钟'; + }); + }); + } + }); +} diff --git a/notification/styles/style.css b/notification/styles/style.css new file mode 100644 index 0000000..b657603 --- /dev/null +++ b/notification/styles/style.css @@ -0,0 +1,376 @@ +/* 基础样式 */ +body { + font-family: 'Segoe UI', Arial, sans-serif; + margin: 0; + padding: 20px; + background: #f5f7fa; + color: #2d3436; +} +.container { + max-width: 800px; + margin: 0 auto; + position: relative; +} + +/* 状态显示框 */ +.status-box { + background: linear-gradient(145deg, #ffffff, #f8f9fa); + border: 1px solid #e0e6ed; + padding: 30px; + margin: 20px 0; + border-radius: 16px; + box-shadow: 0 8px 24px rgba(0,0,0,0.06); + transition: all 0.3s ease; +} +.time-display { + font-size: 48px; + color: #2c3e50; + font-weight: 700; + margin: 15px 0; + text-align: center; + letter-spacing: 2px; + text-shadow: 0 2px 4px rgba(0,0,0,0.1); +} +.status-label { + font-size: 28px; + text-align: center; + margin: 15px 0; + color: #57606f; +} + +/* 课程表格 */ +.schedule-table { + width: 100%; + border-collapse: collapse; + margin: 25px 0; + background: white; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 4px 12px rgba(0,0,0,0.08); +} +.schedule-table th { + background: #f8f9fa; + padding: 16px; + color: #57606f; + font-weight: 600; + border-bottom: 2px solid #e0e6ed; +} +.schedule-table td { + padding: 14px; + border-bottom: 1px solid #f1f3f6; +} +.current-class { + background: #e8f5e9 !important; + position: relative; +} +.current-class:after { + content: ''; + position: absolute; + left: 0; + top: 0; + height: 100%; + width: 4px; + background: #2ecc71; +} +.future-class { + background: #f8f9fa !important; +} +.past-class { + background: #fafafa !important; + color: #a4b0be; +} + +/* 全屏模式 */ +.fullscreen-mode { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #ffffff; + z-index: 9999; + padding: 0; + margin: 0; + overflow: hidden; +} +.fullscreen-mode .status-box { + width: 100%; + height: 100%; + border: none; + box-shadow: none; + border-radius: 0; + padding: 0; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background: none; +} +.fullscreen-mode .time-display { + font-size: 25vw; + line-height: 0.9; + color: #2c3e50; +} +.fullscreen-mode .status-label { + font-size: 6vw; + margin-top: 4vw; + color: #57606f; +} +.fullscreen-mode .control-bar, +.fullscreen-mode .settings-panel, +.fullscreen-mode .schedule-table { + display: none !important; +} + +/* 控制按钮 */ +.control-bar { + position: fixed; + top: 20px; + right: 20px; + z-index: 10000; + display: flex; + gap: 10px; +} +.control-btn { + background: linear-gradient(135deg, #3498db, #2980b9); + color: white; + border: none; + padding: 12px 24px; + border-radius: 8px; + cursor: pointer; + font-size: 15px; + font-weight: 500; + transition: all 0.2s ease; + box-shadow: 0 4px 12px rgba(52,152,219,0.25); +} +.control-btn:hover { + transform: translateY(-1px); + box-shadow: 0 6px 16px rgba(52,152,219,0.35); +} + +/* 设置面板 */ +.settings-panel { + background: rgba(255,255,255,0.95); + padding: 25px; + margin: 25px 0; + border-radius: 12px; + border: 1px solid #e0e6ed; + backdrop-filter: blur(8px); +} +.settings-panel h3 { + margin: 0 0 20px; + color: #2c3e50; + font-size: 20px; +} +.settings-panel label { + display: flex; + align-items: center; + gap: 10px; + margin: 12px 0; + font-size: 16px; + color: #57606f; + flex-wrap: wrap; /* 解决文本框显示在页面外的问题 */ +} +.settings-panel input[type="checkbox"] { + width: 18px; + height: 18px; + accent-color: #3498db; +} +.settings-panel input[type="number"] { + width: 60px; + padding: 5px; + border: 1px solid #e0e6ed; + border-radius: 4px; + font-size: 14px; + color: #2d3436; + margin-right: 10px; +} +.settings-panel input[type="file"] { + margin-top: 10px; +} +.file-input-label { + display: inline-block; + padding: 10px 20px; + background: #27ae60; + color: white; + border-radius: 8px; + cursor: pointer; + transition: background 0.3s ease; +} +.file-input-label:hover { + background: #219150; +} +.file-input-label input[type="file"] { + display: none; +} +.button-group { + display: flex; + justify-content: space-between; + gap: 10px; + margin-top: 20px; +} + +/* 信息提示 */ +.info-container { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: #3498db; + color: white; + padding: 16px; + display: none; + z-index: 10001; + animation: slideUp 0.3s ease; +} +@keyframes slideUp { + from { transform: translateY(100%); } + to { transform: translateY(0); } +} +.info-content { + max-width: 800px; + margin: 0 auto; + font-size: 15px; + display: flex; + align-items: center; + gap: 12px; +} +.info-content:before { + content: 'i'; + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + background: white; + color: #3498db; + border-radius: 50%; + font-weight: bold; +} + +/* 错误提示 */ +.error-container { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: #ff6b6b; + color: white; + padding: 16px; + display: none; + z-index: 10001; + animation: slideUp 0.3s ease; +} +.error-content { + max-width: 800px; + margin: 0 auto; + font-size: 15px; + display: flex; + align-items: center; + gap: 12px; +} +.error-content:before { + content: '!'; + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + background: white; + color: #ff6b6b; + border-radius: 50%; + font-weight: bold; +} + +/* 提醒表格 */ +.reminder-table { + width: 100%; + border-collapse: collapse; + margin: 15px 0; + background: white; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 4px 12px rgba(0,0,0,0.08); +} +.reminder-table th, .reminder-table td { + padding: 12px; + border-bottom: 1px solid #f1f3f6; + text-align: left; +} +.reminder-table th { + background: #f8f9fa; + color: #57606f; + font-weight: 600; +} +.reminder-table td { + padding: 12px; + border-bottom: 1px solid #f1f3f6; + text-align: left; + cursor: move; /* 添加拖拽光标 */ +} +.reminder-table td.drag-handle { + cursor: grab; /* 添加拖拽光标 */ +} +.reminder-table tr.dragging { + opacity: 0.5; /* 拖拽时透明度 */ +} +.reminder-table td input, .reminder-table td select { + width: 100%; + padding: 8px; + border: 1px solid #e0e6ed; + border-radius: 4px; + font-size: 14px; + color: #2d3436; +} +.reminder-table td button { + background: #e74c3c; + color: white; + border: none; + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + transition: all 0.2s ease; +} +.reminder-table td button:hover { + background: #c0392b; +} +.reminder-table td:last-child { + text-align: center; +} + +/* 文件输入标签 */ +.file-input-label { + display: inline-block; + padding: 10px 20px; + background: #27ae60; + color: white; + border-radius: 8px; + cursor: pointer; + transition: background 0.3s ease; +} +.file-input-label:hover { + background: #219150; +} +.file-input-label input[type="file"] { + display: none; +} + +/* 动作按钮 */ +.action-btn { + background: #27ae60; + color: white; + border: none; + padding: 10px 20px; + border-radius: 8px; + cursor: pointer; + font-size: 15px; + font-weight: 500; + transition: all 0.2s ease; + box-shadow: 0 4px 12px rgba(39, 174, 96, 0.25); + margin-top: 10px; +} +.action-btn:hover { + transform: translateY(-1px); + box-shadow: 0 6px 16px rgba(39, 174, 96, 0.35); +}