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();
};
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,

View File

@ -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,
};
};

View File

@ -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>
);

View File

@ -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')}

View File

@ -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,

View File

@ -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]);

View File

@ -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) {

View File

@ -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: {},
},

View File

@ -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);

View File

@ -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']
);
},
},
];

View File

@ -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');
};

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