This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { Archive, ArchiveRestore, CalendarHeart, Pencil, Pin, Plus, Trash2, X } from 'lucide-vue-next'
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { Archive, ArchiveRestore, CalendarHeart, Pencil, Pin, Trash2, X } from 'lucide-vue-next'
|
||||
import { csrfHeader } from './lib/csrf'
|
||||
import { calendarModeLabel, countdownDayText, countdownKindLabel, dateKey, formatApiErrorDetail } from './lib/mvp-utils'
|
||||
|
||||
@@ -18,6 +18,8 @@ const editingId = ref<string|null>(null), error = ref('')
|
||||
const currentYear = new Date().getFullYear()
|
||||
const freshForm = (): Form => ({ title:'', event_date:dateKey(new Date()), kind:'countdown', repeat_rule:'none', icon:'📅', calendar_mode:'solar', lunar_year:currentYear, lunar_month:1, lunar_day:1, leap_month:false, ignore_year:false })
|
||||
const form = ref<Form>(freshForm())
|
||||
const composerOrigin = ref({ x: window.innerWidth - 43, y: window.innerHeight - 104 })
|
||||
const composerStyle = computed(() => ({ '--fab-origin-x': `${composerOrigin.value.x}px`, '--fab-origin-y': `${composerOrigin.value.y}px` }))
|
||||
const titleInput = ref<HTMLInputElement | null>(null)
|
||||
let previousFocus: HTMLElement | null = null
|
||||
|
||||
@@ -72,7 +74,6 @@ async function request(path:string, options:RequestInit={}) {
|
||||
}
|
||||
async function safe(work:()=>Promise<void>) { busy.value=true; error.value=''; try { await work() } catch(reason) { error.value=reason instanceof Error ? reason.message : '请求失败' } finally { busy.value=false } }
|
||||
async function load() { await safe(async()=>{ const [a,b]=await Promise.all([request('/countdowns'),request('/countdowns?archived=true')]); items.value=a; archived.value=b }) }
|
||||
function add() { onAdd() }
|
||||
function edit(item:Countdown) {
|
||||
editingId.value=item.id
|
||||
form.value={ title:item.title, event_date:item.event_date, kind:item.kind, repeat_rule:item.repeat_rule, icon:item.icon, calendar_mode:item.calendar_mode, lunar_year:item.lunar_year || Number(item.event_date.slice(0,4)), lunar_month:Math.abs(item.lunar_month || 1), lunar_day:item.lunar_day || 1, leap_month:(item.lunar_month || 0)<0, ignore_year:item.ignore_year }
|
||||
@@ -96,7 +97,8 @@ async function purge(item:Countdown){if(!confirm(`永久删除“${item.title}
|
||||
function formatDate(value:string){const [y,m,d]=value.split('-');return `${y}年${Number(m)}月${Number(d)}日`}
|
||||
function formatDateShort(value:string){const [y,m,d]=value.split('-');return `${y}/${Number(m)}/${Number(d)}`}
|
||||
function repeatLabel(value:Countdown['repeat_rule']){return({none:'不重复',weekly:'每周',monthly:'每月',yearly:'每年'})[value]}
|
||||
function onAdd(){editingId.value=null;form.value=freshForm();open.value=true;focusDialog()}
|
||||
function openCountdownComposer(origin?: { x: number; y: number }){if(origin)composerOrigin.value=origin;editingId.value=null;form.value=freshForm();open.value=true;focusDialog()}
|
||||
defineExpose({ openCountdownComposer })
|
||||
onMounted(load)
|
||||
onBeforeUnmount(() => { previousFocus = null })
|
||||
</script>
|
||||
@@ -104,7 +106,7 @@ onBeforeUnmount(() => { previousFocus = null })
|
||||
<template>
|
||||
<section class="countdown-view" :class="{ loading:busy }">
|
||||
<div class="countdown-content" :inert="open" :aria-hidden="open ? 'true' : undefined">
|
||||
<header class="countdown-hero"><div><small>记住值得期待与纪念的日子</small><h2>倒数日</h2></div><button class="countdown-add" @click="onAdd"><Plus/>添加日子</button></header>
|
||||
<header class="countdown-hero"><div><small>记住值得期待与纪念的日子</small><h2>倒数日</h2></div></header>
|
||||
<p v-if="error" class="inline-error">{{error}}</p>
|
||||
<div class="countdown-grid">
|
||||
<article v-for="item in items" :key="item.id" class="countdown-card" :class="{ pinned:item.pinned }">
|
||||
@@ -113,27 +115,28 @@ onBeforeUnmount(() => { previousFocus = null })
|
||||
<p><b>{{primaryDate(item)}}</b><span v-if="secondaryDate(item)"> · {{secondaryDate(item)}}</span></p>
|
||||
<div class="countdown-actions"><button v-if="!item.pinned" @click="pin(item)"><Pin/>置顶</button><button @click="edit(item)"><Pencil/>编辑</button><button @click="archiveItem(item)"><Archive/>归档</button></div>
|
||||
</article>
|
||||
<button v-if="!items.length&&!busy" class="countdown-empty" @click="onAdd"><CalendarHeart/><b>添加第一个重要日子</b><span>生日、纪念日,或一场期待已久的旅行</span></button>
|
||||
<div v-if="!items.length&&!busy" class="countdown-empty"><CalendarHeart/><b>还没有重要日子</b><span>用右下角的圆圈添加生日、纪念日或旅行</span></div>
|
||||
</div>
|
||||
<button v-if="archived.length" class="archived-toggle" @click="showArchived=!showArchived"><ArchiveRestore/>已归档({{archived.length}})</button>
|
||||
<div v-if="showArchived" class="archived-countdowns"><article v-for="item in archived" :key="item.id"><span>{{item.icon}}</span><b>{{item.title}}</b><small>{{formatDateShort(item.display_date)}}<template v-if="item.lunar_text"> · {{item.lunar_text}}</template><template v-if="item.calendar_mode==='lunar'"> · 农历</template></small><button @click="restore(item)"><ArchiveRestore/>恢复</button><button class="danger-text" @click="purge(item)"><Trash2/>永久删除</button></article></div>
|
||||
<button class="fab countdown-fab mobile-only" aria-label="添加倒数日" @click="add"><Plus/></button>
|
||||
</div>
|
||||
<div v-if="open" class="countdown-modal-mask" @click.self="closeDialog"><form class="countdown-modal" role="dialog" aria-modal="true" aria-labelledby="countdown-dialog-title" @submit.prevent="save" @keydown.esc="closeDialog" @keydown="trapDialogFocus">
|
||||
<header><div><small>{{editingId?'调整重要日子':'记下重要日子'}}</small><h3 id="countdown-dialog-title">{{editingId?'编辑倒数日':'新建倒数日'}}</h3></div><button type="button" aria-label="关闭" @click="closeDialog"><X/></button></header>
|
||||
<label>图标与名称<div class="countdown-title-fields"><input v-model="form.icon" maxlength="8" aria-label="图标"><input ref="titleInput" v-model="form.title" maxlength="200" required placeholder="例如:去北海道旅行" autofocus></div></label>
|
||||
<label>历法<select v-model="form.calendar_mode"><option value="solar">{{calendarModeLabel('solar')}}</option><option value="lunar">{{calendarModeLabel('lunar')}}</option></select></label>
|
||||
<label v-if="form.calendar_mode==='solar'">日期<input v-model="form.event_date" type="date" required></label>
|
||||
<div v-else class="countdown-title-fields">
|
||||
<label>年份<input v-model.number="form.lunar_year" type="number" min="1900" max="2099" required></label>
|
||||
<label>月份<select v-model.number="form.lunar_month"><option v-for="month in 12" :key="month" :value="month">{{month}}月</option></select></label>
|
||||
<label>日期<select v-model.number="form.lunar_day"><option v-for="day in 30" :key="day" :value="day">{{day}}日</option></select></label>
|
||||
</div>
|
||||
<label v-if="form.calendar_mode==='lunar'"><input v-model="form.leap_month" type="checkbox"> 闰月(如果是闰月,勾选这里)</label>
|
||||
<label><input v-model="form.ignore_year" type="checkbox"> 忽略年份<span v-if="form.calendar_mode==='lunar'">,每年按农历计算</span></label>
|
||||
<label>类型<select v-model="form.kind"><option value="countdown">倒数日</option><option value="anniversary">纪念日</option><option value="birthday">生日</option></select></label>
|
||||
<label>重复<select v-model="form.repeat_rule"><option value="none">不重复</option><option value="weekly">每周</option><option value="monthly">每月</option><option value="yearly">每年</option></select></label>
|
||||
<footer><button type="button" class="secondary" @click="closeDialog">取消</button><button class="primary-small">保存</button></footer>
|
||||
</form></div>
|
||||
<Transition name="countdown-compose">
|
||||
<div v-if="open" class="countdown-modal-mask" @click.self="closeDialog"><form class="countdown-modal" :style="composerStyle" role="dialog" aria-modal="true" aria-labelledby="countdown-dialog-title" @submit.prevent="save" @keydown.esc="closeDialog" @keydown="trapDialogFocus">
|
||||
<header><div><small>{{editingId?'调整重要日子':'记下重要日子'}}</small><h3 id="countdown-dialog-title">{{editingId?'编辑倒数日':'新建倒数日'}}</h3></div><button type="button" aria-label="关闭" @click="closeDialog"><X/></button></header>
|
||||
<label>图标与名称<div class="countdown-title-fields"><input v-model="form.icon" maxlength="8" aria-label="图标"><input ref="titleInput" v-model="form.title" maxlength="200" required placeholder="例如:去北海道旅行" autofocus></div></label>
|
||||
<label>历法<select v-model="form.calendar_mode"><option value="solar">{{calendarModeLabel('solar')}}</option><option value="lunar">{{calendarModeLabel('lunar')}}</option></select></label>
|
||||
<label v-if="form.calendar_mode==='solar'">日期<input v-model="form.event_date" type="date" required></label>
|
||||
<div v-else class="countdown-title-fields">
|
||||
<label>年份<input v-model.number="form.lunar_year" type="number" min="1900" max="2099" required></label>
|
||||
<label>月份<select v-model.number="form.lunar_month"><option v-for="month in 12" :key="month" :value="month">{{month}}月</option></select></label>
|
||||
<label>日期<select v-model.number="form.lunar_day"><option v-for="day in 30" :key="day" :value="day">{{day}}日</option></select></label>
|
||||
</div>
|
||||
<label v-if="form.calendar_mode==='lunar'"><input v-model="form.leap_month" type="checkbox"> 闰月(如果是闰月,勾选这里)</label>
|
||||
<label><input v-model="form.ignore_year" type="checkbox"> 忽略年份<span v-if="form.calendar_mode==='lunar'">,每年按农历计算</span></label>
|
||||
<label>类型<select v-model="form.kind"><option value="countdown">倒数日</option><option value="anniversary">纪念日</option><option value="birthday">生日</option></select></label>
|
||||
<label>重复<select v-model="form.repeat_rule"><option value="none">不重复</option><option value="weekly">每周</option><option value="monthly">每月</option><option value="yearly">每年</option></select></label>
|
||||
<footer><button type="button" class="secondary" @click="closeDialog">取消</button><button class="primary-small">保存</button></footer>
|
||||
</form></div>
|
||||
</Transition>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user