优化工作台输入与表情,并补充访客 IP 地区

- 输入框改为单层大尺寸 textarea,修复双层边框
- 新增表情选择器(工作台/Widget),插入 Unicode 表情
- 会话记录访客 IP/地区/UA,工作台顶栏与侧栏展示
- 去掉表情面板无用横向滚动条
This commit is contained in:
yml2213
2026-07-15 12:13:59 +08:00
parent 318ba8a563
commit 85b407fddf
8 changed files with 441 additions and 36 deletions
+22 -4
View File
@@ -1,8 +1,9 @@
import { useState, useEffect, useRef, useCallback, useMemo } from 'react'
import {
CloseOutlined, MessageOutlined, SmileOutlined, SendOutlined,
CloseOutlined, MessageOutlined, SendOutlined,
StarFilled, MinusOutlined, PictureOutlined, CustomerServiceOutlined,
} from '@ant-design/icons'
import EmojiPicker, { insertAtCursor } from '@/components/common/EmojiPicker'
interface Message {
id: number
@@ -68,9 +69,22 @@ const VisitorChat = ({
const typingTimerRef = useRef<number | null>(null)
const chatEndRef = useRef<HTMLDivElement>(null)
const fileInputRef = useRef<HTMLInputElement>(null)
const textInputRef = useRef<HTMLInputElement>(null)
const socketRef = useRef<WebSocket | null>(null)
const lastTypingAt = useRef(0)
const insertEmoji = (emoji: string) => {
const { next, cursor } = insertAtCursor(textInputRef.current, input, emoji)
setInput(next)
if (agentsOnline) emitTyping()
requestAnimationFrame(() => {
const el = textInputRef.current
if (!el) return
el.focus()
el.setSelectionRange(cursor, cursor)
})
}
const loadMessages = useCallback(async (sid?: number, token?: string) => {
const s = sid || sessionId
const visitorCredential = token || visitorToken || localStorage.getItem(tokenKey) || ''
@@ -536,9 +550,12 @@ const VisitorChat = ({
>
<PictureOutlined className="text-base" />
</button>
<button type="button" className="w-8 h-8 rounded-md border-0 bg-transparent cursor-pointer flex items-center justify-center text-neutral-400 hover:bg-neutral-50" aria-label="表情">
<SmileOutlined className="text-base" />
</button>
<EmojiPicker
onSelect={insertEmoji}
disabled={sessionEnded || !sessionId || sending}
className="w-8 h-8 rounded-md border-0 bg-transparent cursor-pointer flex items-center justify-center text-neutral-400 hover:bg-neutral-50 disabled:opacity-40"
placement="topLeft"
/>
<input
ref={fileInputRef}
type="file"
@@ -551,6 +568,7 @@ const VisitorChat = ({
<div className="flex items-center gap-2">
<input
ref={textInputRef}
className="flex-1 min-w-0 h-10 px-3 rounded-xl border border-neutral-200 bg-neutral-50 text-[13px] text-neutral-800 outline-none focus:border-[#2563eb]"
placeholder={
sessionEnded