You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

401 lines
13 KiB
JavaScript

// URL 参数处理
function getQueryParams() {
const params = new URLSearchParams(window.location.search);
return {
query: params.get('q') || '',
is_done: params.get('done') === null ? null : params.get('done') === 'true',
limit: parseInt(params.get('limit')) || 10,
sort: params.get('sort') || 'urgency'
};
}
function updateQueryParams(params) {
const url = new URL(window.location);
if (params.query) url.searchParams.set('q', params.query);
else url.searchParams.delete('q');
if (params.is_done !== null) url.searchParams.set('done', params.is_done);
else url.searchParams.delete('done');
if (params.limit !== 10) url.searchParams.set('limit', params.limit);
else url.searchParams.delete('limit');
if (params.sort !== 'urgency') url.searchParams.set('sort', params.sort);
else url.searchParams.delete('sort');
window.history.pushState({}, '', url);
}
// 表单初始化
function initializeFormValues() {
const params = getQueryParams();
document.getElementById('searchInput').value = params.query;
document.getElementById('statusFilter').value = params.is_done === null ? '' : params.is_done;
document.getElementById('limitFilter').value = params.limit;
document.getElementById('sortFilter').value = params.sort;
}
// 时间处理函数
function formatDate(dateStr) {
if (!dateStr) return '';
const date = new Date(dateStr);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
}
function calculateUrgency(task) {
if (!task.deadline || task.completed) return Infinity;
const now = new Date();
const deadline = new Date(task.deadline);
const hoursLeft = (deadline - now) / (1000 * 60 * 60);
return hoursLeft;
}
function formatTimeDiff(diffMs) {
const hours = Math.floor(Math.abs(diffMs) / (1000 * 60 * 60));
const minutes = Math.floor((Math.abs(diffMs) % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((Math.abs(diffMs) % (1000 * 60)) / 1000);
if (hours > 2) {
return `${hours} 小时`;
} else if (hours > 0) {
return `${hours} 小时 ${minutes} 分钟`;
} else if (minutes > 0) {
return `${minutes} 分钟 ${seconds}`;
} else {
return `${seconds}`;
}
}
function getDeadlineStatus(deadline, completed) {
if (!deadline) return null;
if (completed) return { status: 'completed', class: 'text-gray-500' };
const now = new Date();
const deadlineDate = new Date(deadline);
const hoursLeft = (deadlineDate - now) / (1000 * 60 * 60);
if (hoursLeft < 0) return { status: 'error', class: 'text-red-600 font-bold' };
if (hoursLeft <= 12) return { status: 'warning', class: 'text-yellow-600 font-bold' };
return { status: 'normal', class: 'text-gray-500' };
}
// 倒计时处理
function updateCountdown(element, deadline) {
const now = new Date();
const diffMs = deadline - now;
if (diffMs <= 0) {
loadTasks();
return;
}
let text = `截止: ${formatDate(deadline)}`;
if (diffMs > 0) {
text += ` (剩余 ${formatTimeDiff(diffMs)})`;
} else {
text += ` (已超出 ${formatTimeDiff(Math.abs(diffMs))})`;
}
element.textContent = text;
}
// Task 列表处理
async function loadTasks() {
const params = getQueryParams();
try {
const response = await fetch('/task/api', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'list',
list: params
})
});
const data = await response.json();
if (data.status === 'success') {
renderTasks(data.task_list);
}
} catch (error) {
console.error('Failed to load tasks:', error);
}
}
function renderTasks(tasks) {
const list = document.getElementById('taskList');
const template = document.getElementById('taskTemplate');
list.innerHTML = '';
// 清除所有现有的倒计时定时器
if (window.countdownTimers) {
window.countdownTimers.forEach(timer => clearInterval(timer));
}
window.countdownTimers = [];
// 排序
const sortType = document.getElementById('sortFilter').value;
tasks.sort((a, b) => {
if (sortType === 'urgency') {
return calculateUrgency(a) - calculateUrgency(b);
} else if (sortType === 'deadline') {
if (!a.deadline) return 1;
if (!b.deadline) return -1;
return new Date(a.deadline) - new Date(b.deadline);
} else { // created
return new Date(b.created_at) - new Date(a.created_at);
}
});
tasks.forEach(task => {
const clone = template.content.cloneNode(true);
const item = clone.querySelector('.task-item');
item.dataset.id = task.id;
item.dataset.task = JSON.stringify(task);
item.querySelector('.task-checkbox').checked = task.completed;
item.querySelector('.task-title').textContent = task.title;
item.querySelector('.task-content').textContent = task.content;
item.querySelector('.task-created').textContent = `创建: ${formatDate(task.created_at)}`;
if (task.deadline) {
const deadlineEl = item.querySelector('.task-deadline');
const status = getDeadlineStatus(task.deadline, task.completed);
deadlineEl.className = `task-deadline ${status.class}`;
const now = new Date();
const deadline = new Date(task.deadline);
const hoursLeft = (deadline - now) / (1000 * 60 * 60);
const needsCountdown = !task.completed && hoursLeft > 0 && hoursLeft <= 2;
if (needsCountdown) {
updateCountdown(deadlineEl, deadline);
const timer = setInterval(() => {
updateCountdown(deadlineEl, deadline);
}, 1000);
window.countdownTimers.push(timer);
} else {
const diffMs = deadline - now;
let text = `截止: ${formatDate(task.deadline)}`;
if (!task.completed) {
text += ` (${diffMs > 0 ? '剩余 ' : '已超出 '}${formatTimeDiff(diffMs)})`;
}
deadlineEl.textContent = text;
}
}
if (task.completed) {
item.querySelector('.task-title').classList.add('line-through', 'text-gray-500');
item.querySelector('.task-content').classList.add('line-through', 'text-gray-500');
}
list.appendChild(clone);
});
}
// 对话框处理
function openAddDialog() {
document.getElementById('addDialog').classList.remove('hidden');
document.getElementById('addForm').reset();
}
function closeAddDialog() {
document.getElementById('addDialog').classList.add('hidden');
}
function openEditDialog(task) {
const dialog = document.getElementById('editDialog');
const form = document.getElementById('editForm');
form.id.value = task.id;
form.title.value = task.title;
form.content.value = task.content;
form.deadline.value = task.deadline ? task.deadline.slice(0, 16) : '';
dialog.classList.remove('hidden');
}
function closeEditDialog() {
document.getElementById('editDialog').classList.add('hidden');
}
// 事件处理
function debounce(func, delay) {
let timer;
return function() {
clearTimeout(timer);
timer = setTimeout(() => func.apply(this, arguments), delay);
}
}
const handleFilterChange = debounce(() => {
const params = {
query: document.getElementById('searchInput').value,
is_done: document.getElementById('statusFilter').value === '' ? null : document.getElementById('statusFilter').value === 'true',
limit: parseInt(document.getElementById('limitFilter').value),
sort: document.getElementById('sortFilter').value
};
updateQueryParams(params);
loadTasks();
}, 300);
// 在文件顶部添加定时器变量
let autoRefreshTimer;
// 初始化事件监听
document.addEventListener('DOMContentLoaded', () => {
// 添加任务
document.getElementById('addTaskBtn').addEventListener('click', openAddDialog);
document.getElementById('addForm').addEventListener('submit', async (e) => {
e.preventDefault();
const form = e.target;
const task = {
title: form.title.value,
content: form.content.value,
deadline: form.deadline.value
};
try {
const response = await fetch('/task/api', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'add',
task: task
})
});
const data = await response.json();
if (data.status === 'success') {
closeAddDialog();
form.reset();
loadTasks();
}
} catch (error) {
console.error('Failed to add task:', error);
}
});
// 更新任务状态
document.getElementById('taskList').addEventListener('change', async (e) => {
if (e.target.matches('.task-checkbox')) {
const item = e.target.closest('.task-item');
const id = item.dataset.id;
const completed = e.target.checked;
try {
const response = await fetch('/task/api', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'update',
task: { id, completed }
})
});
const data = await response.json();
if (data.status === 'success') {
loadTasks();
}
} catch (error) {
console.error('Failed to update task:', error);
}
}
});
// 删除任务
document.getElementById('taskList').addEventListener('click', async (e) => {
if (e.target.closest('.delete-btn')) {
const item = e.target.closest('.task-item');
const id = item.dataset.id;
if (!confirm('确定要删除这个任务吗?')) return;
try {
const response = await fetch('/task/api', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'delete',
task: { id }
})
});
const data = await response.json();
if (data.status === 'success') {
loadTasks();
}
} catch (error) {
console.error('Failed to delete task:', error);
}
}
});
// 编辑任务
document.getElementById('taskList').addEventListener('click', async (e) => {
if (e.target.closest('.edit-btn')) {
const item = e.target.closest('.task-item');
const task = JSON.parse(item.dataset.task);
openEditDialog(task);
}
});
document.getElementById('editForm').addEventListener('submit', async (e) => {
e.preventDefault();
const form = e.target;
const task = {
id: form.id.value,
title: form.title.value,
content: form.content.value,
deadline: form.deadline.value
};
try {
const response = await fetch('/task/api', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'update',
task: task
})
});
const data = await response.json();
if (data.status === 'success') {
closeEditDialog();
loadTasks();
}
} catch (error) {
console.error('Failed to update task:', error);
}
});
// 筛选事件
document.getElementById('searchInput').addEventListener('input', handleFilterChange);
document.getElementById('statusFilter').addEventListener('change', handleFilterChange);
document.getElementById('limitFilter').addEventListener('change', handleFilterChange);
document.getElementById('sortFilter').addEventListener('change', handleFilterChange);
// 浏览器前进/后退
window.addEventListener('popstate', () => {
initializeFormValues();
loadTasks();
});
// 设置自动刷新定时器
autoRefreshTimer = setInterval(loadTasks, 10000); // 每10秒刷新一次
// 修改页面卸载事件处理,清理所有定时器
window.addEventListener('beforeunload', () => {
if (window.countdownTimers) {
window.countdownTimers.forEach(timer => clearInterval(timer));
}
if (autoRefreshTimer) {
clearInterval(autoRefreshTimer);
}
});
// 初始化
initializeFormValues();
loadTasks();
});