refactor(@vben/playground): share date range codecs

This commit is contained in:
Dream
2026-07-23 15:39:53 +08:00
parent decbed20bc
commit 43b78da024
5 changed files with 135 additions and 75 deletions

View File

@@ -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<string, unknown> {
createdAt?: [Dayjs, Dayjs];
keyword?: string;
}
const codec = createDateRangeCodec<SearchFormValues>()({
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' });
});
});

View File

@@ -0,0 +1,64 @@
import type { Dayjs } from 'dayjs';
import dayjs from 'dayjs';
type DateRange = [Dayjs, Dayjs];
type DateRangeSubmitValues<
TFormValues extends Record<string, unknown>,
TRangeField extends keyof TFormValues,
TStartField extends string,
TEndField extends string,
> = Omit<TFormValues, TRangeField> &
Record<TEndField | TStartField, string | undefined>;
interface DateRangeCodecOptions<
TFormValues extends Record<string, unknown>,
TRangeField extends keyof TFormValues & string,
TStartField extends string,
TEndField extends string,
> {
endField: TEndField;
rangeField: TRangeField;
startField: TStartField;
}
export function createDateRangeCodec<
TFormValues extends Record<string, unknown>,
>() {
return function createCodec<
TRangeField extends keyof TFormValues & string,
TStartField extends string,
TEndField extends string,
>({
endField,
rangeField,
startField,
}: DateRangeCodecOptions<TFormValues, TRangeField, TStartField, TEndField>) {
type SubmitValues = DateRangeSubmitValues<
TFormValues,
TRangeField,
TStartField,
TEndField
>;
return {
decode(values: Readonly<SubmitValues>): TFormValues {
const { [endField]: end, [startField]: start, ...formValues } = values;
return {
...formValues,
...(start && end ? { [rangeField]: [dayjs(start), dayjs(end)] } : {}),
} as TFormValues;
},
encode(values: Readonly<TFormValues>): 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;
},
};
};
}

View File

@@ -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<string, unknown> {
productName?: string;
}
function encodeSearchFormValues(values: Readonly<SearchFormValues>) {
const { date, ...formValues } = values;
return {
...formValues,
end: date?.[1]?.format('YYYY-MM-DD'),
start: date?.[0]?.format('YYYY-MM-DD'),
};
}
const searchCodec = createDateRangeCodec<SearchFormValues>()({
endField: 'end',
rangeField: 'date',
startField: 'start',
});
type SearchSubmitValues = ReturnType<typeof encodeSearchFormValues>;
function decodeSearchFormValues(
values: Readonly<SearchSubmitValues>,
): SearchFormValues {
const { end, start, ...formValues } = values;
return {
...formValues,
...(start && end ? { date: [dayjs(start), dayjs(end)] } : {}),
};
}
type SearchSubmitValues = ReturnType<typeof searchCodec.encode>;
const formOptions: VbenFormProps<SearchFormValues, SearchSubmitValues> = {
codec: {
decode: decodeSearchFormValues,
encode: encodeSearchFormValues,
},
codec: searchCodec,
// 默认展开
collapsed: false,
schema: [

View File

@@ -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<string, unknown> {
createTime?: [Dayjs, Dayjs];
}
function encodeRoleSearchValues(values: Readonly<RoleSearchFormValues>) {
const { createTime, ...formValues } = values;
return {
...formValues,
endTime: createTime?.[1]?.format('YYYY-MM-DD'),
startTime: createTime?.[0]?.format('YYYY-MM-DD'),
};
}
const roleSearchCodec = createDateRangeCodec<RoleSearchFormValues>()({
endField: 'endTime',
rangeField: 'createTime',
startField: 'startTime',
});
type RoleSearchSubmitValues = ReturnType<typeof encodeRoleSearchValues>;
function decodeRoleSearchValues(
values: Readonly<RoleSearchSubmitValues>,
): RoleSearchFormValues {
const { endTime, startTime, ...formValues } = values;
return {
...formValues,
...(startTime && endTime
? { createTime: [dayjs(startTime), dayjs(endTime)] }
: {}),
};
}
type RoleSearchSubmitValues = ReturnType<typeof roleSearchCodec.encode>;
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,
},

View File

@@ -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<string, unknown> {
createTime?: [Dayjs, Dayjs];
}
function encodeUserSearchValues(values: Readonly<UserSearchFormValues>) {
const { createTime, ...formValues } = values;
return {
...formValues,
endTime: createTime?.[1]?.format('YYYY-MM-DD'),
startTime: createTime?.[0]?.format('YYYY-MM-DD'),
};
}
const userSearchCodec = createDateRangeCodec<UserSearchFormValues>()({
endField: 'endTime',
rangeField: 'createTime',
startField: 'startTime',
});
type UserSearchSubmitValues = ReturnType<typeof encodeUserSearchValues>;
function decodeUserSearchValues(
values: Readonly<UserSearchSubmitValues>,
): UserSearchFormValues {
const { endTime, startTime, ...formValues } = values;
return {
...formValues,
...(startTime && endTime
? { createTime: [dayjs(startTime), dayjs(endTime)] }
: {}),
};
}
type UserSearchSubmitValues = ReturnType<typeof userSearchCodec.encode>;
const deptList = ref<SystemDeptApi.SystemDept[]>([]);
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,
},