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:
wjh 2024-04-12 22:03:56 +08:00 committed by sealday
parent ec406f25ef
commit a1223960e2
14 changed files with 191 additions and 32 deletions

View File

@ -0,0 +1,5 @@
---
"@hera/plugin-mobile": patch
---
新增mobile筛选类型

View File

@ -73,7 +73,7 @@ export const SwiperFieldSettings = new SchemaSettings({
dn.refresh(); dn.refresh();
}; };
return { return {
title: t('SetShowfield'), title: t('Set show field'),
options, options,
value: fieldSchema['x-component-props'].fieldValue, value: fieldSchema['x-component-props'].fieldValue,
onChange, onChange,
@ -86,6 +86,7 @@ export const SwiperFieldSettings = new SchemaSettings({
useComponentProps() { useComponentProps() {
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const { dn } = useDesignable(); const { dn } = useDesignable();
const { t } = useTranslation();
const options = []; const options = [];
for (let i = 1; i <= 20; i++) { for (let i = 1; i <= 20; i++) {
options.push({ options.push({
@ -104,7 +105,7 @@ export const SwiperFieldSettings = new SchemaSettings({
dn.refresh(); dn.refresh();
}; };
return { return {
title: 'setFieldCount', title: t('Set field count'),
options, options,
value: fieldSchema['x-component-props'].pageSize || 5, value: fieldSchema['x-component-props'].pageSize || 5,
onChange, onChange,

View File

@ -1,36 +1,74 @@
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { useFieldSchema } from '@nocobase/schema'; import { useFieldSchema } from '@nocobase/schema';
import { useTranslation } from '../../../../locale'; import { useCollection, useCollectionManager, useDesignable, useDesigner } from '@nocobase/client';
import { useCollection, useDesigner } from '@nocobase/client'; import { canBeDataField, canBeRelatedField, convertFormat, isTabSearchCollapsibleInputItem } from '../../utils';
import { isTabSearchCollapsibleInputItem } from '../../utils';
import { useTabSearchCollapsibleInputItem } from './hooks'; import { useTabSearchCollapsibleInputItem } from './hooks';
import { dayjs } from '@nocobase/utils/client';
export const useTabSearchCollapsibleInputItemAction = (props) => { export const useTabSearchCollapsibleInputItemAction = (props) => {
const { onSelected } = useTabSearchCollapsibleInputItem(); const { onSelected } = useTabSearchCollapsibleInputItem();
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const collection = useCollection(); const collection = useCollection();
const cm = useCollectionManager();
const { dn } = useDesignable();
const collectionField = useMemo(() => collection?.getField(fieldSchema.name as any), [collection, fieldSchema.name]); const collectionField = useMemo(() => collection?.getField(fieldSchema.name as any), [collection, fieldSchema.name]);
const properties = fieldSchema.parent.properties; const properties = fieldSchema.parent.properties;
const Designer = useDesigner(); const Designer = useDesigner();
const [value, setValue] = useState('');
const [customLabelKey, setCustomLabelKey] = useState(fieldSchema['name'] as string); 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) const options = Object.values(properties)
.map((option) => { .map((option) => {
if (isTabSearchCollapsibleInputItem(option['x-component'])) { if (isTabSearchCollapsibleInputItem(option['x-component'])) {
return { return {
label: option['title'], label: option['title'],
value: option['name'], value: option['name'],
interface: option['x-component-props'].interface,
}; };
} }
}) })
.filter(Boolean); .filter(Boolean);
const onSelect = (value) => { 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 onInputChange = (v) => {
const inputValue = v.target?.value || v; const inputValue = v.target?.value || v;
setValue(inputValue); setValue(inputValue);
@ -41,6 +79,20 @@ export const useTabSearchCollapsibleInputItemAction = (props) => {
const onButtonClick = () => { const onButtonClick = () => {
onSelect(value); 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,
};
}; };

View File

@ -2,24 +2,40 @@ import { SortableItem, withDynamicSchemaProps } from '@nocobase/client';
import { Grid } from 'antd-mobile'; import { Grid } from 'antd-mobile';
import React from 'react'; import React from 'react';
import { useTabSearchCollapsibleInputItemAction } from './TabSearchCollapsibleInputItemAction'; 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( export const TabSearchCollapsibleInputMItem = withDynamicSchemaProps(
(props) => { (props) => {
const { collectionField, Designer, options, value, onSelectChange, onInputChange, onButtonClick, customLabelKey } = const {
useTabSearchCollapsibleInputItemAction(props); collectionField,
Designer,
options,
value,
onSelectChange,
onInputChange,
onButtonClick,
customLabelKey,
fieldInterface,
onDateClick,
} = useTabSearchCollapsibleInputItemAction(props);
if (!collectionField) { if (!collectionField) {
return null; return null;
} }
return ( return (
<SortableItem> <SortableItem>
<Designer /> <Designer />
<Grid columns={4} style={{ backgroundColor: '#f9f9f9', borderRadius: '5px', margin: '5px 0 0 0' }}> <Grid columns={4} style={{ backgroundColor: '#f9f9f9', borderRadius: '5px', margin: '5px 0 0 0' }}>
<ISelect options={options} onChange={onSelectChange} customLabelKey={customLabelKey} /> <ISelect options={options} onChange={onSelectChange} customLabelKey={customLabelKey} />
<IInput options={options} value={value} onChange={onInputChange} /> {canBeDataField(fieldInterface) ? (
<IButton onClick={onButtonClick} /> <IDatePicker value={value} onChange={onDateClick} onInputChange={onInputChange} options={options} />
) : (
<>
<IInput options={options} value={value} onChange={onInputChange} />
<IButton onClick={onButtonClick} />
</>
)}
</Grid> </Grid>
</SortableItem> </SortableItem>
); );

View File

@ -1,8 +1,9 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useTranslation } from '../../../../locale'; 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 { DownOutline } from 'antd-mobile-icons';
import type { Action } from 'antd-mobile/es/components/action-sheet'; import type { Action } from 'antd-mobile/es/components/action-sheet';
import { convertFormat } from '../../utils';
export const ISelect = (props) => { export const ISelect = (props) => {
const { options, onChange, customLabelKey } = props; const { options, onChange, customLabelKey } = props;
@ -53,6 +54,43 @@ export const ISelect = (props) => {
) : null; ) : 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) => { export const IInput = (props) => {
const { options, value, onChange } = props; const { options, value, onChange } = props;
const { t } = useTranslation(); const { t } = useTranslation();
@ -71,7 +109,6 @@ export const IInput = (props) => {
export const IButton = (props) => { export const IButton = (props) => {
const { onClick } = props; const { onClick } = props;
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<Grid.Item onClick={onClick} style={{ color: '#2c6eff', textAlign: 'center', cursor: 'pointer' }}> <Grid.Item onClick={onClick} style={{ color: '#2c6eff', textAlign: 'center', cursor: 'pointer' }}>
{t('Search')} {t('Search')}

View File

@ -15,7 +15,7 @@ export const useTabSearchFieldItemAction = (props) => {
const labelKey = _labelKey || fieldSchema['x-component-props']?.fieldNames?.label || valueKey; const labelKey = _labelKey || fieldSchema['x-component-props']?.fieldNames?.label || valueKey;
const onSelect = (itemKey) => { const onSelect = (itemKey) => {
const key = itemKey.keyPath?.[0] || itemKey; const key = itemKey.keyPath?.[0] || itemKey;
onSelected([key], null, filterKey); onSelected([key], filterKey);
}; };
const fieldNames = { const fieldNames = {
title: labelKey || valueKey, title: labelKey || valueKey,

View File

@ -8,7 +8,6 @@ import { canBeOptionalField } from '../../utils';
export const useTabSearchFieldItemProps = () => { export const useTabSearchFieldItemProps = () => {
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const collection = useCollection(); const collection = useCollection();
const optionalFieldList = useOptionalFieldList(); const optionalFieldList = useOptionalFieldList();
const cm = useCollectionManager(); const cm = useCollectionManager();
const collectionField = useMemo(() => collection?.getField(fieldSchema.name as any), [collection, fieldSchema.name]); const collectionField = useMemo(() => collection?.getField(fieldSchema.name as any), [collection, fieldSchema.name]);

View File

@ -9,16 +9,13 @@ export const useTabSearchCollapsibleInputItem = () => {
const { getDataBlocks } = useFilterBlock(); const { getDataBlocks } = useFilterBlock();
const collectionField = useMemo(() => collection?.getField(fieldSchema.name as any), [collection, fieldSchema.name]); 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); const { targets, uid } = findFilterTargets(fieldSchema);
getDataBlocks().forEach((block) => { getDataBlocks().forEach((block) => {
const target = targets.find((target) => target.uid === block.uid); const target = targets.find((target) => target.uid === block.uid);
if (!target) return; if (!target) return;
const key = `${uid}${fieldSchema.name}`; const key = `${uid}${fieldSchema.name}`;
const param = block.service.params?.[0] || {}; const param = block.service.params?.[0] || {};
if (!collectionField?.target && customLabelKey) {
filterKey = `${customLabelKey}.$includes`;
}
// 保留原有的 filter // 保留原有的 filter
let storedFilter = block.service.params?.[1]?.filters || {}; let storedFilter = block.service.params?.[1]?.filters || {};
if (value.length) { if (value.length) {

View File

@ -1,5 +1,5 @@
export const createTabSearchItemSchema = (options) => { export const createTabSearchItemSchema = (options) => {
const { field, itemComponent, label, itemUseProps } = options; const { field, itemComponent, label, itemUseProps, collection } = options;
return { return {
name: field.key, name: field.key,
title: field.uiSchema?.title, title: field.uiSchema?.title,
@ -17,6 +17,8 @@ export const createTabSearchItemSchema = (options) => {
label, label,
}, },
interface: field.interface, interface: field.interface,
collectionName: collection?.name,
correlation: field.name,
}, },
properties: {}, properties: {},
}, },

View File

@ -1,7 +1,7 @@
import { SchemaInitializer, SchemaInitializerItemType, useCollection, useCollectionManager } from '@nocobase/client'; import { SchemaInitializer, SchemaInitializerItemType, useCollection, useCollectionManager } from '@nocobase/client';
import _ from 'lodash'; import _ from 'lodash';
import { tval } from '../../../locale'; import { tval } from '../../../locale';
import { canBeOptionalField, canBeRelatedField, canBeSearchField } from '../utils'; import { canBeDataField, canBeOptionalField, canBeRelatedField, canBeSearchField } from '../utils';
import { createTabSearchItemSchema } from '../create/createTabSearchItemSchema'; import { createTabSearchItemSchema } from '../create/createTabSearchItemSchema';
import { useIsMobile } from '../components/field-item/hooks'; import { useIsMobile } from '../components/field-item/hooks';
@ -12,13 +12,19 @@ const textFieldsItem: SchemaInitializerItemType<any> = {
useChildren() { useChildren() {
const collection = useCollection(); const collection = useCollection();
const associatedFields = collection.fields; const associatedFields = collection.fields;
const cm = useCollectionManager();
const isMobield = useIsMobile(); const isMobield = useIsMobile();
const itemComponent = isMobield ? 'TabSearchCollapsibleInputMItem' : 'TabSearchCollapsibleInputItem'; const itemComponent = isMobield ? 'TabSearchCollapsibleInputMItem' : 'TabSearchCollapsibleInputItem';
const children = associatedFields const children = associatedFields
.map((field) => { .map((field) => {
if (canBeSearchField(field.interface) || (canBeOptionalField(field.interface) && !field['isForeignKey'])) { if (
const label = field.name; !field['isForeignKey'] &&
return createTabSearchItemSchema({ field, itemComponent, label }); (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); .filter(Boolean);

View File

@ -72,8 +72,12 @@ export const TabSearchItemFieldSettings = new SchemaSettings({
const { t } = useTranslation(); const { t } = useTranslation();
const cm = useCollectionManager(); const cm = useCollectionManager();
const c = useCollection(); const c = useCollection();
const fieldCollection = fieldSchema['x-component-props']?.['collectionName'];
const correlation = fieldSchema['x-component-props']?.['correlation'];
const collectionField = 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 compile = useCompile();
const { dn } = useDesignable(); const { dn } = useDesignable();
const targetFields = collectionField?.target ? cm.getCollectionFields(collectionField?.target) : []; const targetFields = collectionField?.target ? cm.getCollectionFields(collectionField?.target) : [];
@ -87,6 +91,7 @@ export const TabSearchItemFieldSettings = new SchemaSettings({
const schema = { const schema = {
['x-uid']: fieldSchema['x-uid'], ['x-uid']: fieldSchema['x-uid'],
}; };
const fieldNames = { const fieldNames = {
...collectionField?.uiSchema?.['x-component-props']?.['fieldNames'], ...collectionField?.uiSchema?.['x-component-props']?.['fieldNames'],
...fieldSchema['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'] = fieldSchema['x-component-props'] || {};
fieldSchema['x-component-props']['fieldNames'] = fieldNames; fieldSchema['x-component-props']['fieldNames'] = fieldNames;
schema['x-component-props'] = fieldSchema['x-component-props']; schema['x-component-props'] = fieldSchema['x-component-props'];
dn.emit('patch', { dn.emit('patch', {
schema, schema,
@ -111,7 +117,10 @@ export const TabSearchItemFieldSettings = new SchemaSettings({
}, },
useVisible() { useVisible() {
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
return !isTabSearchCollapsibleInputItem(fieldSchema['x-component']); return (
isTabSearchCollapsibleInputItem(fieldSchema['x-component']) ||
fieldSchema['x-component-props']?.['correlation']
);
}, },
}, },
]; ];

View File

@ -1,5 +1,8 @@
import { dayjs } from '@nocobase/utils/client';
const canBeOptionalFields = ['select', 'multipleSelect', 'radioGroup', 'checkboxGroup']; const canBeOptionalFields = ['select', 'multipleSelect', 'radioGroup', 'checkboxGroup'];
const canBeRelatedFields = ['oho', 'obo', 'o2m', 'm2o', 'm2m']; const canBeRelatedFields = ['oho', 'obo', 'o2m', 'm2o', 'm2m'];
const canBeDataFields = ['datetime'];
const canBeSearchFields = [ const canBeSearchFields = [
'input', 'input',
@ -22,5 +25,9 @@ export const isTabSearchCollapsibleInputItem = (component: string) =>
export const canBeOptionalField = (_interface: string) => canBeOptionalFields.includes(_interface); export const canBeOptionalField = (_interface: string) => canBeOptionalFields.includes(_interface);
export const canBeRelatedField = (_interface: string) => canBeRelatedFields.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 canBeSearchField = (_interface: string) => canBeSearchFields.includes(_interface);
export const convertFormat = (currentDate) => {
return dayjs(currentDate).format('YYYY-MM-DD');
};

View 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"
}

View 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": "创建区块"
}