: 移动端录单与筛选新设计 (#969)
Co-authored-by: sealday <zhanglin@daoyoucloud.com> Co-authored-by: hello@lv <2256334253@qq.com> Co-authored-by: bai.zixv <bai.zixv@foxmail.com> Reviewed-on: daoyoucloud/tachybase#951 Co-authored-by: wjh <wwwjh0710@163.com> Co-committed-by: wjh <wwwjh0710@163.com> Co-authored-by: wjh <wwwjh0710@163.com> Co-authored-by: bai.zixv <bai.zixv@foxmail.com> Reviewed-on: daoyoucloud/tachybase#969
This commit is contained in:
		
							parent
							
								
									0602743922
								
							
						
					
					
						commit
						d7b2695612
					
				| @ -35,6 +35,11 @@ import { | ||||
|   usePropsRelatedImageSearchItemField, | ||||
|   useTabSearchFieldItemProps, | ||||
|   useTabSearchFieldItemRelatedProps, | ||||
|   MInput, | ||||
|   MCheckbox, | ||||
|   MDatePicker, | ||||
|   MRadio, | ||||
|   MImageUploader, | ||||
| } from './schema'; | ||||
| import './bridge'; | ||||
| import './assets/svg'; | ||||
| @ -70,6 +75,11 @@ export const MobileCore: React.FC = (props) => { | ||||
|         ImageSearchItemView: ImageSearchItemView, | ||||
|         // NoticeBlock,
 | ||||
|         // NoticeBlockInitializer,
 | ||||
|         MInput, | ||||
|         MCheckbox, | ||||
|         MDatePicker, | ||||
|         MRadio, | ||||
|         MImageUploader, | ||||
|       }} | ||||
|       scope={{ | ||||
|         useGridCardBlockItemProps, | ||||
|  | ||||
| @ -0,0 +1,69 @@ | ||||
| import { useCollectionField } from '@tachybase/client'; | ||||
| import { connect, isValid, mapProps, mapReadPretty, useField } from '@tachybase/schema'; | ||||
| import { Checkbox, CheckboxGroupProps, CheckboxProps, Tag } from 'antd-mobile'; | ||||
| import React, { useEffect } from 'react'; | ||||
| type ComposedCheckBox = React.FC<CheckboxProps> & { | ||||
|   Group?: any; | ||||
| }; | ||||
| export const MCheckbox: ComposedCheckBox = connect( | ||||
|   Checkbox, | ||||
|   mapProps((props) => { | ||||
|     return { ...props }; | ||||
|   }), | ||||
| ); | ||||
| MCheckbox.Group = connect( | ||||
|   (props) => { | ||||
|     const collectionField = useCollectionField(); | ||||
|     const dataSource = collectionField?.uiSchema.enum || []; | ||||
|     const check = props.value || []; | ||||
|     return ( | ||||
|       <Checkbox.Group value={check}> | ||||
|         {dataSource.map((item, index) => { | ||||
|           return ( | ||||
|             <Checkbox | ||||
|               value={item.value} | ||||
|               key={index} | ||||
|               style={{ marginRight: '10px' }} | ||||
|               onChange={(status) => { | ||||
|                 if (status) { | ||||
|                   check.push(item.value); | ||||
|                 } else { | ||||
|                   const itemIndex = check.findIndex((cItem) => item.value?.toString() === cItem); | ||||
|                   check.splice(itemIndex, 1); | ||||
|                 } | ||||
|                 props.onChange(check); | ||||
|               }} | ||||
|             > | ||||
|               {item.label} | ||||
|             </Checkbox> | ||||
|           ); | ||||
|         })} | ||||
|       </Checkbox.Group> | ||||
|     ); | ||||
|   }, | ||||
|   mapProps((props: any, field: any) => { | ||||
|     return { ...props }; | ||||
|   }), | ||||
|   mapReadPretty((props) => { | ||||
|     if (!isValid(props.value)) { | ||||
|       return <div></div>; | ||||
|     } | ||||
|     const { value } = props; | ||||
|     const field = useField<any>(); | ||||
|     const collectionField = useCollectionField(); | ||||
|     const dataSource = field.dataSource || collectionField?.uiSchema.enum || []; | ||||
|     return ( | ||||
|       <div> | ||||
|         {dataSource | ||||
|           .filter((option) => option.value == value) | ||||
|           .map((option, key) => ( | ||||
|             <Tag key={key} color={option.color}> | ||||
|               {option.label} | ||||
|             </Tag> | ||||
|           ))} | ||||
|       </div> | ||||
|     ); | ||||
|   }), | ||||
| ); | ||||
| 
 | ||||
| export default MCheckbox; | ||||
| @ -0,0 +1 @@ | ||||
| export * from './Checkbox'; | ||||
| @ -0,0 +1,37 @@ | ||||
| import { dayjs } from '@tachybase/utils/client'; | ||||
| import { connect, mapProps } from '@tachybase/schema'; | ||||
| import { Button, DatePicker, Input, Space } from 'antd-mobile'; | ||||
| import React, { useState } from 'react'; | ||||
| 
 | ||||
| export const MDatePicker = connect( | ||||
|   (props) => { | ||||
|     const [visible, setVisible] = useState(false); | ||||
|     const nowDate = props.value || new Date(); | ||||
|     return ( | ||||
|       <> | ||||
|         <Button | ||||
|           onClick={() => { | ||||
|             setVisible(true); | ||||
|           }} | ||||
|         > | ||||
|           {dayjs(nowDate).format('YYYY-MM-DD')} | ||||
|         </Button> | ||||
| 
 | ||||
|         <DatePicker | ||||
|           visible={visible} | ||||
|           onClose={() => { | ||||
|             setVisible(false); | ||||
|           }} | ||||
|           onConfirm={(value) => { | ||||
|             props.onChange(value); | ||||
|             setVisible(false); | ||||
|           }} | ||||
|         /> | ||||
|       </> | ||||
|     ); | ||||
|   }, | ||||
|   mapProps((props) => { | ||||
|     return { ...props }; | ||||
|   }), | ||||
| ); | ||||
| export default MDatePicker; | ||||
| @ -0,0 +1 @@ | ||||
| export * from './DatePicker'; | ||||
| @ -0,0 +1,65 @@ | ||||
| import { useAPIClient } from '@tachybase/client'; | ||||
| import { connect, mapProps, mapReadPretty, useForm } from '@tachybase/schema'; | ||||
| import { ImageUploadItem, ImageUploader } from 'antd-mobile'; | ||||
| import React, { useEffect, useState } from 'react'; | ||||
| 
 | ||||
| export const MImageUploader = connect( | ||||
|   (props) => { | ||||
|     const [fileList, setFileList] = useState<ImageUploadItem[]>([]); | ||||
|     const [change, setChange] = useState(false); | ||||
|     const field = props.value || []; | ||||
|     useEffect(() => { | ||||
|       if (!change && props.value) { | ||||
|         const data = props.value.map((item) => { | ||||
|           return { url: item.url, key: item.id, thumbnailUrl: item.url }; | ||||
|         }); | ||||
|         setFileList(data); | ||||
|       } | ||||
|     }, [props.value]); | ||||
|     const api = useAPIClient(); | ||||
|     return ( | ||||
|       <ImageUploader | ||||
|         value={fileList} | ||||
|         upload={async (file) => { | ||||
|           const { name } = file; | ||||
|           const imageField = isImage(name); | ||||
|           if (imageField) { | ||||
|             const formData = new FormData(); | ||||
|             formData.append('file', file); | ||||
|             let result; | ||||
|             await api | ||||
|               .request({ url: props.action, method: 'post', data: formData }) | ||||
|               .then((res) => { | ||||
|                 result = res?.data?.data; | ||||
|                 field.push(result); | ||||
|               }) | ||||
|               .catch(() => {}); | ||||
|             return { | ||||
|               url: result?.url, | ||||
|               key: result?.id, | ||||
|               thumbnailUrl: result?.url, | ||||
|             }; | ||||
|           } | ||||
|         }} | ||||
|         onChange={(file) => { | ||||
|           setFileList(file); | ||||
|           setChange(true); | ||||
|           props.onChange(field); | ||||
|         }} | ||||
|       /> | ||||
|     ); | ||||
|   }, | ||||
|   mapProps((props) => { | ||||
|     return { ...props }; | ||||
|   }), | ||||
| ); | ||||
| export default MImageUploader; | ||||
| 
 | ||||
| export const isImage = (extName: string) => { | ||||
|   const reg = /\.(png|jpg|jpeg|gif|webp)$/i; | ||||
|   return reg.test(extName); | ||||
| }; | ||||
| 
 | ||||
| export const isPdf = (extName: string) => { | ||||
|   return extName.toLowerCase().endsWith('.pdf'); | ||||
| }; | ||||
| @ -0,0 +1 @@ | ||||
| export * from './ImageUploader'; | ||||
| @ -0,0 +1,23 @@ | ||||
| import { connect, mapProps } from '@tachybase/schema'; | ||||
| import { Input, InputProps, TextArea, TextAreaProps } from 'antd-mobile'; | ||||
| 
 | ||||
| type ComposedInput = React.FC<InputProps> & { | ||||
|   TextArea?: React.FC<TextAreaProps>; | ||||
| }; | ||||
| 
 | ||||
| export const MInput: ComposedInput = connect( | ||||
|   Input, | ||||
|   mapProps((props) => { | ||||
|     return { placeholder: '请输入内容', clearable: true, ...props }; | ||||
|   }), | ||||
| ); | ||||
| 
 | ||||
| const MTextArea = connect( | ||||
|   TextArea, | ||||
|   mapProps((props) => { | ||||
|     return { placeholder: '请输入内容', ...props }; | ||||
|   }), | ||||
| ); | ||||
| 
 | ||||
| MInput.TextArea = MTextArea; | ||||
| export default MInput; | ||||
| @ -0,0 +1 @@ | ||||
| export * from './Input'; | ||||
| @ -0,0 +1,67 @@ | ||||
| import { useCollectionField } from '@tachybase/client'; | ||||
| import { connect, isValid, mapProps, mapReadPretty, useField } from '@tachybase/schema'; | ||||
| import { Radio, RadioGroupProps, RadioProps, Tag } from 'antd-mobile'; | ||||
| import React, { useEffect } from 'react'; | ||||
| 
 | ||||
| type ComposedRadio = React.FC<RadioProps> & { | ||||
|   Group?: any; | ||||
| }; | ||||
| 
 | ||||
| export const MRadio: ComposedRadio = connect( | ||||
|   Radio, | ||||
|   mapProps({ | ||||
|     value: 'checked', | ||||
|     onInput: 'onChange', | ||||
|   }), | ||||
| ); | ||||
| 
 | ||||
| MRadio.Group = connect( | ||||
|   (props) => { | ||||
|     const collectionField = useCollectionField(); | ||||
|     const dataSource = collectionField?.uiSchema.enum || []; | ||||
|     return ( | ||||
|       <Radio.Group {...props}> | ||||
|         {dataSource.map((item, index) => { | ||||
|           return ( | ||||
|             <Radio value={item.value} key={index} style={{ marginRight: '10px' }}> | ||||
|               {item.label} | ||||
|             </Radio> | ||||
|           ); | ||||
|         })} | ||||
|       </Radio.Group> | ||||
|     ); | ||||
|   }, | ||||
|   mapProps((props: any, field: any) => { | ||||
|     useEffect(() => { | ||||
|       const defaultOption = field.dataSource?.find((option) => option.value == props.value); | ||||
|       if (defaultOption) { | ||||
|         field.setValue(defaultOption.value); | ||||
|       } | ||||
|     }, [props.value, field.dataSource]); | ||||
|     return { | ||||
|       ...props, | ||||
|     }; | ||||
|   }), | ||||
|   mapReadPretty((props) => { | ||||
|     if (!isValid(props.value)) { | ||||
|       return <div></div>; | ||||
|     } | ||||
|     const { value } = props; | ||||
|     const field = useField<any>(); | ||||
|     const collectionField = useCollectionField(); | ||||
|     const dataSource = field.dataSource || collectionField?.uiSchema.enum || []; | ||||
|     return ( | ||||
|       <div> | ||||
|         {dataSource | ||||
|           .filter((option) => option.value == value) | ||||
|           .map((option, key) => ( | ||||
|             <Tag key={key} color={option.color}> | ||||
|               {option.label} | ||||
|             </Tag> | ||||
|           ))} | ||||
|       </div> | ||||
|     ); | ||||
|   }), | ||||
| ); | ||||
| 
 | ||||
| export default MRadio; | ||||
| @ -0,0 +1 @@ | ||||
| export * from './Radio'; | ||||
| @ -0,0 +1,5 @@ | ||||
| export * from './Input'; | ||||
| export * from './Checkbox'; | ||||
| export * from './DatePicker'; | ||||
| export * from './ImageUploader'; | ||||
| export * from './Radio'; | ||||
| @ -4,6 +4,7 @@ import React, { useEffect } from 'react'; | ||||
| import { Navigate, useLocation, useNavigate, useParams } from 'react-router-dom'; | ||||
| import { ContainerDesigner } from './Container.Designer'; | ||||
| import useStyles from './style'; | ||||
| import { MobileProvider } from '../../provider/MobileProvider'; | ||||
| 
 | ||||
| const findGrid = (schema, uid) => { | ||||
|   return schema.reduceProperties((final, next) => { | ||||
| @ -52,6 +53,7 @@ const InternalContainer: React.FC = (props) => { | ||||
|   }, [location.pathname, navigate, params.name, redirectToUid]); | ||||
| 
 | ||||
|   return ( | ||||
|     <MobileProvider> | ||||
|       <SortableItem eid="nb-mobile-scroll-wrapper" className={cx('nb-mobile-container', styles.mobileContainer)}> | ||||
|         <Designer></Designer> | ||||
|         <div | ||||
| @ -83,6 +85,7 @@ const InternalContainer: React.FC = (props) => { | ||||
|           </div> | ||||
|         )} | ||||
|       </SortableItem> | ||||
|     </MobileProvider> | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -0,0 +1,10 @@ | ||||
| const custonComponent = { | ||||
|   Input: 'MInput', | ||||
|   'Input.TextArea': 'MInput.TextArea', | ||||
|   'Radio.Group': 'MRadio.Group', | ||||
|   'Checkbox.Group': 'MCheckbox.Group', | ||||
|   'Upload.Attachment': 'MImageUploader', | ||||
|   DatePicker: 'MDatePicker', | ||||
| }; | ||||
| 
 | ||||
| export const canMobileField = (componentName: string) => custonComponent[componentName]; | ||||
| @ -0,0 +1,146 @@ | ||||
| import { | ||||
|   AssociatedFields, | ||||
|   CompatibleSchemaInitializer, | ||||
|   ParentCollectionFields, | ||||
|   SchemaInitializerItemType, | ||||
|   formItemInitializers_deprecated, | ||||
|   gridRowColWrap, | ||||
|   useActionContext, | ||||
|   useCollectionManager_deprecated, | ||||
|   useCollection_deprecated, | ||||
| } from '@tachybase/client'; | ||||
| import { Schema, useForm } from '@tachybase/schema'; | ||||
| import { useIsMobile } from '../../hooks'; | ||||
| import { canMobileField } from './CustomComponent'; | ||||
| 
 | ||||
| export const useFormItemInitializerFields = (options?: any) => { | ||||
|   const { name, currentFields } = useCollection_deprecated(); | ||||
|   const isMobile = useIsMobile(); | ||||
|   const { getInterface, getCollection } = useCollectionManager_deprecated(); | ||||
|   const form = useForm(); | ||||
|   const { readPretty = form.readPretty, block = 'Form' } = options || {}; | ||||
|   const { fieldSchema } = useActionContext(); | ||||
|   const action = fieldSchema?.['x-action']; | ||||
| 
 | ||||
|   return currentFields | ||||
|     ?.filter((field) => field?.interface && !field?.isForeignKey && !field?.treeChildren) | ||||
|     ?.map((field) => { | ||||
|       const interfaceConfig = getInterface(field.interface); | ||||
|       const targetCollection = getCollection(field.target); | ||||
|       const isFileCollection = field?.target && getCollection(field?.target)?.template === 'file'; | ||||
|       const isAssociationField = targetCollection; | ||||
|       const fieldNames = field?.uiSchema['x-component-props']?.['fieldNames']; | ||||
|       const isMobileComponent = canMobileField(field.uiSchema['x-component']); | ||||
|       const schema = { | ||||
|         type: 'string', | ||||
|         name: field.name, | ||||
|         'x-toolbar': 'FormItemSchemaToolbar', | ||||
|         'x-settings': 'fieldSettings:FormItem', | ||||
|         'x-component': 'CollectionField', | ||||
|         'x-decorator': 'FormItem', | ||||
|         'x-collection-field': `${name}.${field.name}`, | ||||
|         'x-component-props': isFileCollection | ||||
|           ? { | ||||
|               fieldNames: { | ||||
|                 label: 'preview', | ||||
|                 value: 'id', | ||||
|               }, | ||||
|             } | ||||
|           : isAssociationField && fieldNames | ||||
|             ? { | ||||
|                 fieldNames: { ...fieldNames, label: targetCollection?.titleField || fieldNames.label }, | ||||
|               } | ||||
|             : {}, | ||||
|         'x-read-pretty': field?.uiSchema?.['x-read-pretty'], | ||||
|       }; | ||||
|       if (isMobile && isMobileComponent) { | ||||
|         schema['x-component-props']['component'] = isMobileComponent; | ||||
|       } | ||||
|       const resultItem = { | ||||
|         type: 'item', | ||||
|         name: field.name, | ||||
|         title: field?.uiSchema?.title || field.name, | ||||
|         Component: 'CollectionFieldInitializer', | ||||
|         remove: removeGridFormItem, | ||||
|         schemaInitialize: (s) => { | ||||
|           interfaceConfig?.schemaInitialize?.(s, { | ||||
|             field, | ||||
|             block, | ||||
|             readPretty, | ||||
|             action, | ||||
|             targetCollection, | ||||
|           }); | ||||
|         }, | ||||
|         schema, | ||||
|       } as SchemaInitializerItemType; | ||||
|       if (block == 'Kanban') { | ||||
|         resultItem['find'] = (schema: Schema, key: string, action: string) => { | ||||
|           const s = findSchema(schema, 'x-component', block); | ||||
|           return findSchema(s, key, action); | ||||
|         }; | ||||
|       } | ||||
| 
 | ||||
|       return resultItem; | ||||
|     }); | ||||
| }; | ||||
| 
 | ||||
| export const MobileFormItemInitializers = new CompatibleSchemaInitializer( | ||||
|   { | ||||
|     name: 'form:configureFields', | ||||
|     wrap: gridRowColWrap, | ||||
|     icon: 'SettingOutlined', | ||||
|     title: '{{t("Configure fields")}}', | ||||
|     items: [ | ||||
|       { | ||||
|         type: 'itemGroup', | ||||
|         name: 'displayFields', | ||||
|         title: '{{t("Display fields")}}', | ||||
|         useChildren: useFormItemInitializerFields, | ||||
|       }, | ||||
|       { | ||||
|         name: 'parentCollectionFields', | ||||
|         Component: ParentCollectionFields, | ||||
|       }, | ||||
|       { | ||||
|         name: 'associationFields', | ||||
|         Component: AssociatedFields, | ||||
|       }, | ||||
|       { | ||||
|         name: 'divider', | ||||
|         type: 'divider', | ||||
|       }, | ||||
|       { | ||||
|         name: 'addText', | ||||
|         title: '{{t("Add text")}}', | ||||
|         Component: 'MarkdownFormItemInitializer', | ||||
|       }, | ||||
|     ], | ||||
|   }, | ||||
|   formItemInitializers_deprecated, | ||||
| ); | ||||
| 
 | ||||
| export const removeGridFormItem = (schema, cb) => { | ||||
|   cb(schema, { | ||||
|     removeParentsIfNoChildren: true, | ||||
|     breakRemoveOn: { | ||||
|       'x-component': 'Grid', | ||||
|     }, | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| const findSchema = (schema: Schema, key: string, action: string) => { | ||||
|   if (!Schema.isSchemaInstance(schema)) return null; | ||||
|   return schema.reduceProperties((buf, s) => { | ||||
|     if (s[key] === action) { | ||||
|       return s; | ||||
|     } | ||||
|     if (s['x-component'] !== 'Action.Container' && s['x-component'] !== 'AssociationField.Viewer') { | ||||
|       const c = findSchema(s, key, action); | ||||
|       if (c) { | ||||
|         return c; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return buf; | ||||
|   }); | ||||
| }; | ||||
| @ -0,0 +1 @@ | ||||
| export * from './MobileFormItemInitializers'; | ||||
| @ -1,7 +1,7 @@ | ||||
| import { SchemaInitializer, useCollection, useCollectionManager } from '@tachybase/client'; | ||||
| import { useIsMobile } from '../tab-search/components/field-item/hooks'; | ||||
| import { canBeOptionalField, canBeRelatedField } from '../tab-search/utils'; | ||||
| import { createSchemaImageSearchItem } from './search-item/ImageSearchItem.schema'; | ||||
| import { useIsMobile } from '../../hooks'; | ||||
| 
 | ||||
| export const ImageSearchConfigureFields = new SchemaInitializer({ | ||||
|   name: 'ImageSearchView:configureFields', | ||||
|  | ||||
| @ -8,3 +8,5 @@ export * from './image-search'; | ||||
| export * from './swiper'; | ||||
| export * from './tab-search'; | ||||
| export * from './notice'; | ||||
| export * from './form'; | ||||
| export * from './antd-mobile'; | ||||
|  | ||||
| @ -1,10 +1,11 @@ | ||||
| import React, { useState } from 'react'; | ||||
| import { useTranslation } from '../../../../../../locale'; | ||||
| import { Grid, Divider, Picker, Input, Space, ActionSheet, DatePicker, CalendarPicker } from 'antd-mobile'; | ||||
| import { dayjs } from '@tachybase/utils/client'; | ||||
| import { ActionSheet, Button, Calendar, Divider, Grid, Input, Picker, Popup } from 'antd-mobile'; | ||||
| import { DownOutline } from 'antd-mobile-icons'; | ||||
| import type { Action } from 'antd-mobile/es/components/action-sheet'; | ||||
| import { changFormat, convertFormat } from '../../utils'; | ||||
| import { dayjs } from '@tachybase/utils/client'; | ||||
| import React, { useRef, useState } from 'react'; | ||||
| import { useTranslation } from '../../../../../../locale'; | ||||
| import { convertFormat } from '../../utils'; | ||||
| import { css } from '@tachybase/client'; | ||||
| 
 | ||||
| export const ISelect = (props) => { | ||||
|   const { options, onChange, customLabelKey } = props; | ||||
| @ -59,13 +60,34 @@ export const IDatePicker = (props) => { | ||||
|   const { options, value, onChange, onInputChange } = props; | ||||
|   const time = value.split('&'); | ||||
|   const [visible, setVisible] = useState(false); | ||||
|   const [clickCount, setClickCount] = useState(0); | ||||
|   const minDate = dayjs().subtract(10, 'year').toDate(); | ||||
|   const maxDate = dayjs().add(3, 'year').toDate(); | ||||
| 
 | ||||
|   const onClick = () => { | ||||
|     setVisible(true); | ||||
|   }; | ||||
| 
 | ||||
|   const onChangeDate = ([start, end]) => { | ||||
|     const startTime = dayjs(start).startOf('date').toISOString(); | ||||
|     const endTime = dayjs(end).endOf('date').toISOString(); | ||||
|     // TODO: 此处受上游影响,格式必须确定为这样, 时间有限,不再往上追查
 | ||||
|     const timeString = `"${startTime}"&"${endTime}"`; | ||||
|     onInputChange(timeString); | ||||
|     onChange(timeString); | ||||
| 
 | ||||
|     // XXX: UI 组件库这个组件是实验性组件, 此处模拟实现自动关闭浮层功能
 | ||||
|     if (clickCount > 0 && clickCount % 2 === 1) { | ||||
|       setVisible(false); | ||||
|       setClickCount(0); | ||||
|     } else { | ||||
|       setClickCount((preClickCount) => preClickCount + 1); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   return ( | ||||
|     <Grid.Item span={options.length > 1 ? 3 : 4}> | ||||
|       <div | ||||
|         onClick={() => { | ||||
|           setVisible(true); | ||||
|         }} | ||||
|       > | ||||
|       <div onClick={onClick}> | ||||
|         <Grid columns={5}> | ||||
|           <Grid.Item span={2} style={{ textAlign: 'end' }}> | ||||
|             {convertFormat(JSON.parse(time[0]))} | ||||
| @ -78,22 +100,24 @@ export const IDatePicker = (props) => { | ||||
|           </Grid.Item> | ||||
|         </Grid> | ||||
|       </div> | ||||
|       <CalendarPicker | ||||
|       <Popup | ||||
|         visible={visible} | ||||
|         selectionMode="range" | ||||
|         destroyOnClose | ||||
|         mask | ||||
|         closeOnMaskClick | ||||
|         onMaskClick={() => setVisible(false)} | ||||
|         onClose={() => setVisible(false)} | ||||
|         onConfirm={([start, end]) => { | ||||
|           const startTime = dayjs(start).startOf('date').toISOString(); | ||||
|           const endTime = dayjs(end).endOf('date').toISOString(); | ||||
| 
 | ||||
|           // TODO: 此处受上游影响,格式必须确定为这样, 时间有限,不再往上追查
 | ||||
|           const timeString = `"${startTime}"&"${endTime}"`; | ||||
| 
 | ||||
|           onInputChange(timeString); | ||||
|           onChange(timeString); | ||||
|         }} | ||||
|         // bodyStyle={{ height: '50vh' }}
 | ||||
|       > | ||||
|         <Calendar | ||||
|           selectionMode="range" | ||||
|           allowClear | ||||
|           min={minDate} | ||||
|           max={maxDate} | ||||
|           // defaultValue={defaultRange}
 | ||||
|           onChange={onChangeDate} | ||||
|         /> | ||||
|       </Popup> | ||||
|     </Grid.Item> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| @ -81,9 +81,3 @@ export const useTabSearchCollapsibleInputItem = () => { | ||||
|     onSelected, | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| export const useIsMobile = () => { | ||||
|   const fieldSchema = useFieldSchema(); | ||||
|   const isMobile = Object.values(fieldSchema.root.properties).some((value) => value['x-component'] === 'MContainer'); | ||||
|   return isMobile; | ||||
| }; | ||||
|  | ||||
| @ -14,7 +14,8 @@ import { | ||||
| import React, { useCallback } from 'react'; | ||||
| import { tval } from '../../../../../locale'; | ||||
| import { canBeDataField, canBeOptionalField, canBeRelatedField, canBeSearchField } from '../utils'; | ||||
| import { useIsMobile } from '../components/field-item/hooks'; | ||||
| import { useIsMobile } from '../../../hooks'; | ||||
| import { mapToMobile } from '../mapToMobile'; | ||||
| 
 | ||||
| interface ItemInterface { | ||||
|   field: FieldOptions; | ||||
| @ -159,7 +160,7 @@ function getItemInput(params: ItemInterface, isMobile?: boolean) { | ||||
|   // 然后排除其他类型的关联字段
 | ||||
|   if (isAssocField(field)) return null; | ||||
| 
 | ||||
|   if (!(canBeSearchField(_interface) && !canBeRelatedField(_interface) && !canBeDataField(_interface))) { | ||||
|   if (!canBeSearchField(_interface) && !canBeRelatedField(_interface) && !canBeDataField(_interface)) { | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
| @ -201,10 +202,7 @@ function getItemInput(params: ItemInterface, isMobile?: boolean) { | ||||
|       'x-collection-field': `${collectionName}.${schemaName}`, | ||||
|       // 'x-decorator': 'FormItem',
 | ||||
|       // 'x-component': 'CollectionField',
 | ||||
|       'x-component': matchTruthValue({ | ||||
|         ['TabSearchCollapsibleInputMItem']: isMobile, | ||||
|         ['TabSearchCollapsibleInputItem']: !isMobile, | ||||
|       }), | ||||
|       'x-component': mapToMobile('TabSearchCollapsibleInputItem'), | ||||
|       'x-component-props': { | ||||
|         fieldNames: { | ||||
|           label, | ||||
| @ -317,10 +315,7 @@ function getItemChoice(params: ItemInterface, isMobile?: boolean) { | ||||
|       'x-collection-field': `${collectionName}.${schemaName}`, | ||||
|       // 'x-decorator': 'FormItem',
 | ||||
|       // 'x-component': 'CollectionField',
 | ||||
|       'x-component': matchTruthValue({ | ||||
|         ['TabSearchFieldMItem']: isMobile, | ||||
|         ['TabSearchFieldItem']: !isMobile, | ||||
|       }), | ||||
|       'x-component': mapToMobile('TabSearchFieldItem'), | ||||
|       'x-component-props': { | ||||
|         fieldNames: { | ||||
|           label, | ||||
|  | ||||
| @ -1,9 +1,9 @@ | ||||
| import { SchemaInitializer, useCollection, useCollectionManager } from '@tachybase/client'; | ||||
| import { tval } from '../../../../../locale'; | ||||
| import { useIsMobile } from '../components/field-item/hooks'; | ||||
| import { createTabSearchItemSchema } from '../create/createTabSearchItemSchema'; | ||||
| import { canBeDataField, canBeOptionalField, canBeRelatedField, canBeSearchField } from '../utils'; | ||||
| import { TabSearchAssociatedFields } from './TabSearchAssociated.fields'; | ||||
| import { useIsMobile } from '../../../hooks'; | ||||
| 
 | ||||
| export const TabSearchFieldSchemaInitializer = new SchemaInitializer({ | ||||
|   name: 'tabSearch:configureFields', | ||||
|  | ||||
| @ -0,0 +1,9 @@ | ||||
| const customComponent = { | ||||
|   ['TabSearchCollapsibleInputItem']: 'TabSearchCollapsibleInputMItem', | ||||
|   ['useTabSearchFieldItemProps']: 'useTabSearchFieldItemRelatedProps', | ||||
|   ['TabSearchFieldItem']: 'TabSearchFieldMItem', | ||||
| }; | ||||
| 
 | ||||
| export function mapToMobile(componentName: string) { | ||||
|   return customComponent[componentName]; | ||||
| } | ||||
| @ -1 +1,2 @@ | ||||
| export * from './useSchemaPatch'; | ||||
| export * from './useIsMobile'; | ||||
|  | ||||
| @ -0,0 +1,7 @@ | ||||
| import { useContext } from 'react'; | ||||
| import { MobileContext } from '../provider/MobileProvider'; | ||||
| 
 | ||||
| export const useIsMobile = () => { | ||||
|   const ctx = useContext(MobileContext); | ||||
|   return ctx.isMobile; | ||||
| }; | ||||
| @ -31,11 +31,11 @@ export const mBlockInitializers_deprecated = new CompatibleSchemaInitializer({ | ||||
|           title: '{{t("Form")}}', | ||||
|           Component: 'FormBlockInitializer', | ||||
|         }, | ||||
|         // {
 | ||||
|         //   name: 'details',
 | ||||
|         //   title: '{{t("Details")}}',
 | ||||
|         //   Component: 'DetailsBlockInitializer',
 | ||||
|         // },
 | ||||
|         { | ||||
|           name: 'details', | ||||
|           title: '{{t("Details")}}', | ||||
|           Component: 'DetailsBlockInitializer', | ||||
|         }, | ||||
|         { | ||||
|           name: 'calendar', | ||||
|           title: '{{t("Calendar")}}', | ||||
| @ -96,11 +96,11 @@ export const mBlockInitializers = new CompatibleSchemaInitializer( | ||||
|             title: '{{t("Table")}}', | ||||
|             Component: 'TableBlockInitializer', | ||||
|           }, | ||||
|           // {
 | ||||
|           //   name: 'form',
 | ||||
|           //   title: '{{t("Form")}}',
 | ||||
|           //   Component: 'FormBlockInitializer',
 | ||||
|           // },
 | ||||
|           { | ||||
|             name: 'form', | ||||
|             title: '{{t("Form")}}', | ||||
|             Component: 'FormBlockInitializer', | ||||
|           }, | ||||
|           { | ||||
|             name: 'details', | ||||
|             title: '{{t("Details")}}', | ||||
|  | ||||
| @ -0,0 +1,7 @@ | ||||
| import React, { createContext } from 'react'; | ||||
| 
 | ||||
| export const MobileContext = createContext<any>({}); | ||||
| 
 | ||||
| export const MobileProvider = (props) => { | ||||
|   return <MobileContext.Provider value={{ isMobile: true }}> {props.children}</MobileContext.Provider>; | ||||
| }; | ||||
| @ -8,6 +8,7 @@ import { | ||||
|   ImageSearchItemFieldSettings, | ||||
|   mBlockInitializers, | ||||
|   mBlockInitializers_deprecated, | ||||
|   MobileFormItemInitializers, | ||||
|   TabSearchFieldSchemaInitializer, | ||||
|   TabSearchItemFieldSettings, | ||||
| } from './core/schema'; | ||||
| @ -27,7 +28,8 @@ export class MobileClientPlugin extends Plugin { | ||||
|     this.app.schemaInitializerManager.add(TabSearchFieldSchemaInitializer); | ||||
| 
 | ||||
|     this.app.schemaSettingsManager.add(ImageSearchItemFieldSettings); | ||||
|     this.schemaSettingsManager.add(TabSearchItemFieldSettings); | ||||
|     this.app.schemaSettingsManager.add(TabSearchItemFieldSettings); | ||||
|     this.app.schemaInitializerManager.add(MobileFormItemInitializers); | ||||
|   } | ||||
| 
 | ||||
|   addSettings() { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user