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 { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { getExampleTableApi } from '#/api';
|
import { getExampleTableApi } from '#/api';
|
||||||
|
import { createDateRangeCodec } from '#/utils/date-range-codec';
|
||||||
|
|
||||||
interface RowType {
|
interface RowType {
|
||||||
category: string;
|
category: string;
|
||||||
@@ -29,32 +30,16 @@ interface SearchFormValues extends Record<string, unknown> {
|
|||||||
productName?: string;
|
productName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function encodeSearchFormValues(values: Readonly<SearchFormValues>) {
|
const searchCodec = createDateRangeCodec<SearchFormValues>()({
|
||||||
const { date, ...formValues } = values;
|
endField: 'end',
|
||||||
return {
|
rangeField: 'date',
|
||||||
...formValues,
|
startField: 'start',
|
||||||
end: date?.[1]?.format('YYYY-MM-DD'),
|
});
|
||||||
start: date?.[0]?.format('YYYY-MM-DD'),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
type SearchSubmitValues = ReturnType<typeof encodeSearchFormValues>;
|
type SearchSubmitValues = ReturnType<typeof searchCodec.encode>;
|
||||||
|
|
||||||
function decodeSearchFormValues(
|
|
||||||
values: Readonly<SearchSubmitValues>,
|
|
||||||
): SearchFormValues {
|
|
||||||
const { end, start, ...formValues } = values;
|
|
||||||
return {
|
|
||||||
...formValues,
|
|
||||||
...(start && end ? { date: [dayjs(start), dayjs(end)] } : {}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const formOptions: VbenFormProps<SearchFormValues, SearchSubmitValues> = {
|
const formOptions: VbenFormProps<SearchFormValues, SearchSubmitValues> = {
|
||||||
codec: {
|
codec: searchCodec,
|
||||||
decode: decodeSearchFormValues,
|
|
||||||
encode: encodeSearchFormValues,
|
|
||||||
},
|
|
||||||
// 默认展开
|
// 默认展开
|
||||||
collapsed: false,
|
collapsed: false,
|
||||||
schema: [
|
schema: [
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ import { Page, useVbenDrawer } from '@vben/common-ui';
|
|||||||
import { Plus } from '@vben/icons';
|
import { Plus } from '@vben/icons';
|
||||||
|
|
||||||
import { Button, message, Modal } from 'antdv-next';
|
import { Button, message, Modal } from 'antdv-next';
|
||||||
import dayjs from 'dayjs';
|
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { deleteRole, getRoleList, updateRole } from '#/api';
|
import { deleteRole, getRoleList, updateRole } from '#/api';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
import { createDateRangeCodec } from '#/utils/date-range-codec';
|
||||||
|
|
||||||
import { useColumns, useGridFormSchema } from './data';
|
import { useColumns, useGridFormSchema } from './data';
|
||||||
import Form from './modules/form.vue';
|
import Form from './modules/form.vue';
|
||||||
@@ -26,28 +26,13 @@ interface RoleSearchFormValues extends Record<string, unknown> {
|
|||||||
createTime?: [Dayjs, Dayjs];
|
createTime?: [Dayjs, Dayjs];
|
||||||
}
|
}
|
||||||
|
|
||||||
function encodeRoleSearchValues(values: Readonly<RoleSearchFormValues>) {
|
const roleSearchCodec = createDateRangeCodec<RoleSearchFormValues>()({
|
||||||
const { createTime, ...formValues } = values;
|
endField: 'endTime',
|
||||||
return {
|
rangeField: 'createTime',
|
||||||
...formValues,
|
startField: 'startTime',
|
||||||
endTime: createTime?.[1]?.format('YYYY-MM-DD'),
|
});
|
||||||
startTime: createTime?.[0]?.format('YYYY-MM-DD'),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
type RoleSearchSubmitValues = ReturnType<typeof encodeRoleSearchValues>;
|
type RoleSearchSubmitValues = ReturnType<typeof roleSearchCodec.encode>;
|
||||||
|
|
||||||
function decodeRoleSearchValues(
|
|
||||||
values: Readonly<RoleSearchSubmitValues>,
|
|
||||||
): RoleSearchFormValues {
|
|
||||||
const { endTime, startTime, ...formValues } = values;
|
|
||||||
return {
|
|
||||||
...formValues,
|
|
||||||
...(startTime && endTime
|
|
||||||
? { createTime: [dayjs(startTime), dayjs(endTime)] }
|
|
||||||
: {}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const [FormDrawer, formDrawerApi] = useVbenDrawer({
|
const [FormDrawer, formDrawerApi] = useVbenDrawer({
|
||||||
connectedComponent: Form,
|
connectedComponent: Form,
|
||||||
@@ -56,10 +41,7 @@ const [FormDrawer, formDrawerApi] = useVbenDrawer({
|
|||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
codec: {
|
codec: roleSearchCodec,
|
||||||
decode: decodeRoleSearchValues,
|
|
||||||
encode: encodeRoleSearchValues,
|
|
||||||
},
|
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
submitOnChange: true,
|
submitOnChange: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ import { Page, Tree, useVbenDrawer } from '@vben/common-ui';
|
|||||||
import { Plus } from '@vben/icons';
|
import { Plus } from '@vben/icons';
|
||||||
|
|
||||||
import { Button, Card, InputSearch, message, Modal } from 'antdv-next';
|
import { Button, Card, InputSearch, message, Modal } from 'antdv-next';
|
||||||
import dayjs from 'dayjs';
|
|
||||||
|
|
||||||
import { useVbenVxeGrid, VbenTableAction } from '#/adapter/vxe-table';
|
import { useVbenVxeGrid, VbenTableAction } from '#/adapter/vxe-table';
|
||||||
import { deleteUser, getDeptList, getUserList, updateUser } from '#/api';
|
import { deleteUser, getDeptList, getUserList, updateUser } from '#/api';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
import { createDateRangeCodec } from '#/utils/date-range-codec';
|
||||||
|
|
||||||
import { useColumns, useGridFormSchema } from './data';
|
import { useColumns, useGridFormSchema } from './data';
|
||||||
import Detail from './modules/detail.vue';
|
import Detail from './modules/detail.vue';
|
||||||
@@ -26,28 +26,13 @@ interface UserSearchFormValues extends Record<string, unknown> {
|
|||||||
createTime?: [Dayjs, Dayjs];
|
createTime?: [Dayjs, Dayjs];
|
||||||
}
|
}
|
||||||
|
|
||||||
function encodeUserSearchValues(values: Readonly<UserSearchFormValues>) {
|
const userSearchCodec = createDateRangeCodec<UserSearchFormValues>()({
|
||||||
const { createTime, ...formValues } = values;
|
endField: 'endTime',
|
||||||
return {
|
rangeField: 'createTime',
|
||||||
...formValues,
|
startField: 'startTime',
|
||||||
endTime: createTime?.[1]?.format('YYYY-MM-DD'),
|
});
|
||||||
startTime: createTime?.[0]?.format('YYYY-MM-DD'),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserSearchSubmitValues = ReturnType<typeof encodeUserSearchValues>;
|
type UserSearchSubmitValues = ReturnType<typeof userSearchCodec.encode>;
|
||||||
|
|
||||||
function decodeUserSearchValues(
|
|
||||||
values: Readonly<UserSearchSubmitValues>,
|
|
||||||
): UserSearchFormValues {
|
|
||||||
const { endTime, startTime, ...formValues } = values;
|
|
||||||
return {
|
|
||||||
...formValues,
|
|
||||||
...(startTime && endTime
|
|
||||||
? { createTime: [dayjs(startTime), dayjs(endTime)] }
|
|
||||||
: {}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const deptList = ref<SystemDeptApi.SystemDept[]>([]);
|
const deptList = ref<SystemDeptApi.SystemDept[]>([]);
|
||||||
const inputSearchValue = ref('');
|
const inputSearchValue = ref('');
|
||||||
@@ -65,10 +50,7 @@ const [DetailDrawer, detailDrawerApi] = useVbenDrawer({
|
|||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
codec: {
|
codec: userSearchCodec,
|
||||||
decode: decodeUserSearchValues,
|
|
||||||
encode: encodeUserSearchValues,
|
|
||||||
},
|
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
submitOnChange: true,
|
submitOnChange: true,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user