From 14370fd08dec464ef2706f4300864296a2fb1835 Mon Sep 17 00:00:00 2001 From: chenshoulin Date: Sun, 18 Oct 2026 18:09:20 +0800 Subject: [PATCH] =?UTF-8?q?fix(ui):=20=E6=97=A5=E6=9C=9F=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E5=99=A8=E7=A9=BA=E5=80=BC=E6=97=B6=E6=94=B9=E7=94=A8=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E5=8D=A0=E4=BD=8D=EF=BC=8C=E9=81=AE=E4=BD=8F?= =?UTF-8?q?=E6=B5=8F=E8=A7=88=E5=99=A8=E8=87=AA=E5=B8=A6=E7=9A=84=20yyyy/m?= =?UTF-8?q?m/=E6=97=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题:截止日期为空时,Chrome 会按页面 lang="zh-CN" 拼出「yyyy/mm/日」 这个半英半中的格式提示。既与界面语言不统一,又容易被误读成「已经有内容」。 处理:空值且未聚焦时把原生分段文字设为透明,改由组件自己渲染「选择日期」 占位(与标签输入框的「回车添加标签」风格一致)。 两个必要的边界: - 只在 :not(:focus) 时透明 —— 否则聚焦编辑时连正在输入的数字都看不见。 实测聚焦后原生分段会正常显示为「年/月/日」,比空值时的混合格式更干净。 - 占位层用 pointer-events-none,点击仍能唤起原生日期选择器。 只作用于 :::-webkit-datetime-edit(Chromium 系)。Firefox 的分段控件 不支持该伪元素,会退回浏览器自身的提示——这比强行自绘一个日期控件更好, 原生控件的可访问性与格式保证是这个组件当初的选型理由。 验证:单元测试 485 个(新增 5 个针对占位与遮挡)、E2E 25 个通过; 空值与聚焦两种状态均已截图确认。 --- src/components/DatePicker.spec.ts | 27 +++++++++++++++++++++ src/components/DatePicker.vue | 40 ++++++++++++++++++++++++------- 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/src/components/DatePicker.spec.ts b/src/components/DatePicker.spec.ts index 0f52bff..cee6677 100644 --- a/src/components/DatePicker.spec.ts +++ b/src/components/DatePicker.spec.ts @@ -58,3 +58,30 @@ describe('DatePicker', () => { expect(mountPicker(null, '开始日期').find('input').attributes('aria-label')).toBe('开始日期') }) }) + +describe('DatePicker 空值占位', () => { + it('空值时显示自定义占位文案', () => { + expect(mountPicker(null).text()).toContain('选择日期') + }) + + it('有值时不再显示占位文案', () => { + expect(mountPicker('2026-10-20').text()).not.toContain('选择日期') + }) + + it('空值时遮住浏览器自带的格式提示(yyyy/mm/日)', () => { + const classes = mountPicker(null).find('input').classes().join(' ') + expect(classes).toContain('::-webkit-datetime-edit') + // 只在未聚焦时透明,否则聚焦后看不见正在编辑的数字 + expect(classes).toContain(':not(:focus)') + }) + + it('有值时不做任何遮挡', () => { + const classes = mountPicker('2026-10-20').find('input').classes().join(' ') + expect(classes).not.toContain('::-webkit-datetime-edit') + }) + + it('占位层不拦截鼠标事件(点击仍能唤起原生选择器)', () => { + const placeholder = mountPicker(null).find('span') + expect(placeholder.classes()).toContain('pointer-events-none') + }) +}) diff --git a/src/components/DatePicker.vue b/src/components/DatePicker.vue index eef8134..fbff563 100644 --- a/src/components/DatePicker.vue +++ b/src/components/DatePicker.vue @@ -6,9 +6,13 @@ * - 其 value 规范就是 YYYY-MM-DD,与 ADR-003 的存储约定天然一致 * - 自带可访问性、键盘操作与本地化日历,无需自己实现 * - 深色模式下日历弹层跟随 html 的 color-scheme + * + * 空值时刻意遮住原生占位文字:Chrome 会按页面 lang 拼出「yyyy/mm/日」 + * 这种半英半中的提示,既别扭又容易被误读成已有内容。改为显示自己的 + * 「选择日期」,聚焦时再让位给原生分段输入(否则编辑时看不见数字)。 */ -defineProps<{ modelValue: string | null; label?: string }>() +const props = defineProps<{ modelValue: string | null; label?: string }>() const emit = defineEmits<{ 'update:modelValue': [value: string | null] }>() @@ -16,17 +20,37 @@ function onChange(event: Event): void { const value = (event.target as HTMLInputElement).value emit('update:modelValue', value === '' ? null : value) } + +/** 空值且未聚焦时才遮住原生占位文字 */ +function hideNativePlaceholder(): boolean { + return props.modelValue === null +}