diff --git a/docs/zh-CN/components/form/input-datetime.md b/docs/zh-CN/components/form/input-datetime.md index 2584adcc0..871cdbc1b 100755 --- a/docs/zh-CN/components/form/input-datetime.md +++ b/docs/zh-CN/components/form/input-datetime.md @@ -254,7 +254,7 @@ order: 14 可以通过 `disabledDate` 字符函数来控制,比如不允许选择周一、周六、周日 -函数签名: `(currentDate: moment.Moment, props: any) => boolean` +函数签名: `(currentDate: moment.Moment, props: any) => boolean` 示例: `"return currentDate.day() == 1 || currentDate.day() == 0 || currentDate.day() == 6"` ```schema: scope="body" diff --git a/packages/amis-ui/src/components/CalendarMobile.tsx b/packages/amis-ui/src/components/CalendarMobile.tsx index e200ee2ec..07025c357 100644 --- a/packages/amis-ui/src/components/CalendarMobile.tsx +++ b/packages/amis-ui/src/components/CalendarMobile.tsx @@ -11,6 +11,7 @@ import {themeable, ThemeProps} from 'amis-core'; import {LocaleProps, localeable} from 'amis-core'; import {autobind} from 'amis-core'; import {ShortCuts} from './DatePicker'; +import type {ViewMode} from './calendar/Calendar'; export interface CalendarMobileProps extends ThemeProps, LocaleProps { className?: string; @@ -25,7 +26,7 @@ export interface CalendarMobileProps extends ThemeProps, LocaleProps { maxDuration?: moment.Duration; dateFormat?: string; embed?: boolean; - viewMode?: 'days' | 'months' | 'years' | 'time' | 'quarters'; + viewMode?: ViewMode; close?: () => void; confirm?: (startDate?: any, endTime?: any) => void; onChange?: (data: any, callback?: () => void) => void; diff --git a/packages/amis-ui/src/components/DateRangePicker.tsx b/packages/amis-ui/src/components/DateRangePicker.tsx index 379bc0b8c..2f332363d 100644 --- a/packages/amis-ui/src/components/DateRangePicker.tsx +++ b/packages/amis-ui/src/components/DateRangePicker.tsx @@ -30,6 +30,7 @@ import Input from './Input'; import Button from './Button'; import type {PlainObject, ThemeProps, LocaleProps} from 'amis-core'; +import type {ViewMode} from './calendar/Calendar'; export interface DateRangePickerProps extends ThemeProps, LocaleProps { className?: string; @@ -67,7 +68,7 @@ export interface DateRangePickerProps extends ThemeProps, LocaleProps { popOverContainer?: any; dateFormat?: string; embed?: boolean; - viewMode?: 'days' | 'months' | 'years' | 'time' | 'quarters'; + viewMode?: ViewMode; borderMode?: 'full' | 'half' | 'none'; onFocus?: Function; onBlur?: Function; @@ -882,12 +883,23 @@ export class DateRangePicker extends React.Component< filterDate( date: moment.Moment, - originValue?: moment.Moment, - timeFormat?: string, - type: 'start' | 'end' = 'start' + options: { + type: 'start' | 'end'; + originValue?: moment.Moment; + timeFormat?: string; + subControlViewMode?: 'time'; + } = {type: 'start'} ): moment.Moment { + const {type, originValue, timeFormat, subControlViewMode} = options || { + type: 'start' + }; let value = date.clone(); + /** 日期时间选择器组件支持用户选择时间,如果用户手动选择了时间,则不需要走默认处理 */ + if (subControlViewMode && subControlViewMode === 'time') { + return value; + } + // 没有初始值 if (!originValue) { value = value[type === 'start' ? 'startOf' : 'endOf']('day'); @@ -915,18 +927,26 @@ export class DateRangePicker extends React.Component< } } - handleStartDateChange(newValue: moment.Moment) { - const {timeFormat, minDate, inputFormat, displayFormat, type} = this.props; - let {startDate, endDateOpenedFirst} = this.state; + /** + * @param {Moment} newValue 当前选择的日期时间值 + * @param {ViewMode=} subControlViewMode 子选择控件的类型,可选参数('time'),用于区分datetime选择器的触发控件 + */ + handleStartDateChange( + newValue: moment.Moment, + subControlViewMode?: Extract + ) { + const {minDate, inputFormat, displayFormat, type} = this.props; + let {startDate, endDateOpenedFirst, curTimeFormat: timeFormat} = this.state; if (minDate && newValue.isBefore(minDate)) { newValue = minDate; } - const date = this.filterDate( - newValue, - startDate || minDate, + + const date = this.filterDate(newValue, { + type: 'start', + originValue: startDate || minDate, timeFormat, - 'start' - ); + subControlViewMode + }); const newState = { startDate: date, startInputValue: date.format(displayFormat || inputFormat) @@ -944,13 +964,29 @@ export class DateRangePicker extends React.Component< this.setState(newState); } - handelEndDateChange(newValue: moment.Moment) { - const {embed, timeFormat, inputFormat, displayFormat, type} = this.props; - let {startDate, endDate, endDateOpenedFirst} = this.state; + /** + * @param {Moment} newValue 当前选择的日期时间值 + * @param {string=} subControlViewMode 子选择控件的类型的类型,可选参数('time'),用于区分datetime选择器的触发控件 + */ + handelEndDateChange( + newValue: moment.Moment, + subControlViewMode?: Extract + ) { + const {embed, inputFormat, displayFormat, type} = this.props; + let { + startDate, + endDate, + endDateOpenedFirst, + curTimeFormat: timeFormat + } = this.state; newValue = this.getEndDateByDuration(newValue); const editState = endDateOpenedFirst ? 'start' : 'end'; - - const date = this.filterDate(newValue, endDate, timeFormat, 'end'); + const date = this.filterDate(newValue, { + type: 'end', + originValue: endDate, + timeFormat, + subControlViewMode + }); this.setState( { endDate: date, diff --git a/packages/amis-ui/src/components/calendar/Calendar.tsx b/packages/amis-ui/src/components/calendar/Calendar.tsx index 0806e5d19..50a8600d9 100644 --- a/packages/amis-ui/src/components/calendar/Calendar.tsx +++ b/packages/amis-ui/src/components/calendar/Calendar.tsx @@ -16,6 +16,9 @@ import 'moment/locale/zh-cn'; import 'moment/locale/de'; import type {RendererEnv} from 'amis-core'; +/** 视图模式 */ +export type ViewMode = 'days' | 'months' | 'years' | 'time' | 'quarters'; + export type DateType = | 'year' | 'month' @@ -48,7 +51,7 @@ interface BaseDatePickerProps { className?: string; value?: any; defaultValue?: any; - viewMode?: 'years' | 'months' | 'days' | 'time' | 'quarters'; + viewMode?: ViewMode; dateFormat?: boolean | string; inputFormat?: boolean | string; displayForamt?: boolean | string; @@ -63,7 +66,7 @@ interface BaseDatePickerProps { onViewModeChange?: (type: string) => void; requiredConfirm?: boolean; onClose?: () => void; - onChange?: (value: any) => void; + onChange?: (value: any, viewMode?: Extract) => void; isEndDate?: boolean; minDate?: moment.Moment; maxDate?: moment.Moment; @@ -480,7 +483,7 @@ class BaseDatePicker extends React.Component< inputValue: date.format(state.displayForamt as string) }); } - this.props.onChange && this.props.onChange(date); + this.props.onChange && this.props.onChange(date, 'time'); }; setDate = (type: 'month' | 'year' | 'quarters') => { diff --git a/packages/amis-ui/src/components/calendar/DaysView.tsx b/packages/amis-ui/src/components/calendar/DaysView.tsx index a3a947bb7..4a0df6294 100644 --- a/packages/amis-ui/src/components/calendar/DaysView.tsx +++ b/packages/amis-ui/src/components/calendar/DaysView.tsx @@ -19,9 +19,11 @@ import type {RendererEnv} from 'amis-core'; import Picker from '../Picker'; import {PickerOption} from '../PickerColumn'; import {DateType} from './Calendar'; -import type {TimeScale} from './TimeView'; import {Icon} from '../icons'; +import type {TimeScale} from './TimeView'; +import type {ViewMode} from './Calendar'; + interface CustomDaysViewProps extends LocaleProps { classPrefix?: string; prevIcon?: string; @@ -37,7 +39,10 @@ interface CustomDaysViewProps extends LocaleProps { isEndDate?: boolean; renderDay?: Function; onClose?: () => void; - onChange: (value: moment.Moment) => void; + onChange: ( + value: moment.Moment, + viewMode?: Extract + ) => void; onConfirm?: (value: number[], types: DateType[]) => void; setDateTimeState: (state: any) => void; showTime: () => void; @@ -294,6 +299,7 @@ export class CustomDaysView extends React.Component { showTime = () => { const {selectedDate, viewDate, timeFormat} = this.props; + return (
{(selectedDate || viewDate || moment()).format(timeFormat)} @@ -306,15 +312,16 @@ export class CustomDaysView extends React.Component { value: number ) => { const date = (this.props.selectedDate || this.props.viewDate).clone(); - date[type](value); + date[type](value); + const updatedDate = date.clone(); this.props.setDateTimeState({ - viewDate: date.clone(), - selectedDate: date.clone() + viewDate: updatedDate, + selectedDate: updatedDate }); if (!this.props.requiredConfirm) { - this.props.onChange(date); + this.props.onChange(date, 'time'); } }; diff --git a/packages/amis/__tests__/renderers/Tree.test.tsx b/packages/amis/__tests__/renderers/Tree.test.tsx index 2e248d3aa..2bce5e09e 100644 --- a/packages/amis/__tests__/renderers/Tree.test.tsx +++ b/packages/amis/__tests__/renderers/Tree.test.tsx @@ -530,3 +530,76 @@ test('Tree: item disabled', async () => { transfer: '' }); }); + +test('Tree: single value mode should not render input when searchable enabled and default value settled', async () => { + const {container} = render( + amisRender({ + type: 'container', + body: [ + { + "type": "tree-select", + "name": "tree", + "label": "Tree", + "searchable": true, + "value": 2, + "inputClassName": "single", + "options": [ + { + "label": "Folder A", + "value": 1, + "children": [ + { + "label": "file A", + "value": 2 + }, + { + "label": "file B", + "value": 3 + } + ] + } + ] + }, + { + "type": "tree-select", + "name": "tree2", + "label": "Tree2", + "searchable": true, + "value": "2,4", + "multiple": true, + "inputClassName": "multiple", + "options": [ + { + "label": "Folder A", + "value": 1, + "children": [ + { + "label": "file A", + "value": 2 + }, + { + "label": "file B", + "value": 3 + } + ] + }, + { + "label": "file C", + "value": 4 + } + ] + } + ] + }, + {}, + makeEnv({}) + )); + + const singleModeInput = container.querySelector('.single .cxd-ResultBox-value-input'); + const multipleModeInput = container.querySelector('.multiple .cxd-ResultBox-value-input'); + + /** 单选模式且已选值,不应该再有 input */ + expect(singleModeInput).not.toBeInTheDocument(); + /** 多选模式始终都有 input */ + expect(multipleModeInput).toBeInTheDocument(); +}) diff --git a/packages/amis/src/renderers/Form/TreeSelect.tsx b/packages/amis/src/renderers/Form/TreeSelect.tsx index 265e46f1d..bce643d61 100644 --- a/packages/amis/src/renderers/Form/TreeSelect.tsx +++ b/packages/amis/src/renderers/Form/TreeSelect.tsx @@ -695,8 +695,13 @@ export default class TreeSelectControl extends React.Component< env, loadingConfig } = this.props; - const {isOpened} = this.state; + const resultValue = multiple + ? selectedOptions + : selectedOptions.length + ? this.renderItem(selectedOptions[0]) + : ''; + return (