diff --git a/packages/core/client/src/collection-manager/collectionPlugin.ts b/packages/core/client/src/collection-manager/collectionPlugin.ts
index 913ef22dbe..93b0bf2b00 100644
--- a/packages/core/client/src/collection-manager/collectionPlugin.ts
+++ b/packages/core/client/src/collection-manager/collectionPlugin.ts
@@ -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,
]);
}
diff --git a/packages/core/client/src/collection-manager/interfaces/index.ts b/packages/core/client/src/collection-manager/interfaces/index.ts
index 10a1672cbe..f1a10caede 100644
--- a/packages/core/client/src/collection-manager/interfaces/index.ts
+++ b/packages/core/client/src/collection-manager/interfaces/index.ts
@@ -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';
diff --git a/packages/core/client/src/collection-manager/interfaces/nanoid.ts b/packages/core/client/src/collection-manager/interfaces/nanoid.ts
new file mode 100644
index 0000000000..adfd59fa2c
--- /dev/null
+++ b/packages/core/client/src/collection-manager/interfaces/nanoid.ts
@@ -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;
+}
diff --git a/packages/core/client/src/collection-manager/interfaces/unixTimestamp.tsx b/packages/core/client/src/collection-manager/interfaces/unixTimestamp.tsx
new file mode 100644
index 0000000000..f2de2f355e
--- /dev/null
+++ b/packages/core/client/src/collection-manager/interfaces/unixTimestamp.tsx
@@ -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;
+}
diff --git a/packages/core/client/src/collection-manager/interfaces/uuid.ts b/packages/core/client/src/collection-manager/interfaces/uuid.ts
new file mode 100644
index 0000000000..f54be0b344
--- /dev/null
+++ b/packages/core/client/src/collection-manager/interfaces/uuid.ts
@@ -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;
+}
diff --git a/packages/core/client/src/locale/zh-CN.json b/packages/core/client/src/locale/zh-CN.json
index d0d907b5b2..13f2b191e8 100644
--- a/packages/core/client/src/locale/zh-CN.json
+++ b/packages/core/client/src/locale/zh-CN.json
@@ -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": "字符长度要求"
}
diff --git a/packages/core/client/src/modules/fields/component/UnixTimestamp/unixTimestampComponentFieldSettings.tsx b/packages/core/client/src/modules/fields/component/UnixTimestamp/unixTimestampComponentFieldSettings.tsx
new file mode 100644
index 0000000000..5bc587c9d5
--- /dev/null
+++ b/packages/core/client/src/modules/fields/component/UnixTimestamp/unixTimestampComponentFieldSettings.tsx
@@ -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,
+ };
+ },
+ },
+ ],
+});
diff --git a/packages/core/client/src/schema-component/antd/date-picker/DatePicker.tsx b/packages/core/client/src/schema-component/antd/date-picker/DatePicker.tsx
index 60dfe93bf2..6430eddb62 100644
--- a/packages/core/client/src/schema-component/antd/date-picker/DatePicker.tsx
+++ b/packages/core/client/src/schema-component/antd/date-picker/DatePicker.tsx
@@ -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 ;
};
diff --git a/packages/core/client/src/schema-component/antd/index.ts b/packages/core/client/src/schema-component/antd/index.ts
index 9e75092e17..e03080bde4 100644
--- a/packages/core/client/src/schema-component/antd/index.ts
+++ b/packages/core/client/src/schema-component/antd/index.ts
@@ -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';
diff --git a/packages/core/client/src/schema-component/antd/nanoIDInput/NanoIDInput.tsx b/packages/core/client/src/schema-component/antd/nanoIDInput/NanoIDInput.tsx
new file mode 100644
index 0000000000..648cb411ef
--- /dev/null
+++ b/packages/core/client/src/schema-component/antd/nanoIDInput/NanoIDInput.tsx
@@ -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: {field?.['loading'] || field?.['validating'] ? : props.suffix},
+ };
+ }),
+ mapReadPretty(ReadPretty.Input),
+ ),
+ {
+ ReadPretty: ReadPretty.Input,
+ },
+);
diff --git a/packages/core/client/src/schema-component/antd/nanoIDInput/index.tsx b/packages/core/client/src/schema-component/antd/nanoIDInput/index.tsx
new file mode 100644
index 0000000000..7381a29c01
--- /dev/null
+++ b/packages/core/client/src/schema-component/antd/nanoIDInput/index.tsx
@@ -0,0 +1 @@
+export * from './NanoIDInput';
diff --git a/packages/core/client/src/schema-component/antd/radio/Radio.tsx b/packages/core/client/src/schema-component/antd/radio/Radio.tsx
index eb0b165e61..7ff5cf6e80 100644
--- a/packages/core/client/src/schema-component/antd/radio/Radio.tsx
+++ b/packages/core/client/src/schema-component/antd/radio/Radio.tsx
@@ -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
;
diff --git a/packages/core/client/src/schema-component/antd/radio/demos/demo2.tsx b/packages/core/client/src/schema-component/antd/radio/demos/demo2.tsx
index c99560caab..849f988794 100644
--- a/packages/core/client/src/schema-component/antd/radio/demos/demo2.tsx
+++ b/packages/core/client/src/schema-component/antd/radio/demos/demo2.tsx
@@ -9,11 +9,11 @@ import React from 'react';
const options = [
{
label: '男',
- value: 1,
+ value: '1',
},
{
label: '女',
- value: 2,
+ value: '2',
},
];
diff --git a/packages/core/client/src/schema-component/antd/radio/demos/demo3.tsx b/packages/core/client/src/schema-component/antd/radio/demos/demo3.tsx
index ed086d49bf..e9ca3c293d 100644
--- a/packages/core/client/src/schema-component/antd/radio/demos/demo3.tsx
+++ b/packages/core/client/src/schema-component/antd/radio/demos/demo3.tsx
@@ -8,12 +8,12 @@ import React from 'react';
const options = [
{
label: '男',
- value: 1,
+ value: '1',
color: 'blue',
},
{
label: '女',
- value: 2,
+ value: '2',
color: 'red',
},
];
diff --git a/packages/core/client/src/schema-component/antd/select/Select.tsx b/packages/core/client/src/schema-component/antd/select/Select.tsx
index 3d9784817b..9207a1bc73 100644
--- a/packages/core/client/src/schema-component/antd/select/Select.tsx
+++ b/packages/core/client/src/schema-component/antd/select/Select.tsx
@@ -122,7 +122,7 @@ const InternalSelect = connect(
}
return undefined;
}
- return v;
+ return v ? v.toString() : v;
};
return (
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;
diff --git a/packages/core/client/src/schema-component/antd/unixTimestamp/UnixTimestamp.tsx b/packages/core/client/src/schema-component/antd/unixTimestamp/UnixTimestamp.tsx
new file mode 100644
index 0000000000..4eeed12d33
--- /dev/null
+++ b/packages/core/client/src/schema-component/antd/unixTimestamp/UnixTimestamp.tsx
@@ -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 (
+ {
+ if (onChange) {
+ onChange(getTimestamp(v, accuracy));
+ }
+ }}
+ />
+ );
+ },
+ mapReadPretty((props) => {
+ const { value, accuracy } = props;
+ const v = useMemo(() => toValue(value, accuracy), [value]);
+ return ;
+ }),
+);
diff --git a/packages/core/client/src/schema-component/antd/unixTimestamp/index.tsx b/packages/core/client/src/schema-component/antd/unixTimestamp/index.tsx
new file mode 100644
index 0000000000..fdc2fed171
--- /dev/null
+++ b/packages/core/client/src/schema-component/antd/unixTimestamp/index.tsx
@@ -0,0 +1 @@
+export * from './UnixTimestamp';
diff --git a/packages/core/client/src/schema-settings/SchemaSettingsPlugin.ts b/packages/core/client/src/schema-settings/SchemaSettingsPlugin.ts
index 2afec8ee8b..c6db2687bd 100644
--- a/packages/core/client/src/schema-settings/SchemaSettingsPlugin.ts
+++ b/packages/core/client/src/schema-settings/SchemaSettingsPlugin.ts
@@ -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);
diff --git a/packages/core/database/src/__tests__/fields/nanoid-field.test.ts b/packages/core/database/src/__tests__/fields/nanoid-field.test.ts
new file mode 100644
index 0000000000..84c4be4595
--- /dev/null
+++ b/packages/core/database/src/__tests__/fields/nanoid-field.test.ts
@@ -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);
+ });
+});
diff --git a/packages/core/database/src/fields/index.ts b/packages/core/database/src/fields/index.ts
index 264140a243..eb630051da 100644
--- a/packages/core/database/src/fields/index.ts
+++ b/packages/core/database/src/fields/index.ts
@@ -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
diff --git a/packages/core/database/src/fields/nanoid-field.ts b/packages/core/database/src/fields/nanoid-field.ts
new file mode 100644
index 0000000000..78e5a0aa84
--- /dev/null
+++ b/packages/core/database/src/fields/nanoid-field.ts
@@ -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;
+}
diff --git a/packages/core/database/src/view/field-type-map.ts b/packages/core/database/src/view/field-type-map.ts
index 7ca352ed3a..fc21163739 100644
--- a/packages/core/database/src/view/field-type-map.ts
+++ b/packages/core/database/src/view/field-type-map.ts
@@ -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',