diff --git a/packages/core/client/src/plugin-manager/PinnedPluginListProvider.tsx b/packages/core/client/src/plugin-manager/PinnedPluginListProvider.tsx index 095a5804c3..137458b9ee 100644 --- a/packages/core/client/src/plugin-manager/PinnedPluginListProvider.tsx +++ b/packages/core/client/src/plugin-manager/PinnedPluginListProvider.tsx @@ -88,7 +88,7 @@ const dividerTheme = { }, }; -export const PinnedPluginList = React.memo(() => { +export const PinnedPluginList = React.memo((props: { onClick?: () => void }) => { const { allowAll, snippets } = useACLRoleContext(); const getSnippetsAllow = (aclKey) => { return allowAll || aclKey === '*' || snippets?.includes(aclKey); @@ -98,13 +98,15 @@ export const PinnedPluginList = React.memo(() => { return (
- {Object.keys(ctx.items) - .sort((a, b) => ctx.items[a].order - ctx.items[b].order) - .filter((key) => getSnippetsAllow(ctx.items[key].snippet)) - .map((key) => { - const Action = get(components, ctx.items[key].component); - return Action ? : null; - })} +
+ {Object.keys(ctx.items) + .sort((a, b) => ctx.items[a].order - ctx.items[b].order) + .filter((key) => getSnippetsAllow(ctx.items[key].snippet)) + .map((key) => { + const Action = get(components, ctx.items[key].component); + return Action ? : null; + })} +
diff --git a/packages/core/client/src/route-switch/antd/admin-layout/index.tsx b/packages/core/client/src/route-switch/antd/admin-layout/index.tsx index 73d8bc2f48..244df891d7 100644 --- a/packages/core/client/src/route-switch/antd/admin-layout/index.tsx +++ b/packages/core/client/src/route-switch/antd/admin-layout/index.tsx @@ -12,6 +12,7 @@ import ProLayout, { RouteContext, RouteContextType } from '@ant-design/pro-layou import { HeaderViewProps } from '@ant-design/pro-layout/es/components/Header'; import { css } from '@emotion/css'; import { theme as antdTheme, ConfigProvider, Popover, Tooltip } from 'antd'; +import { createStyles } from 'antd-style'; import React, { createContext, FC, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; import ReactDOM from 'react-dom'; import { Link, Navigate, Outlet, useLocation, useNavigate } from 'react-router-dom'; @@ -52,7 +53,6 @@ import { KeepAlive } from './KeepAlive'; import { NocoBaseDesktopRoute, NocoBaseDesktopRouteType } from './convertRoutesToSchema'; import { MenuSchemaToolbar, ResetThemeTokenAndKeepAlgorithm } from './menuItemSettings'; import { userCenterSettings } from './userCenterSettings'; -import { createGlobalStyle, createStyles } from 'antd-style'; export { KeepAlive, NocoBaseDesktopRouteType }; @@ -75,7 +75,7 @@ const AllAccessDesktopRoutesContext = createContext<{ refresh: () => void; }>({ allAccessRoutes: emptyArray, - refresh: () => {}, + refresh: () => { }, }); AllAccessDesktopRoutesContext.displayName = 'AllAccessDesktopRoutesContext'; @@ -418,9 +418,22 @@ const popoverStyle = css` const MobileActions: FC = (props) => { const { token } = useToken(); + const [open, setOpen] = useState(false); + + // 点击时立即关闭 Popover,避免影响用户操作 + const handleContentClick = useCallback(() => { + setOpen(false); + }, []); return ( - } color={token.colorBgHeader}> + } + color={token.colorBgHeader} + trigger="click" + open={open} + onOpenChange={setOpen} + >
>; }>({ isMobileLayout: false, - setIsMobileLayout: () => {}, + setIsMobileLayout: () => { }, }); const MobileLayoutProvider: FC = (props) => { diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/adaptor-of-desktop/ActionDrawer.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/adaptor-of-desktop/ActionDrawer.tsx index bed4abaaa8..1dfbc525d5 100644 --- a/packages/plugins/@nocobase/plugin-mobile/src/client/adaptor-of-desktop/ActionDrawer.tsx +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/adaptor-of-desktop/ActionDrawer.tsx @@ -20,55 +20,54 @@ import { import { ConfigProvider } from 'antd'; import { Popup } from 'antd-mobile'; import { CloseOutline } from 'antd-mobile-icons'; -import React, { useCallback, useEffect, useMemo } from 'react'; +import React, { FC, ReactNode, useCallback, useEffect, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; import { useMobileActionDrawerStyle } from './ActionDrawer.style'; import { usePopupContainer } from './FilterAction'; import { MIN_Z_INDEX_INCREMENT } from './zIndex'; -export const ActionDrawerUsedInMobile: any = observer((props: { footerNodeName?: string }) => { - const fieldSchema = useFieldSchema(); - const field = useField(); - const { visible, setVisible } = useActionContext(); - const { popupContainerRef, visiblePopup } = usePopupContainer(visible); +export interface MobilePopupProps { + title?: string; + visible: boolean; + minHeight?: number | string; + onClose: () => void; + children: ReactNode; +} + +export const MobilePopup: FC = (props) => { + const { + title, + visible, + onClose: closePopup, + children, + minHeight, + } = props; + const { t } = useTranslation(); + const { popupContainerRef } = usePopupContainer(visible); const { componentCls, hashId } = useMobileActionDrawerStyle(); const parentZIndex = useZIndexContext(); const { theme: globalTheme } = useGlobalTheme(); - // 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 = parentZIndex + MIN_Z_INDEX_INCREMENT; const zIndexStyle = useMemo(() => { return { zIndex: newZIndex, + minHeight, }; - }, [newZIndex]); - - const footerSchema = fieldSchema.reduceProperties((buf, s) => { - if (s['x-component'] === footerNodeName) { - return s; - } - return buf; - }); - - const title = field.title || ''; - - const closePopup = useCallback(() => { - setVisible(false); - }, [setVisible]); + }, [newZIndex, minHeight]); const theme = useMemo(() => { return { ...globalTheme, token: { ...globalTheme.token, - marginBlock: 12, zIndexPopupBase: newZIndex, + paddingPageHorizontal: 8, + paddingPageVertical: 8, + marginBlock: 12, + borderRadiusBlock: 8, + fontSize: 14, }, }; }, [globalTheme, newZIndex]); @@ -78,7 +77,7 @@ export const ActionDrawerUsedInMobile: any = observer((props: { footerNodeName?: popupContainerRef.current} @@ -93,43 +92,90 @@ export const ActionDrawerUsedInMobile: any = observer((props: { footerNodeName?: {title} - +
- {isSpecialSchema ? ( -
- { - return s['x-component'] !== footerNodeName; - }} - /> -
- ) : ( - { - return s['x-component'] !== footerNodeName; - }} - /> - )} - {footerSchema ? ( -
- { - return s['x-component'] === footerNodeName; - }} - /> -
- ) : null} + {children} + ) +} + +export const ActionDrawerUsedInMobile: any = observer((props: { footerNodeName?: string }) => { + const fieldSchema = useFieldSchema(); + const field = useField(); + const { visible, setVisible } = useActionContext(); + const { visiblePopup } = usePopupContainer(visible); + + // 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 footerSchema = fieldSchema.reduceProperties((buf, s) => { + if (s['x-component'] === footerNodeName) { + return s; + } + return buf; + }); + + const title = field.title || ''; + + const closePopup = useCallback(() => { + setVisible(false); + }, [setVisible]); + + const popupContent = isSpecialSchema ? ( +
+ { + return s['x-component'] !== footerNodeName; + }} + /> +
+ ) : ( + { + return s['x-component'] !== footerNodeName; + }} + /> + ); + + const footerContent = footerSchema ? ( +
+ { + return s['x-component'] === footerNodeName; + }} + /> +
+ ) : null; + + return ( + + {popupContent} + {footerContent} + ); }); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/index.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/index.tsx index 6016313b48..0fea40523d 100644 --- a/packages/plugins/@nocobase/plugin-mobile/src/client/index.tsx +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/index.tsx @@ -55,6 +55,7 @@ import { MobileSettingsBlockSchemaSettings } from './mobile-blocks/settings-bloc import pkg from './../../package.json'; import { MobileComponentsProvider } from './MobileComponentsProvider'; +export { MobilePopup } from './adaptor-of-desktop/ActionDrawer'; export * from './desktop-mode'; export * from './mobile'; export * from './mobile-layout'; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/MobilePageContentContainer.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/MobilePageContentContainer.tsx index 1285cf538a..7a35c8b085 100644 --- a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/MobilePageContentContainer.tsx +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/MobilePageContentContainer.tsx @@ -12,7 +12,7 @@ import _ from 'lodash'; import React, { FC, useEffect } from 'react'; import { PageBackgroundColor } from '../../../constants'; -export const MobilePageContentContainer: FC<{ hideTabBar?: boolean }> = ({ children, hideTabBar }) => { +export const MobilePageContentContainer: FC<{ hideTabBar?: boolean; displayPageHeader?: boolean }> = ({ children, hideTabBar, displayPageHeader = true }) => { const [mobileTabBarHeight, setMobileTabBarHeight] = React.useState(0); const [mobilePageHeader, setMobilePageHeader] = React.useState(0); const { token } = useToken(); @@ -29,7 +29,7 @@ export const MobilePageContentContainer: FC<{ hideTabBar?: boolean }> = ({ child }); return ( <> - {mobilePageHeader ?
: null} + {(mobilePageHeader && displayPageHeader) ?
: null}
{ - const { title } = useMobileTitle(); + const { title } = useMobileTitle() || {}; const { displayNavigationBar = true, displayPageTitle = true } = useMobilePage(); const fieldSchema = useFieldSchema(); const { componentCls, hashId } = useStyles(); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/styles.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/styles.ts index 548eb9ebaa..9536c49d93 100644 --- a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/styles.ts +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/header/styles.ts @@ -9,7 +9,6 @@ export const mobilePageHeaderStyle: any = { position: 'absolute', - top: 0, left: 0, right: 0, borderBottom: '1px solid var(--adm-color-border)', diff --git a/packages/plugins/@nocobase/plugin-notification-in-app-message/src/client/components/Inbox.tsx b/packages/plugins/@nocobase/plugin-notification-in-app-message/src/client/components/Inbox.tsx index 1a04a6ac76..0269a22427 100644 --- a/packages/plugins/@nocobase/plugin-notification-in-app-message/src/client/components/Inbox.tsx +++ b/packages/plugins/@nocobase/plugin-notification-in-app-message/src/client/components/Inbox.tsx @@ -16,27 +16,23 @@ * For more information, please rwefer to: https://www.nocobase.com/agreement. */ -import React, { useEffect, useCallback } from 'react'; import { reaction } from '@formily/reactive'; -import { Badge, Button, ConfigProvider, Drawer, Tooltip, notification, theme } from 'antd'; -import { CloseOutlined } from '@ant-design/icons'; -import { createStyles } from 'antd-style'; -import { Icon } from '@nocobase/client'; -import { InboxContent } from './InboxContent'; -import { useLocalTranslation } from '../../locale'; -import { fetchChannels } from '../observables'; import { observer } from '@formily/reactive-react'; -import { useCurrentUserContext } from '@nocobase/client'; +import { Icon, useCurrentUserContext, useMobileLayout } from '@nocobase/client'; +import { MobilePopup } from '@nocobase/plugin-mobile/client'; +import { Badge, Button, ConfigProvider, Drawer, notification, theme, Tooltip } from 'antd'; +import { createStyles } from 'antd-style'; +import React, { FC, useCallback, useEffect, useState } from 'react'; +import { useLocalTranslation } from '../../locale'; +import { Channel } from '../../types'; import { - updateUnreadMsgsCount, - unreadMsgsCountObs, - startMsgSSEStreamWithRetry, - inboxVisible, - userIdObs, - liveSSEObs, + fetchChannels, inboxVisible, liveSSEObs, messageMapObs, - selectedChannelNameObs, + selectedChannelNameObs, startMsgSSEStreamWithRetry, unreadMsgsCountObs, updateUnreadMsgsCount, userIdObs } from '../observables'; +import { InboxContent } from './InboxContent'; +import { MobileChannelPage } from './mobile/ChannelPage'; +import { MobileMessagePage } from './mobile/MessagePage'; const useStyles = createStyles(({ token }) => { return { button: { @@ -46,6 +42,42 @@ const useStyles = createStyles(({ token }) => { }; }); +const InboxPopup: FC<{ title: string; visible: boolean; onClose: () => void }> = (props) => { + const { token } = theme.useToken(); + const { isMobileLayout } = useMobileLayout(); + const [selectedChannel, setSelectedChannel] = useState(null); + + if (isMobileLayout) { + return ( + <> + + + + setSelectedChannel(null)} minHeight={'60vh'}> + + + + ) + } + + return ( + {props.title}
} + open={props.visible} + width={900} + onClose={props.onClose} + styles={{ + header: { + paddingLeft: token.paddingMD, + }, + }} + > + + + ) +} + + const InnerInbox = (props) => { const { t } = useLocalTranslation(); const { styles } = useStyles(); @@ -89,7 +121,6 @@ const InnerInbox = (props) => { document.removeEventListener('visibilitychange', onVisibilityChange); }; }, []); - const DrawerTitle =
{t('Message')}
; useEffect(() => { const dispose = reaction( () => liveSSEObs.value, @@ -138,21 +169,7 @@ const InnerInbox = (props) => { - { - inboxVisible.value = false; - }} - styles={{ - header: { - paddingLeft: token.paddingMD, - }, - }} - > - - + { inboxVisible.value = false; }} /> ); }; diff --git a/packages/plugins/@nocobase/plugin-notification-in-app-message/src/client/components/mobile/ChannelList.tsx b/packages/plugins/@nocobase/plugin-notification-in-app-message/src/client/components/mobile/ChannelList.tsx index 7acaa97665..b55441c018 100644 --- a/packages/plugins/@nocobase/plugin-notification-in-app-message/src/client/components/mobile/ChannelList.tsx +++ b/packages/plugins/@nocobase/plugin-notification-in-app-message/src/client/components/mobile/ChannelList.tsx @@ -7,18 +7,18 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import React, { useRef, useEffect, useState } from 'react'; -import { observer } from '@formily/reactive-react'; -import { reaction } from '@formily/reactive'; -import { List, Badge, InfiniteScroll, ListRef } from 'antd-mobile'; -import { useNavigate } from 'react-router-dom'; -import { dayjs } from '@nocobase/utils/client'; -import InfiniteScrollContent from './InfiniteScrollContent'; -import { channelListObs, channelStatusFilterObs, showChannelLoadingMoreObs, fetchChannels } from '../../observables'; import { Schema } from '@formily/react'; +import { reaction } from '@formily/reactive'; +import { observer } from '@formily/reactive-react'; import { useApp } from '@nocobase/client'; +import { dayjs } from '@nocobase/utils/client'; +import { Badge, InfiniteScroll, List, ListRef } from 'antd-mobile'; +import React, { useEffect, useRef, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { channelListObs, channelStatusFilterObs, fetchChannels, showChannelLoadingMoreObs } from '../../observables'; +import InfiniteScrollContent from './InfiniteScrollContent'; -const InternalChannelList = () => { +const InternalChannelList = (props: { onClickItem?: (item: any) => void }) => { const app = useApp(); const navigate = useNavigate(); const channels = channelListObs.value; @@ -66,7 +66,11 @@ const InternalChannelList = () => { { - navigate(`/page/in-app-message/messages?channel=${item.name}`); + if (props.onClickItem) { + props.onClickItem(item); + } else { + navigate(`/page/in-app-message/messages?channel=${item.name}`); + } }} description={
diff --git a/packages/plugins/@nocobase/plugin-notification-in-app-message/src/client/components/mobile/ChannelPage.tsx b/packages/plugins/@nocobase/plugin-notification-in-app-message/src/client/components/mobile/ChannelPage.tsx index e258b71520..4a34bc9d7b 100644 --- a/packages/plugins/@nocobase/plugin-notification-in-app-message/src/client/components/mobile/ChannelPage.tsx +++ b/packages/plugins/@nocobase/plugin-notification-in-app-message/src/client/components/mobile/ChannelPage.tsx @@ -7,20 +7,20 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import React, { useEffect, useCallback } from 'react'; -import { Tabs } from 'antd-mobile'; import { observer } from '@formily/reactive-react'; -import { useCurrentUserContext, css } from '@nocobase/client'; +import { css, useCurrentUserContext } from '@nocobase/client'; import { + MobilePageContentContainer, MobilePageHeader, MobilePageNavigationBar, MobilePageProvider, - MobilePageContentContainer, } from '@nocobase/plugin-mobile/client'; -import { userIdObs, fetchChannels, ChannelStatus, channelStatusFilterObs } from '../../observables'; -import { ChannelList } from './ChannelList'; +import { Tabs } from 'antd-mobile'; +import React, { useEffect } from 'react'; import { useLocalTranslation } from '../../../locale'; -const MobileMessageBoxInner = () => { +import { ChannelStatus, channelStatusFilterObs, fetchChannels, userIdObs } from '../../observables'; +import { ChannelList } from './ChannelList'; +const MobileMessageBoxInner = (props: { displayNavigationBar?: boolean; onClickItem?: (item: any) => void; }) => { const { t } = useLocalTranslation(); const ctx = useCurrentUserContext(); const currUserId = ctx.data?.data?.id; @@ -31,7 +31,7 @@ const MobileMessageBoxInner = () => { fetchChannels({}); }, []); return ( - + { - + ); diff --git a/packages/plugins/@nocobase/plugin-notification-in-app-message/src/client/components/mobile/MessagePage.tsx b/packages/plugins/@nocobase/plugin-notification-in-app-message/src/client/components/mobile/MessagePage.tsx index d0befaa9d1..9ef319165f 100644 --- a/packages/plugins/@nocobase/plugin-notification-in-app-message/src/client/components/mobile/MessagePage.tsx +++ b/packages/plugins/@nocobase/plugin-notification-in-app-message/src/client/components/mobile/MessagePage.tsx @@ -7,35 +7,34 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import React, { useEffect, useCallback, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { List, Badge, InfiniteScroll, NavBar, DotLoading } from 'antd-mobile'; import { observer } from '@formily/reactive-react'; -import { useCurrentUserContext, css, useApp } from '@nocobase/client'; -import { useSearchParams } from 'react-router-dom'; +import { css, useApp, useCurrentUserContext } from '@nocobase/client'; import { dayjs } from '@nocobase/utils/client'; +import { Badge, InfiniteScroll, List, NavBar } from 'antd-mobile'; +import React, { useCallback, useEffect, useState } from 'react'; +import { useNavigate, useSearchParams } from 'react-router-dom'; +import { Schema } from '@formily/react'; import { - MobilePageHeader, - MobilePageProvider, MobilePageContentContainer, - useMobileTitle, + MobilePageHeader, + MobilePageProvider } from '@nocobase/plugin-mobile/client'; +import { useLocalTranslation } from '../../../locale'; import { - userIdObs, + fetchChannels, + fetchMessages, + inboxVisible, selectedChannelNameObs, selectedChannelObs, selectedMessageListObs, - fetchChannels, - updateMessage, - fetchMessages, showMsgLoadingMoreObs, + updateMessage, + userIdObs, } from '../../observables'; -import { useLocalTranslation } from '../../../locale'; import InfiniteScrollContent from './InfiniteScrollContent'; -import { Schema } from '@formily/react'; -const MobileMessagePageInner = () => { +const MobileMessagePageInner = (props: { displayPageHeader?: boolean }) => { const app = useApp(); const { t } = useLocalTranslation(); const navigate = useNavigate(); @@ -58,11 +57,13 @@ const MobileMessagePageInner = () => { }, [currUserId]); const messages = selectedMessageListObs.value; const viewMessageDetail = (message) => { - const url = message.options?.mobileUrl; + const url = message.options?.mobileUrl || message.options?.url; if (url) { if (url.startsWith('/m/')) navigate(url.substring(2)); - else if (url.startsWith('/')) navigate(url); - else { + else if (url.startsWith('/')) { + navigate(url); + inboxVisible.value = false; + } else { window.location.href = url; } } @@ -101,13 +102,13 @@ const MobileMessagePageInner = () => { const title = Schema.compile(selectedChannelObs.value?.title, { t: app.i18n.t }) || t('Message'); return ( - + navigate('/page/in-app-message')}> {title} - +