feat: unify draggable creation button
ci / docker (push) Successful in 3m23s

This commit is contained in:
2026-09-07 12:04:34 +08:00
parent 373c038fe7
commit acab663226
6 changed files with 161 additions and 114 deletions
+27 -11
View File
@@ -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>
<!-- 习惯列表支持整行滑动记录 -->