diff --git a/client/shared/index.tsx b/client/shared/index.tsx index a123ac91..1e223028 100644 --- a/client/shared/index.tsx +++ b/client/shared/index.tsx @@ -140,6 +140,7 @@ export type { GroupInvite, GroupMember, } from './model/group'; +export type { InboxItem } from './model/inbox'; export { sendMessage, recallMessage, @@ -195,7 +196,7 @@ export { useHasGroupPermission, } from './redux/hooks/useGroupPermission'; export { useUserInfo, useUserId } from './redux/hooks/useUserInfo'; -export { useInboxList } from './redux/hooks/useInboxList'; +export { useInboxList, useInboxItem } from './redux/hooks/useInbox'; export { useUnread } from './redux/hooks/useUnread'; export { userActions, diff --git a/client/shared/model/message.ts b/client/shared/model/message.ts index 81bb50a1..55716821 100644 --- a/client/shared/model/message.ts +++ b/client/shared/model/message.ts @@ -108,6 +108,23 @@ export async function fetchConverseLastMessages( return data; } +/** + * @param converseId 会话ID + * @param messageId 消息ID + * @returns 消息附近的信息 + */ +export async function fetchNearbyMessage( + converseId: string, + messageId: string +): Promise { + const { data } = await request.post('/api/chat/message/fetchNearbyMessage', { + converseId, + messageId, + }); + + return data; +} + /** * 增加表情行为 */ diff --git a/client/shared/redux/hooks/useInboxList.ts b/client/shared/redux/hooks/useInbox.ts similarity index 53% rename from client/shared/redux/hooks/useInboxList.ts rename to client/shared/redux/hooks/useInbox.ts index f581dab8..3ed96db0 100644 --- a/client/shared/redux/hooks/useInboxList.ts +++ b/client/shared/redux/hooks/useInbox.ts @@ -7,3 +7,12 @@ import { useAppSelector } from './useAppSelector'; export function useInboxList(): InboxItem[] { return useAppSelector((state) => state.chat.inbox ?? []); } + +/** + * 返回收件箱某一项的值 + */ +export function useInboxItem(inboxItemId: string): InboxItem | null { + const list = useInboxList(); + + return list.find((item) => item._id === inboxItemId) ?? null; +} diff --git a/client/web/src/components/Problem.tsx b/client/web/src/components/Problem.tsx index 182f0cf8..3e8f999a 100644 --- a/client/web/src/components/Problem.tsx +++ b/client/web/src/components/Problem.tsx @@ -1,5 +1,6 @@ import React from 'react'; import problemSvg from '@assets/images/problem.svg'; +import { t } from 'tailchat-shared'; interface ProblemProps { text?: React.ReactNode; @@ -13,7 +14,7 @@ export const Problem: React.FC = React.memo((props) => {
-
{props.text}
+
{props.text ?? t('出现了一些问题')}
); }); diff --git a/client/web/src/routes/Main/Content/Inbox/Content/Message.tsx b/client/web/src/routes/Main/Content/Inbox/Content/Message.tsx new file mode 100644 index 00000000..23742088 --- /dev/null +++ b/client/web/src/routes/Main/Content/Inbox/Content/Message.tsx @@ -0,0 +1,84 @@ +import { NormalMessageList } from '@/components/ChatBox/ChatMessageList/NormalList'; +import { LoadingSpinner } from '@/components/LoadingSpinner'; +import { Problem } from '@/components/Problem'; +import React from 'react'; +import { useNavigate } from 'react-router'; +import { + InboxItem, + model, + showErrorToasts, + t, + useAsync, +} from 'tailchat-shared'; + +interface Props { + info: InboxItem; +} +export const InboxMessageContent: React.FC = React.memo((props) => { + const info = props.info; + const navigate = useNavigate(); + + const message = info.message; + if (!message) { + return ; + } + const { groupId, converseId, messageId } = message; + + return ( +
+ + +
+
{ + if (groupId) { + // 跳转到群组 + navigate(`/main/group/${groupId}/${converseId}`); + } else { + navigate(`/main/personal/converse/${converseId}`); + } + }} + > + {t('跳转到会话')} +
+
+
+ ); +}); +InboxMessageContent.displayName = 'InboxMessageContent'; + +export const NearbyMessages: React.FC<{ + converseId: string; + messageId: string; +}> = React.memo((props) => { + const { value = [], loading } = useAsync(async () => { + try { + const list = await model.message.fetchNearbyMessage( + props.converseId, + props.messageId + ); + + return list; + } catch (err) { + showErrorToasts(err); + console.error(err); + } + }, [props.converseId, props.messageId]); + + if (loading) { + return ; + } + + return ( +
+ {}} + /> +
+ ); +}); +NearbyMessages.displayName = 'NearbyMessages'; diff --git a/client/web/src/routes/Main/Content/Inbox/Content/index.tsx b/client/web/src/routes/Main/Content/Inbox/Content/index.tsx new file mode 100644 index 00000000..9cffc0c2 --- /dev/null +++ b/client/web/src/routes/Main/Content/Inbox/Content/index.tsx @@ -0,0 +1,21 @@ +import { NotFound } from '@/components/NotFound'; +import React from 'react'; +import { useParams } from 'react-router'; +import { t, useInboxItem } from 'tailchat-shared'; +import { InboxMessageContent } from './Message'; + +export const InboxContent: React.FC = React.memo((props) => { + const { inboxItemId } = useParams(); + const inboxItem = useInboxItem(inboxItemId ?? ''); + + if (!inboxItem) { + return ; + } + + if (inboxItem.type === 'message') { + return ; + } + + return ; +}); +InboxContent.displayName = 'InboxContent'; diff --git a/client/web/src/routes/Main/Content/Inbox/Sidebar.tsx b/client/web/src/routes/Main/Content/Inbox/Sidebar.tsx index 230ea2b3..356002ff 100644 --- a/client/web/src/routes/Main/Content/Inbox/Sidebar.tsx +++ b/client/web/src/routes/Main/Content/Inbox/Sidebar.tsx @@ -6,16 +6,13 @@ import _orderBy from 'lodash/orderBy'; import { GroupName } from '@/components/GroupName'; import { ConverseName } from '@/components/ConverseName'; import { getMessageRender } from '@/plugin/common'; - -interface InboxSidebarProps { - selectedItem: string; - onSelect: (itemId: string) => void; -} +import { useLocation } from 'react-router'; +import { Link } from 'react-router-dom'; /** * 收件箱侧边栏组件 */ -export const InboxSidebar: React.FC = React.memo((props) => { +export const InboxSidebar: React.FC = React.memo(() => { const inbox = useInboxList(); const list = useMemo(() => _orderBy(inbox, 'createdAt', 'desc'), [inbox]); @@ -41,8 +38,7 @@ export const InboxSidebar: React.FC = React.memo((props) => { title={title} desc={getMessageRender(message.messageSnippet ?? '')} source={'Tailchat'} - selected={props.selectedItem === item._id} - onSelect={() => props.onSelect(item._id)} + to={`/main/inbox/${item._id}`} /> ); } @@ -57,30 +53,32 @@ const InboxSidebarItem: React.FC<{ title: React.ReactNode; desc: React.ReactNode; source: string; - selected: boolean; - onSelect: () => void; + to: string; }> = React.memo((props) => { + const location = useLocation(); + const isActive = location.pathname.startsWith(props.to); + return ( -
-
- {props.title ||  } -
-
- {props.desc} -
-
- {t('来自')}: {props.source} + +
+
+ {props.title ||  } +
+
+ {props.desc} +
+
+ {t('来自')}: {props.source} +
-
+ ); }); InboxSidebarItem.displayName = 'InboxSidebarItem'; diff --git a/client/web/src/routes/Main/Content/Inbox/index.tsx b/client/web/src/routes/Main/Content/Inbox/index.tsx index 1ef14aa5..577b76eb 100644 --- a/client/web/src/routes/Main/Content/Inbox/index.tsx +++ b/client/web/src/routes/Main/Content/Inbox/index.tsx @@ -1,17 +1,15 @@ -import React, { useState } from 'react'; +import React from 'react'; +import { Route, Routes } from 'react-router'; import { PageContent } from '../PageContent'; +import { InboxContent } from './Content'; import { InboxSidebar } from './Sidebar'; export const Inbox: React.FC = React.memo(() => { - const [selectedItem, setSelectedItem] = useState(''); return ( - - } - > -
Inbox {selectedItem}
+ }> + + } /> + ); });