From dcaad79370b18ae17747e7fb1a1eb146783f88a1 Mon Sep 17 00:00:00 2001 From: Katherine Date: Sun, 29 Sep 2024 17:49:44 +0800 Subject: [PATCH] feat: date type fields support setting the picker type (#5271) * refactor: date field support picker * refactor: date field support picker * refactor: date time field * refactor: date time field * refactor: locale improve * refactor: defaultValue * refactor: datetime field * refactor: remove week * fix: bug * fix: bug * fix: bug * fix: test * fix: test * fix: test * fix: test * fix: test * refactor: test * refactor: test * fix: bug * fix: bug * fix: bug * refactor: expiresRadio * refactor: datepicker * fix: bug * fix: bug * fix: bug * fix: test * refactor: change operator * refactor: change picker * refactor: datetime operator schema * refactor: filterWithPicker * refactor: support linkage rule * fix: bug * refactor: useFilterFormItemInitializerFields * refactor: useFilterFormItemInitializerFields * fix: default value for datetime * fix: bug * fix: filter date time * fix: filter date time * fix: bug * fix: bug * refactor: datePicker RangePicker * refactor: datePicker RangePicker * style: date picker style improve * fix: bug * fix: bug * fix: bug * fix: default value * fix: bulk edit datetime field * fix: picker support third party data source * fix: test --- .../collection-manager/interfaces/dateOnly.ts | 24 +-- .../interfaces/properties/index.ts | 97 +++++++++++- .../interfaces/properties/operators.ts | 49 +++++- .../interfaces/unixTimestamp.tsx | 3 +- .../CollectionFieldInterface.ts | 11 ++ packages/core/client/src/locale/zh-CN.json | 5 +- .../datePickerComponentFieldSettings.tsx | 18 +++ .../antd/date-picker/DatePicker.tsx | 142 +++++++++++++++++- .../antd/date-picker/ReadPretty.tsx | 9 +- .../__tests__/date-picker.test.tsx | 3 - .../__tests__/mapDatePicker.test.ts | 26 ++-- .../antd/date-picker/__tests__/util.test.ts | 8 +- .../schema-component/antd/date-picker/util.ts | 81 ++++++++-- .../antd/expiresRadio/index.tsx} | 47 ++++-- .../antd/filter/DynamicComponent.tsx | 1 + .../antd/form-item/SchemaSettingOptions.tsx | 11 +- .../client/src/schema-component/antd/index.ts | 1 + .../antd/rich-text/RichText.tsx | 7 +- .../antd/unix-timestamp/UnixTimestamp.tsx | 1 - .../schema-component/common/utils/logic.js | 121 +++++++++++---- .../schema-component/common/utils/uitls.tsx | 18 ++- .../client/src/schema-initializer/utils.ts | 8 +- .../SchemaSettingsDateFormat.tsx | 77 +++++++++- .../SchemaSettingsDefaultValue.tsx | 6 +- .../schema-settings/SchemaSettingsPlugin.ts | 6 +- packages/core/utils/src/date.ts | 46 ++++-- .../src/client/component/BulkEditField.tsx | 7 +- .../CollectionsManager/ConfigurationTable.tsx | 2 + .../Configuration/ConfigurationTable.tsx | 5 +- 29 files changed, 677 insertions(+), 163 deletions(-) rename packages/core/client/src/{schema-settings/DateFormat/ExpiresRadio.tsx => schema-component/antd/expiresRadio/index.tsx} (71%) diff --git a/packages/core/client/src/collection-manager/interfaces/dateOnly.ts b/packages/core/client/src/collection-manager/interfaces/dateOnly.ts index defeb80b16..7bc57cbb60 100644 --- a/packages/core/client/src/collection-manager/interfaces/dateOnly.ts +++ b/packages/core/client/src/collection-manager/interfaces/dateOnly.ts @@ -31,26 +31,12 @@ export class DateFieldInterface extends CollectionFieldInterface { hasDefaultValue = true; properties = { ...defaultProps, - 'uiSchema.x-component-props.dateFormat': { - type: 'string', - title: '{{t("Date format")}}', - 'x-component': 'Radio.Group', + ...dateTimeProps, + 'uiSchema.x-component-props.showTime': { + type: 'boolean', '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', - }, - ], + 'x-component': 'Checkbox', + 'x-visible': false, }, }; filterable = { diff --git a/packages/core/client/src/collection-manager/interfaces/properties/index.ts b/packages/core/client/src/collection-manager/interfaces/properties/index.ts index edb01ef255..82d08c2ad9 100644 --- a/packages/core/client/src/collection-manager/interfaces/properties/index.ts +++ b/packages/core/client/src/collection-manager/interfaces/properties/index.ts @@ -10,6 +10,8 @@ import { Field } from '@formily/core'; import { ISchema } from '@formily/react'; import { uid } from '@formily/shared'; +import { css } from '@emotion/css'; +import { DateFormatCom } from '../../../schema-component/antd/expiresRadio'; export * as operators from './operators'; export const type: ISchema = { @@ -225,26 +227,88 @@ export const reverseFieldProperties: Record = { }; export const dateTimeProps: { [key: string]: ISchema } = { + 'uiSchema.x-component-props.picker': { + type: 'string', + title: '{{t("Picker")}}', + 'x-decorator': 'FormItem', + 'x-component': 'Radio.Group', + default: 'date', + enum: [ + { + label: '{{t("Date")}}', + value: 'date', + }, + // { + // label: '{{t("Week")}}', + // value: 'week', + // }, + { + label: '{{t("Month")}}', + value: 'month', + }, + { + label: '{{t("Quarter")}}', + value: 'quarter', + }, + { + label: '{{t("Year")}}', + value: 'year', + }, + ], + }, + 'uiSchema.x-component-props.dateFormat': { type: 'string', title: '{{t("Date format")}}', - 'x-component': 'Radio.Group', 'x-decorator': 'FormItem', + 'x-component': 'ExpiresRadio', + 'x-decorator-props': {}, + 'x-component-props': { + className: css` + .ant-radio-wrapper { + display: flex; + margin: 5px 0px; + } + `, + defaultValue: 'dddd', + formats: ['MMMM Do YYYY', 'YYYY-MM-DD', 'MM/DD/YY', 'YYYY/MM/DD', 'DD/MM/YYYY'], + }, default: 'YYYY-MM-DD', enum: [ { - label: '{{t("Year/Month/Day")}}', - value: 'YYYY/MM/DD', + label: DateFormatCom({ format: 'MMMM Do YYYY' }), + value: 'MMMM Do YYYY', }, { - label: '{{t("Year-Month-Day")}}', + label: DateFormatCom({ format: 'YYYY-MM-DD' }), value: 'YYYY-MM-DD', }, { - label: '{{t("Day/Month/Year")}}', + label: DateFormatCom({ format: 'MM/DD/YY' }), + value: 'MM/DD/YY', + }, + { + label: DateFormatCom({ format: 'YYYY/MM/DD' }), + value: 'YYYY/MM/DD', + }, + { + label: DateFormatCom({ format: 'DD/MM/YYYY' }), value: 'DD/MM/YYYY', }, + { + label: 'custom', + value: 'custom', + }, ], + 'x-reactions': { + dependencies: ['uiSchema.x-component-props.picker'], + fulfill: { + state: { + value: `{{ getPickerFormat($deps[0])}}`, + componentProps: { picker: `{{$deps[0]}}` }, + }, + }, + }, }, 'uiSchema.x-component-props.showTime': { type: 'boolean', @@ -258,6 +322,21 @@ export const dateTimeProps: { [key: string]: ISchema } = { f.value='HH:mm:ss' }); }}}`, + { + dependencies: ['uiSchema.x-component-props.picker'], + when: '{{$deps[0]==="date"}}', + fulfill: { + state: { + hidden: false, + }, + }, + otherwise: { + state: { + hidden: true, + value: false, + }, + }, + }, ], }, 'uiSchema.x-component-props.timeFormat': { @@ -276,6 +355,14 @@ export const dateTimeProps: { [key: string]: ISchema } = { value: 'HH:mm:ss', }, ], + 'x-reactions': { + dependencies: ['uiSchema.x-component-props.showTime'], + fulfill: { + state: { + hidden: `{{ !$deps[0] }}`, + }, + }, + }, }, }; diff --git a/packages/core/client/src/collection-manager/interfaces/properties/operators.ts b/packages/core/client/src/collection-manager/interfaces/properties/operators.ts index 1e031153c0..a79128316b 100644 --- a/packages/core/client/src/collection-manager/interfaces/properties/operators.ts +++ b/packages/core/client/src/collection-manager/interfaces/properties/operators.ts @@ -60,13 +60,48 @@ export const object = [ ]; export const datetime = [ - { label: "{{ t('is') }}", value: '$dateOn', selected: true }, - { label: "{{ t('is not') }}", value: '$dateNotOn' }, - { label: "{{ t('is before') }}", value: '$dateBefore' }, - { label: "{{ t('is after') }}", value: '$dateAfter' }, - { label: "{{ t('is on or after') }}", value: '$dateNotBefore' }, - { label: "{{ t('is on or before') }}", value: '$dateNotAfter' }, - { label: "{{ t('is between') }}", value: '$dateBetween', schema: { 'x-component': 'DatePicker.RangePicker' } }, + { + label: "{{ t('is') }}", + value: '$dateOn', + selected: true, + schema: { 'x-component': 'DatePicker.FilterWithPicker' }, + onlyFilterAction: true, //schema 仅在Filter.Action生效,筛选表单中不生效 + }, + { + label: "{{ t('is not') }}", + value: '$dateNotOn', + schema: { 'x-component': 'DatePicker.FilterWithPicker' }, + onlyFilterAction: true, + }, + { + label: "{{ t('is before') }}", + value: '$dateBefore', + schema: { 'x-component': 'DatePicker.FilterWithPicker' }, + onlyFilterAction: true, + }, + { + label: "{{ t('is after') }}", + value: '$dateAfter', + schema: { 'x-component': 'DatePicker.FilterWithPicker' }, + onlyFilterAction: true, + }, + { + label: "{{ t('is on or after') }}", + value: '$dateNotBefore', + schema: { 'x-component': 'DatePicker.FilterWithPicker' }, + onlyFilterAction: true, + }, + { + label: "{{ t('is on or before') }}", + value: '$dateNotAfter', + schema: { 'x-component': 'DatePicker.FilterWithPicker' }, + onlyFilterAction: true, + }, + { + label: "{{ t('is between') }}", + value: '$dateBetween', + schema: { 'x-component': 'DatePicker.RangePicker' }, + }, { label: "{{ t('is empty') }}", value: '$empty', noValue: true }, { label: "{{ t('is not empty') }}", value: '$notEmpty', noValue: true }, ]; diff --git a/packages/core/client/src/collection-manager/interfaces/unixTimestamp.tsx b/packages/core/client/src/collection-manager/interfaces/unixTimestamp.tsx index 5b0de8c91a..8166174c48 100644 --- a/packages/core/client/src/collection-manager/interfaces/unixTimestamp.tsx +++ b/packages/core/client/src/collection-manager/interfaces/unixTimestamp.tsx @@ -8,7 +8,7 @@ */ import { CollectionFieldInterface } from '../../data-source/collection-field-interface/CollectionFieldInterface'; -import { defaultProps, operators } from './properties'; +import { defaultProps, operators, dateTimeProps } from './properties'; export class UnixTimestampFieldInterface extends CollectionFieldInterface { name = 'unixTimestamp'; type = 'object'; @@ -34,6 +34,7 @@ export class UnixTimestampFieldInterface extends CollectionFieldInterface { hasDefaultValue = false; properties = { ...defaultProps, + ...dateTimeProps, accuracy: { type: 'string', title: '{{t("Accuracy")}}', diff --git a/packages/core/client/src/data-source/collection-field-interface/CollectionFieldInterface.ts b/packages/core/client/src/data-source/collection-field-interface/CollectionFieldInterface.ts index a0c616c7e3..befd3def02 100644 --- a/packages/core/client/src/data-source/collection-field-interface/CollectionFieldInterface.ts +++ b/packages/core/client/src/data-source/collection-field-interface/CollectionFieldInterface.ts @@ -106,6 +106,7 @@ export abstract class CollectionFieldInterface { 'uiSchema.x-component-props.showTime', 'uiSchema.x-component-props.dateFormat', 'uiSchema.x-component-props.timeFormat', + 'uiSchema.x-component-props.picker', ], fulfill: { state: { @@ -114,10 +115,20 @@ export abstract class CollectionFieldInterface { showTime: '{{$deps[1]}}', dateFormat: '{{$deps[2]}}', timeFormat: '{{$deps[3]}}', + picker: '{{$deps[4]}}', }, }, }, }, + { + // 当 picker 改变时,清空 defaultValue + dependencies: ['uiSchema.x-component-props.picker'], + fulfill: { + state: { + value: null, + }, + }, + }, { dependencies: ['primaryKey', 'unique', 'autoIncrement', 'defaultToCurrentTime'], when: '{{$deps[0]||$deps[1]||$deps[2]||$deps[3]}}', diff --git a/packages/core/client/src/locale/zh-CN.json b/packages/core/client/src/locale/zh-CN.json index 88dd3a3a48..b5f16d5eca 100644 --- a/packages/core/client/src/locale/zh-CN.json +++ b/packages/core/client/src/locale/zh-CN.json @@ -997,5 +997,8 @@ "Are you sure you want to perform the Submit action?": "你确定执行提交操作吗?", "Perform the Trigger workflow": "执行触发工作流", "Are you sure you want to perform the Trigger workflow action?": "你确定执行触发工作流吗?", - "Ellipsis overflow content": "省略超出长度的内容" + "Ellipsis overflow content": "省略超出长度的内容", + "Picker": "选择器", + "Quarter":"季度", + "Switching the picker, the value and default value will be cleared":"切换选择器时,字段的值和默认值将会被清空" } diff --git a/packages/core/client/src/modules/fields/component/DatePicker/datePickerComponentFieldSettings.tsx b/packages/core/client/src/modules/fields/component/DatePicker/datePickerComponentFieldSettings.tsx index 4e243074e8..905b26ac7f 100644 --- a/packages/core/client/src/modules/fields/component/DatePicker/datePickerComponentFieldSettings.tsx +++ b/packages/core/client/src/modules/fields/component/DatePicker/datePickerComponentFieldSettings.tsx @@ -29,3 +29,21 @@ export const datePickerComponentFieldSettings = new SchemaSettings({ }, ], }); + +export const rangePickerPickerComponentFieldSettings = new SchemaSettings({ + name: 'fieldSettings:component:DatePicker.RangePicker', + 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 02955e3314..6424155c08 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 @@ -7,14 +7,17 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { connect, mapProps, mapReadPretty } from '@formily/react'; -import { DatePicker as AntdDatePicker, DatePickerProps as AntdDatePickerProps } from 'antd'; +import { connect, mapProps, mapReadPretty, useField, useFieldSchema } from '@formily/react'; +import { DatePicker as AntdDatePicker, DatePickerProps as AntdDatePickerProps, Space, Select } from 'antd'; import { RangePickerProps } from 'antd/es/date-picker'; import dayjs from 'dayjs'; -import React from 'react'; +import React, { useState, useContext } from 'react'; +import { getPickerFormat } from '@nocobase/utils/client'; import { useTranslation } from 'react-i18next'; import { ReadPretty, ReadPrettyComposed } from './ReadPretty'; -import { getDateRanges, mapDatePicker, mapRangePicker } from './util'; +import { getDateRanges, mapDatePicker, mapRangePicker, inferPickerType } from './util'; +import { FilterContext } from '../../antd/filter'; +import { useCompile } from '../../'; interface IDatePickerProps { utc?: boolean; @@ -23,6 +26,7 @@ interface IDatePickerProps { type ComposedDatePicker = React.FC & { ReadPretty?: ReadPrettyComposed['DatePicker']; RangePicker?: ComposedRangePicker; + FilterWithPicker?: any; }; type ComposedRangePicker = React.FC & { @@ -60,9 +64,14 @@ export const DatePicker: ComposedDatePicker = (props: any) => { DatePicker.ReadPretty = ReadPretty.DatePicker; DatePicker.RangePicker = function RangePicker(props: any) { + const { value, picker = 'date', format } = props; const { t } = useTranslation(); + const fieldSchema = useFieldSchema(); + const field: any = useField(); const { utc = true } = useDatePickerContext(); const rangesValue = getDateRanges(); + const compile = useCompile(); + const ctx: any = useContext(FilterContext); // 在筛选按钮中使用 const presets = [ { label: t('Today'), value: rangesValue.today }, { label: t('Last week'), value: rangesValue.lastWeek }, @@ -84,13 +93,138 @@ DatePicker.RangePicker = function RangePicker(props: any) { { label: t('Last 90 days'), value: rangesValue.last90Days }, { label: t('Next 90 days'), value: rangesValue.next90Days }, ]; + + const targetPicker = value ? inferPickerType(value?.[0]) : picker; + const targetFormat = getPickerFormat(targetPicker) || format; const newProps: any = { utc, presets, ...props, + format: targetFormat, + picker: targetPicker, showTime: props.showTime ? { defaultValue: [dayjs('00:00:00', 'HH:mm:ss'), dayjs('00:00:00', 'HH:mm:ss')] } : false, }; + const [stateProps, setStateProps] = useState(newProps); + + if (ctx) { + return ( + + { + const format = getPickerFormat(value); + field.setComponentProps({ + picker: value, + format, + }); + newProps.picker = value; + newProps.format = format; + setStateProps(newProps); + fieldSchema['x-component-props'] = { + ...props, + picker: value, + format, + }; + field.value = null; + }} + /> + + + ); +}; + export default DatePicker; diff --git a/packages/core/client/src/schema-component/antd/date-picker/ReadPretty.tsx b/packages/core/client/src/schema-component/antd/date-picker/ReadPretty.tsx index f05fc9c312..58e1e60e22 100644 --- a/packages/core/client/src/schema-component/antd/date-picker/ReadPretty.tsx +++ b/packages/core/client/src/schema-component/antd/date-picker/ReadPretty.tsx @@ -34,16 +34,15 @@ export interface ReadPrettyDatePickerProps extends Str2momentOptions, GetDefault showTime?: boolean; } -ReadPretty.DatePicker = function DatePicker(props) { +ReadPretty.DatePicker = function DatePicker(props: any) { + const { value, picker = 'date' } = props; const prefixCls = usePrefixCls('description-date-picker', props); - - if (!props.value) { + if (!value) { return
; } - const getLabels = () => { const format = getDefaultFormat(props) as string; - const m = str2moment(props.value, props); + const m = str2moment(value, props); const labels = dayjs.isDayjs(m) ? m.format(format) : ''; return isArr(labels) ? labels.join('~') : labels; }; diff --git a/packages/core/client/src/schema-component/antd/date-picker/__tests__/date-picker.test.tsx b/packages/core/client/src/schema-component/antd/date-picker/__tests__/date-picker.test.tsx index e89323d675..6405a5ec1b 100644 --- a/packages/core/client/src/schema-component/antd/date-picker/__tests__/date-picker.test.tsx +++ b/packages/core/client/src/schema-component/antd/date-picker/__tests__/date-picker.test.tsx @@ -232,9 +232,6 @@ describe('RangePicker', () => { expect( screen.getByText(currentDateString.replace(/-/g, '/'), { selector: '.ant-description-date-picker' }), ).toBeInTheDocument(); - - // Value - expect(screen.getByText(`${currentDateString}T00:00:00.000Z`)).toBeInTheDocument(); }); }); diff --git a/packages/core/client/src/schema-component/antd/date-picker/__tests__/mapDatePicker.test.ts b/packages/core/client/src/schema-component/antd/date-picker/__tests__/mapDatePicker.test.ts index f966ee3a3e..41a8feef50 100644 --- a/packages/core/client/src/schema-component/antd/date-picker/__tests__/mapDatePicker.test.ts +++ b/packages/core/client/src/schema-component/antd/date-picker/__tests__/mapDatePicker.test.ts @@ -62,7 +62,7 @@ describe('mapDatePicker', () => { }; const result = mapDatePicker()(props); result.onChange(dayjs.utc('2022-02-22 22:22:22')); - expect(props.onChange).toHaveBeenCalledWith('2022-02-22T22:22:22.000Z'); + expect(props.onChange).toHaveBeenCalledWith('2022-02-22 00:00:00'); }); it('should call onChange with correct value when showTime is true and gmt is false', () => { @@ -74,7 +74,7 @@ describe('mapDatePicker', () => { const result = mapDatePicker()(props); const m = dayjs('2022-02-22 22:22:22'); result.onChange(m); - expect(props.onChange).toHaveBeenCalledWith(m.toISOString()); + expect(props.onChange).toHaveBeenCalledWith('2022-02-22 00:00:00'); }); it('should call onChange with correct value when showTime is false and gmt is true', () => { @@ -85,7 +85,7 @@ describe('mapDatePicker', () => { }; const result = mapDatePicker()(props); result.onChange(dayjs.utc('2022-02-22')); - expect(props.onChange).toHaveBeenCalledWith('2022-02-22T00:00:00.000Z'); + expect(props.onChange).toHaveBeenCalledWith('2022-02-22'); }); it('should call onChange with correct value when showTime is false and gmt is false', () => { @@ -97,7 +97,7 @@ describe('mapDatePicker', () => { const result = mapDatePicker()(props); const m = dayjs('2022-02-22'); result.onChange(m); - expect(props.onChange).toHaveBeenCalledWith(m.toISOString()); + expect(props.onChange).toHaveBeenCalledWith('2022-02-22'); }); it('should call onChange with correct value when picker is year and gmt is true', () => { @@ -108,7 +108,7 @@ describe('mapDatePicker', () => { }; const result = mapDatePicker()(props); result.onChange(dayjs.utc('2022-01-01T00:00:00.000Z')); - expect(props.onChange).toHaveBeenCalledWith('2022-01-01T00:00:00.000Z'); + expect(props.onChange).toHaveBeenCalledWith('2022-01-01'); }); it('should call onChange with correct value when picker is year and gmt is false', () => { @@ -120,7 +120,7 @@ describe('mapDatePicker', () => { const result = mapDatePicker()(props); const m = dayjs('2022-02-01 00:00:00'); result.onChange(m); - expect(props.onChange).toHaveBeenCalledWith(m.startOf('year').toISOString()); + expect(props.onChange).toHaveBeenCalledWith('2022-01-01'); }); it('should call onChange with correct value when picker is month and gmt is true', () => { @@ -131,7 +131,7 @@ describe('mapDatePicker', () => { }; const result = mapDatePicker()(props); result.onChange(dayjs.utc('2022-02-22T00:00:00.000Z')); - expect(props.onChange).toHaveBeenCalledWith('2022-02-01T00:00:00.000Z'); + expect(props.onChange).toHaveBeenCalledWith('2022-02-01'); }); it('should call onChange with correct value when picker is month and gmt is false', () => { @@ -143,7 +143,7 @@ describe('mapDatePicker', () => { const result = mapDatePicker()(props); const m = dayjs('2022-02-01 00:00:00'); result.onChange(m); - expect(props.onChange).toHaveBeenCalledWith(m.startOf('month').toISOString()); + expect(props.onChange).toHaveBeenCalledWith('2022-02-01'); }); it('should call onChange with correct value when picker is quarter and gmt is true', () => { @@ -154,7 +154,7 @@ describe('mapDatePicker', () => { }; const result = mapDatePicker()(props); result.onChange(dayjs.utc('2022-02-22T00:00:00.000Z')); - expect(props.onChange).toHaveBeenCalledWith('2022-01-01T00:00:00.000Z'); + expect(props.onChange).toHaveBeenCalledWith('2022-01-01'); }); it('should call onChange with correct value when picker is quarter and gmt is false', () => { @@ -166,7 +166,7 @@ describe('mapDatePicker', () => { const result = mapDatePicker()(props); const m = dayjs('2022-02-01 00:00:00'); result.onChange(m); - expect(props.onChange).toHaveBeenCalledWith(m.startOf('quarter').toISOString()); + expect(props.onChange).toHaveBeenCalledWith('2022-01-01'); }); it('should call onChange with correct value when picker is week and gmt is true', () => { @@ -178,7 +178,7 @@ describe('mapDatePicker', () => { const result = mapDatePicker()(props); const m = dayjs.utc('2022-02-21T00:00:00.000Z'); result.onChange(m); - expect(props.onChange).toHaveBeenCalledWith(m.startOf('week').add(1, 'day').toISOString()); + expect(props.onChange).toHaveBeenCalledWith('2022-02-20'); }); it('should call onChange with correct value when picker is week and gmt is false', () => { @@ -190,7 +190,7 @@ describe('mapDatePicker', () => { const result = mapDatePicker()(props); const m = dayjs('2022-02-21 00:00:00'); result.onChange(m); - expect(props.onChange).toHaveBeenCalledWith(m.startOf('week').add(1, 'day').toISOString()); + expect(props.onChange).toHaveBeenCalledWith('2022-02-20'); }); it('should call onChange with correct value when utc is false', () => { @@ -202,7 +202,7 @@ describe('mapDatePicker', () => { }; const result = mapDatePicker()(props); result.onChange(dayjs('2022-02-22 22:22:22')); - expect(props.onChange).toHaveBeenCalledWith('2022-02-22 22:22:22'); + expect(props.onChange).toHaveBeenCalledWith('2022-02-22 00:00:00'); }); it('should call onChange with correct value when picker is year and utc is false', () => { diff --git a/packages/core/client/src/schema-component/antd/date-picker/__tests__/util.test.ts b/packages/core/client/src/schema-component/antd/date-picker/__tests__/util.test.ts index 5ef7d817f5..93387abd60 100644 --- a/packages/core/client/src/schema-component/antd/date-picker/__tests__/util.test.ts +++ b/packages/core/client/src/schema-component/antd/date-picker/__tests__/util.test.ts @@ -29,7 +29,7 @@ describe('str2moment', () => { }); test('picker is month', async () => { - const m = str2moment('2022-06-01T00:00:00.000Z', { picker: 'month' }); + const m = str2moment('2022-06-01T00:00:00.000Z', { picker: 'month', gmt: true }); expect(m.format('YYYY-MM-DD HH:mm:ss')).toBe('2022-06-01 00:00:00'); }); }); @@ -130,10 +130,10 @@ describe('moment2str', () => { expect(str).toBe('2023-06-01T00:00:00.000Z'); }); - test('picker is month', () => { + test('picker is month gmt is false', () => { const m = dayjs('2023-06-21 10:10:00'); - const str = moment2str(m, { picker: 'month', gmt: true }); - expect(str).toBe('2023-06-01T00:00:00.000Z'); + const str = moment2str(m, { picker: 'month', gmt: false }); + expect(str).toBe(dayjs('2023-06-01 00:00:00').toISOString()); }); test('picker is week, gmt is false', () => { diff --git a/packages/core/client/src/schema-component/antd/date-picker/util.ts b/packages/core/client/src/schema-component/antd/date-picker/util.ts index 5bb825c817..9a8d2b66a3 100644 --- a/packages/core/client/src/schema-component/antd/date-picker/util.ts +++ b/packages/core/client/src/schema-component/antd/date-picker/util.ts @@ -7,11 +7,11 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { getDefaultFormat, str2moment, toGmt, toLocal } from '@nocobase/utils/client'; +import { getDefaultFormat, str2moment, toGmt, toLocal, getPickerFormat } from '@nocobase/utils/client'; import type { Dayjs } from 'dayjs'; import dayjs from 'dayjs'; -const toStringByPicker = (value, picker, timezone: 'gmt' | 'local') => { +const toStringByPicker = (value, picker = 'date', timezone: 'gmt' | 'local') => { if (!dayjs.isDayjs(value)) return value; if (timezone === 'local') { const offset = new Date().getTimezoneOffset(); @@ -57,7 +57,7 @@ export interface Moment2strOptions { } export const moment2str = (value?: Dayjs | null, options: Moment2strOptions = {}) => { - const { showTime, gmt, picker, utc = true } = options; + const { showTime, gmt, picker = 'date', utc = true } = options; if (!value) { return value; } @@ -74,9 +74,39 @@ export const moment2str = (value?: Dayjs | null, options: Moment2strOptions = {} return toLocalByPicker(value, picker); }; +const handleChangeOnFilter = (value, picker, showTime) => { + const format = showTime ? 'YYYY-MM-DD HH:mm:ss' : getPickerFormat(picker); + if (value) { + return value.format(format); + } + return value; +}; + +const handleChangeOnForm = (value, dateOnly, utc, picker, showTime, gmt) => { + const format = showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD'; + if (!value) { + return value; + } + if (dateOnly) { + return dayjs(value).startOf(picker).format('YYYY-MM-DD'); + } + if (utc) { + if (gmt) { + return toGmt(value); + } + if (picker !== 'date') { + return dayjs(value).startOf(picker).toISOString(); + } + const formattedDate = dayjs(value).format(format); + return dayjs(formattedDate).toISOString(); + } + return dayjs(value).startOf(picker).format(format); +}; + export const mapDatePicker = function () { return (props: any) => { - const format = getDefaultFormat(props) as any; + const { dateOnly, showTime, picker = 'date', utc, gmt, underFilter } = props; + const format = getDefaultFormat(props); const onChange = props.onChange; return { ...props, @@ -84,13 +114,10 @@ export const mapDatePicker = function () { value: str2moment(props.value, props), onChange: (value: Dayjs | null, dateString) => { if (onChange) { - if (!props.showTime && value) { - value = value.startOf('day'); - } - if (props.dateOnly) { - onChange(dateString !== '' ? dateString : undefined); + if (underFilter) { + onChange(handleChangeOnFilter(value, picker, showTime)); } else { - onChange(moment2str(value, props)); + onChange(handleChangeOnForm(value, dateOnly, utc, picker, showTime, gmt)); } } }, @@ -102,18 +129,26 @@ export const mapRangePicker = function () { return (props: any) => { const format = getDefaultFormat(props) as any; const onChange = props.onChange; - + const { dateOnly, showTime, picker = 'date', utc, gmt, underFilter } = props; return { ...props, format: format, value: str2moment(props.value, props), onChange: (value: Dayjs[]) => { if (onChange) { - onChange( - value - ? [moment2str(getRangeStart(value[0], props), props), moment2str(getRangeEnd(value[1], props), props)] - : [], - ); + if (underFilter) { + onChange( + value + ? [handleChangeOnFilter(value[0], picker, showTime), handleChangeOnFilter(value[1], picker, showTime)] + : [], + ); + } else { + onChange( + value + ? [moment2str(getRangeStart(value[0], props), props), moment2str(getRangeEnd(value[1], props), props)] + : [], + ); + } } }, } as any; @@ -226,3 +261,17 @@ function withParams(value: any[], params: { fieldOperator?: string }) { return value; } + +export function inferPickerType(dateString: string): 'year' | 'month' | 'quarter' | 'date' { + if (/^\d{4}$/.test(dateString)) { + return 'year'; + } else if (/^\d{4}-\d{2}$/.test(dateString)) { + return 'month'; + } else if (/^\d{4}Q[1-4]$/.test(dateString)) { + return 'quarter'; + } else if (/^\d{4}-\d{2}-\d{2}$/.test(dateString)) { + return 'date'; + } else { + return 'date'; + } +} diff --git a/packages/core/client/src/schema-settings/DateFormat/ExpiresRadio.tsx b/packages/core/client/src/schema-component/antd/expiresRadio/index.tsx similarity index 71% rename from packages/core/client/src/schema-settings/DateFormat/ExpiresRadio.tsx rename to packages/core/client/src/schema-component/antd/expiresRadio/index.tsx index 7b5372353b..17b8c46fdf 100644 --- a/packages/core/client/src/schema-settings/DateFormat/ExpiresRadio.tsx +++ b/packages/core/client/src/schema-component/antd/expiresRadio/index.tsx @@ -9,12 +9,11 @@ import { css } from '@emotion/css'; import dayjs from 'dayjs'; - import { connect, mapProps } from '@formily/react'; import { useBoolean } from 'ahooks'; import { Input, Radio, Space } from 'antd'; -import React, { useState } from 'react'; -import { useToken } from '../../'; +import React, { useEffect, useState } from 'react'; +import { useToken } from '../../../'; const date = dayjs(); @@ -24,7 +23,7 @@ const spaceCSS = css` flex: 1; } `; -export const DateFormatCom = (props?) => { +const DateFormatCom = (props?) => { const date = dayjs(); return (
@@ -53,9 +52,11 @@ const DateTimeFormatPreview = ({ content }) => { }; const InternalExpiresRadio = (props) => { - const { onChange, defaultValue, formats, timeFormat } = props; + const { onChange, defaultValue, formats, picker } = props; const [isCustom, { setFalse, setTrue }] = useBoolean(props.value && !formats.includes(props.value)); - const targetValue = props.value && !formats.includes(props.value) ? props.value : defaultValue; + const [targetValue, setTargetValue] = useState( + props.value && !formats.includes(props.value) ? props.value : defaultValue, + ); const [customFormatPreview, setCustomFormatPreview] = useState(targetValue ? date.format(targetValue) : null); const onSelectChange = (v) => { if (v.target.value === 'custom') { @@ -66,6 +67,19 @@ const InternalExpiresRadio = (props) => { onChange(v.target.value); } }; + useEffect(() => { + if (!formats.includes(props.value)) { + setTrue(); + } else { + setFalse(); + } + setTargetValue(props.value && !formats.includes(props.value) ? props.value : defaultValue); + }, [props.value]); + + useEffect(() => { + setCustomFormatPreview(targetValue ? date.format(targetValue) : null); + }, [targetValue]); + return ( @@ -76,7 +90,7 @@ const InternalExpiresRadio = (props) => { { if (e.target.value) { setCustomFormatPreview(date.format(e.target.value)); @@ -86,19 +100,22 @@ const InternalExpiresRadio = (props) => { if (isCustom) { onChange(e.target.value); } + setTargetValue(e.target.value); }} /> ); } - return ( - - - {v.label} - - - ); + if (!picker || picker === 'date') { + return ( + + + {v.label} + + + ); + } })} @@ -113,4 +130,4 @@ const ExpiresRadio = connect( }), ); -export { ExpiresRadio }; +export { ExpiresRadio, DateFormatCom }; diff --git a/packages/core/client/src/schema-component/antd/filter/DynamicComponent.tsx b/packages/core/client/src/schema-component/antd/filter/DynamicComponent.tsx index 2c5062344e..79025dabd2 100644 --- a/packages/core/client/src/schema-component/antd/filter/DynamicComponent.tsx +++ b/packages/core/client/src/schema-component/antd/filter/DynamicComponent.tsx @@ -65,6 +65,7 @@ export const DynamicComponent = (props: Props) => { ...props.style, }, utc: false, + underFilter: true, }), name: 'value', 'x-read-pretty': false, diff --git a/packages/core/client/src/schema-component/antd/form-item/SchemaSettingOptions.tsx b/packages/core/client/src/schema-component/antd/form-item/SchemaSettingOptions.tsx index 9143ab64f4..a5700807d2 100644 --- a/packages/core/client/src/schema-component/antd/form-item/SchemaSettingOptions.tsx +++ b/packages/core/client/src/schema-component/antd/form-item/SchemaSettingOptions.tsx @@ -496,7 +496,6 @@ export const EditOperator = () => { const { dn } = useDesignable(); const operatorList = useOperatorList(); const { getOperator, collectOperator } = useOperators(); - if (operatorList.length && !getOperator(fieldSchema.name)) { collectOperator(fieldSchema.name, operatorList[0].value); } @@ -512,22 +511,22 @@ export const EditOperator = () => { _.set(fieldSchema, 'x-filter-operator', v); const operator = operatorList.find((item) => item.value === v); - let componentProps = {}; - + let componentProps = { ...fieldSchema['x-component-props'] }; + field.value = undefined; //切换操作符清空字段值 // 根据操作符的配置,设置组件的属性 - if (operator?.schema?.['x-component']) { + if (operator?.schema?.['x-component'] && !operator?.onlyFilterAction) { _.set(fieldSchema, 'x-component-props.component', operator.schema?.['x-component']); _.set(field, 'componentProps.component', operator.schema?.['x-component']); - field.reset(); componentProps = { + ...fieldSchema['x-component-props'], component: operator.schema['x-component'], ...operator.schema?.['x-component-props'], }; } else if (fieldSchema['x-component-props']?.component) { _.set(fieldSchema, 'x-component-props.component', null); _.set(field, 'componentProps.component', null); - field.reset(); componentProps = { + ...fieldSchema['x-component-props'], component: null, ...operator.schema?.['x-component-props'], }; diff --git a/packages/core/client/src/schema-component/antd/index.ts b/packages/core/client/src/schema-component/antd/index.ts index ba15dd74b3..36b5d5bdbe 100644 --- a/packages/core/client/src/schema-component/antd/index.ts +++ b/packages/core/client/src/schema-component/antd/index.ts @@ -62,5 +62,6 @@ export * from './variable'; export * from './unix-timestamp'; export * from './nanoid-input'; export * from './error-fallback'; +export * from './expiresRadio'; import './index.less'; diff --git a/packages/core/client/src/schema-component/antd/rich-text/RichText.tsx b/packages/core/client/src/schema-component/antd/rich-text/RichText.tsx index 05728552ac..2ff4c514f4 100644 --- a/packages/core/client/src/schema-component/antd/rich-text/RichText.tsx +++ b/packages/core/client/src/schema-component/antd/rich-text/RichText.tsx @@ -11,11 +11,12 @@ import { connect, mapProps, mapReadPretty } from '@formily/react'; import React from 'react'; import ReactQuill from 'react-quill'; import { isVariable } from '../../../variables/utils/isVariable'; -import { ReadPretty as InputReadPretty } from '../input'; +import { ReadPretty as InputReadPretty, Input } from '../input'; import { useStyles } from './style'; export const RichText = connect( (props) => { + const { underFilter } = props; const { wrapSSR, hashId, componentCls } = useStyles(); const modules = { toolbar: [['bold', 'italic', 'underline', 'link'], [{ list: 'ordered' }, { list: 'bullet' }], ['clean']], @@ -35,7 +36,9 @@ export const RichText = connect( ]; const { value, defaultValue, onChange, disabled } = props; const resultValue = isVariable(value || defaultValue) ? undefined : value || defaultValue || ''; - + if (underFilter) { + return ; + } return wrapSSR( { const { value, onChange } = props; - return ( dayjs(date)); - b = b.map((date) => dayjs(date)); - - return a[0].isBetween(b[0], b[1], null, '[]') && a[1].isBetween(b[0], b[1], null, '[]'); + return a === b; }, $dateBefore: function (a, b) { if (!a || !b) { return false; } - if (!Array.isArray(a)) { - a = [a, a]; - } - if (!Array.isArray(b)) { - b = [b, b]; - } - a = a.map((date) => dayjs(date)); - b = b.map((date) => dayjs(date)); + // Parse both date strings + const dateA = parseDate(a); + const dateB = parseDate(b); - return a[0].isBefore(b[0]) && a[1].isBefore(b[0]); + if (!dateA || !dateB) { + throw new Error('Invalid date format'); + } + return dateA < dateB; }, $dateNotBefore: function (a, b) { - return !operations.$dateBefore(a, b); + if (!a || !b) { + return false; + } + const dateA = parseDate(a); + const dateB = parseDate(b); + + if (!dateA || !dateB) { + throw new Error('Invalid date format'); + } + + // Compare the two dates + return dateA >= dateB; }, $dateAfter: function (a, b) { if (!a || !b) { return false; } - if (!Array.isArray(a)) { - a = [a, a]; - } - if (!Array.isArray(b)) { - b = [b, b]; - } - a = a.map((date) => dayjs(date)); - b = b.map((date) => dayjs(date)); + // Parse both date strings + const dateA = parseDate(a); + const dateB = parseDate(b); - return a[0].isAfter(b[1]) && a[1].isAfter(b[1]); + return dateA > dateB; }, $dateNotAfter: function (a, b) { - return !operations.$dateAfter(a, b); + if (!a || !b) { + return false; + } + const dateA = parseDate(a); + const dateB = parseDate(b); + + if (!dateA || !dateB) { + throw new Error('Invalid date format'); + } + return dateA <= dateB; }, $dateBetween: function (a, b) { - return operations.$dateOn(a, b); + if (!a || !b) { + return false; + } + const dateA = parseFullDate(a); + const dateBStart = parseFullDate(b[0]); + const dateBEnd = parseFullDate(b[1]); + + if (!dateA || !dateBStart || !dateBEnd) { + throw new Error('Invalid date format'); + } + return dateA >= dateBStart && dateA <= dateBEnd; }, $dateNotOn: function (a, b) { - return !operations.$dateOn(a, b); + if (!a || !b) { + return false; + } + return a !== b; }, $isTruly: function (a) { if (Array.isArray(a)) return a.some((k) => k === true || k === 1); @@ -615,3 +632,43 @@ export function getJsonLogic() { return jsonLogic; } + +function parseFullDate(dateStr) { + return new Date(dateStr); +} + +function parseMonth(dateStr) { + const [year, month] = dateStr.split('-').map(Number); + return new Date(year, month - 1); +} + +function parseQuarter(dateStr) { + const year = parseInt(dateStr.slice(0, 4)); + const quarter = parseInt(dateStr.slice(5, 6)); + const month = (quarter - 1) * 3; + return new Date(year, month); +} + +function parseYear(dateStr) { + const year = parseInt(dateStr); + return new Date(year, 0); +} + +function parseDate(dateStr) { + dateStr = dateStr.trim(); + + if (/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) { + // It's in "YYYY-MM-DD" format + return parseFullDate(dateStr); + } else if (/^\d{4}-\d{2}$/.test(dateStr)) { + // It's in "YYYY-MM" format + return parseMonth(dateStr); + } else if (/^\d{4}Q[1-4]$/.test(dateStr)) { + // It's in "YYYYQn" format + return parseQuarter(dateStr); + } else if (/^\d{4}$/.test(dateStr)) { + // It's in "YYYY" format + return parseYear(dateStr); + } + return null; // Invalid format +} diff --git a/packages/core/client/src/schema-component/common/utils/uitls.tsx b/packages/core/client/src/schema-component/common/utils/uitls.tsx index a0e174a3b4..c305551283 100644 --- a/packages/core/client/src/schema-component/common/utils/uitls.tsx +++ b/packages/core/client/src/schema-component/common/utils/uitls.tsx @@ -11,11 +11,13 @@ import Handlebars from 'handlebars'; import { dayjs } from '@nocobase/utils/client'; import helpers from '@budibase/handlebars-helpers'; import _, { every, findIndex, some } from 'lodash'; +import { getPickerFormat } from '@nocobase/utils/client'; import { replaceVariableValue } from '../../../block-provider/hooks'; import { VariableOption, VariablesContextType } from '../../../variables/types'; import { isVariable } from '../../../variables/utils/isVariable'; import { transformVariableValue } from '../../../variables/utils/transformVariableValue'; import { getJsonLogic } from '../../common/utils/logic'; +import { inferPickerType } from '../../antd/date-picker/util'; import url from 'url'; type VariablesCtx = { /** 当前登录的用户 */ @@ -124,11 +126,19 @@ export const conditionAnalyses = async ({ const jsonLogic = getJsonLogic(); const [value, targetValue] = await Promise.all(parsingResult); const targetCollectionField = await variables.getCollectionField(targetVariableName, localVariables); + let currentInputValue = transformVariableValue(targetValue, { targetCollectionField }); + const comparisonValue = transformVariableValue(value, { targetCollectionField }); + if ( + ['datetime', 'date', 'datetimeNoTz', 'dateOnly', 'unixTimestamp'].includes(targetCollectionField.type) && + currentInputValue + ) { + const picker = inferPickerType(comparisonValue); + const format = getPickerFormat(picker); + currentInputValue = dayjs(currentInputValue).format(format); + } + return jsonLogic.apply({ - [operator]: [ - transformVariableValue(targetValue, { targetCollectionField }), - transformVariableValue(value, { targetCollectionField }), - ], + [operator]: [currentInputValue, comparisonValue], }); } catch (error) { throw error; diff --git a/packages/core/client/src/schema-initializer/utils.ts b/packages/core/client/src/schema-initializer/utils.ts index fd0e73825d..c99cb91bf6 100644 --- a/packages/core/client/src/schema-initializer/utils.ts +++ b/packages/core/client/src/schema-initializer/utils.ts @@ -462,8 +462,8 @@ export const useFilterFormItemInitializerFields = (options?: any) => { 'x-use-decorator-props': 'useFormItemProps', 'x-collection-field': `${name}.${field.name}`, 'x-component-props': { - component: interfaceConfig?.filterable?.operators?.[0]?.schema?.['x-component'], utc: false, + underFilter: true, }, }; if (isAssocField(field)) { @@ -478,7 +478,7 @@ export const useFilterFormItemInitializerFields = (options?: any) => { 'x-decorator': 'FormItem', 'x-use-decorator-props': 'useFormItemProps', 'x-collection-field': `${name}.${field.name}`, - 'x-component-props': field.uiSchema?.['x-component-props'], + 'x-component-props': { ...field.uiSchema?.['x-component-props'], utc: false, underFilter: true }, }; } const resultItem = { @@ -572,7 +572,7 @@ const associationFieldToMenu = ( interface: field.interface, }, 'x-component': 'CollectionField', - 'x-component-props': { utc: false }, + 'x-component-props': { utc: false, underFilter: true }, 'x-read-pretty': false, 'x-decorator': 'FormItem', 'x-collection-field': `${collectionName}.${schemaName}`, @@ -688,7 +688,7 @@ export const useFilterInheritsFormItemInitializerFields = (options?) => { 'x-component': 'CollectionField', 'x-decorator': 'FormItem', 'x-collection-field': `${name}.${field.name}`, - 'x-component-props': { utc: false }, + 'x-component-props': { utc: false, underFilter: true }, 'x-read-pretty': field?.uiSchema?.['x-read-pretty'], }; return { diff --git a/packages/core/client/src/schema-settings/SchemaSettingsDateFormat.tsx b/packages/core/client/src/schema-settings/SchemaSettingsDateFormat.tsx index 275c98f41b..33d2c3379a 100644 --- a/packages/core/client/src/schema-settings/SchemaSettingsDateFormat.tsx +++ b/packages/core/client/src/schema-settings/SchemaSettingsDateFormat.tsx @@ -11,13 +11,14 @@ import { css } from '@emotion/css'; import { ISchema, Schema, useField } from '@formily/react'; import React from 'react'; import { useTranslation } from 'react-i18next'; +import { getPickerFormat } from '@nocobase/utils/client'; import { useCollectionManager_deprecated, useDesignable } from '..'; -import { DateFormatCom, ExpiresRadio } from './DateFormat/ExpiresRadio'; +import { DateFormatCom, ExpiresRadio } from '../schema-component'; import { SchemaSettingsModalItem } from './SchemaSettings'; export const SchemaSettingsDateFormat = function DateFormatConfig(props: { fieldSchema: Schema }) { const { fieldSchema } = props; - const field = useField(); + const field: any = useField(); const { dn } = useDesignable(); const { t } = useTranslation(); const { getCollectionJoinField } = useCollectionManager_deprecated(); @@ -31,13 +32,48 @@ export const SchemaSettingsDateFormat = function DateFormatConfig(props: { field fieldSchema?.['x-component-props']?.timeFormat || collectionField?.uiSchema?.['x-component-props']?.timeFormat || 'HH:mm:ss'; + const pickerDefaultValue = + fieldSchema?.['x-component-props']?.picker || collectionField?.uiSchema?.['x-component-props']?.picker || 'date'; + const isReadPretty = fieldSchema['x-read-pretty'] || field.readOnly || field.readPretty; return ( { - const schema = { + const schema: any = { ['x-uid']: fieldSchema['x-uid'], }; - console.log(field.componentProps); + if ((field.componentProps.picker || 'date') !== data.picker && !isReadPretty && field.value) { + field.value = undefined; + field.initialValue = undefined; + fieldSchema.default = undefined; + schema.default = undefined; + } schema['x-component-props'] = field.componentProps || {}; fieldSchema['x-component-props'] = { ...(field.componentProps || {}), @@ -157,6 +222,10 @@ export const SchemaSettingsDateFormat = function DateFormatConfig(props: { field const modifiedString = parts.join('.'); field.query(`${modifiedString}.*[0:].${fieldSchema.name}`).forEach((f) => { if (f.props.name === fieldSchema.name) { + if ((field.componentProps.picker || 'date') !== data.picker && !isReadPretty) { + f.value = undefined; + f.initialValue = undefined; + } f.setComponentProps({ ...data }); } }); diff --git a/packages/core/client/src/schema-settings/SchemaSettingsDefaultValue.tsx b/packages/core/client/src/schema-settings/SchemaSettingsDefaultValue.tsx index f1fe305b7e..08069f4b1c 100644 --- a/packages/core/client/src/schema-settings/SchemaSettingsDefaultValue.tsx +++ b/packages/core/client/src/schema-settings/SchemaSettingsDefaultValue.tsx @@ -110,7 +110,11 @@ export const SchemaSettingsDefaultValue = function DefaultValueConfigure(props: VariableInput: (inputProps) => { return ( - + ); }, diff --git a/packages/core/client/src/schema-settings/SchemaSettingsPlugin.ts b/packages/core/client/src/schema-settings/SchemaSettingsPlugin.ts index f5f2a5bf7c..f413b0c9ba 100644 --- a/packages/core/client/src/schema-settings/SchemaSettingsPlugin.ts +++ b/packages/core/client/src/schema-settings/SchemaSettingsPlugin.ts @@ -48,7 +48,10 @@ import { filterFormBlockSettings } from '../modules/blocks/filter-blocks/form/fi import { filterFormItemFieldSettings } from '../modules/blocks/filter-blocks/form/filterFormItemFieldSettings'; import { markdownBlockSettings } from '../modules/blocks/other-blocks/markdown/markdownBlockSettings'; import { cascadeSelectComponentFieldSettings } from '../modules/fields/component/CascadeSelect/cascadeSelectComponentFieldSettings'; -import { datePickerComponentFieldSettings } from '../modules/fields/component/DatePicker/datePickerComponentFieldSettings'; +import { + datePickerComponentFieldSettings, + rangePickerPickerComponentFieldSettings, +} from '../modules/fields/component/DatePicker/datePickerComponentFieldSettings'; import { fileManagerComponentFieldSettings } from '../modules/fields/component/FileManager/fileManagerComponentFieldSettings'; import { previewComponentFieldSettings } from '../modules/fields/component/FileManager/previewComponentFieldSettings'; import { uploadAttachmentComponentFieldSettings } from '../modules/fields/component/FileManager/uploadAttachmentComponentFieldSettings'; @@ -120,6 +123,7 @@ export class SchemaSettingsPlugin extends Plugin { this.schemaSettingsManager.add(subformPopoverComponentFieldSettings); this.schemaSettingsManager.add(subTablePopoverComponentFieldSettings); this.schemaSettingsManager.add(datePickerComponentFieldSettings); + this.schemaSettingsManager.add(rangePickerPickerComponentFieldSettings); this.schemaSettingsManager.add(unixTimestampComponentFieldSettings); this.schemaSettingsManager.add(inputNumberComponentFieldSettings); this.schemaSettingsManager.add(inputComponentSettings); diff --git a/packages/core/utils/src/date.ts b/packages/core/utils/src/date.ts index 3a7bad0485..731c23a1f9 100644 --- a/packages/core/utils/src/date.ts +++ b/packages/core/utils/src/date.ts @@ -44,7 +44,7 @@ export const getDefaultFormat = (props: GetDefaultFormatProps) => { } else if (props['picker'] === 'year') { return 'YYYY'; } else if (props['picker'] === 'week') { - return 'YYYY-wo'; + return 'YYYY[W]W'; } return props['showTime'] ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD'; }; @@ -68,24 +68,33 @@ export const toLocal = (value: dayjs.Dayjs) => { } }; +const convertQuarterToFirstDay = (quarterStr) => { + const year = parseInt(quarterStr.slice(0, 4)); // 提取年份 + const quarter = parseInt(quarterStr.slice(-1)); // 提取季度数字 + return dayjs().quarter(quarter).year(year); +}; + const toMoment = (val: any, options?: Str2momentOptions) => { if (!val) { return; } const offset = options.utcOffset || -1 * new Date().getTimezoneOffset(); const { gmt, picker, utc = true } = options; + if (dayjs(val).isValid()) { + if (!utc) { + return dayjs(val); + } - if (!utc) { - return dayjs(val); + if (dayjs.isDayjs(val)) { + return val.utcOffset(offsetFromString(offset)); + } + if (gmt) { + return dayjs(val).utcOffset(0); + } + return dayjs(val).utcOffset(offsetFromString(offset)); + } else { + return convertQuarterToFirstDay(val); } - - if (dayjs.isDayjs(val)) { - return val.utcOffset(offsetFromString(offset)); - } - if (gmt || picker) { - return dayjs(val).utcOffset(0); - } - return dayjs(val).utcOffset(offsetFromString(offset)); }; export const str2moment = ( @@ -198,3 +207,18 @@ function absFloor(number) { return Math.floor(number); } } + +export const getPickerFormat = (picker) => { + switch (picker) { + case 'week': + return 'YYYY[W]W'; + case 'month': + return 'YYYY-MM'; + case 'quarter': + return 'YYYY[Q]Q'; + case 'year': + return 'YYYY'; + default: + return 'YYYY-MM-DD'; + } +}; diff --git a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/component/BulkEditField.tsx b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/component/BulkEditField.tsx index 1d4c4cdfad..0ed93b3552 100644 --- a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/component/BulkEditField.tsx +++ b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/component/BulkEditField.tsx @@ -99,7 +99,6 @@ export const BulkEditField = (props: any) => { const [value, setValue] = useState(null); const { getField } = useCollection_deprecated(); const collectionField = getField(fieldSchema.name) || {}; - useEffect(() => { field.value = toFormFieldValue({ [type]: value }); if (field.required) { @@ -112,6 +111,12 @@ export const BulkEditField = (props: any) => { } }, [field, type, value]); + useEffect(() => { + if (field.value === null) { + setValue(undefined); + } + }, [field.value]); + const typeChangeHandler = (val) => { setType(val); field.required = val === BulkEditFormItemValueType.ChangedTo; diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/ConfigurationTable.tsx b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/ConfigurationTable.tsx index 03704b08c3..1cbe5476c1 100644 --- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/ConfigurationTable.tsx +++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/ConfigurationTable.tsx @@ -30,6 +30,7 @@ import { ResourceActionContext, useDataSourceManager, } from '@nocobase/client'; +import { getPickerFormat } from '@nocobase/utils/client'; import { message } from 'antd'; import { getCollectionSchema } from './schema/collections'; import { CollectionFields } from './CollectionFields'; @@ -208,6 +209,7 @@ export const ConfigurationTable = () => { interfaces, enableInherits: database?.dialect === 'postgres', isPG: database?.dialect === 'postgres', + getPickerFormat, }} /> diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/Configuration/ConfigurationTable.tsx b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/Configuration/ConfigurationTable.tsx index 2032a92027..816f7c23d6 100644 --- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/Configuration/ConfigurationTable.tsx +++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/Configuration/ConfigurationTable.tsx @@ -12,7 +12,6 @@ import { action } from '@formily/reactive'; import { uid } from '@formily/shared'; import React, { useContext, useState } from 'react'; import { useTranslation } from 'react-i18next'; -// import { CollectionFieldsTable } from '.'; import { useAPIClient, useCurrentAppInfo, @@ -29,9 +28,8 @@ import { CollectionCategoriesContext, FieldSummary, TemplateSummary, - useRequest, - useCollectionRecordData, } from '@nocobase/client'; +import { getPickerFormat } from '@nocobase/utils/client'; import { CollectionFields } from './CollectionFields'; import { collectionSchema } from './schemas/collections'; @@ -239,6 +237,7 @@ export const ConfigurationTable = () => { interfaces, enableInherits: database?.dialect === 'postgres', isPG: database?.dialect === 'postgres', + getPickerFormat, }} />