From 43b78da024ad2a31b1e5a676da54b516dd4cee33 Mon Sep 17 00:00:00 2001 From: Dream <1012377328@qq.com> Date: Thu, 23 Jul 2026 15:39:53 +0800 Subject: [PATCH] refactor(@vben/playground): share date range codecs --- .../__tests__/unit/date-range-codec.test.ts | 47 ++++++++++++++ playground/src/utils/date-range-codec.ts | 64 +++++++++++++++++++ .../src/views/examples/vxe-table/form.vue | 31 +++------ playground/src/views/system/role/list.vue | 34 +++------- playground/src/views/system/user/list.vue | 34 +++------- 5 files changed, 135 insertions(+), 75 deletions(-) create mode 100644 playground/__tests__/unit/date-range-codec.test.ts create mode 100644 playground/src/utils/date-range-codec.ts diff --git a/playground/__tests__/unit/date-range-codec.test.ts b/playground/__tests__/unit/date-range-codec.test.ts new file mode 100644 index 00000000..589ffe30 --- /dev/null +++ b/playground/__tests__/unit/date-range-codec.test.ts @@ -0,0 +1,47 @@ +import type { Dayjs } from 'dayjs'; + +import dayjs from 'dayjs'; +import { describe, expect, it } from 'vitest'; + +import { createDateRangeCodec } from '../../src/utils/date-range-codec'; + +interface SearchFormValues extends Record { + createdAt?: [Dayjs, Dayjs]; + keyword?: string; +} + +const codec = createDateRangeCodec()({ + endField: 'finishedAt', + rangeField: 'createdAt', + startField: 'startedAt', +}); + +describe('date range codec', () => { + it('encodes and decodes configurable date range fields', () => { + const submitValues = codec.encode({ + createdAt: [dayjs('2026-07-01'), dayjs('2026-07-23')], + keyword: 'admin', + }); + + expect(submitValues).toEqual({ + finishedAt: '2026-07-23', + keyword: 'admin', + startedAt: '2026-07-01', + }); + const formValues = codec.decode(submitValues); + expect(formValues.keyword).toBe('admin'); + expect( + formValues.createdAt?.map((value) => value.format('YYYY-MM-DD')), + ).toEqual(['2026-07-01', '2026-07-23']); + }); + + it('does not reconstruct a range with a missing bound', () => { + expect( + codec.decode({ + finishedAt: undefined, + keyword: 'admin', + startedAt: '2026-07-01', + }), + ).toEqual({ keyword: 'admin' }); + }); +}); diff --git a/playground/src/utils/date-range-codec.ts b/playground/src/utils/date-range-codec.ts new file mode 100644 index 00000000..00243e7f --- /dev/null +++ b/playground/src/utils/date-range-codec.ts @@ -0,0 +1,64 @@ +import type { Dayjs } from 'dayjs'; + +import dayjs from 'dayjs'; + +type DateRange = [Dayjs, Dayjs]; + +type DateRangeSubmitValues< + TFormValues extends Record, + TRangeField extends keyof TFormValues, + TStartField extends string, + TEndField extends string, +> = Omit & + Record; + +interface DateRangeCodecOptions< + TFormValues extends Record, + TRangeField extends keyof TFormValues & string, + TStartField extends string, + TEndField extends string, +> { + endField: TEndField; + rangeField: TRangeField; + startField: TStartField; +} + +export function createDateRangeCodec< + TFormValues extends Record, +>() { + return function createCodec< + TRangeField extends keyof TFormValues & string, + TStartField extends string, + TEndField extends string, + >({ + endField, + rangeField, + startField, + }: DateRangeCodecOptions) { + type SubmitValues = DateRangeSubmitValues< + TFormValues, + TRangeField, + TStartField, + TEndField + >; + + return { + decode(values: Readonly): TFormValues { + const { [endField]: end, [startField]: start, ...formValues } = values; + return { + ...formValues, + ...(start && end ? { [rangeField]: [dayjs(start), dayjs(end)] } : {}), + } as TFormValues; + }, + encode(values: Readonly): SubmitValues { + const { [rangeField]: value, ...formValues } = values; + const range = value as DateRange | undefined; + return { + ...formValues, + [endField]: range?.[1]?.format('YYYY-MM-DD'), + [startField]: range?.[0]?.format('YYYY-MM-DD'), + } as SubmitValues; + }, + }; + }; +} diff --git a/playground/src/views/examples/vxe-table/form.vue b/playground/src/views/examples/vxe-table/form.vue index ed0645ba..f4d83c55 100644 --- a/playground/src/views/examples/vxe-table/form.vue +++ b/playground/src/views/examples/vxe-table/form.vue @@ -11,6 +11,7 @@ import dayjs from 'dayjs'; import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { getExampleTableApi } from '#/api'; +import { createDateRangeCodec } from '#/utils/date-range-codec'; interface RowType { category: string; @@ -29,32 +30,16 @@ interface SearchFormValues extends Record { productName?: string; } -function encodeSearchFormValues(values: Readonly) { - const { date, ...formValues } = values; - return { - ...formValues, - end: date?.[1]?.format('YYYY-MM-DD'), - start: date?.[0]?.format('YYYY-MM-DD'), - }; -} +const searchCodec = createDateRangeCodec()({ + endField: 'end', + rangeField: 'date', + startField: 'start', +}); -type SearchSubmitValues = ReturnType; - -function decodeSearchFormValues( - values: Readonly, -): SearchFormValues { - const { end, start, ...formValues } = values; - return { - ...formValues, - ...(start && end ? { date: [dayjs(start), dayjs(end)] } : {}), - }; -} +type SearchSubmitValues = ReturnType; const formOptions: VbenFormProps = { - codec: { - decode: decodeSearchFormValues, - encode: encodeSearchFormValues, - }, + codec: searchCodec, // 默认展开 collapsed: false, schema: [ diff --git a/playground/src/views/system/role/list.vue b/playground/src/views/system/role/list.vue index 3802753b..321eef7d 100644 --- a/playground/src/views/system/role/list.vue +++ b/playground/src/views/system/role/list.vue @@ -13,11 +13,11 @@ import { Page, useVbenDrawer } from '@vben/common-ui'; import { Plus } from '@vben/icons'; import { Button, message, Modal } from 'antdv-next'; -import dayjs from 'dayjs'; import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { deleteRole, getRoleList, updateRole } from '#/api'; import { $t } from '#/locales'; +import { createDateRangeCodec } from '#/utils/date-range-codec'; import { useColumns, useGridFormSchema } from './data'; import Form from './modules/form.vue'; @@ -26,28 +26,13 @@ interface RoleSearchFormValues extends Record { createTime?: [Dayjs, Dayjs]; } -function encodeRoleSearchValues(values: Readonly) { - const { createTime, ...formValues } = values; - return { - ...formValues, - endTime: createTime?.[1]?.format('YYYY-MM-DD'), - startTime: createTime?.[0]?.format('YYYY-MM-DD'), - }; -} +const roleSearchCodec = createDateRangeCodec()({ + endField: 'endTime', + rangeField: 'createTime', + startField: 'startTime', +}); -type RoleSearchSubmitValues = ReturnType; - -function decodeRoleSearchValues( - values: Readonly, -): RoleSearchFormValues { - const { endTime, startTime, ...formValues } = values; - return { - ...formValues, - ...(startTime && endTime - ? { createTime: [dayjs(startTime), dayjs(endTime)] } - : {}), - }; -} +type RoleSearchSubmitValues = ReturnType; const [FormDrawer, formDrawerApi] = useVbenDrawer({ connectedComponent: Form, @@ -56,10 +41,7 @@ const [FormDrawer, formDrawerApi] = useVbenDrawer({ const [Grid, gridApi] = useVbenVxeGrid({ formOptions: { - codec: { - decode: decodeRoleSearchValues, - encode: encodeRoleSearchValues, - }, + codec: roleSearchCodec, schema: useGridFormSchema(), submitOnChange: true, }, diff --git a/playground/src/views/system/user/list.vue b/playground/src/views/system/user/list.vue index a73b4bc0..d4b65dea 100644 --- a/playground/src/views/system/user/list.vue +++ b/playground/src/views/system/user/list.vue @@ -12,11 +12,11 @@ import { Page, Tree, useVbenDrawer } from '@vben/common-ui'; import { Plus } from '@vben/icons'; import { Button, Card, InputSearch, message, Modal } from 'antdv-next'; -import dayjs from 'dayjs'; import { useVbenVxeGrid, VbenTableAction } from '#/adapter/vxe-table'; import { deleteUser, getDeptList, getUserList, updateUser } from '#/api'; import { $t } from '#/locales'; +import { createDateRangeCodec } from '#/utils/date-range-codec'; import { useColumns, useGridFormSchema } from './data'; import Detail from './modules/detail.vue'; @@ -26,28 +26,13 @@ interface UserSearchFormValues extends Record { createTime?: [Dayjs, Dayjs]; } -function encodeUserSearchValues(values: Readonly) { - const { createTime, ...formValues } = values; - return { - ...formValues, - endTime: createTime?.[1]?.format('YYYY-MM-DD'), - startTime: createTime?.[0]?.format('YYYY-MM-DD'), - }; -} +const userSearchCodec = createDateRangeCodec()({ + endField: 'endTime', + rangeField: 'createTime', + startField: 'startTime', +}); -type UserSearchSubmitValues = ReturnType; - -function decodeUserSearchValues( - values: Readonly, -): UserSearchFormValues { - const { endTime, startTime, ...formValues } = values; - return { - ...formValues, - ...(startTime && endTime - ? { createTime: [dayjs(startTime), dayjs(endTime)] } - : {}), - }; -} +type UserSearchSubmitValues = ReturnType; const deptList = ref([]); const inputSearchValue = ref(''); @@ -65,10 +50,7 @@ const [DetailDrawer, detailDrawerApi] = useVbenDrawer({ const [Grid, gridApi] = useVbenVxeGrid({ formOptions: { - codec: { - decode: decodeUserSearchValues, - encode: encodeUserSearchValues, - }, + codec: userSearchCodec, schema: useGridFormSchema(), submitOnChange: true, },