diff --git a/apps/web-antd/src/components/form/components/search-time-range-picker.vue b/apps/web-antd/src/components/form/components/search-time-range-picker.vue index 68881a28..65b347ae 100644 --- a/apps/web-antd/src/components/form/components/search-time-range-picker.vue +++ b/apps/web-antd/src/components/form/components/search-time-range-picker.vue @@ -3,6 +3,7 @@ * 带快捷预设的搜索时间范围选择器 * - 上方 RangePicker 手选,下方快捷 link:今天 / 本周 / 本月 / 上一个月 / 下一个月 * - 支持 valueFormat='YYYY-MM-DD'(VbenForm)或 Dayjs 二元组(对账页) + * - 界面按天展示;写出值时开始为当天 00:00:00、结束为当天 23:59:59,避免漏查当天末笔 */ import type { Dayjs } from 'dayjs'; @@ -69,8 +70,9 @@ const innerRange = computed<[Dayjs, Dayjs] | null>({ emit('change', null); return; } + // formatRangeByValueFormat 内会规范到 00:00:00 / 23:59:59 const formatted = formatRangeByValueFormat( - [next[0].startOf('day'), next[1].startOf('day')], + [next[0], next[1]], props.valueFormat, ); mValue.value = formatted as [string, string] | [Dayjs, Dayjs]; @@ -145,7 +147,6 @@ const pickerValue = computed<[Dayjs, Dayjs] | undefined>({ v-bind="$attrs" :disabled="disabled" :format="format" - :value-format="valueFormat" class="search-time-range-picker__picker w-full" @change="onPickerChange" /> diff --git a/apps/web-antd/src/utils/search-time-range.ts b/apps/web-antd/src/utils/search-time-range.ts index ac55828f..f6edb623 100644 --- a/apps/web-antd/src/utils/search-time-range.ts +++ b/apps/web-antd/src/utils/search-time-range.ts @@ -9,9 +9,21 @@ export type SearchTimePreset = | 'prevMonth' | 'nextMonth'; +/** 界面展示用纯日期;提交给后端时再拼时分秒 */ const YMD = 'YYYY-MM-DD'; +/** 查询边界:开始日 00:00:00、结束日 23:59:59,避免当天末笔被滤掉 */ +const YMD_START = 'YYYY-MM-DD 00:00:00'; +const YMD_END = 'YYYY-MM-DD 23:59:59'; -/** 今天日期(Dayjs) */ +/** + * 将区间规范为「开始日 00:00:00 ~ 结束日 23:59:59」 + * 搜索按天选时,结束若不落到当天最后一秒,当天 0 点之后的数据会被漏掉 + */ +export function toSearchDayBounds(range: [Dayjs, Dayjs]): [Dayjs, Dayjs] { + return [range[0].startOf('day'), range[1].endOf('day')]; +} + +/** 今天日期(Dayjs,当天 00:00:00) */ export function todayDayjs(): Dayjs { return dayjs().startOf('day'); } @@ -24,30 +36,27 @@ export function startOfWeekMonday(base: Dayjs = todayDayjs()): Dayjs { return d.subtract(diff, 'day'); } -/** 本月1日 ~ 今天(字符串,供 RangePicker valueFormat) */ +/** 本月1日 00:00:00 ~ 今天 23:59:59(字符串,供表单默认值 / 接口) */ export function monthToTodayRangeString(): [string, string] { - const today = todayDayjs(); - return [today.startOf('month').format(YMD), today.format(YMD)]; + return rangeDayjsToString(monthToTodayRangeDayjs()); } -/** 本月1日 ~ 今天(Dayjs) */ +/** 本月1日 ~ 今天(Dayjs,已含起止边界) */ export function monthToTodayRangeDayjs(): [Dayjs, Dayjs] { const today = todayDayjs(); - return [today.startOf('month'), today]; + return toSearchDayBounds([today.startOf('month'), today]); } -/** 近 N 天(含今天),默认 7 天 */ +/** 近 N 天(含今天),默认 7 天;字符串已含 00:00:00 / 23:59:59 */ export function lastNDaysRangeString(days = 7): [string, string] { - const today = todayDayjs(); - const start = today.subtract(Math.max(days - 1, 0), 'day'); - return [start.format(YMD), today.format(YMD)]; + return rangeDayjsToString(lastNDaysRangeDayjs(days)); } -/** 近 N 天(Dayjs) */ +/** 近 N 天(Dayjs,已含起止边界) */ export function lastNDaysRangeDayjs(days = 7): [Dayjs, Dayjs] { const today = todayDayjs(); const start = today.subtract(Math.max(days - 1, 0), 'day'); - return [start, today]; + return toSearchDayBounds([start, today]); } /** 将 Dayjs / 字符串 / 时间戳统一为 YYYY-MM-DD */ @@ -98,17 +107,17 @@ export function getMonthAnchorDayjs(current: unknown): Dayjs { return todayDayjs(); } -/** 自然月整月,结束日不超过今天 */ +/** 自然月整月,结束日不超过今天(返回值已含起止边界) */ function fullMonthRangeCapped( month: Dayjs, ): [Dayjs, Dayjs] { const start = month.startOf('month'); const end = month.endOf('month').startOf('day'); const today = todayDayjs(); - return [start, end.isAfter(today) ? today : end]; + return toSearchDayBounds([start, end.isAfter(today) ? today : end]); } -/** 按预设计算区间(Dayjs) */ +/** 按预设计算区间(Dayjs,已含 00:00:00 / 23:59:59 边界) */ export function resolveSearchTimePreset( preset: SearchTimePreset, current: unknown, @@ -116,11 +125,11 @@ export function resolveSearchTimePreset( const today = todayDayjs(); switch (preset) { case 'today': - return [today, today]; + return toSearchDayBounds([today, today]); case 'thisWeek': - return [startOfWeekMonday(today), today]; + return toSearchDayBounds([startOfWeekMonday(today), today]); case 'thisMonth': - return [today.startOf('month'), today]; + return toSearchDayBounds([today.startOf('month'), today]); case 'prevMonth': { const anchor = getMonthAnchorDayjs(current); return fullMonthRangeCapped(anchor.subtract(1, 'month')); @@ -134,36 +143,44 @@ export function resolveSearchTimePreset( } } -/** 按预设计算区间(字符串) */ +/** 按预设计算区间(字符串,已含时分秒) */ export function resolveSearchTimePresetString( preset: SearchTimePreset, current: unknown, ): [string, string] { - const [a, b] = resolveSearchTimePreset(preset, current); - return [a.format(YMD), b.format(YMD)]; + return rangeDayjsToString(resolveSearchTimePreset(preset, current)); } -/** Dayjs 区间转字符串 */ +/** + * Dayjs 区间转接口字符串 + * 固定开始 00:00:00、结束 23:59:59,与按天筛选的业务口径一致 + */ export function rangeDayjsToString( range: [Dayjs, Dayjs], ): [string, string] { - return [range[0].format(YMD), range[1].format(YMD)]; + const [start, end] = toSearchDayBounds(range); + return [start.format(YMD_START), end.format(YMD_END)]; } -/** 字符串区间转 Dayjs */ +/** 字符串区间转 Dayjs(兼容纯日期或带时分秒) */ export function rangeStringToDayjs( range: [string, string], ): [Dayjs, Dayjs] { - return [dayjs(range[0], YMD), dayjs(range[1], YMD)]; + return toSearchDayBounds([dayjs(range[0]), dayjs(range[1])]); } -/** 将区间按 valueFormat 输出 */ +/** + * 将区间按表单模式输出 + * valueFormat=YYYY-MM-DD 表示「按天选择」,值仍带完整时分秒边界供接口使用 + * 未传 valueFormat 时返回 Dayjs(同样规范到起止边界) + */ export function formatRangeByValueFormat( range: [Dayjs, Dayjs], valueFormat?: string, ): [string, string] | [Dayjs, Dayjs] { + const bounded = toSearchDayBounds(range); if (valueFormat === YMD) { - return rangeDayjsToString(range); + return rangeDayjsToString(bounded); } - return range; + return bounded; }