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",
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"npmClientArgs": [
|
||||
"--ignore-engines"
|
||||
],
|
||||
"npmClientArgs": ["--ignore-engines"],
|
||||
"command": {
|
||||
"version": {
|
||||
"forcePublish": true,
|
||||
|
@ -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;
|
||||
|
@ -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 = () => {
|
||||
|
@ -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 };
|
||||
|
Loading…
x
Reference in New Issue
Block a user