From 4ac01b28db925b47c563cafb742b43331db6fe1b Mon Sep 17 00:00:00 2001 From: Dunqing Date: Tue, 13 Jun 2023 11:31:34 +0800 Subject: [PATCH] fix(mobile-client): some mobile-client bugs (#2017) * fix: avoid delete last tab will brake mobile client * fix: when schema not exists * feat: support redirect to mobile route in app * fix: re-insert repeat routes * feat: sync title * fix: sync title * fix: when TabBar is empty will cause page crashed * fix: default label i18n not work * fix: delete last menu will cannot add block ,Close T-434 * fix: tab cannot add block in drawer, Close T-437 * fix: hidden connect data block * fix: login should use replace * feat: display configuration of mobile-client address --- packages/core/client/src/auth/SigninPage.tsx | 2 +- .../src/filter-provider/FilterProvider.tsx | 11 +++++- .../src/schema-component/antd/tabs/Tabs.tsx | 16 ++++++-- .../schema-component/antd/tabs/context.tsx | 7 +++- .../src/schema-settings/SchemaSettings.tsx | 6 +++ .../src/client/configuration/App.tsx | 20 +++++++++- .../src/client/core/bridge/index.ts | 1 + .../src/client/core/bridge/injects.ts | 2 + .../src/client/core/schema/common/index.ts | 1 + .../src/client/core/schema/common/page.ts | 16 ++++++++ .../container/Container.Designer.tsx | 13 ++++--- .../schema/components/container/Container.tsx | 6 ++- .../core/schema/components/header/Header.tsx | 16 ++++++-- .../core/schema/components/menu/Menu.tsx | 27 +------------- .../schema/components/page/Page.Designer.tsx | 4 +- .../core/schema/components/page/Page.tsx | 20 +++++----- .../core/schema/components/tab-bar/TabBar.tsx | 37 ++++++++----------- .../mobile-client/src/client/locale/index.ts | 2 +- .../mobile-client/src/client/locale/zh-CN.ts | 2 + .../src/client/router/InterfaceRouter.tsx | 2 +- .../src/client/router/RouteSwitchProvider.tsx | 8 ++++ 21 files changed, 142 insertions(+), 77 deletions(-) create mode 100644 packages/plugins/mobile-client/src/client/core/schema/common/index.ts create mode 100644 packages/plugins/mobile-client/src/client/core/schema/common/page.ts diff --git a/packages/core/client/src/auth/SigninPage.tsx b/packages/core/client/src/auth/SigninPage.tsx index 6694ea623..010b1a10e 100644 --- a/packages/core/client/src/auth/SigninPage.tsx +++ b/packages/core/client/src/auth/SigninPage.tsx @@ -52,7 +52,7 @@ export function useRedirect(next = '/admin') { const history = useHistory(); const redirect = location?.['query']?.redirect; return useCallback(() => { - history.push(redirect || '/admin'); + history.replace(redirect || '/admin'); }, [redirect, history]); } diff --git a/packages/core/client/src/filter-provider/FilterProvider.tsx b/packages/core/client/src/filter-provider/FilterProvider.tsx index b117e8250..03f252424 100644 --- a/packages/core/client/src/filter-provider/FilterProvider.tsx +++ b/packages/core/client/src/filter-provider/FilterProvider.tsx @@ -119,6 +119,7 @@ export const useFilterBlock = () => { // 有可能存在页面没有提供 FilterBlockProvider 的情况,比如内部使用的数据表管理页面 if (!ctx) { return { + inProvider: false, recordDataBlocks: () => {}, getDataBlocks: () => [] as DataBlock[], removeDataBlock: () => {}, @@ -142,5 +143,13 @@ export const useFilterBlock = () => { setDataBlocks((prev) => prev.filter((item) => item.uid !== uid)); }; - return { recordDataBlocks, getDataBlocks, removeDataBlock }; + return { + recordDataBlocks, + getDataBlocks, + removeDataBlock, + /** + * running in FilterBlockProvider + */ + inProvider: true, + }; }; diff --git a/packages/core/client/src/schema-component/antd/tabs/Tabs.tsx b/packages/core/client/src/schema-component/antd/tabs/Tabs.tsx index 60c025af0..7d08eabe9 100644 --- a/packages/core/client/src/schema-component/antd/tabs/Tabs.tsx +++ b/packages/core/client/src/schema-component/antd/tabs/Tabs.tsx @@ -2,12 +2,12 @@ import { css } from '@emotion/css'; import { observer, RecursionField, useField, useFieldSchema } from '@formily/react'; import { TabPaneProps, Tabs as AntdTabs, TabsProps } from 'antd'; import classNames from 'classnames'; -import React from 'react'; +import React, { useMemo } from 'react'; import { Icon } from '../../../icon'; import { useSchemaInitializer } from '../../../schema-initializer'; import { DndContext, SortableItem } from '../../common'; import { useDesigner } from '../../hooks/useDesigner'; -import { useTabsContext } from './context'; +import { TabsContextProvider, useTabsContext } from './context'; import { TabsDesigner } from './Tabs.Designer'; export const Tabs: any = observer( @@ -15,6 +15,14 @@ export const Tabs: any = observer( const fieldSchema = useFieldSchema(); const { render } = useSchemaInitializer(fieldSchema['x-initializer']); const contextProps = useTabsContext(); + + const PaneProvider = useMemo(() => { + if (contextProps.deep === false) { + return TabsContextProvider; + } + return React.Fragment; + }, [contextProps.deep]); + return ( { return ( } key={key}> - + + + ); })} diff --git a/packages/core/client/src/schema-component/antd/tabs/context.tsx b/packages/core/client/src/schema-component/antd/tabs/context.tsx index 48b026b4b..24d05eddc 100644 --- a/packages/core/client/src/schema-component/antd/tabs/context.tsx +++ b/packages/core/client/src/schema-component/antd/tabs/context.tsx @@ -1,9 +1,12 @@ import { TabsProps } from 'antd'; import React from 'react'; -const TabsContext = React.createContext({}); +interface TabsContextProps extends TabsProps { + deep?: boolean; +} +const TabsContext = React.createContext({}); -export const TabsContextProvider: React.FC = ({ children, ...props }) => { +export const TabsContextProvider: React.FC = ({ children, ...props }) => { return {children}; }; diff --git a/packages/core/client/src/schema-settings/SchemaSettings.tsx b/packages/core/client/src/schema-settings/SchemaSettings.tsx index 55ea1b903..2e781b425 100644 --- a/packages/core/client/src/schema-settings/SchemaSettings.tsx +++ b/packages/core/client/src/schema-settings/SchemaSettings.tsx @@ -41,6 +41,7 @@ import { useCollectionManager, useCompile, useDesignable, + useFilterBlock, useLinkageCollectionFilterOptions, } from '..'; import { findFilterTargets, updateFilterTargets } from '../block-provider/hooks'; @@ -465,10 +466,15 @@ SchemaSettings.ConnectDataBlocks = function ConnectDataBlocks(props: { const { dn } = useDesignable(); const { t } = useTranslation(); const collection = useCollection(); + const { inProvider } = useFilterBlock(); const dataBlocks = useSupportedBlocks(type); let { targets = [], uid } = findFilterTargets(fieldSchema); const compile = useCompile(); + if (!inProvider) { + return null; + } + const Content = dataBlocks.map((block) => { const title = `${compile(block.collection.title)} #${block.uid.slice(0, 4)}`; const onHover = () => { diff --git a/packages/plugins/mobile-client/src/client/configuration/App.tsx b/packages/plugins/mobile-client/src/client/configuration/App.tsx index 97372e1d6..c285ceffc 100644 --- a/packages/plugins/mobile-client/src/client/configuration/App.tsx +++ b/packages/plugins/mobile-client/src/client/configuration/App.tsx @@ -1,5 +1,23 @@ +import { Card, Form, Input } from 'antd'; import React from 'react'; +import { useTranslation } from '../locale'; export const AppConfiguration = () => { - return <>App Configuration; + const { t } = useTranslation(); + return ( + +
+ + + +
+
+ ); }; diff --git a/packages/plugins/mobile-client/src/client/core/bridge/index.ts b/packages/plugins/mobile-client/src/client/core/bridge/index.ts index 0d69b9a8d..4cae9d3a1 100644 --- a/packages/plugins/mobile-client/src/client/core/bridge/index.ts +++ b/packages/plugins/mobile-client/src/client/core/bridge/index.ts @@ -1 +1,2 @@ import './native-call'; +export * from './injects'; diff --git a/packages/plugins/mobile-client/src/client/core/bridge/injects.ts b/packages/plugins/mobile-client/src/client/core/bridge/injects.ts index 6fa9b1010..ff0af6d75 100644 --- a/packages/plugins/mobile-client/src/client/core/bridge/injects.ts +++ b/packages/plugins/mobile-client/src/client/core/bridge/injects.ts @@ -10,3 +10,5 @@ const jsBridge = (window as any).jsBridge as { export const invoke: InvokeFunction = (params, cb) => { jsBridge.invoke(params, cb); }; + +export const isJSBridge = !!jsBridge; diff --git a/packages/plugins/mobile-client/src/client/core/schema/common/index.ts b/packages/plugins/mobile-client/src/client/core/schema/common/index.ts new file mode 100644 index 000000000..c3a84df74 --- /dev/null +++ b/packages/plugins/mobile-client/src/client/core/schema/common/index.ts @@ -0,0 +1 @@ +export * from './page'; diff --git a/packages/plugins/mobile-client/src/client/core/schema/common/page.ts b/packages/plugins/mobile-client/src/client/core/schema/common/page.ts new file mode 100644 index 000000000..a2335e96c --- /dev/null +++ b/packages/plugins/mobile-client/src/client/core/schema/common/page.ts @@ -0,0 +1,16 @@ +export const PageSchema = { + type: 'void', + 'x-component': 'MPage', + 'x-designer': 'MPage.Designer', + 'x-component-props': {}, + properties: { + grid: { + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'MBlockInitializers', + 'x-component-props': { + showDivider: false, + }, + }, + }, +}; diff --git a/packages/plugins/mobile-client/src/client/core/schema/components/container/Container.Designer.tsx b/packages/plugins/mobile-client/src/client/core/schema/components/container/Container.Designer.tsx index f5966c444..a76f50802 100644 --- a/packages/plugins/mobile-client/src/client/core/schema/components/container/Container.Designer.tsx +++ b/packages/plugins/mobile-client/src/client/core/schema/components/container/Container.Designer.tsx @@ -1,12 +1,13 @@ import { SchemaSettings, useDesignable } from '@nocobase/client'; import React from 'react'; -import { useTranslation } from '../../../../locale'; +import { generateNTemplate, useTranslation } from '../../../../locale'; import { Schema, useField, useFieldSchema } from '@formily/react'; import { uid } from '@formily/shared'; import { useHistory } from 'react-router-dom'; import { findSchema } from '../../helpers'; import { Button } from 'antd'; import { MenuOutlined } from '@ant-design/icons'; +import { PageSchema } from '../../common'; export const ContainerDesigner = () => { const { t } = useTranslation(); @@ -60,7 +61,7 @@ export const ContainerDesigner = () => { 'x-designer': 'MTabBar.Item.Designer', 'x-component-props': { icon: 'HomeOutlined', - title: t('Untitled'), + title: generateNTemplate('Untitled'), }, properties: { page: pageSchema.toJSON(), @@ -69,10 +70,12 @@ export const ContainerDesigner = () => { }, }); } else { - const pageSchema = findSchema(tabBarSchema.properties[Object.keys(tabBarSchema.properties)[0]], 'MPage'); - if (!pageSchema) return; + const tabBarSchemaFirstKey = Object.keys(tabBarSchema.properties || {})?.[0]; + const pageSchema = tabBarSchemaFirstKey + ? findSchema(tabBarSchema.properties[tabBarSchemaFirstKey], 'MPage') + : null; await dn.remove(tabBarSchema); - await dn.insertBeforeEnd(pageSchema, { + await dn.insertBeforeEnd(pageSchema || PageSchema, { onSuccess() { history.push('../'); }, diff --git a/packages/plugins/mobile-client/src/client/core/schema/components/container/Container.tsx b/packages/plugins/mobile-client/src/client/core/schema/components/container/Container.tsx index bc7c7af87..f372ebb3e 100644 --- a/packages/plugins/mobile-client/src/client/core/schema/components/container/Container.tsx +++ b/packages/plugins/mobile-client/src/client/core/schema/components/container/Container.tsx @@ -34,7 +34,11 @@ const InternalContainer: React.FC = (props) => { const params = useParams<{ name: string }>(); const match = useRouteMatch(); const tabBarSchema = fieldSchema?.properties?.['tabBar']; - const redirectToUid = tabBarSchema?.properties[Object.keys(tabBarSchema.properties)[0]]['x-uid']; + const tabBarCurrentFirstKey = tabBarSchema?.properties ? Object.keys(tabBarSchema.properties)[0] : null; + let redirectToUid = null; + if (tabBarCurrentFirstKey) { + redirectToUid = tabBarSchema?.properties[tabBarCurrentFirstKey]?.['x-uid']; + } const tabRoutes = useMemo(() => { if (!redirectToUid) { diff --git a/packages/plugins/mobile-client/src/client/core/schema/components/header/Header.tsx b/packages/plugins/mobile-client/src/client/core/schema/components/header/Header.tsx index 46c25b13d..bbd4112ee 100644 --- a/packages/plugins/mobile-client/src/client/core/schema/components/header/Header.tsx +++ b/packages/plugins/mobile-client/src/client/core/schema/components/header/Header.tsx @@ -1,10 +1,11 @@ import { css, cx } from '@emotion/css'; import { SortableItem, useCompile, useDesigner } from '@nocobase/client'; import { NavBar, NavBarProps } from 'antd-mobile'; -import React from 'react'; +import React, { useEffect } from 'react'; import { useHistory } from 'react-router-dom'; import { HeaderDesigner } from './Header.Designer'; -import { useField, useFieldSchema } from '@formily/react'; +import { useField } from '@formily/react'; +import { generateNTemplate } from '../../../../locale'; export interface HeaderProps extends NavBarProps { title?: string; @@ -12,10 +13,17 @@ export interface HeaderProps extends NavBarProps { } const InternalHeader = (props: HeaderProps) => { const field = useField(); - const { title = '{{ t("Untitled") }}', showBack = false } = { ...props, ...field?.componentProps }; + const { title = generateNTemplate('Untitled'), showBack = false } = { ...props, ...field?.componentProps }; const Designer = useDesigner(); const compile = useCompile(); + const compiledTitle = compile(title); const history = useHistory(); + + useEffect(() => { + // sync title + document.title = `${compiledTitle} - NocoBase`; + }, [compiledTitle]); + return ( { )} > - {compile(title)} + {compiledTitle} diff --git a/packages/plugins/mobile-client/src/client/core/schema/components/menu/Menu.tsx b/packages/plugins/mobile-client/src/client/core/schema/components/menu/Menu.tsx index e8e60a1ac..8d981ac00 100644 --- a/packages/plugins/mobile-client/src/client/core/schema/components/menu/Menu.tsx +++ b/packages/plugins/mobile-client/src/client/core/schema/components/menu/Menu.tsx @@ -14,6 +14,7 @@ import { useFieldSchema } from '@formily/react'; import { List } from 'antd-mobile'; import { useTranslation } from '../../../../locale'; import { menuItemSchema } from './schema'; +import { PageSchema } from '../../common'; const InternalMenu: React.FC = (props) => { const Designer = useDesigner(); @@ -24,31 +25,7 @@ const InternalMenu: React.FC = (props) => { const onAddMenuItem = (values: any) => { const properties = { - page: { - type: 'void', - 'x-component': 'MPage', - 'x-designer': 'MPage.Designer', - 'x-component-props': {}, - properties: { - header: { - type: 'void', - 'x-component': 'MHeader', - 'x-designer': 'MHeader.Designer', - 'x-component-props': { - title: values.name, - showBack: true, - }, - }, - grid: { - type: 'void', - 'x-component': 'Grid', - 'x-component-props': { - showDivider: false, - }, - 'x-initializer': 'MBlockInitializers', - }, - }, - }, + page: PageSchema, }; return insertBeforeEnd({ diff --git a/packages/plugins/mobile-client/src/client/core/schema/components/page/Page.Designer.tsx b/packages/plugins/mobile-client/src/client/core/schema/components/page/Page.Designer.tsx index dbbfec8b3..5408173bb 100644 --- a/packages/plugins/mobile-client/src/client/core/schema/components/page/Page.Designer.tsx +++ b/packages/plugins/mobile-client/src/client/core/schema/components/page/Page.Designer.tsx @@ -1,6 +1,6 @@ import { GeneralSchemaDesigner, SchemaSettings, useDesignable } from '@nocobase/client'; import React from 'react'; -import { useTranslation } from '../../../../locale'; +import { generateNTemplate, useTranslation } from '../../../../locale'; import { useField, useFieldSchema } from '@formily/react'; import { findGridSchema } from '../../helpers'; import { uid } from '@formily/shared'; @@ -78,7 +78,7 @@ export const PageDesigner = (props) => { properties: { tab1: { type: 'void', - title: '{{t("Untitled")}}', + title: generateNTemplate('Untitled'), 'x-component': 'Tabs.TabPane', 'x-designer': 'Tabs.Designer', 'x-component-props': {}, diff --git a/packages/plugins/mobile-client/src/client/core/schema/components/page/Page.tsx b/packages/plugins/mobile-client/src/client/core/schema/components/page/Page.tsx index db5bcadcb..0361bd459 100644 --- a/packages/plugins/mobile-client/src/client/core/schema/components/page/Page.tsx +++ b/packages/plugins/mobile-client/src/client/core/schema/components/page/Page.tsx @@ -32,16 +32,18 @@ const InternalPage: React.FC = (props) => { let hasGlobalActions = false; if (!tabsSchema) { hasGlobalActions = countGridCol(fieldSchema.properties['grid'], 2) === 1; - } else if (query.has('tab') && tabsSchema.properties[query.get('tab')]) { + } else if (query.has('tab') && tabsSchema.properties?.[query.get('tab')]) { hasGlobalActions = countGridCol(tabsSchema.properties[query.get('tab')]?.properties?.['grid'], 2) === 1; - } else { + } else if (tabsSchema.properties) { const schema = Object.values(tabsSchema.properties).sort((t1, t2) => t1['x-index'] - t2['x-index'])[0]; - history.replace({ - pathname: location.pathname, - search: new URLSearchParams({ - tab: schema.name.toString(), - }).toString(), - }); + if (schema) { + history.replace({ + pathname: location.pathname, + search: new URLSearchParams({ + tab: schema.name.toString(), + }).toString(), + }); + } } const onTabsChange = useCallback( @@ -110,7 +112,7 @@ const InternalPage: React.FC = (props) => { return 'MHeader' === s['x-component']; }} > - + { diff --git a/packages/plugins/mobile-client/src/client/core/schema/components/tab-bar/TabBar.tsx b/packages/plugins/mobile-client/src/client/core/schema/components/tab-bar/TabBar.tsx index 180bd3a09..c0e85b32a 100644 --- a/packages/plugins/mobile-client/src/client/core/schema/components/tab-bar/TabBar.tsx +++ b/packages/plugins/mobile-client/src/client/core/schema/components/tab-bar/TabBar.tsx @@ -2,21 +2,30 @@ import { TabBar } from 'antd-mobile'; import { TabBarItem } from './TabBar.Item'; import React, { useCallback, useContext } from 'react'; import { SchemaOptionsContext, useFieldSchema } from '@formily/react'; -import { DndContext, Icon, SchemaComponent, SchemaInitializer, SortableItem, useDesignable } from '@nocobase/client'; +import { + DndContext, + Icon, + SchemaComponent, + SchemaInitializer, + SortableItem, + useCompile, + useDesignable, +} from '@nocobase/client'; import { useTranslation } from '../../../../locale'; import { css, cx } from '@emotion/css'; import { uid } from '@formily/shared'; import { useHistory, useParams } from 'react-router-dom'; import { tabItemSchema } from './schema'; +import { PageSchema } from '../../common'; export const InternalTabBar: React.FC = (props) => { const fieldSchema = useFieldSchema(); const { designable } = useDesignable(); const { t } = useTranslation(); - const options = useContext(SchemaOptionsContext); - const { insertBeforeEnd, dn } = useDesignable(); + const { insertBeforeEnd } = useDesignable(); const history = useHistory(); const params = useParams<{ name: string }>(); + const compile = useCompile(); const onAddTab = useCallback((values: any) => { return insertBeforeEnd({ @@ -25,25 +34,11 @@ export const InternalTabBar: React.FC = (props) => { 'x-component-props': values, 'x-designer': 'MTabBar.Item.Designer', properties: { - [uid()]: { - type: 'void', - 'x-component': 'MPage', - 'x-designer': 'MPage.Designer', - 'x-component-props': {}, - properties: { - grid: { - type: 'void', - 'x-component': 'Grid', - 'x-initializer': 'MBlockInitializers', - 'x-component-props': { - showDivider: false, - }, - }, - }, - }, + [uid()]: PageSchema, }, }); }, []); + return ( { key={`tab_${schema['x-uid']}`} title={ <> - {cp.title} + {compile(cp.title)} } @@ -88,7 +83,7 @@ export const InternalTabBar: React.FC = (props) => { > ); })} - {designable && Object.keys(fieldSchema.properties).length < 5 ? ( + {designable && (!fieldSchema.properties || Object.keys(fieldSchema.properties).length < 5) ? ( { const { routes, components } = useRouteSwitchContext(); + // redirect to mobile + if (isJSBridge) { + const redirectRoute = routes[0]; + if (redirectRoute.type === 'redirect' && redirectRoute.to === '/admin') { + redirectRoute.to = '/mobile'; + } + } return (