Files
feishu_screen/code (4).html

1029 lines
36 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>高级任务清单</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.container {
width: 100%;
max-width: 900px;
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.2);
overflow: hidden;
backdrop-filter: blur(10px);
}
header {
background: #2c3e50;
color: white;
padding: 25px;
text-align: center;
position: relative;
}
header h1 {
font-size: 2.2rem;
margin-bottom: 5px;
letter-spacing: 1px;
}
header p {
opacity: 0.8;
font-size: 0.9rem;
}
.stats {
position: absolute;
right: 25px;
top: 50%;
transform: translateY(-50%);
text-align: center;
background: rgba(255,255,255,0.15);
padding: 8px 15px;
border-radius: 10px;
font-size: 0.85rem;
}
.input-section {
padding: 25px;
border-bottom: 1px solid #eee;
background: #f8f9fa;
}
.input-group {
display: flex;
gap: 10px;
margin-bottom: 15px;
flex-wrap: wrap;
}
input[type="text"], input[type="datetime-local"] {
flex: 1;
padding: 14px 18px;
border: 2px solid #e1e5eb;
border-radius: 12px;
font-size: 1rem;
transition: all 0.3s;
min-width: 200px;
}
input[type="text"]:focus, input[type="datetime-local"]:focus {
border-color: #3498db;
outline: none;
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2);
}
select {
padding: 14px 18px;
border: 2px solid #e1e5eb;
border-radius: 12px;
background: white;
font-size: 0.95rem;
cursor: pointer;
}
.priority {
display: flex;
gap: 10px;
margin-top: 10px;
flex-wrap: wrap;
}
.priority label {
display: flex;
align-items: center;
gap: 5px;
padding: 8px 15px;
background: #f0f4f8;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
}
.priority input[type="radio"] {
accent-color: #3498db;
}
.priority label:hover {
background: #e1e8f0;
}
.priority input:checked + span {
font-weight: bold;
}
button#addTask {
background: #3498db;
color: white;
border: none;
padding: 14px 25px;
border-radius: 12px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
display: flex;
align-items: center;
gap: 8px;
}
button#addTask:hover {
background: #2980b9;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(52, 152, 219, 0.4);
}
button#requestPermission {
background: #27ae60;
color: white;
border: none;
padding: 12px 20px;
border-radius: 12px;
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
display: flex;
align-items: center;
gap: 8px;
margin-top: 10px;
}
button#requestPermission:hover {
background: #219653;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(39, 174, 96, 0.4);
}
.filter-section {
padding: 15px 25px;
background: #f8f9fa;
border-bottom: 1px solid #eee;
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.filter-btn {
background: #ecf0f1;
border: none;
padding: 8px 16px;
border-radius: 20px;
cursor: pointer;
transition: all 0.2s;
font-size: 0.9rem;
}
.filter-btn.active, .filter-btn:hover {
background: #3498db;
color: white;
}
.task-list {
padding: 25px;
max-height: 500px;
overflow-y: auto;
}
.task-item {
background: white;
border-radius: 12px;
padding: 18px;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 15px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
transform: translateY(0);
transition: all 0.3s;
border-left: 4px solid transparent;
animation: fadeIn 0.4s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.task-item:hover {
transform: translateY(-3px);
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1);
}
.task-item.high {
border-left-color: #e74c3c;
}
.task-item.medium {
border-left-color: #f39c12;
}
.task-item.low {
border-left-color: #2ecc71;
}
.task-item.completed {
opacity: 0.7;
background: #f8f9fa;
}
.task-item.expiring {
background: #fff8e1;
border-left-color: #f39c12;
}
.task-item.expired {
background: #ffebee;
border-left-color: #e74c3c;
}
.task-checkbox {
width: 22px;
height: 22px;
accent-color: #2ecc71;
cursor: pointer;
}
.task-content {
flex: 1;
}
.task-text {
font-size: 1.05rem;
margin-bottom: 5px;
word-break: break-word;
display: flex;
align-items: center;
gap: 8px;
}
.task-item.completed .task-text {
text-decoration: line-through;
color: #7f8c8d;
}
.task-meta {
display: flex;
gap: 10px;
font-size: 0.8rem;
color: #7f8c8d;
flex-wrap: wrap;
align-items: center;
}
.task-tag {
background: #e1f0fa;
color: #3498db;
padding: 3px 8px;
border-radius: 4px;
font-weight: 500;
}
.task-priority {
padding: 3px 8px;
border-radius: 4px;
font-weight: 500;
color: white;
}
.high .task-priority { background: #e74c3c; }
.medium .task-priority { background: #f39c12; }
.low .task-priority { background: #2ecc71; }
.deadline {
display: flex;
align-items: center;
gap: 5px;
padding: 3px 8px;
border-radius: 4px;
font-weight: 500;
background: #f8f9fa;
color: #2c3e50;
}
.deadline.expiring {
background: #ffecb3;
color: #ff8f00;
}
.deadline.expired {
background: #ffcdd2;
color: #c62828;
}
.task-actions {
display: flex;
gap: 10px;
}
.task-btn {
background: none;
border: none;
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
font-size: 1.1rem;
}
.task-btn.edit {
color: #3498db;
}
.task-btn.delete {
color: #e74c3c;
}
.task-btn:hover {
transform: scale(1.15);
background: rgba(0,0,0,0.05);
}
.empty-state {
text-align: center;
padding: 40px 20px;
color: #7f8c8d;
}
.empty-state i {
font-size: 3.5rem;
margin-bottom: 15px;
opacity: 0.3;
}
.empty-state p {
font-size: 1.1rem;
margin-top: 10px;
}
footer {
padding: 20px;
text-align: center;
color: #7f8c8d;
font-size: 0.9rem;
border-top: 1px solid #eee;
background: #f8f9fa;
}
.notification-status {
text-align: center;
padding: 8px;
margin-top: 10px;
border-radius: 8px;
font-size: 0.85rem;
}
.notification-status.enabled {
background: #d4edda;
color: #155724;
}
.notification-status.disabled {
background: #f8d7da;
color: #721c24;
}
/* 响应式设计 */
@media (max-width: 768px) {
.input-group {
flex-direction: column;
}
input[type="text"], input[type="datetime-local"], select {
width: 100%;
}
.priority {
flex-wrap: wrap;
}
.stats {
position: static;
transform: none;
margin-top: 10px;
display: inline-block;
}
header {
padding: 20px 15px;
}
.task-item {
flex-wrap: wrap;
}
.task-actions {
width: 100%;
justify-content: flex-end;
margin-top: 8px;
}
}
/* 编辑模态框 */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-content {
background: white;
padding: 30px;
border-radius: 15px;
width: 90%;
max-width: 500px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
animation: modalAppear 0.3s ease-out;
}
@keyframes modalAppear {
from { opacity: 0; transform: translateY(-20px); }
to { opacity: 1; transform: translateY(0); }
}
.modal h2 {
margin-bottom: 20px;
color: #2c3e50;
}
.modal input {
width: 100%;
padding: 12px;
margin-bottom: 15px;
border: 2px solid #e1e5eb;
border-radius: 8px;
font-size: 1rem;
}
.modal-buttons {
display: flex;
gap: 10px;
justify-content: flex-end;
}
.modal-buttons button {
padding: 10px 20px;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
}
.save-btn {
background: #3498db;
color: white;
}
.cancel-btn {
background: #ecf0f1;
color: #2c3e50;
}
/* 滚动条样式 */
.task-list::-webkit-scrollbar {
width: 8px;
}
.task-list::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
.task-list::-webkit-scrollbar-thumb {
background: #bdc3c7;
border-radius: 10px;
}
.task-list::-webkit-scrollbar-thumb:hover {
background: #95a5a6;
}
/* 通知图标动画 */
.fa-bell {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
</style>
</head>
<body>
<div class="container">
<header>
<h1><i class="fas fa-tasks"></i> 高级任务清单</h1>
<p>高效管理你的日常任务,支持截止时间提醒</p>
<div class="stats">
<div>总计: <span id="totalTasks">0</span></div>
<div>完成: <span id="completedTasks">0</span></div>
</div>
</header>
<div class="input-section">
<div class="input-group">
<input type="text" id="taskInput" placeholder="添加新任务..." autocomplete="off">
<input type="datetime-local" id="deadlineInput">
<select id="categorySelect">
<option value="工作">工作</option>
<option value="学习">学习</option>
<option value="生活">生活</option>
<option value="其他">其他</option>
</select>
<button id="addTask"><i class="fas fa-plus"></i> 添加</button>
</div>
<div class="priority">
<label>
<input type="radio" name="priority" value="high">
<span style="color: #e74c3c;">高优先级</span>
</label>
<label>
<input type="radio" name="priority" value="medium" checked>
<span style="color: #f39c12;">中优先级</span>
</label>
<label>
<input type="radio" name="priority" value="low">
<span style="color: #2ecc71;">低优先级</span>
</label>
</div>
<div style="margin-top: 15px;">
<button id="requestPermission"><i class="fas fa-bell"></i> 启用任务提醒通知</button>
<div id="notificationStatus" class="notification-status disabled">通知权限: 未授权</div>
</div>
</div>
<div class="filter-section">
<button class="filter-btn active" data-filter="all">全部</button>
<button class="filter-btn" data-filter="work">工作</button>
<button class="filter-btn" data-filter="study">学习</button>
<button class="filter-btn" data-filter="life">生活</button>
<button class="filter-btn" data-filter="other">其他</button>
<button class="filter-btn" data-filter="completed">已完成</button>
<button class="filter-btn" data-filter="active">未完成</button>
<button class="filter-btn" data-filter="expiring">即将到期</button>
</div>
<div class="task-list" id="taskList">
<div class="empty-state">
<i class="fas fa-clipboard-list"></i>
<h3>暂无任务</h3>
<p>添加您的第一个任务开始吧!</p>
</div>
</div>
<footer>
<p>© 2023 高级TodoList | 数据保存在本地浏览器中 | 支持截止时间提醒</p>
</footer>
</div>
<!-- 编辑任务模态框 -->
<div class="modal" id="editModal">
<div class="modal-content">
<h2><i class="fas fa-edit"></i> 编辑任务</h2>
<input type="text" id="editTaskInput" placeholder="编辑任务内容...">
<div class="modal-buttons">
<button class="cancel-btn" id="cancelEdit">取消</button>
<button class="save-btn" id="saveEdit">保存</button>
</div>
</div>
</div>
<script>
// 任务数据结构
let tasks = JSON.parse(localStorage.getItem('tasks')) || [];
let currentEditId = null;
let currentFilter = 'all';
let notificationPermission = Notification.permission === 'granted';
let reminderCheckInterval;
// DOM元素
const taskInput = document.getElementById('taskInput');
const deadlineInput = document.getElementById('deadlineInput');
const categorySelect = document.getElementById('categorySelect');
const addTaskBtn = document.getElementById('addTask');
const taskList = document.getElementById('taskList');
const totalTasks = document.getElementById('totalTasks');
const completedTasks = document.getElementById('completedTasks');
const filterBtns = document.querySelectorAll('.filter-btn');
const editModal = document.getElementById('editModal');
const editTaskInput = document.getElementById('editTaskInput');
const saveEditBtn = document.getElementById('saveEdit');
const cancelEditBtn = document.getElementById('cancelEdit');
const requestPermissionBtn = document.getElementById('requestPermission');
const notificationStatus = document.getElementById('notificationStatus');
// 初始化
document.addEventListener('DOMContentLoaded', () => {
renderTasks();
updateStats();
checkNotificationPermission();
// 设置默认日期时间为明天
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
tomorrow.setHours(9, 0, 0, 0);
deadlineInput.value = formatDateTimeLocal(tomorrow);
// 为优先级单选按钮添加事件
document.querySelectorAll('input[name="priority"]').forEach(radio => {
radio.addEventListener('change', function() {
document.querySelectorAll('.priority label').forEach(label => {
label.style.background = '#f0f4f8';
});
this.parentElement.style.background = '#d6eaf8';
});
});
// 设置默认选中的优先级样式
document.querySelector('input[name="priority"][value="medium"]').parentElement.style.background = '#d6eaf8';
// 启动提醒检查
startReminderCheck();
});
// 检查通知权限
function checkNotificationPermission() {
if (!("Notification" in window)) {
notificationStatus.textContent = "您的浏览器不支持通知功能";
notificationStatus.className = "notification-status disabled";
return;
}
if (Notification.permission === "granted") {
notificationPermission = true;
notificationStatus.textContent = "通知权限: 已授权";
notificationStatus.className = "notification-status enabled";
requestPermissionBtn.style.display = "none";
} else if (Notification.permission === "denied") {
notificationPermission = false;
notificationStatus.textContent = "通知权限: 已被拒绝,请在浏览器设置中手动启用";
notificationStatus.className = "notification-status disabled";
} else {
notificationPermission = false;
notificationStatus.textContent = "通知权限: 未授权";
notificationStatus.className = "notification-status disabled";
}
}
// 请求通知权限
requestPermissionBtn.addEventListener('click', () => {
if (!("Notification" in window)) {
alert("您的浏览器不支持通知功能");
return;
}
Notification.requestPermission().then(permission => {
checkNotificationPermission();
if (permission === "granted") {
showNotification("通知权限已启用!您将收到任务提醒");
}
});
});
// 启动提醒检查
function startReminderCheck() {
if (reminderCheckInterval) clearInterval(reminderCheckInterval);
reminderCheckInterval = setInterval(checkReminders, 60000); // 每分钟检查一次
checkReminders(); // 立即检查一次
}
// 检查提醒
function checkReminders() {
if (!notificationPermission) return;
const now = new Date();
tasks.forEach(task => {
if (task.completed || !task.deadline || task.reminded) return;
const deadline = new Date(task.deadline);
const timeDiff = deadline - now;
// 如果剩余时间小于等于10分钟且大于0则发送提醒
if (timeDiff <= 10 * 60 * 1000 && timeDiff > 0) {
sendNotification("任务即将到期", `任务"${task.text}"将在10分钟后到期`);
task.reminded = true;
saveTasks();
renderTasks();
}
// 如果已过期且未提醒过,则发送过期通知
if (timeDiff <= 0 && !task.expiredNotified) {
sendNotification("任务已过期", `任务"${task.text}"已超过截止时间!`);
task.expiredNotified = true;
saveTasks();
renderTasks();
}
});
}
// 发送通知
function sendNotification(title, body) {
if (!notificationPermission) return;
try {
const notification = new Notification(title, {
body: body,
icon: "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/svgs/solid/check-circle.svg",
badge: "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/svgs/solid/check-circle.svg"
});
notification.onclick = () => {
window.focus();
notification.close();
};
// 自动关闭通知
setTimeout(() => {
notification.close();
}, 8000);
} catch (e) {
console.error("通知发送失败:", e);
}
}
// 添加任务
addTaskBtn.addEventListener('click', addTask);
taskInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') addTask();
});
function addTask() {
const text = taskInput.value.trim();
if (!text) {
taskInput.focus();
return;
}
const priority = document.querySelector('input[name="priority"]:checked').value;
const category = categorySelect.value;
const deadline = deadlineInput.value ? new Date(deadlineInput.value).toISOString() : null;
const newTask = {
id: Date.now(),
text,
category,
priority,
deadline,
completed: false,
reminded: false,
expiredNotified: false,
createdAt: new Date().toISOString()
};
tasks.unshift(newTask); // 添加到数组开头
saveTasks();
renderTasks();
updateStats();
// 清空输入框
taskInput.value = '';
taskInput.focus();
// 显示添加成功提示
showNotification('任务添加成功!');
// 检查是否需要立即提醒
if (deadline) {
checkReminders();
}
}
// 渲染任务列表
function renderTasks() {
const filteredTasks = filterTasks();
if (filteredTasks.length === 0) {
taskList.innerHTML = `
<div class="empty-state">
<i class="fas fa-clipboard-list"></i>
<h3>${getFilterTitle()}</h3>
<p>${getFilterMessage()}</p>
</div>
`;
return;
}
taskList.innerHTML = filteredTasks.map(task => {
const deadlineInfo = getDeadlineInfo(task.deadline);
let deadlineClass = '';
if (deadlineInfo.status === 'expiring') deadlineClass = 'expiring';
if (deadlineInfo.status === 'expired') deadlineClass = 'expired';
return `
<div class="task-item ${task.completed ? 'completed' : ''} ${task.priority} ${deadlineClass}" data-id="${task.id}">
<input type="checkbox" class="task-checkbox" ${task.completed ? 'checked' : ''}
onchange="toggleComplete(${task.id})">
<div class="task-content">
<div class="task-text">
${task.text}
${deadlineInfo.status === 'expired' ? '<i class="fas fa-exclamation-circle" style="color: #e74c3c;"></i>' : ''}
</div>
<div class="task-meta">
<span class="task-tag">${task.category}</span>
<span class="task-priority">${getPriorityText(task.priority)}</span>
${task.deadline ? `<span class="deadline ${deadlineClass}"><i class="fas fa-clock"></i> ${deadlineInfo.text}</span>` : ''}
<span>${formatDate(task.createdAt)}</span>
</div>
</div>
<div class="task-actions">
<button class="task-btn edit" onclick="openEditModal(${task.id})" title="编辑">
<i class="fas fa-edit"></i>
</button>
<button class="task-btn delete" onclick="deleteTask(${task.id})" title="删除">
<i class="fas fa-trash-alt"></i>
</button>
</div>
</div>
`}).join('');
}
// 获取截止时间信息
function getDeadlineInfo(deadlineStr) {
if (!deadlineStr) return { text: '无截止时间', status: 'none' };
const now = new Date();
const deadline = new Date(deadlineStr);
const diff = deadline - now;
const hours = Math.floor(diff / (1000 * 60 * 60));
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
if (diff < 0) {
return { text: `已过期 (${Math.abs(hours)}小时${Math.abs(minutes)}分钟)`, status: 'expired' };
} else if (diff < 10 * 60 * 1000) {
return { text: `即将到期 (${minutes}分钟)`, status: 'expiring' };
} else if (diff < 24 * 60 * 60 * 1000) {
return { text: `今天 ${deadline.getHours().toString().padStart(2, '0')}:${deadline.getMinutes().toString().padStart(2, '0')}`, status: 'normal' };
} else {
return { text: `${deadline.getMonth() + 1}/${deadline.getDate()} ${deadline.getHours().toString().padStart(2, '0')}:${deadline.getMinutes().toString().padStart(2, '0')}`, status: 'normal' };
}
}
// 过滤任务
function filterTasks() {
const now = new Date();
switch(currentFilter) {
case 'work': return tasks.filter(t => t.category === '工作');
case 'study': return tasks.filter(t => t.category === '学习');
case 'life': return tasks.filter(t => t.category === '生活');
case 'other': return tasks.filter(t => t.category === '其他');
case 'completed': return tasks.filter(t => t.completed);
case 'active': return tasks.filter(t => !t.completed);
case 'expiring':
return tasks.filter(t => {
if (!t.deadline || t.completed) return false;
const deadline = new Date(t.deadline);
return deadline - now <= 10 * 60 * 1000 && deadline > now;
});
default: return tasks;
}
}
// 获取过滤标题
function getFilterTitle() {
const titles = {
'all': '暂无任务',
'work': '暂无工作任务',
'study': '暂无学习任务',
'life': '暂无生活任务',
'other': '暂无其他任务',
'completed': '暂无已完成任务',
'active': '暂无未完成任务',
'expiring': '暂无即将到期的任务'
};
return titles[currentFilter] || '暂无任务';
}
// 获取过滤消息
function getFilterMessage() {
const messages = {
'all': '添加您的第一个任务开始吧!',
'work': '添加工作任务提高效率',
'study': '添加学习任务充实自己',
'life': '添加生活任务让生活更有序',
'other': '添加其他任务记录一切',
'completed': '完成更多任务吧!',
'active': '快去完成您的任务吧!',
'expiring': '没有即将到期的任务,太棒了!'
};
return messages[currentFilter] || '添加您的第一个任务开始吧!';
}
// 切换完成状态
function toggleComplete(id) {
const task = tasks.find(t => t.id === id);
if (task) {
task.completed = !task.completed;
saveTasks();
renderTasks();
updateStats();
showNotification(task.completed ? '任务标记为完成!' : '任务已重新激活!');
}
}
// 删除任务
function deleteTask(id) {
if (confirm('确定要删除这个任务吗?')) {
tasks = tasks.filter(t => t.id !== id);
saveTasks();
renderTasks();
updateStats();
showNotification('任务已删除');
}
}
// 打开编辑模态框
function openEditModal(id) {
const task = tasks.find(t => t.id === id);
if (task) {
currentEditId = id;
editTaskInput.value = task.text;
editModal.style.display = 'flex';
editTaskInput.focus();
editTaskInput.select();
}
}
// 保存编辑
saveEditBtn.addEventListener('click', saveEdit);
editTaskInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') saveEdit();
});
function saveEdit() {
const newText = editTaskInput.value.trim();
if (!newText) return;
const task = tasks.find(t => t.id === currentEditId);
if (task) {
task.text = newText;
saveTasks();
renderTasks();
closeEditModal();
showNotification('任务已更新');
}
}
// 取消编辑
cancelEditBtn.addEventListener('click', closeEditModal);
function closeEditModal() {
editModal.style.display = 'none';
currentEditId = null;
}
// 点击模态框背景关闭
editModal.addEventListener('click', (e) => {
if (e.target === editModal) closeEditModal();
});
// 过滤按钮事件
filterBtns.forEach(btn => {
btn.addEventListener('click', () => {
filterBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
currentFilter = btn.dataset.filter;
renderTasks();
});
});
// 更新统计
function updateStats() {
totalTasks.textContent = tasks.length;
completedTasks.textContent = tasks.filter(t => t.completed).length;
}
// 保存到localStorage
function saveTasks() {
localStorage.setItem('tasks', JSON.stringify(tasks));
}
// 工具函数
function getPriorityText(priority) {
const map = { 'high': '高', 'medium': '中', 'low': '低' };
return map[priority] || '中';
}
function formatDate(isoString) {
const date = new Date(isoString);
const now = new Date();
const diff = Math.floor((now - date) / (1000 * 60 * 60 * 24));
if (diff === 0) return '今天';
if (diff === 1) return '昨天';
if (diff < 7) return `${diff}天前`;
return date.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' });
}
function formatDateTimeLocal(date) {
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const day = date.getDate().toString().padStart(2, '0');
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
return `${year}-${month}-${day}T${hours}:${minutes}`;
}
// 显示通知
function showNotification(message) {
// 创建通知元素
const notification = document.createElement('div');
notification.textContent = message;
notification.style.cssText = `
position: fixed;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
background: #2c3e50;
color: white;
padding: 12px 25px;
border-radius: 50px;
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
z-index: 2000;
animation: fadeIn 0.3s, fadeOut 0.3s 2.5s forwards;
font-weight: 500;
`;
// 添加动画样式
if (!document.getElementById('notificationStyles')) {
const style = document.createElement('style');
style.id = 'notificationStyles';
style.textContent = `
@keyframes fadeIn {
from { opacity: 0; transform: translate(-50%, 20px); }
to { opacity: 1; transform: translate(-50%, 0); }
}
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
`;
document.head.appendChild(style);
}
document.body.appendChild(notification);
// 2.8秒后移除
setTimeout(() => {
notification.remove();
}, 2800);
}
// 点击页面其他地方关闭模态框
window.addEventListener('click', (e) => {
if (e.target === editModal) {
closeEditModal();
}
});
</script>
</body>
</html>