diff --git a/packages/core/database/src/options-parser.ts b/packages/core/database/src/options-parser.ts index 806d69074f..d7acc8e103 100644 --- a/packages/core/database/src/options-parser.ts +++ b/packages/core/database/src/options-parser.ts @@ -182,7 +182,12 @@ export class OptionsParser { sortField.push(direction); if (this.database.isMySQLCompatibleDialect()) { - orderParams.push([Sequelize.fn('ISNULL', Sequelize.col(`${this.model.name}.${sortField[0]}`))]); + const fieldName = sortField[0]; + + // @ts-ignore + if (this.model.fieldRawAttributesMap[fieldName]) { + orderParams.push([Sequelize.fn('ISNULL', Sequelize.col(`${this.model.name}.${sortField[0]}`))]); + } } orderParams.push(sortField); } diff --git a/packages/plugins/@nocobase/plugin-mobile-client/package.json b/packages/plugins/@nocobase/plugin-mobile-client/package.json index 4a3dedbd6a..36d503bbdb 100644 --- a/packages/plugins/@nocobase/plugin-mobile-client/package.json +++ b/packages/plugins/@nocobase/plugin-mobile-client/package.json @@ -18,7 +18,7 @@ "@types/react-dom": "17.x", "ahooks": "3.x", "antd": "5.x", - "antd-mobile": "^5.29.1", + "antd-mobile": "^5.38", "antd-style": "3.x", "classnames": "2.x", "react": "18.x", diff --git a/packages/plugins/@nocobase/plugin-mobile-client/src/client/core/schema/components/tab-bar/TabBar.Item.tsx b/packages/plugins/@nocobase/plugin-mobile-client/src/client/core/schema/components/tab-bar/TabBar.Item.tsx index 250d2a3c76..53dceb137a 100644 --- a/packages/plugins/@nocobase/plugin-mobile-client/src/client/core/schema/components/tab-bar/TabBar.Item.tsx +++ b/packages/plugins/@nocobase/plugin-mobile-client/src/client/core/schema/components/tab-bar/TabBar.Item.tsx @@ -37,6 +37,7 @@ const InternalItem: React.FC = () => { height: 100%; top: 0; left: 0; + padding-top: 5px; `, )} > diff --git a/packages/plugins/@nocobase/plugin-mobile/package.json b/packages/plugins/@nocobase/plugin-mobile/package.json index 33600c2a9a..5fa6d77e07 100644 --- a/packages/plugins/@nocobase/plugin-mobile/package.json +++ b/packages/plugins/@nocobase/plugin-mobile/package.json @@ -22,7 +22,7 @@ "@formily/shared": "2.x", "@types/react": "17.x", "@types/react-dom": "17.x", - "antd-mobile": "^5.36.1", + "antd-mobile": "^5.38", "re-resizable": "6.6.0", "react-device-detect": "2.2.3", "@emotion/css": "11.x", diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/MobileTabBar.Item.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/MobileTabBar.Item.tsx index ff95766c5a..39f331f41d 100644 --- a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/MobileTabBar.Item.tsx +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/MobileTabBar.Item.tsx @@ -47,7 +47,7 @@ export const MobileTabBarItem: FC = (props) => { })} style={{ lineHeight: 1 }} > - + {icon} { schemaUid: string; + url?: string; } export const MobileTabBarPage: FC = (props) => { const { schemaUid, ...rests } = props; const navigate = useNavigate(); const location = useLocation(); - const url = useMemo(() => `/page/${schemaUid}`, [schemaUid]); + const url = useMemo(() => { + if (schemaUid) return `/page/${schemaUid}`; + else if (rests.url) return `${rests.url}`; + else return '/'; + }, [schemaUid, rests.url]); const handleClick = useCallback(() => { navigate(url); }, [url, navigate]); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/index.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/index.ts index 110d754a9a..c50af96fb6 100644 --- a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/index.ts +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/content/index.ts @@ -8,5 +8,6 @@ */ export * from './MobilePageContent'; +export * from './MobilePageContentContainer'; export * from './initializer'; export * from './schema'; diff --git a/packages/plugins/@nocobase/plugin-notification-in-app-message/package.json b/packages/plugins/@nocobase/plugin-notification-in-app-message/package.json index 028aa80db6..ad68c036c2 100644 --- a/packages/plugins/@nocobase/plugin-notification-in-app-message/package.json +++ b/packages/plugins/@nocobase/plugin-notification-in-app-message/package.json @@ -12,6 +12,9 @@ "dependencies": { "immer": "^10.1.1" }, + "devDependencies": { + "antd-mobile": "^5.38" + }, "peerDependencies": { "@formily/reactive": "^2", "@formily/reactive-react": "^2", @@ -20,6 +23,5 @@ "@nocobase/server": "1.x", "@nocobase/test": "1.x", "react-router-dom": "^6.x" - } } diff --git a/packages/plugins/@nocobase/plugin-notification-in-app-message/src/client/components/ContentConfigForm.tsx b/packages/plugins/@nocobase/plugin-notification-in-app-message/src/client/components/ContentConfigForm.tsx index 99c0d928a0..1e83293810 100644 --- a/packages/plugins/@nocobase/plugin-notification-in-app-message/src/client/components/ContentConfigForm.tsx +++ b/packages/plugins/@nocobase/plugin-notification-in-app-message/src/client/components/ContentConfigForm.tsx @@ -51,7 +51,7 @@ export const ContentConfigForm = ({ variableOptions }) => { url: { type: 'string', required: false, - title: `{{t("Detail URL")}}`, + title: `{{t("PC detail URL")}}`, 'x-decorator': 'FormItem', 'x-component': 'Variable.TextArea', 'x-component-props': { @@ -62,6 +62,20 @@ export const ContentConfigForm = ({ variableOptions }) => { 'Support two types of links: internal links and external links. If using an internal link, the link starts with"/", for example, "/admin". If using an external link, the link starts with "http", for example, "https://example.com".', ), }, + mobileUrl: { + type: 'string', + required: false, + title: `{{t("Mobile detail URL")}}`, + 'x-decorator': 'FormItem', + 'x-component': 'Variable.TextArea', + 'x-component-props': { + scope: variableOptions, + useTypedConstant: ['string'], + }, + description: tval( + "Support two types of links: internal links and external links. If using an internal link, the link starts with '/', for example, '/m'. If using an external link, the link starts with 'http', for example, 'https://example.com'.", + ), + }, }, }, }, diff --git a/packages/plugins/@nocobase/plugin-notification-in-app-message/src/client/components/FilterTab.tsx b/packages/plugins/@nocobase/plugin-notification-in-app-message/src/client/components/FilterTab.tsx new file mode 100644 index 0000000000..f8fbdcb935 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-notification-in-app-message/src/client/components/FilterTab.tsx @@ -0,0 +1,46 @@ +/** + * 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 React from 'react'; +import { Tabs, ConfigProvider } from 'antd'; +import { observer } from '@formily/reactive-react'; +import { fetchChannels, channelStatusFilterObs, ChannelStatus } from '../observables'; +import { useLocalTranslation } from '../../locale'; + +const _FilterTab = () => { + const { t } = useLocalTranslation(); + interface TabItem { + label: string; + key: ChannelStatus; + } + const items: Array = [ + { label: t('All'), key: 'all' }, + { label: t('Unread'), key: 'unread' }, + { label: t('Read'), key: 'read' }, + ]; + return ( + + { + channelStatusFilterObs.value = key; + fetchChannels({}); + }} + /> + + ); +}; + +const FilterTab = observer(_FilterTab); +export default FilterTab; 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 7d8004dc90..a5370d1064 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 @@ -17,7 +17,8 @@ */ import React, { useEffect, useCallback } from 'react'; -import { Badge, Button, ConfigProvider, Drawer, Tooltip } from 'antd'; +import { reaction } from '@formily/reactive'; +import { Badge, Button, ConfigProvider, Drawer, Tooltip, notification } from 'antd'; import { CloseOutlined } from '@ant-design/icons'; import { createStyles } from 'antd-style'; import { Icon } from '@nocobase/client'; @@ -32,6 +33,9 @@ import { startMsgSSEStreamWithRetry, inboxVisible, userIdObs, + liveSSEObs, + messageMapObs, + selectedChannelNameObs, } from '../observables'; const useStyles = createStyles(({ token }) => { return { @@ -61,7 +65,28 @@ const InnerInbox = (props) => { }, []); useEffect(() => { - startMsgSSEStreamWithRetry(); + const disposes: Array<() => void> = []; + disposes.push(startMsgSSEStreamWithRetry()); + const disposeAll = () => { + while (disposes.length > 0) { + const dispose = disposes.pop(); + dispose && dispose(); + } + }; + + const onVisibilityChange = () => { + if (document.visibilityState === 'visible') { + disposes.push(startMsgSSEStreamWithRetry()); + } else { + disposeAll(); + } + }; + + document.addEventListener('visibilitychange', onVisibilityChange); + return () => { + disposeAll(); + document.removeEventListener('visibilitychange', onVisibilityChange); + }; }, []); const DrawerTitle =
{t('Message')}
; const CloseIcon = ( @@ -69,6 +94,41 @@ const InnerInbox = (props) => { ); + useEffect(() => { + const dispose = reaction( + () => liveSSEObs.value, + (sseData) => { + if (!sseData) return; + + if (['message:created', 'message:updated'].includes(sseData.type)) { + const { data } = sseData; + messageMapObs.value[data.id] = data; + if (sseData.type === 'message:created') { + notification.info({ + message: ( +
+ {data.title} +
+ ), + description: data.content.slice(0, 100) + (data.content.length > 100 ? '...' : ''), + onClick: () => { + inboxVisible.value = true; + selectedChannelNameObs.value = data.channelName; + notification.destroy(); + }, + }); + } + } + }, + ); + return dispose; + }, []); return ( {