refactor(@vben/playground): share date range codecs
This commit is contained in:
47
playground/__tests__/unit/date-range-codec.test.ts
Normal file
47
playground/__tests__/unit/date-range-codec.test.ts
Normal 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' });
|
||||
});
|
||||
});
|
||||
64
playground/src/utils/date-range-codec.ts
Normal file
64
playground/src/utils/date-range-codec.ts
Normal 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;
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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: [
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user