feat: 移动端筛选区块二期:支持更多类型 (#702)
Reviewed-on: daoyoucloud/tachycode#702 Co-authored-by: wjh <wwwjh0710@163.com> Co-committed-by: wjh <wwwjh0710@163.com>
This commit is contained in:
		
							parent
							
								
									ec406f25ef
								
							
						
					
					
						commit
						a1223960e2
					
				
							
								
								
									
										5
									
								
								.changeset/friendly-dingos-call.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.changeset/friendly-dingos-call.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| --- | ||||
| "@hera/plugin-mobile": patch | ||||
| --- | ||||
| 
 | ||||
| 新增mobile筛选类型 | ||||
| @ -73,7 +73,7 @@ export const SwiperFieldSettings = new SchemaSettings({ | ||||
|           dn.refresh(); | ||||
|         }; | ||||
|         return { | ||||
|           title: t('SetShowfield'), | ||||
|           title: t('Set show field'), | ||||
|           options, | ||||
|           value: fieldSchema['x-component-props'].fieldValue, | ||||
|           onChange, | ||||
| @ -86,6 +86,7 @@ export const SwiperFieldSettings = new SchemaSettings({ | ||||
|       useComponentProps() { | ||||
|         const fieldSchema = useFieldSchema(); | ||||
|         const { dn } = useDesignable(); | ||||
|         const { t } = useTranslation(); | ||||
|         const options = []; | ||||
|         for (let i = 1; i <= 20; i++) { | ||||
|           options.push({ | ||||
| @ -104,7 +105,7 @@ export const SwiperFieldSettings = new SchemaSettings({ | ||||
|           dn.refresh(); | ||||
|         }; | ||||
|         return { | ||||
|           title: 'setFieldCount', | ||||
|           title: t('Set field count'), | ||||
|           options, | ||||
|           value: fieldSchema['x-component-props'].pageSize || 5, | ||||
|           onChange, | ||||
|  | ||||
| @ -1,36 +1,74 @@ | ||||
| import { useMemo, useState } from 'react'; | ||||
| import { useFieldSchema } from '@nocobase/schema'; | ||||
| import { useTranslation } from '../../../../locale'; | ||||
| import { useCollection, useDesigner } from '@nocobase/client'; | ||||
| import { isTabSearchCollapsibleInputItem } from '../../utils'; | ||||
| import { useCollection, useCollectionManager, useDesignable, useDesigner } from '@nocobase/client'; | ||||
| import { canBeDataField, canBeRelatedField, convertFormat, isTabSearchCollapsibleInputItem } from '../../utils'; | ||||
| import { useTabSearchCollapsibleInputItem } from './hooks'; | ||||
| import { dayjs } from '@nocobase/utils/client'; | ||||
| 
 | ||||
| export const useTabSearchCollapsibleInputItemAction = (props) => { | ||||
|   const { onSelected } = useTabSearchCollapsibleInputItem(); | ||||
|   const fieldSchema = useFieldSchema(); | ||||
|   const collection = useCollection(); | ||||
|   const cm = useCollectionManager(); | ||||
|   const { dn } = useDesignable(); | ||||
|   const collectionField = useMemo(() => collection?.getField(fieldSchema.name as any), [collection, fieldSchema.name]); | ||||
|   const properties = fieldSchema.parent.properties; | ||||
|   const Designer = useDesigner(); | ||||
|   const [value, setValue] = useState(''); | ||||
|   const [customLabelKey, setCustomLabelKey] = useState(fieldSchema['name'] as string); | ||||
| 
 | ||||
|   const [fieldInterface, setFieldInterface] = useState(fieldSchema['x-component-props'].interface); | ||||
|   const defaultValue = canBeDataField(fieldInterface) | ||||
|     ? convertFormat(new Date()) + '&' + convertFormat(new Date()) | ||||
|     : ''; | ||||
|   const [value, setValue] = useState(defaultValue); | ||||
|   const options = Object.values(properties) | ||||
|     .map((option) => { | ||||
|       if (isTabSearchCollapsibleInputItem(option['x-component'])) { | ||||
|         return { | ||||
|           label: option['title'], | ||||
|           value: option['name'], | ||||
|           interface: option['x-component-props'].interface, | ||||
|         }; | ||||
|       } | ||||
|     }) | ||||
|     .filter(Boolean); | ||||
| 
 | ||||
|   const onSelect = (value) => { | ||||
|     onSelected([value], customLabelKey); | ||||
|     let time; | ||||
|     let filterKey = `${customLabelKey}.$includes`; | ||||
|     if (canBeDataField(fieldInterface)) { | ||||
|       time = value.split('&'); | ||||
|       filterKey = `${customLabelKey}.$dateBetween`; | ||||
|     } | ||||
|     if (canBeRelatedField(fieldInterface)) { | ||||
|       const label = fieldSchema['x-component-props']['fieldNames'].label; | ||||
|       const correlation = fieldSchema['x-component-props']['correlation']; | ||||
|       filterKey = `${correlation}.${label}.$includes`; | ||||
|     } | ||||
|     onSelected(time || [value], filterKey); | ||||
|   }; | ||||
| 
 | ||||
|   const onSelectChange = (label) => setCustomLabelKey(label as string); | ||||
|   const onSelectChange = (label) => { | ||||
|     const type = Object.values(properties).find((value) => value.name === label)['x-component-props'].interface; | ||||
|     if (canBeRelatedField(type)) { | ||||
|       const titleField = cm.getCollection(collection.name + '.' + label).titleField; | ||||
|       fieldSchema['x-component-props']['correlation'] = label; | ||||
|       fieldSchema['x-component-props']['fieldNames'] = { label: titleField }; | ||||
|       dn.emit('patch', { | ||||
|         schema: { | ||||
|           ['u-id']: fieldSchema['u-id'], | ||||
|           ['x-component-props']: fieldSchema['x-component-props'], | ||||
|         }, | ||||
|       }); | ||||
|       dn.refresh(); | ||||
|     } | ||||
|     setFieldInterface(type); | ||||
|     if (canBeDataField(type)) { | ||||
|       setValue(convertFormat(new Date()) + '&' + convertFormat(new Date())); | ||||
|     } else { | ||||
|       setValue(''); | ||||
|     } | ||||
|     setCustomLabelKey(label as string); | ||||
|   }; | ||||
|   const onInputChange = (v) => { | ||||
|     const inputValue = v.target?.value || v; | ||||
|     setValue(inputValue); | ||||
| @ -41,6 +79,20 @@ export const useTabSearchCollapsibleInputItemAction = (props) => { | ||||
|   const onButtonClick = () => { | ||||
|     onSelect(value); | ||||
|   }; | ||||
|   const onDateClick = (time) => { | ||||
|     onSelect(time); | ||||
|   }; | ||||
| 
 | ||||
|   return { collectionField, Designer, options, value, onSelectChange, onInputChange, onButtonClick, customLabelKey }; | ||||
|   return { | ||||
|     collectionField, | ||||
|     Designer, | ||||
|     options, | ||||
|     value, | ||||
|     onSelectChange, | ||||
|     onInputChange, | ||||
|     onButtonClick, | ||||
|     customLabelKey, | ||||
|     fieldInterface, | ||||
|     onDateClick, | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| @ -2,24 +2,40 @@ import { SortableItem, withDynamicSchemaProps } from '@nocobase/client'; | ||||
| import { Grid } from 'antd-mobile'; | ||||
| import React from 'react'; | ||||
| import { useTabSearchCollapsibleInputItemAction } from './TabSearchCollapsibleInputItemAction'; | ||||
| import { IButton, IInput, ISelect } from './TabSearchCollapsibleInputMItemChild'; | ||||
| import { IButton, IDatePicker, IInput, ISelect } from './TabSearchCollapsibleInputMItemChild'; | ||||
| import { canBeDataField } from '../../utils'; | ||||
| 
 | ||||
| export const TabSearchCollapsibleInputMItem = withDynamicSchemaProps( | ||||
|   (props) => { | ||||
|     const { collectionField, Designer, options, value, onSelectChange, onInputChange, onButtonClick, customLabelKey } = | ||||
|       useTabSearchCollapsibleInputItemAction(props); | ||||
|     const { | ||||
|       collectionField, | ||||
|       Designer, | ||||
|       options, | ||||
|       value, | ||||
|       onSelectChange, | ||||
|       onInputChange, | ||||
|       onButtonClick, | ||||
|       customLabelKey, | ||||
|       fieldInterface, | ||||
|       onDateClick, | ||||
|     } = useTabSearchCollapsibleInputItemAction(props); | ||||
| 
 | ||||
|     if (!collectionField) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <SortableItem> | ||||
|         <Designer /> | ||||
|         <Grid columns={4} style={{ backgroundColor: '#f9f9f9', borderRadius: '5px', margin: '5px 0 0 0' }}> | ||||
|           <ISelect options={options} onChange={onSelectChange} customLabelKey={customLabelKey} /> | ||||
|           <IInput options={options} value={value} onChange={onInputChange} /> | ||||
|           <IButton onClick={onButtonClick} /> | ||||
|           {canBeDataField(fieldInterface) ? ( | ||||
|             <IDatePicker value={value} onChange={onDateClick} onInputChange={onInputChange} options={options} /> | ||||
|           ) : ( | ||||
|             <> | ||||
|               <IInput options={options} value={value} onChange={onInputChange} /> | ||||
|               <IButton onClick={onButtonClick} /> | ||||
|             </> | ||||
|           )} | ||||
|         </Grid> | ||||
|       </SortableItem> | ||||
|     ); | ||||
|  | ||||
| @ -1,8 +1,9 @@ | ||||
| import React, { useState } from 'react'; | ||||
| import { useTranslation } from '../../../../locale'; | ||||
| import { Grid, Divider, Picker, Input, Space, ActionSheet } from 'antd-mobile'; | ||||
| import { Grid, Divider, Picker, Input, Space, ActionSheet, DatePicker, CalendarPicker } from 'antd-mobile'; | ||||
| import { DownOutline } from 'antd-mobile-icons'; | ||||
| import type { Action } from 'antd-mobile/es/components/action-sheet'; | ||||
| import { convertFormat } from '../../utils'; | ||||
| 
 | ||||
| export const ISelect = (props) => { | ||||
|   const { options, onChange, customLabelKey } = props; | ||||
| @ -53,6 +54,43 @@ export const ISelect = (props) => { | ||||
|   ) : null; | ||||
| }; | ||||
| 
 | ||||
| export const IDatePicker = (props) => { | ||||
|   const { options, value, onChange, onInputChange } = props; | ||||
|   const time = value.split('&'); | ||||
|   const [visible, setVisible] = useState(false); | ||||
|   return ( | ||||
|     <Grid.Item span={options.length > 1 ? 3 : 4}> | ||||
|       <div | ||||
|         onClick={() => { | ||||
|           setVisible(true); | ||||
|         }} | ||||
|       > | ||||
|         <Grid columns={5}> | ||||
|           <Grid.Item span={2} style={{ textAlign: 'end' }}> | ||||
|             {time[0]} | ||||
|           </Grid.Item> | ||||
|           <Grid.Item span={1} style={{ textAlign: 'center' }}> | ||||
|             - | ||||
|           </Grid.Item> | ||||
|           <Grid.Item span={2} style={{ textAlign: 'start' }}> | ||||
|             {time[1]} | ||||
|           </Grid.Item> | ||||
|         </Grid> | ||||
|       </div> | ||||
|       <CalendarPicker | ||||
|         visible={visible} | ||||
|         selectionMode="range" | ||||
|         onMaskClick={() => setVisible(false)} | ||||
|         onClose={() => setVisible(false)} | ||||
|         onConfirm={(v) => { | ||||
|           onInputChange(convertFormat(v[0]) + '&' + convertFormat(v[1])); | ||||
|           onChange(convertFormat(v[0]) + '&' + convertFormat(v[1])); | ||||
|         }} | ||||
|       /> | ||||
|     </Grid.Item> | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| export const IInput = (props) => { | ||||
|   const { options, value, onChange } = props; | ||||
|   const { t } = useTranslation(); | ||||
| @ -71,7 +109,6 @@ export const IInput = (props) => { | ||||
| export const IButton = (props) => { | ||||
|   const { onClick } = props; | ||||
|   const { t } = useTranslation(); | ||||
| 
 | ||||
|   return ( | ||||
|     <Grid.Item onClick={onClick} style={{ color: '#2c6eff', textAlign: 'center', cursor: 'pointer' }}> | ||||
|       {t('Search')} | ||||
|  | ||||
| @ -15,7 +15,7 @@ export const useTabSearchFieldItemAction = (props) => { | ||||
|   const labelKey = _labelKey || fieldSchema['x-component-props']?.fieldNames?.label || valueKey; | ||||
|   const onSelect = (itemKey) => { | ||||
|     const key = itemKey.keyPath?.[0] || itemKey; | ||||
|     onSelected([key], null, filterKey); | ||||
|     onSelected([key], filterKey); | ||||
|   }; | ||||
|   const fieldNames = { | ||||
|     title: labelKey || valueKey, | ||||
|  | ||||
| @ -8,7 +8,6 @@ import { canBeOptionalField } from '../../utils'; | ||||
| export const useTabSearchFieldItemProps = () => { | ||||
|   const fieldSchema = useFieldSchema(); | ||||
|   const collection = useCollection(); | ||||
| 
 | ||||
|   const optionalFieldList = useOptionalFieldList(); | ||||
|   const cm = useCollectionManager(); | ||||
|   const collectionField = useMemo(() => collection?.getField(fieldSchema.name as any), [collection, fieldSchema.name]); | ||||
|  | ||||
| @ -9,16 +9,13 @@ export const useTabSearchCollapsibleInputItem = () => { | ||||
|   const { getDataBlocks } = useFilterBlock(); | ||||
|   const collectionField = useMemo(() => collection?.getField(fieldSchema.name as any), [collection, fieldSchema.name]); | ||||
| 
 | ||||
|   const onSelected = (value, customLabelKey?, filterKey?) => { | ||||
|   const onSelected = (value, filterKey) => { | ||||
|     const { targets, uid } = findFilterTargets(fieldSchema); | ||||
|     getDataBlocks().forEach((block) => { | ||||
|       const target = targets.find((target) => target.uid === block.uid); | ||||
|       if (!target) return; | ||||
|       const key = `${uid}${fieldSchema.name}`; | ||||
|       const param = block.service.params?.[0] || {}; | ||||
|       if (!collectionField?.target && customLabelKey) { | ||||
|         filterKey = `${customLabelKey}.$includes`; | ||||
|       } | ||||
|       // 保留原有的 filter
 | ||||
|       let storedFilter = block.service.params?.[1]?.filters || {}; | ||||
|       if (value.length) { | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| export const createTabSearchItemSchema = (options) => { | ||||
|   const { field, itemComponent, label, itemUseProps } = options; | ||||
|   const { field, itemComponent, label, itemUseProps, collection } = options; | ||||
|   return { | ||||
|     name: field.key, | ||||
|     title: field.uiSchema?.title, | ||||
| @ -17,6 +17,8 @@ export const createTabSearchItemSchema = (options) => { | ||||
|           label, | ||||
|         }, | ||||
|         interface: field.interface, | ||||
|         collectionName: collection?.name, | ||||
|         correlation: field.name, | ||||
|       }, | ||||
|       properties: {}, | ||||
|     }, | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| import { SchemaInitializer, SchemaInitializerItemType, useCollection, useCollectionManager } from '@nocobase/client'; | ||||
| import _ from 'lodash'; | ||||
| import { tval } from '../../../locale'; | ||||
| import { canBeOptionalField, canBeRelatedField, canBeSearchField } from '../utils'; | ||||
| import { canBeDataField, canBeOptionalField, canBeRelatedField, canBeSearchField } from '../utils'; | ||||
| import { createTabSearchItemSchema } from '../create/createTabSearchItemSchema'; | ||||
| import { useIsMobile } from '../components/field-item/hooks'; | ||||
| 
 | ||||
| @ -12,13 +12,19 @@ const textFieldsItem: SchemaInitializerItemType<any> = { | ||||
|   useChildren() { | ||||
|     const collection = useCollection(); | ||||
|     const associatedFields = collection.fields; | ||||
|     const cm = useCollectionManager(); | ||||
|     const isMobield = useIsMobile(); | ||||
|     const itemComponent = isMobield ? 'TabSearchCollapsibleInputMItem' : 'TabSearchCollapsibleInputItem'; | ||||
|     const children = associatedFields | ||||
|       .map((field) => { | ||||
|         if (canBeSearchField(field.interface) || (canBeOptionalField(field.interface) && !field['isForeignKey'])) { | ||||
|           const label = field.name; | ||||
|           return createTabSearchItemSchema({ field, itemComponent, label }); | ||||
|         if ( | ||||
|           !field['isForeignKey'] && | ||||
|           (canBeSearchField(field.interface) || canBeRelatedField(field.interface) || canBeDataField(field.interface)) | ||||
|         ) { | ||||
|           const label = canBeRelatedField(field.interface) | ||||
|             ? cm.getCollection(`${collection.name}.${field.name}`).titleField | ||||
|             : field.name; | ||||
|           return createTabSearchItemSchema({ field, itemComponent, label, collection }); | ||||
|         } | ||||
|       }) | ||||
|       .filter(Boolean); | ||||
|  | ||||
| @ -72,8 +72,12 @@ export const TabSearchItemFieldSettings = new SchemaSettings({ | ||||
|               const { t } = useTranslation(); | ||||
|               const cm = useCollectionManager(); | ||||
|               const c = useCollection(); | ||||
|               const fieldCollection = fieldSchema['x-component-props']?.['collectionName']; | ||||
|               const correlation = fieldSchema['x-component-props']?.['correlation']; | ||||
|               const collectionField = | ||||
|                 c.getField(fieldSchema['name']) || cm.getCollectionField(fieldSchema['x-collection-field']); | ||||
|                 c.getField(fieldSchema['name']) || | ||||
|                 cm.getCollectionField(fieldSchema['x-collection-field']) || | ||||
|                 cm.getCollection(fieldCollection + '.' + correlation); | ||||
|               const compile = useCompile(); | ||||
|               const { dn } = useDesignable(); | ||||
|               const targetFields = collectionField?.target ? cm.getCollectionFields(collectionField?.target) : []; | ||||
| @ -87,6 +91,7 @@ export const TabSearchItemFieldSettings = new SchemaSettings({ | ||||
|                 const schema = { | ||||
|                   ['x-uid']: fieldSchema['x-uid'], | ||||
|                 }; | ||||
| 
 | ||||
|                 const fieldNames = { | ||||
|                   ...collectionField?.uiSchema?.['x-component-props']?.['fieldNames'], | ||||
|                   ...fieldSchema['x-component-props']?.['fieldNames'], | ||||
| @ -94,6 +99,7 @@ export const TabSearchItemFieldSettings = new SchemaSettings({ | ||||
|                 }; | ||||
|                 fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {}; | ||||
|                 fieldSchema['x-component-props']['fieldNames'] = fieldNames; | ||||
| 
 | ||||
|                 schema['x-component-props'] = fieldSchema['x-component-props']; | ||||
|                 dn.emit('patch', { | ||||
|                   schema, | ||||
| @ -111,7 +117,10 @@ export const TabSearchItemFieldSettings = new SchemaSettings({ | ||||
|             }, | ||||
|             useVisible() { | ||||
|               const fieldSchema = useFieldSchema(); | ||||
|               return !isTabSearchCollapsibleInputItem(fieldSchema['x-component']); | ||||
|               return ( | ||||
|                 isTabSearchCollapsibleInputItem(fieldSchema['x-component']) || | ||||
|                 fieldSchema['x-component-props']?.['correlation'] | ||||
|               ); | ||||
|             }, | ||||
|           }, | ||||
|         ]; | ||||
|  | ||||
| @ -1,5 +1,8 @@ | ||||
| import { dayjs } from '@nocobase/utils/client'; | ||||
| 
 | ||||
| const canBeOptionalFields = ['select', 'multipleSelect', 'radioGroup', 'checkboxGroup']; | ||||
| const canBeRelatedFields = ['oho', 'obo', 'o2m', 'm2o', 'm2m']; | ||||
| const canBeDataFields = ['datetime']; | ||||
| 
 | ||||
| const canBeSearchFields = [ | ||||
|   'input', | ||||
| @ -22,5 +25,9 @@ export const isTabSearchCollapsibleInputItem = (component: string) => | ||||
| 
 | ||||
| export const canBeOptionalField = (_interface: string) => canBeOptionalFields.includes(_interface); | ||||
| export const canBeRelatedField = (_interface: string) => canBeRelatedFields.includes(_interface); | ||||
| 
 | ||||
| export const canBeDataField = (_interface: string) => canBeDataFields.includes(_interface); | ||||
| export const canBeSearchField = (_interface: string) => canBeSearchFields.includes(_interface); | ||||
| 
 | ||||
| export const convertFormat = (currentDate) => { | ||||
|   return dayjs(currentDate).format('YYYY-MM-DD'); | ||||
| }; | ||||
|  | ||||
							
								
								
									
										14
									
								
								packages/plugins/@hera/plugin-mobile/src/locale/en-US.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								packages/plugins/@hera/plugin-mobile/src/locale/en-US.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| { | ||||
|   "Choices fields":"Choices fields", | ||||
|   "Text fields":"Text fields", | ||||
|   "Generic properties":"Generic properties", | ||||
|   "Search":"Search", | ||||
|   "Please enter search content":"Please enter search content", | ||||
|   "Title field":"Title field", | ||||
|   "Set show field":"Set show field", | ||||
|   "Set field count":"Set field count", | ||||
|   "Configure field": "Configure field", | ||||
|   "Configure fields": "Configure fields", | ||||
|   "Configure columns": "Configure columns", | ||||
|   "Add block": "Add block" | ||||
| } | ||||
							
								
								
									
										14
									
								
								packages/plugins/@hera/plugin-mobile/src/locale/zh-CN.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								packages/plugins/@hera/plugin-mobile/src/locale/zh-CN.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| { | ||||
|   "Choices fields":"选择字段", | ||||
|   "Text fields":"文本字段", | ||||
|   "Generic properties":"通用属性", | ||||
|   "Search":"搜索", | ||||
|   "Please enter search content":"请输入查询内容", | ||||
|   "Title field":"标题字段", | ||||
|   "Set show field":"设置展示字段", | ||||
|   "Set field count":"设置展示数量", | ||||
|   "Configure field": "配置字段", | ||||
|   "Configure fields": "配置字段", | ||||
|   "Configure columns": "配置字段", | ||||
|   "Add block": "创建区块" | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user