From bbce341ea17d593c6205272f921cac6f5b0d2be0 Mon Sep 17 00:00:00 2001 From: Harvey Date: Fri, 4 Sep 2026 19:52:43 +0800 Subject: [PATCH 1/3] fix: max date compare min date (#4185) --- .../pc/app/date-picker/date-range.spec.ts | 25 ++++++++++++ packages/renderless/src/date-range/index.ts | 8 ++-- .../__tests__/end-time-clamp.test.ts | 39 +++++++++++++++++++ 3 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 packages/vue/src/date-range/__tests__/end-time-clamp.test.ts diff --git a/examples/sites/demos/pc/app/date-picker/date-range.spec.ts b/examples/sites/demos/pc/app/date-picker/date-range.spec.ts index a2a553e1e7..33b63e86db 100644 --- a/examples/sites/demos/pc/app/date-picker/date-range.spec.ts +++ b/examples/sites/demos/pc/app/date-picker/date-range.spec.ts @@ -98,3 +98,28 @@ test('测试日期范围选择', async ({ page }) => { await expect(startYear).toHaveValue('2020') await expect(endYear).toHaveValue('2023') }) + +test('同一天结束时间早于开始时间时不改写开始时间 #4185', async ({ page }) => { + page.on('pageerror', (exception) => expect(exception).toBeNull()) + await page.goto('date-picker#date-range') + + const panelTrigger = page.getByPlaceholder('开始日期').nth(1) + await panelTrigger.click() + + const panel = page.locator('.tiny-date-range-picker.has-time') + await expect(panel).toBeVisible() + + const day = panel.locator('td.available.today').first() + await day.click() + await day.click() + + const startTime = panel.getByPlaceholder('开始时间') + const endTime = panel.getByPlaceholder('结束时间') + await startTime.fill('10:00:00') + await startTime.press('Enter') + await endTime.fill('05:00:00') + await endTime.press('Enter') + + await expect(startTime).toHaveValue('10:00:00') + await expect(endTime).toHaveValue('10:00:00') +}) diff --git a/packages/renderless/src/date-range/index.ts b/packages/renderless/src/date-range/index.ts index 11a16504cf..ebddfd5794 100644 --- a/packages/renderless/src/date-range/index.ts +++ b/packages/renderless/src/date-range/index.ts @@ -295,7 +295,7 @@ export const handleDateChange = ) if (state.maxDate < state.minDate) { - state.minDate = state.maxDate + state.maxDate = state.minDate } } } @@ -360,10 +360,10 @@ export const handleTimeChange = ) if (state.maxDate < state.minDate) { - state.minDate = state.maxDate + state.maxDate = state.minDate } - vm.$refs.maxTimePicker.state.value = state.minDate + vm.$refs.maxTimePicker.state.value = state.maxDate state.maxTimePickerVisible = false } } @@ -483,7 +483,7 @@ export const handleMaxTimePick = } if (state.maxDate && state.minDate && state.minDate.getTime() > state.maxDate.getTime()) { - state.minDate = new Date(state.maxDate) + state.maxDate = new Date(state.minDate) } } diff --git a/packages/vue/src/date-range/__tests__/end-time-clamp.test.ts b/packages/vue/src/date-range/__tests__/end-time-clamp.test.ts new file mode 100644 index 0000000000..a2e0a0727c --- /dev/null +++ b/packages/vue/src/date-range/__tests__/end-time-clamp.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, test } from 'vitest' +import { handleMaxTimePick, handleTimeChange } from '@opentiny/vue-renderless/date-range' + +describe('datetimerange end time vs start (#4185)', () => { + test('handleMaxTimePick clamps end time instead of rewriting start', () => { + const state = { + minDate: new Date(2026, 8, 4, 10, 0, 0), + maxDate: new Date(2026, 8, 4, 10, 0, 0), + maxTimePickerVisible: true + } + + handleMaxTimePick({ state })(new Date(2026, 8, 4, 5, 0, 0), false, false) + + expect(state.minDate.getHours()).toBe(10) + expect(state.maxDate.getHours()).toBe(10) + expect(state.maxTimePickerVisible).toBe(false) + }) + + test('handleTimeChange clamps end time instead of rewriting start', () => { + const state = { + minDate: new Date(2026, 8, 4, 10, 0, 0), + maxDate: new Date(2026, 8, 4, 10, 0, 0), + timeFormat: 'HH:mm:ss', + maxTimePickerVisible: true + } + const vm = { + $refs: { + maxTimePicker: { state: { value: null } } + } + } + const t = (key) => key + + handleTimeChange({ state, t, vm })('05:00:00', 'max') + + expect(state.minDate.getHours()).toBe(10) + expect(state.maxDate.getHours()).toBe(10) + expect(vm.$refs.maxTimePicker.state.value).toEqual(state.maxDate) + }) +}) From a7adbf2607b58558367d7e8ff0bd515b466101fd Mon Sep 17 00:00:00 2001 From: Harvey Date: Mon, 7 Sep 2026 10:16:30 +0800 Subject: [PATCH 2/3] fix: newsrc app --- examples/docs/newsrc/App.vue | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/examples/docs/newsrc/App.vue b/examples/docs/newsrc/App.vue index 5f7b8fc78d..d8ca0d20a0 100644 --- a/examples/docs/newsrc/App.vue +++ b/examples/docs/newsrc/App.vue @@ -34,7 +34,6 @@ import { iconEditorMenuRight, iconEditorMenuLeft } from '@opentiny/vue-icon' import TinyPc from './pc.vue' import TinyMobileFirst from './mobile-first.vue' import { hooks } from '@opentiny/vue-common' -import { getCurrentInstance } from 'vue' import { useModeCtx } from './uses' export default { @@ -82,13 +81,12 @@ export default { // 解析url, 生成modeState modeFn.loadPage() - // 全局语言切换(按照官方文档推荐写法) - const ctx = getCurrentInstance()?.ctx - const currentLang = hooks.ref(ctx?.$i18n?.locale || 'zhCN') + const proxy = hooks.getCurrentInstance()?.proxy + const currentLang = hooks.ref(proxy?.$i18n?.locale || 'zhCN') const toggleLocale = () => { - if (ctx?.$i18n) { + if (proxy?.$i18n) { currentLang.value = currentLang.value === 'zhCN' ? 'enUS' : 'zhCN' - ctx.$i18n.locale = currentLang.value + proxy.$i18n.locale = currentLang.value } } From 8148f27eaf9a474817972e067bd13a43b9a20521 Mon Sep 17 00:00:00 2001 From: Harvey Date: Mon, 7 Sep 2026 10:16:44 +0800 Subject: [PATCH 3/3] fix: e2e test --- .../pc/app/date-picker/date-range.spec.ts | 8 +++--- packages/renderless/src/date-range/index.ts | 15 ++++++++--- .../__tests__/end-time-clamp.test.ts | 25 ++++++++++++++++--- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/examples/sites/demos/pc/app/date-picker/date-range.spec.ts b/examples/sites/demos/pc/app/date-picker/date-range.spec.ts index 33b63e86db..0531bb8331 100644 --- a/examples/sites/demos/pc/app/date-picker/date-range.spec.ts +++ b/examples/sites/demos/pc/app/date-picker/date-range.spec.ts @@ -113,12 +113,12 @@ test('同一天结束时间早于开始时间时不改写开始时间 #4185', as await day.click() await day.click() - const startTime = panel.getByPlaceholder('开始时间') - const endTime = panel.getByPlaceholder('结束时间') + const startTime = panel.getByRole('textbox', { name: '开始时间' }) + const endTime = panel.getByRole('textbox', { name: '结束时间' }) await startTime.fill('10:00:00') - await startTime.press('Enter') + await startTime.blur() await endTime.fill('05:00:00') - await endTime.press('Enter') + await endTime.blur() await expect(startTime).toHaveValue('10:00:00') await expect(endTime).toHaveValue('10:00:00') diff --git a/packages/renderless/src/date-range/index.ts b/packages/renderless/src/date-range/index.ts index ebddfd5794..e1d55f7b68 100644 --- a/packages/renderless/src/date-range/index.ts +++ b/packages/renderless/src/date-range/index.ts @@ -284,7 +284,7 @@ export const handleDateChange = ) if (state.minDate > state.maxDate) { - state.maxDate = state.minDate + state.maxDate = new Date(state.minDate) } } else { state.maxDate = modifyDate( @@ -295,7 +295,7 @@ export const handleDateChange = ) if (state.maxDate < state.minDate) { - state.maxDate = state.minDate + state.maxDate = new Date(state.minDate) } } } @@ -327,6 +327,11 @@ export const handleTimeInput = parsedValue.getMinutes(), parsedValue.getSeconds() ) + + if (state.maxDate < state.minDate) { + state.maxDate = new Date(state.minDate) + state.timeUserInput.max = null + } } } } @@ -346,9 +351,10 @@ export const handleTimeChange = ) if (state.minDate > state.maxDate) { - state.maxDate = state.minDate + state.maxDate = new Date(state.minDate) } + state.timeUserInput.min = null vm.$refs.minTimePicker.state.value = state.minDate state.minTimePickerVisible = false } else { @@ -360,9 +366,10 @@ export const handleTimeChange = ) if (state.maxDate < state.minDate) { - state.maxDate = state.minDate + state.maxDate = new Date(state.minDate) } + state.timeUserInput.max = null vm.$refs.maxTimePicker.state.value = state.maxDate state.maxTimePickerVisible = false } diff --git a/packages/vue/src/date-range/__tests__/end-time-clamp.test.ts b/packages/vue/src/date-range/__tests__/end-time-clamp.test.ts index a2e0a0727c..cefd090e19 100644 --- a/packages/vue/src/date-range/__tests__/end-time-clamp.test.ts +++ b/packages/vue/src/date-range/__tests__/end-time-clamp.test.ts @@ -1,8 +1,8 @@ import { describe, expect, test } from 'vitest' -import { handleMaxTimePick, handleTimeChange } from '@opentiny/vue-renderless/date-range' +import { handleMaxTimePick, handleTimeChange, handleTimeInput } from '@opentiny/vue-renderless/date-range' describe('datetimerange end time vs start (#4185)', () => { - test('handleMaxTimePick clamps end time instead of rewriting start', () => { + test('handleMaxTimePick clamps end instead of rewriting start', () => { const state = { minDate: new Date(2026, 8, 4, 10, 0, 0), maxDate: new Date(2026, 8, 4, 10, 0, 0), @@ -16,11 +16,12 @@ describe('datetimerange end time vs start (#4185)', () => { expect(state.maxTimePickerVisible).toBe(false) }) - test('handleTimeChange clamps end time instead of rewriting start', () => { + test('handleTimeChange clamps end, clears draft, syncs picker', () => { const state = { minDate: new Date(2026, 8, 4, 10, 0, 0), maxDate: new Date(2026, 8, 4, 10, 0, 0), timeFormat: 'HH:mm:ss', + timeUserInput: { min: null, max: '05:00:00' }, maxTimePickerVisible: true } const vm = { @@ -34,6 +35,24 @@ describe('datetimerange end time vs start (#4185)', () => { expect(state.minDate.getHours()).toBe(10) expect(state.maxDate.getHours()).toBe(10) + expect(state.maxDate).not.toBe(state.minDate) + expect(state.timeUserInput.max).toBeNull() expect(vm.$refs.maxTimePicker.state.value).toEqual(state.maxDate) }) + + test('handleTimeInput clamps end and clears draft', () => { + const state = { + minDate: new Date(2026, 8, 4, 10, 0, 0), + maxDate: new Date(2026, 8, 4, 10, 0, 0), + timeFormat: 'HH:mm:ss', + timeUserInput: { min: null, max: null } + } + const t = (key) => key + + handleTimeInput({ state, t })('05:00:00', 'max') + + expect(state.minDate.getHours()).toBe(10) + expect(state.maxDate.getHours()).toBe(10) + expect(state.timeUserInput.max).toBeNull() + }) })