mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 13:39:24 +08:00
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:
parent
289d476ad1
commit
5a66ba578a
@ -2,9 +2,7 @@
|
|||||||
"version": "1.5.0-beta.9",
|
"version": "1.5.0-beta.9",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"useWorkspaces": true,
|
"useWorkspaces": true,
|
||||||
"npmClientArgs": [
|
"npmClientArgs": ["--ignore-engines"],
|
||||||
"--ignore-engines"
|
|
||||||
],
|
|
||||||
"command": {
|
"command": {
|
||||||
"version": {
|
"version": {
|
||||||
"forcePublish": true,
|
"forcePublish": true,
|
||||||
|
@ -16,15 +16,17 @@ import { ReadPretty } from './ReadPretty';
|
|||||||
|
|
||||||
type ComposedTimePicker = React.FC<AntdTimePickerProps> & {
|
type ComposedTimePicker = React.FC<AntdTimePickerProps> & {
|
||||||
RangePicker?: React.FC<TimeRangePickerProps>;
|
RangePicker?: React.FC<TimeRangePickerProps>;
|
||||||
|
ReadPretty?: React.FC<any>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapTimeFormat = function () {
|
export const mapTimeFormat = function () {
|
||||||
return (props: any, field) => {
|
return (props: any, field) => {
|
||||||
const format = props['format'] || 'HH:mm:ss';
|
const format = props['format'] || 'HH:mm:ss';
|
||||||
const onChange = props.onChange;
|
const onChange = props.onChange;
|
||||||
return {
|
return {
|
||||||
...props,
|
...props,
|
||||||
format,
|
format,
|
||||||
|
inputReadOnly: true,
|
||||||
value: dayjsable(props.value, format),
|
value: dayjsable(props.value, format),
|
||||||
onChange: (value: dayjs.Dayjs | dayjs.Dayjs[]) => {
|
onChange: (value: dayjs.Dayjs | dayjs.Dayjs[]) => {
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
@ -42,5 +44,5 @@ export const TimePicker: ComposedTimePicker = connect(
|
|||||||
);
|
);
|
||||||
|
|
||||||
TimePicker.RangePicker = connect(AntdTimePicker.RangePicker, mapProps(mapTimeFormat()), mapReadPretty(ReadPretty));
|
TimePicker.RangePicker = connect(AntdTimePicker.RangePicker, mapProps(mapTimeFormat()), mapReadPretty(ReadPretty));
|
||||||
|
TimePicker.ReadPretty = ReadPretty;
|
||||||
export default TimePicker;
|
export default TimePicker;
|
||||||
|
@ -15,12 +15,18 @@ import {
|
|||||||
DatePicker,
|
DatePicker,
|
||||||
Action,
|
Action,
|
||||||
SchemaComponentOptions,
|
SchemaComponentOptions,
|
||||||
|
TimePicker,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { Outlet, useParams } from 'react-router-dom';
|
import { Outlet, useParams } from 'react-router-dom';
|
||||||
import { Button as MobileButton, Dialog as MobileDialog } from 'antd-mobile';
|
import { Button as MobileButton, Dialog as MobileDialog } from 'antd-mobile';
|
||||||
import { MobilePicker } from './components/MobilePicker';
|
import { MobilePicker } from './components/MobilePicker';
|
||||||
import { MobileDateTimePicker } from './components/MobileDatePicker';
|
import {
|
||||||
|
MobileDateTimePicker,
|
||||||
|
MobileRangePicker,
|
||||||
|
MobileDateFilterWithPicker,
|
||||||
|
MobileTimePicker,
|
||||||
|
} from './components/MobileDatePicker';
|
||||||
|
|
||||||
const AssociationFieldMobile = (props) => {
|
const AssociationFieldMobile = (props) => {
|
||||||
return <AssociationField {...props} popupMatchSelectWidth={true} />;
|
return <AssociationField {...props} popupMatchSelectWidth={true} />;
|
||||||
@ -43,8 +49,16 @@ const DatePickerMobile = (props) => {
|
|||||||
return <MobileDateTimePicker {...props} />;
|
return <MobileDateTimePicker {...props} />;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
DatePickerMobile.FilterWithPicker = DatePicker.FilterWithPicker;
|
DatePickerMobile.FilterWithPicker = (props) => {
|
||||||
DatePickerMobile.RangePicker = DatePicker.RangePicker;
|
// 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 = {
|
const mobileComponents = {
|
||||||
Button: MobileButton,
|
Button: MobileButton,
|
||||||
@ -60,6 +74,14 @@ const mobileComponents = {
|
|||||||
UnixTimestamp: MobileDateTimePicker,
|
UnixTimestamp: MobileDateTimePicker,
|
||||||
Modal: MobileDialog,
|
Modal: MobileDialog,
|
||||||
AssociationField: AssociationFieldMobile,
|
AssociationField: AssociationFieldMobile,
|
||||||
|
TimePicker: (props) => {
|
||||||
|
const { designable } = useDesignable();
|
||||||
|
if (designable !== false) {
|
||||||
|
return <TimePicker {...props} />;
|
||||||
|
} else {
|
||||||
|
return <MobileTimePicker {...props} />;
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MobilePage = () => {
|
export const MobilePage = () => {
|
||||||
|
@ -8,9 +8,20 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useCallback } from 'react';
|
import React, { useState, useCallback } from 'react';
|
||||||
import { DatePicker } from 'antd-mobile';
|
import { DatePicker, Picker } from 'antd-mobile';
|
||||||
import { mapDatePicker, DatePicker as NBDatePicker } from '@nocobase/client';
|
import { Space, Select } from 'antd';
|
||||||
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
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';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
function getPrecision(timeFormat: string): 'hour' | 'minute' | 'second' {
|
function getPrecision(timeFormat: string): 'hour' | 'minute' | 'second' {
|
||||||
@ -37,7 +48,7 @@ const MobileDateTimePicker = connect(
|
|||||||
timeFormat = 'HH:mm',
|
timeFormat = 'HH:mm',
|
||||||
showTime = false,
|
showTime = false,
|
||||||
picker,
|
picker,
|
||||||
...rest
|
...others
|
||||||
} = props;
|
} = props;
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
|
|
||||||
@ -67,6 +78,7 @@ const MobileDateTimePicker = connect(
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div contentEditable="false" onClick={() => setVisible(true)}>
|
<div contentEditable="false" onClick={() => setVisible(true)}>
|
||||||
@ -74,12 +86,13 @@ const MobileDateTimePicker = connect(
|
|||||||
onClick={() => setVisible(true)}
|
onClick={() => setVisible(true)}
|
||||||
value={value}
|
value={value}
|
||||||
picker={picker}
|
picker={picker}
|
||||||
{...rest}
|
{...others}
|
||||||
popupStyle={{ display: 'none' }}
|
popupStyle={{ display: 'none' }}
|
||||||
style={{ pointerEvents: 'none', width: '100%' }}
|
style={{ pointerEvents: 'none', width: '100%' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
|
{...others}
|
||||||
cancelText={t('Cancel')}
|
cancelText={t('Cancel')}
|
||||||
confirmText={t('Confirm')}
|
confirmText={t('Confirm')}
|
||||||
visible={visible}
|
visible={visible}
|
||||||
@ -87,10 +100,10 @@ const MobileDateTimePicker = connect(
|
|||||||
onClose={() => {
|
onClose={() => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
}}
|
}}
|
||||||
precision={showTime ? getPrecision(timeFormat) : picker === 'date' ? 'day' : picker}
|
precision={showTime && picker === 'date' ? getPrecision(timeFormat) : picker === 'date' ? 'day' : picker}
|
||||||
renderLabel={labelRenderer}
|
renderLabel={labelRenderer}
|
||||||
min={new Date(1000, 0, 1)}
|
min={others.min || new Date(1000, 0, 1)}
|
||||||
max={new Date(9999, 11, 31)}
|
max={others.max || new Date(9999, 11, 31)}
|
||||||
onConfirm={(val) => {
|
onConfirm={(val) => {
|
||||||
handleConfirm(val);
|
handleConfirm(val);
|
||||||
}}
|
}}
|
||||||
@ -102,4 +115,173 @@ const MobileDateTimePicker = connect(
|
|||||||
mapReadPretty(NBDatePicker.ReadPretty),
|
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 };
|
||||||
|
Loading…
x
Reference in New Issue
Block a user