feat: use compact deadline controls
ci / gitleaks (push) Successful in 7s
ci / docker (push) Successful in 6m29s

This commit is contained in:
2026-09-08 16:41:35 +08:00
parent da3899a4bd
commit ab46bb4f6a
3 changed files with 42 additions and 16 deletions
+29 -4
View File
@@ -69,6 +69,7 @@ const composeListId = ref('')
const composeDueAt = ref('') const composeDueAt = ref('')
const composeHasTime = ref(false) const composeHasTime = ref(false)
const composeTime = ref('12:00') const composeTime = ref('12:00')
const composeTimePicker = ref<HTMLInputElement | null>(null)
const composePriority = ref(0) const composePriority = ref(0)
const composeDescription = ref('') const composeDescription = ref('')
const composeRepeat = ref<RepeatOption>('none') const composeRepeat = ref<RepeatOption>('none')
@@ -102,6 +103,24 @@ function openTaskCompose() {
nextTick(() => document.querySelector<HTMLInputElement>('.task-compose-input')?.focus()) nextTick(() => document.querySelector<HTMLInputElement>('.task-compose-input')?.focus())
} }
const taskComposeTitle = computed(() => activeView.value === 'today' ? '添加今天任务' : '添加任务') const taskComposeTitle = computed(() => activeView.value === 'today' ? '添加今天任务' : '添加任务')
const composeDueLabel = computed(() => {
if (!composeDueAt.value) return '设置截止日期'
const [year, month, day] = composeDueAt.value.split('-').map(Number)
const value = new Date(year, month - 1, day)
return Number.isNaN(value.getTime()) ? '设置截止日期' : new Intl.DateTimeFormat('zh-CN', { month: 'short', day: 'numeric', weekday: 'short' }).format(value)
})
function clearComposeDueDate() {
composeDueAt.value = ''
composeHasTime.value = false
composeRepeat.value = 'none'
}
function addComposeTime() {
composeHasTime.value = true
nextTick(() => {
const picker = composeTimePicker.value as (HTMLInputElement & { showPicker?: () => void }) | null
try { picker?.showPicker?.() } catch { picker?.focus() }
})
}
function closeTaskCompose() { taskComposeOpen.value = false } function closeTaskCompose() { taskComposeOpen.value = false }
function activateFloatingAdd(origin: { x: number; y: number }) { function activateFloatingAdd(origin: { x: number; y: number }) {
composeOrigin.value = origin composeOrigin.value = origin
@@ -859,10 +878,16 @@ onMounted(bootstrap)
<header><div><small>NEW TASK</small><h2 id="task-compose-title">{{ taskComposeTitle }}</h2></div><button class="icon" type="button" aria-label="关闭添加任务" @click="closeTaskCompose"><X/></button></header> <header><div><small>NEW TASK</small><h2 id="task-compose-title">{{ taskComposeTitle }}</h2></div><button class="icon" type="button" aria-label="关闭添加任务" @click="closeTaskCompose"><X/></button></header>
<label>任务名称<input v-model="composeTitle" class="task-compose-input" placeholder="准备做点什么?" autocomplete="off"></label> <label>任务名称<input v-model="composeTitle" class="task-compose-input" placeholder="准备做点什么?" autocomplete="off"></label>
<div class="task-compose-row"><label>清单<select v-model="composeListId"><option v-for="list in lists" :key="list.id" :value="list.id">{{list.name}}</option></select></label><label>优先级<select v-model.number="composePriority"><option :value="0">无</option><option :value="1">低</option><option :value="2">中</option><option :value="3"></option></select></label></div> <div class="task-compose-row"><label>清单<select v-model="composeListId"><option v-for="list in lists" :key="list.id" :value="list.id">{{list.name}}</option></select></label><label>优先级<select v-model.number="composePriority"><option :value="0">无</option><option :value="1">低</option><option :value="2">中</option><option :value="3"></option></select></label></div>
<div class="task-compose-due-row"> <div class="task-compose-date-actions" aria-label="截止时间">
<label>截止日期<input v-model="composeDueAt" type="date"></label> <div class="task-compose-date-control">
<button v-if="!composeHasTime" class="task-compose-time-add" type="button" @click="composeHasTime=true">添加时间</button> <label class="task-compose-date-chip" :class="{selected:composeDueAt}">
<label v-else class="task-compose-time-field">时间<span><input v-model="composeTime" type="time"><button class="task-compose-time-remove" type="button" aria-label="移除时间" @click="composeHasTime=false"><X/></button></span></label> <CalendarDays/><span>{{ composeDueLabel }}</span>
<input v-model="composeDueAt" class="task-compose-native-picker" type="date" aria-label="选择截止日期">
</label>
<button v-if="composeDueAt" class="task-compose-date-clear" type="button" aria-label="清除截止日期" @click="clearComposeDueDate"><X/></button>
</div>
<button v-if="composeDueAt && !composeHasTime" class="task-compose-time-add" type="button" @click="addComposeTime">添加时间</button>
<label v-else-if="composeDueAt" class="task-compose-time-chip"><span>时间</span><input ref="composeTimePicker" v-model="composeTime" type="time" aria-label="选择截止时间"><button class="task-compose-time-remove" type="button" aria-label="移除时间" @click.prevent="composeHasTime=false"><X/></button></label>
</div> </div>
<label>重复<select v-model="composeRepeat" :disabled="!composeDueAt"><option value="none">不重复</option><option value="daily">每天</option><option value="weekly">每周</option><option value="monthly">每月</option><option value="yearly">每年</option><option value="custom">自定义…</option></select><small v-if="!composeDueAt" class="field-hint">设置截止时间后可重复</small></label> <label>重复<select v-model="composeRepeat" :disabled="!composeDueAt"><option value="none">不重复</option><option value="daily">每天</option><option value="weekly">每周</option><option value="monthly">每月</option><option value="yearly">每年</option><option value="custom">自定义…</option></select><small v-if="!composeDueAt" class="field-hint">设置截止时间后可重复</small></label>
<section v-if="composeRepeat==='custom'" class="repeat-custom-fields"><div><span>每隔</span><input v-model.number="composeRepeatConfig.interval" type="number" min="1"><select v-model="composeRepeatConfig.frequency"><option value="daily">天</option><option value="weekly">周</option><option value="monthly">月</option><option value="yearly">年</option></select></div><label v-if="composeRepeatConfig.frequency==='weekly'">重复日期<span class="weekday-picker"><label v-for="day in weekdayOptions" :key="day.value"><input v-model="composeRepeatConfig.weekdays" type="checkbox" :value="day.value">{{day.label}}</label></span></label><label v-if="composeRepeatConfig.frequency==='monthly'">每月日期<input v-model.number="composeRepeatConfig.monthDays![0]" type="number" min="1" max="31"></label><label>结束方式<select v-model="composeRepeatConfig.endMode"><option value="never">永不结束</option><option value="date">指定日期</option><option value="count">重复次数</option></select></label><label v-if="composeRepeatConfig.endMode==='date'">结束日期<input v-model="composeRepeatConfig.until" type="date"></label><label v-if="composeRepeatConfig.endMode==='count'">重复次数<input v-model.number="composeRepeatConfig.count" type="number" min="1"></label></section> <section v-if="composeRepeat==='custom'" class="repeat-custom-fields"><div><span>每隔</span><input v-model.number="composeRepeatConfig.interval" type="number" min="1"><select v-model="composeRepeatConfig.frequency"><option value="daily">天</option><option value="weekly">周</option><option value="monthly">月</option><option value="yearly">年</option></select></div><label v-if="composeRepeatConfig.frequency==='weekly'">重复日期<span class="weekday-picker"><label v-for="day in weekdayOptions" :key="day.value"><input v-model="composeRepeatConfig.weekdays" type="checkbox" :value="day.value">{{day.label}}</label></span></label><label v-if="composeRepeatConfig.frequency==='monthly'">每月日期<input v-model.number="composeRepeatConfig.monthDays![0]" type="number" min="1" max="31"></label><label>结束方式<select v-model="composeRepeatConfig.endMode"><option value="never">永不结束</option><option value="date">指定日期</option><option value="count">重复次数</option></select></label><label v-if="composeRepeatConfig.endMode==='date'">结束日期<input v-model="composeRepeatConfig.until" type="date"></label><label v-if="composeRepeatConfig.endMode==='count'">重复次数<input v-model.number="composeRepeatConfig.count" type="number" min="1"></label></section>
File diff suppressed because one or more lines are too long
+12 -10
View File
@@ -270,17 +270,19 @@ describe('settings data tools', () => {
}) })
describe('unified floating add interaction', () => { describe('unified floating add interaction', () => {
it('leaves task-list deadlines empty and keeps Today tasks due today', () => { it('uses compact date and time chips instead of large visible mobile inputs', () => {
expect(app).toContain("composeDueAt.value = activeView.value === 'today' ? defaultTaskDueAt() : ''") expect(app).toContain("composeDueAt.value = activeView.value === 'today' ? defaultTaskDueAt() : ''")
expect(app).toContain('v-model="composeDueAt" type="date"') expect(app).toContain('const composeDueLabel = computed')
expect(app).not.toContain('class="time-toggle"') expect(app).toContain('class="task-compose-date-chip"')
expect(app).not.toContain('v-model="composeHasTime" type="checkbox"') expect(app).toContain('class="task-compose-native-picker"')
expect(app).toContain('class="task-compose-due-row"') expect(app).toContain('<span>{{ composeDueLabel }}</span>')
expect(app).toContain('class="task-compose-time-add"') expect(app).toContain('v-if="composeDueAt" class="task-compose-date-clear"')
expect(app).toContain('class="task-compose-time-remove"') expect(app).toContain('v-if="composeDueAt && !composeHasTime"')
expect(css).toContain('.task-compose-due-row{display:grid;grid-template-columns:minmax(0,1fr) auto;') expect(app).toContain('v-else-if="composeDueAt" class="task-compose-time-chip"')
expect(css).toContain('@media(max-width:480px){.task-compose-due-row{grid-template-columns:1fr}') expect(app).toContain('function clearComposeDueDate()')
expect(css).toContain('.task-compose-time-add{width:100%') expect(css).toContain('.task-compose-date-actions{display:flex;flex-wrap:wrap;align-items:center;gap:8px}')
expect(css).toContain('.task-compose-native-picker{position:absolute;inset:0;opacity:0;cursor:pointer')
expect(css).not.toContain('@media(max-width:480px){.task-compose-due-row')
expect(app).toContain("composeHasTime.value ? composeTime.value : '23:59'") expect(app).toContain("composeHasTime.value ? composeTime.value : '23:59'")
}) })