feat(frontend): add api client and socket client wrappers with runtime config

This commit is contained in:
root
2026-05-12 13:46:19 +00:00
parent b3291b5874
commit 6219fbbcfe
5 changed files with 227 additions and 2 deletions

View File

@@ -1,8 +1,86 @@
import { useEffect, useMemo, useState } from 'react'
import { getApiClient } from '../lib/api-client'
import { disconnectSocket, getSocketClient } from '../lib/socket-client'
type MessageItem = {
messageId: string
seq: number
content: string
isDeleted?: boolean
}
export default function ChatPage() {
const [channelId, setChannelId] = useState('')
const [content, setContent] = useState('')
const [messages, setMessages] = useState<MessageItem[]>([])
const [socketState, setSocketState] = useState<'offline' | 'online'>('offline')
const socket = useMemo(() => getSocketClient('frontend-user'), [])
useEffect(() => {
socket.on('connect', () => setSocketState('online'))
socket.on('disconnect', () => setSocketState('offline'))
socket.on('message.created', (m: MessageItem) => setMessages((prev) => [...prev, m]))
socket.on('message.updated', (m: MessageItem) =>
setMessages((prev) => prev.map((x) => (x.messageId === m.messageId ? m : x))),
)
socket.on('message.deleted', (m: MessageItem) =>
setMessages((prev) =>
prev.map((x) => (x.messageId === m.messageId ? { ...x, isDeleted: true, content: '[deleted]' } : x)),
),
)
socket.connect()
return () => {
socket.removeAllListeners()
disconnectSocket()
}
}, [socket])
async function pullMessages() {
if (!channelId) return
const res = await getApiClient().get(`/channels/${channelId}/messages`, {
params: { seq_from: 1, seq_to: 999999, limit: 100 },
})
setMessages(res.data.items ?? [])
}
async function sendMessage() {
if (!channelId || !content.trim()) return
await getApiClient().post(`/channels/${channelId}/messages`, {
content,
authorUserId: 'frontend-user',
})
setContent('')
}
useEffect(() => {
if (!channelId || !socket.connected) return
socket.emit('join_channel', { channelId })
return () => {
socket.emit('leave_channel', { channelId })
}
}, [channelId, socket, socketState])
return (
<section>
<h2></h2>
<p>///</p>
<p>Socket: {socketState}</p>
<div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
<input value={channelId} onChange={(e) => setChannelId(e.target.value)} placeholder="Channel ID" />
<button onClick={pullMessages}></button>
</div>
<div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
<input value={content} onChange={(e) => setContent(e.target.value)} placeholder="输入消息" />
<button onClick={sendMessage}></button>
</div>
<ul>
{messages.map((m) => (
<li key={m.messageId}>
#{m.seq} {m.content}
</li>
))}
</ul>
</section>
)
}