feat: add Today environment summary
ci / gitleaks (push) Successful in 51s
ci / docker (push) Successful in 4m34s

This commit is contained in:
2026-09-16 12:23:59 +08:00
parent ed6d999ffb
commit 0a4d5f2f05
12 changed files with 1405 additions and 7 deletions
+40 -1
View File
@@ -21,7 +21,8 @@ import FloatingAddButton from './components/FloatingAddButton.vue'
import CompletedFilterPill from './components/CompletedFilterPill.vue'
import CalendarPicker from './components/CalendarPicker.vue'
import TaskDueDisplay from './components/TaskDueDisplay.vue'
import { useTaskDueClock } from './lib/task-due-clock'
import TodayEnvironmentStrip, { type TodayEnvironment } from './components/TodayEnvironmentStrip.vue'
import { shanghaiDateKey, useTaskDueClock, watchShanghaiDateRollover } from './lib/task-due-clock'
import { readTodaySectionCollapse, writeTodaySectionCollapse, type TodaySectionCollapse } from './lib/today-section-collapse'
type FolderItem = { id: string; name: string }
@@ -108,6 +109,10 @@ const todayTaskTotal = ref(0)
const todayTaskCompleted = ref(0)
const todayHabitTotal = ref(0)
const todayHabitCompleted = ref(0)
const todayEnvironment = ref<TodayEnvironment | null>(null)
const todayEnvironmentLoading = ref(false)
const todayEnvironmentError = ref(false)
const todayEnvironmentDateKey = ref('')
const totalPages = computed(() => Math.max(1, Math.ceil(totalTasks.value / pageSize)))
const taskReorderAvailable = computed(() => activeView.value === 'tasks' && !query.value && totalPages.value === 1 && taskTree.value.length > 1)
const expandedFolders = ref(new Set<string>())
@@ -157,6 +162,7 @@ const selectedRepeatConfig = ref<TaskRepeatConfig>(defaultRepeatConfig())
const weekdayOptions = [{ value: 'MO', label: '一' }, { value: 'TU', label: '二' }, { value: 'WE', label: '三' }, { value: 'TH', label: '四' }, { value: 'FR', label: '五' }, { value: 'SA', label: '六' }, { value: 'SU', label: '日' }]
let recurrenceLoadToken = 0
let todaySummaryLoadToken = 0
let todayEnvironmentLoadToken = 0
const habitComposer = ref<InstanceType<typeof MvpPanel> | null>(null)
const countdownComposer = ref<InstanceType<typeof CountdownPanel> | null>(null)
const memoPanel = ref<InstanceType<typeof MemoPanel> | null>(null)
@@ -534,6 +540,27 @@ async function loadTodayTaskSummary() {
todayTaskTotal.value = overdue + open + completed
} catch { /* 概览统计失败不阻断今天页 */ }
}
async function loadTodayEnvironment() {
const token = ++todayEnvironmentLoadToken
todayEnvironmentLoading.value = true
todayEnvironmentError.value = false
try {
const data = await api('/today/environment') as TodayEnvironment
if (token !== todayEnvironmentLoadToken || activeView.value !== 'today') return
todayEnvironment.value = data
todayEnvironmentDateKey.value = data.date?.solar_date || shanghaiDateKey()
} catch {
if (token !== todayEnvironmentLoadToken || activeView.value !== 'today') return
todayEnvironmentError.value = true
} finally {
if (token === todayEnvironmentLoadToken && activeView.value === 'today') todayEnvironmentLoading.value = false
}
}
function handleTodayEnvironmentResume() {
if (document.visibilityState === 'hidden' || activeView.value !== 'today') return
if (todayEnvironmentDateKey.value !== shanghaiDateKey()) void loadTodayEnvironment()
}
watchShanghaiDateRollover(taskDueNowMs, handleTodayEnvironmentResume)
async function loadOverdueTasks(request = beginLatestRequest('tasks')) {
const params = new URLSearchParams()
params.set('due_to', isoAtLocalDayOffset(0))
@@ -601,6 +628,7 @@ async function loadAll() {
if (!navigationLoaded.value) await loadNavigation()
if (!isLatestRequest('tasks', request)) return
if (activeView.value === 'today') {
void loadTodayEnvironment()
await startPrimaryWithBackground(
[() => loadTasksPage(request), () => loadOverdueTasks(request)],
loadTodayTaskSummary,
@@ -655,6 +683,10 @@ async function switchView(view: View, listId?: string) {
taskMutationNavigation.value += 1
taskReorderMode.value = false
cancelTaskReorder()
if (view !== 'today') {
++todayEnvironmentLoadToken
todayEnvironmentLoading.value = false
}
activeView.value = view
if (!query.value) mobileSearchOpen.value = false
searchPullDistance.value = 0
@@ -1376,6 +1408,9 @@ onMounted(() => {
document.addEventListener('keydown', handleArchivedListEscape)
document.addEventListener('keydown', handleTaskSearchShortcut)
window.addEventListener('resize', handleViewportResize)
document.addEventListener('visibilitychange', handleTodayEnvironmentResume)
window.addEventListener('focus', handleTodayEnvironmentResume)
window.addEventListener('pageshow', handleTodayEnvironmentResume)
document.addEventListener('scroll', handleArchivedListViewportChange, true)
void bootstrap()
})
@@ -1384,6 +1419,9 @@ onUnmounted(() => {
document.removeEventListener('keydown', handleArchivedListEscape)
document.removeEventListener('keydown', handleTaskSearchShortcut)
window.removeEventListener('resize', handleViewportResize)
document.removeEventListener('visibilitychange', handleTodayEnvironmentResume)
window.removeEventListener('focus', handleTodayEnvironmentResume)
window.removeEventListener('pageshow', handleTodayEnvironmentResume)
document.removeEventListener('scroll', handleArchivedListViewportChange, true)
})
</script>
@@ -1491,6 +1529,7 @@ onUnmounted(() => {
<CountdownPanel ref="countdownComposer" v-else-if="activeView==='countdowns'" @notice="toast" />
<template v-else>
<section v-if="activeView==='today'" class="today-board" aria-label="今日进度">
<TodayEnvironmentStrip :environment="todayEnvironment" :loading="todayEnvironmentLoading" :failed="todayEnvironmentError" />
<button class="today-track today-task-track" type="button" aria-controls="today-tasks" @click="navigateTodaySection('tasks')">
<span class="today-track-head"><strong>任务 {{ todayTaskCompleted }} / {{ todayTaskTotal }}</strong></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>