mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-01 18:52:20 +08:00
feat: support to add Settings block in mobile (#5025)
* feat: support to add Settings block in mobile * style(Popup): clear the margin-bottom of the last block * refactor: extract hideDivider * fix: fix form values
This commit is contained in:
parent
5f9f94d008
commit
4590c754ce
@ -24,8 +24,7 @@ export const useMobileActionDrawerStyle = createStyles(({ css, token }: any) =>
|
|||||||
|
|
||||||
// to match the button named 'Add block'
|
// to match the button named 'Add block'
|
||||||
& + .nb-grid-container > .nb-grid > .nb-grid-warp > .ant-btn {
|
& + .nb-grid-container > .nb-grid > .nb-grid-warp > .ant-btn {
|
||||||
// 18px is the token marginBlock value
|
margin: 12px;
|
||||||
margin: 12px 12px calc(12px + 18px);
|
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
|
||||||
@ -48,6 +47,15 @@ export const useMobileActionDrawerStyle = createStyles(({ css, token }: any) =>
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
background-color: ${token.colorBgLayout};
|
background-color: ${token.colorBgLayout};
|
||||||
|
|
||||||
|
// clear the margin-bottom of the last block
|
||||||
|
& > .nb-grid-container > .nb-grid > .nb-grid-warp > .nb-grid-row:nth-last-child(2) .noco-card-item {
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
& > .ant-card {
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
|
|
||||||
footer: css`
|
footer: css`
|
||||||
@ -62,6 +70,10 @@ export const useMobileActionDrawerStyle = createStyles(({ css, token }: any) =>
|
|||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
border-top: 1px solid ${token.colorSplit};
|
border-top: 1px solid ${token.colorSplit};
|
||||||
background-color: ${token.colorBgLayout};
|
background-color: ${token.colorBgLayout};
|
||||||
|
|
||||||
|
.ant-btn {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { observer, RecursionField, useField, useFieldSchema } from '@formily/react';
|
import { ISchema, observer, RecursionField, useField, useFieldSchema } from '@formily/react';
|
||||||
import { Action, SchemaComponent, useActionContext } from '@nocobase/client';
|
import { Action, SchemaComponent, useActionContext } from '@nocobase/client';
|
||||||
import { ConfigProvider } from 'antd';
|
import { ConfigProvider } from 'antd';
|
||||||
import { Popup } from 'antd-mobile';
|
import { Popup } from 'antd-mobile';
|
||||||
@ -17,7 +17,7 @@ import { useMobileActionDrawerStyle } from './ActionDrawer.style';
|
|||||||
import { BasicZIndexProvider, MIN_Z_INDEX_INCREMENT, useBasicZIndex } from './BasicZIndexProvider';
|
import { BasicZIndexProvider, MIN_Z_INDEX_INCREMENT, useBasicZIndex } from './BasicZIndexProvider';
|
||||||
import { usePopupContainer } from './FilterAction';
|
import { usePopupContainer } from './FilterAction';
|
||||||
|
|
||||||
export const ActionDrawerUsedInMobile = observer((props: { footerNodeName?: string }) => {
|
export const ActionDrawerUsedInMobile: any = observer((props: { footerNodeName?: string }) => {
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const field = useField();
|
const field = useField();
|
||||||
const { visible, setVisible } = useActionContext();
|
const { visible, setVisible } = useActionContext();
|
||||||
@ -25,6 +25,13 @@ export const ActionDrawerUsedInMobile = observer((props: { footerNodeName?: stri
|
|||||||
const { styles } = useMobileActionDrawerStyle();
|
const { styles } = useMobileActionDrawerStyle();
|
||||||
const { basicZIndex } = useBasicZIndex();
|
const { basicZIndex } = useBasicZIndex();
|
||||||
|
|
||||||
|
// this schema need to add padding in the content area of the popup
|
||||||
|
const isSpecialSchema = isChangePasswordSchema(fieldSchema) || isEditProfileSchema(fieldSchema);
|
||||||
|
|
||||||
|
const footerNodeName = isSpecialSchema ? 'Action.Drawer.Footer' : props.footerNodeName;
|
||||||
|
|
||||||
|
const specialStyle = isSpecialSchema ? { backgroundColor: 'white' } : {};
|
||||||
|
|
||||||
const newZIndex = basicZIndex + MIN_Z_INDEX_INCREMENT;
|
const newZIndex = basicZIndex + MIN_Z_INDEX_INCREMENT;
|
||||||
|
|
||||||
const zIndexStyle = useMemo(() => {
|
const zIndexStyle = useMemo(() => {
|
||||||
@ -34,7 +41,7 @@ export const ActionDrawerUsedInMobile = observer((props: { footerNodeName?: stri
|
|||||||
}, [newZIndex]);
|
}, [newZIndex]);
|
||||||
|
|
||||||
const footerSchema = fieldSchema.reduceProperties((buf, s) => {
|
const footerSchema = fieldSchema.reduceProperties((buf, s) => {
|
||||||
if (s['x-component'] === props.footerNodeName) {
|
if (s['x-component'] === footerNodeName) {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
return buf;
|
return buf;
|
||||||
@ -81,24 +88,32 @@ export const ActionDrawerUsedInMobile = observer((props: { footerNodeName?: stri
|
|||||||
<CloseOutline />
|
<CloseOutline />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<SchemaComponent
|
{isSpecialSchema ? (
|
||||||
schema={fieldSchema}
|
<div style={{ padding: 12, ...specialStyle }}>
|
||||||
onlyRenderProperties
|
<SchemaComponent
|
||||||
filterProperties={(s) => {
|
schema={fieldSchema}
|
||||||
return s['x-component'] !== props.footerNodeName;
|
filterProperties={(s) => {
|
||||||
}}
|
return s['x-component'] !== footerNodeName;
|
||||||
/>
|
}}
|
||||||
{/* used to offset the margin-bottom of the last block */}
|
/>
|
||||||
{/* The number 1 is to prevent the scroll bar from appearing */}
|
</div>
|
||||||
<div style={{ marginBottom: 1 - marginBlock }}></div>
|
) : (
|
||||||
|
<SchemaComponent
|
||||||
|
schema={fieldSchema}
|
||||||
|
onlyRenderProperties
|
||||||
|
filterProperties={(s) => {
|
||||||
|
return s['x-component'] !== footerNodeName;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{footerSchema ? (
|
{footerSchema ? (
|
||||||
<div className={styles.footer}>
|
<div className={styles.footer} style={isSpecialSchema ? specialStyle : null}>
|
||||||
<RecursionField
|
<RecursionField
|
||||||
basePath={field.address}
|
basePath={field.address}
|
||||||
schema={fieldSchema}
|
schema={fieldSchema}
|
||||||
onlyRenderProperties
|
onlyRenderProperties
|
||||||
filterProperties={(s) => {
|
filterProperties={(s) => {
|
||||||
return s['x-component'] === props.footerNodeName;
|
return s['x-component'] === footerNodeName;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -125,3 +140,11 @@ export const useToAdaptActionDrawerToMobile = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function isEditProfileSchema(schema: ISchema) {
|
||||||
|
return schema.title === `{{t("Edit profile")}}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isChangePasswordSchema(schema: ISchema) {
|
||||||
|
return schema.title === `{{t("Change password")}}`;
|
||||||
|
}
|
||||||
|
@ -108,7 +108,7 @@ export const useToAdaptFilterActionToMobile = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 之所以不直接使用 mobile-container 作为容器,是因为会影响到区块的拖拽功能。详见:https://nocobase.height.app/T-4959
|
* 之所以不直接在 mobile-container 中设置 transform,是因为会影响到子页面区块的拖拽功能。详见:https://nocobase.height.app/T-4959
|
||||||
* @param visible
|
* @param visible
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of the NocoBase (R) project.
|
||||||
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||||
|
* Authors: NocoBase Team.
|
||||||
|
*
|
||||||
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||||
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Schema } from '@nocobase/utils';
|
||||||
|
|
||||||
|
// 隐藏 Grid 组件的左右 divider,因为移动端不需要在一行中并列展示两个区块
|
||||||
|
export function hideDivider(schema: Schema) {
|
||||||
|
schema?.mapProperties((schema) => {
|
||||||
|
if (schema['x-component'] === 'Grid') {
|
||||||
|
schema['x-component-props'] = {
|
||||||
|
...schema['x-component-props'],
|
||||||
|
showDivider: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return schema;
|
||||||
|
}
|
@ -7,7 +7,7 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { observer, RecursionField, Schema, useField, useFieldSchema } from '@formily/react';
|
import { observer, RecursionField, useField, useFieldSchema } from '@formily/react';
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
DndContext,
|
DndContext,
|
||||||
@ -27,6 +27,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|||||||
import { MobilePageHeader } from '../../pages/dynamic-page';
|
import { MobilePageHeader } from '../../pages/dynamic-page';
|
||||||
import { MobilePageContentContainer } from '../../pages/dynamic-page/content/MobilePageContentContainer';
|
import { MobilePageContentContainer } from '../../pages/dynamic-page/content/MobilePageContentContainer';
|
||||||
import { useStyles } from '../../pages/dynamic-page/header/tabs';
|
import { useStyles } from '../../pages/dynamic-page/header/tabs';
|
||||||
|
import { hideDivider } from '../hideDivider';
|
||||||
import { useMobileTabsForMobileActionPageStyle } from './MobileTabsForMobileActionPage.style';
|
import { useMobileTabsForMobileActionPageStyle } from './MobileTabsForMobileActionPage.style';
|
||||||
|
|
||||||
export const MobileTabsForMobileActionPage: any = observer(
|
export const MobileTabsForMobileActionPage: any = observer(
|
||||||
@ -162,17 +163,3 @@ MobileTabsForMobileActionPage.TabPane = observer(
|
|||||||
);
|
);
|
||||||
|
|
||||||
MobileTabsForMobileActionPage.Designer = TabsOfPC.Designer;
|
MobileTabsForMobileActionPage.Designer = TabsOfPC.Designer;
|
||||||
|
|
||||||
// 隐藏 Grid 组件的左右 divider,因为移动端不需要在一行中并列展示两个区块
|
|
||||||
function hideDivider(tabPaneSchema: Schema) {
|
|
||||||
tabPaneSchema?.mapProperties((schema) => {
|
|
||||||
if (schema['x-component'] === 'Grid') {
|
|
||||||
schema['x-component-props'] = {
|
|
||||||
...schema['x-component-props'],
|
|
||||||
showDivider: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return tabPaneSchema;
|
|
||||||
}
|
|
||||||
|
@ -45,6 +45,9 @@ import {
|
|||||||
|
|
||||||
// 导出 JSBridge,会挂在到 window 上
|
// 导出 JSBridge,会挂在到 window 上
|
||||||
import './js-bridge';
|
import './js-bridge';
|
||||||
|
import { MobileSettings } from './mobile-blocks/settings-block/MobileSettings';
|
||||||
|
import { MobileSettingsBlockInitializer } from './mobile-blocks/settings-block/MobileSettingsBlockInitializer';
|
||||||
|
import { MobileSettingsBlockSchemaSettings } from './mobile-blocks/settings-block/schemaSettings';
|
||||||
import { MobileCheckerProvider } from './providers';
|
import { MobileCheckerProvider } from './providers';
|
||||||
|
|
||||||
export * from './desktop-mode';
|
export * from './desktop-mode';
|
||||||
@ -135,6 +138,7 @@ export class PluginMobileClient extends Plugin {
|
|||||||
mobileTabBarLinkSettings,
|
mobileTabBarLinkSettings,
|
||||||
mobilePageTabsSettings,
|
mobilePageTabsSettings,
|
||||||
mobileNavigationBarLinkSettings,
|
mobileNavigationBarLinkSettings,
|
||||||
|
MobileSettingsBlockSchemaSettings,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,6 +154,8 @@ export class PluginMobileClient extends Plugin {
|
|||||||
MobilePageTabs,
|
MobilePageTabs,
|
||||||
MobileNavigationActionBar,
|
MobileNavigationActionBar,
|
||||||
MobileNotFoundPage,
|
MobileNotFoundPage,
|
||||||
|
MobileSettingsBlockInitializer,
|
||||||
|
MobileSettings,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of the NocoBase (R) project.
|
||||||
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||||
|
* Authors: NocoBase Team.
|
||||||
|
*
|
||||||
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||||
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { cx, SettingsMenu, SortableItem, useDesigner, useToken } from '@nocobase/client';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
|
||||||
|
export const InternalSettings = () => {
|
||||||
|
const Designer = useDesigner();
|
||||||
|
const { token } = useToken();
|
||||||
|
const style = useMemo(() => {
|
||||||
|
return {
|
||||||
|
marginBottom: token.marginBlock,
|
||||||
|
};
|
||||||
|
}, [token.marginBlock]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SortableItem className={cx('nb-mobile-setting')} style={style}>
|
||||||
|
<Designer />
|
||||||
|
<SettingsMenu />
|
||||||
|
</SortableItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export const MobileSettings = InternalSettings;
|
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of the NocoBase (R) project.
|
||||||
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||||
|
* Authors: NocoBase Team.
|
||||||
|
*
|
||||||
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||||
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { SettingOutlined } from '@ant-design/icons';
|
||||||
|
import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '@nocobase/client';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export const MobileSettingsBlockInitializer = () => {
|
||||||
|
const itemConfig = useSchemaInitializerItem();
|
||||||
|
const { insert } = useSchemaInitializer();
|
||||||
|
return (
|
||||||
|
<SchemaInitializerItem
|
||||||
|
icon={<SettingOutlined />}
|
||||||
|
onClick={async () => {
|
||||||
|
insert({
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'MobileSettings',
|
||||||
|
'x-settings': 'blockSettings:mobileSettings',
|
||||||
|
'x-component-props': {},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
{...itemConfig}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of the NocoBase (R) project.
|
||||||
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||||
|
* Authors: NocoBase Team.
|
||||||
|
*
|
||||||
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||||
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { SchemaSettings } from '@nocobase/client';
|
||||||
|
import { usePluginTranslation } from '../../locale';
|
||||||
|
|
||||||
|
export const MobileSettingsBlockSchemaSettings = new SchemaSettings({
|
||||||
|
name: 'blockSettings:mobileSettings',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: 'delete',
|
||||||
|
type: 'remove',
|
||||||
|
useComponentProps() {
|
||||||
|
const { t } = usePluginTranslation();
|
||||||
|
|
||||||
|
return {
|
||||||
|
removeParentsIfNoChildren: true,
|
||||||
|
confirm: {
|
||||||
|
title: t('Delete settings block'),
|
||||||
|
},
|
||||||
|
breakRemoveOn: {
|
||||||
|
'x-component': 'Grid',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
@ -8,6 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { SchemaInitializer, gridRowColWrap } from '@nocobase/client';
|
import { SchemaInitializer, gridRowColWrap } from '@nocobase/client';
|
||||||
|
import { generatePluginTranslationTemplate } from '../../../locale';
|
||||||
|
|
||||||
export const mobileAddBlockInitializer = new SchemaInitializer({
|
export const mobileAddBlockInitializer = new SchemaInitializer({
|
||||||
title: '{{t("Add block")}}',
|
title: '{{t("Add block")}}',
|
||||||
@ -60,6 +61,11 @@ export const mobileAddBlockInitializer = new SchemaInitializer({
|
|||||||
title: '{{t("Markdown")}}',
|
title: '{{t("Markdown")}}',
|
||||||
Component: 'MarkdownBlockInitializer',
|
Component: 'MarkdownBlockInitializer',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'settings',
|
||||||
|
title: generatePluginTranslationTemplate('Settings'),
|
||||||
|
Component: 'MobileSettingsBlockInitializer',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -20,5 +20,6 @@
|
|||||||
"Title field is required": "Title field is required",
|
"Title field is required": "Title field is required",
|
||||||
"Icon field is required": "Icon field is required",
|
"Icon field is required": "Icon field is required",
|
||||||
"Desktop data blocks": "Desktop data blocks",
|
"Desktop data blocks": "Desktop data blocks",
|
||||||
"Other desktop blocks": "Other desktop blocks"
|
"Other desktop blocks": "Other desktop blocks",
|
||||||
|
"Settings": "Settings"
|
||||||
}
|
}
|
||||||
|
@ -21,5 +21,6 @@
|
|||||||
"Title field is required": "标题必填",
|
"Title field is required": "标题必填",
|
||||||
"Icon field is required": "图标必填",
|
"Icon field is required": "图标必填",
|
||||||
"Desktop data blocks": "桌面端数据区块",
|
"Desktop data blocks": "桌面端数据区块",
|
||||||
"Other desktop blocks": "其他桌面端区块"
|
"Other desktop blocks": "其他桌面端区块",
|
||||||
|
"Settings": "设置"
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { SelectWithTitle, useAPIClient, useCurrentUserContext, useSystemSettings } from '@nocobase/client';
|
import { SelectWithTitle, useCurrentUserContext, useSystemSettings } from '@nocobase/client';
|
||||||
import { error } from '@nocobase/utils/client';
|
import { error } from '@nocobase/utils/client';
|
||||||
import { MenuProps } from 'antd';
|
import { MenuProps } from 'antd';
|
||||||
import React, { useEffect, useMemo } from 'react';
|
import React, { useEffect, useMemo } from 'react';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user