mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-10-27 21:10:40 +00:00
fix(form): 日期/时间字段支持 valueFormat 为 X 的秒级时间戳
- 日期类字段 change 时经 normalizeTimestampValue 把选择器输出收成秒级 number,避免 Element Plus 写出秒字符串导致校验失败 - Date/DateTime/Time/Daterange/Timerange 回填 model 时,valueFormat 为 X 直接使用秒级数字,不再拼接展示格式 - typeMatch 对 X 格式要求有限整数,示例值与报错文案区分秒级/毫秒级 - datetimeFormatter 支持 X 格式输出秒级数字 - 补充相应单元测试,并更新 docs/form-config/rules.md 中的校验规则说明
This commit is contained in:
parent
94fae10ae0
commit
f12f78c9e0
@ -29,7 +29,7 @@
|
||||
| `display` / `hidden` | 不校验 |
|
||||
| `number` | `number`(非 NaN) |
|
||||
| `number-range` | 长度为 2 的数字数组 |
|
||||
| `date` / `datetime` / `time` | 按 `valueFormat` 校验;`x` / `timestamp` 期望 `number`,其余按 [Day.js format](https://day.js.org/docs/en/display/format) 严格解析字符串 |
|
||||
| `date` / `datetime` / `time` | 按 `valueFormat` 校验;`x` / `X` / `timestamp` 期望 `number`(`x`、`timestamp` 为毫秒,`X` 为秒)。日期组件会把选择器写出的秒字符串收成 number。其余格式按 [Day.js format](https://day.js.org/docs/en/display/format) 严格解析字符串 |
|
||||
| `daterange` / `timerange` | 无 `names` 时为长度为 2 的数组,元素按 `valueFormat` 校验;有 `names` 时跳过 |
|
||||
| `switch` / `checkbox` | 值必须是解析后的 `activeValue` / `inactiveValue` 之一(显式配置优先;未配置且 `filter === 'number'` 时为 `1`/`0`;否则为 `true`/`false`) |
|
||||
| `select` | 单选:值 ∈ options;`multiple`:数组且每项 ∈ options;`allowCreate` / `remote`:只校验基础形态,不做 options 枚举 |
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
import { TMagicDatePicker } from '@tmagic/design';
|
||||
|
||||
import type { DateConfig, FieldProps } from '@form/schema';
|
||||
import { normalizeTimestampValue } from '@form/utils/form';
|
||||
import { useAddField } from '@form/utils/useAddField';
|
||||
|
||||
defineOptions({
|
||||
@ -24,12 +25,12 @@ defineOptions({
|
||||
const props = defineProps<FieldProps<DateConfig>>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [value: string];
|
||||
change: [value: string | number | null];
|
||||
}>();
|
||||
|
||||
useAddField(props.prop);
|
||||
|
||||
const changeHandler = (v: string) => {
|
||||
emit('change', v);
|
||||
const changeHandler = (v: string | number | null) => {
|
||||
emit('change', normalizeTimestampValue(v, props.config.valueFormat));
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
import { TMagicDatePicker } from '@tmagic/design';
|
||||
|
||||
import type { DateTimeConfig, FieldProps } from '@form/schema';
|
||||
import { normalizeTimestampValue } from '@form/utils/form';
|
||||
import { useAddField } from '@form/utils/useAddField';
|
||||
|
||||
defineOptions({
|
||||
@ -26,12 +27,12 @@ defineOptions({
|
||||
const props = defineProps<FieldProps<DateTimeConfig>>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [value: string];
|
||||
change: [value: string | number | null];
|
||||
}>();
|
||||
|
||||
useAddField(props.prop);
|
||||
|
||||
const changeHandler = (v: string) => {
|
||||
emit('change', v);
|
||||
const changeHandler = (v: string | number | null) => {
|
||||
emit('change', normalizeTimestampValue(v, props.config.valueFormat));
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -23,7 +23,7 @@ import { onUnmounted, ref, watch } from 'vue';
|
||||
import { TMagicDatePicker } from '@tmagic/design';
|
||||
|
||||
import type { ChangeRecord, DaterangeConfig, FieldProps } from '../schema';
|
||||
import { datetimeFormatter } from '../utils/form';
|
||||
import { datetimeFormatter, normalizeTimestampValue } from '../utils/form';
|
||||
import { useAddField } from '../utils/useAddField';
|
||||
|
||||
defineOptions({
|
||||
@ -37,7 +37,14 @@ const emit = defineEmits(['change']);
|
||||
useAddField(props.prop);
|
||||
|
||||
const { names } = props.config;
|
||||
const value = ref<(Date | string | undefined)[] | null>([]);
|
||||
const value = ref<(Date | string | number | undefined)[] | null>([]);
|
||||
const rangeFormat = () => `${props.config.dateFormat || 'YYYY/MM/DD'} ${props.config.timeFormat || 'HH:mm:ss'}`;
|
||||
/** `X` 的选择器值就是秒级数字,展示格式不能拿去代替 valueFormat。 */
|
||||
const toPickerValue = (item: string | number | Date) => {
|
||||
if (!item) return undefined;
|
||||
if (props.config.valueFormat === 'X') return normalizeTimestampValue(item, 'X');
|
||||
return datetimeFormatter(item, '', rangeFormat()) as string;
|
||||
};
|
||||
|
||||
if (props.model !== undefined) {
|
||||
if (names?.length) {
|
||||
@ -48,10 +55,9 @@ if (props.model !== undefined) {
|
||||
value.value = [];
|
||||
}
|
||||
|
||||
const format = `${props.config.dateFormat || 'YYYY/MM/DD'} ${props.config.timeFormat || 'HH:mm:ss'}`;
|
||||
if (!start || !end) value.value = [];
|
||||
if (start !== preStart) value.value[0] = datetimeFormatter(start, '', format) as string;
|
||||
if (end !== preEnd) value.value[1] = datetimeFormatter(end, '', format) as string;
|
||||
if (start !== preStart) value.value[0] = toPickerValue(start);
|
||||
if (end !== preEnd) value.value[1] = toPickerValue(end);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
@ -65,12 +71,7 @@ if (props.model !== undefined) {
|
||||
const unWatch = watch(
|
||||
() => props.model[props.name],
|
||||
(start, preStart) => {
|
||||
const format = `${props.config.dateFormat || 'YYYY/MM/DD'} ${props.config.timeFormat || 'HH:mm:ss'}`;
|
||||
|
||||
if (start !== preStart)
|
||||
value.value = start.map((item: string) =>
|
||||
item ? (datetimeFormatter(item, '', format) as string) : undefined,
|
||||
);
|
||||
if (start !== preStart) value.value = start.map((item: string | number | Date) => toPickerValue(item));
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
@ -82,8 +83,9 @@ if (props.model !== undefined) {
|
||||
}
|
||||
}
|
||||
|
||||
const changeHandler = (v: Date[]) => {
|
||||
const value = v || [];
|
||||
const changeHandler = (v: Array<Date | string | number> | null) => {
|
||||
const normalized = normalizeTimestampValue(v, props.config.valueFormat);
|
||||
const value = normalized || [];
|
||||
|
||||
if (props.name) {
|
||||
emit('change', value);
|
||||
@ -94,10 +96,10 @@ const changeHandler = (v: Date[]) => {
|
||||
if (!props.model) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(v)) {
|
||||
if (Array.isArray(normalized)) {
|
||||
newChangeRecords.push({
|
||||
propPath: props.prop ? `${props.prop}.${item}` : item,
|
||||
value: v[index],
|
||||
value: normalized[index],
|
||||
});
|
||||
} else {
|
||||
newChangeRecords.push({
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
import { TMagicTimePicker } from '@tmagic/design';
|
||||
|
||||
import type { FieldProps, TimeConfig } from '../schema';
|
||||
import { normalizeTimestampValue } from '../utils/form';
|
||||
import { useAddField } from '../utils/useAddField';
|
||||
|
||||
defineOptions({
|
||||
@ -23,12 +24,12 @@ defineOptions({
|
||||
const props = defineProps<FieldProps<TimeConfig>>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [value: string];
|
||||
change: [value: string | number | null];
|
||||
}>();
|
||||
|
||||
useAddField(props.prop);
|
||||
|
||||
const changeHandler = (v: string) => {
|
||||
emit('change', v);
|
||||
const changeHandler = (v: string | number | null) => {
|
||||
emit('change', normalizeTimestampValue(v, props.config.valueFormat));
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -22,7 +22,7 @@ import dayjs from 'dayjs';
|
||||
import { TMagicTimePicker } from '@tmagic/design';
|
||||
|
||||
import type { ChangeRecord, FieldProps, TimerangeConfig } from '../schema';
|
||||
import { datetimeFormatter } from '../utils/form';
|
||||
import { datetimeFormatter, normalizeTimestampValue } from '../utils/form';
|
||||
import { useAddField } from '../utils/useAddField';
|
||||
|
||||
defineOptions({
|
||||
@ -36,12 +36,18 @@ const emit = defineEmits(['change']);
|
||||
useAddField(props.prop);
|
||||
|
||||
const { names } = props.config;
|
||||
const value = ref<(Date | string | undefined)[] | null>([]);
|
||||
const value = ref<(Date | string | number | undefined)[] | null>([]);
|
||||
|
||||
const getFullDatetime = (item: string) => (item ? `${dayjs().format('YYYY/MM/DD')} ${item}` : '');
|
||||
const getFormat = () => `YYYY/MM/DD ${props.config.valueFormat || 'HH:mm:ss'}`;
|
||||
const timeFormatter = (time: string, format: string) =>
|
||||
(datetimeFormatter(getFullDatetime(time), '', format) as string).substring(11);
|
||||
/** `X` 的选择器值就是秒级数字,不能再拼成日期后截取。 */
|
||||
const toPickerValue = (time: string | number) => {
|
||||
if (!time) return undefined;
|
||||
if (props.config.valueFormat === 'X') return normalizeTimestampValue(time, 'X');
|
||||
return timeFormatter(time as string, getFormat());
|
||||
};
|
||||
|
||||
if (props.model !== undefined) {
|
||||
if (names?.length) {
|
||||
@ -52,10 +58,9 @@ if (props.model !== undefined) {
|
||||
value.value = [];
|
||||
}
|
||||
|
||||
const format = getFormat();
|
||||
if (!start || !end) value.value = [];
|
||||
if (start !== preStart) value.value[0] = timeFormatter(start, format);
|
||||
if (end !== preEnd) value.value[1] = timeFormatter(end, format);
|
||||
if (start !== preStart) value.value[0] = toPickerValue(start);
|
||||
if (end !== preEnd) value.value[1] = toPickerValue(end);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
@ -69,7 +74,7 @@ if (props.model !== undefined) {
|
||||
() => props.model[props.name],
|
||||
(v = []) => {
|
||||
if (Array.isArray(v)) {
|
||||
value.value = v.map((item: string) => (item ? timeFormatter(item, getFormat()) : undefined));
|
||||
value.value = v.map((item: string | number) => toPickerValue(item));
|
||||
} else {
|
||||
value.value = [];
|
||||
}
|
||||
@ -84,8 +89,9 @@ if (props.model !== undefined) {
|
||||
}
|
||||
}
|
||||
|
||||
const changeHandler = (v: Date[]) => {
|
||||
const value = v || [];
|
||||
const changeHandler = (v: Array<Date | string | number> | null) => {
|
||||
const normalized = normalizeTimestampValue(v, props.config.valueFormat);
|
||||
const value = normalized || [];
|
||||
|
||||
if (props.name) {
|
||||
emit('change', value);
|
||||
@ -96,10 +102,10 @@ const changeHandler = (v: Date[]) => {
|
||||
if (!props.model) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(v)) {
|
||||
if (Array.isArray(normalized)) {
|
||||
newChangeRecords.push({
|
||||
propPath: props.prop ? `${props.prop}.${item}` : item,
|
||||
value: v[index],
|
||||
value: normalized[index],
|
||||
});
|
||||
} else {
|
||||
newChangeRecords.push({
|
||||
|
||||
@ -42,7 +42,7 @@ import type {
|
||||
} from '../schema';
|
||||
|
||||
import { getConfig } from './config';
|
||||
import { createTypeMatchValidator } from './typeMatch';
|
||||
import { createTypeMatchValidator, TIMESTAMP_VALUE_FORMATS } from './typeMatch';
|
||||
|
||||
export { createFormStateProxy, mergeFormContexts } from './formStateProxy';
|
||||
|
||||
@ -509,14 +509,44 @@ export const initValue = async (
|
||||
return valuesTmp || {};
|
||||
};
|
||||
|
||||
/**
|
||||
* 把值收成 Unix 秒,无法解析时返回 undefined。
|
||||
*
|
||||
* 纯数字(含数字字符串)按已是秒处理:dayjs 会把数字当成毫秒,不能再过一遍。
|
||||
* Date、ISO 和日期字符串则解析后取秒。
|
||||
*/
|
||||
const toUnixSeconds = (v: string | Date | number): number | undefined => {
|
||||
if (typeof v === 'number') {
|
||||
return Number.isFinite(v) ? Math.trunc(v) : undefined;
|
||||
}
|
||||
if (typeof v === 'string' && /^-?\d+$/.test(v.trim())) {
|
||||
return Math.trunc(Number(v));
|
||||
}
|
||||
const parsed = dayjs(v);
|
||||
return parsed.isValid() ? parsed.unix() : undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* `valueFormat: 'X'` 时把选择器值收成秒级 number,其余格式原样返回。
|
||||
* Element Plus 对 `X` 写出的是秒字符串(`x` 才会直接给毫秒数字);空值、无法解析的值原样返回。
|
||||
*/
|
||||
export const normalizeTimestampValue = (value: any, valueFormat?: string): any => {
|
||||
if (valueFormat !== 'X') return value;
|
||||
if (Array.isArray(value)) return value.map((item) => normalizeTimestampValue(item, valueFormat));
|
||||
if (value === null || value === undefined || value === '') return value;
|
||||
return toUnixSeconds(value) ?? value;
|
||||
};
|
||||
|
||||
export const datetimeFormatter = (
|
||||
v: string | Date,
|
||||
v: string | Date | number,
|
||||
defaultValue = '-',
|
||||
format = 'YYYY-MM-DD HH:mm:ss',
|
||||
): string | number => {
|
||||
if (v) {
|
||||
let time: string | number;
|
||||
if (['x', 'timestamp'].includes(format)) {
|
||||
if (format === 'X') {
|
||||
time = toUnixSeconds(v) ?? 'Invalid Date';
|
||||
} else if (TIMESTAMP_VALUE_FORMATS.has(format)) {
|
||||
time = dayjs(Number.isNaN(Number(v)) ? v : Number(v)).valueOf();
|
||||
} else if ((typeof v === 'string' && v.includes('Z')) || v instanceof Date) {
|
||||
// dayjs.extend 内部有防重复机制 (plugin.$i),无需额外判断
|
||||
|
||||
@ -150,7 +150,7 @@ const DATE_LIKE_DEFAULT_VALUE_FORMAT: Record<string, string> = {
|
||||
timerange: 'HH:mm:ss',
|
||||
};
|
||||
|
||||
const TIMESTAMP_VALUE_FORMATS = new Set(['x', 'timestamp']);
|
||||
export const TIMESTAMP_VALUE_FORMATS = new Set(['x', 'X', 'timestamp']);
|
||||
|
||||
/**
|
||||
* 将值格式化为可读的参考示例字符串。
|
||||
@ -281,7 +281,9 @@ const isTimestampValueFormat = (valueFormat: string) => TIMESTAMP_VALUE_FORMATS.
|
||||
|
||||
const isValidDateValueByFormat = (value: any, valueFormat: string): boolean => {
|
||||
if (isTimestampValueFormat(valueFormat)) {
|
||||
return typeof value === 'number' && !Number.isNaN(value);
|
||||
if (typeof value !== 'number') return false;
|
||||
// 秒级时间戳必须是有限整数,毫秒级只排除 NaN
|
||||
return valueFormat === 'X' ? Number.isInteger(value) : !Number.isNaN(value);
|
||||
}
|
||||
if (typeof value !== 'string') {
|
||||
return false;
|
||||
@ -293,9 +295,11 @@ const isValidDateValueByFormat = (value: any, valueFormat: string): boolean => {
|
||||
/**
|
||||
* 生成单个日期类字段的参考示例值:时间戳格式给出当前时间戳,其余按 valueFormat 格式化当前时间。
|
||||
*/
|
||||
const timestampExample = (valueFormat: string) => (valueFormat === 'X' ? Math.floor(Date.now() / 1000) : Date.now());
|
||||
|
||||
const dateSuggestion = (valueFormat: string): string =>
|
||||
isTimestampValueFormat(valueFormat)
|
||||
? `请参考以下示例值:${Date.now()}`
|
||||
? `请参考以下示例值:${timestampExample(valueFormat)}`
|
||||
: `请参考以下示例值:"${dayjs().format(valueFormat)}"`;
|
||||
|
||||
/**
|
||||
@ -303,16 +307,19 @@ const dateSuggestion = (valueFormat: string): string =>
|
||||
*/
|
||||
const dateRangeSuggestion = (valueFormat: string): string => {
|
||||
if (isTimestampValueFormat(valueFormat)) {
|
||||
return `请参考以下示例值:[${Date.now()}, ${Date.now()}]`;
|
||||
const example = timestampExample(valueFormat);
|
||||
return `请参考以下示例值:[${example}, ${example}]`;
|
||||
}
|
||||
const example = dayjs().format(valueFormat);
|
||||
return `请参考以下示例值:["${example}", "${example}"]`;
|
||||
};
|
||||
|
||||
const timestampTypeLabel = (valueFormat: string) => (valueFormat === 'X' ? '秒级时间戳数字' : '毫秒级时间戳数字');
|
||||
|
||||
const dateValueFormatErrorMessage = (message: string | undefined, valueFormat: string) =>
|
||||
defaultMessage(
|
||||
message,
|
||||
isTimestampValueFormat(valueFormat) ? '值类型应为时间戳数字' : `值格式应为 ${valueFormat}`,
|
||||
isTimestampValueFormat(valueFormat) ? `值类型应为${timestampTypeLabel(valueFormat)}` : `值格式应为 ${valueFormat}`,
|
||||
dateSuggestion(valueFormat),
|
||||
);
|
||||
|
||||
@ -748,7 +755,7 @@ const validateBuiltinTypeMatch = (
|
||||
return defaultMessage(
|
||||
message,
|
||||
isTimestampValueFormat(valueFormat)
|
||||
? `${formatErrorValue(value)} 类型应为长度为 2 的时间戳数字数组`
|
||||
? `${formatErrorValue(value)} 类型应为长度为 2 的${timestampTypeLabel(valueFormat)}数组`
|
||||
: `${formatErrorValue(value)} 格式应为长度为 2 的 ${valueFormat} 数组`,
|
||||
dateRangeSuggestion(valueFormat),
|
||||
);
|
||||
|
||||
@ -79,6 +79,20 @@ describe('Date', () => {
|
||||
expect(value.date).toMatch('2021-07-28');
|
||||
});
|
||||
|
||||
test('valueFormat 为 X 时收成秒级数字', async () => {
|
||||
const source = '2021-07-17T07:37:00.000Z';
|
||||
const wrapper = getWrapper([{ text: 'date', type: 'date', name: 'date', valueFormat: 'X' }], { date: source });
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect((wrapper.vm as any).values.date).toBe(Math.floor(new Date(source).getTime() / 1000));
|
||||
|
||||
wrapper.findComponent({ name: 'TMDatePicker' }).vm.$emit('update:modelValue', '1626507420');
|
||||
await nextTick();
|
||||
|
||||
expect((wrapper.vm as any).values.date).toBe(1626507420);
|
||||
});
|
||||
|
||||
test('清空', async () => {
|
||||
const wrapper = getWrapper();
|
||||
|
||||
|
||||
@ -117,6 +117,22 @@ describe('DateTime', () => {
|
||||
expect(value.datetime).toMatch('2021-07-28 00:00:00');
|
||||
});
|
||||
|
||||
test('valueFormat 为 X 时收成秒级数字', async () => {
|
||||
const source = '2021-07-17T07:37:00.000Z';
|
||||
const wrapper = getWrapper([{ text: 'dateTime', type: 'datetime', name: 'datetime', valueFormat: 'X' }], {
|
||||
datetime: source,
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect((wrapper.vm as any).values.datetime).toBe(Math.floor(new Date(source).getTime() / 1000));
|
||||
|
||||
wrapper.findComponent({ name: 'TMDatePicker' }).vm.$emit('update:modelValue', '1626507420');
|
||||
await nextTick();
|
||||
|
||||
expect((wrapper.vm as any).values.datetime).toBe(1626507420);
|
||||
});
|
||||
|
||||
test('清空', async () => {
|
||||
const wrapper = getWrapper();
|
||||
|
||||
|
||||
@ -66,4 +66,32 @@ describe('Daterange', () => {
|
||||
const daterange = wrapper.findComponent(MDaterange);
|
||||
expect(daterange.exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('valueFormat 为 X 时区间收成秒数字', async () => {
|
||||
const wrapper = getWrapper([{ type: 'daterange', text: 'daterange', name: 'range', valueFormat: 'X' }], {
|
||||
range: [1626507420, 1626593820],
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
|
||||
wrapper.findComponent({ name: 'TMDatePicker' }).vm.$emit('update:modelValue', ['1626507420', '1626593820']);
|
||||
await nextTick();
|
||||
|
||||
expect((wrapper.vm as any).values.range).toEqual([1626507420, 1626593820]);
|
||||
});
|
||||
|
||||
test('valueFormat 为 X 且 names 时分别写入秒数字', async () => {
|
||||
const wrapper = getWrapper([{ type: 'daterange', text: 'daterange', names: ['start', 'end'], valueFormat: 'X' }], {
|
||||
start: 1626507420,
|
||||
end: 1626593820,
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
|
||||
wrapper.findComponent({ name: 'TMDatePicker' }).vm.$emit('update:modelValue', ['1626507420', '1626593820']);
|
||||
await nextTick();
|
||||
|
||||
expect((wrapper.vm as any).values.start).toBe(1626507420);
|
||||
expect((wrapper.vm as any).values.end).toBe(1626593820);
|
||||
});
|
||||
});
|
||||
|
||||
@ -60,4 +60,15 @@ describe('Time', () => {
|
||||
const value = await (wrapper.vm as any).submitForm();
|
||||
expect(value.time).toMatch('12:00:00');
|
||||
});
|
||||
|
||||
test('valueFormat 为 X 时把秒字符串收成数字', async () => {
|
||||
const wrapper = getWrapper([{ type: 'time', text: 'time', name: 'time', valueFormat: 'X' }], { time: 1626507420 });
|
||||
|
||||
await nextTick();
|
||||
|
||||
wrapper.findComponent({ name: 'TMTimePicker' }).vm.$emit('update:modelValue', '1626507421');
|
||||
await nextTick();
|
||||
|
||||
expect((wrapper.vm as any).values.time).toBe(1626507421);
|
||||
});
|
||||
});
|
||||
|
||||
@ -31,4 +31,32 @@ describe('Timerange', () => {
|
||||
await nextTick();
|
||||
expect(wrapper.findComponent(MTimerange).exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('valueFormat 为 X 时区间收成秒数字', async () => {
|
||||
const wrapper = mountForm([{ name: 'tr', type: 'timerange', text: 'tr', valueFormat: 'X' }], {
|
||||
tr: [1626507420, 1626593820],
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
|
||||
wrapper.findComponent({ name: 'TMTimePicker' }).vm.$emit('change', ['1626507420', '1626593820']);
|
||||
await nextTick();
|
||||
|
||||
expect((wrapper.vm as any).values.tr).toEqual([1626507420, 1626593820]);
|
||||
});
|
||||
|
||||
test('valueFormat 为 X 且 names 时分别写入秒数字', async () => {
|
||||
const wrapper = mountForm([{ type: 'timerange', text: 'tr', names: ['start', 'end'], valueFormat: 'X' }], {
|
||||
start: 1626507420,
|
||||
end: 1626593820,
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
|
||||
wrapper.findComponent({ name: 'TMTimePicker' }).vm.$emit('change', ['1626507420', '1626593820']);
|
||||
await nextTick();
|
||||
|
||||
expect((wrapper.vm as any).values.start).toBe(1626507420);
|
||||
expect((wrapper.vm as any).values.end).toBe(1626593820);
|
||||
});
|
||||
});
|
||||
|
||||
@ -29,6 +29,7 @@ import {
|
||||
getDataByPage,
|
||||
getRules,
|
||||
initValue,
|
||||
normalizeTimestampValue,
|
||||
sortArray,
|
||||
sortChange,
|
||||
} from '@form/utils/form';
|
||||
@ -681,6 +682,40 @@ describe('datetimeFormatter', () => {
|
||||
test('自定义format只显示时间', () => {
|
||||
expect(datetimeFormatter(date, defaultValue, 'HH:mm:ss')).toBe('15:37:00');
|
||||
});
|
||||
|
||||
test('format是X时日期转成秒级时间戳', () => {
|
||||
const seconds = Math.floor(date.getTime() / 1000);
|
||||
expect(datetimeFormatter(date.toISOString(), defaultValue, 'X')).toBe(seconds);
|
||||
expect(datetimeFormatter(date, defaultValue, 'X')).toBe(seconds);
|
||||
});
|
||||
|
||||
test('format是X时已是秒的数字原样保留', () => {
|
||||
const seconds = Math.floor(date.getTime() / 1000);
|
||||
expect(datetimeFormatter(String(seconds), defaultValue, 'X')).toBe(seconds);
|
||||
expect(datetimeFormatter(seconds, defaultValue, 'X')).toBe(seconds);
|
||||
});
|
||||
|
||||
test('format是X时非法值回退默认值', () => {
|
||||
expect(datetimeFormatter('abc', defaultValue, 'X')).toBe(defaultValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizeTimestampValue', () => {
|
||||
test('非 X 格式原样返回', () => {
|
||||
expect(normalizeTimestampValue('1626507420', 'x')).toBe('1626507420');
|
||||
expect(normalizeTimestampValue('2021/07/17', undefined)).toBe('2021/07/17');
|
||||
});
|
||||
|
||||
test('X 格式:空值原样返回,秒字符串和日期收成秒数字', () => {
|
||||
expect(normalizeTimestampValue(null, 'X')).toBeNull();
|
||||
expect(normalizeTimestampValue('', 'X')).toBe('');
|
||||
expect(normalizeTimestampValue('1626507420', 'X')).toBe(1626507420);
|
||||
expect(normalizeTimestampValue(1626507420.9, 'X')).toBe(1626507420);
|
||||
expect(normalizeTimestampValue(['1626507420', '1626593820'], 'X')).toEqual([1626507420, 1626593820]);
|
||||
expect(normalizeTimestampValue('2021-07-17T07:37:00.000Z', 'X')).toBe(1626507420);
|
||||
expect(normalizeTimestampValue('not-a-date', 'X')).toBe('not-a-date');
|
||||
expect(normalizeTimestampValue(Infinity, 'X')).toBe(Infinity);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sortArray', () => {
|
||||
|
||||
@ -153,13 +153,26 @@ describe('validateTypeMatch', () => {
|
||||
validateTypeMatch('25/01/2019', mForm, propsOf({ type: 'date', valueFormat: 'DD/MM/YYYY' })),
|
||||
).toBeUndefined();
|
||||
|
||||
// timestamp / x → number
|
||||
// timestamp / x → 毫秒 number;X → 秒 number(日期选择器写出的秒字符串不合法)
|
||||
expect(
|
||||
validateTypeMatch(1710000000000, mForm, propsOf({ type: 'date', valueFormat: 'timestamp' })),
|
||||
).toBeUndefined();
|
||||
expect(validateTypeMatch(1710000000000, mForm, propsOf({ type: 'datetime', valueFormat: 'x' }))).toBeUndefined();
|
||||
expect(validateTypeMatch('2020-01-01', mForm, propsOf({ type: 'date', valueFormat: 'x' }))).toMatch(
|
||||
/^值类型应为时间戳数字\n\n请参考以下示例值:\d+$/,
|
||||
/^值类型应为毫秒级时间戳数字\n\n请参考以下示例值:\d{13}$/,
|
||||
);
|
||||
expect(validateTypeMatch(1626507420, mForm, propsOf({ type: 'date', valueFormat: 'X' }))).toBeUndefined();
|
||||
expect(validateTypeMatch(1626507420.5, mForm, propsOf({ type: 'date', valueFormat: 'X' }))).toMatch(
|
||||
/^值类型应为秒级时间戳数字/,
|
||||
);
|
||||
expect(validateTypeMatch(Infinity, mForm, propsOf({ type: 'date', valueFormat: 'X' }))).toMatch(
|
||||
/^值类型应为秒级时间戳数字/,
|
||||
);
|
||||
expect(validateTypeMatch('1626507420', mForm, propsOf({ type: 'datetime', valueFormat: 'X' }))).toMatch(
|
||||
/^值类型应为秒级时间戳数字\n\n请参考以下示例值:\d{10}$/,
|
||||
);
|
||||
expect(validateTypeMatch('2020-01-01', mForm, propsOf({ type: 'time', valueFormat: 'X' }))).toMatch(
|
||||
/^值类型应为秒级时间戳数字\n\n请参考以下示例值:\d{10}$/,
|
||||
);
|
||||
});
|
||||
|
||||
@ -553,6 +566,14 @@ describe('validateTypeMatch', () => {
|
||||
/^\["a"\] 格式应为长度为 2 的 YYYY\/MM\/DD HH:mm:ss 数组\n\n请参考以下示例值:\["\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}", "\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}"\]$/,
|
||||
);
|
||||
expect(validateTypeMatch([1, 2], mForm, propsOf({ type: 'daterange', valueFormat: 'timestamp' }))).toBeUndefined();
|
||||
expect(
|
||||
validateTypeMatch([1626507420, 1626593820], mForm, propsOf({ type: 'daterange', valueFormat: 'X' })),
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
validateTypeMatch(['2020-01-01', '2020-01-02'], mForm, propsOf({ type: 'daterange', valueFormat: 'X' })),
|
||||
).toMatch(
|
||||
/^\["2020-01-01","2020-01-02"\] 类型应为长度为 2 的秒级时间戳数字数组\n\n请参考以下示例值:\[\d{10}, \d{10}\]$/,
|
||||
);
|
||||
expect(validateTypeMatch('x', mForm, propsOf({ type: 'daterange', names: ['a', 'b'] }))).toBeUndefined();
|
||||
|
||||
expect(validateTypeMatch([{ id: 1 }], mForm, propsOf({ type: 'table' }))).toBeUndefined();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user