This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { Activity, ArchiveRestore, Check, Download, FileJson, LogOut, Plus, RefreshCw, Trash2, Upload } from 'lucide-vue-next'
|
||||
import { dateKey, isHabitComplete, mergePage, numericHabitInputValue } from './lib/mvp-utils'
|
||||
import { Activity, ArchiveRestore, Download, FileJson, LogOut, Plus, RefreshCw, Trash2, Upload } from 'lucide-vue-next'
|
||||
import { dateKey, isHabitComplete, mergePage, numericHabitInputValue, shouldToggleHabitSwipe } from './lib/mvp-utils'
|
||||
import { csrfHeader } from './lib/csrf'
|
||||
|
||||
type View = 'habits' | 'settings'
|
||||
@@ -20,8 +20,9 @@ const habitTarget = ref(1)
|
||||
const importFile = ref<File | null>(null)
|
||||
const importPreview = ref<any>(null)
|
||||
const restoreFile = ref<File | null>(null)
|
||||
const numericValues = ref<Record<string, number>>({})
|
||||
const numericValues = ref<Record<string, number | string>>({})
|
||||
const todayKey = ref(dateKey(new Date()))
|
||||
const habitSwipeStart = ref<{ id: string; x: number; y: number } | null>(null)
|
||||
let dayRolloverTimer: ReturnType<typeof setInterval> | undefined
|
||||
|
||||
function formatErrorMessage(detail: unknown): string {
|
||||
@@ -70,6 +71,23 @@ async function toggleHabit(h: Habit, day: string) {
|
||||
emit('notice', next ? '打卡成功 🎉' : '已取消打卡')
|
||||
})
|
||||
}
|
||||
function startHabitSwipe(h: Habit, event: TouchEvent) {
|
||||
if (h.kind === 'numeric' || busy.value) return
|
||||
const touch = event.touches[0]
|
||||
if (touch) habitSwipeStart.value = { id: h.id, x: touch.clientX, y: touch.clientY }
|
||||
}
|
||||
async function finishHabitSwipe(h: Habit, event: TouchEvent) {
|
||||
const start = habitSwipeStart.value
|
||||
habitSwipeStart.value = null
|
||||
if (!start || start.id !== h.id || h.kind === 'numeric' || busy.value) return
|
||||
const touch = event.changedTouches[0]
|
||||
if (touch && shouldToggleHabitSwipe(touch.clientX - start.x, touch.clientY - start.y)) {
|
||||
await toggleHabit(h, todayKey.value)
|
||||
}
|
||||
}
|
||||
function cancelHabitSwipe() {
|
||||
habitSwipeStart.value = null
|
||||
}
|
||||
async function recordNumeric(h: Habit, day: string) {
|
||||
if (busy.value) return
|
||||
const next = numericHabitInputValue(numericValues.value[h.id])
|
||||
@@ -185,14 +203,12 @@ onBeforeUnmount(() => {
|
||||
</form>
|
||||
|
||||
|
||||
<!-- 习惯列表:展示和操作分离,只点右侧明确按钮,降低误触 -->
|
||||
<!-- 习惯列表:布尔习惯整行右滑打卡,数值习惯保留明确输入记录 -->
|
||||
<div class="habit-list">
|
||||
<article v-for="h in habits" :key="h.id" class="habit-row" :class="{ done: isDone(h, todayKey) }">
|
||||
<article v-for="h in habits" :key="h.id" class="habit-row" :class="{ done: isDone(h, todayKey), swipeable: h.kind !== 'numeric' }" @touchstart.passive="startHabitSwipe(h, $event)" @touchend="finishHabitSwipe(h, $event)" @touchcancel="cancelHabitSwipe">
|
||||
<div v-if="h.kind !== 'numeric'" class="habit-main">
|
||||
<span><span class="habit-name">{{ h.name }}</span><small>{{ isDone(h, todayKey) ? '今天已打卡' : '今天还没做' }}</small></span>
|
||||
<button class="habit-check-button" :class="{ done: isDone(h, todayKey) }" :aria-label="isDone(h, todayKey) ? `取消${h.name}今天的打卡` : `完成${h.name}今天的打卡`" :aria-pressed="isDone(h, todayKey)" @click="toggleHabit(h, todayKey)">
|
||||
<Check v-if="isDone(h, todayKey)" />
|
||||
</button>
|
||||
<span><span class="habit-name">{{ h.name }}</span><small>{{ isDone(h, todayKey) ? '今天已完成 · 右滑可取消' : '右滑整行打卡' }}</small></span>
|
||||
<span class="habit-swipe-hint" aria-hidden="true">{{ isDone(h, todayKey) ? '已完成' : '右滑' }}</span>
|
||||
</div>
|
||||
<div v-else class="habit-main numeric-habit">
|
||||
<span><span class="habit-name">{{ h.name }}</span><small>今天 {{ logFor(h, todayKey)?.value || 0 }} / {{ h.target || 1 }}{{ h.unit || '' }}</small></span>
|
||||
|
||||
Reference in New Issue
Block a user