Merge pull request '重构:调整 schema-components 和 components 中的组件,现在能相对清晰反馈他们的用途' (#400) from feat_drawer_in_page into @hera/dev
Reviewed-on: daoyoucloud/tachycode#400 Reviewed-by: wangjiahui <wwwjh0710@163.com>
This commit is contained in:
commit
7f2755a336
@ -11,8 +11,8 @@ export const ErrorFallback: FC<FallbackProps> = ({ error }) => {
|
|||||||
|
|
||||||
const subTitle = (
|
const subTitle = (
|
||||||
<Trans>
|
<Trans>
|
||||||
{'This is likely a NocoBase internals bug. Please open an issue at '}
|
{'This is likely a TachyCode internals bug. Please open an issue at '}
|
||||||
<Link href="https://github.com/nocobase/nocobase/issues" target="_blank">
|
<Link href="https://git.daoyoucloud.com/daoyoucloud/tachycode/issues" target="_blank">
|
||||||
here
|
here
|
||||||
</Link>
|
</Link>
|
||||||
</Trans>
|
</Trans>
|
||||||
@ -26,7 +26,12 @@ export const ErrorFallback: FC<FallbackProps> = ({ error }) => {
|
|||||||
title={t('Render Failed')}
|
title={t('Render Failed')}
|
||||||
subTitle={subTitle}
|
subTitle={subTitle}
|
||||||
extra={[
|
extra={[
|
||||||
<Button type="primary" key="feedback" href="https://github.com/nocobase/nocobase/issues" target="_blank">
|
<Button
|
||||||
|
type="primary"
|
||||||
|
key="feedback"
|
||||||
|
href="https://git.daoyoucloud.com/daoyoucloud/tachycode/issues"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
{t('Feedback')}
|
{t('Feedback')}
|
||||||
</Button>,
|
</Button>,
|
||||||
<Button key="try" onClick={resetBoundary}>
|
<Button key="try" onClick={resetBoundary}>
|
@ -1 +1 @@
|
|||||||
export * from './ErrorFallback';
|
export * from './ErrorFallback2';
|
||||||
|
@ -1,86 +0,0 @@
|
|||||||
import { ISchema, useFieldSchema } from '@formily/react';
|
|
||||||
import { SchemaSettingsModalItem, useDesignable } from '@nocobase/client';
|
|
||||||
import { useTranslation } from '../locale';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
export function AfterSuccess() {
|
|
||||||
const { dn } = useDesignable();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const fieldSchema = useFieldSchema();
|
|
||||||
const component = fieldSchema.parent.parent['x-component'];
|
|
||||||
const schema = {
|
|
||||||
type: 'object',
|
|
||||||
title: t('After successful submission'),
|
|
||||||
properties: {
|
|
||||||
successMessage: {
|
|
||||||
title: t('Popup message'),
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'Input.TextArea',
|
|
||||||
'x-component-props': {},
|
|
||||||
},
|
|
||||||
dataClear: {
|
|
||||||
title: t('Clear data method'),
|
|
||||||
enum: [
|
|
||||||
{ label: t('Automatic clear'), value: false },
|
|
||||||
{ label: t('Manually clear'), value: true },
|
|
||||||
],
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'Radio.Group',
|
|
||||||
'x-component-props': {},
|
|
||||||
},
|
|
||||||
manualClose: {
|
|
||||||
title: t('Popup close method'),
|
|
||||||
enum: [
|
|
||||||
{ label: t('Automatic close'), value: false },
|
|
||||||
{ label: t('Manually close'), value: true },
|
|
||||||
],
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'Radio.Group',
|
|
||||||
'x-component-props': {},
|
|
||||||
},
|
|
||||||
redirecting: {
|
|
||||||
title: t('Then'),
|
|
||||||
enum: [
|
|
||||||
{ label: t('Stay on current page'), value: false },
|
|
||||||
{ label: t('Redirect to'), value: true },
|
|
||||||
],
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'Radio.Group',
|
|
||||||
'x-component-props': {},
|
|
||||||
'x-reactions': {
|
|
||||||
target: 'redirectTo',
|
|
||||||
fulfill: {
|
|
||||||
state: {
|
|
||||||
visible: '{{!!$self.value}}',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
redirectTo: {
|
|
||||||
title: t('Link'),
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'Input',
|
|
||||||
'x-component-props': {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
if (!(component as string).includes('Form')) {
|
|
||||||
delete schema.properties.dataClear;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<SchemaSettingsModalItem
|
|
||||||
title={t('After successful submission')}
|
|
||||||
initialValues={fieldSchema?.['x-action-settings']?.['onSuccess']}
|
|
||||||
schema={{ ...schema } as ISchema}
|
|
||||||
onSubmit={(onSuccess) => {
|
|
||||||
fieldSchema['x-action-settings']['onSuccess'] = onSuccess;
|
|
||||||
dn.emit('patch', {
|
|
||||||
schema: {
|
|
||||||
['x-uid']: fieldSchema['x-uid'],
|
|
||||||
'x-action-settings': fieldSchema['x-action-settings'],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
import { useFieldSchema } from '@formily/react';
|
|
||||||
import { SchemaSettingsSwitchItem, useDesignable } from '@nocobase/client';
|
|
||||||
import React from 'react';
|
|
||||||
import { useTranslation } from '../locale';
|
|
||||||
|
|
||||||
// 添加跳转页面选项
|
|
||||||
export const SessionSubmit = () => {
|
|
||||||
const { dn } = useDesignable();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const fieldSchema = useFieldSchema();
|
|
||||||
return (
|
|
||||||
<SchemaSettingsSwitchItem
|
|
||||||
title={t('Navigate to new page')}
|
|
||||||
checked={!!fieldSchema?.['x-action-settings']?.sessionSubmit}
|
|
||||||
onChange={(value) => {
|
|
||||||
fieldSchema['x-action-settings'].sessionSubmit = value;
|
|
||||||
dn.emit('patch', {
|
|
||||||
schema: {
|
|
||||||
['x-uid']: fieldSchema['x-uid'],
|
|
||||||
'x-action-settings': {
|
|
||||||
...fieldSchema['x-action-settings'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export function SessionUpdate() {
|
|
||||||
const { dn } = useDesignable();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const fieldSchema = useFieldSchema();
|
|
||||||
return (
|
|
||||||
<SchemaSettingsSwitchItem
|
|
||||||
title={t('更新询问')}
|
|
||||||
checked={!!fieldSchema?.['x-action-settings']?.sessionUpdate}
|
|
||||||
onChange={(value) => {
|
|
||||||
fieldSchema['x-action-settings'].sessionUpdate = value;
|
|
||||||
dn.emit('patch', {
|
|
||||||
schema: {
|
|
||||||
['x-uid']: fieldSchema['x-uid'],
|
|
||||||
'x-action-settings': {
|
|
||||||
...fieldSchema['x-action-settings'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Popover, Space, Button, Input } from 'antd';
|
|
||||||
import { useFieldSchema } from '@formily/react';
|
|
||||||
import { useProps } from '@nocobase/client';
|
|
||||||
import { ShareAltOutlined } from '@ant-design/icons';
|
|
||||||
|
|
||||||
export const OutboundButton = (props) => {
|
|
||||||
const { onClick } = useProps(props);
|
|
||||||
const schema = useFieldSchema();
|
|
||||||
const url = window.location.href.split('/', 3).join('/');
|
|
||||||
return (
|
|
||||||
<Popover
|
|
||||||
placement="bottomRight"
|
|
||||||
trigger="click"
|
|
||||||
autoAdjustOverflow
|
|
||||||
content={
|
|
||||||
<Space.Compact style={{ width: '100%' }}>
|
|
||||||
<Input defaultValue={`${url}/r/${schema['x-uid']}`} />
|
|
||||||
<Button type="primary" onClick={onClick}>
|
|
||||||
复制链接
|
|
||||||
</Button>
|
|
||||||
</Space.Compact>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Button>
|
|
||||||
<ShareAltOutlined />
|
|
||||||
外链
|
|
||||||
</Button>
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,10 +1,10 @@
|
|||||||
import { SchemaComponent, SchemaSettings, useApp, useDesignable, usePlugin } from '@nocobase/client';
|
import { SchemaComponent, SchemaSettings, useApp, useDesignable, usePlugin } from '@nocobase/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from '../locale';
|
import { useTranslation } from '../../locale';
|
||||||
import { useField, useFieldSchema } from '@formily/react';
|
import { useField, useFieldSchema } from '@formily/react';
|
||||||
import { Field } from '@nocobase/database';
|
import { Field } from '@nocobase/database';
|
||||||
import { useCustomComponent } from '../hooks/useCustomComponent';
|
import { useCustomComponent } from '../../hooks/useCustomComponent';
|
||||||
import { CustomComponentType } from '../custom-components';
|
import { CustomComponentType } from './custom-components';
|
||||||
|
|
||||||
export const CustomComponentStub = (props) => {
|
export const CustomComponentStub = (props) => {
|
||||||
return <div>请选择组件</div>;
|
return <div>请选择组件</div>;
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import { css } from '@nocobase/client';
|
import { css } from '@nocobase/client';
|
||||||
import { Button, Tooltip } from 'antd';
|
import { Button, Tooltip } from 'antd';
|
||||||
import { MobileOutlined } from '@ant-design/icons';
|
import { MobileOutlined } from '@ant-design/icons';
|
||||||
import { useTranslation } from '../locale';
|
import { useTranslation } from '../../locale';
|
||||||
|
|
||||||
export const MobileLink = () => {
|
export const MobileLink = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import { css, useRequest } from '@nocobase/client';
|
import { css, useRequest } from '@nocobase/client';
|
||||||
import { Badge, Button, Popover, List, message } from 'antd';
|
import { Badge, Button, Popover, List, message } from 'antd';
|
||||||
import { BellOutlined } from '@ant-design/icons';
|
import { BellOutlined } from '@ant-design/icons';
|
||||||
import { useLinkKey, useInitializationLinkKey } from '../hooks/useNotifications';
|
import { useLinkKey, useInitializationLinkKey } from '../../hooks/useNotifications';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { dayjs } from '@nocobase/utils/client';
|
import { dayjs } from '@nocobase/utils/client';
|
||||||
|
|
@ -8,16 +8,9 @@ import {
|
|||||||
SchemaSettingOptions,
|
SchemaSettingOptions,
|
||||||
useCollection,
|
useCollection,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import { HeraAdminLayout } from './layouts';
|
|
||||||
import { remove } from 'lodash';
|
import { remove } from 'lodash';
|
||||||
import { CalendarBlockInitializer } from './schema-initializer/CalendarBlockInitializer';
|
import { CalendarBlockInitializer } from './schema-initializer/CalendarBlockInitializer';
|
||||||
import { MenuDesigner } from './components/ExtendedMenuDesigner';
|
import { MenuDesigner } from './schema-components/deprecated/ExtendedMenuDesigner';
|
||||||
import { SessionSubmit, SessionUpdate } from './components/ExtendedActionDesigner';
|
|
||||||
import { DetailsPage } from './pages/DetailsPage';
|
|
||||||
import { OutboundPage } from './pages/OutboundPage';
|
|
||||||
import { HomePageConfiguration } from './pages/HomePageConfiguration';
|
|
||||||
import { Configuration } from './components/TokenConfiguration';
|
|
||||||
import { HomePage } from './pages/Home';
|
|
||||||
import { InternalPDFViewer } from './schema-components/PDFViewer';
|
import { InternalPDFViewer } from './schema-components/PDFViewer';
|
||||||
import {
|
import {
|
||||||
PDFViewerBlockInitializer,
|
PDFViewerBlockInitializer,
|
||||||
@ -26,21 +19,20 @@ import {
|
|||||||
pdfViewActionInitializer,
|
pdfViewActionInitializer,
|
||||||
usePDFViewerPrintActionProps,
|
usePDFViewerPrintActionProps,
|
||||||
} from './schema-initializer/PDFVIewerBlockInitializer';
|
} from './schema-initializer/PDFVIewerBlockInitializer';
|
||||||
import { OutboundButton } from './components/OutboundButton';
|
|
||||||
import { useCustomizeUpdateActionProps } from './hooks/useCustomizeUpdateActionProps';
|
import { useCustomizeUpdateActionProps } from './hooks/useCustomizeUpdateActionProps';
|
||||||
import { OutboundLinkActionInitializer } from './schema-initializer/OutboundLinkActionInitializer';
|
import { OutboundButton, OutboundLinkActionInitializer } from './schema-initializer/OutboundLinkActionInitializer';
|
||||||
import { CreateSubmitActionInitializer } from './schema-initializer/CreateSubmitActionInitializer';
|
import { CreateSubmitActionInitializer } from './schema-initializer/CreateSubmitActionInitializer';
|
||||||
import { FilterAssociatedFields } from './schema-initializer/FilterAssociatedFields';
|
import { FilterAssociatedFields } from './schema-initializer/FilterAssociatedFields';
|
||||||
import { useFieldSchema } from '@formily/react';
|
import { useFieldSchema } from '@formily/react';
|
||||||
import { isValid } from '@formily/shared';
|
import { isValid } from '@formily/shared';
|
||||||
import { useCreateActionProps } from './hooks/useCreateActionProps';
|
import { useCreateActionProps } from './hooks/useCreateActionProps';
|
||||||
import { PageLayout } from './pages/PageLayout';
|
|
||||||
import { useOutboundActionProps } from './hooks/useOutboundActionProps';
|
import { useOutboundActionProps } from './hooks/useOutboundActionProps';
|
||||||
import { AssociatedField } from './components/AssociatedField';
|
import { AssociatedField } from './components/AssociatedField';
|
||||||
import { DatePicker } from './schema-components/date-picker';
|
import { DatePicker } from './schema-components/date-picker';
|
||||||
import { Page } from './schema-components/page';
|
|
||||||
import { SettingBlockInitializer } from './schema-initializer/SettingBlockInitializer';
|
import { SettingBlockInitializer } from './schema-initializer/SettingBlockInitializer';
|
||||||
import {
|
import {
|
||||||
|
SessionSubmit,
|
||||||
|
SessionUpdate,
|
||||||
EditFormulaTitleField,
|
EditFormulaTitleField,
|
||||||
IsTablePageSize,
|
IsTablePageSize,
|
||||||
useFormulaTitleVisible,
|
useFormulaTitleVisible,
|
||||||
@ -48,12 +40,12 @@ import {
|
|||||||
EditTitle,
|
EditTitle,
|
||||||
SetFilterScope,
|
SetFilterScope,
|
||||||
useSetFilterScopeVisible,
|
useSetFilterScopeVisible,
|
||||||
} from './components/SchemaSettingOptions';
|
AfterSuccess,
|
||||||
|
} from './schema-settings';
|
||||||
import { SignatureInput } from './components/SignatureInput';
|
import { SignatureInput } from './components/SignatureInput';
|
||||||
import { RemoteSelect } from './schema-components/remote-select';
|
import { RemoteSelect } from './schema-components/remote-select';
|
||||||
import { Select } from './schema-components/select/Select';
|
import { Select } from './schema-components/select/Select';
|
||||||
import { Locale, tval } from './locale';
|
import { Locale, tval } from './locale';
|
||||||
import { LinkManager } from './components/LinkManager';
|
|
||||||
import {
|
import {
|
||||||
GroupBlockInitializer,
|
GroupBlockInitializer,
|
||||||
GroupBlockProvider,
|
GroupBlockProvider,
|
||||||
@ -71,24 +63,25 @@ import {
|
|||||||
CustomComponentDispatcher,
|
CustomComponentDispatcher,
|
||||||
CustomComponentStub,
|
CustomComponentStub,
|
||||||
customComponentDispatcherSettings,
|
customComponentDispatcherSettings,
|
||||||
} from './components/CustomComponentDispatcher';
|
} from './components/custom-components/CustomComponentDispatcher';
|
||||||
import { useFilterBlockActionProps } from './hooks/useFilterBlockActionProps';
|
import { useFilterBlockActionProps } from './hooks/useFilterBlockActionProps';
|
||||||
import { AfterSuccess } from './components/Action.Designer';
|
|
||||||
import { GroupBlockConfigure } from './components/GroupBlockConfigure/GroupBlockConfigure';
|
import { GroupBlockConfigure } from './components/GroupBlockConfigure/GroupBlockConfigure';
|
||||||
import { AssociatedFieldInterface } from './interfaces/associated';
|
import { AssociatedFieldInterface } from './interfaces/associated';
|
||||||
import { CalcFieldInterface } from './interfaces/calc';
|
import { CalcFieldInterface } from './interfaces/calc';
|
||||||
import { CustomFieldInterface } from './interfaces/custom';
|
import { CustomFieldInterface } from './interfaces/custom';
|
||||||
import { CustomAssociatedFieldInterface } from './interfaces/customAssociated';
|
import { CustomAssociatedFieldInterface } from './interfaces/customAssociated';
|
||||||
import { SignaturePadFieldInterface } from './interfaces/signatureSchema';
|
import { SignaturePadFieldInterface } from './interfaces/signatureSchema';
|
||||||
import { CalcResult } from './components/CalcResult';
|
import { CalcResult } from './components/fields/CalcResult';
|
||||||
import { CustomAssociatedField } from './components/CustomAssociatedField';
|
import { CustomAssociatedField } from './components/custom-components/CustomAssociatedField';
|
||||||
import Expression from './components/Expression';
|
import Expression from './components/Expression';
|
||||||
import { CustomField } from './components/CustomField';
|
import { CustomField } from './components/custom-components/CustomField';
|
||||||
import { useGetCustomAssociatedComponents } from './hooks/useGetCustomAssociatedComponents';
|
import { useGetCustomAssociatedComponents } from './hooks/useGetCustomAssociatedComponents';
|
||||||
import { useGetCustomComponents } from './hooks/useGetCustomComponents';
|
import { useGetCustomComponents } from './hooks/useGetCustomComponents';
|
||||||
import { AutoComplete } from './schema-components/AutoComplete/AutoComplete';
|
import { AutoComplete } from './schema-components/AutoComplete/AutoComplete';
|
||||||
|
import { AdminLayout, DetailsPage, HomePage, OutboundPage, PageLayout } from './pages';
|
||||||
|
import { Configuration, HomePageConfiguration, LinkManager } from './settings-manager-components';
|
||||||
export { usePDFViewerRef } from './schema-initializer/PDFVIewerBlockInitializer';
|
export { usePDFViewerRef } from './schema-initializer/PDFVIewerBlockInitializer';
|
||||||
export * from './custom-components';
|
export * from './components/custom-components/custom-components';
|
||||||
|
|
||||||
export class PluginCoreClient extends Plugin {
|
export class PluginCoreClient extends Plugin {
|
||||||
locale: Locale;
|
locale: Locale;
|
||||||
@ -157,7 +150,6 @@ export class PluginCoreClient extends Plugin {
|
|||||||
GroupBlockInitializer,
|
GroupBlockInitializer,
|
||||||
GroupBlockToolbar,
|
GroupBlockToolbar,
|
||||||
GroupBlockProvider,
|
GroupBlockProvider,
|
||||||
Page,
|
|
||||||
DatePicker,
|
DatePicker,
|
||||||
RemoteSelect,
|
RemoteSelect,
|
||||||
SignatureInput,
|
SignatureInput,
|
||||||
@ -165,7 +157,7 @@ export class PluginCoreClient extends Plugin {
|
|||||||
OutboundLinkActionInitializer,
|
OutboundLinkActionInitializer,
|
||||||
PDFViewerBlockInitializer,
|
PDFViewerBlockInitializer,
|
||||||
PDFViwer: InternalPDFViewer,
|
PDFViwer: InternalPDFViewer,
|
||||||
AdminLayout: HeraAdminLayout,
|
AdminLayout,
|
||||||
ExtendedCalendarBlockInitializer: CalendarBlockInitializer,
|
ExtendedCalendarBlockInitializer: CalendarBlockInitializer,
|
||||||
SettingBlock: SettingBlockInitializer,
|
SettingBlock: SettingBlockInitializer,
|
||||||
CreateSubmitActionInitializer,
|
CreateSubmitActionInitializer,
|
||||||
|
@ -26,9 +26,9 @@ import {
|
|||||||
import { Tabs } from 'antd';
|
import { Tabs } from 'antd';
|
||||||
import type { TabsProps } from 'antd';
|
import type { TabsProps } from 'antd';
|
||||||
import { usePluginVersion } from '../hooks/usePluginVersion';
|
import { usePluginVersion } from '../hooks/usePluginVersion';
|
||||||
import { OnlineUserDropdown } from '../components/OnlineUserProvider';
|
import { OnlineUserDropdown } from '../components/system/OnlineUserProvider';
|
||||||
import { MobileLink } from '../components/MobileLink';
|
import { MobileLink } from '../components/system/MobileLink';
|
||||||
import { Notifications } from '../components/Notifications';
|
import { Notifications } from '../components/system/Notifications';
|
||||||
import { useTranslation } from '../locale';
|
import { useTranslation } from '../locale';
|
||||||
|
|
||||||
export const useAppSpin = () => {
|
export const useAppSpin = () => {
|
||||||
@ -451,7 +451,7 @@ const usePageStyle = () => {
|
|||||||
return useContext(PageStyleContext).style;
|
return useContext(PageStyleContext).style;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HeraAdminLayout = (props) => {
|
export const AdminLayout = (props) => {
|
||||||
const { addMenuItem } = useCurrentUserSettingsMenu();
|
const { addMenuItem } = useCurrentUserSettingsMenu();
|
||||||
const [style, setStyle] = useState('classical');
|
const [style, setStyle] = useState('classical');
|
||||||
const tabItem = useTabSettings({ style, setStyle });
|
const tabItem = useTabSettings({ style, setStyle });
|
@ -8,7 +8,7 @@ import {
|
|||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import pathToRegexp from 'path-to-regexp';
|
import pathToRegexp from 'path-to-regexp';
|
||||||
import { useHeadStyles } from '../pages/style';
|
import { useHeadStyles } from './style';
|
||||||
import { PageHeader as AntdPageHeader } from '@ant-design/pro-layout';
|
import { PageHeader as AntdPageHeader } from '@ant-design/pro-layout';
|
||||||
|
|
||||||
export const DetailsPage: React.FC = () => {
|
export const DetailsPage: React.FC = () => {
|
||||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import { useStyles } from './style';
|
import { useStyles } from './style';
|
||||||
import { Carousel, Image } from 'antd';
|
import { Carousel, Image } from 'antd';
|
||||||
import { useRequest } from '@nocobase/client';
|
import { useRequest } from '@nocobase/client';
|
||||||
import { useAppSpin } from '../layouts';
|
import { useAppSpin } from './AdminLayout';
|
||||||
|
|
||||||
export const HomePage: React.FC<{}> = () => {
|
export const HomePage: React.FC<{}> = () => {
|
||||||
const { styles } = useStyles();
|
const { styles } = useStyles();
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
export * from './AdminLayout';
|
||||||
|
export * from './DetailsPage';
|
||||||
|
export * from './Home';
|
||||||
|
export * from './OutboundPage';
|
||||||
|
export * from './PageLayout';
|
@ -1,3 +0,0 @@
|
|||||||
export * from './useConfig';
|
|
||||||
export * from './usePrefixCls';
|
|
||||||
export * from './useToken';
|
|
@ -1,5 +0,0 @@
|
|||||||
import { ConfigProvider } from 'antd';
|
|
||||||
import { useContext } from 'react';
|
|
||||||
|
|
||||||
const { ConfigContext } = ConfigProvider;
|
|
||||||
export const useConfig = () => useContext(ConfigContext);
|
|
@ -1,17 +0,0 @@
|
|||||||
import { ConfigProvider } from 'antd';
|
|
||||||
import { useContext } from 'react';
|
|
||||||
|
|
||||||
export const usePrefixCls = (
|
|
||||||
tag?: string,
|
|
||||||
props?: {
|
|
||||||
prefixCls?: string;
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
const { getPrefixCls } = useContext(ConfigProvider.ConfigContext) || {};
|
|
||||||
if ('ConfigContext' in ConfigProvider) {
|
|
||||||
return getPrefixCls?.(tag, props?.prefixCls) || '';
|
|
||||||
} else {
|
|
||||||
const prefix = props?.prefixCls ?? 'ant-';
|
|
||||||
return `${prefix}${tag ?? ''}`;
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,13 +0,0 @@
|
|||||||
import { theme } from 'antd';
|
|
||||||
import { CustomToken } from '../style';
|
|
||||||
|
|
||||||
interface Result extends ReturnType<typeof theme.useToken> {
|
|
||||||
token: CustomToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
const useToken = () => {
|
|
||||||
const result = theme.useToken();
|
|
||||||
return result as Result;
|
|
||||||
};
|
|
||||||
|
|
||||||
export { useToken };
|
|
@ -1,4 +0,0 @@
|
|||||||
export * from './hooks';
|
|
||||||
export * from './loading';
|
|
||||||
export * from './portal';
|
|
||||||
export * from './style';
|
|
@ -1,14 +0,0 @@
|
|||||||
import { message } from 'antd';
|
|
||||||
|
|
||||||
export const loading = async (title: React.ReactNode = 'Loading...', processor: () => Promise<any>) => {
|
|
||||||
let hide: any = null;
|
|
||||||
const loading = setTimeout(() => {
|
|
||||||
hide = message.loading(title);
|
|
||||||
}, 100);
|
|
||||||
try {
|
|
||||||
return await processor();
|
|
||||||
} finally {
|
|
||||||
hide?.();
|
|
||||||
clearTimeout(loading);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,63 +0,0 @@
|
|||||||
import { Observer, ReactFC } from '@formily/react';
|
|
||||||
import { observable } from '@formily/reactive';
|
|
||||||
import React, { Fragment } from 'react';
|
|
||||||
import { createPortal } from 'react-dom';
|
|
||||||
import { render as reactRender, unmount as reactUnmount } from './render';
|
|
||||||
export interface IPortalProps {
|
|
||||||
id?: string | symbol;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PortalMap = observable(new Map<string | symbol, React.ReactNode>());
|
|
||||||
|
|
||||||
export const createPortalProvider = (id: string | symbol) => {
|
|
||||||
const Portal: ReactFC<IPortalProps> = (props) => {
|
|
||||||
if (props.id && !PortalMap.has(props.id)) {
|
|
||||||
PortalMap.set(props.id, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
{props.children}
|
|
||||||
<Observer>
|
|
||||||
{() => {
|
|
||||||
if (!props.id) return <></>;
|
|
||||||
const portal = PortalMap.get(props.id);
|
|
||||||
if (portal) return createPortal(portal, document.body);
|
|
||||||
return <></>;
|
|
||||||
}}
|
|
||||||
</Observer>
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
Portal.defaultProps = {
|
|
||||||
id,
|
|
||||||
};
|
|
||||||
return Portal;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function createPortalRoot<T extends React.ReactNode>(host: HTMLElement, id: string) {
|
|
||||||
function render(renderer?: () => T) {
|
|
||||||
if (PortalMap.has(id)) {
|
|
||||||
PortalMap.set(id, renderer?.());
|
|
||||||
} else if (host) {
|
|
||||||
reactRender(<Fragment>{renderer?.()}</Fragment>, host);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function unmount() {
|
|
||||||
if (PortalMap.has(id)) {
|
|
||||||
PortalMap.set(id, null);
|
|
||||||
}
|
|
||||||
if (host) {
|
|
||||||
const unmountResult = reactUnmount(host);
|
|
||||||
if (unmountResult && host.parentNode) {
|
|
||||||
host.parentNode?.removeChild(host);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
render,
|
|
||||||
unmount,
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,89 +0,0 @@
|
|||||||
import { ReactElement } from 'react';
|
|
||||||
import * as ReactDOM from 'react-dom';
|
|
||||||
import type { Root } from 'react-dom/client';
|
|
||||||
|
|
||||||
// 移植自rc-util: https://github.com/react-component/util/blob/master/src/React/render.ts
|
|
||||||
|
|
||||||
type CreateRoot = (container: ContainerType) => Root;
|
|
||||||
|
|
||||||
// Let compiler not to search module usage
|
|
||||||
const fullClone = {
|
|
||||||
...ReactDOM,
|
|
||||||
} as typeof ReactDOM & {
|
|
||||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED?: {
|
|
||||||
usingClientEntryPoint?: boolean;
|
|
||||||
};
|
|
||||||
createRoot?: CreateRoot;
|
|
||||||
};
|
|
||||||
|
|
||||||
const { version, render: reactRender, unmountComponentAtNode } = fullClone;
|
|
||||||
|
|
||||||
let createRoot: CreateRoot;
|
|
||||||
try {
|
|
||||||
const mainVersion = Number((version || '').split('.')[0]);
|
|
||||||
if (mainVersion >= 18 && fullClone.createRoot) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
createRoot = fullClone.createRoot;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Do nothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleWarning(skip: boolean) {
|
|
||||||
const { __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED } = fullClone;
|
|
||||||
|
|
||||||
if (
|
|
||||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED &&
|
|
||||||
typeof __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED === 'object'
|
|
||||||
) {
|
|
||||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.usingClientEntryPoint = skip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const MARK = '__antd_mobile_root__';
|
|
||||||
|
|
||||||
// ========================== Render ==========================
|
|
||||||
type ContainerType = (Element | DocumentFragment) & {
|
|
||||||
[MARK]?: Root;
|
|
||||||
};
|
|
||||||
|
|
||||||
function legacyRender(node: ReactElement, container: ContainerType) {
|
|
||||||
reactRender(node, container);
|
|
||||||
}
|
|
||||||
|
|
||||||
function concurrentRender(node: ReactElement, container: ContainerType) {
|
|
||||||
toggleWarning(true);
|
|
||||||
const root = container[MARK] || createRoot(container);
|
|
||||||
toggleWarning(false);
|
|
||||||
root.render(node);
|
|
||||||
container[MARK] = root;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function render(node: ReactElement, container: ContainerType) {
|
|
||||||
if (createRoot as unknown) {
|
|
||||||
concurrentRender(node, container);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
legacyRender(node, container);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========================== Unmount =========================
|
|
||||||
function legacyUnmount(container: ContainerType) {
|
|
||||||
return unmountComponentAtNode(container);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function concurrentUnmount(container: ContainerType) {
|
|
||||||
// Delay to unmount to avoid React 18 sync warning
|
|
||||||
return Promise.resolve().then(() => {
|
|
||||||
container[MARK]?.unmount();
|
|
||||||
delete container[MARK];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function unmount(container: ContainerType) {
|
|
||||||
if (createRoot as unknown) {
|
|
||||||
return concurrentUnmount(container);
|
|
||||||
}
|
|
||||||
|
|
||||||
return legacyUnmount(container);
|
|
||||||
}
|
|
@ -1,130 +0,0 @@
|
|||||||
import type { CSSInterpolation, CSSObject } from '@ant-design/cssinjs';
|
|
||||||
import { useStyleRegister } from '@ant-design/cssinjs';
|
|
||||||
import { merge } from '@formily/shared';
|
|
||||||
import type { ComponentTokenMap, GlobalToken } from 'antd/es/theme/interface';
|
|
||||||
import { AliasToken } from 'antd/es/theme/internal';
|
|
||||||
import { useConfig, usePrefixCls, useToken } from './hooks';
|
|
||||||
|
|
||||||
export interface CustomToken extends AliasToken {
|
|
||||||
/** 顶部导航栏主色 */
|
|
||||||
colorPrimaryHeader: string;
|
|
||||||
/** 导航栏背景色 */
|
|
||||||
colorBgHeader: string;
|
|
||||||
/** 导航栏菜单背景色悬浮态 */
|
|
||||||
colorBgHeaderMenuHover: string;
|
|
||||||
/** 导航栏菜单背景色激活态 */
|
|
||||||
colorBgHeaderMenuActive: string;
|
|
||||||
/** 导航栏菜单文本色 */
|
|
||||||
colorTextHeaderMenu: string;
|
|
||||||
/** 导航栏菜单文本色悬浮态 */
|
|
||||||
colorTextHeaderMenuHover: string;
|
|
||||||
/** 导航栏菜单文本色激活态 */
|
|
||||||
colorTextHeaderMenuActive: string;
|
|
||||||
|
|
||||||
/** UI 配置色 */
|
|
||||||
colorSettings: string;
|
|
||||||
/** 鼠标悬浮时显示的背景色 */
|
|
||||||
colorBgSettingsHover: string;
|
|
||||||
/** 鼠标悬浮时显示的边框色 */
|
|
||||||
colorBorderSettingsHover: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type OverrideComponent = keyof ComponentTokenMap | string;
|
|
||||||
|
|
||||||
export interface StyleInfo {
|
|
||||||
hashId: string;
|
|
||||||
prefixCls: string;
|
|
||||||
rootPrefixCls: string;
|
|
||||||
iconPrefixCls: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TokenWithCommonCls<T> = T & {
|
|
||||||
/** Wrap component class with `.` prefix */
|
|
||||||
componentCls: string;
|
|
||||||
/** Origin prefix which do not have `.` prefix */
|
|
||||||
prefixCls: string;
|
|
||||||
/** Wrap icon class with `.` prefix */
|
|
||||||
iconCls: string;
|
|
||||||
/** Wrap ant prefixCls class with `.` prefix */
|
|
||||||
antCls: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type GenerateStyle<
|
|
||||||
ComponentToken extends object = TokenWithCommonCls<GlobalToken>,
|
|
||||||
ReturnType = CSSInterpolation,
|
|
||||||
> = (token: ComponentToken, options?: any) => ReturnType;
|
|
||||||
|
|
||||||
export const genCommonStyle = (token: any, componentPrefixCls: string): CSSObject => {
|
|
||||||
const { fontFamily, fontSize } = token;
|
|
||||||
|
|
||||||
const rootPrefixSelector = `[class^="${componentPrefixCls}"], [class*=" ${componentPrefixCls}"]`;
|
|
||||||
|
|
||||||
return {
|
|
||||||
[rootPrefixSelector]: {
|
|
||||||
fontFamily,
|
|
||||||
fontSize,
|
|
||||||
boxSizing: 'border-box',
|
|
||||||
|
|
||||||
'&::before, &::after': {
|
|
||||||
boxSizing: 'border-box',
|
|
||||||
},
|
|
||||||
|
|
||||||
[rootPrefixSelector]: {
|
|
||||||
boxSizing: 'border-box',
|
|
||||||
|
|
||||||
'&::before, &::after': {
|
|
||||||
boxSizing: 'border-box',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
export type UseComponentStyleResult = {
|
|
||||||
wrapSSR: ReturnType<typeof useStyleRegister>;
|
|
||||||
hashId: string;
|
|
||||||
componentCls: string;
|
|
||||||
rootPrefixCls: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const genStyleHook = <ComponentName extends OverrideComponent>(
|
|
||||||
component: ComponentName,
|
|
||||||
styleFn: (token: TokenWithCommonCls<CustomToken>, props: any, info: StyleInfo) => CSSInterpolation,
|
|
||||||
) => {
|
|
||||||
return (props?: any): UseComponentStyleResult => {
|
|
||||||
const { theme, token, hashId } = useToken();
|
|
||||||
const { getPrefixCls, iconPrefixCls } = useConfig();
|
|
||||||
const prefixCls = usePrefixCls(component);
|
|
||||||
const rootPrefixCls = getPrefixCls();
|
|
||||||
|
|
||||||
return {
|
|
||||||
wrapSSR: useStyleRegister(
|
|
||||||
{
|
|
||||||
theme: theme as any,
|
|
||||||
token,
|
|
||||||
hashId,
|
|
||||||
path: ['formily-antd', component, prefixCls, iconPrefixCls],
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
const componentCls = `.${prefixCls}`;
|
|
||||||
const mergedToken: TokenWithCommonCls<CustomToken> = merge(token, {
|
|
||||||
componentCls,
|
|
||||||
prefixCls,
|
|
||||||
iconCls: `.${iconPrefixCls}`,
|
|
||||||
antCls: `.${rootPrefixCls}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
const styleInterpolation = styleFn(mergedToken, props, {
|
|
||||||
hashId,
|
|
||||||
prefixCls,
|
|
||||||
rootPrefixCls,
|
|
||||||
iconPrefixCls,
|
|
||||||
});
|
|
||||||
return [genCommonStyle(token, prefixCls), styleInterpolation];
|
|
||||||
},
|
|
||||||
),
|
|
||||||
hashId,
|
|
||||||
componentCls: prefixCls,
|
|
||||||
rootPrefixCls,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,18 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { render, screen } from 'testUtils';
|
|
||||||
import App1 from '../demos/demo1';
|
|
||||||
|
|
||||||
describe('ErrorFallback', () => {
|
|
||||||
it('should render correctly', () => {
|
|
||||||
render(<App1 />);
|
|
||||||
|
|
||||||
expect(screen.getByText(/render failed/i)).toBeInTheDocument();
|
|
||||||
expect(screen.getByText(/this is likely a nocobase internals bug\. please open an issue at/i)).toBeInTheDocument();
|
|
||||||
expect(screen.getByRole('link', { name: /feedback/i })).toBeInTheDocument();
|
|
||||||
expect(screen.getByText(/try again/i)).toBeInTheDocument();
|
|
||||||
expect(screen.getByText(/error: error message/i)).toBeInTheDocument();
|
|
||||||
|
|
||||||
// 底部复制按钮
|
|
||||||
expect(document.querySelector('.ant-typography-copy')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,15 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { ErrorBoundary } from 'react-error-boundary';
|
|
||||||
import { ErrorFallback } from '../ErrorFallback';
|
|
||||||
|
|
||||||
const App = () => {
|
|
||||||
throw new Error('error message');
|
|
||||||
};
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
return (
|
|
||||||
<ErrorBoundary FallbackComponent={ErrorFallback} onError={console.error}>
|
|
||||||
<App />
|
|
||||||
</ErrorBoundary>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,10 +0,0 @@
|
|||||||
---
|
|
||||||
group:
|
|
||||||
title: Schema Components
|
|
||||||
---
|
|
||||||
|
|
||||||
# ErrorFallback
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
<code src="./demos/demo1.tsx"></code>
|
|
@ -1 +0,0 @@
|
|||||||
export * from './ErrorFallback';
|
|
@ -1,145 +0,0 @@
|
|||||||
import { css } from '@emotion/css';
|
|
||||||
import { useField, useFieldSchema } from '@formily/react';
|
|
||||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useRecord, useDesignable, SchemaSettingsSwitchItem } from '@nocobase/client';
|
|
||||||
import { useIsBlockInPage } from './hooks/useIsBlockInPage';
|
|
||||||
|
|
||||||
const FixedBlockContext = React.createContext<{
|
|
||||||
setFixedBlock: (value: string | false) => void;
|
|
||||||
height: number | string;
|
|
||||||
fixedBlockUID: boolean | string;
|
|
||||||
fixedBlockUIDRef: React.MutableRefObject<boolean | string>;
|
|
||||||
inFixedBlock: boolean;
|
|
||||||
}>({
|
|
||||||
setFixedBlock: () => {},
|
|
||||||
height: 0,
|
|
||||||
fixedBlockUID: false,
|
|
||||||
fixedBlockUIDRef: { current: false },
|
|
||||||
inFixedBlock: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const useFixedSchema = () => {
|
|
||||||
const field = useField();
|
|
||||||
const fieldSchema = useFieldSchema();
|
|
||||||
const { setFixedBlock, fixedBlockUID, fixedBlockUIDRef } = useFixedBlock();
|
|
||||||
const hasSet = useRef(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!fixedBlockUIDRef.current || hasSet.current) {
|
|
||||||
setFixedBlock(field?.decoratorProps?.fixedBlock ? fieldSchema['x-uid'] : false);
|
|
||||||
hasSet.current = true;
|
|
||||||
}
|
|
||||||
}, [field?.decoratorProps?.fixedBlock]);
|
|
||||||
|
|
||||||
return fieldSchema['x-uid'] === fixedBlockUID;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useFixedBlock = () => {
|
|
||||||
return useContext(FixedBlockContext);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FixedBlockWrapper: React.FC = (props) => {
|
|
||||||
const fixedBlock = useFixedSchema();
|
|
||||||
const { height, fixedBlockUID } = useFixedBlock();
|
|
||||||
const record = useRecord();
|
|
||||||
const isPopup = Object.keys(record).length;
|
|
||||||
if (isPopup) {
|
|
||||||
return <>{props.children}</>;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* The fixedBlockUID of false means that the page has no fixed blocks
|
|
||||||
* isPopup means that the FixedBlock is in the popup mode
|
|
||||||
*/
|
|
||||||
if (!fixedBlock && fixedBlockUID) return null;
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="nb-fixed-block"
|
|
||||||
style={{
|
|
||||||
height: fixedBlockUID ? `calc(100vh - ${height})` : undefined,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FixedBlockDesignerItem = () => {
|
|
||||||
const field = useField();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const fieldSchema = useFieldSchema();
|
|
||||||
const { dn } = useDesignable();
|
|
||||||
const { inFixedBlock } = useFixedBlock();
|
|
||||||
const { isBlockInPage } = useIsBlockInPage();
|
|
||||||
|
|
||||||
if (!isBlockInPage() || !inFixedBlock) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<SchemaSettingsSwitchItem
|
|
||||||
title={t('Fix block')}
|
|
||||||
checked={fieldSchema['x-decorator-props']?.fixedBlock}
|
|
||||||
onChange={async (fixedBlock) => {
|
|
||||||
const decoratorProps = {
|
|
||||||
...fieldSchema['x-decorator-props'],
|
|
||||||
fixedBlock,
|
|
||||||
};
|
|
||||||
await dn.emit('patch', {
|
|
||||||
schema: {
|
|
||||||
['x-uid']: fieldSchema['x-uid'],
|
|
||||||
'x-decorator-props': decoratorProps,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
field.decoratorProps = fieldSchema['x-decorator-props'] = decoratorProps;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface FixedBlockProps {
|
|
||||||
height: number | string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fixedBlockCss = css`
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
.noco-card-item {
|
|
||||||
height: 100%;
|
|
||||||
.ant-card {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
.ant-card-body {
|
|
||||||
height: 1px;
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const FixedBlock: React.FC<FixedBlockProps> = (props) => {
|
|
||||||
const { height } = props;
|
|
||||||
const [fixedBlockUID, _setFixedBlock] = useState<false | string>(false);
|
|
||||||
const fixedBlockUIDRef = useRef(fixedBlockUID);
|
|
||||||
const setFixedBlock = (v) => {
|
|
||||||
fixedBlockUIDRef.current = v;
|
|
||||||
_setFixedBlock(v);
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<FixedBlockContext.Provider value={{ inFixedBlock: true, height, setFixedBlock, fixedBlockUID, fixedBlockUIDRef }}>
|
|
||||||
<div
|
|
||||||
className={fixedBlockUID ? fixedBlockCss : ''}
|
|
||||||
style={{
|
|
||||||
height: fixedBlockUID ? `calc(100vh - ${height})` : undefined,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</div>
|
|
||||||
</FixedBlockContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default FixedBlock;
|
|
@ -1,139 +0,0 @@
|
|||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { ISchema, useField, useFieldSchema } from '@formily/react';
|
|
||||||
import { SchemaSettings, useDesignable, useSchemaToolbar } from '@nocobase/client';
|
|
||||||
|
|
||||||
function useNotDisableHeader() {
|
|
||||||
const fieldSchema = useFieldSchema();
|
|
||||||
return !fieldSchema['x-component-props']?.disablePageHeader;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const pageSettings = new SchemaSettings({
|
|
||||||
name: 'PageSettings',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
name: 'enablePageHeader',
|
|
||||||
type: 'switch',
|
|
||||||
useComponentProps() {
|
|
||||||
const { dn } = useDesignable();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const fieldSchema = useFieldSchema();
|
|
||||||
return {
|
|
||||||
title: t('Enable page header'),
|
|
||||||
checked: !fieldSchema['x-component-props']?.disablePageHeader,
|
|
||||||
onChange(v) {
|
|
||||||
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
|
|
||||||
fieldSchema['x-component-props']['disablePageHeader'] = !v;
|
|
||||||
dn.emit('patch', {
|
|
||||||
schema: {
|
|
||||||
['x-uid']: fieldSchema['x-uid'],
|
|
||||||
['x-component-props']: fieldSchema['x-component-props'],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
dn.refresh();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'divider',
|
|
||||||
type: 'divider',
|
|
||||||
useVisible: useNotDisableHeader,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'displayPageTitle',
|
|
||||||
type: 'switch',
|
|
||||||
useVisible: useNotDisableHeader,
|
|
||||||
useComponentProps() {
|
|
||||||
const { dn } = useDesignable();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const fieldSchema = useFieldSchema();
|
|
||||||
return {
|
|
||||||
title: t('Display page title'),
|
|
||||||
checked: !fieldSchema['x-component-props']?.hidePageTitle,
|
|
||||||
onChange(v) {
|
|
||||||
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
|
|
||||||
fieldSchema['x-component-props']['hidePageTitle'] = !v;
|
|
||||||
dn.emit('patch', {
|
|
||||||
schema: {
|
|
||||||
['x-uid']: fieldSchema['x-uid'],
|
|
||||||
['x-component-props']: fieldSchema['x-component-props'],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
dn.refresh();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'editPageTitle',
|
|
||||||
type: 'modal',
|
|
||||||
useVisible() {
|
|
||||||
const notDisablePageHeader = useNotDisableHeader();
|
|
||||||
const fieldSchema = useFieldSchema();
|
|
||||||
const notHidePageTitle = !fieldSchema['x-component-props']?.hidePageTitle;
|
|
||||||
|
|
||||||
return notDisablePageHeader && notHidePageTitle;
|
|
||||||
},
|
|
||||||
useComponentProps() {
|
|
||||||
const { dn } = useDesignable();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const field = useField();
|
|
||||||
const fieldSchema = useFieldSchema();
|
|
||||||
const { title } = useSchemaToolbar();
|
|
||||||
return {
|
|
||||||
hide: true,
|
|
||||||
title: t('Edit page title'),
|
|
||||||
schema: {
|
|
||||||
type: 'object',
|
|
||||||
title: t('Edit page title'),
|
|
||||||
properties: {
|
|
||||||
title: {
|
|
||||||
title: t('Title'),
|
|
||||||
required: true,
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'Input',
|
|
||||||
'x-component-props': {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as ISchema,
|
|
||||||
initialValues: { title },
|
|
||||||
onSubmit({ title }) {
|
|
||||||
field.title = title;
|
|
||||||
fieldSchema['title'] = title;
|
|
||||||
dn.emit('patch', {
|
|
||||||
schema: {
|
|
||||||
['x-uid']: fieldSchema['x-uid'],
|
|
||||||
title,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'enablePageTabs',
|
|
||||||
type: 'switch',
|
|
||||||
useVisible: useNotDisableHeader,
|
|
||||||
useComponentProps() {
|
|
||||||
const { dn } = useDesignable();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const fieldSchema = useFieldSchema();
|
|
||||||
return {
|
|
||||||
title: t('Enable page tabs'),
|
|
||||||
checked: fieldSchema['x-component-props']?.enablePageTabs,
|
|
||||||
onChange(v) {
|
|
||||||
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
|
|
||||||
fieldSchema['x-component-props']['enablePageTabs'] = v;
|
|
||||||
dn.emit('patch', {
|
|
||||||
schema: {
|
|
||||||
['x-uid']: fieldSchema['x-uid'],
|
|
||||||
['x-component-props']: fieldSchema['x-component-props'],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
dn.refresh();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
@ -1,235 +0,0 @@
|
|||||||
import { PlusOutlined } from '@ant-design/icons';
|
|
||||||
import { PageHeader as AntdPageHeader } from '@ant-design/pro-layout';
|
|
||||||
import { FormLayout } from '@formily/antd-v5';
|
|
||||||
import { Schema, SchemaOptionsContext, useFieldSchema } from '@formily/react';
|
|
||||||
import { Button, Tabs } from 'antd';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
|
||||||
import { ErrorBoundary } from 'react-error-boundary';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useSearchParams } from 'react-router-dom';
|
|
||||||
import FixedBlock from './FixedBlock';
|
|
||||||
import { PageDesigner, PageTabDesigner } from './PageTabDesigner';
|
|
||||||
import { useAClStyles, useStyles } from './style';
|
|
||||||
import {
|
|
||||||
DndContext,
|
|
||||||
FilterBlockProvider,
|
|
||||||
FormDialog,
|
|
||||||
Icon,
|
|
||||||
SchemaComponent,
|
|
||||||
SchemaComponentOptions,
|
|
||||||
SortableItem,
|
|
||||||
useCompile,
|
|
||||||
useDesignable,
|
|
||||||
useDocumentTitle,
|
|
||||||
useGlobalTheme,
|
|
||||||
useToken,
|
|
||||||
} from '@nocobase/client';
|
|
||||||
import { useAppSpin } from '../../layouts';
|
|
||||||
import { useGetAriaLabelOfSchemaInitializer } from './hooks/useGetAriaLabelOfSchemaInitializer';
|
|
||||||
import { ErrorFallback } from '../error-fallback';
|
|
||||||
|
|
||||||
export const Page = (props) => {
|
|
||||||
const { children, ...others } = props;
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const compile = useCompile();
|
|
||||||
const { title, setTitle } = useDocumentTitle();
|
|
||||||
const fieldSchema = useFieldSchema();
|
|
||||||
const dn = useDesignable();
|
|
||||||
const { theme } = useGlobalTheme();
|
|
||||||
const { getAriaLabel } = useGetAriaLabelOfSchemaInitializer();
|
|
||||||
|
|
||||||
// react18 tab 动画会卡顿,所以第一个 tab 时,动画禁用,后面的 tab 才启用
|
|
||||||
const [hasMounted, setHasMounted] = useState(false);
|
|
||||||
useEffect(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
setHasMounted(true);
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!title) {
|
|
||||||
setTitle(t(fieldSchema.title));
|
|
||||||
}
|
|
||||||
}, [fieldSchema.title, title]);
|
|
||||||
const disablePageHeader = fieldSchema['x-component-props']?.disablePageHeader;
|
|
||||||
const enablePageTabs = fieldSchema['x-component-props']?.enablePageTabs;
|
|
||||||
const hidePageTitle = fieldSchema['x-component-props']?.hidePageTitle;
|
|
||||||
const options = useContext(SchemaOptionsContext);
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const activeKey = useMemo(
|
|
||||||
() => searchParams.get('tab') || Object.keys(fieldSchema.properties || {}).shift(),
|
|
||||||
[fieldSchema.properties, searchParams],
|
|
||||||
);
|
|
||||||
const [height, setHeight] = useState(0);
|
|
||||||
const { wrapSSR, hashId, componentCls } = useStyles();
|
|
||||||
const aclStyles = useAClStyles();
|
|
||||||
|
|
||||||
const handleErrors = (error) => {
|
|
||||||
console.error(error);
|
|
||||||
};
|
|
||||||
|
|
||||||
const pageHeaderTitle = hidePageTitle ? undefined : fieldSchema.title || compile(title);
|
|
||||||
return wrapSSR(
|
|
||||||
<FilterBlockProvider>
|
|
||||||
<div className={`${componentCls} ${hashId} ${aclStyles.styles}`}>
|
|
||||||
<PageDesigner title={fieldSchema.title || title} />
|
|
||||||
<div
|
|
||||||
ref={(ref) => {
|
|
||||||
setHeight(Math.floor(ref?.getBoundingClientRect().height || 0) + 1);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{!disablePageHeader && (
|
|
||||||
<AntdPageHeader
|
|
||||||
className={classNames('pageHeaderCss', pageHeaderTitle || enablePageTabs ? '' : 'height0')}
|
|
||||||
ghost={false}
|
|
||||||
// 如果标题为空的时候会导致 PageHeader 不渲染,所以这里设置一个空白字符,然后再设置高度为 0
|
|
||||||
title={pageHeaderTitle || ' '}
|
|
||||||
{...others}
|
|
||||||
footer={
|
|
||||||
enablePageTabs && (
|
|
||||||
<DndContext>
|
|
||||||
<Tabs
|
|
||||||
size={'small'}
|
|
||||||
animated={hasMounted}
|
|
||||||
activeKey={activeKey}
|
|
||||||
onTabClick={(activeKey) => {
|
|
||||||
setLoading(true);
|
|
||||||
setSearchParams([['tab', activeKey]]);
|
|
||||||
setTimeout(() => {
|
|
||||||
setLoading(false);
|
|
||||||
}, 50);
|
|
||||||
}}
|
|
||||||
tabBarExtraContent={
|
|
||||||
dn.designable && (
|
|
||||||
<Button
|
|
||||||
aria-label={getAriaLabel('tabs')}
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
className={'addTabBtn'}
|
|
||||||
type={'dashed'}
|
|
||||||
onClick={async () => {
|
|
||||||
const values = await FormDialog(
|
|
||||||
t('Add tab'),
|
|
||||||
() => {
|
|
||||||
return (
|
|
||||||
<SchemaComponentOptions
|
|
||||||
scope={options.scope}
|
|
||||||
components={{ ...options.components }}
|
|
||||||
>
|
|
||||||
<FormLayout layout={'vertical'}>
|
|
||||||
<SchemaComponent
|
|
||||||
schema={{
|
|
||||||
properties: {
|
|
||||||
title: {
|
|
||||||
title: t('Tab name'),
|
|
||||||
'x-component': 'Input',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
title: t('Icon'),
|
|
||||||
'x-component': 'IconPicker',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormLayout>
|
|
||||||
</SchemaComponentOptions>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
theme,
|
|
||||||
).open({
|
|
||||||
initialValues: {},
|
|
||||||
});
|
|
||||||
const { title, icon } = values;
|
|
||||||
dn.insertBeforeEnd({
|
|
||||||
type: 'void',
|
|
||||||
title,
|
|
||||||
'x-icon': icon,
|
|
||||||
'x-component': 'Grid',
|
|
||||||
'x-initializer': 'BlockInitializers',
|
|
||||||
properties: {},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('Add tab')}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
items={fieldSchema.mapProperties((schema) => {
|
|
||||||
return {
|
|
||||||
label: (
|
|
||||||
<SortableItem
|
|
||||||
id={schema.name as string}
|
|
||||||
schema={schema}
|
|
||||||
className={classNames('nb-action-link', 'designerCss', props.className)}
|
|
||||||
>
|
|
||||||
{schema['x-icon'] && <Icon style={{ marginRight: 8 }} type={schema['x-icon']} />}
|
|
||||||
<span>{schema.title || t('Unnamed')}</span>
|
|
||||||
<PageTabDesigner schema={schema} />
|
|
||||||
</SortableItem>
|
|
||||||
),
|
|
||||||
key: schema.name as string,
|
|
||||||
};
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</DndContext>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="nb-page-wrapper">
|
|
||||||
<ErrorBoundary FallbackComponent={ErrorFallback} onError={handleErrors}>
|
|
||||||
{PageContent(loading, disablePageHeader, enablePageTabs, fieldSchema, activeKey, height, props)}
|
|
||||||
</ErrorBoundary>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</FilterBlockProvider>,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Page.displayName = 'Page';
|
|
||||||
|
|
||||||
function PageContent(
|
|
||||||
loading: boolean,
|
|
||||||
disablePageHeader: any,
|
|
||||||
enablePageTabs: any,
|
|
||||||
fieldSchema: Schema<any, any, any, any, any, any, any, any, any>,
|
|
||||||
activeKey: string,
|
|
||||||
height: number,
|
|
||||||
props: any,
|
|
||||||
): React.ReactNode {
|
|
||||||
const { token } = useToken();
|
|
||||||
const { render } = useAppSpin();
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return render();
|
|
||||||
}
|
|
||||||
|
|
||||||
return !disablePageHeader && enablePageTabs ? (
|
|
||||||
fieldSchema.mapProperties((schema) => {
|
|
||||||
if (schema.name !== activeKey) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FixedBlock key={schema.name} height={`calc(${height}px + 46px + ${token.marginLG}px * 2)`}>
|
|
||||||
<SchemaComponent
|
|
||||||
schema={
|
|
||||||
new Schema({
|
|
||||||
properties: {
|
|
||||||
[schema.name]: schema,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</FixedBlock>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
) : (
|
|
||||||
<FixedBlock height={`calc(${height}px + 46px + ${token.marginLG}px * 2)`}>
|
|
||||||
<div className={`pageWithFixedBlockCss nb-page-content`}>{props.children}</div>
|
|
||||||
</FixedBlock>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
import { App } from 'antd';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { ISchema } from '@formily/json-schema';
|
|
||||||
import { useDesignable, useSchemaToolbar, SchemaSettings } from '@nocobase/client';
|
|
||||||
|
|
||||||
export const pageTabSettings = new SchemaSettings({
|
|
||||||
name: 'PageTabSettings',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
name: 'edit',
|
|
||||||
type: 'modal',
|
|
||||||
useComponentProps() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { schema } = useSchemaToolbar<{ schema: ISchema }>();
|
|
||||||
const { dn } = useDesignable();
|
|
||||||
return {
|
|
||||||
title: t('Edit'),
|
|
||||||
schema: {
|
|
||||||
type: 'object',
|
|
||||||
title: t('Edit tab'),
|
|
||||||
properties: {
|
|
||||||
title: {
|
|
||||||
title: t('Tab name'),
|
|
||||||
required: true,
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'Input',
|
|
||||||
'x-component-props': {},
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
title: t('Icon'),
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'IconPicker',
|
|
||||||
'x-component-props': {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as ISchema,
|
|
||||||
initialValues: { title: schema.title, icon: schema['x-icon'] },
|
|
||||||
onSubmit: ({ title, icon }) => {
|
|
||||||
schema.title = title;
|
|
||||||
schema['x-icon'] = icon;
|
|
||||||
dn.emit('patch', {
|
|
||||||
schema: {
|
|
||||||
['x-uid']: schema['x-uid'],
|
|
||||||
title,
|
|
||||||
'x-icon': icon,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
dn.refresh();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'divider',
|
|
||||||
type: 'divider',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'delete',
|
|
||||||
type: 'item',
|
|
||||||
useComponentProps() {
|
|
||||||
const { modal } = App.useApp();
|
|
||||||
const { dn } = useDesignable();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { schema } = useSchemaToolbar();
|
|
||||||
return {
|
|
||||||
title: 'Delete',
|
|
||||||
eventKey: 'remove',
|
|
||||||
onClick() {
|
|
||||||
modal.confirm({
|
|
||||||
title: t('Delete block'),
|
|
||||||
content: t('Are you sure you want to delete it?'),
|
|
||||||
...confirm,
|
|
||||||
onOk() {
|
|
||||||
dn.remove(schema);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
children: t('Delete'),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
@ -1,56 +0,0 @@
|
|||||||
import { DragOutlined } from '@ant-design/icons';
|
|
||||||
import { useFieldSchema } from '@formily/react';
|
|
||||||
import { DragHandler, SchemaToolbarProvider, useDesignable, useSchemaSettingsRender } from '@nocobase/client';
|
|
||||||
import { Space } from 'antd';
|
|
||||||
import React from 'react';
|
|
||||||
import { useGetAriaLabelOfDesigner } from './hooks/useGetAriaLabelOfDesigner';
|
|
||||||
|
|
||||||
export const PageDesigner = ({ title }) => {
|
|
||||||
const { designable } = useDesignable();
|
|
||||||
const fieldSchema = useFieldSchema();
|
|
||||||
const { render } = useSchemaSettingsRender(
|
|
||||||
fieldSchema['x-settings'] || 'PageSettings',
|
|
||||||
fieldSchema['x-settings-props'],
|
|
||||||
);
|
|
||||||
if (!designable) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<SchemaToolbarProvider title={title}>
|
|
||||||
<div className={'general-schema-designer'}>
|
|
||||||
<div className={'general-schema-designer-icons'}>
|
|
||||||
<Space size={2} align={'center'}>
|
|
||||||
{render()}
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</SchemaToolbarProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const PageTabDesigner = ({ schema }) => {
|
|
||||||
const { designable } = useDesignable();
|
|
||||||
const { getAriaLabel } = useGetAriaLabelOfDesigner();
|
|
||||||
const fieldSchema = useFieldSchema();
|
|
||||||
const { render } = useSchemaSettingsRender(
|
|
||||||
fieldSchema['x-settings'] || 'PageTabSettings',
|
|
||||||
fieldSchema['x-settings-props'],
|
|
||||||
);
|
|
||||||
if (!designable) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<SchemaToolbarProvider schema={schema}>
|
|
||||||
<div className={'general-schema-designer'}>
|
|
||||||
<div className={'general-schema-designer-icons'}>
|
|
||||||
<Space size={3} align={'center'}>
|
|
||||||
<DragHandler>
|
|
||||||
<DragOutlined style={{ marginRight: 0 }} role="button" aria-label={getAriaLabel('drag-handler', 'tab')} />
|
|
||||||
</DragHandler>
|
|
||||||
{render()}
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</SchemaToolbarProvider>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,22 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { render, screen, waitFor } from 'testUtils';
|
|
||||||
import App1 from '../demos/demo1';
|
|
||||||
import { GlobalThemeProvider } from '@nocobase/client';
|
|
||||||
|
|
||||||
describe('Page', () => {
|
|
||||||
it('should render correctly', async () => {
|
|
||||||
render(<App1 />, {
|
|
||||||
wrapper: GlobalThemeProvider,
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(screen.getByText(/page title/i)).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(screen.getByText(/page content/i)).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(document.title).toBe('Page Title - NocoBase');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,37 +0,0 @@
|
|||||||
import { ISchema } from '@formily/react';
|
|
||||||
import { DocumentTitleProvider, Page, SchemaComponent, SchemaComponentProvider, Application } from '@nocobase/client';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
const schema: ISchema = {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
page1: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'Page',
|
|
||||||
title: 'Page Title',
|
|
||||||
properties: {
|
|
||||||
content: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'div',
|
|
||||||
'x-content': 'Page Content',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const Root = () => {
|
|
||||||
return (
|
|
||||||
<SchemaComponentProvider components={{ Page }}>
|
|
||||||
<DocumentTitleProvider addonAfter={'NocoBase'}>
|
|
||||||
<SchemaComponent schema={schema} />
|
|
||||||
</DocumentTitleProvider>
|
|
||||||
</SchemaComponentProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const app = new Application({
|
|
||||||
providers: [Root],
|
|
||||||
});
|
|
||||||
|
|
||||||
export default app.getRootComponent();
|
|
@ -1,28 +0,0 @@
|
|||||||
import { useFieldSchema } from '@formily/react';
|
|
||||||
import { useCollection_deprecated } from '@nocobase/client';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* label = 'designer' + name + x-component + [x-designer] + [collectionName] + [x-collection-field] + [postfix]
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const useGetAriaLabelOfDesigner = () => {
|
|
||||||
const fieldSchema = useFieldSchema();
|
|
||||||
const { name: _collectionName } = useCollection_deprecated();
|
|
||||||
const getAriaLabel = useCallback(
|
|
||||||
(name: string, postfix?: string) => {
|
|
||||||
if (!fieldSchema) return '';
|
|
||||||
|
|
||||||
const component = fieldSchema['x-component'];
|
|
||||||
const designer = fieldSchema['x-designer'] ? `-${fieldSchema['x-designer']}` : '';
|
|
||||||
const collectionField = fieldSchema['x-collection-field'] ? `-${fieldSchema['x-collection-field']}` : '';
|
|
||||||
const collectionName = _collectionName ? `-${_collectionName}` : '';
|
|
||||||
postfix = postfix ? `-${postfix}` : '';
|
|
||||||
|
|
||||||
return `designer-${name}-${component}${designer}${collectionName}${collectionField}${postfix}`;
|
|
||||||
},
|
|
||||||
[fieldSchema, _collectionName],
|
|
||||||
);
|
|
||||||
|
|
||||||
return { getAriaLabel };
|
|
||||||
};
|
|
@ -1,25 +0,0 @@
|
|||||||
import { useFieldSchema } from '@formily/react';
|
|
||||||
import { useCollection_deprecated } from '@nocobase/client';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* label = 'schema-initializer' + x-component + [x-initializer] + [collectionName] + [postfix]
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const useGetAriaLabelOfSchemaInitializer = () => {
|
|
||||||
const fieldSchema = useFieldSchema();
|
|
||||||
const { name } = useCollection_deprecated();
|
|
||||||
const getAriaLabel = useCallback(
|
|
||||||
(postfix?: string) => {
|
|
||||||
if (!fieldSchema) return '';
|
|
||||||
const initializer = fieldSchema['x-initializer'] ? `-${fieldSchema['x-initializer']}` : '';
|
|
||||||
const collectionName = name ? `-${name}` : '';
|
|
||||||
postfix = postfix ? `-${postfix}` : '';
|
|
||||||
|
|
||||||
return `schema-initializer-${fieldSchema['x-component']}${initializer}${collectionName}${postfix}`;
|
|
||||||
},
|
|
||||||
[fieldSchema, name],
|
|
||||||
);
|
|
||||||
|
|
||||||
return { getAriaLabel };
|
|
||||||
};
|
|
@ -1,15 +0,0 @@
|
|||||||
import { useActionContext } from '@nocobase/client';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断当前区块是否在页面而不是在弹窗中
|
|
||||||
*/
|
|
||||||
export const useIsBlockInPage = () => {
|
|
||||||
const { visible } = useActionContext();
|
|
||||||
|
|
||||||
const isBlockInPage = useCallback(() => {
|
|
||||||
return !visible;
|
|
||||||
}, [visible]);
|
|
||||||
|
|
||||||
return { isBlockInPage };
|
|
||||||
};
|
|
@ -1,11 +0,0 @@
|
|||||||
---
|
|
||||||
group:
|
|
||||||
title: Schema Components
|
|
||||||
order: 3
|
|
||||||
---
|
|
||||||
|
|
||||||
# Page
|
|
||||||
|
|
||||||
可以与 DocumentTitleProvider 搭配使用,将 page title 显示在 document.title 上。
|
|
||||||
|
|
||||||
<code src="./demos/demo1.tsx"></code>
|
|
@ -1,4 +0,0 @@
|
|||||||
export * from './Page';
|
|
||||||
export * from './FixedBlock';
|
|
||||||
export * from './PageTab.Settings';
|
|
||||||
export * from './Page.Settings';
|
|
@ -1,132 +0,0 @@
|
|||||||
import { createStyles } from 'antd-style';
|
|
||||||
import { genStyleHook } from '../__builtins__';
|
|
||||||
|
|
||||||
export const useAClStyles = createStyles(({ css }) => {
|
|
||||||
return css`
|
|
||||||
.ant-table-cell {
|
|
||||||
> .ant-space-horizontal {
|
|
||||||
.ant-space-item-split:has(+ .ant-space-item:empty) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
});
|
|
||||||
|
|
||||||
export const useStyles = genStyleHook('nb-page', (token) => {
|
|
||||||
const { componentCls } = token;
|
|
||||||
|
|
||||||
return {
|
|
||||||
[componentCls]: {
|
|
||||||
position: 'relative',
|
|
||||||
zIndex: 20,
|
|
||||||
flex: 1,
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
'&:hover': { '> .general-schema-designer': { display: 'block' } },
|
|
||||||
'.ant-page-header': { zIndex: 1, position: 'relative' },
|
|
||||||
'> .general-schema-designer': {
|
|
||||||
position: 'absolute',
|
|
||||||
zIndex: 999,
|
|
||||||
top: '0',
|
|
||||||
bottom: '0',
|
|
||||||
left: '0',
|
|
||||||
right: '0',
|
|
||||||
display: 'none',
|
|
||||||
border: '0',
|
|
||||||
pointerEvents: 'none',
|
|
||||||
'> .general-schema-designer-icons': {
|
|
||||||
zIndex: 9999,
|
|
||||||
position: 'absolute',
|
|
||||||
right: '2px',
|
|
||||||
top: '2px',
|
|
||||||
lineHeight: '16px',
|
|
||||||
pointerEvents: 'all',
|
|
||||||
'.ant-space-item': {
|
|
||||||
backgroundColor: 'var(--colorSettings)',
|
|
||||||
color: '#fff',
|
|
||||||
lineHeight: '16px',
|
|
||||||
width: '16px',
|
|
||||||
paddingLeft: '1px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
'.pageHeaderCss': {
|
|
||||||
backgroundColor: token.colorBgContainer,
|
|
||||||
paddingInline: token.paddingLG,
|
|
||||||
'&.ant-page-header-has-footer': {
|
|
||||||
paddingTop: token.paddingSM,
|
|
||||||
paddingBottom: '0',
|
|
||||||
'.ant-page-header-heading-left': {},
|
|
||||||
'.ant-page-header-footer': { marginBlockStart: '0' },
|
|
||||||
},
|
|
||||||
'.ant-tabs-nav': { marginBottom: '0' },
|
|
||||||
'.ant-page-header-heading-title': {
|
|
||||||
color: token.colorText,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
'.height0': {
|
|
||||||
fontSize: 0,
|
|
||||||
height: 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
'.addTabBtn': {
|
|
||||||
borderColor: 'var(--colorSettings)',
|
|
||||||
color: 'var(--colorSettings)',
|
|
||||||
},
|
|
||||||
|
|
||||||
'.designerCss': {
|
|
||||||
position: 'relative',
|
|
||||||
'&:hover': { '> .general-schema-designer': { display: 'block' } },
|
|
||||||
'&.nb-action-link': {
|
|
||||||
'> .general-schema-designer': {
|
|
||||||
top: '-10px',
|
|
||||||
bottom: '-10px',
|
|
||||||
left: '-10px',
|
|
||||||
right: '-10px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'> .general-schema-designer': {
|
|
||||||
position: 'absolute',
|
|
||||||
zIndex: 999,
|
|
||||||
top: '0',
|
|
||||||
bottom: '0',
|
|
||||||
left: '0',
|
|
||||||
right: '0',
|
|
||||||
display: 'none',
|
|
||||||
background: 'var(--colorBgSettingsHover)',
|
|
||||||
border: '0',
|
|
||||||
pointerEvents: 'none',
|
|
||||||
'> .general-schema-designer-icons': {
|
|
||||||
position: 'absolute',
|
|
||||||
right: '2px',
|
|
||||||
top: '2px',
|
|
||||||
lineHeight: '16px',
|
|
||||||
pointerEvents: 'all',
|
|
||||||
'.ant-space-item': {
|
|
||||||
backgroundColor: 'var(--colorSettings)',
|
|
||||||
color: '#fff',
|
|
||||||
lineHeight: '16px',
|
|
||||||
width: '16px',
|
|
||||||
paddingLeft: '1px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
'.pageWithFixedBlockCss': {
|
|
||||||
height: '100%',
|
|
||||||
'> .nb-grid:not(:last-child)': {
|
|
||||||
'> .nb-schema-initializer-button': { display: 'none' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
'.nb-page-wrapper': {
|
|
||||||
margin: token.marginLG,
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
@ -22,7 +22,6 @@ import {
|
|||||||
SchemaSettingsDataScope,
|
SchemaSettingsDataScope,
|
||||||
EditComponent,
|
EditComponent,
|
||||||
useFormBlockContext,
|
useFormBlockContext,
|
||||||
removeNullCondition,
|
|
||||||
useAPIClient,
|
useAPIClient,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import { ConfigProvider } from 'antd';
|
import { ConfigProvider } from 'antd';
|
||||||
@ -31,7 +30,7 @@ import { Schema, SchemaOptionsContext, observer, useField, useFieldSchema } from
|
|||||||
import { FormLayout } from '@formily/antd-v5';
|
import { FormLayout } from '@formily/antd-v5';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import { Field } from '@formily/core';
|
import { Field } from '@formily/core';
|
||||||
import { EditFormulaTitleField, EditTitle, EditTitleField } from '../../components/SchemaSettingOptions';
|
import { EditFormulaTitleField, EditTitle, EditTitleField } from '../../schema-settings';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { SchemaSettingsRemove } from '../../components/FormFilter/SchemaSettingsRemove';
|
import { SchemaSettingsRemove } from '../../components/FormFilter/SchemaSettingsRemove';
|
||||||
import { useTranslation } from '../../locale';
|
import { useTranslation } from '../../locale';
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ActionInitializer } from '@nocobase/client';
|
import { ActionInitializer } from '@nocobase/client';
|
||||||
|
import { Popover, Space, Button, Input } from 'antd';
|
||||||
|
import { useFieldSchema } from '@formily/react';
|
||||||
|
import { useProps } from '@nocobase/client';
|
||||||
|
import { ShareAltOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
export const OutboundLinkActionInitializer = (props) => {
|
export const OutboundLinkActionInitializer = (props) => {
|
||||||
const schema = {
|
const schema = {
|
||||||
@ -14,3 +18,29 @@ export const OutboundLinkActionInitializer = (props) => {
|
|||||||
};
|
};
|
||||||
return <ActionInitializer {...props} schema={schema} />;
|
return <ActionInitializer {...props} schema={schema} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const OutboundButton = (props) => {
|
||||||
|
const { onClick } = useProps(props);
|
||||||
|
const schema = useFieldSchema();
|
||||||
|
const url = window.location.href.split('/', 3).join('/');
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
placement="bottomRight"
|
||||||
|
trigger="click"
|
||||||
|
autoAdjustOverflow
|
||||||
|
content={
|
||||||
|
<Space.Compact style={{ width: '100%' }}>
|
||||||
|
<Input defaultValue={`${url}/r/${schema['x-uid']}`} />
|
||||||
|
<Button type="primary" onClick={onClick}>
|
||||||
|
复制链接
|
||||||
|
</Button>
|
||||||
|
</Space.Compact>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button>
|
||||||
|
<ShareAltOutlined />
|
||||||
|
外链
|
||||||
|
</Button>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -1,35 +1,25 @@
|
|||||||
import { Field } from '@formily/core';
|
import { Field } from '@formily/core';
|
||||||
import { ISchema, useField, useFieldSchema, useForm } from '@formily/react';
|
import { ISchema, useField, useFieldSchema } from '@formily/react';
|
||||||
import {
|
import {
|
||||||
SchemaSettingsLinkageRules,
|
|
||||||
SchemaSettingsModalItem,
|
SchemaSettingsModalItem,
|
||||||
SchemaSettingsSelectItem,
|
SchemaSettingsSelectItem,
|
||||||
SchemaSettingsSwitchItem,
|
SchemaSettingsSwitchItem,
|
||||||
css,
|
|
||||||
mergeFilter,
|
|
||||||
removeNullCondition,
|
|
||||||
useBlockRequestContext,
|
|
||||||
useCollection_deprecated,
|
useCollection_deprecated,
|
||||||
useCollectionManager_deprecated,
|
useCollectionManager_deprecated,
|
||||||
useCompile,
|
useCompile,
|
||||||
useDesignable,
|
useDesignable,
|
||||||
useFieldModeOptions,
|
|
||||||
useFilterFieldProps,
|
|
||||||
useFilterOptions,
|
|
||||||
useFormBlockContext,
|
useFormBlockContext,
|
||||||
useFormBlockType,
|
useFormBlockType,
|
||||||
useLinkageCollectionFilterOptions,
|
useLinkageCollectionFilterOptions,
|
||||||
useLocalVariables,
|
useLocalVariables,
|
||||||
useProps,
|
|
||||||
useRecord,
|
useRecord,
|
||||||
useSchemaTemplateManager,
|
useSchemaTemplateManager,
|
||||||
useSortFields,
|
|
||||||
useVariables,
|
useVariables,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import _, { get } from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useCallback, useContext, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from '../locale';
|
import { useTranslation } from '../locale';
|
||||||
import { FormFilterScope } from './FormFilter/FormFilterScope';
|
import { FormFilterScope } from '../components/FormFilter/FormFilterScope';
|
||||||
|
|
||||||
export const useFormulaTitleOptions = () => {
|
export const useFormulaTitleOptions = () => {
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
@ -376,3 +366,132 @@ export const SetFilterScope = (props) => {
|
|||||||
);
|
);
|
||||||
return <SchemaSettingsModalItem title={t('Custom filter')} width={770} schema={schema} onSubmit={onSubmit} />;
|
return <SchemaSettingsModalItem title={t('Custom filter')} width={770} schema={schema} onSubmit={onSubmit} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function AfterSuccess() {
|
||||||
|
const { dn } = useDesignable();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const component = fieldSchema.parent.parent['x-component'];
|
||||||
|
const schema = {
|
||||||
|
type: 'object',
|
||||||
|
title: t('After successful submission'),
|
||||||
|
properties: {
|
||||||
|
successMessage: {
|
||||||
|
title: t('Popup message'),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input.TextArea',
|
||||||
|
'x-component-props': {},
|
||||||
|
},
|
||||||
|
dataClear: {
|
||||||
|
title: t('Clear data method'),
|
||||||
|
enum: [
|
||||||
|
{ label: t('Automatic clear'), value: false },
|
||||||
|
{ label: t('Manually clear'), value: true },
|
||||||
|
],
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Radio.Group',
|
||||||
|
'x-component-props': {},
|
||||||
|
},
|
||||||
|
manualClose: {
|
||||||
|
title: t('Popup close method'),
|
||||||
|
enum: [
|
||||||
|
{ label: t('Automatic close'), value: false },
|
||||||
|
{ label: t('Manually close'), value: true },
|
||||||
|
],
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Radio.Group',
|
||||||
|
'x-component-props': {},
|
||||||
|
},
|
||||||
|
redirecting: {
|
||||||
|
title: t('Then'),
|
||||||
|
enum: [
|
||||||
|
{ label: t('Stay on current page'), value: false },
|
||||||
|
{ label: t('Redirect to'), value: true },
|
||||||
|
],
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Radio.Group',
|
||||||
|
'x-component-props': {},
|
||||||
|
'x-reactions': {
|
||||||
|
target: 'redirectTo',
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
visible: '{{!!$self.value}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
redirectTo: {
|
||||||
|
title: t('Link'),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-component-props': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (!(component as string).includes('Form')) {
|
||||||
|
delete schema.properties.dataClear;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<SchemaSettingsModalItem
|
||||||
|
title={t('After successful submission')}
|
||||||
|
initialValues={fieldSchema?.['x-action-settings']?.['onSuccess']}
|
||||||
|
schema={{ ...schema } as ISchema}
|
||||||
|
onSubmit={(onSuccess) => {
|
||||||
|
fieldSchema['x-action-settings']['onSuccess'] = onSuccess;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema: {
|
||||||
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
|
'x-action-settings': fieldSchema['x-action-settings'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加跳转页面选项
|
||||||
|
export const SessionSubmit = () => {
|
||||||
|
const { dn } = useDesignable();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
return (
|
||||||
|
<SchemaSettingsSwitchItem
|
||||||
|
title={t('Navigate to new page')}
|
||||||
|
checked={!!fieldSchema?.['x-action-settings']?.sessionSubmit}
|
||||||
|
onChange={(value) => {
|
||||||
|
fieldSchema['x-action-settings'].sessionSubmit = value;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema: {
|
||||||
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
|
'x-action-settings': {
|
||||||
|
...fieldSchema['x-action-settings'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function SessionUpdate() {
|
||||||
|
const { dn } = useDesignable();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
return (
|
||||||
|
<SchemaSettingsSwitchItem
|
||||||
|
title={t('更新询问')}
|
||||||
|
checked={!!fieldSchema?.['x-action-settings']?.sessionUpdate}
|
||||||
|
onChange={(value) => {
|
||||||
|
fieldSchema['x-action-settings'].sessionUpdate = value;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema: {
|
||||||
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
|
'x-action-settings': {
|
||||||
|
...fieldSchema['x-action-settings'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -29,7 +29,7 @@ export const LinkManager = () => {
|
|||||||
updateLink();
|
updateLink();
|
||||||
// 更新表格数据
|
// 更新表格数据
|
||||||
const mergedArray = tableData.map((item) => {
|
const mergedArray = tableData.map((item) => {
|
||||||
const matchingItem = item.id == formData.id;
|
const matchingItem = item.id === formData.id;
|
||||||
if (matchingItem) {
|
if (matchingItem) {
|
||||||
return formData;
|
return formData;
|
||||||
}
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
export * from './HomePageConfiguration';
|
||||||
|
export * from './LinkManager';
|
||||||
|
export * from './TokenConfiguration';
|
Loading…
Reference in New Issue
Block a user