| <!DOCTYPE html> |
| <html lang="zh-CN"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>多功能搜索与任务管理</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| @keyframes fadeIn { |
| from { opacity: 0; transform: translateY(10px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| |
| .task-item { |
| animation: fadeIn 0.3s ease-out forwards; |
| } |
| |
| .completed { |
| position: relative; |
| } |
| |
| .completed::after { |
| content: ''; |
| position: absolute; |
| left: 0; |
| top: 50%; |
| width: 100%; |
| height: 1px; |
| background: #000; |
| transform: scaleX(0); |
| transform-origin: left; |
| animation: strike 0.3s ease-out forwards; |
| } |
| |
| @keyframes strike { |
| to { transform: scaleX(1); } |
| } |
| |
| |
| input[type="date"]::-webkit-calendar-picker-indicator { |
| filter: invert(0.5); |
| } |
| |
| input[type="date"]::-webkit-datetime-edit { |
| color: #4b5563; |
| } |
| |
| |
| .priority-urgent { |
| background-color: #fee2e2; |
| border-left: 4px solid #ef4444; |
| } |
| |
| .priority-high { |
| background-color: #fef3c7; |
| border-left: 4px solid #f59e0b; |
| } |
| |
| .priority-normal { |
| background-color: white; |
| border-left: 4px solid #d1d5db; |
| } |
| |
| |
| .complete-btn-urgent { |
| border-color: #ef4444; |
| } |
| |
| .complete-btn-high { |
| border-color: #f59e0b; |
| } |
| |
| .complete-btn-normal { |
| border-color: #d1d5db; |
| } |
| |
| |
| .complete-btn-done { |
| background-color: #10b981; |
| border-color: #10b981; |
| } |
| |
| |
| .todo-logo { |
| display: inline-flex; |
| align-items: center; |
| justify-content: center; |
| position: relative; |
| font-weight: 800; |
| font-size: 2.5rem; |
| color: #1f2937; |
| margin-bottom: 0.5rem; |
| } |
| |
| .todo-logo span { |
| position: relative; |
| z-index: 2; |
| padding: 0 0.5rem; |
| } |
| |
| .todo-logo::before { |
| content: ''; |
| position: absolute; |
| width: 100%; |
| height: 1rem; |
| background: linear-gradient(90deg, rgba(239,68,68,0.3), rgba(245,158,11,0.3), rgba(16,185,129,0.3)); |
| bottom: 0.5rem; |
| left: 0; |
| z-index: 1; |
| transform: skewY(-2deg); |
| } |
| |
| .todo-logo .dot { |
| color: #ef4444; |
| font-size: 3rem; |
| line-height: 1; |
| margin-left: -0.5rem; |
| } |
| |
| |
| .dark-mode { |
| background-color: #111827; |
| color: #e5e7eb; |
| } |
| |
| .dark-mode .todo-logo { |
| color: #e5e7eb; |
| } |
| |
| .dark-mode .todo-logo::before { |
| background: linear-gradient(90deg, rgba(239,68,68,0.5), rgba(245,158,11,0.5), rgba(16,185,129,0.5)); |
| } |
| |
| .dark-mode input, |
| .dark-mode select, |
| .dark-mode .task-item { |
| background-color: #1f2937; |
| color: #e5e7eb; |
| border-color: #374151; |
| } |
| |
| .dark-mode .priority-normal { |
| background-color: #1f2937; |
| } |
| |
| .dark-mode .task-text { |
| color: #e5e7eb; |
| } |
| |
| .dark-mode .completed .task-text { |
| color: #9ca3af; |
| } |
| |
| .dark-mode .bg-white { |
| background-color: #1f2937; |
| border-color: #374151; |
| } |
| |
| .dark-mode .text-gray-600 { |
| color: #9ca3af; |
| } |
| |
| .dark-mode .border-gray-200 { |
| border-color: #374151; |
| } |
| |
| .dark-mode .hover\:bg-gray-100:hover { |
| background-color: #374151; |
| } |
| |
| .dark-mode .text-gray-700 { |
| color: #e5e7eb; |
| } |
| |
| .dark-mode .text-gray-500 { |
| color: #9ca3af; |
| } |
| |
| .dark-mode .text-gray-300 { |
| color: #6b7280; |
| } |
| |
| |
| .settings-panel { |
| position: fixed; |
| top: 0; |
| right: -400px; |
| width: 380px; |
| height: 100vh; |
| background-color: white; |
| box-shadow: -5px 0 15px rgba(0,0,0,0.1); |
| transition: right 0.3s ease; |
| z-index: 1000; |
| padding: 20px; |
| overflow-y: auto; |
| } |
| |
| .dark-mode .settings-panel { |
| background-color: #1f2937; |
| color: #e5e7eb; |
| } |
| |
| .settings-panel.open { |
| right: 0; |
| } |
| |
| .settings-overlay { |
| position: fixed; |
| top: 0; |
| left: 0; |
| right: 0; |
| bottom: 0; |
| background-color: rgba(0,0,0,0.5); |
| z-index: 999; |
| opacity: 0; |
| pointer-events: none; |
| transition: opacity 0.3s ease; |
| } |
| |
| .settings-overlay.active { |
| opacity: 1; |
| pointer-events: all; |
| } |
| |
| |
| .music-sidebar { |
| position: fixed; |
| top: 0; |
| right: -400px; |
| width: 380px; |
| height: 100vh; |
| background-color: white; |
| box-shadow: -5px 0 15px rgba(0,0,0,0.1); |
| transition: right 0.3s ease; |
| z-index: 998; |
| display: flex; |
| flex-direction: column; |
| } |
| |
| .dark-mode .music-sidebar { |
| background-color: #1f2937; |
| color: #e5e7eb; |
| } |
| |
| .music-sidebar.open { |
| right: 0; |
| } |
| |
| .music-header { |
| padding: 20px; |
| border-bottom: 1px solid #e5e7eb; |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| } |
| |
| .dark-mode .music-header { |
| border-bottom-color: #374151; |
| } |
| |
| .music-content { |
| flex: 1; |
| overflow-y: auto; |
| padding: 20px; |
| } |
| |
| .music-player { |
| padding: 15px; |
| border-top: 1px solid #e5e7eb; |
| } |
| |
| .dark-mode .music-player { |
| border-top-color: #374151; |
| } |
| |
| .progress-container { |
| width: 100%; |
| height: 3px; |
| background-color: #eee; |
| margin-bottom: 10px; |
| } |
| |
| .dark-mode .progress-container { |
| background-color: #374151; |
| } |
| |
| .progress-bar { |
| height: 100%; |
| background-color: #ef4444; |
| width: 0%; |
| } |
| |
| .player-controls { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| margin-bottom: 10px; |
| } |
| |
| .player-info { |
| margin-bottom: 15px; |
| } |
| |
| .music-title { |
| font-weight: bold; |
| white-space: nowrap; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| } |
| |
| .music-artist { |
| font-size: 0.8rem; |
| color: #666; |
| } |
| |
| .dark-mode .music-artist { |
| color: #9ca3af; |
| } |
| |
| |
| .main-content { |
| transition: margin-right 0.3s ease; |
| } |
| |
| .main-content.sidebar-open { |
| margin-right: 400px; |
| } |
| |
| |
| .music-item { |
| padding: 10px; |
| border-bottom: 1px solid #e5e7eb; |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| cursor: pointer; |
| } |
| |
| .dark-mode .music-item { |
| border-bottom-color: #374151; |
| } |
| |
| .music-item:hover { |
| background-color: #f3f4f6; |
| } |
| |
| .dark-mode .music-item:hover { |
| background-color: #374151; |
| } |
| |
| |
| .netease-login-btn { |
| display: inline-block; |
| padding: 8px 15px; |
| background-color: #e60026; |
| color: white; |
| border-radius: 4px; |
| font-size: 14px; |
| text-decoration: none; |
| transition: background-color 0.2s; |
| } |
| |
| .netease-login-btn:hover { |
| background-color: #c50020; |
| } |
| |
| |
| .search-engine-btn { |
| position: absolute; |
| right: 10px; |
| top: 50%; |
| transform: translateY(-50%); |
| background: none; |
| border: none; |
| cursor: pointer; |
| color: #6b7280; |
| } |
| |
| .search-engine-btn:hover { |
| color: #4b5563; |
| } |
| |
| .dark-mode .search-engine-btn { |
| color: #9ca3af; |
| } |
| |
| .dark-mode .search-engine-btn:hover { |
| color: #e5e7eb; |
| } |
| |
| |
| .search-engine-dropdown { |
| position: absolute; |
| right: 0; |
| top: 100%; |
| background-color: white; |
| border: 1px solid #e5e7eb; |
| border-radius: 0.5rem; |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); |
| z-index: 10; |
| display: none; |
| min-width: 200px; |
| } |
| |
| .dark-mode .search-engine-dropdown { |
| background-color: #1f2937; |
| border-color: #374151; |
| } |
| |
| .search-engine-dropdown.show { |
| display: block; |
| } |
| |
| .search-engine-item { |
| padding: 0.5rem 1rem; |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| } |
| |
| .search-engine-item:hover { |
| background-color: #f3f4f6; |
| } |
| |
| .dark-mode .search-engine-item:hover { |
| background-color: #374151; |
| } |
| |
| .search-engine-icon { |
| margin-right: 0.5rem; |
| width: 16px; |
| height: 16px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| |
| .search-card { |
| background-color: white; |
| border-radius: 0.5rem; |
| box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); |
| padding: 1.5rem; |
| margin-bottom: 1.5rem; |
| } |
| |
| .dark-mode .search-card { |
| background-color: #1f2937; |
| box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); |
| } |
| |
| |
| .task-manager-section { |
| margin-top: 2rem; |
| background-color: white; |
| border-radius: 0.5rem; |
| box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); |
| padding: 1.5rem; |
| } |
| |
| .dark-mode .task-manager-section { |
| background-color: #1f2937; |
| } |
| |
| |
| .search-container { |
| display: grid; |
| grid-template-columns: 1fr; |
| gap: 1.5rem; |
| } |
| |
| @media (min-width: 768px) { |
| .search-container { |
| grid-template-columns: 1fr 1fr; |
| } |
| } |
| |
| .search-box { |
| position: relative; |
| margin-bottom: 1rem; |
| } |
| |
| .search-input { |
| width: 100%; |
| padding: 0.75rem 1rem; |
| padding-right: 3.5rem; |
| border-radius: 0.5rem; |
| border: 1px solid #e5e7eb; |
| font-size: 1rem; |
| transition: border-color 0.2s; |
| } |
| |
| .search-input:focus { |
| outline: none; |
| border-color: #3b82f6; |
| box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); |
| } |
| |
| .search-button { |
| position: absolute; |
| right: 0.5rem; |
| top: 50%; |
| transform: translateY(-50%); |
| background-color: #3b82f6; |
| color: white; |
| border: none; |
| border-radius: 0.25rem; |
| padding: 0.5rem; |
| cursor: pointer; |
| transition: background-color 0.2s; |
| } |
| |
| .search-button:hover { |
| background-color: #2563eb; |
| } |
| |
| .search-engine-toggle { |
| position: absolute; |
| right: 3.5rem; |
| top: 50%; |
| transform: translateY(-50%); |
| background: none; |
| border: none; |
| color: #6b7280; |
| cursor: pointer; |
| } |
| |
| .search-engine-toggle:hover { |
| color: #4b5563; |
| } |
| </style> |
| </head> |
| <body class="bg-gray-50 min-h-screen"> |
| |
| <div class="fixed top-4 right-4 z-50 flex space-x-2"> |
| <button id="musicToggleBtn" class="p-3 rounded-full bg-gray-900 text-white hover:bg-gray-800 transition-colors shadow-lg"> |
| <i class="fas fa-music"></i> |
| </button> |
| <button id="settingsBtn" class="p-3 rounded-full bg-gray-900 text-white hover:bg-gray-800 transition-colors shadow-lg"> |
| <i class="fas fa-cog"></i> |
| </button> |
| </div> |
|
|
| |
| <div class="settings-overlay" id="settingsOverlay"></div> |
| |
| <div class="settings-panel" id="settingsPanel"> |
| <div class="flex justify-between items-center mb-6"> |
| <h2 class="text-xl font-bold">设置</h2> |
| <button id="closeSettings" class="text-gray-500 hover:text-gray-700"> |
| <i class="fas fa-times"></i> |
| </button> |
| </div> |
| |
| <div class="mb-6"> |
| <h3 class="text-lg font-semibold mb-3">主题</h3> |
| <div class="flex items-center justify-between"> |
| <span>暗夜模式</span> |
| <label class="relative inline-flex items-center cursor-pointer"> |
| <input type="checkbox" id="darkModeToggle" class="sr-only peer"> |
| <div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-gray-800"></div> |
| </label> |
| </div> |
| </div> |
| |
| <div class="mb-6"> |
| <h3 class="text-lg font-semibold mb-3">网易云音乐</h3> |
| <div class="mb-4"> |
| <label class="block text-sm text-gray-600 mb-1">网易云音乐网址</label> |
| <div class="flex"> |
| <input type="text" id="neteaseUrl" placeholder="https://music.163.com" value="https://music.163.com" class="flex-1 px-4 py-2 rounded-l-lg border border-gray-300 focus:outline-none focus:border-gray-500"> |
| <a id="neteaseLoginBtn" href="https://music.163.com" target="_blank" class="netease-login-btn px-4 py-2 rounded-r-lg"> |
| <i class="fas fa-sign-in-alt mr-1"></i> 登录 |
| </a> |
| </div> |
| </div> |
| |
| <div class="mb-4"> |
| <label class="block text-sm text-gray-600 mb-1">搜索歌曲</label> |
| <div class="flex"> |
| <input type="text" id="musicSearch" placeholder="输入歌曲名" class="flex-1 px-4 py-2 rounded-l-lg border border-gray-300 focus:outline-none focus:border-gray-500"> |
| <button id="searchMusicBtn" class="px-4 py-2 bg-gray-900 text-white rounded-r-lg hover:bg-gray-800"> |
| <i class="fas fa-search"></i> |
| </button> |
| </div> |
| </div> |
| |
| <div id="musicResults" class="max-h-60 overflow-y-auto border border-gray-200 rounded-lg"> |
| |
| <div class="text-center py-8 text-gray-500"> |
| <i class="fas fa-music text-2xl mb-2 text-gray-300"></i> |
| <p>搜索你喜欢的音乐</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="music-sidebar" id="musicSidebar"> |
| <div class="music-header"> |
| <h2 class="text-xl font-bold">音乐播放器</h2> |
| <button id="closeMusicSidebar" class="text-gray-500 hover:text-gray-700"> |
| <i class="fas fa-times"></i> |
| </button> |
| </div> |
| |
| <div class="music-content" id="musicContent"> |
| <div class="text-center py-8 text-gray-500"> |
| <i class="fas fa-music text-2xl mb-2 text-gray-300"></i> |
| <p>从设置中搜索音乐开始播放</p> |
| </div> |
| </div> |
| |
| <div class="music-player"> |
| <div class="progress-container"> |
| <div class="progress-bar" id="progressBar"></div> |
| </div> |
| |
| <div class="player-info"> |
| <div class="music-title" id="musicTitle">未播放</div> |
| <div class="music-artist" id="musicArtist">-</div> |
| </div> |
| |
| <div class="player-controls"> |
| <button id="prevBtn" class="text-gray-900 hover:text-gray-700"> |
| <i class="fas fa-step-backward"></i> |
| </button> |
| <button id="playPauseBtn" class="text-gray-900 hover:text-gray-700 text-xl"> |
| <i class="fas fa-play"></i> |
| </button> |
| <button id="nextBtn" class="text-gray-900 hover:text-gray-700"> |
| <i class="fas fa-step-forward"></i> |
| </button> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="container mx-auto px-4 py-8 max-w-4xl main-content" id="mainContent"> |
| |
| <div class="search-card"> |
| <h1 class="text-3xl font-bold text-center mb-8">多功能搜索</h1> |
| |
| <div class="search-container"> |
| |
| <div> |
| <h2 class="text-xl font-semibold mb-4">通用搜索</h2> |
| <div class="search-box"> |
| <input |
| type="text" |
| id="generalSearchInput" |
| placeholder="在 Google 上搜索..." |
| class="search-input" |
| > |
| <button id="generalSearchEngineBtn" class="search-engine-toggle"> |
| <i class="fas fa-chevron-down"></i> |
| </button> |
| <button id="generalSearchBtn" class="search-button"> |
| <i class="fas fa-search"></i> |
| </button> |
| <div id="generalSearchEngineDropdown" class="search-engine-dropdown"> |
| <div class="search-engine-item" data-engine="google"> |
| <div class="search-engine-icon"> |
| <i class="fab fa-google text-blue-500"></i> |
| </div> |
| <span>Google</span> |
| </div> |
| <div class="search-engine-item" data-engine="bing"> |
| <div class="search-engine-icon"> |
| <i class="fab fa-microsoft text-green-500"></i> |
| </div> |
| <span>Bing</span> |
| </div> |
| <div class="search-engine-item" data-engine="baidu"> |
| <div class="search-engine-icon"> |
| <i class="fas fa-search text-blue-600"></i> |
| </div> |
| <span>百度</span> |
| </div> |
| <div class="search-engine-item" data-engine="duckduckgo"> |
| <div class="search-engine-icon"> |
| <i class="fas fa-search text-yellow-500"></i> |
| </div> |
| <span>DuckDuckGo</span> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div> |
| <h2 class="text-xl font-semibold mb-4">学术搜索</h2> |
| <div class="search-box"> |
| <input |
| type="text" |
| id="academicSearchInput" |
| placeholder="在 Google 学术上搜索..." |
| class="search-input" |
| > |
| <button id="academicSearchEngineBtn" class="search-engine-toggle"> |
| <i class="fas fa-chevron-down"></i> |
| </button> |
| <button id="academicSearchBtn" class="search-button"> |
| <i class="fas fa-search"></i> |
| </button> |
| <div id="academicSearchEngineDropdown" class="search-engine-dropdown"> |
| <div class="search-engine-item" data-engine="google_scholar"> |
| <div class="search-engine-icon"> |
| <i class="fas fa-graduation-cap text-blue-500"></i> |
| </div> |
| <span>Google 学术</span> |
| </div> |
| <div class="search-engine-item" data-engine="semantic_scholar"> |
| <div class="search-engine-icon"> |
| <i class="fas fa-book text-orange-500"></i> |
| </div> |
| <span>Semantic Scholar</span> |
| </div> |
| <div class="search-engine-item" data-engine="cnki"> |
| <div class="search-engine-icon"> |
| <i class="fas fa-university text-red-500"></i> |
| </div> |
| <span>CNKI</span> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="task-manager-section"> |
| <div class="todo-logo"> |
| <span>任务管理</span> |
| <span class="dot">.</span> |
| </div> |
| <p class="text-gray-600 mb-6">专注当下,高效生活</p> |
| |
| <div class="relative mb-4"> |
| <input |
| type="text" |
| id="newTaskInput" |
| placeholder="添加新任务..." |
| class="w-full px-6 py-3 rounded-lg border border-gray-300 focus:outline-none focus:border-gray-500 bg-white text-gray-900" |
| > |
| <button |
| id="addTaskBtn" |
| class="absolute right-2 top-1/2 transform -translate-y-1/2 bg-gray-900 text-white p-2 rounded-lg hover:bg-gray-800 transition-colors" |
| > |
| <i class="fas fa-plus"></i> |
| </button> |
| </div> |
| |
| <div class="flex items-center justify-between mb-4"> |
| <div class="flex-1 mr-2"> |
| <label for="dueDate" class="block text-sm text-gray-600 mb-1">截止日期</label> |
| <input |
| type="date" |
| id="dueDate" |
| class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:border-gray-500 bg-white text-gray-900" |
| > |
| </div> |
| <div class="flex-1 ml-2"> |
| <label for="priority" class="block text-sm text-gray-600 mb-1">优先级</label> |
| <select |
| id="priority" |
| class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:border-gray-500 bg-white text-gray-900" |
| > |
| <option value="normal">普通</option> |
| <option value="high">高</option> |
| <option value="urgent">紧急</option> |
| </select> |
| </div> |
| </div> |
|
|
| |
| <div class="flex justify-between items-center mb-6"> |
| <div class="flex space-x-2"> |
| <button id="filterAll" class="filter-btn active px-4 py-2 rounded-lg bg-gray-900 text-white font-medium">全部</button> |
| <button id="filterActive" class="filter-btn px-4 py-2 rounded-lg border border-gray-300 hover:bg-gray-100 text-gray-700">待办</button> |
| <button id="filterCompleted" class="filter-btn px-4 py-2 rounded-lg border border-gray-300 hover:bg-gray-100 text-gray-700">已完成</button> |
| <button id="filterDueToday" class="filter-btn px-4 py-2 rounded-lg border border-gray-300 hover:bg-gray-100 text-gray-700">今日到期</button> |
| </div> |
| <button id="clearCompleted" class="text-gray-700 hover:text-gray-900 text-sm"> |
| <i class="fas fa-trash-alt mr-1"></i> 清除已完成 |
| </button> |
| </div> |
|
|
| |
| <div class="bg-white rounded-lg p-4 mb-6 border border-gray-200 flex justify-between items-center"> |
| <div> |
| <span class="text-gray-600">总任务:</span> |
| <span id="totalTasks" class="font-bold ml-1">0</span> |
| </div> |
| <div> |
| <span class="text-gray-600">已完成:</span> |
| <span id="completedTasks" class="font-bold ml-1">0</span> |
| </div> |
| <div> |
| <span class="text-gray-600">待完成:</span> |
| <span id="remainingTasks" class="font-bold ml-1">0</span> |
| </div> |
| <div> |
| <span class="text-gray-600">今日到期:</span> |
| <span id="dueTodayTasks" class="font-bold ml-1">0</span> |
| </div> |
| </div> |
|
|
| |
| <div id="taskList" class="space-y-3"> |
| |
| <div class="text-center py-10 text-gray-500" id="emptyState"> |
| <i class="fas fa-tasks text-4xl mb-3 text-gray-300"></i> |
| <p class="text-lg">暂无任务</p> |
| <p class="mt-1">添加你的第一个任务开始吧</p> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| document.addEventListener('DOMContentLoaded', function() { |
| |
| const newTaskInput = document.getElementById('newTaskInput'); |
| const addTaskBtn = document.getElementById('addTaskBtn'); |
| const dueDateInput = document.getElementById('dueDate'); |
| const prioritySelect = document.getElementById('priority'); |
| const taskList = document.getElementById('taskList'); |
| const emptyState = document.getElementById('emptyState'); |
| const filterAll = document.getElementById('filterAll'); |
| const filterActive = document.getElementById('filterActive'); |
| const filterCompleted = document.getElementById('filterCompleted'); |
| const filterDueToday = document.getElementById('filterDueToday'); |
| const clearCompleted = document.getElementById('clearCompleted'); |
| const totalTasks = document.getElementById('totalTasks'); |
| const completedTasks = document.getElementById('completedTasks'); |
| const remainingTasks = document.getElementById('remainingTasks'); |
| const dueTodayTasks = document.getElementById('dueTodayTasks'); |
| |
| |
| const settingsBtn = document.getElementById('settingsBtn'); |
| const settingsPanel = document.getElementById('settingsPanel'); |
| const settingsOverlay = document.getElementById('settingsOverlay'); |
| const closeSettings = document.getElementById('closeSettings'); |
| const darkModeToggle = document.getElementById('darkModeToggle'); |
| const musicToggleBtn = document.getElementById('musicToggleBtn'); |
| const musicSidebar = document.getElementById('musicSidebar'); |
| const closeMusicSidebar = document.getElementById('closeMusicSidebar'); |
| const musicContent = document.getElementById('musicContent'); |
| const neteaseUrl = document.getElementById('neteaseUrl'); |
| const neteaseLoginBtn = document.getElementById('neteaseLoginBtn'); |
| |
| |
| const musicSearch = document.getElementById('musicSearch'); |
| const searchMusicBtn = document.getElementById('searchMusicBtn'); |
| const musicResults = document.getElementById('musicResults'); |
| const playPauseBtn = document.getElementById('playPauseBtn'); |
| const prevBtn = document.getElementById('prevBtn'); |
| const nextBtn = document.getElementById('nextBtn'); |
| const musicTitle = document.getElementById('musicTitle'); |
| const musicArtist = document.getElementById('musicArtist'); |
| const progressBar = document.getElementById('progressBar'); |
| const mainContent = document.getElementById('mainContent'); |
| |
| |
| const generalSearchInput = document.getElementById('generalSearchInput'); |
| const generalSearchBtn = document.getElementById('generalSearchBtn'); |
| const generalSearchEngineBtn = document.getElementById('generalSearchEngineBtn'); |
| const generalSearchEngineDropdown = document.getElementById('generalSearchEngineDropdown'); |
| |
| const academicSearchInput = document.getElementById('academicSearchInput'); |
| const academicSearchBtn = document.getElementById('academicSearchBtn'); |
| const academicSearchEngineBtn = document.getElementById('academicSearchEngineBtn'); |
| const academicSearchEngineDropdown = document.getElementById('academicSearchEngineDropdown'); |
| |
| |
| let tasks = JSON.parse(localStorage.getItem('tasks')) || []; |
| let currentFilter = 'all'; |
| let isDarkMode = localStorage.getItem('darkMode') === 'true'; |
| let currentMusicIndex = -1; |
| let musicList = []; |
| let audio = new Audio(); |
| let isPlaying = false; |
| let progressInterval; |
| let neteaseMusicUrl = localStorage.getItem('neteaseMusicUrl') || 'https://music.163.com'; |
| |
| |
| let currentGeneralEngine = 'google'; |
| let currentAcademicEngine = 'google_scholar'; |
| |
| const searchEngines = { |
| |
| google: { |
| name: 'Google', |
| url: 'https://www.google.com/search?q=', |
| icon: 'fab fa-google text-blue-500' |
| }, |
| bing: { |
| name: 'Bing', |
| url: 'https://www.bing.com/search?q=', |
| icon: 'fab fa-microsoft text-green-500' |
| }, |
| baidu: { |
| name: '百度', |
| url: 'https://www.baidu.com/s?wd=', |
| icon: 'fas fa-search text-blue-600' |
| }, |
| duckduckgo: { |
| name: 'DuckDuckGo', |
| url: 'https://duckduckgo.com/?q=', |
| icon: 'fas fa-search text-yellow-500' |
| }, |
| |
| |
| google_scholar: { |
| name: 'Google 学术', |
| url: 'https://scholar.google.com/scholar?q=', |
| icon: 'fas fa-graduation-cap text-blue-500' |
| }, |
| semantic_scholar: { |
| name: 'Semantic Scholar', |
| url: 'https://www.semanticscholar.org/search?q=', |
| icon: 'fas fa-book text-orange-500' |
| }, |
| cnki: { |
| name: 'CNKI', |
| url: 'https://search.cnki.net/search.aspx?q=', |
| icon: 'fas fa-university text-red-500' |
| } |
| }; |
| |
| |
| initDarkMode(); |
| renderTasks(); |
| updateStats(); |
| setupMusicPlayer(); |
| neteaseUrl.value = neteaseMusicUrl; |
| updateNeteaseLoginBtn(); |
| initSearchEngines(); |
| |
| |
| addTaskBtn.addEventListener('click', addTask); |
| newTaskInput.addEventListener('keypress', function(e) { |
| if (e.key === 'Enter') addTask(); |
| }); |
| |
| filterAll.addEventListener('click', () => setFilter('all')); |
| filterActive.addEventListener('click', () => setFilter('active')); |
| filterCompleted.addEventListener('click', () => setFilter('completed')); |
| filterDueToday.addEventListener('click', () => setFilter('dueToday')); |
| |
| clearCompleted.addEventListener('click', clearCompletedTasks); |
| |
| |
| settingsBtn.addEventListener('click', openSettings); |
| closeSettings.addEventListener('click', closeSettingsPanel); |
| settingsOverlay.addEventListener('click', closeSettingsPanel); |
| darkModeToggle.addEventListener('change', toggleDarkMode); |
| musicToggleBtn.addEventListener('click', toggleMusicSidebar); |
| closeMusicSidebar.addEventListener('click', closeMusicSidebarPanel); |
| |
| |
| neteaseUrl.addEventListener('change', updateNeteaseUrl); |
| |
| |
| searchMusicBtn.addEventListener('click', searchMusic); |
| musicSearch.addEventListener('keypress', function(e) { |
| if (e.key === 'Enter') searchMusic(); |
| }); |
| |
| playPauseBtn.addEventListener('click', togglePlayPause); |
| prevBtn.addEventListener('click', playPrevious); |
| nextBtn.addEventListener('click', playNext); |
| |
| |
| generalSearchBtn.addEventListener('click', () => performSearch('general')); |
| generalSearchInput.addEventListener('keypress', function(e) { |
| if (e.key === 'Enter') performSearch('general'); |
| }); |
| generalSearchEngineBtn.addEventListener('click', () => toggleSearchEngineDropdown('general')); |
| |
| academicSearchBtn.addEventListener('click', () => performSearch('academic')); |
| academicSearchInput.addEventListener('keypress', function(e) { |
| if (e.key === 'Enter') performSearch('academic'); |
| }); |
| academicSearchEngineBtn.addEventListener('click', () => toggleSearchEngineDropdown('academic')); |
| |
| |
| document.addEventListener('click', function(e) { |
| if (!generalSearchEngineBtn.contains(e.target) && !generalSearchEngineDropdown.contains(e.target)) { |
| generalSearchEngineDropdown.classList.remove('show'); |
| } |
| |
| if (!academicSearchEngineBtn.contains(e.target) && !academicSearchEngineDropdown.contains(e.target)) { |
| academicSearchEngineDropdown.classList.remove('show'); |
| } |
| }); |
| |
| |
| function addTask() { |
| const taskText = newTaskInput.value.trim(); |
| if (taskText === '') return; |
| |
| const dueDate = dueDateInput.value; |
| const priority = prioritySelect.value; |
| |
| const task = { |
| id: Date.now().toString(), |
| text: taskText, |
| completed: false, |
| createdAt: new Date().toISOString(), |
| dueDate: dueDate, |
| priority: priority |
| }; |
| |
| tasks.unshift(task); |
| saveTasks(); |
| renderTasks(); |
| updateStats(); |
| |
| |
| newTaskInput.value = ''; |
| dueDateInput.value = ''; |
| prioritySelect.value = 'normal'; |
| } |
| |
| function renderTasks() { |
| |
| let filteredTasks = []; |
| |
| switch(currentFilter) { |
| case 'active': |
| filteredTasks = tasks.filter(task => !task.completed); |
| break; |
| case 'completed': |
| filteredTasks = tasks.filter(task => task.completed); |
| break; |
| case 'dueToday': |
| filteredTasks = tasks.filter(task => isDueToday(task.dueDate) && !task.completed); |
| break; |
| default: |
| filteredTasks = [...tasks]; |
| } |
| |
| if (filteredTasks.length === 0) { |
| emptyState.classList.remove('hidden'); |
| taskList.innerHTML = ''; |
| taskList.appendChild(emptyState); |
| } else { |
| emptyState.classList.add('hidden'); |
| taskList.innerHTML = ''; |
| |
| |
| filteredTasks.sort((a, b) => { |
| |
| if (a.completed !== b.completed) { |
| return a.completed ? 1 : -1; |
| } |
| |
| |
| const priorityOrder = { 'urgent': 0, 'high': 1, 'normal': 2 }; |
| if (a.priority !== b.priority) { |
| return priorityOrder[a.priority] - priorityOrder[b.priority]; |
| } |
| |
| |
| if (a.dueDate && b.dueDate) { |
| return new Date(a.dueDate) - new Date(b.dueDate); |
| } else if (a.dueDate) { |
| return -1; |
| } else if (b.dueDate) { |
| return 1; |
| } |
| |
| return 0; |
| }); |
| |
| filteredTasks.forEach(task => { |
| const taskElement = createTaskElement(task); |
| taskList.appendChild(taskElement); |
| }); |
| } |
| } |
| |
| function createTaskElement(task) { |
| const taskElement = document.createElement('div'); |
| taskElement.className = `task-item rounded-lg p-4 border border-gray-200 flex items-center justify-between priority-${task.priority}`; |
| taskElement.dataset.id = task.id; |
| |
| if (task.completed) { |
| taskElement.classList.add('completed'); |
| } |
| |
| |
| let dueDateDisplay = ''; |
| if (task.dueDate) { |
| const dueDate = new Date(task.dueDate); |
| const today = new Date(); |
| today.setHours(0, 0, 0, 0); |
| |
| const timeDiff = dueDate - today; |
| const daysDiff = Math.ceil(timeDiff / (1000 * 60 * 60 * 24)); |
| |
| if (daysDiff === 0) { |
| dueDateDisplay = '<span class="text-red-500">今天到期</span>'; |
| } else if (daysDiff === 1) { |
| dueDateDisplay = '<span class="text-orange-500">明天到期</span>'; |
| } else if (daysDiff < 0) { |
| dueDateDisplay = '<span class="text-red-500">已过期</span>'; |
| } else { |
| dueDateDisplay = `${daysDiff}天后到期`; |
| } |
| |
| dueDateDisplay = `<span class="text-xs ml-2">${dueDateDisplay}</span>`; |
| } |
| |
| |
| const completeBtnClass = task.completed |
| ? 'complete-btn-done' |
| : `complete-btn-${task.priority}`; |
| |
| taskElement.innerHTML = ` |
| <div class="flex items-center space-x-3 flex-1"> |
| <button class="complete-btn w-5 h-5 rounded border ${completeBtnClass} flex items-center justify-center"> |
| ${task.completed ? '<i class="fas fa-check text-xs text-white"></i>' : ''} |
| </button> |
| <div class="flex-1"> |
| <div class="flex items-center"> |
| <span class="task-text ${task.completed ? 'text-gray-400' : 'text-gray-800'}">${task.text}</span> |
| </div> |
| ${task.dueDate ? ` |
| <div class="text-xs text-gray-500 mt-1 ml-7"> |
| <i class="far fa-calendar-alt mr-1"></i> |
| ${formatDate(task.dueDate)} |
| ${dueDateDisplay} |
| </div> |
| ` : ''} |
| </div> |
| </div> |
| <div class="flex space-x-2"> |
| <button class="edit-btn text-gray-500 hover:text-gray-700"> |
| <i class="fas fa-pencil-alt text-sm"></i> |
| </button> |
| <button class="delete-btn text-gray-500 hover:text-gray-700"> |
| <i class="fas fa-trash-alt text-sm"></i> |
| </button> |
| </div> |
| `; |
| |
| |
| const completeBtn = taskElement.querySelector('.complete-btn'); |
| const editBtn = taskElement.querySelector('.edit-btn'); |
| const deleteBtn = taskElement.querySelector('.delete-btn'); |
| |
| completeBtn.addEventListener('click', () => toggleComplete(task.id)); |
| editBtn.addEventListener('click', () => editTask(task.id)); |
| deleteBtn.addEventListener('click', () => deleteTask(task.id)); |
| |
| return taskElement; |
| } |
| |
| function toggleComplete(taskId) { |
| const taskIndex = tasks.findIndex(task => task.id === taskId); |
| if (taskIndex !== -1) { |
| tasks[taskIndex].completed = !tasks[taskIndex].completed; |
| saveTasks(); |
| renderTasks(); |
| updateStats(); |
| } |
| } |
| |
| function editTask(taskId) { |
| const task = tasks.find(task => task.id === taskId); |
| if (!task) return; |
| |
| const taskElement = document.querySelector(`.task-item[data-id="${taskId}"]`); |
| const taskTextElement = taskElement.querySelector('.task-text'); |
| |
| const editForm = document.createElement('div'); |
| editForm.className = 'w-full'; |
| editForm.innerHTML = ` |
| <div class="mb-2"> |
| <input type="text" value="${task.text}" class="task-edit-input w-full px-3 py-2 border border-gray-300 rounded"> |
| </div> |
| <div class="flex space-x-2"> |
| <div class="flex-1"> |
| <label class="block text-xs text-gray-600 mb-1">截止日期</label> |
| <input type="date" value="${task.dueDate || ''}" class="due-date-edit w-full px-3 py-1 border border-gray-300 rounded"> |
| </div> |
| <div class="flex-1"> |
| <label class="block text-xs text-gray-600 mb-1">优先级</label> |
| <select class="priority-edit w-full px-3 py-1 border border-gray-300 rounded"> |
| <option value="normal" ${task.priority === 'normal' ? 'selected' : ''}>普通</option> |
| <option value="high" ${task.priority === 'high' ? 'selected' : ''}>高</option> |
| <option value="urgent" ${task.priority === 'urgent' ? 'selected' : ''}>紧急</option> |
| </select> |
| </div> |
| </div> |
| <div class="mt-2 flex justify-end space-x-2"> |
| <button class="save-edit px-3 py-1 bg-gray-900 text-white text-sm rounded">保存</button> |
| <button class="cancel-edit px-3 py-1 border border-gray-300 text-gray-700 text-sm rounded">取消</button> |
| </div> |
| `; |
| |
| taskTextElement.parentElement.parentElement.replaceWith(editForm); |
| |
| const saveBtn = editForm.querySelector('.save-edit'); |
| const cancelBtn = editForm.querySelector('.cancel-edit'); |
| const editInput = editForm.querySelector('.task-edit-input'); |
| const dueDateEdit = editForm.querySelector('.due-date-edit'); |
| const priorityEdit = editForm.querySelector('.priority-edit'); |
| |
| editInput.focus(); |
| |
| const saveChanges = () => { |
| const newText = editInput.value.trim(); |
| if (newText !== '' && (newText !== task.text || dueDateEdit.value !== task.dueDate || priorityEdit.value !== task.priority)) { |
| task.text = newText; |
| task.dueDate = dueDateEdit.value; |
| task.priority = priorityEdit.value; |
| saveTasks(); |
| } |
| renderTasks(); |
| }; |
| |
| saveBtn.addEventListener('click', saveChanges); |
| |
| cancelBtn.addEventListener('click', () => { |
| renderTasks(); |
| }); |
| |
| editInput.addEventListener('keypress', function(e) { |
| if (e.key === 'Enter') saveChanges(); |
| }); |
| } |
| |
| function deleteTask(taskId) { |
| tasks = tasks.filter(task => task.id !== taskId); |
| saveTasks(); |
| renderTasks(); |
| updateStats(); |
| } |
| |
| function clearCompletedTasks() { |
| tasks = tasks.filter(task => !task.completed); |
| saveTasks(); |
| renderTasks(); |
| updateStats(); |
| } |
| |
| function setFilter(filter) { |
| currentFilter = filter; |
| |
| |
| document.querySelectorAll('.filter-btn').forEach(btn => { |
| btn.classList.remove('active', 'bg-gray-900', 'text-white'); |
| btn.classList.add('border', 'border-gray-300', 'hover:bg-gray-100', 'text-gray-700'); |
| }); |
| |
| const activeBtn = document.getElementById(`filter${filter.charAt(0).toUpperCase() + filter.slice(1)}`); |
| if (activeBtn) { |
| activeBtn.classList.add('active', 'bg-gray-900', 'text-white'); |
| activeBtn.classList.remove('border', 'hover:bg-gray-100', 'text-gray-700'); |
| } |
| |
| renderTasks(); |
| } |
| |
| function updateStats() { |
| const total = tasks.length; |
| const completed = tasks.filter(task => task.completed).length; |
| const remaining = total - completed; |
| const dueToday = tasks.filter(task => isDueToday(task.dueDate) && !task.completed).length; |
| |
| totalTasks.textContent = total; |
| completedTasks.textContent = completed; |
| remainingTasks.textContent = remaining; |
| dueTodayTasks.textContent = dueToday; |
| } |
| |
| function saveTasks() { |
| localStorage.setItem('tasks', JSON.stringify(tasks)); |
| } |
| |
| |
| function openSettings() { |
| settingsPanel.classList.add('open'); |
| settingsOverlay.classList.add('active'); |
| mainContent.classList.add('sidebar-open'); |
| } |
| |
| function closeSettingsPanel() { |
| settingsPanel.classList.remove('open'); |
| settingsOverlay.classList.remove('active'); |
| mainContent.classList.remove('sidebar-open'); |
| } |
| |
| function toggleMusicSidebar() { |
| if (musicSidebar.classList.contains('open')) { |
| closeMusicSidebarPanel(); |
| } else { |
| openMusicSidebar(); |
| } |
| } |
| |
| function openMusicSidebar() { |
| musicSidebar.classList.add('open'); |
| mainContent.classList.add('sidebar-open'); |
| } |
| |
| function closeMusicSidebarPanel() { |
| musicSidebar.classList.remove('open'); |
| mainContent.classList.remove('sidebar-open'); |
| } |
| |
| function initDarkMode() { |
| if (isDarkMode) { |
| document.body.classList.add('dark-mode'); |
| darkModeToggle.checked = true; |
| } else { |
| document.body.classList.remove('dark-mode'); |
| darkModeToggle.checked = false; |
| } |
| } |
| |
| function toggleDarkMode() { |
| isDarkMode = !isDarkMode; |
| localStorage.setItem('darkMode', isDarkMode); |
| initDarkMode(); |
| } |
| |
| |
| function updateNeteaseUrl() { |
| neteaseMusicUrl = neteaseUrl.value.trim(); |
| if (neteaseMusicUrl === '') { |
| neteaseMusicUrl = 'https://music.163.com'; |
| neteaseUrl.value = neteaseMusicUrl; |
| } |
| localStorage.setItem('neteaseMusicUrl', neteaseMusicUrl); |
| updateNeteaseLoginBtn(); |
| } |
| |
| function updateNeteaseLoginBtn() { |
| neteaseLoginBtn.href = neteaseMusicUrl; |
| } |
| |
| |
| function setupMusicPlayer() { |
| audio.addEventListener('ended', playNext); |
| audio.addEventListener('timeupdate', updateProgress); |
| |
| |
| const savedPlaylist = localStorage.getItem('musicPlaylist'); |
| if (savedPlaylist) { |
| musicList = JSON.parse(savedPlaylist); |
| renderMusicList(); |
| } |
| |
| |
| const savedIndex = localStorage.getItem('currentMusicIndex'); |
| if (savedIndex !== null) { |
| currentMusicIndex = parseInt(savedIndex); |
| if (currentMusicIndex >= 0 && currentMusicIndex < musicList.length) { |
| updateMusicInfo(); |
| } |
| } |
| } |
| |
| function renderMusicList() { |
| if (musicList.length === 0) { |
| musicContent.innerHTML = '<div class="text-center py-8 text-gray-500"><i class="fas fa-music text-2xl mb-2 text-gray-300"></i><p>从设置中搜索音乐开始播放</p></div>'; |
| return; |
| } |
| |
| musicContent.innerHTML = ''; |
| musicList.forEach((song, index) => { |
| const songElement = document.createElement('div'); |
| songElement.className = 'music-item'; |
| songElement.innerHTML = ` |
| <div> |
| <div class="font-medium">${song.title}</div> |
| <div class="text-sm text-gray-600">${song.artist}</div> |
| </div> |
| ${currentMusicIndex === index ? '<i class="fas fa-volume-up text-blue-500"></i>' : '<i class="fas fa-play text-gray-500"></i>'} |
| `; |
| |
| songElement.addEventListener('click', () => { |
| playSong(index); |
| }); |
| |
| musicContent.appendChild(songElement); |
| }); |
| } |
| |
| function searchMusic() { |
| const query = musicSearch.value.trim(); |
| if (query === '') return; |
| |
| |
| musicResults.innerHTML = '<div class="text-center py-4"><i class="fas fa-spinner fa-spin"></i> 搜索中...</div>'; |
| |
| |
| fetch(`https://api.injahow.cn/meting/?server=netease&type=search&id=${encodeURIComponent(query)}`) |
| .then(response => response.json()) |
| .then(data => { |
| if (data && data.length > 0) { |
| musicResults.innerHTML = ''; |
| |
| |
| const songs = data.map(song => ({ |
| id: song.id, |
| title: song.name, |
| artist: song.artist, |
| url: song.url, |
| cover: song.cover |
| })); |
| |
| songs.forEach((song, index) => { |
| const songElement = document.createElement('div'); |
| songElement.className = 'p-3 border-b border-gray-200 hover:bg-gray-100 cursor-pointer flex justify-between items-center'; |
| songElement.innerHTML = ` |
| <div class="flex items-center"> |
| <img src="${song.cover}" alt="${song.title}" class="w-10 h-10 rounded mr-3"> |
| <div> |
| <div class="font-medium">${song.title}</div> |
| <div class="text-sm text-gray-600">${song.artist}</div> |
| </div> |
| </div> |
| <i class="fas fa-play text-gray-500"></i> |
| `; |
| |
| songElement.addEventListener('click', () => { |
| playSong(index, songs); |
| }); |
| |
| musicResults.appendChild(songElement); |
| }); |
| |
| |
| musicList = songs; |
| localStorage.setItem('musicPlaylist', JSON.stringify(musicList)); |
| renderMusicList(); |
| } else { |
| musicResults.innerHTML = '<div class="text-center py-8 text-gray-500">没有找到相关歌曲</div>'; |
| } |
| }) |
| .catch(error => { |
| console.error('搜索失败:', error); |
| musicResults.innerHTML = '<div class="text-center py-8 text-gray-500">搜索失败,请稍后再试</div>'; |
| }); |
| } |
| |
| function playSong(index, playlist = musicList) { |
| if (index < 0 || index >= playlist.length) return; |
| |
| currentMusicIndex = index; |
| const song = playlist[currentMusicIndex]; |
| |
| |
| if (!song.url) { |
| alert('无法获取歌曲播放地址'); |
| return; |
| } |
| |
| audio.src = song.url; |
| audio.play() |
| .then(() => { |
| isPlaying = true; |
| updatePlayPauseIcon(); |
| updateMusicInfo(); |
| startProgressUpdate(); |
| renderMusicList(); |
| |
| |
| localStorage.setItem('currentMusicIndex', currentMusicIndex); |
| }) |
| .catch(error => { |
| console.error('播放失败:', error); |
| alert('播放失败,请稍后再试'); |
| }); |
| } |
| |
| function togglePlayPause() { |
| if (audio.src) { |
| if (isPlaying) { |
| audio.pause(); |
| } else { |
| audio.play(); |
| } |
| isPlaying = !isPlaying; |
| updatePlayPauseIcon(); |
| |
| if (isPlaying) { |
| startProgressUpdate(); |
| } else { |
| stopProgressUpdate(); |
| } |
| } else if (musicList.length > 0) { |
| |
| playSong(0); |
| } |
| } |
| |
| function playPrevious() { |
| if (musicList.length === 0) return; |
| |
| let newIndex = currentMusicIndex - 1; |
| if (newIndex < 0) { |
| newIndex = musicList.length - 1; |
| } |
| |
| playSong(newIndex); |
| } |
| |
| function playNext() { |
| if (musicList.length === 0) return; |
| |
| let newIndex = currentMusicIndex + 1; |
| if (newIndex >= musicList.length) { |
| newIndex = 0; |
| } |
| |
| playSong(newIndex); |
| } |
| |
| function updatePlayPauseIcon() { |
| playPauseBtn.innerHTML = isPlaying ? '<i class="fas fa-pause"></i>' : '<i class="fas fa-play"></i>'; |
| } |
| |
| function updateMusicInfo() { |
| if (currentMusicIndex >= 0 && currentMusicIndex < musicList.length) { |
| const song = musicList[currentMusicIndex]; |
| musicTitle.textContent = song.title; |
| musicArtist.textContent = song.artist; |
| } else { |
| musicTitle.textContent = '未播放'; |
| musicArtist.textContent = '-'; |
| } |
| } |
| |
| function updateProgress() { |
| if (audio.duration) { |
| const progress = (audio.currentTime / audio.duration) * 100; |
| progressBar.style.width = `${progress}%`; |
| } |
| } |
| |
| function startProgressUpdate() { |
| stopProgressUpdate(); |
| progressInterval = setInterval(updateProgress, 1000); |
| } |
| |
| function stopProgressUpdate() { |
| if (progressInterval) { |
| clearInterval(progressInterval); |
| } |
| } |
| |
| |
| function initSearchEngines() { |
| |
| generalSearchInput.placeholder = `在 ${searchEngines[currentGeneralEngine].name} 上搜索...`; |
| academicSearchInput.placeholder = `在 ${searchEngines[currentAcademicEngine].name} 上搜索...`; |
| |
| |
| document.querySelectorAll('#generalSearchEngineDropdown .search-engine-item').forEach(item => { |
| item.addEventListener('click', function() { |
| const engine = this.getAttribute('data-engine'); |
| currentGeneralEngine = engine; |
| generalSearchInput.placeholder = `在 ${searchEngines[engine].name} 上搜索...`; |
| generalSearchEngineDropdown.classList.remove('show'); |
| }); |
| }); |
| |
| document.querySelectorAll('#academicSearchEngineDropdown .search-engine-item').forEach(item => { |
| item.addEventListener('click', function() { |
| const engine = this.getAttribute('data-engine'); |
| currentAcademicEngine = engine; |
| academicSearchInput.placeholder = `在 ${searchEngines[engine].name} 上搜索...`; |
| academicSearchEngineDropdown.classList.remove('show'); |
| }); |
| }); |
| } |
| |
| function toggleSearchEngineDropdown(type) { |
| let dropdown; |
| switch(type) { |
| case 'general': |
| dropdown = generalSearchEngineDropdown; |
| break; |
| case 'academic': |
| dropdown = academicSearchEngineDropdown; |
| break; |
| } |
| |
| |
| if (type !== 'general') generalSearchEngineDropdown.classList.remove('show'); |
| if (type !== 'academic') academicSearchEngineDropdown.classList.remove('show'); |
| |
| |
| dropdown.classList.toggle('show'); |
| } |
| |
| function performSearch(type) { |
| let input, engine; |
| switch(type) { |
| case 'general': |
| input = generalSearchInput; |
| engine = currentGeneralEngine; |
| break; |
| case 'academic': |
| input = academicSearchInput; |
| engine = currentAcademicEngine; |
| break; |
| } |
| |
| const query = input.value.trim(); |
| if (query === '') return; |
| |
| const searchUrl = searchEngines[engine].url + encodeURIComponent(query); |
| window.open(searchUrl, '_blank'); |
| } |
| |
| |
| function formatDate(dateString) { |
| if (!dateString) return ''; |
| const date = new Date(dateString); |
| return `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日`; |
| } |
| |
| function isDueToday(dateString) { |
| if (!dateString) return false; |
| |
| const today = new Date(); |
| today.setHours(0, 0, 0, 0); |
| |
| const dueDate = new Date(dateString); |
| dueDate.setHours(0, 0, 0, 0); |
| |
| return dueDate.getTime() === today.getTime(); |
| } |
| }); |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=uu531/web" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |