This commit is contained in:
+1
-22
@@ -27,7 +27,6 @@ const trash = ref<Task[]>([])
|
||||
const activeList = ref('')
|
||||
const activeView = ref<View>('tasks')
|
||||
const selectedTask = ref<Task | null>(null)
|
||||
const selectedIds = ref(new Set<string>())
|
||||
const title = ref('')
|
||||
const query = ref('')
|
||||
const error = ref('')
|
||||
@@ -64,8 +63,6 @@ const visibleTasks = computed(() => {
|
||||
})
|
||||
const filteredTaskTree = computed(() => groupTaskTree(visibleTasks.value))
|
||||
const taskTree = computed(() => filteredTaskTree.value)
|
||||
const selectedCount = computed(() => selectedIds.value.size)
|
||||
const parentTasks = computed(() => tasks.value.filter((task) => !task.parent_id))
|
||||
let searchTimer: number | undefined
|
||||
watch(query, () => {
|
||||
if (searchTimer) window.clearTimeout(searchTimer)
|
||||
@@ -177,7 +174,7 @@ async function switchView(view: View, listId?: string) {
|
||||
activeView.value = view
|
||||
if (listId) activeList.value = listId
|
||||
page.value = 1
|
||||
selectedTask.value = null; selectedIds.value = new Set(); mobileSidebar.value = false; mobileDetail.value = false
|
||||
selectedTask.value = null; mobileSidebar.value = false; mobileDetail.value = false
|
||||
if (view === 'trash') await loadTrash()
|
||||
else await loadAll()
|
||||
}
|
||||
@@ -227,22 +224,6 @@ function selectTask(task: Task) {
|
||||
selectedTask.value = { ...task, tags: task.tags ? [...task.tags] : [] }
|
||||
markdownPreview.value = false; mobileDetail.value = true
|
||||
}
|
||||
function toggleSelect(id: string) {
|
||||
const next = new Set(selectedIds.value); next.has(id) ? next.delete(id) : next.add(id); selectedIds.value = next
|
||||
}
|
||||
async function batch(action: 'complete' | 'move' | 'delete') {
|
||||
if (!selectedIds.value.size) return
|
||||
const payload: Record<string, unknown> = { task_ids: [...selectedIds.value] }
|
||||
if (action === 'complete') payload.completed = true
|
||||
if (action === 'move') {
|
||||
const listId = window.prompt('输入目标清单 ID', activeList.value)?.trim(); if (!listId) return; payload.list_id = listId
|
||||
}
|
||||
try {
|
||||
if (action === 'delete') payload.soft_delete = true
|
||||
await api('/tasks/batch', { method: 'POST', body: JSON.stringify(payload) })
|
||||
selectedIds.value = new Set(); await loadAll(); toast(action === 'delete' ? '已批量移到回收站' : '批量操作完成')
|
||||
} catch (reason) { fail(reason) }
|
||||
}
|
||||
async function createFolder() {
|
||||
const name = window.prompt('文件夹名称')?.trim(); if (!name) return
|
||||
try { folders.value.push(await api('/folders', { method: 'POST', body: JSON.stringify({ name }) })); toast('文件夹已创建') } catch (reason) { fail(reason) }
|
||||
@@ -333,11 +314,9 @@ onMounted(bootstrap)
|
||||
<form v-if="activeView!=='trash'" class="quick" @submit.prevent="addTask"><CirclePlus/><input v-model="title" class="quick-input" placeholder="添加任务,按回车保存"><button>添加</button></form>
|
||||
<div class="list-toolbar"><label v-if="activeView!=='trash'"><input v-model="showCompleted" type="checkbox"> 显示已完成</label><span>第 {{page}} / {{totalPages}} 页 · 共 {{totalTasks}} 项</span><button v-if="query" class="link" @click="query=''">清除搜索</button></div>
|
||||
<div v-if="activeView!=='trash' && totalPages > 1" class="pager"><button class="secondary" :disabled="page<=1 || loading" @click="previousPage">上一页</button><span>{{page}} / {{totalPages}}</span><button class="secondary" :disabled="page>=totalPages || loading" @click="nextPage">下一页</button></div>
|
||||
<div v-if="selectedCount" class="batch-bar"><b>已选 {{selectedCount}} 项</b><button @click="batch('complete')"><Check/>完成</button><button @click="batch('move')"><Folder/>移动</button><button class="danger" @click="batch('delete')"><Trash2/>删除</button><button class="icon" aria-label="取消选择" @click="selectedIds=new Set()"><X/></button></div>
|
||||
<section class="task-list" :class="{loading}">
|
||||
<template v-for="node in taskTree" :key="node.task.id">
|
||||
<article class="task-row" :class="{done:node.task.completed,selected:selectedTask?.id===node.task.id}">
|
||||
<button v-if="activeView!=='trash'" class="select-box" :class="{checked:selectedIds.has(node.task.id)}" aria-label="选择任务" @click.stop="toggleSelect(node.task.id)"><Check v-if="selectedIds.has(node.task.id)"/></button>
|
||||
<button v-if="activeView!=='trash'" class="check" :class="`p${node.task.priority}`" aria-label="切换完成状态" @click.stop="toggle(node.task)"><Check v-if="node.task.completed"/></button>
|
||||
<button class="task-main" @click="activeView==='trash'?undefined:selectTask(node.task)"><strong>{{node.task.title}}</strong><span class="meta"><span v-if="node.task.due_at"><CalendarDays/>{{formatDue(node.task.due_at)}}</span><span v-if="node.subtasks.length"><ListChecks/>{{node.subtasks.filter(t=>t.completed).length}}/{{node.subtasks.length}}</span><i v-for="tag in node.task.tags" :key="tag.id" class="tag-dot" :style="{background:tag.color}" :title="tag.name"/></span></button>
|
||||
<span v-if="node.task.priority" class="priority" :class="`p${node.task.priority}`">{{['','低','中','高'][node.task.priority]}}</span>
|
||||
|
||||
Reference in New Issue
Block a user