fix: harden countdown validation and recurrence
This commit is contained in:
@@ -8,14 +8,14 @@ type Countdown = {
|
||||
id: string; title: string; event_date: string; display_date: string; kind: 'countdown'|'anniversary'|'birthday'
|
||||
repeat_rule: 'none'|'weekly'|'monthly'|'yearly'; icon: string; pinned: boolean; archived_at: string|null; days: number
|
||||
calendar_mode: 'solar'|'lunar'; lunar_year: number|null; lunar_month: number|null; lunar_day: number|null
|
||||
ignore_year: boolean; lunar_text: string|null
|
||||
ignore_year: boolean; lunar_text: string|null; updated_at: string
|
||||
}
|
||||
type Form = { title:string; event_date:string; kind:Countdown['kind']; repeat_rule:Countdown['repeat_rule']; calendar_mode:Countdown['calendar_mode']; lunar_year:number; lunar_month:number; lunar_day:number; leap_month:boolean; ignore_year:boolean }
|
||||
type CountdownGroup = { key: string; title: string; items: Countdown[] }
|
||||
const emit = defineEmits<{ notice: [message: string] }>()
|
||||
const items = ref<Countdown[]>([]), archived = ref<Countdown[]>([])
|
||||
const showArchived = ref(false), open = ref(false), busy = ref(false)
|
||||
const editingId = ref<string|null>(null), error = ref('')
|
||||
const editingId = ref<string|null>(null), editingItem = ref<Countdown|null>(null), error = ref('')
|
||||
const detailItem = ref<Countdown|null>(null), showAdvanced = ref(false)
|
||||
const currentYear = new Date().getFullYear()
|
||||
const freshForm = (): Form => ({ title:'', event_date:dateKey(new Date()), kind:'countdown', repeat_rule:'none', calendar_mode:'solar', lunar_year:currentYear, lunar_month:1, lunar_day:1, leap_month:false, ignore_year:false })
|
||||
@@ -55,7 +55,7 @@ function primaryDate(item: Countdown) {
|
||||
function secondaryDate(item: Countdown) {
|
||||
if (item.calendar_mode === 'lunar' && item.lunar_text) {
|
||||
const suffix = item.ignore_year ? ' · 每年农历' : ''
|
||||
return `${formatDate(item.display_date)} · ${item.lunar_text}${suffix}`
|
||||
return `${formatDate(item.display_date)}${suffix}`
|
||||
}
|
||||
return ''
|
||||
}
|
||||
@@ -122,6 +122,7 @@ async function load() {
|
||||
function edit(item:Countdown) {
|
||||
detailItem.value=null
|
||||
editingId.value=item.id
|
||||
editingItem.value=item
|
||||
showAdvanced.value=false
|
||||
form.value={ title:item.title, event_date:item.event_date, kind:item.kind, repeat_rule:item.repeat_rule, 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 }
|
||||
open.value=true
|
||||
@@ -132,9 +133,11 @@ function applyKindDefaults() {
|
||||
if (form.value.kind === 'countdown') form.value.repeat_rule='none'
|
||||
}
|
||||
async function save() {
|
||||
if (busy.value) return
|
||||
if (!form.value.title.trim()) return
|
||||
await safe(async()=>{
|
||||
const payload:any={ title:form.value.title.trim(), event_date:form.value.calendar_mode==='lunar' ? `${form.value.lunar_year}-01-01` : form.value.event_date, kind:form.value.kind, repeat_rule:form.value.ignore_year ? 'yearly' : form.value.repeat_rule, calendar_mode:form.value.calendar_mode, ignore_year:form.value.ignore_year }
|
||||
if (editingId.value) payload.expected_updated_at=editingItem.value?.updated_at
|
||||
if (form.value.calendar_mode==='lunar') { payload.lunar_month=form.value.leap_month ? -form.value.lunar_month : form.value.lunar_month; payload.lunar_day=form.value.lunar_day }
|
||||
const path=editingId.value ? `/countdowns/${editingId.value}` : '/countdowns'
|
||||
await request(path,{ method:editingId.value?'PATCH':'POST', body:JSON.stringify(payload) })
|
||||
@@ -162,7 +165,7 @@ function trapDetailFocus(event: KeyboardEvent) {
|
||||
else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus() }
|
||||
}
|
||||
function openFromEmpty(){openCountdownComposer()}
|
||||
function openCountdownComposer(origin?: { x: number; y: number }){if(origin)composerOrigin.value=origin;detailItem.value=null;editingId.value=null;showAdvanced.value=false;form.value=freshForm();open.value=true;focusDialog()}
|
||||
function openCountdownComposer(origin?: { x: number; y: number }){if(origin)composerOrigin.value=origin;detailItem.value=null;editingId.value=null;editingItem.value=null;showAdvanced.value=false;form.value=freshForm();open.value=true;focusDialog()}
|
||||
defineExpose({ openCountdownComposer })
|
||||
onMounted(load)
|
||||
onBeforeUnmount(() => { previousFocus = null })
|
||||
@@ -219,7 +222,7 @@ onBeforeUnmount(() => { previousFocus = null })
|
||||
<label><input v-model="form.ignore_year" type="checkbox"> 忽略年份<span v-if="form.calendar_mode==='lunar'">,每年按农历计算</span></label>
|
||||
<label>重复<select v-model="form.repeat_rule" :disabled="form.ignore_year"><option value="none">不重复</option><option value="weekly">每周</option><option value="monthly">每月</option><option value="yearly">每年</option></select></label>
|
||||
</details></div>
|
||||
<footer class="app-sheet__footer"><button type="button" class="secondary" @click="closeDialog">取消</button><button class="primary-small">保存</button></footer>
|
||||
<footer class="app-sheet__footer"><button type="button" class="secondary" :disabled="busy" @click="closeDialog">取消</button><button class="primary-small" :disabled="busy">保存</button></footer>
|
||||
</form></div>
|
||||
</Transition>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user