feat: add uuid & nanoid & unitTimestamp interface (#3684)

* refactor: select & radio compoent supports multiple type of value

* fix: ridio test

* feat: uuid & nanoid & snowflake interface

* refactor: delete snowflake

* feat: nanoid field type (#3685)

* refactor: add child in inheritance of tree collection (#3676)

* refactor: add child in inheritance of tree collection

* refactor: add child in inheritance of tree collection

* style: style improve

* feat: nanoid field

* chore: nanoid field type map

---------

Co-authored-by: katherinehhh <shunai.tang@hand-china.com>

* chore: nanoid options

* refactor: unixTimestamp

* fix: test

* refactor: unixTimestamp

* refactor: unixTimestamp

* refactor: locale imrove

* refactor: local improve

* refactor: nanoIDInput

* refactor: nanoIDInput

* refactor: nanoIDInput

* refactor: unixTimestamp

* refactor: nanoIDInput

* fix: test

---------

Co-authored-by: ChengLei Shao <chareice@live.com>
This commit is contained in:
katherinehhh 2024-03-13 12:07:45 +08:00 committed by GitHub
parent 4015cf1c0d
commit c7cfeec6a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 394 additions and 17 deletions

View File

@ -40,6 +40,9 @@ import {
UpdatedByFieldInterface,
UrlFieldInterface,
SortFieldInterface,
UUIDFieldInterface,
NanoidFieldInterface,
UnixTimestampFieldInterface,
} from './interfaces';
import {
GeneralCollectionTemplate,
@ -155,6 +158,9 @@ export class CollectionPlugin extends Plugin {
UpdatedByFieldInterface,
UrlFieldInterface,
SortFieldInterface,
UUIDFieldInterface,
NanoidFieldInterface,
UnixTimestampFieldInterface,
]);
}

View File

@ -34,3 +34,6 @@ export * from './updatedAt';
export * from './updatedBy';
export * from './url';
export * from './sort';
export * from './uuid';
export * from './nanoid';
export * from './unixTimestamp';

View File

@ -0,0 +1,56 @@
import { CollectionFieldInterface } from '../../data-source/collection-field-interface/CollectionFieldInterface';
import { operators } from './properties';
export class NanoidFieldInterface extends CollectionFieldInterface {
name = 'nanoid';
type = 'object';
group = 'advanced';
order = 0;
title = '{{t("Nano ID")}}';
hidden = false;
sortable = true;
default = {
type: 'nanoid',
uiSchema: {
type: 'string',
'x-component': 'NanoIDInput',
},
};
availableTypes = ['string', 'uid'];
properties = {
'uiSchema.title': {
type: 'string',
title: '{{t("Field display name")}}',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
name: {
type: 'string',
title: '{{t("Field name")}}',
required: true,
'x-disabled': true,
'x-decorator': 'FormItem',
'x-component': 'Input',
description:
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.')}}",
},
customAlphabet: {
type: 'string',
title: '{{t("Alphabet")}}',
default: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
size: {
type: 'number',
title: '{{t("Length")}}',
default: 21,
'x-decorator': 'FormItem',
'x-component': 'InputNumber',
},
};
filterable = {
operators: operators.string,
};
titleUsable = true;
}

View File

@ -0,0 +1,42 @@
import { CollectionFieldInterface } from '../../data-source/collection-field-interface/CollectionFieldInterface';
import { dateTimeProps, defaultProps, operators } from './properties';
export class UnixTimestampFieldInterface extends CollectionFieldInterface {
name = 'unixTimestamp';
type = 'object';
group = 'datetime';
order = 1;
title = '{{t("Unix Timestamp")}}';
sortable = true;
default = {
type: 'bigInt',
uiSchema: {
type: 'number',
'x-component': 'UnixTimestamp',
'x-component-props': {
accuracy: 'millisecond',
showTime: true,
},
},
};
availableTypes = ['integet', 'bigInt'];
hasDefaultValue = true;
properties = {
...defaultProps,
'uiSchema.x-component-props.accuracy': {
type: 'string',
title: '{{t("Accuracy")}}',
'x-component': 'Radio.Group',
'x-decorator': 'FormItem',
default: 'millisecond',
enum: [
{ value: 'millisecond', label: '{{t("Millisecond")}}' },
{ value: 'second', label: '{{t("Second")}}' },
],
},
};
filterable = {
operators: operators.number,
};
titleUsable = true;
}

View File

@ -0,0 +1,44 @@
import { CollectionFieldInterface } from '../../data-source/collection-field-interface/CollectionFieldInterface';
import { operators } from './properties';
export class UUIDFieldInterface extends CollectionFieldInterface {
name = 'uuid';
type = 'object';
group = 'advanced';
order = 0;
title = '{{t("UUID")}}';
hidden = false;
sortable = true;
default = {
type: 'uuid',
uiSchema: {
type: 'string',
'x-component': 'Input',
'x-validator': 'uuid',
},
};
availableTypes = ['string', 'uid'];
properties = {
'uiSchema.title': {
type: 'string',
title: '{{t("Field display name")}}',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
name: {
type: 'string',
title: '{{t("Field name")}}',
required: true,
'x-disabled': true,
'x-decorator': 'FormItem',
'x-component': 'Input',
description:
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.')}}",
},
};
filterable = {
operators: operators.string,
};
titleUsable = true;
}

View File

@ -891,5 +891,12 @@
"Owners": "负责人",
"Plugin settings": "插件设置",
"Menu": "菜单",
"Drag and drop sorting field": "拖拽排序字段"
"Drag and drop sorting field": "拖拽排序字段",
"Alphabet": "字符",
"Accuracy": "精确度",
"Millisecond": "毫秒",
"Second": "秒",
"Unix Timestamp": "Unix 时间戳",
"Field value do not meet the requirements": "字符不符合要求",
"Field value size is": "字符长度要求"
}

View File

@ -0,0 +1,22 @@
import { useFieldSchema } from '@formily/react';
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
import { SchemaSettingsDateFormat } from '../../../../schema-settings/SchemaSettingsDateFormat';
import { useColumnSchema } from '../../../../schema-component/antd/table-v2/Table.Column.Decorator';
export const unixTimestampComponentFieldSettings = new SchemaSettings({
name: 'fieldSettings:component:UnixTimestamp',
items: [
{
name: 'dateDisplayFormat',
Component: SchemaSettingsDateFormat as any,
useComponentProps() {
const schema = useFieldSchema();
const { fieldSchema: tableColumnSchema } = useColumnSchema();
const fieldSchema = tableColumnSchema || schema;
return {
fieldSchema,
};
},
},
],
});

View File

@ -38,6 +38,7 @@ const InternalRangePicker = connect(
export const DatePicker = (props) => {
const { utc = true } = useDatePickerContext();
const value = Array.isArray(props.value) ? props.value[0] : props.value;
console.log(value);
props = { utc, ...props };
return <InternalDatePicker {...props} value={value} />;
};

View File

@ -50,5 +50,7 @@ export * from './time-picker';
export * from './tree-select';
export * from './upload';
export * from './variable';
export * from './unixTimestamp';
export * from './nanoIDInput';
import './index.less';

View File

@ -0,0 +1,46 @@
import { customAlphabet as Alphabet } from 'nanoid';
import React, { useEffect } from 'react';
import { LoadingOutlined } from '@ant-design/icons';
import { connect, mapProps, mapReadPretty, useForm } from '@formily/react';
import { Input as AntdInput } from 'antd';
import { ReadPretty } from '../input';
import { useCollectionField } from '../../../data-source/collection-field/CollectionFieldProvider';
import { useTranslation } from 'react-i18next';
export const NanoIDInput = Object.assign(
connect(
AntdInput,
mapProps((props: any, field: any) => {
const { size, customAlphabet } = useCollectionField();
const { t } = useTranslation();
const form = useForm();
function isValidNanoid(value) {
if (value.length !== size) {
return t('Field value size is') + ` ${size}`;
}
for (let i = 0; i < value.length; i++) {
if (customAlphabet.indexOf(value[i]) === -1) {
return t(`Field value do not meet the requirements`);
}
}
}
useEffect(() => {
if (!field.initialValue) {
field.setInitialValue(Alphabet(customAlphabet, size)());
}
form.setFieldState(field.props.name, (state) => {
state.validator = isValidNanoid;
});
}, []);
return {
...props,
suffix: <span>{field?.['loading'] || field?.['validating'] ? <LoadingOutlined /> : props.suffix}</span>,
};
}),
mapReadPretty(ReadPretty.Input),
),
{
ReadPretty: ReadPretty.Input,
},
);

View File

@ -0,0 +1 @@
export * from './NanoIDInput';

View File

@ -21,9 +21,17 @@ Radio.__ANT_RADIO = true;
Radio.Group = connect(
AntdRadio.Group,
mapProps({
dataSource: 'options',
}),
mapProps(
{
dataSource: 'options',
},
(props) => {
return {
...props,
value: props.value && typeof props.value !== 'boolean' ? props.value.toString() : props.value,
};
},
),
mapReadPretty((props) => {
if (!isValid(props.value)) {
return <div></div>;

View File

@ -9,11 +9,11 @@ import React from 'react';
const options = [
{
label: '男',
value: 1,
value: '1',
},
{
label: '女',
value: 2,
value: '2',
},
];

View File

@ -8,12 +8,12 @@ import React from 'react';
const options = [
{
label: '男',
value: 1,
value: '1',
color: 'blue',
},
{
label: '女',
value: 2,
value: '2',
color: 'red',
},
];

View File

@ -122,7 +122,7 @@ const InternalSelect = connect(
}
return undefined;
}
return v;
return v ? v.toString() : v;
};
return (
<AntdSelect

View File

@ -44,7 +44,10 @@ export function getCurrentOptions(values: string | string[], dataSource: any[],
if (!options) return [];
const current: Option[] = [];
for (const value of arrValues) {
const option = options.find((v) => v[fieldNames.value] === value) || { value, label: value };
const option = options.find((v) => v[fieldNames.value] == value) || {
value,
label: value ? value.toString() : value,
};
current.push(option);
}
return current;

View File

@ -0,0 +1,49 @@
import { connect, mapReadPretty } from '@formily/react';
import React, { useMemo } from 'react';
import { DatePicker } from '../date-picker';
import dayjs from 'dayjs';
const toValue = (value: any, accuracy) => {
if (value) {
return timestampToDate(value, accuracy);
}
return null;
};
function timestampToDate(timestamp, accuracy = 'millisecond') {
if (accuracy === 'second') {
timestamp *= 1000; // 如果精确度是秒级则将时间戳乘以1000转换为毫秒级
}
return dayjs(timestamp);
}
function getTimestamp(date, accuracy = 'millisecond') {
if (accuracy === 'second') {
return dayjs(date).unix();
} else {
return dayjs(date).valueOf(); // 默认返回毫秒级时间戳
}
}
export const UnixTimestamp = connect(
(props) => {
const { value, onChange, accuracy } = props;
const v = useMemo(() => toValue(value, accuracy), [value]);
return (
<DatePicker
{...props}
value={v}
onChange={(v: any) => {
if (onChange) {
onChange(getTimestamp(v, accuracy));
}
}}
/>
);
},
mapReadPretty((props) => {
const { value, accuracy } = props;
const v = useMemo(() => toValue(value, accuracy), [value]);
return <DatePicker.ReadPretty {...props} value={v} />;
}),
);

View File

@ -0,0 +1 @@
export * from './UnixTimestamp';

View File

@ -42,6 +42,7 @@ import { subformPopoverComponentFieldSettings } from '../modules/fields/componen
import { selectComponentFieldSettings } from '../modules/fields/component/Select/selectComponentFieldSettings';
import { subTablePopoverComponentFieldSettings } from '../modules/fields/component/SubTable/subTablePopoverComponentFieldSettings';
import { tagComponentFieldSettings } from '../modules/fields/component/Tag/tagComponentFieldSettings';
import { unixTimestampComponentFieldSettings } from '../modules/fields/component/UnixTimestamp/unixTimestampComponentFieldSettings';
export class SchemaSettingsPlugin extends Plugin {
async load() {
@ -90,6 +91,8 @@ export class SchemaSettingsPlugin extends Plugin {
this.schemaSettingsManager.add(subformPopoverComponentFieldSettings);
this.schemaSettingsManager.add(subTablePopoverComponentFieldSettings);
this.schemaSettingsManager.add(datePickerComponentFieldSettings);
this.schemaSettingsManager.add(unixTimestampComponentFieldSettings);
this.schemaSettingsManager.add(fileManagerComponentFieldSettings);
this.schemaSettingsManager.add(tagComponentFieldSettings);
this.schemaSettingsManager.add(cascadeSelectComponentFieldSettings);

View File

@ -0,0 +1,39 @@
import { mockDatabase } from '../';
import { Database } from '../../database';
describe('nanoid field', () => {
let db: Database;
beforeEach(async () => {
db = mockDatabase();
await db.clean({ drop: true });
});
afterEach(async () => {
await db.close();
});
it('should create nanoid field type', async () => {
const Test = db.collection({
name: 'tests',
autoGenId: false,
fields: [
{
type: 'nanoid',
name: 'id',
primaryKey: true,
size: 21,
customAlphabet: '1234567890abcdef',
},
{
type: 'nanoid',
name: 'id2',
},
],
});
await Test.sync();
const test = await Test.model.create();
expect(test.id).toHaveLength(21);
expect(test.id2).toHaveLength(12);
});
});

View File

@ -25,6 +25,7 @@ import { TimeFieldOptions } from './time-field';
import { UidFieldOptions } from './uid-field';
import { UUIDFieldOptions } from './uuid-field';
import { VirtualFieldOptions } from './virtual-field';
import { NanoidFieldOptions } from './nanoid-field';
export * from './array-field';
export * from './belongs-to-field';
@ -48,6 +49,7 @@ export * from './time-field';
export * from './uid-field';
export * from './uuid-field';
export * from './virtual-field';
export * from './nanoid-field';
export type FieldOptions =
| BaseFieldOptions
@ -70,6 +72,7 @@ export type FieldOptions =
| DateFieldOptions
| UidFieldOptions
| UUIDFieldOptions
| NanoidFieldOptions
| PasswordFieldOptions
| ContextFieldOptions
| BelongsToFieldOptions

View File

@ -0,0 +1,40 @@
import { DataTypes } from 'sequelize';
import { BaseColumnFieldOptions, Field } from './field';
import { customAlphabet, nanoid } from 'nanoid';
const DEFAULT_SIZE = 12;
export class NanoidField extends Field {
get dataType() {
return DataTypes.STRING;
}
init() {
const { name, size, customAlphabet: customAlphabetOptions } = this.options;
this.listener = async (instance) => {
const value = instance.get(name);
if (!value) {
const nanoIdFunc = customAlphabetOptions ? customAlphabet(customAlphabetOptions) : nanoid;
instance.set(name, nanoIdFunc(size || DEFAULT_SIZE));
}
};
}
bind() {
super.bind();
this.on('beforeCreate', this.listener);
this.on('beforeUpdate', this.listener);
}
unbind() {
super.unbind();
this.off('beforeCreate', this.listener);
this.off('beforeUpdate', this.listener);
}
}
export interface NanoidFieldOptions extends BaseColumnFieldOptions {
type: 'nanoid';
size?: number;
customAlphabet?: string;
}

View File

@ -1,9 +1,10 @@
const postgres = {
'character varying': 'string',
varchar: 'string',
'character varying': ['string', 'uuid', 'nanoid'],
varchar: ['string', 'uuid', 'nanoid'],
char: ['string', 'uuid', 'nanoid'],
character: 'string',
text: 'text',
char: 'string',
oid: 'string',
name: 'string',
@ -29,7 +30,7 @@ const postgres = {
path: 'json',
polygon: 'json',
circle: 'json',
uuid: 'string',
uuid: 'uuid',
};
const mysql = {
@ -41,10 +42,10 @@ const mysql = {
'tinyint unsigned': ['integer', 'boolean', 'sort'],
'mediumint unsigned': ['integer', 'boolean', 'sort'],
char: 'string',
char: ['string', 'uuid', 'nanoid'],
varchar: ['string', 'uuid', 'nanoid'],
date: 'date',
time: 'time',
varchar: 'string',
text: 'text',
longtext: 'text',
int: ['integer', 'sort'],
@ -64,7 +65,7 @@ const mysql = {
const sqlite = {
text: 'text',
varchar: 'string',
varchar: ['string', 'uuid', 'nanoid'],
integer: 'integer',
real: 'real',