|
|
<!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> |