diff --git a/package.json b/package.json index 6f176e4..7871c3e 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "antd": "^5.9.2", "crypto-js": "^4.1.1", "intro.js": "^7.2.0", + "rc-virtual-list": "^3.11.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.16.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b9c1bf1..ba0b591 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ dependencies: intro.js: specifier: ^7.2.0 version: 7.2.0 + rc-virtual-list: + specifier: ^3.11.2 + version: 3.11.2(react-dom@18.2.0)(react@18.2.0) react: specifier: ^18.2.0 version: 18.2.0 diff --git a/src/component/chatRoom/index.tsx b/src/component/chatRoom/index.tsx new file mode 100644 index 0000000..2006b88 --- /dev/null +++ b/src/component/chatRoom/index.tsx @@ -0,0 +1,97 @@ +import { useChatMessageStore } from '@/pages/playRoom/store/chatMessage.js'; +import { customScrollBar } from '@/styles/scrollbar.js'; +import { emitSocket } from '@/utils/api.js'; +import { infoContext } from '@/utils/infoContext.js'; +import { SendOutlined } from '@ant-design/icons'; +import { css } from '@emotion/react'; +import { Button, Input, Tooltip } from 'antd'; +import React, { useContext, useEffect, useRef, useState } from 'react'; +import PlayerAvatar from '../playerAvatar/index.js'; + +const chatContainerStyle = css` + width: 50vw; + margin: 0 auto; + ${customScrollBar()} +`; + +const scrollMessageBox = css` + width: 100%; + max-height: 30vh; + min-height: 200px; + overflow-y: auto; + overflow-x: hidden; + margin: 0 auto; + background-color: rgb(246,248,250); + ${customScrollBar()} +`; + +const chatBubbleBoxStyle = (isUser = false) => css` + width: 100%; + color: #fff; + padding: 4px 8px; + border-radius: 10px; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: ${isUser ? 'flex-end;' : 'flex-start;'} +`; + +const chatBubbleStyle = (isUser = false) => css` + background-color: ${isUser ? '#69c0ff' : '#87e8de'}; + color: #fff; + padding: 4px 8px; + border-radius: 10px; + margin: 5px; + max-width: 70%; +`; + +export default function ChatRoom (): React.JSX.Element { + const listRef = useRef(null); + const [message, setMessage] = useState(''); + const { chatMessage: { info: messages } } = useChatMessageStore(); + const { player } = useContext(infoContext); + + useEffect(() => { + listRef.current?.scrollTo({ top: listRef.current.scrollHeight }); + }, [messages]); + + const handleSendMessage = () => { + if (message.trim() === '') return; + emitSocket('sendMessage', { player, msg: message }); + setMessage(''); + }; + + return ( +
+
+ { + messages.map(({ player: curPlayer, msg, key }) => { + const isMe = curPlayer.name === player?.name; + + return
+ {!isMe && } + + {msg} + + {isMe && } +
; + }) + } +
+ setMessage(e.target.value)} + onPressEnter={handleSendMessage} + suffix={ +
+ ); +} \ No newline at end of file diff --git a/src/component/playerAvatar/index.tsx b/src/component/playerAvatar/index.tsx new file mode 100644 index 0000000..33dccd4 --- /dev/null +++ b/src/component/playerAvatar/index.tsx @@ -0,0 +1,21 @@ +import { PlayerInfoType } from '@/pages/playRoom/type.js'; +import { css } from '@emotion/react'; +import { Avatar, AvatarProps } from 'antd'; + +const ColorList = ['#f56a00', '#7265e6', '#ffbf00', '#00a2ae']; + +export default function PlayerAvatar ({ player, importCss, ...restProps } : { player: PlayerInfoType; importCss?: string } & AvatarProps) { + return <> + + {player.name[0].toLocaleUpperCase()} + + ; +} \ No newline at end of file diff --git a/src/pages/playRoom/component/customFloatButton/index.tsx b/src/pages/playRoom/component/customFloatButton/index.tsx index 4a742e3..90239f4 100644 --- a/src/pages/playRoom/component/customFloatButton/index.tsx +++ b/src/pages/playRoom/component/customFloatButton/index.tsx @@ -6,6 +6,7 @@ import introJs from 'intro.js'; import 'intro.js/introjs.css'; import { getFloatCss } from '../../styles/playRoom.js'; import AudioButton from '../audioButton/index.js'; +import TalkRommButton from '../talkRoomButton/index.js'; const Introduct = introJs as unknown as () => { start: () => void }; @@ -41,5 +42,6 @@ export function CustomFloatButton () { /> + ; } \ No newline at end of file diff --git a/src/pages/playRoom/component/playerBox/index.tsx b/src/pages/playRoom/component/playerBox/index.tsx index fc80d1f..7d92fa9 100644 --- a/src/pages/playRoom/component/playerBox/index.tsx +++ b/src/pages/playRoom/component/playerBox/index.tsx @@ -1,12 +1,10 @@ +import PlayerAvatar from '@/component/playerAvatar/index.js'; import { boxHoverShadow } from '@/styles/animation.js'; -import { css } from '@emotion/react'; -import { Avatar, Tooltip } from 'antd'; +import { Tooltip } from 'antd'; import { usePlayerAudioInfo } from '../../store/playerInfo.js'; import { PlayerInfoType } from '../../type.js'; import { ripplePlayerBoxCss, UsersBoxFlexCss } from './style.js'; -const ColorList = ['#f56a00', '#7265e6', '#ffbf00', '#00a2ae']; - export default function PlayerBox ({ player } : { player: PlayerInfoType }) { const { playingPlayer } = usePlayerAudioInfo((state) => state); const isBarking = playingPlayer[0].has(player.name); @@ -28,14 +26,10 @@ export default function PlayerBox ({ player } : { player: PlayerInfoType }) { return <>
- - {player.name[0].toLocaleUpperCase()} - + `} size={size}/>
; diff --git a/src/pages/playRoom/component/talkRoomButton/index.tsx b/src/pages/playRoom/component/talkRoomButton/index.tsx new file mode 100644 index 0000000..802ab89 --- /dev/null +++ b/src/pages/playRoom/component/talkRoomButton/index.tsx @@ -0,0 +1,37 @@ +import { Badge, Button, Popconfirm } from 'antd'; +import { CommentOutlined } from '@ant-design/icons'; +import 'intro.js/introjs.css'; +import { getFloatCss } from '../../styles/playRoom.js'; +import ChatRoom from '@/component/chatRoom/index.js'; +import { useChatMessageStore } from '../../store/chatMessage.js'; +import { useState } from 'react'; + +export default function TalkRommButton () { + const { chatMessage: { unReadNum }, readMsg } = useChatMessageStore(); + const [open, setOpen] = useState(false); + + return <> + } + okText="关闭" + showCancel={false} + placement='bottomLeft' + icon={<>} + open={open} + onOpenChange={() => { + setOpen(pre => !pre); + readMsg(); + }} + > + +