fix: harden today progress summary
This commit is contained in:
+15
-11
@@ -174,7 +174,7 @@ async function submitTaskCompose() {
|
|||||||
tasks.value.push(task)
|
tasks.value.push(task)
|
||||||
totalTasks.value = nextTotalAfterLocalTaskAdd(totalTasks.value)
|
totalTasks.value = nextTotalAfterLocalTaskAdd(totalTasks.value)
|
||||||
}
|
}
|
||||||
if (activeView.value === 'today') await loadTodayTaskSummary()
|
if (activeView.value === 'today') void loadTodayTaskSummary()
|
||||||
taskComposeOpen.value = false
|
taskComposeOpen.value = false
|
||||||
toast('任务已添加')
|
toast('任务已添加')
|
||||||
} catch (reason) { fail(reason) }
|
} catch (reason) { fail(reason) }
|
||||||
@@ -229,7 +229,7 @@ const progressWidth = (completed: number, total: number) => `${total > 0 ? Math.
|
|||||||
const todayTaskProgressPercent = computed(() => progressWidth(todayTaskCompleted.value, todayTaskTotal.value))
|
const todayTaskProgressPercent = computed(() => progressWidth(todayTaskCompleted.value, todayTaskTotal.value))
|
||||||
const todayHabitProgressPercent = computed(() => progressWidth(todayHabitCompleted.value, todayHabitTotal.value))
|
const todayHabitProgressPercent = computed(() => progressWidth(todayHabitCompleted.value, todayHabitTotal.value))
|
||||||
const todayTaskProgressHint = computed(() => todayTaskTotal.value ? `还剩 ${todayOpenTasks.value} 件` : '今天暂无任务')
|
const todayTaskProgressHint = computed(() => todayTaskTotal.value ? `还剩 ${todayOpenTasks.value} 件` : '今天暂无任务')
|
||||||
const todayHabitProgressHint = computed(() => todayHabitTotal.value ? `还差 ${Math.max(todayHabitTotal.value - todayHabitCompleted.value, 0)} 次` : '今天暂无习惯')
|
const todayHabitProgressHint = computed(() => todayHabitTotal.value ? `还有 ${Math.max(todayHabitTotal.value - todayHabitCompleted.value, 0)} 项未完成` : '今天暂无习惯')
|
||||||
function scrollTodaySection(id: 'today-tasks' | 'today-habits') {
|
function scrollTodaySection(id: 'today-tasks' | 'today-habits') {
|
||||||
document.getElementById(id)?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
document.getElementById(id)?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||||
}
|
}
|
||||||
@@ -368,6 +368,7 @@ function isoAtLocalDayOffset(offset: number) {
|
|||||||
}
|
}
|
||||||
async function loadTodayTaskSummary() {
|
async function loadTodayTaskSummary() {
|
||||||
const token = ++todaySummaryLoadToken
|
const token = ++todaySummaryLoadToken
|
||||||
|
try {
|
||||||
const summaryTotal = async (completed: boolean) => {
|
const summaryTotal = async (completed: boolean) => {
|
||||||
const params = new URLSearchParams({ page: '1', page_size: '1' })
|
const params = new URLSearchParams({ page: '1', page_size: '1' })
|
||||||
params.set('due_from', isoAtLocalDayOffset(0))
|
params.set('due_from', isoAtLocalDayOffset(0))
|
||||||
@@ -380,6 +381,7 @@ async function loadTodayTaskSummary() {
|
|||||||
if (token !== todaySummaryLoadToken || activeView.value !== 'today') return
|
if (token !== todaySummaryLoadToken || activeView.value !== 'today') return
|
||||||
todayTaskCompleted.value = completed
|
todayTaskCompleted.value = completed
|
||||||
todayTaskTotal.value = open + completed
|
todayTaskTotal.value = open + completed
|
||||||
|
} catch { /* 概览统计失败不阻断今天页 */ }
|
||||||
}
|
}
|
||||||
async function loadOverdueTasks() {
|
async function loadOverdueTasks() {
|
||||||
const params = new URLSearchParams()
|
const params = new URLSearchParams()
|
||||||
@@ -422,8 +424,10 @@ async function loadAll() {
|
|||||||
try {
|
try {
|
||||||
if (!navigationLoaded.value) await loadNavigation()
|
if (!navigationLoaded.value) await loadNavigation()
|
||||||
await loadTasksPage()
|
await loadTasksPage()
|
||||||
if (activeView.value === 'today') await Promise.all([loadOverdueTasks(), loadTodayTaskSummary()])
|
if (activeView.value === 'today') {
|
||||||
else overdueTasks.value = []
|
await loadOverdueTasks()
|
||||||
|
void loadTodayTaskSummary()
|
||||||
|
} else overdueTasks.value = []
|
||||||
if (page.value > totalPages.value) { page.value = totalPages.value; await loadTasksPage() }
|
if (page.value > totalPages.value) { page.value = totalPages.value; await loadTasksPage() }
|
||||||
} catch (reason) { fail(reason) } finally { loading.value = false }
|
} catch (reason) { fail(reason) } finally { loading.value = false }
|
||||||
}
|
}
|
||||||
@@ -469,7 +473,7 @@ async function patchTask(task: Task, patch: Partial<Task>) {
|
|||||||
async function toggle(task: Task) {
|
async function toggle(task: Task) {
|
||||||
try {
|
try {
|
||||||
await patchTask(task, { completed: !task.completed })
|
await patchTask(task, { completed: !task.completed })
|
||||||
if (activeView.value === 'today') await loadTodayTaskSummary()
|
if (activeView.value === 'today') void loadTodayTaskSummary()
|
||||||
toast(task.completed ? '已重新打开' : '完成啦')
|
toast(task.completed ? '已重新打开' : '完成啦')
|
||||||
} catch (reason) { fail(reason) }
|
} catch (reason) { fail(reason) }
|
||||||
}
|
}
|
||||||
@@ -642,7 +646,7 @@ async function saveTask() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (selectedTask.value) selectedTask.value = { ...selectedTask.value, ...updated }
|
if (selectedTask.value) selectedTask.value = { ...selectedTask.value, ...updated }
|
||||||
if (activeView.value === 'today') await loadTodayTaskSummary()
|
if (activeView.value === 'today') void loadTodayTaskSummary()
|
||||||
toast('已保存')
|
toast('已保存')
|
||||||
} catch (reason) { fail(reason) }
|
} catch (reason) { fail(reason) }
|
||||||
}
|
}
|
||||||
@@ -653,7 +657,7 @@ async function removeTask(task: Task) {
|
|||||||
tasks.value = tasks.value.filter((item) => item.id !== task.id && item.parent_id !== task.id)
|
tasks.value = tasks.value.filter((item) => item.id !== task.id && item.parent_id !== task.id)
|
||||||
selectedTask.value = null
|
selectedTask.value = null
|
||||||
mobileDetail.value = false
|
mobileDetail.value = false
|
||||||
if (activeView.value === 'today') await loadTodayTaskSummary()
|
if (activeView.value === 'today') void loadTodayTaskSummary()
|
||||||
toast('已移到回收站')
|
toast('已移到回收站')
|
||||||
} catch (reason) { fail(reason) }
|
} catch (reason) { fail(reason) }
|
||||||
}
|
}
|
||||||
@@ -776,13 +780,13 @@ onMounted(bootstrap)
|
|||||||
<CountdownPanel ref="countdownComposer" v-else-if="activeView==='countdowns'" @notice="toast" />
|
<CountdownPanel ref="countdownComposer" v-else-if="activeView==='countdowns'" @notice="toast" />
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<section v-if="activeView==='today'" class="today-board" aria-label="今日进度">
|
<section v-if="activeView==='today'" class="today-board" aria-label="今日进度">
|
||||||
<button class="today-track today-task-track" type="button" @click="scrollTodaySection('today-tasks')">
|
<button class="today-track today-task-track" type="button" aria-controls="today-tasks" @click="scrollTodaySection('today-tasks')">
|
||||||
<span class="today-track-head"><strong>任务 {{ todayTaskCompleted }} / {{ todayTaskTotal }}</strong><small>{{ todayTaskProgressHint }}</small></span>
|
<span class="today-track-head"><strong>任务 {{ todayTaskCompleted }} / {{ todayTaskTotal }}</strong><small>{{ todayTaskProgressHint }}</small></span>
|
||||||
<span class="today-track-rail" aria-hidden="true"><i class="today-track-fill" :style="{ width: todayTaskProgressPercent }" /></span>
|
<span class="today-track-rail" role="progressbar" aria-label="今日任务进度" aria-valuemin="0" :aria-valuemax="todayTaskTotal" :aria-valuenow="todayTaskCompleted"><i class="today-track-fill" :style="{ width: todayTaskProgressPercent }" /></span>
|
||||||
</button>
|
</button>
|
||||||
<button class="today-track today-habit-track" type="button" @click="scrollTodaySection('today-habits')">
|
<button class="today-track today-habit-track" type="button" aria-controls="today-habits" @click="scrollTodaySection('today-habits')">
|
||||||
<span class="today-track-head"><strong>习惯 {{ todayHabitProgress }}</strong><small>{{ todayHabitProgressHint }}</small></span>
|
<span class="today-track-head"><strong>习惯 {{ todayHabitProgress }}</strong><small>{{ todayHabitProgressHint }}</small></span>
|
||||||
<span class="today-track-rail" aria-hidden="true"><i class="today-track-fill" :style="{ width: todayHabitProgressPercent }" /></span>
|
<span class="today-track-rail" role="progressbar" aria-label="今日习惯进度" aria-valuemin="0" :aria-valuemax="todayHabitTotal" :aria-valuenow="todayHabitCompleted"><i class="today-track-fill" :style="{ width: todayHabitProgressPercent }" /></span>
|
||||||
</button>
|
</button>
|
||||||
</section>
|
</section>
|
||||||
<template v-if="activeView==='today'">
|
<template v-if="activeView==='today'">
|
||||||
|
|||||||
@@ -165,7 +165,13 @@ describe('task and habit row decoration', () => {
|
|||||||
expect(app).toContain('const token = ++todaySummaryLoadToken')
|
expect(app).toContain('const token = ++todaySummaryLoadToken')
|
||||||
expect(app).toContain("params.set('completed', String(completed))")
|
expect(app).toContain("params.set('completed', String(completed))")
|
||||||
expect(app).toContain('await Promise.all([summaryTotal(false), summaryTotal(true)])')
|
expect(app).toContain('await Promise.all([summaryTotal(false), summaryTotal(true)])')
|
||||||
expect(app).toContain("if (activeView.value === 'today') await loadTodayTaskSummary()")
|
expect(app).toContain("if (activeView.value === 'today') void loadTodayTaskSummary()")
|
||||||
|
expect(app).toContain('aria-controls="today-tasks"')
|
||||||
|
expect(app).toContain('aria-controls="today-habits"')
|
||||||
|
expect(app).toContain('role="progressbar"')
|
||||||
|
expect(app).toContain(':aria-valuemax="todayTaskTotal"')
|
||||||
|
expect(app).toContain(':aria-valuenow="todayHabitCompleted"')
|
||||||
|
expect(app).toContain('还有 ${Math.max(todayHabitTotal.value - todayHabitCompleted.value, 0)} 项未完成')
|
||||||
expect(app).toContain('任务 {{ todayTaskCompleted }} / {{ todayTaskTotal }}')
|
expect(app).toContain('任务 {{ todayTaskCompleted }} / {{ todayTaskTotal }}')
|
||||||
expect(app).toContain('习惯 {{ todayHabitProgress }}')
|
expect(app).toContain('习惯 {{ todayHabitProgress }}')
|
||||||
expect(app).toContain("scrollTodaySection('today-tasks')")
|
expect(app).toContain("scrollTodaySection('today-tasks')")
|
||||||
|
|||||||
Reference in New Issue
Block a user