feat(albums): date-only pickers for start/end (create & edit, admin + app)
- 管理后台:DatePicker type=datetime → date;提交归一(开始=当天 00:00、 结束=当天 23:59 整天活动语义;契约仍 datetime 仅交互粒度) - App:去掉时间选择步骤(选日期即定;同上归一);表单显示只显 yyyy/M/d - e2e 日期填入改 '2026-10-01'(date 格式);widget 断言日期粒度 - admin 87/87 + build、app 397/397 全绿 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -23,7 +23,7 @@ async function createAlbumViaUi(page: Page): Promise<string> {
|
|||||||
await page.getByTestId('album-form-title-zh-CN').fill(`E2E治理相册 ${suffix}`)
|
await page.getByTestId('album-form-title-zh-CN').fill(`E2E治理相册 ${suffix}`)
|
||||||
await page.getByRole('tab', { name: '英文' }).click()
|
await page.getByRole('tab', { name: '英文' }).click()
|
||||||
await page.getByTestId('album-form-title-en').fill(`E2E Gov Album ${suffix}`)
|
await page.getByTestId('album-form-title-en').fill(`E2E Gov Album ${suffix}`)
|
||||||
await page.getByTestId('album-form-start-at').locator('input').fill('2026-10-01 10:00:00')
|
await page.getByTestId('album-form-start-at').locator('input').fill('2026-10-01') // UI06:日期粒度
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
await page.getByTestId('album-form-submit').click()
|
await page.getByTestId('album-form-submit').click()
|
||||||
// 保存成功后跳相册详情;返回列表做断言
|
// 保存成功后跳相册详情;返回列表做断言
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ async function createAlbumViaUi(page: Page): Promise<string> {
|
|||||||
await page.getByRole('tab', { name: '英文' }).click()
|
await page.getByRole('tab', { name: '英文' }).click()
|
||||||
await page.getByTestId('album-form-title-en').fill(`E2E Album ${suffix}`)
|
await page.getByTestId('album-form-title-en').fill(`E2E Album ${suffix}`)
|
||||||
// ElDatePicker 透传 attr 被 ElTooltip 吞掉:组件里用普通 div 挂 data-test,这里进 div 找 input
|
// ElDatePicker 透传 attr 被 ElTooltip 吞掉:组件里用普通 div 挂 data-test,这里进 div 找 input
|
||||||
await page.getByTestId('album-form-start-at').locator('input').fill('2026-10-01 10:00:00')
|
await page.getByTestId('album-form-start-at').locator('input').fill('2026-10-01') // UI06:日期粒度
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
await page.getByTestId('album-form-submit').click()
|
await page.getByTestId('album-form-submit').click()
|
||||||
// 保存成功后跳相册详情;返回列表做断言
|
// 保存成功后跳相册详情;返回列表做断言
|
||||||
|
|||||||
@@ -58,18 +58,20 @@
|
|||||||
<!-- ElDatePicker 的透传 attr 被 ElTooltip 吞掉,data-test 挂不到 DOM:
|
<!-- ElDatePicker 的透传 attr 被 ElTooltip 吞掉,data-test 挂不到 DOM:
|
||||||
外层普通 div 承载选择器,测试经 div 内 input 定位 -->
|
外层普通 div 承载选择器,测试经 div 内 input 定位 -->
|
||||||
<div data-test="album-form-start-at">
|
<div data-test="album-form-start-at">
|
||||||
|
<!-- UI06:只选日期(开始=当天 00:00;契约仍 datetime) -->
|
||||||
<ElDatePicker
|
<ElDatePicker
|
||||||
v-model="form.startAt"
|
v-model="form.startAt"
|
||||||
type="datetime"
|
type="date"
|
||||||
:placeholder="t('albumsManage.form.fieldStartAt')"
|
:placeholder="t('albumsManage.form.fieldStartAt')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem :label="t('albumsManage.form.fieldEndAt')" :error="fieldErrors.endAt">
|
<ElFormItem :label="t('albumsManage.form.fieldEndAt')" :error="fieldErrors.endAt">
|
||||||
<div data-test="album-form-end-at">
|
<div data-test="album-form-end-at">
|
||||||
|
<!-- UI06:只选日期(结束=当天 23:59 全天结束语义) -->
|
||||||
<ElDatePicker
|
<ElDatePicker
|
||||||
v-model="form.endAt"
|
v-model="form.endAt"
|
||||||
type="datetime"
|
type="date"
|
||||||
clearable
|
clearable
|
||||||
:placeholder="t('albumsManage.form.fieldEndAt')"
|
:placeholder="t('albumsManage.form.fieldEndAt')"
|
||||||
/>
|
/>
|
||||||
@@ -332,14 +334,34 @@
|
|||||||
drafts['zh-CN'],
|
drafts['zh-CN'],
|
||||||
drafts.en
|
drafts.en
|
||||||
]) as Api.Album.Translation[]
|
]) as Api.Album.Translation[]
|
||||||
const endAt = form.endAt ? form.endAt.toISOString() : null
|
// UI06 纯日期选择:开始=当天 00:00、结束=当天 23:59(整天活动语义)。
|
||||||
|
const startAtDate = form.startAt!
|
||||||
|
const startAt = new Date(
|
||||||
|
startAtDate.getFullYear(),
|
||||||
|
startAtDate.getMonth(),
|
||||||
|
startAtDate.getDate(),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
).toISOString()
|
||||||
|
const endDate = form.endAt
|
||||||
|
const endAt = endDate
|
||||||
|
? new Date(
|
||||||
|
endDate.getFullYear(),
|
||||||
|
endDate.getMonth(),
|
||||||
|
endDate.getDate(),
|
||||||
|
23,
|
||||||
|
59,
|
||||||
|
59
|
||||||
|
).toISOString()
|
||||||
|
: null
|
||||||
let saved: Api.Album.Detail
|
let saved: Api.Album.Detail
|
||||||
if (albumId.value) {
|
if (albumId.value) {
|
||||||
// 编辑:PATCH 全量可编辑字段(开关独立修改;translations 全量替换)
|
// 编辑:PATCH 全量可编辑字段(开关独立修改;translations 全量替换)
|
||||||
saved = await fetchUpdateAlbum(albumId.value, {
|
saved = await fetchUpdateAlbum(albumId.value, {
|
||||||
translations,
|
translations,
|
||||||
defaultLocale: form.defaultLocale,
|
defaultLocale: form.defaultLocale,
|
||||||
startAt: form.startAt!.toISOString(),
|
startAt,
|
||||||
endAt,
|
endAt,
|
||||||
timezone: form.timezone.trim() || 'Asia/Shanghai',
|
timezone: form.timezone.trim() || 'Asia/Shanghai',
|
||||||
uploadEnabled: form.uploadEnabled,
|
uploadEnabled: form.uploadEnabled,
|
||||||
@@ -352,7 +374,7 @@
|
|||||||
saved = await fetchCreateAlbum({
|
saved = await fetchCreateAlbum({
|
||||||
translations,
|
translations,
|
||||||
defaultLocale: form.defaultLocale,
|
defaultLocale: form.defaultLocale,
|
||||||
startAt: form.startAt!.toISOString(),
|
startAt,
|
||||||
endAt,
|
endAt,
|
||||||
timezone: form.timezone.trim() || 'Asia/Shanghai',
|
timezone: form.timezone.trim() || 'Asia/Shanghai',
|
||||||
downloadEnabled: form.downloadEnabled,
|
downloadEnabled: form.downloadEnabled,
|
||||||
|
|||||||
@@ -355,17 +355,14 @@ class _AlbumFormPageState extends ConsumerState<AlbumFormPage>
|
|||||||
lastDate: DateTime(2100),
|
lastDate: DateTime(2100),
|
||||||
);
|
);
|
||||||
if (pickedDate == null || !mounted) return;
|
if (pickedDate == null || !mounted) return;
|
||||||
final pickedTime = await showTimePicker(
|
// UI06(用户需求 2026-09-23):只选日期不带时间——开始=当天 00:00,
|
||||||
context: context,
|
// 结束=当天 23:59(活动整天语义;契约仍为 datetime,仅交互粒度变化)。
|
||||||
initialTime: TimeOfDay(hour: wall.hour, minute: wall.minute),
|
|
||||||
);
|
|
||||||
if (pickedTime == null || !mounted) return;
|
|
||||||
final naive = DateTime(
|
final naive = DateTime(
|
||||||
pickedDate.year,
|
pickedDate.year,
|
||||||
pickedDate.month,
|
pickedDate.month,
|
||||||
pickedDate.day,
|
pickedDate.day,
|
||||||
pickedTime.hour,
|
isStart ? 0 : 23,
|
||||||
pickedTime.minute,
|
isStart ? 0 : 59,
|
||||||
);
|
);
|
||||||
final utc = albumWallClockToUtc(naive, tzName);
|
final utc = albumWallClockToUtc(naive, tzName);
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -764,9 +761,13 @@ class _AlbumFormPageState extends ConsumerState<AlbumFormPage>
|
|||||||
final l10n = AppLocalizations.of(context);
|
final l10n = AppLocalizations.of(context);
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final value = isStart ? _startAt : _endAt;
|
final value = isStart ? _startAt : _endAt;
|
||||||
final formatted = value == null
|
// 只显日期(选择粒度为日期;时间由开始/结束日的 00:00/23:59 语义承载)。
|
||||||
|
final wallShown = value == null
|
||||||
? null
|
? null
|
||||||
: formatAlbumDateTime(value, _timezone.text, l10n.localeName);
|
: (tryToAlbumZone(value, _timezone.text.trim()) ?? value.toUtc());
|
||||||
|
final formatted = wallShown == null
|
||||||
|
? null
|
||||||
|
: '${wallShown.year}/${wallShown.month}/${wallShown.day}';
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 12),
|
padding: const EdgeInsets.only(bottom: 12),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
|
|||||||
@@ -258,8 +258,8 @@ void main() {
|
|||||||
);
|
);
|
||||||
expect(titleField.controller!.text, '开幕晚宴');
|
expect(titleField.controller!.text, '开幕晚宴');
|
||||||
// 时间回显按相册时区。
|
// 时间回显按相册时区。
|
||||||
expect(find.text('2026/9/18 14:30'), findsOneWidget);
|
expect(find.text('2026/9/18'), findsOneWidget); // UI06:日期粒度显示
|
||||||
expect(find.text('2026/9/19 18:00'), findsOneWidget);
|
expect(find.text('2026/9/19'), findsOneWidget); // UI06:日期粒度显示
|
||||||
// 无任何变更 → 保存禁用(PATCH minProperties 1)。
|
// 无任何变更 → 保存禁用(PATCH minProperties 1)。
|
||||||
expect(
|
expect(
|
||||||
tester.widget<FilledButton>(find.byType(FilledButton)).onPressed,
|
tester.widget<FilledButton>(find.byType(FilledButton)).onPressed,
|
||||||
|
|||||||
Reference in New Issue
Block a user