v1.8.0 — 任务checkbox+删除线 + 拖拽排序 + 抽屉删除按钮
This commit is contained in:
@@ -283,11 +283,11 @@ function renderProjectTasks(projectId) {
|
||||
${phases.map((phase) => {
|
||||
const pt = tasks.filter((t) => t.phase === phase);
|
||||
if (!pt.length) return "";
|
||||
return `<div class="task-group"><div class="task-group-hd"><span class="task-group-icon"><i data-lucide="layers"></i></span><span class="task-group-label">${phase}</span><span class="task-group-n">${pt.length}</span></div><div class="task-group-list">${pt.map((t) => `<div class="task-row" data-id="${t.id}" onclick="openTaskForm(${projectId}, ${t.id})"><span class="task-dot"><i data-lucide="${t.status === 'done' ? 'check-circle' : 'circle'}"></i></span><div class="task-main"><span class="task-name">${t.task}</span>${t.notes ? `<span class="task-desc">${t.notes}</span>` : ""}${t.blockers ? `<span class="task-blocker">⚠ ${t.blockers}</span>` : ""}</div><span class="task-col">${t.owner || ""}</span><span class="task-col-badge">${t.due_date || ""}</span></div>`).join("")}</div></div>`;
|
||||
return `<div class="task-group"><div class="task-group-hd"><span class="task-group-icon"><i data-lucide="layers"></i></span><span class="task-group-label">${phase}</span><span class="task-group-n">${pt.length}</span></div><div class="task-group-list" data-phase="${phase}" ondrop="handleTaskDrop(event, ${projectId}, '${phase}')" ondragover="event.preventDefault(); event.currentTarget.classList.add('drag-over')" ondragleave="event.currentTarget.classList.remove('drag-over')">${pt.map((t) => `<div class="task-row ${t.status === 'done' ? 'task-done' : ''}" data-id="${t.id}" draggable="true" ondragstart="handleTaskDragStart(event, ${t.id})" ondragend="event.currentTarget.classList.remove('dragging')"><span class="task-dot" onclick="event.stopPropagation(); toggleTaskDone(${t.id}, ${projectId})"><i data-lucide="${t.status === 'done' ? 'check-circle' : 'circle'}"></i></span><div class="task-main" onclick="openTaskForm(${projectId}, ${t.id})"><span class="task-name">${t.task}</span>${t.notes ? `<span class="task-desc">${t.notes}</span>` : ""}${t.blockers ? `<span class="task-blocker">⚠ ${t.blockers}</span>` : ""}</div><span class="task-col">${t.owner || ""}</span><span class="task-col-badge">${t.due_date || ""}</span></div>`).join("")}</div></div>`;
|
||||
}).join("")}
|
||||
</div>
|
||||
<div id="task-drawer-${projectId}" class="task-drawer">
|
||||
<div class="task-drawer-hd"><span class="task-drawer-title">编辑任务</span><button class="task-close" onclick="closeTaskDrawer(${projectId})"><i data-lucide="x"></i></button></div>
|
||||
<div class="task-drawer-hd"><span class="task-drawer-title">编辑任务</span><div class="flex items-center gap-2"><button type="button" class="btn btn-ghost btn-sm text-red-600 hover:bg-red-50" onclick="deleteTask(${projectId})"><i data-lucide="trash-2"></i>删除</button><button class="task-close" onclick="closeTaskDrawer(${projectId})"><i data-lucide="x"></i></button></div></div>
|
||||
<form class="task-drawer-form" onsubmit="submitTaskForm(event, ${projectId})">
|
||||
<input type="hidden" name="task_id" id="task-id-${projectId}" value="">
|
||||
<label class="task-field"><span>任务名称</span><input name="task" required id="task-name-${projectId}"></label>
|
||||
@@ -586,6 +586,69 @@ window.deleteOperation = async (id) => {
|
||||
alert("删除失败:" + error.message);
|
||||
}
|
||||
};
|
||||
window.toggleTaskDone = async (taskId, projectId) => {
|
||||
const task = (state.data.tasks || []).find((t) => t.id === taskId);
|
||||
if (!task) return;
|
||||
const newStatus = task.status === "done" ? "" : "done";
|
||||
try {
|
||||
await api(`/api/tasks/${taskId}`, { method: "PUT", body: JSON.stringify({ data: { status: newStatus } }) });
|
||||
await load();
|
||||
} catch (error) {
|
||||
alert("更新失败:" + error.message);
|
||||
}
|
||||
};
|
||||
window.deleteTask = async (projectId) => {
|
||||
const taskId = document.querySelector(`#task-id-${projectId}`).value;
|
||||
if (!taskId) return;
|
||||
if (!confirm("确认删除该任务?此操作不可撤销。")) return;
|
||||
try {
|
||||
await api(`/api/tasks/${taskId}`, { method: "DELETE" });
|
||||
closeTaskDrawer(projectId);
|
||||
await load();
|
||||
} catch (error) {
|
||||
alert("删除失败:" + error.message);
|
||||
}
|
||||
};
|
||||
let dragTaskId = null;
|
||||
window.handleTaskDragStart = (event, taskId) => {
|
||||
dragTaskId = taskId;
|
||||
event.currentTarget.classList.add("dragging");
|
||||
event.dataTransfer.effectAllowed = "move";
|
||||
};
|
||||
window.handleTaskDrop = async (event, projectId, phase) => {
|
||||
event.preventDefault();
|
||||
event.currentTarget.classList.remove("drag-over");
|
||||
const target = event.currentTarget;
|
||||
if (!dragTaskId) return;
|
||||
// Find the dragged element and insert after the nearest task
|
||||
const dragged = document.querySelector(`.task-row[data-id="${dragTaskId}"]`);
|
||||
if (!dragged) return;
|
||||
const afterElement = getDragAfterElement(target, event.clientY);
|
||||
if (afterElement) {
|
||||
target.insertBefore(dragged, afterElement);
|
||||
} else {
|
||||
target.appendChild(dragged);
|
||||
}
|
||||
dragged.classList.remove("dragging");
|
||||
// Update sort_order in DB
|
||||
const rows = [...target.querySelectorAll(".task-row")];
|
||||
const updates = rows.map((row, i) => ({ id: parseInt(row.dataset.id), sort_order: i }));
|
||||
try {
|
||||
await api(`/api/tasks/batch-sort`, { method: "POST", body: JSON.stringify({ items: updates }) });
|
||||
} catch (e) { /* non-critical */ }
|
||||
dragTaskId = null;
|
||||
};
|
||||
function getDragAfterElement(container, y) {
|
||||
const elements = [...container.querySelectorAll(".task-row:not(.dragging)")];
|
||||
return elements.reduce((closest, child) => {
|
||||
const box = child.getBoundingClientRect();
|
||||
const offset = y - box.top - box.height / 2;
|
||||
if (offset < 0 && offset > closest.offset) {
|
||||
return { offset, element: child };
|
||||
}
|
||||
return closest;
|
||||
}, { offset: Number.NEGATIVE_INFINITY }).element;
|
||||
}
|
||||
window.closeDrawer = () => document.querySelector("#drawer").classList.remove("open");
|
||||
window.squireInstances = {};
|
||||
window.squireCmd = (cmd) => {
|
||||
|
||||
@@ -543,13 +543,17 @@ td {
|
||||
padding: 1px 7px; border-radius: 10px;
|
||||
}
|
||||
.task-group-list { display: flex; flex-direction: column; }
|
||||
.task-group-list.drag-over { background: #f0f9ff; }
|
||||
.task-row {
|
||||
display: flex; align-items: center; gap: 16px;
|
||||
padding: 10px 16px; border-top: 1px solid #f1f5f9;
|
||||
cursor: pointer;
|
||||
cursor: pointer; transition: background 0.15s;
|
||||
}
|
||||
.task-row.dragging { opacity: 0.4; background: #f1f5f9; }
|
||||
.task-row.task-done .task-name { text-decoration: line-through; color: #94a3b8; }
|
||||
.task-row:hover { background: #f8fafc; }
|
||||
.task-dot { display: flex; color: #cbd5e1; flex-shrink: 0; }
|
||||
.task-dot { display: flex; color: #cbd5e1; flex-shrink: 0; cursor: pointer; }
|
||||
.task-dot:hover { color: #6366f1; }
|
||||
.task-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
|
||||
.task-name { color: #1e293b; font-size: 13px; }
|
||||
.task-desc { color: #94a3b8; font-size: 12px; word-wrap: break-word; overflow-wrap: break-word; }
|
||||
|
||||
Reference in New Issue
Block a user