feat: add Today environment summary
This commit is contained in:
+40
-1
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user