diff --git a/src/components/BatchAddDrawer.spec.ts b/src/components/BatchAddDrawer.spec.ts index 84ee648..2862ce6 100644 --- a/src/components/BatchAddDrawer.spec.ts +++ b/src/components/BatchAddDrawer.spec.ts @@ -5,6 +5,7 @@ import { afterEach, describe, expect, it } from 'vitest' import { createMemoryTaskRepository } from '@/data/taskRepository' import { useTaskStore } from '@/stores/tasks' import { useToastStore } from '@/stores/toast' +import { todayIso } from '@/utils/date' import BatchAddDrawer from './BatchAddDrawer.vue' @@ -68,13 +69,16 @@ describe('BatchAddDrawer 提交', () => { expect(store.byColumn.todo.map((task) => task.title)).toEqual(['第一条', '第二条', '第三条']) }) - it('目标列、优先级、截止日期与标签整批共用', async () => { + it('目标列、优先级、起始日期、截止日期与标签整批共用', async () => { const { store, wrapper } = setup() await wrapper.find('textarea').setValue('第一条\n第二条') await buttonByText(wrapper, '进行中')?.trigger('click') await buttonByText(wrapper, '高')?.trigger('click') - await wrapper.find('input[type="date"]').setValue('2026-10-20') + // 两个日期输入按出现顺序为:起始日期、截止日期; + // 每次赋值后 DOM 会被重渲染重建,必须重新查询再操作下一个 + await wrapper.findAll('input[type="date"]')[0]?.setValue('2026-10-12') + await wrapper.findAll('input[type="date"]')[1]?.setValue('2026-10-20') const tagInput = wrapper.find('input[aria-label="添加标签"]') await tagInput.setValue('前端') @@ -85,11 +89,21 @@ describe('BatchAddDrawer 提交', () => { const created = store.byColumn.doing expect(created.map((task) => task.title)).toEqual(['第一条', '第二条']) expect(created[0]?.priority).toBe(3) + expect(created[0]?.startDate).toBe('2026-10-12') expect(created[0]?.dueDate).toBe('2026-10-20') expect(created[0]?.tags).toEqual(['前端']) expect(created[1]?.tags).toEqual(['前端']) }) + it('起始日期默认预填当天', async () => { + const { store, wrapper } = setup() + await wrapper.find('textarea').setValue('A') + + await buttonByText(wrapper, '添加 1 个任务')?.trigger('click') + + expect(store.tasks[0]?.startDate).toBe(todayIso()) + }) + it('提交后请求关闭并提示数量', async () => { const { wrapper } = setup() await wrapper.find('textarea').setValue('A\nB') diff --git a/src/components/BatchAddDrawer.vue b/src/components/BatchAddDrawer.vue index 6987898..3d40789 100644 --- a/src/components/BatchAddDrawer.vue +++ b/src/components/BatchAddDrawer.vue @@ -5,7 +5,7 @@ * 一行一条,整段粘贴即可录入一批 —— 从笔记、Markdown 清单、聊天记录里 * 直接复制过来就能用,行首的「-」「1.」这类列表符号会被自动去掉。 * - * 目标列、优先级、标签、截止日期是这一批**共用**的属性:批量录入时逐个 + * 目标列、优先级、标签、起始日期、截止日期是这一批**共用**的属性:批量录入时逐个 * 任务调字段不现实,而「先统一建出来、再单独改个别任务」正是看板的用法。 */ @@ -15,6 +15,7 @@ import { useTaskStore } from '@/stores/tasks' import { useToastStore } from '@/stores/toast' import { COLUMNS, PRIORITY_LABELS, type ColumnId, type Priority } from '@/types/task' import { parseTaskLines } from '@/utils/batch' +import { todayIso } from '@/utils/date' import DatePicker from './DatePicker.vue' import SideDrawer from './SideDrawer.vue' @@ -33,6 +34,7 @@ const text = ref('') const status = ref('todo') const priority = ref(2) const dueDate = ref(null) +const startDate = ref(todayIso()) const tags = ref([]) const titles = computed(() => parseTaskLines(text.value)) @@ -47,6 +49,8 @@ watch( status.value = 'todo' priority.value = 2 dueDate.value = null + // 每批都从「今天开工」开始,与 store 的默认值保持一致 + startDate.value = todayIso() tags.value = [] }, ) @@ -60,6 +64,7 @@ function submit(): void { status: status.value, priority: priority.value, dueDate: dueDate.value, + startDate: startDate.value, tags: [...tags.value], })), ) @@ -138,6 +143,11 @@ function submit(): void { +
+ 起始日期 + +
+
截止日期 diff --git a/src/components/BoardColumn.spec.ts b/src/components/BoardColumn.spec.ts index 5907aeb..76e1851 100644 --- a/src/components/BoardColumn.spec.ts +++ b/src/components/BoardColumn.spec.ts @@ -38,6 +38,7 @@ function makeTask(overrides: Partial = {}): Task { priority: 2, tags: [], dueDate: null, + startDate: null, sortOrder: 1024, createdAt: '2026-10-18T00:00:00.000Z', updatedAt: '2026-10-18T00:00:00.000Z', diff --git a/src/components/TaskCard.spec.ts b/src/components/TaskCard.spec.ts index 0f13f7d..c7b8ac5 100644 --- a/src/components/TaskCard.spec.ts +++ b/src/components/TaskCard.spec.ts @@ -26,6 +26,7 @@ function makeTask(overrides: Partial = {}): Task { priority: 2, tags: [], dueDate: null, + startDate: null, sortOrder: 1024, createdAt: '2026-10-18T00:00:00.000Z', updatedAt: '2026-10-18T00:00:00.000Z', @@ -131,6 +132,24 @@ describe('截止日期', () => { }) }) +describe('起始日期', () => { + it('起始日期为当天时不显示「开始」', () => { + const wrapper = mountCard({ startDate: dayjs().format('YYYY-MM-DD') }) + expect(wrapper.text()).not.toContain('开始') + }) + + it('起始日期不是当天时显示「M月D日 开始」', () => { + const startDate = dayjs().add(3, 'day').format('YYYY-MM-DD') + const wrapper = mountCard({ startDate }) + expect(wrapper.text()).toContain('开始') + expect(wrapper.text()).toContain(dayjs(startDate).format('M月D日')) + }) + + it('未设置起始日期时不显示「开始」', () => { + expect(mountCard({ startDate: null }).text()).not.toContain('开始') + }) +}) + describe('已完成样式', () => { it('标题加删除线,但不再整卡降透明度', () => { const wrapper = mountCard({ status: 'done' }) diff --git a/src/components/TaskCard.vue b/src/components/TaskCard.vue index fd31016..bb1f017 100644 --- a/src/components/TaskCard.vue +++ b/src/components/TaskCard.vue @@ -3,7 +3,13 @@ import { computed } from 'vue' import { useTagColorStore } from '@/stores/tagColors' import { PRIORITY_LABELS, type MoveDirection, type Task } from '@/types/task' -import { formatCompactDate, formatDateTime, formatDueDateForTask, isOverdue } from '@/utils/date' +import { + formatCompactDate, + formatDateTime, + formatDueDateForTask, + isOverdue, + todayIso, +} from '@/utils/date' const MAX_VISIBLE_TAGS = 3 @@ -73,6 +79,19 @@ const dueLabel = computed(() => ? formatDueDateForTask(props.task.dueDate, props.task.status, props.task.completedAt) : null, ) +/** + * 起始日期:只在「不是今天」时显示。 + * + * 新建任务的起始日期默认就是当天,若照常展示,几乎每张卡片都会挂一条 + * 相同的「M月D日 开始」,把标签和截止日期挤到第二行却提供不了新信息。 + * 保留下来的是真正有排期差异的那些:提前排的、拖到未来开工的。 + */ +const startLabel = computed(() => { + const startDate = props.task.startDate + if (!startDate || startDate === todayIso()) return null + const compact = formatCompactDate(startDate) + return compact ? `${compact} 开始` : null +}) const visibleTags = computed(() => props.task.tags.slice(0, MAX_VISIBLE_TAGS).map((tag) => ({ name: tag, @@ -244,6 +263,14 @@ const priorityTextClass = computed(() => { {{ PRIORITY_LABELS[task.priority] }} + + {{ startLabel }} + + { @@ -81,7 +83,10 @@ describe('TaskDetailDrawer 保存', () => { await wrapper.find('input[aria-label="标题"]').setValue('新标题') await wrapper.find('textarea[aria-label="描述"]').setValue('新描述') await buttonByText(wrapper, '高')?.trigger('click') - await wrapper.find('input[type="date"]').setValue('2026-11-11') + // 两个日期输入按出现顺序为:起始日期、截止日期; + // 每次赋值后 DOM 会被重渲染重建,必须重新查询再操作下一个 + await wrapper.findAll('input[type="date"]')[0]?.setValue('2026-11-05') + await wrapper.findAll('input[type="date"]')[1]?.setValue('2026-11-11') await wrapper.find('input[aria-label="添加标签"]').setValue('前端') await wrapper.find('input[aria-label="添加标签"]').trigger('keydown', { key: 'Enter' }) await buttonByText(wrapper, '保存')?.trigger('click') @@ -90,6 +95,7 @@ describe('TaskDetailDrawer 保存', () => { expect(task?.title).toBe('新标题') expect(task?.description).toBe('新描述') expect(task?.priority).toBe(3) + expect(task?.startDate).toBe('2026-11-05') expect(task?.dueDate).toBe('2026-11-11') expect(task?.tags).toEqual(['设计', '前端']) expect(wrapper.emitted('close')).toHaveLength(1) @@ -118,7 +124,9 @@ describe('TaskDetailDrawer 保存', () => { it('清空截止日期会写入 null', async () => { const { store, wrapper, taskId } = setup() - await buttonByText(wrapper, '清除')?.trigger('click') + // 起始日期与截止日期各有一个「清除」,按出现顺序取后者 + const clearButtons = wrapper.findAll('button').filter((node) => node.text() === '清除') + await clearButtons[1]?.trigger('click') await buttonByText(wrapper, '保存')?.trigger('click') expect(store.tasks.find((item) => item.id === taskId)?.dueDate).toBeNull() }) diff --git a/src/components/TaskDetailDrawer.vue b/src/components/TaskDetailDrawer.vue index 8061702..d7cbaea 100644 --- a/src/components/TaskDetailDrawer.vue +++ b/src/components/TaskDetailDrawer.vue @@ -24,6 +24,7 @@ const description = ref('') const status = ref('todo') const priority = ref(2) const dueDate = ref(null) +const startDate = ref(null) const tags = ref([]) const titleError = ref(null) @@ -45,6 +46,7 @@ watch( status.value = task?.status ?? 'todo' priority.value = task?.priority ?? 2 dueDate.value = task?.dueDate ?? null + startDate.value = task?.startDate ?? null tags.value = task ? [...task.tags] : [] titleError.value = null confirmingClose.value = false @@ -92,6 +94,7 @@ const dirty = computed(() => { status.value !== task.status || priority.value !== task.priority || dueDate.value !== task.dueDate || + startDate.value !== task.startDate || !sameTags(tags.value, task.tags) ) }) @@ -126,6 +129,7 @@ function save(): void { status: status.value, priority: priority.value, dueDate: dueDate.value, + startDate: startDate.value, tags: [...tags.value], }) emit('close') @@ -239,6 +243,11 @@ function remove(): void {
+
+ 起始日期 + +
+
截止日期 diff --git a/src/data/schema.spec.ts b/src/data/schema.spec.ts index 9cc321d..682c89f 100644 --- a/src/data/schema.spec.ts +++ b/src/data/schema.spec.ts @@ -21,6 +21,7 @@ function validTask(overrides: Record = {}): Record { expect(result?.sortOrder).toBe(0) }) + it('合法 startDate 保留,非法 startDate 收敛为 null', () => { + expect(coerceTask(validTask({ startDate: '2026-10-01' }))?.startDate).toBe('2026-10-01') + expect(coerceTask(validTask({ startDate: '2026-02-31' }))?.startDate).toBeNull() + expect(coerceTask(validTask({ startDate: undefined }))?.startDate).toBeNull() + }) + it('tags 过滤掉非字符串与空白项', () => { const result = coerceTask(validTask({ tags: ['ok', 1, null, ' ', 'ok2'] })) expect(result?.tags).toEqual(['ok', 'ok2']) @@ -265,17 +272,46 @@ describe('版本迁移', () => { expect(state.tasks[0]?.archivedAt).toBe('2026-01-03T00:00:00.000Z') }) - it('v0/v1/v2 都能一路迁移到 v3', () => { + it('v3 数据迁移到 v4 时补上 startDate,其余字段不变', () => { + const v3 = JSON.stringify({ + schemaVersion: 3, + tasks: [ + { + id: 'legacy', + title: '旧任务', + status: 'todo', + priority: 2, + sortOrder: 1024, + createdAt: '2026-01-01T00:00:00.000Z', + updatedAt: '2026-01-01T00:00:00.000Z', + completedAt: null, + archivedAt: null, + deletedAt: null, + }, + ], + }) + + const state = parsePersistedState(v3) + + expect(state.schemaVersion).toBe(SCHEMA_VERSION) + expect(state.tasks[0]?.startDate).toBeNull() + expect(state.tasks[0]?.deletedAt).toBeNull() + expect(state.tasks[0]?.title).toBe('旧任务') + }) + + it('v0/v1/v2/v3 都能一路迁移到当前版本', () => { const builds = [ () => ({ tasks: [{ id: 'a', title: 'A', status: 'todo', priority: 2 }] }), () => ({ schemaVersion: 1, tasks: [{ id: 'a', title: 'A', status: 'todo', priority: 2 }] }), () => ({ schemaVersion: 2, tasks: [{ id: 'a', title: 'A', status: 'todo', priority: 2 }] }), + () => ({ schemaVersion: 3, tasks: [{ id: 'a', title: 'A', status: 'todo', priority: 2 }] }), ] for (const build of builds) { const state = parsePersistedState(JSON.stringify(build())) - expect(state.schemaVersion).toBe(3) + expect(state.schemaVersion).toBe(SCHEMA_VERSION) expect(state.tasks[0]?.deletedAt).toBeNull() + expect(state.tasks[0]?.startDate).toBeNull() } }) diff --git a/src/data/schema.ts b/src/data/schema.ts index 69da18a..53a0ae5 100644 --- a/src/data/schema.ts +++ b/src/data/schema.ts @@ -9,7 +9,7 @@ import { isColumnId, isPriority, type Task } from '@/types/task' -export const SCHEMA_VERSION = 3 +export const SCHEMA_VERSION = 4 /** 主数据在 localStorage 中的键名 */ export const STORAGE_KEY = 'kanban.tasks' @@ -69,6 +69,7 @@ export function coerceTask(value: unknown): Task | null { const tags = rawTags.filter((tag): tag is string => typeof tag === 'string' && tag.trim() !== '') const dueDate = value.dueDate + const startDate = value.startDate const sortOrder = value.sortOrder const createdAt = readString(value, 'createdAt') ?? new Date().toISOString() const updatedAt = readString(value, 'updatedAt') ?? createdAt @@ -85,6 +86,7 @@ export function coerceTask(value: unknown): Task | null { priority, tags, dueDate: isValidDateString(dueDate) ? dueDate : null, + startDate: isValidDateString(startDate) ? startDate : null, sortOrder: typeof sortOrder === 'number' && Number.isFinite(sortOrder) ? sortOrder : 0, createdAt, updatedAt, @@ -117,6 +119,7 @@ type Migration = (state: PersistedState) => PersistedState * - v0:早期数据没有 schemaVersion 字段,任务结构与 v1 一致 * - v1 -> v2:新增 archivedAt 字段(归档功能) * - v2 -> v3:新增 deletedAt 字段(回收站 / 软删除) + * - v3 -> v4:新增 startDate 字段(起始日期) * 未来新增字段时,在此登记 n -> n+1 的转换,并同步提升 SCHEMA_VERSION。 */ const MIGRATIONS: Record = { @@ -129,6 +132,10 @@ const MIGRATIONS: Record = { schemaVersion: 3, tasks: state.tasks.map((task) => ({ ...task, deletedAt: null })), }), + 3: (state) => ({ + schemaVersion: 4, + tasks: state.tasks.map((task) => ({ ...task, startDate: null })), + }), } /** diff --git a/src/data/snapshot.spec.ts b/src/data/snapshot.spec.ts index a09e47d..a51859e 100644 --- a/src/data/snapshot.spec.ts +++ b/src/data/snapshot.spec.ts @@ -39,6 +39,7 @@ function makeTask(id: string): Task { priority: 2, tags: [], dueDate: null, + startDate: null, sortOrder: 1024, createdAt: '2026-10-18T00:00:00.000Z', updatedAt: '2026-10-18T00:00:00.000Z', diff --git a/src/data/taskRepository.spec.ts b/src/data/taskRepository.spec.ts index 4f30a2b..52bea23 100644 --- a/src/data/taskRepository.spec.ts +++ b/src/data/taskRepository.spec.ts @@ -38,6 +38,7 @@ function makeTask(overrides: Partial = {}): Task { priority: 2, tags: [], dueDate: null, + startDate: null, sortOrder: 1024, createdAt: '2026-10-18T00:00:00.000Z', updatedAt: '2026-10-18T00:00:00.000Z', diff --git a/src/stores/tasks.spec.ts b/src/stores/tasks.spec.ts index 2f0daa8..36c480a 100644 --- a/src/stores/tasks.spec.ts +++ b/src/stores/tasks.spec.ts @@ -3,6 +3,7 @@ import { describe, expect, it, vi } from 'vitest' import { createMemoryTaskRepository, type TaskRepository } from '@/data/taskRepository' import type { Task } from '@/types/task' +import { todayIso } from '@/utils/date' import { useTagColorStore } from './tagColors' import { UNDO_LIMIT, useTaskStore } from './tasks' @@ -33,6 +34,7 @@ describe('初始化', () => { priority: 3, tags: [], dueDate: null, + startDate: null, sortOrder: 1024, createdAt: '2026-10-18T00:00:00.000Z', updatedAt: '2026-10-18T00:00:00.000Z', @@ -162,7 +164,14 @@ describe('批量创建', () => { it('每条任务可以带各自的列、优先级、标签与截止日期', () => { const store = freshStore() const created = store.createTasks([ - { title: 'A', status: 'doing', priority: 3, tags: ['前端'], dueDate: '2026-10-20' }, + { + title: 'A', + status: 'doing', + priority: 3, + tags: ['前端'], + dueDate: '2026-10-20', + startDate: '2026-10-12', + }, { title: 'B', status: 'done' }, ]) @@ -170,9 +179,21 @@ describe('批量创建', () => { expect(created[0]?.priority).toBe(3) expect(created[0]?.tags).toEqual(['前端']) expect(created[0]?.dueDate).toBe('2026-10-20') + expect(created[0]?.startDate).toBe('2026-10-12') expect(created[1]?.completedAt).not.toBeNull() }) + it('起始日期默认为当天,显式传 null 时保持为空', () => { + const store = freshStore() + const created = store.createTasks([ + { title: '未指定' }, + { title: '显式清空', startDate: null }, + ]) + + expect(created[0]?.startDate).toBe(todayIso()) + expect(created[1]?.startDate).toBeNull() + }) + it('跨列批量时各列内部都保持输入顺序', () => { const store = freshStore() store.createTasks([ @@ -533,6 +554,7 @@ describe('导出与导入', () => { priority: 2, tags: [], dueDate: null, + startDate: null, sortOrder: 1024, createdAt: '2026-10-18T00:00:00.000Z', updatedAt: '2026-10-18T00:00:00.000Z', @@ -691,6 +713,7 @@ function makePersistedTask(overrides: Partial = {}): Task { priority: 2, tags: [], dueDate: null, + startDate: null, sortOrder: 1024, createdAt: '2026-09-01T00:00:00.000Z', updatedAt: '2026-09-01T00:00:00.000Z', @@ -938,6 +961,19 @@ describe('批量编辑', () => { expect(store.activeTasks.every((task) => task.dueDate === null)).toBe(true) }) + it('批量设置与清除起始日期', () => { + const store = freshStore() + const a = store.createTask({ title: 'A' }) + const b = store.createTask({ title: 'B' }) + const ids = [a?.id ?? '', b?.id ?? ''] + + store.updateTasks(ids, { startDate: '2026-11-03' }) + expect(store.activeTasks.every((task) => task.startDate === '2026-11-03')).toBe(true) + + store.updateTasks(ids, { startDate: null }) + expect(store.activeTasks.every((task) => task.startDate === null)).toBe(true) + }) + it('批量归档只处理未删除任务', () => { const store = freshStore() const a = store.createTask({ title: 'A', status: 'done' }) @@ -1279,6 +1315,7 @@ describe('撤销与重做', () => { priority: 2, tags: [], dueDate: null, + startDate: null, sortOrder: 1024, createdAt: longAgo, updatedAt: longAgo, diff --git a/src/stores/tasks.ts b/src/stores/tasks.ts index b78c48e..07dd89c 100644 --- a/src/stores/tasks.ts +++ b/src/stores/tasks.ts @@ -19,6 +19,7 @@ import { type TaskRepository, } from '@/data/taskRepository' import { COLUMN_IDS, DEFAULT_PRIORITY, type ColumnId, type Priority, type Task } from '@/types/task' +import { todayIso } from '@/utils/date' import { createId } from '@/utils/id' import { mergeTasks as mergeTaskLists } from '@/utils/merge' import { @@ -61,6 +62,8 @@ export interface CreateTaskInput { priority?: Priority tags?: string[] dueDate?: string | null + /** 不传则默认当天;显式传 null 表示不填起始日期 */ + startDate?: string | null status?: ColumnId } @@ -358,6 +361,8 @@ export const useTaskStore = defineStore('tasks', () => { priority: input.priority ?? DEFAULT_PRIORITY, tags: normalizeTags(input.tags ?? []), dueDate: input.dueDate ?? null, + // 起始日期默认当天:新建任务天然「今天开工」,用户显式清空时仍保持 null + startDate: input.startDate === undefined ? todayIso() : input.startDate, sortOrder: sortOrderForPosition(working[status], 0), createdAt: now, updatedAt: now, @@ -431,6 +436,7 @@ export const useTaskStore = defineStore('tasks', () => { if (patch.status !== undefined) taskPatch.status = patch.status if (patch.priority !== undefined) taskPatch.priority = patch.priority if (patch.dueDate !== undefined) taskPatch.dueDate = patch.dueDate + if (patch.startDate !== undefined) taskPatch.startDate = patch.startDate if (patch.addTags !== undefined || patch.removeTags !== undefined) { const tags = tagsAfterBatchPatch(task.tags, patch) diff --git a/src/types/task.ts b/src/types/task.ts index 848e57b..ba345d5 100644 --- a/src/types/task.ts +++ b/src/types/task.ts @@ -28,6 +28,8 @@ export interface Task { tags: string[] /** 仅日期,格式 YYYY-MM-DD;无截止日期时为 null */ dueDate: string | null + /** 仅日期,格式 YYYY-MM-DD;任务尚未排期时为 null */ + startDate: string | null /** 同列内排序用的浮点数,采用相邻中点插入 */ sortOrder: number createdAt: string diff --git a/src/utils/activity.spec.ts b/src/utils/activity.spec.ts index 3822b3f..70304c5 100644 --- a/src/utils/activity.spec.ts +++ b/src/utils/activity.spec.ts @@ -28,6 +28,7 @@ function makeTask(completedAt: string | null, overrides: Partial = {}): Ta priority: 2, tags: [], dueDate: null, + startDate: null, sortOrder: 1024, createdAt: '2026-10-01T00:00:00.000Z', updatedAt: '2026-10-01T00:00:00.000Z', diff --git a/src/utils/archive.spec.ts b/src/utils/archive.spec.ts index f425bb4..2d1a1ee 100644 --- a/src/utils/archive.spec.ts +++ b/src/utils/archive.spec.ts @@ -25,6 +25,7 @@ function makeTask(overrides: Partial = {}): Task { priority: 2, tags: [], dueDate: null, + startDate: null, sortOrder: 1024, createdAt: '2026-10-01T00:00:00.000Z', updatedAt: '2026-10-01T00:00:00.000Z', diff --git a/src/utils/filter.spec.ts b/src/utils/filter.spec.ts index 7d3a202..026dc82 100644 --- a/src/utils/filter.spec.ts +++ b/src/utils/filter.spec.ts @@ -14,6 +14,7 @@ function makeTask(overrides: Partial = {}): Task { priority: 2, tags: [], dueDate: null, + startDate: null, sortOrder: 1024, createdAt: '2026-10-18T00:00:00.000Z', updatedAt: '2026-10-18T00:00:00.000Z', diff --git a/src/utils/merge.spec.ts b/src/utils/merge.spec.ts index 8f7bd80..50f3402 100644 --- a/src/utils/merge.spec.ts +++ b/src/utils/merge.spec.ts @@ -13,6 +13,7 @@ function makeTask(overrides: Partial = {}): Task { priority: 2, tags: [], dueDate: null, + startDate: null, sortOrder: 1024, createdAt: '2026-10-01T00:00:00.000Z', updatedAt: '2026-10-01T00:00:00.000Z', diff --git a/src/utils/sortOrder.spec.ts b/src/utils/sortOrder.spec.ts index 488939a..5e1a5b3 100644 --- a/src/utils/sortOrder.spec.ts +++ b/src/utils/sortOrder.spec.ts @@ -27,6 +27,7 @@ function makeTask(overrides: Partial = {}): Task { priority: 2, tags: [], dueDate: null, + startDate: null, sortOrder: sequence * SORT_ORDER_STEP, createdAt: '2026-10-18T00:00:00.000Z', updatedAt: '2026-10-18T00:00:00.000Z', diff --git a/src/utils/stats.spec.ts b/src/utils/stats.spec.ts index 30457a0..fa7d6c4 100644 --- a/src/utils/stats.spec.ts +++ b/src/utils/stats.spec.ts @@ -22,6 +22,7 @@ function makeTask(overrides: Partial = {}): Task { priority: 2, tags: [], dueDate: null, + startDate: null, sortOrder: 1024, createdAt: '2026-10-01T00:00:00.000Z', updatedAt: '2026-10-01T00:00:00.000Z', diff --git a/src/utils/tags.spec.ts b/src/utils/tags.spec.ts index 484bafb..e14645a 100644 --- a/src/utils/tags.spec.ts +++ b/src/utils/tags.spec.ts @@ -13,6 +13,7 @@ function makeTask(id: string, tags: string[]): Task { priority: 2, tags, dueDate: null, + startDate: null, sortOrder: 1024, createdAt: '2026-10-01T00:00:00.000Z', updatedAt: '2026-10-01T00:00:00.000Z', diff --git a/src/utils/taskPatch.spec.ts b/src/utils/taskPatch.spec.ts index c87852e..d174a96 100644 --- a/src/utils/taskPatch.spec.ts +++ b/src/utils/taskPatch.spec.ts @@ -15,6 +15,7 @@ function makeTask(overrides: Partial = {}): Task { priority: 2, tags: ['设计'], dueDate: null, + startDate: null, sortOrder: 1024, createdAt: '2026-10-01T00:00:00.000Z', updatedAt: '2026-10-01T00:00:00.000Z', diff --git a/src/utils/taskPatch.ts b/src/utils/taskPatch.ts index cc0f3af..4f306ad 100644 --- a/src/utils/taskPatch.ts +++ b/src/utils/taskPatch.ts @@ -23,6 +23,8 @@ export interface BatchPatch { priority?: Priority /** null 表示清空截止日期 */ dueDate?: string | null + /** null 表示清空起始日期 */ + startDate?: string | null addTags?: readonly string[] removeTags?: readonly string[] /** true 归档,false 取消归档 */ @@ -78,6 +80,7 @@ export function isBatchPatchEmpty(patch: BatchPatch): boolean { patch.status === undefined && patch.priority === undefined && patch.dueDate === undefined && + patch.startDate === undefined && !patch.addTags?.length && !patch.removeTags?.length && patch.archived === undefined diff --git a/src/utils/today.spec.ts b/src/utils/today.spec.ts index 49fbe02..6fc18ba 100644 --- a/src/utils/today.spec.ts +++ b/src/utils/today.spec.ts @@ -15,6 +15,7 @@ function makeTask(overrides: Partial = {}): Task { priority: 2, tags: [], dueDate: null, + startDate: null, sortOrder: 1024, createdAt: '2026-10-01T00:00:00.000Z', updatedAt: '2026-10-01T00:00:00.000Z', diff --git a/src/views/StatsView.spec.ts b/src/views/StatsView.spec.ts index b3ac316..f1037e1 100644 --- a/src/views/StatsView.spec.ts +++ b/src/views/StatsView.spec.ts @@ -26,6 +26,7 @@ function makeTask(completedAt: string | null, overrides: Partial = {}): Ta priority: 2, tags: [], dueDate: null, + startDate: null, sortOrder: 1024, createdAt: '2026-10-01T00:00:00.000Z', updatedAt: '2026-10-01T00:00:00.000Z',