fix: 修复手机端表单使用相对应的组件 (#1054)

Reviewed-on: daoyoucloud/tachybase#1054
Co-authored-by: wjh <wwwjh0710@163.com>
Co-committed-by: wjh <wwwjh0710@163.com>
This commit is contained in:
wjh 2024-05-28 09:51:48 +08:00 committed by sealday
parent f3353379ce
commit 502ba8f21c
16 changed files with 308 additions and 118 deletions

View File

@ -1,17 +1,22 @@
import React, { createContext, useEffect, useState } from 'react';
import React, { createContext, useContext, useEffect, useState } from 'react';
import {
CollectionProvider_deprecated,
parseCollectionName,
RemoteSchemaComponent,
SchemaComponent,
SchemaComponentContext,
parseCollectionName,
useAPIClient,
} from '@tachybase/client';
import { useContext } from 'react';
import { NavBar } from 'antd-mobile';
import { useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from '../../locale';
import '../../style/style.css';
import { MobileProvider } from '@tachybase/plugin-mobile-client/client';
import { APPROVAL_ACTION_STATUS } from '../../constants';
import { useCreateSubmit } from '../hook/useCreateSubmit';
@ -41,21 +46,28 @@ export const ViewActionInitiationsContent = () => {
const [dataSource, name] = parseCollectionName(formConfig.config.collection);
const resSchema = {
type: 'void',
'x-component': 'MPage',
'x-designer': 'MPage.Designer',
'x-component-props': {},
'x-component': 'MobileProvider',
properties: {
Approval: {
page: {
type: 'void',
'x-decorator': 'CollectionProvider_deprecated',
'x-decorator-props': {
name,
dataSource,
},
'x-component': 'RemoteSchemaComponent',
'x-component-props': {
uid: formConfig.config.applyForm,
noForm: true,
'x-component': 'MPage',
'x-designer': 'MPage.Designer',
'x-decorator': 'MobileProvider',
'x-component-props': {},
properties: {
Approval: {
type: 'void',
'x-decorator': 'CollectionProvider_deprecated',
'x-decorator-props': {
name,
dataSource,
},
'x-component': 'RemoteSchemaComponent',
'x-component-props': {
uid: formConfig.config.applyForm,
noForm: true,
},
},
},
},
},
@ -86,6 +98,7 @@ export const ViewActionInitiationsContent = () => {
ApplyActionStatusProvider: ContextInitiationsApprovalStatusProvider,
ActionBarProvider,
WithdrawActionProvider: WithdrawActionProvider,
MobileProvider,
}}
scope={{
useSubmit: useCreateSubmit,

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import {
RemoteSchemaComponent,
SchemaComponent,
@ -11,24 +11,28 @@ import {
} from '@tachybase/client';
import { DetailsBlockProvider, FlowContext } from '@tachybase/plugin-workflow/client';
import { useFieldSchema, useForm } from '@tachybase/schema';
import { Result, Spin } from 'antd';
import { useContext } from 'react';
import { ContextWithActionEnabled } from '../../context/WithActionEnabled';
import { SchemaComponentContextProvider } from '../../context/SchemaComponent';
import { NavBar, Skeleton, TabBar } from 'antd-mobile';
import { FileOutline, UserContactOutline } from 'antd-mobile-icons';
import { useNavigate, useParams } from 'react-router-dom';
import { ContextApprovalExecution } from '../../context/ApprovalExecution';
import { FormBlockProvider } from '../../context/FormBlock';
import { SchemaComponentContextProvider } from '../../context/SchemaComponent';
import { ContextWithActionEnabled } from '../../context/WithActionEnabled';
import { useTranslation } from '../../locale';
import { useFormBlockProps } from '../hook/useFormBlockProps';
import { useUpdateSubmit } from '../hook/useUpadteSubmit';
import { useWithdrawAction } from '../hook/useWithdrawAction';
import { ActionBarProvider } from '../provider/ActionBar';
import { ApplyActionStatusProvider } from '../provider/ApplyActionStatus';
import { WithdrawActionProvider } from '../provider/WithdrawAction';
import { ContextApprovalExecution } from '../../context/ApprovalExecution';
import { useUpdateSubmit } from '../hook/useUpadteSubmit';
import { useFormBlockProps } from '../hook/useFormBlockProps';
import { useWithdrawAction } from '../hook/useWithdrawAction';
import { FileOutline, UserContactOutline } from 'antd-mobile-icons';
import { useTranslation } from '../../locale';
import '../../style/style.css';
import { MobileProvider } from '@tachybase/plugin-mobile-client/client';
export const ViewActionUserInitiationsContent = () => {
const params = useParams();
const navigate = useNavigate();
@ -118,18 +122,25 @@ export const ViewActionUserInitiationsContent = () => {
const UserInitiationsComponent = (applyDetail, t, currContext) => {
const formContextSchema = {
type: 'void',
'x-component': 'MPage',
'x-designer': 'MPage.Designer',
'x-component-props': {},
'x-component': 'MobileProvider',
properties: {
Approval: {
page: {
type: 'void',
'x-decorator': 'SchemaComponentContextProvider',
'x-decorator-props': { designable: false },
'x-component': 'RemoteSchemaComponent',
'x-component-props': {
uid: applyDetail,
noForm: true,
'x-component': 'MPage',
'x-designer': 'MPage.Designer',
'x-decorator': 'MobileProvider',
'x-component-props': {},
properties: {
Approval: {
type: 'void',
'x-decorator': 'SchemaComponentContextProvider',
'x-decorator-props': { designable: false },
'x-component': 'RemoteSchemaComponent',
'x-component-props': {
uid: applyDetail,
noForm: true,
},
},
},
},
},
@ -137,14 +148,20 @@ const UserInitiationsComponent = (applyDetail, t, currContext) => {
const approvalSchema = {
type: 'void',
'x-component': 'MPage',
'x-designer': 'MPage.Designer',
'x-component-props': {},
'x-component': 'MobileProvider',
properties: {
process: {
page: {
type: 'void',
'x-decorator': 'CardItem',
'x-component': 'ApprovalCommon.ViewComponent.MApprovalProcess',
'x-component': 'MPage',
'x-designer': 'MPage.Designer',
'x-component-props': {},
properties: {
process: {
type: 'void',
'x-decorator': 'CardItem',
'x-component': 'ApprovalCommon.ViewComponent.MApprovalProcess',
},
},
},
},
};
@ -160,6 +177,7 @@ const UserInitiationsComponent = (applyDetail, t, currContext) => {
ApplyActionStatusProvider,
WithdrawActionProvider,
DetailsBlockProvider,
MobileProvider,
}}
scope={{
useForm,

View File

@ -1,8 +1,10 @@
import { joinCollectionName, useAPIClient, useBlockRequestContext, useCollection_deprecated } from '@tachybase/client';
import { useField, useForm } from '@tachybase/schema';
import { APPROVAL_ACTION_STATUS } from '../../constants';
import { useNavigate, useParams } from 'react-router-dom';
import { Toast } from 'antd-mobile';
import { useNavigate, useParams } from 'react-router-dom';
import { APPROVAL_ACTION_STATUS } from '../../constants';
export function useCreateSubmit() {
const from = useForm();
@ -28,7 +30,6 @@ export function useCreateSubmit() {
workflowId: workflowId,
},
});
console.log('🚀 ~ res ~ res:', res);
if (res.status === 200) {
Toast.show({
icon: 'success',

View File

@ -1,3 +1,4 @@
import React, { useContext, useEffect, useState } from 'react';
import {
RemoteSchemaComponent,
SchemaComponent,
@ -7,26 +8,30 @@ import {
useRecord,
useRequest,
} from '@tachybase/client';
import { Result } from 'antd';
import _ from 'lodash';
import React, { useContext, useEffect, useState } from 'react';
import { useTranslation } from '../../locale';
import { DetailsBlockProvider } from '@tachybase/plugin-workflow/client';
import { ContextWithActionEnabled } from '../../context/WithActionEnabled';
import { ContextApprovalExecution } from '../../context/ApprovalExecution';
import { Result } from 'antd';
import { NavBar, Skeleton, TabBar } from 'antd-mobile';
import { CouponOutline, FileOutline, ScanCodeOutline, UserContactOutline } from 'antd-mobile-icons';
import _ from 'lodash';
import { useNavigate, useParams } from 'react-router-dom';
import { ContextApprovalExecution } from '../../context/ApprovalExecution';
import { FormBlockProvider } from '../../context/FormBlock';
import { SchemaComponentContextProvider } from '../../context/SchemaComponent';
import { ApprovalActionProvider } from '../provider/ApprovalAction';
import { ApprovalFormBlockDecorator } from '../provider/ApprovalFormBlock';
import { ContextWithActionEnabled } from '../../context/WithActionEnabled';
import { useTranslation } from '../../locale';
import { useApprovalDetailBlockProps } from '../hook/useApprovalDetailBlockProps';
import { useApprovalFormBlockProps } from '../hook/useApprovalFormBlockProps';
import { useSubmit } from '../hook/useSubmit';
import { CouponOutline, FileOutline, ScanCodeOutline, UserContactOutline } from 'antd-mobile-icons';
import { ActionBarProvider } from '../provider/ActionBarProvider';
import { FormBlockProvider } from '../../context/FormBlock';
import { ApprovalActionProvider } from '../provider/ApprovalAction';
import { ApprovalFormBlockDecorator } from '../provider/ApprovalFormBlock';
import '../../style/style.css';
import { MobileProvider } from '@tachybase/plugin-mobile-client/client';
// 审批-待办-查看: 内容
export const ViewActionTodosContent = () => {
const { t } = useTranslation();
@ -130,18 +135,25 @@ export const ViewActionTodosContent = () => {
const todosComponent = (applyDetail, t, currContext) => {
const formContextSchema = {
type: 'void',
'x-component': 'MPage',
'x-designer': 'MPage.Designer',
'x-component-props': {},
'x-component': 'MobileProvider',
properties: {
Approval: {
page: {
type: 'void',
'x-decorator': 'SchemaComponentContextProvider',
'x-decorator-props': { designable: false },
'x-component': 'RemoteSchemaComponent',
'x-component-props': {
uid: applyDetail,
noForm: true,
'x-component': 'MPage',
'x-designer': 'MPage.Designer',
'x-decorator': 'MobileProvider',
'x-component-props': {},
properties: {
Approval: {
type: 'void',
'x-decorator': 'SchemaComponentContextProvider',
'x-decorator-props': { designable: false },
'x-component': 'RemoteSchemaComponent',
'x-component-props': {
uid: applyDetail,
noForm: true,
},
},
},
},
},
@ -149,14 +161,20 @@ const todosComponent = (applyDetail, t, currContext) => {
const approvalSchema = {
type: 'void',
'x-component': 'MPage',
'x-designer': 'MPage.Designer',
'x-component-props': {},
'x-component': 'MobileProvider',
properties: {
process: {
page: {
type: 'void',
'x-decorator': 'CardItem',
'x-component': 'ApprovalCommon.ViewComponent.MApprovalProcess',
'x-component': 'MPage',
'x-designer': 'MPage.Designer',
'x-component-props': {},
properties: {
process: {
type: 'void',
'x-decorator': 'CardItem',
'x-component': 'ApprovalCommon.ViewComponent.MApprovalProcess',
},
},
},
},
};
@ -173,6 +191,7 @@ const todosComponent = (applyDetail, t, currContext) => {
ApprovalFormBlockProvider: ApprovalFormBlockDecorator,
DetailsBlockProvider,
TabBar,
MobileProvider,
}}
scope={{
useApprovalDetailBlockProps,

View File

@ -1,46 +1,49 @@
import { SchemaComponentOptions } from '@tachybase/client';
import React, { PropsWithChildren } from 'react';
import { SchemaComponentOptions } from '@tachybase/client';
import {
MMenuBlockInitializer,
MMenu,
MContainer,
MTabBar,
MPage,
MHeader,
MSettingsBlockInitializer,
MSettings,
useGridCardBlockItemProps,
useGridCardBlockProps,
TabSearch,
TabSearchProvider,
TabSearchBlockInitializer,
TabSearchFieldItem,
TabSearchFieldMItem,
TabSearchCollapsibleInputItem,
TabSearchCollapsibleInputMItem,
TabSearchFieldSchemaInitializerGadget,
SwiperBlockInitializer,
SwiperBlock,
SwiperPage,
ImageSearchView,
CollectionField,
ImageSearchInitializer,
ImageSearchProvider,
ImageSearchItemIntializer,
ImageSearchItemToolbar,
ImageSearchItemView,
ImageSearchProvider,
ImageSearchView,
MCheckbox,
MContainer,
MDatePicker,
MHeader,
MImageUploader,
MInput,
MMenu,
MMenuBlockInitializer,
MPage,
MRadio,
MSettings,
MSettingsBlockInitializer,
MTabBar,
NoticeBlock,
NoticeBlockInitializer,
useSwiperBlockProps,
SwiperBlock,
SwiperBlockInitializer,
SwiperPage,
TabSearch,
TabSearchBlockInitializer,
TabSearchCollapsibleInputItem,
TabSearchCollapsibleInputMItem,
TabSearchFieldItem,
TabSearchFieldMItem,
TabSearchFieldSchemaInitializerGadget,
TabSearchProvider,
useGridCardBlockItemProps,
useGridCardBlockProps,
usePropsOptionalImageSearchItemField,
usePropsRelatedImageSearchItemField,
useSwiperBlockProps,
useTabSearchFieldItemProps,
useTabSearchFieldItemRelatedProps,
MInput,
MCheckbox,
MDatePicker,
MRadio,
MImageUploader,
} from './schema';
import './bridge';
import './assets/svg';
@ -83,6 +86,7 @@ export const MobileCore: React.FC<PropsWithChildren> = (props) => {
MDatePicker,
MRadio,
MImageUploader,
CollectionField: CollectionField,
}}
scope={{
useGridCardBlockItemProps,

View File

@ -1,7 +1,8 @@
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';
import { connect, mapProps, mapReadPretty } from '@tachybase/schema';
import { dayjs } from '@tachybase/utils/client';
import { Button, DatePicker, Input, Space } from 'antd-mobile';
export const MDatePicker = connect(
(props) => {
@ -33,5 +34,10 @@ export const MDatePicker = connect(
mapProps((props) => {
return { ...props };
}),
mapReadPretty((props) => {
const changeProps = { ...props };
changeProps.value = dayjs(props.value).format(props.dateFormat);
return <Input {...changeProps} readOnly />;
}),
);
export default MDatePicker;

View File

@ -1,7 +1,8 @@
import React, { useEffect, useState } from 'react';
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';
import { ImageUploader, ImageUploadItem } from 'antd-mobile';
export const MImageUploader = connect(
(props) => {
@ -52,6 +53,18 @@ export const MImageUploader = connect(
mapProps((props) => {
return { ...props };
}),
mapReadPretty((props) => {
return (
<ImageUploader
{...props}
disableUpload
showUpload={false}
upload={async () => {
return { url: '' };
}}
/>
);
}),
);
export default MImageUploader;

View File

@ -1,4 +1,6 @@
import { connect, mapProps } from '@tachybase/schema';
import React from 'react';
import { connect, mapProps, mapReadPretty } from '@tachybase/schema';
import { Input, InputProps, TextArea, TextAreaProps } from 'antd-mobile';
type ComposedInput = React.FC<InputProps> & {
@ -8,14 +10,20 @@ type ComposedInput = React.FC<InputProps> & {
export const MInput: ComposedInput = connect(
Input,
mapProps((props) => {
return { placeholder: '请输入内容', clearable: true, ...props };
return { placeholder: '请输入内容', clearable: true, ...props, style: { fontSize: '12px' } };
}),
mapReadPretty((props) => {
return <Input {...props} readOnly style={{ fontSize: '12px' }} />;
}),
);
const MTextArea = connect(
TextArea,
mapProps((props) => {
return { placeholder: '请输入内容', ...props };
return { ...props, placeholder: '请输入内容', clearable: true, style: { fontSize: '12px' } };
}),
mapReadPretty((props) => {
return <TextArea {...props} readOnly style={{ fontSize: '12px' }} />;
}),
);

View File

@ -0,0 +1,107 @@
import React, { useEffect, useMemo } from 'react';
import {
CollectionFieldProvider,
useCollectionField,
useCompile,
useComponent,
useFormBlockContext,
useIsAllowToSetDefaultValue,
} from '@tachybase/client';
import { connect, Field, merge, useField, useFieldSchema } from '@tachybase/schema';
import { concat } from 'lodash';
import { useIsMobile } from '../../hooks';
import { canMobileField } from '../form/CustomComponent';
type Props = {
component: any;
children?: React.ReactNode;
};
/**
* TODO: 初步适配
* @internal
*/
export const CollectionFieldInternalField: React.FC = (props: Props) => {
const { component } = props;
const compile = useCompile();
const field = useField<Field>();
const fieldSchema = useFieldSchema();
const { uiSchema: uiSchemaOrigin, defaultValue } = useCollectionField();
const { isAllowToSetDefaultValue } = useIsAllowToSetDefaultValue();
const uiSchema = useMemo(() => compile(uiSchemaOrigin), [JSON.stringify(uiSchemaOrigin)]);
const isMobile = useIsMobile();
const Component = useComponent(component || uiComponent(isMobile, uiSchema) || 'Input');
const setFieldProps = (key, value) => {
field[key] = typeof field[key] === 'undefined' ? value : field[key];
};
const setRequired = () => {
if (typeof fieldSchema['required'] === 'undefined') {
field.required = !!uiSchema['required'];
}
};
const ctx = useFormBlockContext();
useEffect(() => {
if (ctx?.field) {
ctx.field.added = ctx.field.added || new Set();
ctx.field.added.add(fieldSchema.name);
}
});
// TODO: 初步适配
useEffect(() => {
if (!uiSchema) {
return;
}
setFieldProps('content', uiSchema['x-content']);
setFieldProps('title', uiSchema.title);
setFieldProps('description', uiSchema.description);
if (ctx?.form) {
const defaultVal = isAllowToSetDefaultValue() ? fieldSchema.default || defaultValue : undefined;
defaultVal !== null && defaultVal !== undefined && setFieldProps('initialValue', defaultVal);
}
if (!field.validator && (uiSchema['x-validator'] || fieldSchema['x-validator'])) {
const concatSchema = concat([], uiSchema['x-validator'] || [], fieldSchema['x-validator'] || []);
field.validator = concatSchema;
}
if (fieldSchema['x-disabled'] === true) {
field.disabled = true;
}
if (fieldSchema['x-read-pretty'] === true) {
field.readPretty = true;
}
setRequired();
// @ts-ignore
field.dataSource = uiSchema.enum;
const originalProps = compile(uiSchema['x-component-props']) || {};
const componentProps = merge(originalProps, field.componentProps || {});
field.component = [Component, componentProps];
}, [JSON.stringify(uiSchema)]);
if (!uiSchema) {
return null;
}
return <Component {...props} />;
};
export const CollectionField = connect((props) => {
const fieldSchema = useFieldSchema();
return (
<CollectionFieldProvider name={fieldSchema.name}>
<CollectionFieldInternalField {...props} />
</CollectionFieldProvider>
);
});
export const uiComponent = (isMobile, uiSchema) => {
const isMobileComponent = canMobileField(uiSchema['x-component']);
if (isMobile && isMobileComponent) {
return isMobileComponent;
} else {
return uiSchema['x-component'];
}
};
CollectionField.displayName = 'CollectionField';

View File

@ -0,0 +1 @@
export * from './CollectionField';

View File

@ -1,7 +1,8 @@
import { useMemo } from 'react';
import { findFilterTargets, mergeFilter, useCollection, useFilterBlock } from '@tachybase/client';
import { useFieldSchema } from '@tachybase/schema';
import _ from 'lodash';
import { useMemo } from 'react';
export const useGetSelected = () => {
const fieldSchema = useFieldSchema();
@ -65,9 +66,3 @@ export const useGetSelected = () => {
onSelected,
};
};
export const useIsMobile = () => {
const fieldSchema = useFieldSchema();
const isMobile = Object.values(fieldSchema.root.properties).some((value) => value['x-component'] === 'MContainer');
return isMobile;
};

View File

@ -10,3 +10,4 @@ export * from './tab-search';
export * from './notice';
export * from './form';
export * from './antd-mobile';
export * from './collection-field';

View File

@ -1,7 +1,8 @@
import { useContext } from 'react';
import { MobileContext } from '../provider/MobileProvider';
export const useIsMobile = () => {
const ctx = useContext(MobileContext);
return ctx.isMobile;
return ctx?.isMobile;
};

View File

@ -1,3 +1,5 @@
export * from './initializers';
export * from './components';
export * from './scopes';
export * from './hooks';
export * from './provider';

View File

@ -1,6 +1,6 @@
import React, { createContext } from 'react';
export const MobileContext = createContext<any>({});
export const MobileContext = createContext<any>({ isMobile: false });
export const MobileProvider = (props) => {
return <MobileContext.Provider value={{ isMobile: true }}> {props.children}</MobileContext.Provider>;

View File

@ -0,0 +1 @@
export * from './MobileProvider';