feat: align upcoming with list layout
This commit is contained in:
+20
-16
@@ -369,7 +369,6 @@ function updateTodayHabitSummary(value: { total: number; completed: number }) {
|
||||
const sourceTasks = computed(() => activeView.value === 'trash' ? trash.value : tasks.value)
|
||||
const visibleTasks = computed(() => {
|
||||
const now = new Date()
|
||||
const end = new Date(now); end.setDate(end.getDate() + 7)
|
||||
let result = sourceTasks.value
|
||||
if (['habits','settings','countdowns','memos'].includes(activeView.value)) return []
|
||||
if (activeView.value === 'today') result = result.filter((task) => {
|
||||
@@ -377,7 +376,7 @@ const visibleTasks = computed(() => {
|
||||
const completedToday = task.completed_at && new Date(task.completed_at).toDateString() === now.toDateString()
|
||||
return Boolean(dueToday || completedToday)
|
||||
})
|
||||
if (activeView.value === 'upcoming') result = result.filter((task) => task.due_at && new Date(task.due_at) >= now && new Date(task.due_at) <= end)
|
||||
if (activeView.value === 'upcoming') result = result.filter((task) => task.due_at && new Date(task.due_at) >= startOfLocalDay(0) && new Date(task.due_at) < startOfLocalDay(8))
|
||||
return query.value.trim() ? filterTasks(result, query.value) : result
|
||||
})
|
||||
const selectedTaskSubtasks = computed(() => selectedTask.value?.subtasks ?? [])
|
||||
@@ -505,11 +504,14 @@ async function loadTaskPages(path: string) {
|
||||
} while (cursor)
|
||||
return rows
|
||||
}
|
||||
function isoAtLocalDayOffset(offset: number) {
|
||||
function startOfLocalDay(offset: number) {
|
||||
const day = new Date()
|
||||
day.setDate(day.getDate() + offset)
|
||||
day.setHours(0, 0, 0, 0)
|
||||
return day.toISOString()
|
||||
return day
|
||||
}
|
||||
function isoAtLocalDayOffset(offset: number) {
|
||||
return startOfLocalDay(offset).toISOString()
|
||||
}
|
||||
async function loadTodayTaskSummary() {
|
||||
const token = ++todaySummaryLoadToken
|
||||
@@ -585,15 +587,18 @@ async function loadTasksPage(request = beginLatestRequest('tasks')) {
|
||||
if (!isLatestRequest('tasks', request)) return
|
||||
tasks.value = data.items ?? []
|
||||
totalTasks.value = data.total ?? tasks.value.length
|
||||
if (activeView.value === 'upcoming' && !showCompleted.value && !query.value) taskOpenTotal.value = totalTasks.value
|
||||
if (activeView.value === 'tasks' && !showCompleted.value && !query.value) taskOpenTotal.value = totalTasks.value
|
||||
if (activeView.value === 'tasks' && (showCompleted.value || query.value)) {
|
||||
if ((activeView.value === 'tasks' || activeView.value === 'upcoming') && (showCompleted.value || query.value)) {
|
||||
const token = ++taskOpenTotalLoadToken
|
||||
const listId = activeList.value
|
||||
const openParams = new URLSearchParams({ page: '1', page_size: '1', completed: 'false' })
|
||||
if (listId) openParams.set('list_id', listId)
|
||||
if (activeView.value === 'tasks' && listId) openParams.set('list_id', listId)
|
||||
if (activeView.value === 'upcoming') { openParams.set('due_from', isoAtLocalDayOffset(0)); openParams.set('due_to', isoAtLocalDayOffset(8)) }
|
||||
const openView = activeView.value
|
||||
loadTaskOpenTotal(
|
||||
() => api(`/tasks?${openParams}`),
|
||||
() => token === taskOpenTotalLoadToken && isLatestRequest('tasks', request) && activeView.value === 'tasks' && activeList.value === listId,
|
||||
() => token === taskOpenTotalLoadToken && isLatestRequest('tasks', request) && activeView.value === openView && (openView !== 'tasks' || activeList.value === listId),
|
||||
value => { taskOpenTotal.value = value },
|
||||
)
|
||||
}
|
||||
@@ -1685,16 +1690,15 @@ onUnmounted(() => {
|
||||
</AppSheet>
|
||||
</aside>
|
||||
|
||||
<main :class="{'today-main':activeView==='today','list-main':activeView==='tasks'||activeView==='habits'}" @touchstart="startSearchPull" @touchmove="moveSearchPull" @touchend="finishSearchPull" @touchcancel="cancelSearchPull">
|
||||
<main :class="{'today-main':activeView==='today','list-main':activeView==='tasks'||activeView==='upcoming'||activeView==='habits'}" @touchstart="startSearchPull" @touchmove="moveSearchPull" @touchend="finishSearchPull" @touchcancel="cancelSearchPull">
|
||||
<header class="topbar" :class="{'settings-topbar':activeView==='settings'}" :inert="memoBackgroundInert ? true : undefined">
|
||||
<button class="icon" :aria-label="(sidebarCollapsed ? '展开' : '收起') + '菜单'" :aria-expanded="sidebarCollapsed ? 'false' : 'true'" @click="toggleSidebar"><Menu /></button>
|
||||
<div v-if="!['today','tasks','habits','settings'].includes(activeView)" class="topbar-title"><h1 :title="activeName">{{ activeName }}</h1></div>
|
||||
<div v-if="!['today','tasks','upcoming','habits','settings'].includes(activeView)" class="topbar-title"><h1 :title="activeName">{{ activeName }}</h1></div>
|
||||
<div v-if="['today', 'tasks', 'upcoming', 'habits'].includes(activeView)" class="topbar-actions">
|
||||
<CompletedFilterPill v-if="activeView==='upcoming'" v-model="showCompleted" class="topbar-filter" />
|
||||
<button class="icon topbar-refresh" :class="{spinning:refreshing}" type="button" aria-label="刷新当前页面" title="刷新" :disabled="refreshing || loading" @click="refreshCurrentView"><RefreshCw /></button>
|
||||
</div>
|
||||
<button v-if="activeView==='settings'" class="icon topbar-refresh settings-refresh" :class="{spinning:refreshing}" type="button" aria-label="刷新设置" title="刷新" :disabled="refreshing || loading" @click="refreshCurrentView"><RefreshCw /></button>
|
||||
<div v-if="taskSearchAvailable && activeView!=='tasks'" class="search-reveal" :class="{'mobile-search-open':mobileSearchOpen,'mobile-search-pulling':searchPullDistance>0}" :style="searchRevealStyle">
|
||||
<div v-if="taskSearchAvailable && !['tasks','upcoming'].includes(activeView)" class="search-reveal" :class="{'mobile-search-open':mobileSearchOpen,'mobile-search-pulling':searchPullDistance>0}" :style="searchRevealStyle">
|
||||
<button ref="taskSearchToggle" class="task-search-toggle" type="button" :aria-label="mobileSearchOpen ? '收起搜索任务' : '展开搜索任务'" :aria-expanded="mobileSearchOpen" aria-controls="task-search-panel" @click="toggleTaskSearch"><Search/></button>
|
||||
<span class="search-pull-hint" aria-hidden="true">{{searchPullDistance >= 56 ? '松开搜索' : '下拉搜索'}}</span>
|
||||
<label id="task-search-panel" class="search"><Search/><input ref="searchInput" v-model="query" placeholder="搜索任务…" aria-label="搜索任务" @keydown="handleTaskSearchKeydown"><kbd>⌘ K</kbd></label>
|
||||
@@ -1713,11 +1717,11 @@ onUnmounted(() => {
|
||||
<CompletedFilterPill v-model="showCompleted" class="today-inline-filter" />
|
||||
</div>
|
||||
</section>
|
||||
<section v-if="activeView==='tasks'" class="list-page-context">
|
||||
<section v-if="activeView==='tasks' || activeView==='upcoming'" class="list-page-context">
|
||||
<div><h1 class="list-page-title" :title="activeName">{{ activeName }}</h1><p class="list-page-summary">{{ taskOpenTotal === null ? '待完成统计暂不可用' : `还有 ${taskOpenTotal} 项待完成` }}</p></div>
|
||||
<CompletedFilterPill v-model="showCompleted" class="list-inline-filter" />
|
||||
</section>
|
||||
<div v-if="activeView==='tasks'" class="list-search-reveal" :class="{'mobile-search-open':mobileSearchOpen}">
|
||||
<div v-if="activeView==='tasks' || activeView==='upcoming'" class="list-search-reveal" :class="{'mobile-search-open':mobileSearchOpen}">
|
||||
<label id="task-list-search-panel" class="list-search"><Search/><input ref="searchInput" v-model="query" placeholder="搜索任务…" aria-label="搜索任务" @keydown="handleTaskSearchKeydown"><kbd>⌘ K</kbd></label>
|
||||
<button v-if="query" class="list-search-clear" type="button" @click="query=''">清除搜索</button>
|
||||
<button ref="taskSearchToggle" class="task-search-toggle" type="button" :aria-label="mobileSearchOpen ? '收起搜索任务' : '展开搜索任务'" :aria-expanded="mobileSearchOpen" aria-controls="task-list-search-panel" @click="toggleTaskSearch"><Search/></button>
|
||||
@@ -1733,11 +1737,11 @@ onUnmounted(() => {
|
||||
</section>
|
||||
<button id="today-tasks-heading" class="today-section-toggle today-section-anchor" type="button" :aria-expanded="!todaySectionCollapse.tasks" aria-controls="today-tasks" @click="toggleTodaySection('tasks')"><span class="today-section-title">今天</span><span class="today-section-summary">{{totalTasks}}</span><span class="today-section-chevron" aria-hidden="true">{{ todaySectionCollapse.tasks ? '›' : '⌄' }}</span></button>
|
||||
</template>
|
||||
<div v-if="activeView==='tasks'" id="task-list-heading" class="list-section-heading"><span id="task-list-title" class="list-section-title">任务</span><span class="list-section-count">{{ totalTasks }}</span><button v-if="taskReorderAvailable" class="list-section-action" type="button" :aria-pressed="taskReorderMode" @click="taskReorderMode=!taskReorderMode;cancelTaskReorder()">{{ taskReorderMode ? '完成' : '调整顺序' }}</button></div>
|
||||
<div v-if="activeView!=='today' && activeView!=='tasks'" class="list-toolbar"><span v-if="activeView==='trash' || totalPages > 1 || totalTasks > 0">{{ totalPages > 1 ? `第 ${page} / ${totalPages} 页 · ` : '' }}共 {{totalTasks}} 项</span><span class="list-toolbar-actions"><button v-if="query" class="link" @click="query=''">清除搜索</button><button v-if="taskReorderAvailable" class="soft-button reorder-mode-toggle task-reorder-toggle" type="button" :aria-pressed="taskReorderMode" @click="taskReorderMode=!taskReorderMode;cancelTaskReorder()">{{ taskReorderMode ? '完成' : '调整顺序' }}</button></span></div>
|
||||
<div v-if="activeView==='tasks' || activeView==='upcoming'" id="task-list-heading" class="list-section-heading"><span id="task-list-title" class="list-section-title">任务</span><span class="list-section-count">{{ totalTasks }}</span><button v-if="activeView==='tasks' && taskReorderAvailable" class="list-section-action" type="button" :aria-pressed="taskReorderMode" @click="taskReorderMode=!taskReorderMode;cancelTaskReorder()">{{ taskReorderMode ? '完成' : '调整顺序' }}</button></div>
|
||||
<div v-if="activeView==='trash'" class="list-toolbar"><span v-if="activeView==='trash' || totalPages > 1 || totalTasks > 0">{{ totalPages > 1 ? `第 ${page} / ${totalPages} 页 · ` : '' }}共 {{totalTasks}} 项</span><span class="list-toolbar-actions"><button v-if="query" class="link" @click="query=''">清除搜索</button><button v-if="taskReorderAvailable" class="soft-button reorder-mode-toggle task-reorder-toggle" type="button" :aria-pressed="taskReorderMode" @click="taskReorderMode=!taskReorderMode;cancelTaskReorder()">{{ taskReorderMode ? '完成' : '调整顺序' }}</button></span></div>
|
||||
<div v-if="activeView==='tasks' && totalPages > 1" class="list-page-meta"><span>第 {{ page }} / {{ totalPages }} 页 · 共 {{ totalTasks }} 项</span></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>
|
||||
<section :id="activeView==='today' ? 'today-tasks' : undefined" class="task-list plain-list" :class="{loading}" v-show="activeView!=='today' || !todaySectionCollapse.tasks" :role="activeView==='today' ? 'region' : undefined" :aria-labelledby="activeView==='today' ? 'today-tasks-heading' : activeView==='tasks' ? 'task-list-title' : undefined">
|
||||
<section :id="activeView==='today' ? 'today-tasks' : undefined" class="task-list plain-list" :class="{loading}" v-show="activeView!=='today' || !todaySectionCollapse.tasks" :role="activeView==='today' ? 'region' : undefined" :aria-labelledby="activeView==='today' ? 'today-tasks-heading' : (activeView==='tasks' || activeView==='upcoming') ? 'task-list-title' : undefined">
|
||||
<template v-for="node in taskTree" :key="node.task.id">
|
||||
<article :data-task-id="node.task.id" class="task-row swipeable" :class="{done:node.task.completed,'task-row--trash':activeView==='trash','just-completed': justCompletedTaskIds.has(node.task.id),'completion-exiting':completionExitingTaskIds.has(node.task.id),selected:selectedTask?.id===node.task.id,ready:Math.abs(taskSwipeOffsets[node.task.id] ?? 0) >= 64,reordering:taskReorder?.id===node.task.id,'reorder-target':taskReorderTarget===node.task.id}" :style="{ '--swipe-x': `${taskSwipeOffsets[node.task.id] ?? 0}px`, '--reorder-y': `${taskReorder?.id === node.task.id ? taskReorder.offsetY : 0}px` }" @pointerdown="startTaskPointer(node.task, $event)" @pointermove="moveTaskPointer(node.task, $event)" @pointerup="finishTaskPointer(node.task, $event)" @pointercancel="cancelTaskPointer(node.task)" @touchstart.passive="startTaskSwipe(node.task, $event)" @touchmove.passive="moveTaskSwipe(node.task, $event)" @touchend="finishTaskSwipe(node.task, $event)" @touchcancel="cancelTaskSwipe(node.task)">
|
||||
<button v-if="taskReorderMode" class="drag-handle task-drag-handle" :disabled="Boolean(query) || totalPages > 1" aria-label="上下拖动任务排序" title="上下拖动排序" @pointerdown.stop="startTaskReorder(node.task, $event)" @pointermove.stop="moveTaskReorder(node.task, $event)" @pointerup.stop="finishTaskReorder(node.task, $event)" @pointercancel.stop="cancelTaskReorder"><GripVertical/></button>
|
||||
|
||||
Reference in New Issue
Block a user