mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 07:25:15 +00:00
Revert "refactor: datetime field support timezone, defaultToCurrentTime, and onUpdateToCurrentTime (#5012)"
Some checks failed
Build Docker Image / build-and-push (push) Waiting to run
Build Pro Image / build-and-push (push) Waiting to run
E2E / Build (push) Waiting to run
E2E / Core and plugins (push) Blocked by required conditions
E2E / plugin-workflow (push) Blocked by required conditions
E2E / plugin-workflow-approval (push) Blocked by required conditions
E2E / plugin-data-source-main (push) Blocked by required conditions
E2E / Comment on PR (push) Blocked by required conditions
NocoBase Backend Test / sqlite-test (20, false) (push) Waiting to run
NocoBase Backend Test / sqlite-test (20, true) (push) Waiting to run
NocoBase Backend Test / postgres-test (public, 20, nocobase, false) (push) Waiting to run
NocoBase Backend Test / postgres-test (public, 20, nocobase, true) (push) Waiting to run
NocoBase Backend Test / postgres-test (public, 20, public, false) (push) Waiting to run
NocoBase Backend Test / postgres-test (public, 20, public, true) (push) Waiting to run
NocoBase Backend Test / postgres-test (user_schema, 20, nocobase, false) (push) Waiting to run
NocoBase Backend Test / postgres-test (user_schema, 20, nocobase, true) (push) Waiting to run
NocoBase Backend Test / postgres-test (user_schema, 20, public, false) (push) Waiting to run
NocoBase Backend Test / postgres-test (user_schema, 20, public, true) (push) Waiting to run
NocoBase Backend Test / mysql-test (20, false) (push) Waiting to run
NocoBase Backend Test / mysql-test (20, true) (push) Waiting to run
NocoBase Backend Test / mariadb-test (20, false) (push) Waiting to run
NocoBase Backend Test / mariadb-test (20, true) (push) Waiting to run
Test on Windows / build (push) Waiting to run
NocoBase FrontEnd Test / frontend-test (18) (push) Has been cancelled
Some checks failed
Build Docker Image / build-and-push (push) Waiting to run
Build Pro Image / build-and-push (push) Waiting to run
E2E / Build (push) Waiting to run
E2E / Core and plugins (push) Blocked by required conditions
E2E / plugin-workflow (push) Blocked by required conditions
E2E / plugin-workflow-approval (push) Blocked by required conditions
E2E / plugin-data-source-main (push) Blocked by required conditions
E2E / Comment on PR (push) Blocked by required conditions
NocoBase Backend Test / sqlite-test (20, false) (push) Waiting to run
NocoBase Backend Test / sqlite-test (20, true) (push) Waiting to run
NocoBase Backend Test / postgres-test (public, 20, nocobase, false) (push) Waiting to run
NocoBase Backend Test / postgres-test (public, 20, nocobase, true) (push) Waiting to run
NocoBase Backend Test / postgres-test (public, 20, public, false) (push) Waiting to run
NocoBase Backend Test / postgres-test (public, 20, public, true) (push) Waiting to run
NocoBase Backend Test / postgres-test (user_schema, 20, nocobase, false) (push) Waiting to run
NocoBase Backend Test / postgres-test (user_schema, 20, nocobase, true) (push) Waiting to run
NocoBase Backend Test / postgres-test (user_schema, 20, public, false) (push) Waiting to run
NocoBase Backend Test / postgres-test (user_schema, 20, public, true) (push) Waiting to run
NocoBase Backend Test / mysql-test (20, false) (push) Waiting to run
NocoBase Backend Test / mysql-test (20, true) (push) Waiting to run
NocoBase Backend Test / mariadb-test (20, false) (push) Waiting to run
NocoBase Backend Test / mariadb-test (20, true) (push) Waiting to run
Test on Windows / build (push) Waiting to run
NocoBase FrontEnd Test / frontend-test (18) (push) Has been cancelled
This reverts commit e232ed7582
.
This commit is contained in:
parent
0b5e43df8a
commit
ded5f26c09
@ -52,7 +52,6 @@ import {
|
|||||||
UUIDFieldInterface,
|
UUIDFieldInterface,
|
||||||
NanoidFieldInterface,
|
NanoidFieldInterface,
|
||||||
UnixTimestampFieldInterface,
|
UnixTimestampFieldInterface,
|
||||||
DateFieldInterface,
|
|
||||||
} from './interfaces';
|
} from './interfaces';
|
||||||
import {
|
import {
|
||||||
GeneralCollectionTemplate,
|
GeneralCollectionTemplate,
|
||||||
@ -174,7 +173,6 @@ export class CollectionPlugin extends Plugin {
|
|||||||
UUIDFieldInterface,
|
UUIDFieldInterface,
|
||||||
NanoidFieldInterface,
|
NanoidFieldInterface,
|
||||||
UnixTimestampFieldInterface,
|
UnixTimestampFieldInterface,
|
||||||
DateFieldInterface,
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,8 +7,8 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Switch, Radio, Input } from 'antd';
|
import { Switch } from 'antd';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
export const TargetKey = () => {
|
export const TargetKey = () => {
|
||||||
return <div>Target key</div>;
|
return <div>Target key</div>;
|
||||||
@ -50,37 +50,3 @@ export const ForeignKey2 = () => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 自定义 Radio 组件
|
|
||||||
export const CustomRadio = (props) => {
|
|
||||||
const { options, onChange } = props;
|
|
||||||
const [value, setValue] = useState(props.value);
|
|
||||||
useEffect(() => {
|
|
||||||
setValue(['server', 'client'].includes(props.value) ? props.value : 'custom');
|
|
||||||
}, [props.value]);
|
|
||||||
const handleRadioChange = (e) => {
|
|
||||||
setValue(e.target.value);
|
|
||||||
if (e.target.value !== 'custom') {
|
|
||||||
onChange?.(e.target.value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Radio.Group onChange={handleRadioChange} value={value}>
|
|
||||||
{options.map((option) => (
|
|
||||||
<Radio key={option.value} value={option.value}>
|
|
||||||
{option.label}
|
|
||||||
{option.value === 'custom' && value === 'custom' ? (
|
|
||||||
<Input
|
|
||||||
style={{ width: 200, marginLeft: 10 }}
|
|
||||||
onChange={(e) => {
|
|
||||||
onChange?.(e.target.value);
|
|
||||||
}}
|
|
||||||
value={['server', 'client', 'custom'].includes(props.value) ? null : props.value}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</Radio>
|
|
||||||
))}
|
|
||||||
</Radio.Group>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
/**
|
|
||||||
* This file is part of the NocoBase (R) project.
|
|
||||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
||||||
* Authors: NocoBase Team.
|
|
||||||
*
|
|
||||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { CollectionFieldInterface } from '../../data-source/collection-field-interface/CollectionFieldInterface';
|
|
||||||
import { dateTimeProps, defaultProps, operators } from './properties';
|
|
||||||
|
|
||||||
export class DateFieldInterface extends CollectionFieldInterface {
|
|
||||||
name = 'date';
|
|
||||||
type = 'object';
|
|
||||||
group = 'datetime';
|
|
||||||
order = 1;
|
|
||||||
title = '{{t("Date")}}';
|
|
||||||
sortable = true;
|
|
||||||
default = {
|
|
||||||
type: 'dateOnly',
|
|
||||||
uiSchema: {
|
|
||||||
type: 'string',
|
|
||||||
'x-component': 'DatePicker',
|
|
||||||
'x-component-props': {
|
|
||||||
dateOnly: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
availableTypes = ['date', 'dateOnly', 'string'];
|
|
||||||
hasDefaultValue = true;
|
|
||||||
properties = {
|
|
||||||
...defaultProps,
|
|
||||||
'uiSchema.x-component-props.dateFormat': {
|
|
||||||
type: 'string',
|
|
||||||
title: '{{t("Date format")}}',
|
|
||||||
'x-component': 'Radio.Group',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
default: 'YYYY-MM-DD',
|
|
||||||
enum: [
|
|
||||||
{
|
|
||||||
label: '{{t("Year/Month/Day")}}',
|
|
||||||
value: 'YYYY/MM/DD',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '{{t("Year-Month-Day")}}',
|
|
||||||
value: 'YYYY-MM-DD',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '{{t("Day/Month/Year")}}',
|
|
||||||
value: 'DD/MM/YYYY',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
filterable = {
|
|
||||||
operators: operators.datetime,
|
|
||||||
};
|
|
||||||
titleUsable = true;
|
|
||||||
}
|
|
@ -19,9 +19,6 @@ export class DatetimeFieldInterface extends CollectionFieldInterface {
|
|||||||
sortable = true;
|
sortable = true;
|
||||||
default = {
|
default = {
|
||||||
type: 'date',
|
type: 'date',
|
||||||
defaultToCurrentTime: false,
|
|
||||||
onUpdateToCurrentTime: false,
|
|
||||||
timezone: 'server',
|
|
||||||
uiSchema: {
|
uiSchema: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
'x-component': 'DatePicker',
|
'x-component': 'DatePicker',
|
||||||
|
@ -46,4 +46,3 @@ export * from './sort';
|
|||||||
export * from './uuid';
|
export * from './uuid';
|
||||||
export * from './nanoid';
|
export * from './nanoid';
|
||||||
export * from './unixTimestamp';
|
export * from './unixTimestamp';
|
||||||
export * from './date';
|
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
import { Field } from '@formily/core';
|
import { Field } from '@formily/core';
|
||||||
import { ISchema } from '@formily/react';
|
import { ISchema } from '@formily/react';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import { CustomRadio } from '../components';
|
|
||||||
export * as operators from './operators';
|
export * as operators from './operators';
|
||||||
|
|
||||||
export const type: ISchema = {
|
export const type: ISchema = {
|
||||||
@ -226,29 +225,6 @@ export const reverseFieldProperties: Record<string, ISchema> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const dateTimeProps: { [key: string]: ISchema } = {
|
export const dateTimeProps: { [key: string]: ISchema } = {
|
||||||
timezone: {
|
|
||||||
type: 'string',
|
|
||||||
title: '{{t("Timezone")}}',
|
|
||||||
'x-component': CustomRadio,
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
default: 'server',
|
|
||||||
'x-component-props': {
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
label: '{{t("None")}}',
|
|
||||||
value: 'server',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '{{t("Client\'s time zone")}}',
|
|
||||||
value: 'client',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '{{t("Custom")}}',
|
|
||||||
value: 'custom',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'uiSchema.x-component-props.dateFormat': {
|
'uiSchema.x-component-props.dateFormat': {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
title: '{{t("Date format")}}',
|
title: '{{t("Date format")}}',
|
||||||
@ -277,10 +253,10 @@ export const dateTimeProps: { [key: string]: ISchema } = {
|
|||||||
'x-content': '{{t("Show time")}}',
|
'x-content': '{{t("Show time")}}',
|
||||||
'x-reactions': [
|
'x-reactions': [
|
||||||
`{{(field) => {
|
`{{(field) => {
|
||||||
field.query('..[].timeFormat').take(f => {
|
field.query('..[].timeFormat').take(f => {
|
||||||
f.display = field.value ? 'visible' : 'none';
|
f.display = field.value ? 'visible' : 'none';
|
||||||
});
|
});
|
||||||
}}}`,
|
}}}`,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
'uiSchema.x-component-props.timeFormat': {
|
'uiSchema.x-component-props.timeFormat': {
|
||||||
@ -300,18 +276,6 @@ export const dateTimeProps: { [key: string]: ISchema } = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
defaultToCurrentTime: {
|
|
||||||
type: 'boolean',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'Checkbox',
|
|
||||||
'x-content': '{{t("Default value to current time")}}',
|
|
||||||
},
|
|
||||||
onUpdateToCurrentTime: {
|
|
||||||
type: 'boolean',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'Checkbox',
|
|
||||||
'x-content': '{{t("Automatically update timestamp on update")}}',
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dataSource: ISchema = {
|
export const dataSource: ISchema = {
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { CollectionFieldInterface } from '../../data-source/collection-field-interface/CollectionFieldInterface';
|
import { CollectionFieldInterface } from '../../data-source/collection-field-interface/CollectionFieldInterface';
|
||||||
import { defaultProps, operators } from './properties';
|
import { dateTimeProps, defaultProps, operators } from './properties';
|
||||||
import { CustomRadio } from './components';
|
|
||||||
export class UnixTimestampFieldInterface extends CollectionFieldInterface {
|
export class UnixTimestampFieldInterface extends CollectionFieldInterface {
|
||||||
name = 'unixTimestamp';
|
name = 'unixTimestamp';
|
||||||
type = 'object';
|
type = 'object';
|
||||||
@ -18,47 +18,21 @@ export class UnixTimestampFieldInterface extends CollectionFieldInterface {
|
|||||||
title = '{{t("Unix Timestamp")}}';
|
title = '{{t("Unix Timestamp")}}';
|
||||||
sortable = true;
|
sortable = true;
|
||||||
default = {
|
default = {
|
||||||
type: 'unixTimestamp',
|
type: 'bigInt',
|
||||||
accuracy: 'second',
|
|
||||||
timezone: 'server',
|
|
||||||
defaultToCurrentTime: false,
|
|
||||||
onUpdateToCurrentTime: false,
|
|
||||||
uiSchema: {
|
uiSchema: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
'x-component': 'UnixTimestamp',
|
'x-component': 'UnixTimestamp',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
|
accuracy: 'second',
|
||||||
showTime: true,
|
showTime: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
availableTypes = ['integer', 'bigInt', 'unixTimestamp'];
|
availableTypes = ['integer', 'bigInt'];
|
||||||
hasDefaultValue = false;
|
hasDefaultValue = true;
|
||||||
properties = {
|
properties = {
|
||||||
...defaultProps,
|
...defaultProps,
|
||||||
timezone: {
|
'uiSchema.x-component-props.accuracy': {
|
||||||
type: 'string',
|
|
||||||
title: '{{t("Timezone")}}',
|
|
||||||
'x-component': CustomRadio,
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
default: 'server',
|
|
||||||
'x-component-props': {
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
label: '{{t("None")}}',
|
|
||||||
value: 'server',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '{{t("Client\'s time zone")}}',
|
|
||||||
value: 'client',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'custom',
|
|
||||||
value: 'custom',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
accuracy: {
|
|
||||||
type: 'string',
|
type: 'string',
|
||||||
title: '{{t("Accuracy")}}',
|
title: '{{t("Accuracy")}}',
|
||||||
'x-component': 'Radio.Group',
|
'x-component': 'Radio.Group',
|
||||||
@ -69,20 +43,6 @@ export class UnixTimestampFieldInterface extends CollectionFieldInterface {
|
|||||||
{ value: 'second', label: '{{t("Second")}}' },
|
{ value: 'second', label: '{{t("Second")}}' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
defaultToCurrentTime: {
|
|
||||||
type: 'boolean',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'Checkbox',
|
|
||||||
'x-content': '{{t("Default value to current time")}}',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
onUpdateToCurrentTime: {
|
|
||||||
type: 'boolean',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'Checkbox',
|
|
||||||
'x-content': '{{t("Automatically update timestamp on update")}}',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
filterable = {
|
filterable = {
|
||||||
operators: operators.number,
|
operators: operators.number,
|
||||||
|
@ -283,7 +283,7 @@
|
|||||||
"Checkbox group": "复选框",
|
"Checkbox group": "复选框",
|
||||||
"China region": "中国行政区",
|
"China region": "中国行政区",
|
||||||
"Date & Time": "日期 & 时间",
|
"Date & Time": "日期 & 时间",
|
||||||
"Datetime": "日期时间",
|
"Datetime": "日期",
|
||||||
"Relation": "关系类型",
|
"Relation": "关系类型",
|
||||||
"Link to": "关联",
|
"Link to": "关联",
|
||||||
"Link to description": "用于快速创建表关系,可兼容大多数普通场景。适合非开发人员使用。作为字段存在时,它是一个下拉选择用于选择目标数据表的数据。创建后,将同时在目标数据表中生成当前数据表的关联字段。",
|
"Link to description": "用于快速创建表关系,可兼容大多数普通场景。适合非开发人员使用。作为字段存在时,它是一个下拉选择用于选择目标数据表的数据。创建后,将同时在目标数据表中生成当前数据表的关联字段。",
|
||||||
@ -967,8 +967,5 @@
|
|||||||
"Clear default value": "清除默认值",
|
"Clear default value": "清除默认值",
|
||||||
"Open in new window": "新窗口打开",
|
"Open in new window": "新窗口打开",
|
||||||
"Sorry, the page you visited does not exist.": "抱歉,你访问的页面不存在。",
|
"Sorry, the page you visited does not exist.": "抱歉,你访问的页面不存在。",
|
||||||
"Template engine": "模板引擎",
|
"Set Template Engine": "设置模板引擎"
|
||||||
"Default value to current time":"设置字段默认值为当前时间",
|
|
||||||
"Automatically update timestamp on update":"当记录更新时自动设置字段值为当前时间",
|
|
||||||
"Client's time zone":"客户端时区"
|
|
||||||
}
|
}
|
||||||
|
@ -78,20 +78,17 @@ export const mapDatePicker = function () {
|
|||||||
return (props: any) => {
|
return (props: any) => {
|
||||||
const format = getDefaultFormat(props) as any;
|
const format = getDefaultFormat(props) as any;
|
||||||
const onChange = props.onChange;
|
const onChange = props.onChange;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...props,
|
...props,
|
||||||
format: format,
|
format: format,
|
||||||
value: str2moment(props.value, props),
|
value: str2moment(props.value, props),
|
||||||
onChange: (value: Dayjs | null, dateString) => {
|
onChange: (value: Dayjs | null) => {
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
if (!props.showTime && value) {
|
if (!props.showTime && value) {
|
||||||
value = value.startOf('day');
|
value = value.startOf('day');
|
||||||
}
|
}
|
||||||
if (props.dateOnly) {
|
onChange(moment2str(value, props));
|
||||||
onChange(dateString);
|
|
||||||
} else {
|
|
||||||
onChange(moment2str(value, props));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -8,32 +8,57 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { connect, mapReadPretty } from '@formily/react';
|
import { connect, mapReadPretty } from '@formily/react';
|
||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { DatePicker } from '../date-picker';
|
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(); // 默认返回毫秒级时间戳
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface UnixTimestampProps {
|
interface UnixTimestampProps {
|
||||||
value?: any;
|
value?: number;
|
||||||
|
accuracy?: 'millisecond' | 'second';
|
||||||
onChange?: (value: number) => void;
|
onChange?: (value: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UnixTimestamp = connect(
|
export const UnixTimestamp = connect(
|
||||||
(props: UnixTimestampProps) => {
|
(props: UnixTimestampProps) => {
|
||||||
const { value, onChange } = props;
|
const { value, onChange, accuracy = 'second' } = props;
|
||||||
|
const v = useMemo(() => toValue(value, accuracy), [value, accuracy]);
|
||||||
return (
|
return (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
{...props}
|
{...props}
|
||||||
value={value}
|
value={v}
|
||||||
onChange={(v: any) => {
|
onChange={(v: any) => {
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
onChange(v);
|
onChange(getTimestamp(v, accuracy));
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
mapReadPretty((props) => {
|
mapReadPretty((props) => {
|
||||||
const { value } = props;
|
const { value, accuracy = 'second' } = props;
|
||||||
return <DatePicker.ReadPretty {...props} value={value} />;
|
const v = useMemo(() => toValue(value, accuracy), [value, accuracy]);
|
||||||
|
return <DatePicker.ReadPretty {...props} value={v} />;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
@ -13,9 +13,11 @@ import { UnixTimestamp } from '@nocobase/client';
|
|||||||
describe('UnixTimestamp', () => {
|
describe('UnixTimestamp', () => {
|
||||||
it('renders without errors', async () => {
|
it('renders without errors', async () => {
|
||||||
const { container } = await renderAppOptions({
|
const { container } = await renderAppOptions({
|
||||||
Component: UnixTimestamp as any,
|
Component: UnixTimestamp,
|
||||||
props: {},
|
props: {
|
||||||
value: null,
|
accuracy: 'millisecond',
|
||||||
|
},
|
||||||
|
value: 0,
|
||||||
});
|
});
|
||||||
expect(container).toMatchInlineSnapshot(`
|
expect(container).toMatchInlineSnapshot(`
|
||||||
<div>
|
<div>
|
||||||
@ -67,10 +69,78 @@ describe('UnixTimestamp', () => {
|
|||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('millisecond', async () => {
|
||||||
|
await renderAppOptions({
|
||||||
|
Component: UnixTimestamp,
|
||||||
|
value: 1712819630000,
|
||||||
|
props: {
|
||||||
|
accuracy: 'millisecond',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByRole('textbox')).toHaveValue('2024-04-11');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('second', async () => {
|
||||||
|
await renderAppOptions({
|
||||||
|
Component: UnixTimestamp,
|
||||||
|
value: 1712819630,
|
||||||
|
props: {
|
||||||
|
accuracy: 'second',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByRole('textbox')).toHaveValue('2024-04-11');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('string', async () => {
|
||||||
|
await renderAppOptions({
|
||||||
|
Component: UnixTimestamp,
|
||||||
|
value: '2024-04-11',
|
||||||
|
props: {
|
||||||
|
accuracy: 'millisecond',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByRole('textbox')).toHaveValue('2024-04-11');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('change', async () => {
|
||||||
|
const onChange = vitest.fn();
|
||||||
|
await renderAppOptions({
|
||||||
|
Component: UnixTimestamp,
|
||||||
|
value: '2024-04-11',
|
||||||
|
onChange,
|
||||||
|
props: {
|
||||||
|
accuracy: 'millisecond',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await userEvent.click(screen.getByRole('textbox'));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByRole('table')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
await userEvent.click(document.querySelector('td[title="2024-04-12"]'));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByRole('textbox')).toHaveValue('2024-04-12');
|
||||||
|
});
|
||||||
|
expect(onChange).toBeCalledWith(1712880000000);
|
||||||
|
});
|
||||||
|
|
||||||
it('read pretty', async () => {
|
it('read pretty', async () => {
|
||||||
const { container } = await renderReadPrettyApp({
|
const { container } = await renderReadPrettyApp({
|
||||||
Component: UnixTimestamp as any,
|
Component: UnixTimestamp,
|
||||||
value: '2024-04-11',
|
value: '2024-04-11',
|
||||||
|
props: {
|
||||||
|
accuracy: 'millisecond',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(screen.getByText('2024-04-11')).toBeInTheDocument();
|
expect(screen.getByText('2024-04-11')).toBeInTheDocument();
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
/**
|
|
||||||
* This file is part of the NocoBase (R) project.
|
|
||||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
||||||
* Authors: NocoBase Team.
|
|
||||||
*
|
|
||||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Database, mockDatabase } from '@nocobase/database';
|
|
||||||
|
|
||||||
describe('date only', () => {
|
|
||||||
let db: Database;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
db = mockDatabase({
|
|
||||||
timezone: '+08:00',
|
|
||||||
});
|
|
||||||
await db.clean({ drop: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
await db.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set date field with dateOnly', async () => {
|
|
||||||
db.collection({
|
|
||||||
name: 'tests',
|
|
||||||
fields: [{ name: 'date1', type: 'dateOnly' }],
|
|
||||||
});
|
|
||||||
|
|
||||||
await db.sync();
|
|
||||||
|
|
||||||
const item = await db.getRepository('tests').create({
|
|
||||||
values: {
|
|
||||||
date1: '2023-03-24',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(item.get('date1')).toBe('2023-03-24');
|
|
||||||
});
|
|
||||||
});
|
|
@ -11,69 +11,6 @@ import { mockDatabase } from '../';
|
|||||||
import { Database } from '../../database';
|
import { Database } from '../../database';
|
||||||
import { Repository } from '../../repository';
|
import { Repository } from '../../repository';
|
||||||
|
|
||||||
describe('timezone', () => {
|
|
||||||
let db: Database;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
db = mockDatabase({
|
|
||||||
timezone: '+08:00',
|
|
||||||
});
|
|
||||||
await db.clean({ drop: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
await db.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('timezone', () => {
|
|
||||||
test('custom', async () => {
|
|
||||||
db.collection({
|
|
||||||
name: 'tests',
|
|
||||||
timestamps: false,
|
|
||||||
fields: [{ name: 'date1', type: 'date', timezone: '+06:00' }],
|
|
||||||
});
|
|
||||||
|
|
||||||
await db.sync();
|
|
||||||
const repository = db.getRepository('tests');
|
|
||||||
const instance = await repository.create({ values: { date1: '2023-03-24 00:00:00' } });
|
|
||||||
const date1 = instance.get('date1');
|
|
||||||
expect(date1.toISOString()).toEqual('2023-03-23T18:00:00.000Z');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('client', async () => {
|
|
||||||
db.collection({
|
|
||||||
name: 'tests',
|
|
||||||
timestamps: false,
|
|
||||||
fields: [{ name: 'date1', type: 'date', timezone: 'client' }],
|
|
||||||
});
|
|
||||||
|
|
||||||
await db.sync();
|
|
||||||
const repository = db.getRepository('tests');
|
|
||||||
const instance = await repository.create({
|
|
||||||
values: { date1: '2023-03-24 01:00:00' },
|
|
||||||
context: {
|
|
||||||
timezone: '+01:00',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const date1 = instance.get('date1');
|
|
||||||
expect(date1.toISOString()).toEqual('2023-03-24T00:00:00.000Z');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('server', async () => {
|
|
||||||
db.collection({
|
|
||||||
name: 'tests',
|
|
||||||
fields: [{ name: 'date1', type: 'date', timezone: 'server' }],
|
|
||||||
});
|
|
||||||
|
|
||||||
await db.sync();
|
|
||||||
const repository = db.getRepository('tests');
|
|
||||||
const instance = await repository.create({ values: { date1: '2023-03-24 08:00:00' } });
|
|
||||||
const date1 = instance.get('date1');
|
|
||||||
expect(date1.toISOString()).toEqual('2023-03-24T00:00:00.000Z');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('date-field', () => {
|
describe('date-field', () => {
|
||||||
let db: Database;
|
let db: Database;
|
||||||
let repository: Repository;
|
let repository: Repository;
|
||||||
@ -93,80 +30,16 @@ describe('date-field', () => {
|
|||||||
await db.close();
|
await db.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set default to current time', async () => {
|
const createExpectToBe = async (key, actual, expected) => {
|
||||||
const c1 = db.collection({
|
const instance = await repository.create({
|
||||||
name: 'test11',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: 'date1',
|
|
||||||
type: 'date',
|
|
||||||
defaultToCurrentTime: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
await db.sync();
|
|
||||||
|
|
||||||
const instance = await c1.repository.create({});
|
|
||||||
const date1 = instance.get('date1');
|
|
||||||
expect(date1).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set to current time when update', async () => {
|
|
||||||
const c1 = db.collection({
|
|
||||||
name: 'test11',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: 'date1',
|
|
||||||
type: 'date',
|
|
||||||
onUpdateToCurrentTime: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'title',
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
await db.sync();
|
|
||||||
|
|
||||||
const instance = await c1.repository.create({
|
|
||||||
values: {
|
values: {
|
||||||
title: 'test',
|
[key]: actual,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
return expect(instance.get(key).toISOString()).toEqual(expected);
|
||||||
const date1Val = instance.get('date1');
|
};
|
||||||
expect(date1Val).toBeDefined();
|
|
||||||
|
|
||||||
console.log('update');
|
|
||||||
await c1.repository.update({
|
|
||||||
values: {
|
|
||||||
title: 'test2',
|
|
||||||
},
|
|
||||||
filter: {
|
|
||||||
id: instance.get('id'),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await instance.reload();
|
|
||||||
|
|
||||||
const date1Val2 = instance.get('date1');
|
|
||||||
expect(date1Val2).toBeDefined();
|
|
||||||
|
|
||||||
expect(date1Val2.getTime()).toBeGreaterThan(date1Val.getTime());
|
|
||||||
});
|
|
||||||
|
|
||||||
test('create', async () => {
|
test('create', async () => {
|
||||||
const createExpectToBe = async (key, actual, expected) => {
|
|
||||||
const instance = await repository.create({
|
|
||||||
values: {
|
|
||||||
[key]: actual,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return expect(instance.get(key).toISOString()).toEqual(expected);
|
|
||||||
};
|
|
||||||
|
|
||||||
// sqlite 时区不能自定义,只有 +00:00,postgres 和 mysql 可以自定义 DB_TIMEZONE
|
// sqlite 时区不能自定义,只有 +00:00,postgres 和 mysql 可以自定义 DB_TIMEZONE
|
||||||
await createExpectToBe('date1', '2023-03-24', '2023-03-24T00:00:00.000Z');
|
await createExpectToBe('date1', '2023-03-24', '2023-03-24T00:00:00.000Z');
|
||||||
await createExpectToBe('date1', '2023-03-24T16:00:00.000Z', '2023-03-24T16:00:00.000Z');
|
await createExpectToBe('date1', '2023-03-24T16:00:00.000Z', '2023-03-24T16:00:00.000Z');
|
||||||
|
@ -1,86 +0,0 @@
|
|||||||
/**
|
|
||||||
* This file is part of the NocoBase (R) project.
|
|
||||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
||||||
* Authors: NocoBase Team.
|
|
||||||
*
|
|
||||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Database, mockDatabase } from '@nocobase/database';
|
|
||||||
import moment from 'moment';
|
|
||||||
|
|
||||||
describe('unix timestamp field', () => {
|
|
||||||
let db: Database;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
db = mockDatabase();
|
|
||||||
await db.clean({ drop: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
await db.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set default to current time', async () => {
|
|
||||||
const c1 = db.collection({
|
|
||||||
name: 'test11',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: 'date1',
|
|
||||||
type: 'unixTimestamp',
|
|
||||||
defaultToCurrentTime: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
await db.sync();
|
|
||||||
|
|
||||||
const instance = await c1.repository.create({});
|
|
||||||
const date1 = instance.get('date1');
|
|
||||||
expect(date1).toBeDefined();
|
|
||||||
|
|
||||||
console.log(instance.toJSON());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set date value', async () => {
|
|
||||||
const c1 = db.collection({
|
|
||||||
name: 'test12',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: 'date1',
|
|
||||||
type: 'unixTimestamp',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
await db.sync();
|
|
||||||
|
|
||||||
await c1.repository.create({
|
|
||||||
values: {
|
|
||||||
date1: '2021-01-01T00:00:00Z',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const item = await c1.repository.findOne();
|
|
||||||
const val = item.get('date1');
|
|
||||||
const date = moment(val).utc().format('YYYY-MM-DD HH:mm:ss');
|
|
||||||
expect(date).toBe('2021-01-01 00:00:00');
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('timezone', () => {
|
|
||||||
test('custom', async () => {
|
|
||||||
db.collection({
|
|
||||||
name: 'tests',
|
|
||||||
timestamps: false,
|
|
||||||
fields: [{ name: 'date1', type: 'unixTimestamp', timezone: '+06:00' }],
|
|
||||||
});
|
|
||||||
|
|
||||||
await db.sync();
|
|
||||||
const repository = db.getRepository('tests');
|
|
||||||
const instance = await repository.create({ values: { date1: '2023-03-24 00:00:00' } });
|
|
||||||
const date1 = instance.get('date1');
|
|
||||||
expect(date1.toISOString()).toEqual('2023-03-23T18:00:00.000Z');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -34,6 +34,7 @@ import {
|
|||||||
import { SequelizeStorage, Umzug } from 'umzug';
|
import { SequelizeStorage, Umzug } from 'umzug';
|
||||||
import { Collection, CollectionOptions, RepositoryType } from './collection';
|
import { Collection, CollectionOptions, RepositoryType } from './collection';
|
||||||
import { CollectionFactory } from './collection-factory';
|
import { CollectionFactory } from './collection-factory';
|
||||||
|
import { CollectionGroupManager } from './collection-group-manager';
|
||||||
import { ImporterReader, ImportFileExtension } from './collection-importer';
|
import { ImporterReader, ImportFileExtension } from './collection-importer';
|
||||||
import DatabaseUtils from './database-utils';
|
import DatabaseUtils from './database-utils';
|
||||||
import ReferencesMap from './features/references-map';
|
import ReferencesMap from './features/references-map';
|
||||||
@ -41,6 +42,7 @@ import { referentialIntegrityCheck } from './features/referential-integrity-chec
|
|||||||
import { ArrayFieldRepository } from './field-repository/array-field-repository';
|
import { ArrayFieldRepository } from './field-repository/array-field-repository';
|
||||||
import * as FieldTypes from './fields';
|
import * as FieldTypes from './fields';
|
||||||
import { Field, FieldContext, RelationField } from './fields';
|
import { Field, FieldContext, RelationField } from './fields';
|
||||||
|
import { checkDatabaseVersion } from './helpers';
|
||||||
import { InheritedCollection } from './inherited-collection';
|
import { InheritedCollection } from './inherited-collection';
|
||||||
import InheritanceMap from './inherited-map';
|
import InheritanceMap from './inherited-map';
|
||||||
import { InterfaceManager } from './interface-manager';
|
import { InterfaceManager } from './interface-manager';
|
||||||
@ -219,9 +221,6 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
opts.rawTimezone = opts.timezone;
|
|
||||||
|
|
||||||
if (options.dialect === 'sqlite') {
|
if (options.dialect === 'sqlite') {
|
||||||
delete opts.timezone;
|
delete opts.timezone;
|
||||||
} else if (!opts.timezone) {
|
} else if (!opts.timezone) {
|
||||||
@ -849,8 +848,7 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
async checkVersion() {
|
async checkVersion() {
|
||||||
return true;
|
return await checkDatabaseVersion(this);
|
||||||
// return await checkDatabaseVersion(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -10,14 +10,8 @@
|
|||||||
import { DataTypes } from 'sequelize';
|
import { DataTypes } from 'sequelize';
|
||||||
import { BaseColumnFieldOptions, Field } from './field';
|
import { BaseColumnFieldOptions, Field } from './field';
|
||||||
|
|
||||||
const datetimeRegex = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
|
|
||||||
|
|
||||||
function isValidDatetime(str) {
|
|
||||||
return datetimeRegex.test(str);
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DateField extends Field {
|
export class DateField extends Field {
|
||||||
get dataType(): any {
|
get dataType() {
|
||||||
return DataTypes.DATE(3);
|
return DataTypes.DATE(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,59 +33,6 @@ export class DateField extends Field {
|
|||||||
return props.gmt;
|
return props.gmt;
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
|
||||||
const { name, defaultToCurrentTime, onUpdateToCurrentTime, timezone } = this.options;
|
|
||||||
|
|
||||||
this.resolveTimeZone = (context) => {
|
|
||||||
// @ts-ignore
|
|
||||||
const serverTimeZone = this.database.options.rawTimezone;
|
|
||||||
if (timezone === 'server') {
|
|
||||||
return serverTimeZone;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timezone === 'client') {
|
|
||||||
return context?.timezone || serverTimeZone;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timezone) {
|
|
||||||
return timezone;
|
|
||||||
}
|
|
||||||
|
|
||||||
return serverTimeZone;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.beforeSave = async (instance, options) => {
|
|
||||||
const value = instance.get(name);
|
|
||||||
|
|
||||||
if (!value && instance.isNewRecord && defaultToCurrentTime) {
|
|
||||||
instance.set(name, new Date());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onUpdateToCurrentTime) {
|
|
||||||
instance.set(name, new Date());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
setter(value, options) {
|
|
||||||
if (value === null) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
if (value instanceof Date) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === 'string' && isValidDatetime(value)) {
|
|
||||||
const dateTimezone = this.resolveTimeZone(options?.context);
|
|
||||||
const dateString = `${value} ${dateTimezone}`;
|
|
||||||
return new Date(dateString);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
bind() {
|
bind() {
|
||||||
super.bind();
|
super.bind();
|
||||||
|
|
||||||
@ -110,13 +51,6 @@ export class DateField extends Field {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
model.refreshAttributes();
|
model.refreshAttributes();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.on('beforeSave', this.beforeSave);
|
|
||||||
}
|
|
||||||
|
|
||||||
unbind() {
|
|
||||||
super.unbind();
|
|
||||||
this.off('beforeSave', this.beforeSave);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
/**
|
|
||||||
* This file is part of the NocoBase (R) project.
|
|
||||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
||||||
* Authors: NocoBase Team.
|
|
||||||
*
|
|
||||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { BaseColumnFieldOptions, Field } from './field';
|
|
||||||
import { DataTypes } from 'sequelize';
|
|
||||||
|
|
||||||
export class DateOnlyField extends Field {
|
|
||||||
get dataType(): any {
|
|
||||||
return DataTypes.DATEONLY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DateOnlyFieldOptions extends BaseColumnFieldOptions {
|
|
||||||
type: 'dateOnly';
|
|
||||||
}
|
|
@ -56,7 +56,7 @@ export abstract class Field {
|
|||||||
return this.options.type;
|
return this.options.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract get dataType(): any;
|
abstract get dataType();
|
||||||
|
|
||||||
isRelationField() {
|
isRelationField() {
|
||||||
return false;
|
return false;
|
||||||
@ -171,13 +171,11 @@ export abstract class Field {
|
|||||||
Object.assign(opts, { type: this.database.sequelize.normalizeDataType(this.dataType) });
|
Object.assign(opts, { type: this.database.sequelize.normalizeDataType(this.dataType) });
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(opts, this.additionalSequelizeOptions());
|
|
||||||
|
|
||||||
return opts;
|
return opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
additionalSequelizeOptions() {
|
isSqlite() {
|
||||||
return {};
|
return this.database.sequelize.getDialect() === 'sqlite';
|
||||||
}
|
}
|
||||||
|
|
||||||
typeToString() {
|
typeToString() {
|
||||||
|
@ -36,8 +36,6 @@ import { UUIDFieldOptions } from './uuid-field';
|
|||||||
import { VirtualFieldOptions } from './virtual-field';
|
import { VirtualFieldOptions } from './virtual-field';
|
||||||
import { NanoidFieldOptions } from './nanoid-field';
|
import { NanoidFieldOptions } from './nanoid-field';
|
||||||
import { EncryptionField } from './encryption-field';
|
import { EncryptionField } from './encryption-field';
|
||||||
import { UnixTimestampFieldOptions } from './unix-timestamp-field';
|
|
||||||
import { DateOnlyFieldOptions } from './date-only-field';
|
|
||||||
|
|
||||||
export * from './array-field';
|
export * from './array-field';
|
||||||
export * from './belongs-to-field';
|
export * from './belongs-to-field';
|
||||||
@ -45,7 +43,6 @@ export * from './belongs-to-many-field';
|
|||||||
export * from './boolean-field';
|
export * from './boolean-field';
|
||||||
export * from './context-field';
|
export * from './context-field';
|
||||||
export * from './date-field';
|
export * from './date-field';
|
||||||
export * from './date-only-field';
|
|
||||||
export * from './field';
|
export * from './field';
|
||||||
export * from './has-many-field';
|
export * from './has-many-field';
|
||||||
export * from './has-one-field';
|
export * from './has-one-field';
|
||||||
@ -64,7 +61,6 @@ export * from './uuid-field';
|
|||||||
export * from './virtual-field';
|
export * from './virtual-field';
|
||||||
export * from './nanoid-field';
|
export * from './nanoid-field';
|
||||||
export * from './encryption-field';
|
export * from './encryption-field';
|
||||||
export * from './unix-timestamp-field';
|
|
||||||
|
|
||||||
export type FieldOptions =
|
export type FieldOptions =
|
||||||
| BaseFieldOptions
|
| BaseFieldOptions
|
||||||
@ -85,8 +81,6 @@ export type FieldOptions =
|
|||||||
| SetFieldOptions
|
| SetFieldOptions
|
||||||
| TimeFieldOptions
|
| TimeFieldOptions
|
||||||
| DateFieldOptions
|
| DateFieldOptions
|
||||||
| DateOnlyFieldOptions
|
|
||||||
| UnixTimestampFieldOptions
|
|
||||||
| UidFieldOptions
|
| UidFieldOptions
|
||||||
| UUIDFieldOptions
|
| UUIDFieldOptions
|
||||||
| NanoidFieldOptions
|
| NanoidFieldOptions
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
/**
|
|
||||||
* This file is part of the NocoBase (R) project.
|
|
||||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
||||||
* Authors: NocoBase Team.
|
|
||||||
*
|
|
||||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { DataTypes } from 'sequelize';
|
|
||||||
import { DateField } from './date-field';
|
|
||||||
import { BaseColumnFieldOptions } from './field';
|
|
||||||
|
|
||||||
export class UnixTimestampField extends DateField {
|
|
||||||
get dataType() {
|
|
||||||
return DataTypes.BIGINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
additionalSequelizeOptions(): {} {
|
|
||||||
const { name } = this.options;
|
|
||||||
let { accuracy } = this.options;
|
|
||||||
|
|
||||||
if (this.options?.uiSchema['x-component-props']?.accuracy) {
|
|
||||||
accuracy = this.options?.uiSchema['x-component-props']?.accuracy;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!accuracy) {
|
|
||||||
accuracy = 'second';
|
|
||||||
}
|
|
||||||
|
|
||||||
let rationalNumber = 1000;
|
|
||||||
|
|
||||||
if (accuracy === 'millisecond') {
|
|
||||||
rationalNumber = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
get() {
|
|
||||||
const value = this.getDataValue(name);
|
|
||||||
if (value === null || value === undefined) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Date(value * rationalNumber);
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
if (value === null || value === undefined) {
|
|
||||||
this.setDataValue(name, value);
|
|
||||||
} else {
|
|
||||||
// date to unix timestamp
|
|
||||||
this.setDataValue(name, Math.floor(new Date(value).getTime() / rationalNumber));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UnixTimestampFieldOptions extends BaseColumnFieldOptions {
|
|
||||||
type: 'unix-timestamp';
|
|
||||||
}
|
|
@ -7,7 +7,7 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import lodash from 'lodash';
|
import lodash, { isPlainObject } from 'lodash';
|
||||||
import { Model as SequelizeModel, ModelStatic } from 'sequelize';
|
import { Model as SequelizeModel, ModelStatic } from 'sequelize';
|
||||||
import { Collection } from './collection';
|
import { Collection } from './collection';
|
||||||
import { Database } from './database';
|
import { Database } from './database';
|
||||||
@ -50,21 +50,6 @@ export class Model<TModelAttributes extends {} = any, TCreationAttributes extend
|
|||||||
return await runner.runSync(options);
|
return await runner.runSync(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
static callSetters(values, options) {
|
|
||||||
// map values
|
|
||||||
const result = {};
|
|
||||||
for (const key of Object.keys(values)) {
|
|
||||||
const field = this.collection.getField(key);
|
|
||||||
if (field && field.setter) {
|
|
||||||
result[key] = field.setter.call(field, values[key], options, values, key);
|
|
||||||
} else {
|
|
||||||
result[key] = values[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
public toChangedWithAssociations() {
|
public toChangedWithAssociations() {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -573,7 +573,7 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
|
|||||||
underscored: this.collection.options.underscored,
|
underscored: this.collection.options.underscored,
|
||||||
});
|
});
|
||||||
|
|
||||||
const values = (this.model as typeof Model).callSetters(guard.sanitize(options.values || {}), options);
|
const values = guard.sanitize(options.values || {});
|
||||||
|
|
||||||
const instance = await this.model.create<any>(values, {
|
const instance = await this.model.create<any>(values, {
|
||||||
...options,
|
...options,
|
||||||
@ -645,7 +645,7 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
|
|||||||
|
|
||||||
const guard = UpdateGuard.fromOptions(this.model, { ...options, underscored: this.collection.options.underscored });
|
const guard = UpdateGuard.fromOptions(this.model, { ...options, underscored: this.collection.options.underscored });
|
||||||
|
|
||||||
const values = (this.model as typeof Model).callSetters(guard.sanitize(options.values || {}), options);
|
const values = guard.sanitize(options.values);
|
||||||
|
|
||||||
// NOTE:
|
// NOTE:
|
||||||
// 1. better to be moved to separated API like bulkUpdate/updateMany
|
// 1. better to be moved to separated API like bulkUpdate/updateMany
|
||||||
|
@ -18,8 +18,8 @@ const postgres = {
|
|||||||
name: 'string',
|
name: 'string',
|
||||||
|
|
||||||
smallint: ['integer', 'sort'],
|
smallint: ['integer', 'sort'],
|
||||||
integer: ['integer', 'unixTimestamp', 'sort'],
|
integer: ['integer', 'sort'],
|
||||||
bigint: ['bigInt', 'unixTimestamp', 'sort'],
|
bigint: ['bigInt', 'sort'],
|
||||||
decimal: 'decimal',
|
decimal: 'decimal',
|
||||||
numeric: 'float',
|
numeric: 'float',
|
||||||
real: 'float',
|
real: 'float',
|
||||||
@ -61,11 +61,11 @@ const mysql = {
|
|||||||
text: 'text',
|
text: 'text',
|
||||||
mediumtext: 'text',
|
mediumtext: 'text',
|
||||||
longtext: 'text',
|
longtext: 'text',
|
||||||
int: ['integer', 'unixTimestamp', 'sort'],
|
int: ['integer', 'sort'],
|
||||||
'int unsigned': ['integer', 'unixTimestamp', 'sort'],
|
'int unsigned': ['integer', 'sort'],
|
||||||
integer: ['integer', 'unixTimestamp', 'sort'],
|
integer: ['integer', 'sort'],
|
||||||
bigint: ['bigInt', 'unixTimestamp', 'sort'],
|
bigint: ['bigInt', 'sort'],
|
||||||
'bigint unsigned': ['bigInt', 'unixTimestamp', 'sort'],
|
'bigint unsigned': ['bigInt', 'sort'],
|
||||||
float: 'float',
|
float: 'float',
|
||||||
double: 'float',
|
double: 'float',
|
||||||
boolean: 'boolean',
|
boolean: 'boolean',
|
||||||
|
@ -213,19 +213,22 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
public perfHistograms = new Map<string, RecordableHistogram>();
|
public perfHistograms = new Map<string, RecordableHistogram>();
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
public syncManager: SyncManager;
|
|
||||||
public requestLogger: Logger;
|
|
||||||
protected plugins = new Map<string, Plugin>();
|
protected plugins = new Map<string, Plugin>();
|
||||||
protected _appSupervisor: AppSupervisor = AppSupervisor.getInstance();
|
protected _appSupervisor: AppSupervisor = AppSupervisor.getInstance();
|
||||||
|
protected _started: Date | null = null;
|
||||||
private _authenticated = false;
|
private _authenticated = false;
|
||||||
private _maintaining = false;
|
private _maintaining = false;
|
||||||
private _maintainingCommandStatus: MaintainingCommandStatus;
|
private _maintainingCommandStatus: MaintainingCommandStatus;
|
||||||
private _maintainingStatusBeforeCommand: MaintainingCommandStatus | null;
|
private _maintainingStatusBeforeCommand: MaintainingCommandStatus | null;
|
||||||
private _actionCommand: Command;
|
private _actionCommand: Command;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public syncManager: SyncManager;
|
||||||
|
public requestLogger: Logger;
|
||||||
private sqlLogger: Logger;
|
private sqlLogger: Logger;
|
||||||
|
protected _logger: SystemLogger;
|
||||||
|
|
||||||
constructor(public options: ApplicationOptions) {
|
constructor(public options: ApplicationOptions) {
|
||||||
super();
|
super();
|
||||||
@ -238,8 +241,6 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _started: Date | null = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @experimental
|
* @experimental
|
||||||
*/
|
*/
|
||||||
@ -247,8 +248,6 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
return this._started;
|
return this._started;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _logger: SystemLogger;
|
|
||||||
|
|
||||||
get logger() {
|
get logger() {
|
||||||
return this._logger;
|
return this._logger;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user