diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 06aab8d..6d454b4 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -77,6 +77,7 @@ const showCompleted = ref(readStoredBoolean(window.localStorage, SHOW_COMPLETED_ const page = ref(1) const pageSize = 50 const totalTasks = ref(0) +const hiddenCompletedTaskCount = ref(0) const todayTaskTotal = ref(0) const todayTaskCompleted = ref(0) const todayHabitTotal = ref(0) @@ -475,6 +476,15 @@ async function loadTasksPage() { const data = await api(`/tasks?${params}`) tasks.value = data.items ?? [] totalTasks.value = data.total ?? tasks.value.length + hiddenCompletedTaskCount.value = 0 + if (!showCompleted.value && !query.value && activeView.value !== 'trash' && tasks.value.length === 0) { + const completedParams = new URLSearchParams(params) + completedParams.set('page', '1') + completedParams.set('page_size', '1') + completedParams.set('completed', 'true') + const completedData = await api(`/tasks?${completedParams}`) + hiddenCompletedTaskCount.value = Number(completedData.total ?? completedData.items?.length ?? 0) + } } async function loadNavigation(force = false) { if (!force && navigationLoaded.value) return @@ -1137,10 +1147,11 @@ onUnmounted(() => { -
{{ totalPages > 1 ? `第 ${page} / ${totalPages} 页 · ` : '' }}共 {{totalTasks}} 项
+
{{ totalPages > 1 ? `第 ${page} / ${totalPages} 页 · ` : '' }}共 {{totalTasks}} 项
{{page}} / {{totalPages}}
-
{{query?'没有匹配的任务':'这里还很安静'}}{{query?'换个关键词试试':'写下第一件想完成的小事吧'}}
+
{{ query ? '没有匹配的任务' : hiddenCompletedTaskCount > 0 ? '已完成任务已隐藏' : '这里还很安静' }}{{query?'换个关键词试试':hiddenCompletedTaskCount > 0 ? '开启“显示已完成”即可查看。':'写下第一件想完成的小事吧'}}

习惯

- +
diff --git a/frontend/src/MvpPanel.vue b/frontend/src/MvpPanel.vue index 2992ac3..a3061ef 100644 --- a/frontend/src/MvpPanel.vue +++ b/frontend/src/MvpPanel.vue @@ -2,7 +2,7 @@ import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue' import { Activity, ArchiveRestore, Check, Download, FileJson, GripVertical, LogOut, Pencil, Trash2, X } from 'lucide-vue-next' import { moveItemWithinScope } from './lib/task-utils' -import { changedHabitFields, dateKey, formatHabitApiError, habitActionState, habitButtonNotice, habitButtonValue, isHabitComplete, isHabitScheduledToday, mergePage, nextHabitSwipeValue, previousHabitSwipeValue, readHabitGridCache, readStoredBoolean, shouldToggleRowSwipe, validateHabitForm, writeHabitGridCache, writeStoredBoolean, type HabitFormErrors, type HabitFormValues } from './lib/mvp-utils' +import { changedHabitFields, dateKey, formatHabitApiError, habitActionState, habitButtonNotice, habitButtonValue, isHabitComplete, isHabitScheduledToday, mergePage, nextHabitSwipeValue, previousHabitSwipeValue, readHabitGridCache, shouldToggleRowSwipe, validateHabitForm, writeHabitGridCache, type HabitFormErrors, type HabitFormValues } from './lib/mvp-utils' import { csrfHeader } from './lib/csrf' import { createCompletionPulse } from './lib/completion-motion' @@ -10,11 +10,12 @@ type View = 'habits' | 'today-habits' | 'settings' type HabitCell = { day: string; scheduled?: boolean; paused?: boolean; value: number | boolean } type Habit = { id: string; name: string; kind?: string; target?: number; max_value?: number | null; schedule_type?: HabitFormValues['schedule_type']; weekdays?: number[] | null; month_days?: number[] | null; interval_days?: number | null; archived_at?: string | null; unit?: string; cells?: HabitCell[]; stats?: Record } type Session = { id: string; created_at?: string; last_seen_at?: string; current?: boolean; user_agent?: string } -const props = defineProps<{ view: View }>() +const props = defineProps<{ view: View; showCompleted: boolean }>() const emit = defineEmits<{ changed: [] notice: [message: string] summary: [value: { total: number; completed: number }] + 'update:showCompleted': [value: boolean] }>() const habits = ref([]) const archivedHabits = ref([]) @@ -61,17 +62,14 @@ const markHabitJustCompleted = createCompletionPulse( (id) => { justCompletedHabitIds.value = new Set(justCompletedHabitIds.value).add(id) }, (id) => { const next = new Set(justCompletedHabitIds.value); next.delete(id); justCompletedHabitIds.value = next }, ) -const HIDE_COMPLETED_HABITS_STORAGE_KEY = 'dodo.hide-completed-habits' -const hideCompletedHabits = ref(readStoredBoolean(window.localStorage, HIDE_COMPLETED_HABITS_STORAGE_KEY, false)) const todayHabits = computed(() => habits.value.filter((item) => isHabitScheduledToday(item, todayKey.value))) -const visibleTodayHabits = computed(() => hideCompletedHabits.value ? todayHabits.value.filter((item) => !isDone(item, todayKey.value)) : todayHabits.value) -const visibleHabits = computed(() => hideCompletedHabits.value ? habits.value.filter((item) => !isDone(item, todayKey.value)) : habits.value) +const visibleTodayHabits = computed(() => props.showCompleted ? todayHabits.value : todayHabits.value.filter((item) => !isDone(item, todayKey.value))) +const visibleHabits = computed(() => props.showCompleted ? habits.value : habits.value.filter((item) => !isDone(item, todayKey.value))) const todayHabitSummary = computed(() => ({ total: todayHabits.value.length, completed: todayHabits.value.filter((item) => isDone(item, todayKey.value)).length, })) watch(todayHabitSummary, (value) => emit('summary', value), { immediate: true }) -watch(hideCompletedHabits, (value) => writeStoredBoolean(window.localStorage, HIDE_COMPLETED_HABITS_STORAGE_KEY, value)) let dayRolloverTimer: ReturnType | undefined async function request(path: string, options: RequestInit = {}) { @@ -99,7 +97,7 @@ function isInteractiveTarget(target: EventTarget | null) { return target instanceof Element && Boolean(target.closest('button,input,select,textarea,a,label')) } function startHabitReorder(h: Habit, event: PointerEvent) { - if (busy.value || hideCompletedHabits.value) return + if (busy.value || !props.showCompleted) return habitReorder.value = { id: h.id, startY: event.clientY, offsetY: 0 } habitReorderTarget.value = h.id try { (event.currentTarget as Element).setPointerCapture(event.pointerId) } catch { /* synthetic events */ } @@ -526,12 +524,11 @@ onBeforeUnmount(() => {