refactor: adapt time & date range picker component for mobile (#5863)

* refactor: date range picker in mobile

* refactor: date range picker in mobile

* refactor: timepicker

* fix: build

* fix: bug

* refactor: mobile time picker
This commit is contained in:
Katherine 2024-12-16 19:34:08 +08:00 committed by GitHub
parent 289d476ad1
commit 5a66ba578a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 221 additions and 17 deletions

View File

@ -2,9 +2,7 @@
"version": "1.5.0-beta.9",
"npmClient": "yarn",
"useWorkspaces": true,
"npmClientArgs": [
"--ignore-engines"
],
"npmClientArgs": ["--ignore-engines"],
"command": {
"version": {
"forcePublish": true,

View File

@ -16,15 +16,17 @@ import { ReadPretty } from './ReadPretty';
type ComposedTimePicker = React.FC<AntdTimePickerProps> & {
RangePicker?: React.FC<TimeRangePickerProps>;
ReadPretty?: React.FC<any>;
};
const mapTimeFormat = function () {
export const mapTimeFormat = function () {
return (props: any, field) => {
const format = props['format'] || 'HH:mm:ss';
const onChange = props.onChange;
return {
...props,
format,
inputReadOnly: true,
value: dayjsable(props.value, format),
onChange: (value: dayjs.Dayjs | dayjs.Dayjs[]) => {
if (onChange) {
@ -42,5 +44,5 @@ export const TimePicker: ComposedTimePicker = connect(
);
TimePicker.RangePicker = connect(AntdTimePicker.RangePicker, mapProps(mapTimeFormat()), mapReadPretty(ReadPretty));
TimePicker.ReadPretty = ReadPretty;
export default TimePicker;

View File

@ -15,12 +15,18 @@ import {
DatePicker,
Action,
SchemaComponentOptions,
TimePicker,
} from '@nocobase/client';
import React, { useCallback } from 'react';
import { Outlet, useParams } from 'react-router-dom';
import { Button as MobileButton, Dialog as MobileDialog } from 'antd-mobile';
import { MobilePicker } from './components/MobilePicker';
import { MobileDateTimePicker } from './components/MobileDatePicker';
import {
MobileDateTimePicker,
MobileRangePicker,
MobileDateFilterWithPicker,
MobileTimePicker,
} from './components/MobileDatePicker';
const AssociationFieldMobile = (props) => {
return <AssociationField {...props} popupMatchSelectWidth={true} />;
@ -43,8 +49,16 @@ const DatePickerMobile = (props) => {
return <MobileDateTimePicker {...props} />;
}
};
DatePickerMobile.FilterWithPicker = DatePicker.FilterWithPicker;
DatePickerMobile.RangePicker = DatePicker.RangePicker;
DatePickerMobile.FilterWithPicker = (props) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const { designable } = useDesignable();
if (designable !== false) {
return <DatePicker.FilterWithPicker {...props} />;
} else {
return <MobileDateFilterWithPicker {...props} />;
}
};
DatePickerMobile.RangePicker = MobileRangePicker;
const mobileComponents = {
Button: MobileButton,
@ -60,6 +74,14 @@ const mobileComponents = {
UnixTimestamp: MobileDateTimePicker,
Modal: MobileDialog,
AssociationField: AssociationFieldMobile,
TimePicker: (props) => {
const { designable } = useDesignable();
if (designable !== false) {
return <TimePicker {...props} />;
} else {
return <MobileTimePicker {...props} />;
}
},
};
export const MobilePage = () => {

View File

@ -8,9 +8,20 @@
*/
import React, { useState, useCallback } from 'react';
import { DatePicker } from 'antd-mobile';
import { mapDatePicker, DatePicker as NBDatePicker } from '@nocobase/client';
import { connect, mapProps, mapReadPretty } from '@formily/react';
import { DatePicker, Picker } from 'antd-mobile';
import { Space, Select } from 'antd';
import {
mapDatePicker,
DatePicker as NBDatePicker,
useDatePickerContext,
useCompile,
inferPickerType,
TimePicker as NBTimePicker,
mapTimeFormat,
} from '@nocobase/client';
import dayjs from 'dayjs';
import { connect, mapProps, mapReadPretty, useField, useFieldSchema } from '@formily/react';
import { getPickerFormat } from '@nocobase/utils/client';
import { useTranslation } from 'react-i18next';
function getPrecision(timeFormat: string): 'hour' | 'minute' | 'second' {
@ -37,7 +48,7 @@ const MobileDateTimePicker = connect(
timeFormat = 'HH:mm',
showTime = false,
picker,
...rest
...others
} = props;
const [visible, setVisible] = useState(false);
@ -67,6 +78,7 @@ const MobileDateTimePicker = connect(
return data;
}
}, []);
return (
<>
<div contentEditable="false" onClick={() => setVisible(true)}>
@ -74,12 +86,13 @@ const MobileDateTimePicker = connect(
onClick={() => setVisible(true)}
value={value}
picker={picker}
{...rest}
{...others}
popupStyle={{ display: 'none' }}
style={{ pointerEvents: 'none', width: '100%' }}
/>
</div>
<DatePicker
{...others}
cancelText={t('Cancel')}
confirmText={t('Confirm')}
visible={visible}
@ -87,10 +100,10 @@ const MobileDateTimePicker = connect(
onClose={() => {
setVisible(false);
}}
precision={showTime ? getPrecision(timeFormat) : picker === 'date' ? 'day' : picker}
precision={showTime && picker === 'date' ? getPrecision(timeFormat) : picker === 'date' ? 'day' : picker}
renderLabel={labelRenderer}
min={new Date(1000, 0, 1)}
max={new Date(9999, 11, 31)}
min={others.min || new Date(1000, 0, 1)}
max={others.max || new Date(9999, 11, 31)}
onConfirm={(val) => {
handleConfirm(val);
}}
@ -102,4 +115,173 @@ const MobileDateTimePicker = connect(
mapReadPretty(NBDatePicker.ReadPretty),
);
export { MobileDateTimePicker };
const MobileRangePicker = (props) => {
const [startDate, setStartDate] = useState(props.value?.[0]);
const [endDate, setEndDate] = useState(props.value?.[1]);
const { t } = useTranslation();
const handleStartDateChange = (value) => {
const selectedDateTime = new Date(value);
props.onChange([selectedDateTime, props.value?.[1]]);
setStartDate(selectedDateTime);
if (endDate && value > endDate) {
setEndDate(null); // 重置结束日期
}
};
const handleEndDateChange = (value) => {
const selectedDateTime = new Date(value);
props.onChange([props.value?.[0], selectedDateTime]);
setEndDate(selectedDateTime);
};
return (
<div
style={{
display: 'flex',
justifyContent: 'space-between',
marginBottom: '16px',
}}
>
<div style={{ flex: 1, marginRight: '8px' }}>
<MobileDateTimePicker
{...props}
value={startDate}
max={endDate}
onChange={handleStartDateChange}
style={{ width: '100%' }}
placeholder={t('Start date')}
/>
</div>
<span
style={{
margin: '0px',
fontSize: '16px',
color: '#333',
}}
>
~
</span>
<div style={{ flex: 1, marginLeft: '8px' }}>
<MobileDateTimePicker
{...props}
value={endDate}
min={startDate}
onChange={handleEndDateChange}
style={{ width: '100%' }}
placeholder={t('End date')}
/>
</div>
</div>
);
};
const MobileDateFilterWithPicker = (props: any) => {
const { picker = 'date', format } = props;
const { utc = true } = useDatePickerContext();
const value = Array.isArray(props.value) ? props.value[0] : props.value;
const compile = useCompile();
const fieldSchema = useFieldSchema();
const targetPicker = value ? inferPickerType(value) : picker;
const targetFormat = getPickerFormat(targetPicker) || format;
const field: any = useField();
const newProps = {
utc,
...props,
underFilter: true,
showTime: props.showTime ? { defaultValue: dayjs('00:00:00', 'HH:mm:ss') } : false,
format: targetFormat,
picker: targetPicker,
onChange: (val) => {
props.onChange(undefined);
setTimeout(() => {
props.onChange(val);
});
},
};
const [stateProps, setStateProps] = useState(newProps);
return (
<Space.Compact>
<Select
// @ts-ignore
role="button"
data-testid="select-picker"
style={{ width: '100px' }}
popupMatchSelectWidth={false}
defaultValue={targetPicker}
options={compile([
{
label: '{{t("Date")}}',
value: 'date',
},
{
label: '{{t("Month")}}',
value: 'month',
},
{
label: '{{t("Quarter")}}',
value: 'quarter',
},
{
label: '{{t("Year")}}',
value: 'year',
},
])}
onChange={(value) => {
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;
}}
/>
<MobileDateTimePicker {...stateProps} value={value} />
</Space.Compact>
);
};
type ComposedMobileTimePicker = React.FC<any> & {
RangePicker?: React.FC<any>;
ReadPretty?: React.FC<any>;
};
const MobileTimePicker: ComposedMobileTimePicker = connect(
(props) => {
const [visible, setVisible] = useState(false);
// 小时、分钟和秒的数据
const hours = Array.from({ length: 24 }, (_, i) => i.toString().padStart(2, '0'));
const minutes = Array.from({ length: 60 }, (_, i) => i.toString().padStart(2, '0'));
const seconds = Array.from({ length: 60 }, (_, i) => i.toString().padStart(2, '0'));
const timeData = [
hours, // 小时
minutes, // 分钟
seconds, // 秒
];
const handleTimeChange = (value: [string, string, string]) => {
props.onChange(`${value[0]}:${value[1]}:${value[2]}`);
setVisible(false);
};
return (
<div contentEditable="false" onClick={() => setVisible(true)}>
<NBTimePicker {...props} style={{ pointerEvents: 'none' }} />
<Picker onConfirm={handleTimeChange} columns={timeData} visible={visible} />
</div>
);
},
mapProps(mapTimeFormat()),
mapReadPretty(NBTimePicker.ReadPretty),
);
MobileTimePicker.RangePicker = NBTimePicker.RangePicker;
export { MobileDateTimePicker, MobileRangePicker, MobileDateFilterWithPicker, MobileTimePicker };