This commit is contained in:
+27
-11
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { Activity, ArchiveRestore, Download, FileJson, LogOut, Plus, RefreshCw, Trash2, Upload } from 'lucide-vue-next'
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { Activity, ArchiveRestore, Download, FileJson, LogOut, RefreshCw, Trash2, Upload, X } from 'lucide-vue-next'
|
||||
import { dateKey, isHabitComplete, isHabitScheduledToday, mergePage, nextHabitSwipeValue, previousHabitSwipeValue, shouldToggleRowSwipe } from './lib/mvp-utils'
|
||||
import { csrfHeader } from './lib/csrf'
|
||||
|
||||
@@ -17,6 +17,10 @@ const error = ref('')
|
||||
const habitName = ref('')
|
||||
const habitType = ref<'boolean' | 'numeric'>('boolean')
|
||||
const habitTarget = ref(1)
|
||||
const habitComposerOpen = ref(false)
|
||||
const habitComposeOrigin = ref({ x: window.innerWidth - 43, y: window.innerHeight - 104 })
|
||||
const habitComposeStyle = computed(() => ({ '--fab-origin-x': `${habitComposeOrigin.value.x}px`, '--fab-origin-y': `${habitComposeOrigin.value.y}px` }))
|
||||
const habitNameInput = ref<HTMLInputElement | null>(null)
|
||||
const importFile = ref<File | null>(null)
|
||||
const importPreview = ref<any>(null)
|
||||
const restoreFile = ref<File | null>(null)
|
||||
@@ -180,10 +184,21 @@ async function addHabit() {
|
||||
await safe(async () => {
|
||||
await request('/habits', { method: 'POST', body: JSON.stringify({ name: habitName.value.trim(), kind: habitType.value, target: habitTarget.value, schedule_type: 'daily' }) })
|
||||
habitName.value = ''
|
||||
habitComposerOpen.value = false
|
||||
await loadHabits()
|
||||
emit('notice', '习惯已创建')
|
||||
})
|
||||
}
|
||||
function openHabitComposer(origin?: { x: number; y: number }) {
|
||||
if (origin) habitComposeOrigin.value = origin
|
||||
habitName.value = ''
|
||||
habitType.value = 'boolean'
|
||||
habitTarget.value = 1
|
||||
habitComposerOpen.value = true
|
||||
void nextTick(() => habitNameInput.value?.focus())
|
||||
}
|
||||
function closeHabitComposer() { habitComposerOpen.value = false }
|
||||
defineExpose({ openHabitComposer })
|
||||
async function archiveHabit(h: Habit) {
|
||||
if (!confirm(`归档习惯“${h.name}”?历史打卡记录会保留。`)) return
|
||||
await safe(async () => {
|
||||
@@ -282,15 +297,16 @@ onBeforeUnmount(() => {
|
||||
<div v-else-if="view === 'today-habits' && busy" class="empty-panel">加载中…</div>
|
||||
|
||||
<!-- 完整习惯列表 -->
|
||||
<form v-if="view === 'habits'" class="habit-create" @submit.prevent="addHabit">
|
||||
<input v-model="habitName" placeholder="新习惯名称(如:喝水 8 杯)" aria-label="新习惯名称">
|
||||
<select v-model="habitType" aria-label="习惯类型">
|
||||
<option value="boolean">完成 / 未完成</option>
|
||||
<option value="numeric">按数量记录</option>
|
||||
</select>
|
||||
<input v-if="habitType === 'numeric'" v-model.number="habitTarget" type="number" min="0" step="any" placeholder="目标值" aria-label="目标值">
|
||||
<button class="primary-small"><Plus />添加</button>
|
||||
</form>
|
||||
<Transition name="task-compose">
|
||||
<div v-if="habitComposerOpen" class="task-compose-mask" @click.self="closeHabitComposer">
|
||||
<form class="task-compose-sheet habit-compose-sheet" :style="habitComposeStyle" role="dialog" aria-modal="true" aria-labelledby="habit-compose-title" @submit.prevent="addHabit" @keydown.esc="closeHabitComposer">
|
||||
<header><div><small>NEW HABIT</small><h2 id="habit-compose-title">添加习惯</h2></div><button class="icon" type="button" aria-label="关闭添加习惯" @click="closeHabitComposer"><X /></button></header>
|
||||
<label>习惯名称<input ref="habitNameInput" v-model="habitName" placeholder="例如:每天喝水 8 杯" aria-label="新习惯名称"></label>
|
||||
<div class="task-compose-row"><label>记录方式<select v-model="habitType" aria-label="习惯类型"><option value="boolean">完成 / 未完成</option><option value="numeric">按数量记录</option></select></label><label v-if="habitType === 'numeric'">目标值<input v-model.number="habitTarget" type="number" min="0" step="any" aria-label="目标值"></label></div>
|
||||
<footer><button type="button" class="secondary" @click="closeHabitComposer">取消</button><button class="primary-small" :disabled="!habitName.trim()">添加习惯</button></footer>
|
||||
</form>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
|
||||
<!-- 习惯列表支持整行滑动记录。 -->
|
||||
|
||||
Reference in New Issue
Block a user