: 移动端录单与筛选新设计 (#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,
|
usePropsRelatedImageSearchItemField,
|
||||||
useTabSearchFieldItemProps,
|
useTabSearchFieldItemProps,
|
||||||
useTabSearchFieldItemRelatedProps,
|
useTabSearchFieldItemRelatedProps,
|
||||||
|
MInput,
|
||||||
|
MCheckbox,
|
||||||
|
MDatePicker,
|
||||||
|
MRadio,
|
||||||
|
MImageUploader,
|
||||||
} from './schema';
|
} from './schema';
|
||||||
import './bridge';
|
import './bridge';
|
||||||
import './assets/svg';
|
import './assets/svg';
|
||||||
@ -70,6 +75,11 @@ export const MobileCore: React.FC = (props) => {
|
|||||||
ImageSearchItemView: ImageSearchItemView,
|
ImageSearchItemView: ImageSearchItemView,
|
||||||
// NoticeBlock,
|
// NoticeBlock,
|
||||||
// NoticeBlockInitializer,
|
// NoticeBlockInitializer,
|
||||||
|
MInput,
|
||||||
|
MCheckbox,
|
||||||
|
MDatePicker,
|
||||||
|
MRadio,
|
||||||
|
MImageUploader,
|
||||||
}}
|
}}
|
||||||
scope={{
|
scope={{
|
||||||
useGridCardBlockItemProps,
|
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 { Navigate, useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||||
import { ContainerDesigner } from './Container.Designer';
|
import { ContainerDesigner } from './Container.Designer';
|
||||||
import useStyles from './style';
|
import useStyles from './style';
|
||||||
|
import { MobileProvider } from '../../provider/MobileProvider';
|
||||||
|
|
||||||
const findGrid = (schema, uid) => {
|
const findGrid = (schema, uid) => {
|
||||||
return schema.reduceProperties((final, next) => {
|
return schema.reduceProperties((final, next) => {
|
||||||
@ -52,37 +53,39 @@ const InternalContainer: React.FC = (props) => {
|
|||||||
}, [location.pathname, navigate, params.name, redirectToUid]);
|
}, [location.pathname, navigate, params.name, redirectToUid]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SortableItem eid="nb-mobile-scroll-wrapper" className={cx('nb-mobile-container', styles.mobileContainer)}>
|
<MobileProvider>
|
||||||
<Designer></Designer>
|
<SortableItem eid="nb-mobile-scroll-wrapper" className={cx('nb-mobile-container', styles.mobileContainer)}>
|
||||||
<div
|
<Designer></Designer>
|
||||||
style={{
|
<div
|
||||||
paddingBottom: redirectToUid ? token.paddingLG * 2 : 0,
|
style={{
|
||||||
}}
|
paddingBottom: redirectToUid ? token.paddingLG * 2 : 0,
|
||||||
className="nb-mobile-container-content"
|
}}
|
||||||
>
|
className="nb-mobile-container-content"
|
||||||
{redirectToUid ? (
|
>
|
||||||
<TabContentComponent />
|
{redirectToUid ? (
|
||||||
) : (
|
<TabContentComponent />
|
||||||
<SchemaComponent
|
) : (
|
||||||
filterProperties={(schema) => {
|
<SchemaComponent
|
||||||
return schema['x-component'] !== 'MTabBar';
|
filterProperties={(schema) => {
|
||||||
}}
|
return schema['x-component'] !== 'MTabBar';
|
||||||
schema={fieldSchema}
|
}}
|
||||||
/>
|
schema={fieldSchema}
|
||||||
)}
|
/>
|
||||||
</div>
|
)}
|
||||||
{isTabBarEnabled && (
|
|
||||||
<div className={cx('nb-mobile-container-tab-bar', styles.tabBar)}>
|
|
||||||
<SchemaComponent
|
|
||||||
onlyRenderProperties
|
|
||||||
filterProperties={(schema) => {
|
|
||||||
return schema['x-component'] === 'MTabBar';
|
|
||||||
}}
|
|
||||||
schema={fieldSchema}
|
|
||||||
></SchemaComponent>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
{isTabBarEnabled && (
|
||||||
</SortableItem>
|
<div className={cx('nb-mobile-container-tab-bar', styles.tabBar)}>
|
||||||
|
<SchemaComponent
|
||||||
|
onlyRenderProperties
|
||||||
|
filterProperties={(schema) => {
|
||||||
|
return schema['x-component'] === 'MTabBar';
|
||||||
|
}}
|
||||||
|
schema={fieldSchema}
|
||||||
|
></SchemaComponent>
|
||||||
|
</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 { SchemaInitializer, useCollection, useCollectionManager } from '@tachybase/client';
|
||||||
import { useIsMobile } from '../tab-search/components/field-item/hooks';
|
|
||||||
import { canBeOptionalField, canBeRelatedField } from '../tab-search/utils';
|
import { canBeOptionalField, canBeRelatedField } from '../tab-search/utils';
|
||||||
import { createSchemaImageSearchItem } from './search-item/ImageSearchItem.schema';
|
import { createSchemaImageSearchItem } from './search-item/ImageSearchItem.schema';
|
||||||
|
import { useIsMobile } from '../../hooks';
|
||||||
|
|
||||||
export const ImageSearchConfigureFields = new SchemaInitializer({
|
export const ImageSearchConfigureFields = new SchemaInitializer({
|
||||||
name: 'ImageSearchView:configureFields',
|
name: 'ImageSearchView:configureFields',
|
||||||
|
@ -8,3 +8,5 @@ export * from './image-search';
|
|||||||
export * from './swiper';
|
export * from './swiper';
|
||||||
export * from './tab-search';
|
export * from './tab-search';
|
||||||
export * from './notice';
|
export * from './notice';
|
||||||
|
export * from './form';
|
||||||
|
export * from './antd-mobile';
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import React, { useState } from 'react';
|
import { dayjs } from '@tachybase/utils/client';
|
||||||
import { useTranslation } from '../../../../../../locale';
|
import { ActionSheet, Button, Calendar, Divider, Grid, Input, Picker, Popup } 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 { changFormat, convertFormat } from '../../utils';
|
import React, { useRef, useState } from 'react';
|
||||||
import { dayjs } from '@tachybase/utils/client';
|
import { useTranslation } from '../../../../../../locale';
|
||||||
|
import { convertFormat } from '../../utils';
|
||||||
|
import { css } from '@tachybase/client';
|
||||||
|
|
||||||
export const ISelect = (props) => {
|
export const ISelect = (props) => {
|
||||||
const { options, onChange, customLabelKey } = props;
|
const { options, onChange, customLabelKey } = props;
|
||||||
@ -59,13 +60,34 @@ export const IDatePicker = (props) => {
|
|||||||
const { options, value, onChange, onInputChange } = props;
|
const { options, value, onChange, onInputChange } = props;
|
||||||
const time = value.split('&');
|
const time = value.split('&');
|
||||||
const [visible, setVisible] = useState(false);
|
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 (
|
return (
|
||||||
<Grid.Item span={options.length > 1 ? 3 : 4}>
|
<Grid.Item span={options.length > 1 ? 3 : 4}>
|
||||||
<div
|
<div onClick={onClick}>
|
||||||
onClick={() => {
|
|
||||||
setVisible(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Grid columns={5}>
|
<Grid columns={5}>
|
||||||
<Grid.Item span={2} style={{ textAlign: 'end' }}>
|
<Grid.Item span={2} style={{ textAlign: 'end' }}>
|
||||||
{convertFormat(JSON.parse(time[0]))}
|
{convertFormat(JSON.parse(time[0]))}
|
||||||
@ -78,22 +100,24 @@ export const IDatePicker = (props) => {
|
|||||||
</Grid.Item>
|
</Grid.Item>
|
||||||
</Grid>
|
</Grid>
|
||||||
</div>
|
</div>
|
||||||
<CalendarPicker
|
<Popup
|
||||||
visible={visible}
|
visible={visible}
|
||||||
selectionMode="range"
|
destroyOnClose
|
||||||
|
mask
|
||||||
|
closeOnMaskClick
|
||||||
onMaskClick={() => setVisible(false)}
|
onMaskClick={() => setVisible(false)}
|
||||||
onClose={() => setVisible(false)}
|
onClose={() => setVisible(false)}
|
||||||
onConfirm={([start, end]) => {
|
// bodyStyle={{ height: '50vh' }}
|
||||||
const startTime = dayjs(start).startOf('date').toISOString();
|
>
|
||||||
const endTime = dayjs(end).endOf('date').toISOString();
|
<Calendar
|
||||||
|
selectionMode="range"
|
||||||
// TODO: 此处受上游影响,格式必须确定为这样, 时间有限,不再往上追查
|
allowClear
|
||||||
const timeString = `"${startTime}"&"${endTime}"`;
|
min={minDate}
|
||||||
|
max={maxDate}
|
||||||
onInputChange(timeString);
|
// defaultValue={defaultRange}
|
||||||
onChange(timeString);
|
onChange={onChangeDate}
|
||||||
}}
|
/>
|
||||||
/>
|
</Popup>
|
||||||
</Grid.Item>
|
</Grid.Item>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -81,9 +81,3 @@ export const useTabSearchCollapsibleInputItem = () => {
|
|||||||
onSelected,
|
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 React, { useCallback } from 'react';
|
||||||
import { tval } from '../../../../../locale';
|
import { tval } from '../../../../../locale';
|
||||||
import { canBeDataField, canBeOptionalField, canBeRelatedField, canBeSearchField } from '../utils';
|
import { canBeDataField, canBeOptionalField, canBeRelatedField, canBeSearchField } from '../utils';
|
||||||
import { useIsMobile } from '../components/field-item/hooks';
|
import { useIsMobile } from '../../../hooks';
|
||||||
|
import { mapToMobile } from '../mapToMobile';
|
||||||
|
|
||||||
interface ItemInterface {
|
interface ItemInterface {
|
||||||
field: FieldOptions;
|
field: FieldOptions;
|
||||||
@ -159,7 +160,7 @@ function getItemInput(params: ItemInterface, isMobile?: boolean) {
|
|||||||
// 然后排除其他类型的关联字段
|
// 然后排除其他类型的关联字段
|
||||||
if (isAssocField(field)) return null;
|
if (isAssocField(field)) return null;
|
||||||
|
|
||||||
if (!(canBeSearchField(_interface) && !canBeRelatedField(_interface) && !canBeDataField(_interface))) {
|
if (!canBeSearchField(_interface) && !canBeRelatedField(_interface) && !canBeDataField(_interface)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,10 +202,7 @@ function getItemInput(params: ItemInterface, isMobile?: boolean) {
|
|||||||
'x-collection-field': `${collectionName}.${schemaName}`,
|
'x-collection-field': `${collectionName}.${schemaName}`,
|
||||||
// 'x-decorator': 'FormItem',
|
// 'x-decorator': 'FormItem',
|
||||||
// 'x-component': 'CollectionField',
|
// 'x-component': 'CollectionField',
|
||||||
'x-component': matchTruthValue({
|
'x-component': mapToMobile('TabSearchCollapsibleInputItem'),
|
||||||
['TabSearchCollapsibleInputMItem']: isMobile,
|
|
||||||
['TabSearchCollapsibleInputItem']: !isMobile,
|
|
||||||
}),
|
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
fieldNames: {
|
fieldNames: {
|
||||||
label,
|
label,
|
||||||
@ -317,10 +315,7 @@ function getItemChoice(params: ItemInterface, isMobile?: boolean) {
|
|||||||
'x-collection-field': `${collectionName}.${schemaName}`,
|
'x-collection-field': `${collectionName}.${schemaName}`,
|
||||||
// 'x-decorator': 'FormItem',
|
// 'x-decorator': 'FormItem',
|
||||||
// 'x-component': 'CollectionField',
|
// 'x-component': 'CollectionField',
|
||||||
'x-component': matchTruthValue({
|
'x-component': mapToMobile('TabSearchFieldItem'),
|
||||||
['TabSearchFieldMItem']: isMobile,
|
|
||||||
['TabSearchFieldItem']: !isMobile,
|
|
||||||
}),
|
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
fieldNames: {
|
fieldNames: {
|
||||||
label,
|
label,
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { SchemaInitializer, useCollection, useCollectionManager } from '@tachybase/client';
|
import { SchemaInitializer, useCollection, useCollectionManager } from '@tachybase/client';
|
||||||
import { tval } from '../../../../../locale';
|
import { tval } from '../../../../../locale';
|
||||||
import { useIsMobile } from '../components/field-item/hooks';
|
|
||||||
import { createTabSearchItemSchema } from '../create/createTabSearchItemSchema';
|
import { createTabSearchItemSchema } from '../create/createTabSearchItemSchema';
|
||||||
import { canBeDataField, canBeOptionalField, canBeRelatedField, canBeSearchField } from '../utils';
|
import { canBeDataField, canBeOptionalField, canBeRelatedField, canBeSearchField } from '../utils';
|
||||||
import { TabSearchAssociatedFields } from './TabSearchAssociated.fields';
|
import { TabSearchAssociatedFields } from './TabSearchAssociated.fields';
|
||||||
|
import { useIsMobile } from '../../../hooks';
|
||||||
|
|
||||||
export const TabSearchFieldSchemaInitializer = new SchemaInitializer({
|
export const TabSearchFieldSchemaInitializer = new SchemaInitializer({
|
||||||
name: 'tabSearch:configureFields',
|
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 './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")}}',
|
title: '{{t("Form")}}',
|
||||||
Component: 'FormBlockInitializer',
|
Component: 'FormBlockInitializer',
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// name: 'details',
|
name: 'details',
|
||||||
// title: '{{t("Details")}}',
|
title: '{{t("Details")}}',
|
||||||
// Component: 'DetailsBlockInitializer',
|
Component: 'DetailsBlockInitializer',
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
name: 'calendar',
|
name: 'calendar',
|
||||||
title: '{{t("Calendar")}}',
|
title: '{{t("Calendar")}}',
|
||||||
@ -96,11 +96,11 @@ export const mBlockInitializers = new CompatibleSchemaInitializer(
|
|||||||
title: '{{t("Table")}}',
|
title: '{{t("Table")}}',
|
||||||
Component: 'TableBlockInitializer',
|
Component: 'TableBlockInitializer',
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// name: 'form',
|
name: 'form',
|
||||||
// title: '{{t("Form")}}',
|
title: '{{t("Form")}}',
|
||||||
// Component: 'FormBlockInitializer',
|
Component: 'FormBlockInitializer',
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
name: 'details',
|
name: 'details',
|
||||||
title: '{{t("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,
|
ImageSearchItemFieldSettings,
|
||||||
mBlockInitializers,
|
mBlockInitializers,
|
||||||
mBlockInitializers_deprecated,
|
mBlockInitializers_deprecated,
|
||||||
|
MobileFormItemInitializers,
|
||||||
TabSearchFieldSchemaInitializer,
|
TabSearchFieldSchemaInitializer,
|
||||||
TabSearchItemFieldSettings,
|
TabSearchItemFieldSettings,
|
||||||
} from './core/schema';
|
} from './core/schema';
|
||||||
@ -27,7 +28,8 @@ export class MobileClientPlugin extends Plugin {
|
|||||||
this.app.schemaInitializerManager.add(TabSearchFieldSchemaInitializer);
|
this.app.schemaInitializerManager.add(TabSearchFieldSchemaInitializer);
|
||||||
|
|
||||||
this.app.schemaSettingsManager.add(ImageSearchItemFieldSettings);
|
this.app.schemaSettingsManager.add(ImageSearchItemFieldSettings);
|
||||||
this.schemaSettingsManager.add(TabSearchItemFieldSettings);
|
this.app.schemaSettingsManager.add(TabSearchItemFieldSettings);
|
||||||
|
this.app.schemaInitializerManager.add(MobileFormItemInitializers);
|
||||||
}
|
}
|
||||||
|
|
||||||
addSettings() {
|
addSettings() {
|
||||||
|
Loading…
Reference in New Issue
Block a user