feat(聊天窗口): 重构消息输入组件并添加微信风格样式
将消息输入功能从ChatWindow组件中拆分为独立的MessageEnter组件 添加微信风格的样式和交互效果 新增消息处理逻辑和素材选择功能 重命名getMessages为getChatMessages以保持命名一致性
This commit is contained in:
@@ -23,7 +23,7 @@ export function clearUnreadCount(params) {
|
||||
return request("/api/WechatFriend/clearUnreadCount", params, "PUT");
|
||||
}
|
||||
//获取聊天记录-2 获取列表
|
||||
export function getMessages(params: {
|
||||
export function getChatMessages(params: {
|
||||
wechatAccountId: number;
|
||||
wechatFriendId?: number;
|
||||
wechatChatroomId?: number;
|
||||
@@ -73,19 +73,6 @@ export const getControlTerminalList = params => {
|
||||
return request("/api/wechataccount", params, "GET");
|
||||
};
|
||||
|
||||
// 搜索联系人
|
||||
export const getChatMessage = (params: {
|
||||
wechatAccountId: number;
|
||||
wechatFriendId: number;
|
||||
From: number;
|
||||
To: number;
|
||||
Count: number;
|
||||
olderData: boolean;
|
||||
keyword: string;
|
||||
}) => {
|
||||
return request("/api/FriendMessage/SearchMessage", params, "GET");
|
||||
};
|
||||
|
||||
// 获取聊天历史
|
||||
export const getChatHistory = (
|
||||
chatId: string,
|
||||
|
||||
@@ -128,127 +128,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
.chatFooter {
|
||||
background: #fff;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
padding: 0;
|
||||
height: auto;
|
||||
min-height: auto;
|
||||
flex-shrink: 0;
|
||||
|
||||
.inputContainer {
|
||||
.inputToolbar {
|
||||
display: flex;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
background: #fafafa;
|
||||
justify-content: space-between;
|
||||
|
||||
.leftTool {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.rightTool {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.toolbarButton {
|
||||
color: #666;
|
||||
border: none;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 18px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: #1890ff;
|
||||
background: #e6f7ff;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: #bae7ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.inputArea {
|
||||
display: flex;
|
||||
padding: 12px 16px;
|
||||
gap: 8px;
|
||||
align-items: flex-end;
|
||||
background: #fff;
|
||||
|
||||
.messageInput {
|
||||
flex: 1;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
resize: none;
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
min-height: 36px;
|
||||
max-height: 120px;
|
||||
background: #fff;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:focus {
|
||||
border-color: #1890ff;
|
||||
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: #bfbfbf;
|
||||
}
|
||||
}
|
||||
|
||||
.sendButton {
|
||||
border-radius: 4px;
|
||||
height: 36px;
|
||||
padding: 0 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
background: #1890ff;
|
||||
border: 1px solid #1890ff;
|
||||
color: #fff;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: #40a9ff;
|
||||
border-color: #40a9ff;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: #096dd9;
|
||||
border-color: #096dd9;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: #f5f5f5;
|
||||
border-color: #d9d9d9;
|
||||
color: #bfbfbf;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.inputHint {
|
||||
padding: 4px 16px 8px;
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
background: #fff;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 右侧个人资料卡片
|
||||
.profileSider {
|
||||
@@ -748,18 +629,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.chatFooter {
|
||||
padding: 12px;
|
||||
|
||||
.inputContainer {
|
||||
.inputArea {
|
||||
.sendButton {
|
||||
height: 28px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.messageItem {
|
||||
.messageContent {
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
// MessageEnter 组件样式 - 微信风格
|
||||
.chatFooter {
|
||||
background: #f7f7f7;
|
||||
border-top: 1px solid #e1e1e1;
|
||||
padding: 0;
|
||||
height: auto;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.inputContainer {
|
||||
padding: 8px 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.inputToolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 4px 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.leftTool {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.toolbarButton {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
transition: all 0.15s;
|
||||
border: none;
|
||||
background: transparent;
|
||||
|
||||
&:hover {
|
||||
background: #e6e6e6;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: #d9d9d9;
|
||||
}
|
||||
}
|
||||
|
||||
.rightTool {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.rightToolItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
color: #666;
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
padding: 3px 6px;
|
||||
border-radius: 3px;
|
||||
transition: all 0.15s;
|
||||
|
||||
&:hover {
|
||||
background: #e6e6e6;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.inputArea {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.inputWrapper {
|
||||
border: 1px solid #d1d1d1;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
|
||||
&:focus-within {
|
||||
border-color: #07c160;
|
||||
}
|
||||
}
|
||||
|
||||
.messageInput {
|
||||
width: 100%;
|
||||
border: none;
|
||||
resize: none;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
padding: 8px 10px;
|
||||
background: transparent;
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: #b3b3b3;
|
||||
}
|
||||
}
|
||||
|
||||
.sendButtonArea {
|
||||
padding: 8px 10px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.sendButton {
|
||||
height: 32px;
|
||||
border-radius: 4px;
|
||||
font-weight: normal;
|
||||
min-width: 60px;
|
||||
font-size: 13px;
|
||||
background: #07c160;
|
||||
border-color: #07c160;
|
||||
|
||||
&:hover {
|
||||
background: #06ad56;
|
||||
border-color: #06ad56;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: #059748;
|
||||
border-color: #059748;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: #b3b3b3;
|
||||
border-color: #b3b3b3;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.inputHint {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
text-align: right;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.inputContainer {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.inputToolbar {
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.rightTool {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.rightToolItem {
|
||||
font-size: 11px;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
|
||||
.inputArea {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.sendButton {
|
||||
align-self: flex-end;
|
||||
min-width: 60px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
import React, { useState } from "react";
|
||||
import { Layout, Input, Button, Dropdown, Menu, Tooltip, Modal } from "antd";
|
||||
import {
|
||||
ShareAltOutlined,
|
||||
SendOutlined,
|
||||
SmileOutlined,
|
||||
FolderOutlined,
|
||||
AudioOutlined,
|
||||
AudioOutlined as AudioHoldOutlined,
|
||||
CodeSandboxOutlined,
|
||||
MessageOutlined,
|
||||
EnvironmentOutlined,
|
||||
StarOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
|
||||
import { useWebSocketStore } from "@/store/module/websocket/websocket";
|
||||
import styles from "./MessageEnter.module.scss";
|
||||
|
||||
const { Footer } = Layout;
|
||||
const { TextArea } = Input;
|
||||
|
||||
interface MessageEnterProps {
|
||||
contract: ContractData | weChatGroup;
|
||||
onSendMessage: (message: string) => void;
|
||||
}
|
||||
|
||||
const { sendCommand } = useWebSocketStore.getState();
|
||||
|
||||
const MessageEnter: React.FC<MessageEnterProps> = ({
|
||||
contract,
|
||||
onSendMessage,
|
||||
}) => {
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
const [showMaterialModal, setShowMaterialModal] = useState(false);
|
||||
|
||||
const handleSend = async () => {
|
||||
if (!inputValue.trim()) return;
|
||||
console.log("发送消息", contract);
|
||||
const params = {
|
||||
wechatAccountId: contract.wechatAccountId,
|
||||
wechatChatroomId: contract?.chatroomId || 0,
|
||||
wechatFriendId: contract?.chatroomId ? 0 : contract.id,
|
||||
msgSubType: 0,
|
||||
msgType: 1,
|
||||
content: inputValue,
|
||||
};
|
||||
sendCommand("CmdSendMessage", params);
|
||||
// try {
|
||||
// onSendMessage(inputValue);
|
||||
// setInputValue("");
|
||||
// } catch (error) {
|
||||
// console.error("发送失败", error);
|
||||
// }
|
||||
};
|
||||
|
||||
const handleKeyPress = (e: React.KeyboardEvent) => {
|
||||
if (e.key === "Enter" && !e.shiftKey && !e.ctrlKey) {
|
||||
e.preventDefault();
|
||||
handleSend();
|
||||
}
|
||||
// Ctrl+Enter 换行由 TextArea 自动处理,不需要阻止默认行为
|
||||
};
|
||||
|
||||
// 素材菜单项
|
||||
const materialMenuItems = [
|
||||
{
|
||||
key: "text",
|
||||
label: "文字素材",
|
||||
icon: <span>📝</span>,
|
||||
},
|
||||
{
|
||||
key: "audio",
|
||||
label: "语音素材",
|
||||
icon: <span>🎵</span>,
|
||||
},
|
||||
{
|
||||
key: "image",
|
||||
label: "图片素材",
|
||||
icon: <span>🖼️</span>,
|
||||
},
|
||||
{
|
||||
key: "video",
|
||||
label: "视频素材",
|
||||
icon: <span>🎬</span>,
|
||||
},
|
||||
{
|
||||
key: "link",
|
||||
label: "链接素材",
|
||||
icon: <span>🔗</span>,
|
||||
},
|
||||
{
|
||||
key: "card",
|
||||
label: "名片素材",
|
||||
icon: <span>📇</span>,
|
||||
},
|
||||
];
|
||||
|
||||
const handleMaterialSelect = (key: string) => {
|
||||
console.log("选择素材类型:", key);
|
||||
setShowMaterialModal(true);
|
||||
// 这里可以根据不同的素材类型显示不同的模态框
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 聊天输入 */}
|
||||
<Footer className={styles.chatFooter}>
|
||||
<div className={styles.inputContainer}>
|
||||
<div className={styles.inputToolbar}>
|
||||
<div className={styles.leftTool}>
|
||||
<Tooltip title="表情">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<SmileOutlined />}
|
||||
className={styles.toolbarButton}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="上传附件">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<FolderOutlined />}
|
||||
className={styles.toolbarButton}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="收藏">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<StarOutlined />}
|
||||
className={styles.toolbarButton}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="位置">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<EnvironmentOutlined />}
|
||||
className={styles.toolbarButton}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="语音">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<AudioOutlined />}
|
||||
className={styles.toolbarButton}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="按住说话">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<AudioHoldOutlined />}
|
||||
className={styles.toolbarButton}
|
||||
style={{ position: "relative" }}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "2px",
|
||||
right: "2px",
|
||||
fontSize: "8px",
|
||||
color: "#52c41a",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
H
|
||||
</span>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Dropdown
|
||||
overlay={
|
||||
<Menu
|
||||
items={materialMenuItems}
|
||||
onClick={({ key }) => handleMaterialSelect(key)}
|
||||
style={{
|
||||
borderRadius: "8px",
|
||||
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
|
||||
}}
|
||||
/>
|
||||
}
|
||||
trigger={["click"]}
|
||||
placement="topLeft"
|
||||
>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<CodeSandboxOutlined />}
|
||||
className={styles.toolbarButton}
|
||||
/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
<div className={styles.rightTool}>
|
||||
<div className={styles.rightToolItem}>
|
||||
<ShareAltOutlined />
|
||||
转给他人
|
||||
</div>
|
||||
<div className={styles.rightToolItem}>
|
||||
<MessageOutlined />
|
||||
聊天记录
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.inputArea}>
|
||||
<div className={styles.inputWrapper}>
|
||||
<TextArea
|
||||
value={inputValue}
|
||||
onChange={e => setInputValue(e.target.value)}
|
||||
onKeyDown={handleKeyPress}
|
||||
placeholder="输入消息..."
|
||||
className={styles.messageInput}
|
||||
autoSize={{ minRows: 2, maxRows: 6 }}
|
||||
/>
|
||||
<div className={styles.sendButtonArea}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SendOutlined />}
|
||||
onClick={handleSend}
|
||||
disabled={!inputValue.trim()}
|
||||
className={styles.sendButton}
|
||||
>
|
||||
发送
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.inputHint}>按下Ctrl+Enter换行,Enter发送</div>
|
||||
</div>
|
||||
</Footer>
|
||||
|
||||
{/* 素材选择模态框 */}
|
||||
<Modal
|
||||
title="选择素材"
|
||||
open={showMaterialModal}
|
||||
onCancel={() => setShowMaterialModal(false)}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={() => setShowMaterialModal(false)}>
|
||||
取消
|
||||
</Button>,
|
||||
<Button
|
||||
key="confirm"
|
||||
type="primary"
|
||||
onClick={() => setShowMaterialModal(false)}
|
||||
>
|
||||
确定
|
||||
</Button>,
|
||||
]}
|
||||
width={800}
|
||||
>
|
||||
<div style={{ display: "flex", height: "400px" }}>
|
||||
{/* 左侧素材分类 */}
|
||||
<div
|
||||
style={{
|
||||
width: "200px",
|
||||
background: "#f5f5f5",
|
||||
borderRight: "1px solid #e8e8e8",
|
||||
}}
|
||||
>
|
||||
<div style={{ padding: "16px", borderBottom: "1px solid #e8e8e8" }}>
|
||||
<h4 style={{ margin: 0, color: "#262626" }}>公共素材</h4>
|
||||
</div>
|
||||
<div style={{ padding: "8px 0" }}>
|
||||
<div
|
||||
style={{
|
||||
padding: "8px 16px",
|
||||
cursor: "pointer",
|
||||
background: "#e6f7ff",
|
||||
borderLeft: "3px solid #1890ff",
|
||||
color: "#1890ff",
|
||||
}}
|
||||
>
|
||||
暗黑4
|
||||
</div>
|
||||
<div style={{ padding: "8px 16px", cursor: "pointer" }}>
|
||||
针对老客户的...
|
||||
</div>
|
||||
<div style={{ padding: "8px 16px", cursor: "pointer" }}>
|
||||
D2辅助
|
||||
</div>
|
||||
<div style={{ padding: "8px 16px", cursor: "pointer" }}>
|
||||
ROS反馈演示...
|
||||
</div>
|
||||
<div style={{ padding: "8px 16px", cursor: "pointer" }}>
|
||||
一键宏产品素...
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ padding: "16px", borderTop: "1px solid #e8e8e8" }}>
|
||||
<h4 style={{ margin: 0, color: "#262626" }}>部门素材</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 右侧内容区域 */}
|
||||
<div style={{ flex: 1, padding: "16px" }}>
|
||||
<div style={{ marginBottom: "16px" }}>
|
||||
<Input.Search placeholder="昵称" style={{ width: "100%" }} />
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
height: "300px",
|
||||
color: "#8c8c8c",
|
||||
}}
|
||||
>
|
||||
请选择左侧素材分类
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MessageEnter;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import {
|
||||
Layout,
|
||||
Input,
|
||||
Button,
|
||||
Avatar,
|
||||
Space,
|
||||
@@ -9,34 +8,25 @@ import {
|
||||
Menu,
|
||||
message,
|
||||
Tooltip,
|
||||
Modal,
|
||||
} from "antd";
|
||||
import {
|
||||
ShareAltOutlined,
|
||||
SendOutlined,
|
||||
SmileOutlined,
|
||||
FolderOutlined,
|
||||
PhoneOutlined,
|
||||
VideoCameraOutlined,
|
||||
MoreOutlined,
|
||||
UserOutlined,
|
||||
AudioOutlined,
|
||||
AudioOutlined as AudioHoldOutlined,
|
||||
DownloadOutlined,
|
||||
CodeSandboxOutlined,
|
||||
MessageOutlined,
|
||||
FileOutlined,
|
||||
FilePdfOutlined,
|
||||
FileWordOutlined,
|
||||
FileExcelOutlined,
|
||||
FilePptOutlined,
|
||||
PlayCircleFilled,
|
||||
EnvironmentOutlined,
|
||||
TeamOutlined,
|
||||
StarOutlined,
|
||||
FolderOutlined,
|
||||
EnvironmentOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
|
||||
import { getMessages } from "@/pages/pc/ckbox/api";
|
||||
import { getChatMessages } from "@/pages/pc/ckbox/api";
|
||||
import styles from "./ChatWindow.module.scss";
|
||||
import {
|
||||
useWebSocketStore,
|
||||
@@ -44,8 +34,8 @@ import {
|
||||
} from "@/store/module/websocket/websocket";
|
||||
import { formatWechatTime } from "@/utils/common";
|
||||
import Person from "./components/Person";
|
||||
const { Header, Content, Footer } = Layout;
|
||||
const { TextArea } = Input;
|
||||
import MessageEnter from "./components/MessageEnter";
|
||||
const { Header, Content } = Layout;
|
||||
|
||||
interface ChatWindowProps {
|
||||
contract: ContractData | weChatGroup;
|
||||
@@ -60,11 +50,9 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
|
||||
showProfile = true,
|
||||
onToggleProfile,
|
||||
}) => {
|
||||
const [messageApi, contextHolder] = message.useMessage();
|
||||
const [, contextHolder] = message.useMessage();
|
||||
const [messages, setMessages] = useState<ChatRecord[]>([]);
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showMaterialModal, setShowMaterialModal] = useState(false);
|
||||
const [pendingVideoRequests, setPendingVideoRequests] = useState<
|
||||
Record<string, string>
|
||||
>({});
|
||||
@@ -84,7 +72,7 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
|
||||
} else {
|
||||
params.wechatChatroomId = contract.id;
|
||||
}
|
||||
getMessages(params)
|
||||
getChatMessages(params)
|
||||
.then(msg => {
|
||||
setMessages(msg);
|
||||
})
|
||||
@@ -188,87 +176,6 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||
};
|
||||
|
||||
const handleSend = async () => {
|
||||
if (!inputValue.trim()) return;
|
||||
|
||||
try {
|
||||
const newMessage: ChatRecord = {
|
||||
id: contract.id,
|
||||
wechatAccountId: contract.wechatAccountId,
|
||||
wechatFriendId: contract.id,
|
||||
tenantId: 0,
|
||||
accountId: 0,
|
||||
synergyAccountId: 0,
|
||||
content: inputValue,
|
||||
msgType: 0,
|
||||
msgSubType: 0,
|
||||
msgSvrId: "",
|
||||
isSend: false,
|
||||
createTime: "",
|
||||
isDeleted: false,
|
||||
deleteTime: "",
|
||||
sendStatus: 0,
|
||||
wechatTime: 0,
|
||||
origin: 0,
|
||||
msgId: 0,
|
||||
recalled: false,
|
||||
};
|
||||
|
||||
setMessages(prev => [...prev, newMessage]);
|
||||
onSendMessage(inputValue);
|
||||
setInputValue("");
|
||||
} catch (error) {
|
||||
messageApi.error("发送失败");
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyPress = (e: React.KeyboardEvent) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSend();
|
||||
}
|
||||
};
|
||||
|
||||
// 素材菜单项
|
||||
const materialMenuItems = [
|
||||
{
|
||||
key: "text",
|
||||
label: "文字素材",
|
||||
icon: <span>📝</span>,
|
||||
},
|
||||
{
|
||||
key: "audio",
|
||||
label: "语音素材",
|
||||
icon: <span>🎵</span>,
|
||||
},
|
||||
{
|
||||
key: "image",
|
||||
label: "图片素材",
|
||||
icon: <span>🖼️</span>,
|
||||
},
|
||||
{
|
||||
key: "video",
|
||||
label: "视频素材",
|
||||
icon: <span>🎬</span>,
|
||||
},
|
||||
{
|
||||
key: "link",
|
||||
label: "链接素材",
|
||||
icon: <span>🔗</span>,
|
||||
},
|
||||
{
|
||||
key: "card",
|
||||
label: "名片素材",
|
||||
icon: <span>📇</span>,
|
||||
},
|
||||
];
|
||||
|
||||
const handleMaterialSelect = (key: string) => {
|
||||
console.log("选择素材类型:", key);
|
||||
setShowMaterialModal(true);
|
||||
// 这里可以根据不同的素材类型显示不同的模态框
|
||||
};
|
||||
|
||||
// 处理视频播放请求,发送socket请求获取真实视频地址
|
||||
const handleVideoPlayRequest = (tencentUrl: string, messageId: number) => {
|
||||
// 生成请求ID (使用当前时间戳作为唯一标识)
|
||||
@@ -867,121 +774,8 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
|
||||
</div>
|
||||
</Content>
|
||||
|
||||
{/* 聊天输入 */}
|
||||
<Footer className={styles.chatFooter}>
|
||||
<div className={styles.inputContainer}>
|
||||
<div className={styles.inputToolbar}>
|
||||
<div className={styles.leftTool}>
|
||||
<Tooltip title="表情">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<SmileOutlined />}
|
||||
className={styles.toolbarButton}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="上传附件">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<FolderOutlined />}
|
||||
className={styles.toolbarButton}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="收藏">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<StarOutlined />}
|
||||
className={styles.toolbarButton}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="位置">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<EnvironmentOutlined />}
|
||||
className={styles.toolbarButton}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="语音">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<AudioOutlined />}
|
||||
className={styles.toolbarButton}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="按住说话">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<AudioHoldOutlined />}
|
||||
className={styles.toolbarButton}
|
||||
style={{ position: "relative" }}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "2px",
|
||||
right: "2px",
|
||||
fontSize: "8px",
|
||||
color: "#52c41a",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
H
|
||||
</span>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Dropdown
|
||||
overlay={
|
||||
<Menu
|
||||
items={materialMenuItems}
|
||||
onClick={({ key }) => handleMaterialSelect(key)}
|
||||
style={{
|
||||
borderRadius: "8px",
|
||||
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
|
||||
}}
|
||||
/>
|
||||
}
|
||||
trigger={["click"]}
|
||||
placement="topLeft"
|
||||
>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<CodeSandboxOutlined />}
|
||||
className={styles.toolbarButton}
|
||||
/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
<div className={styles.rightTool}>
|
||||
<div className={styles.rightToolItem}>
|
||||
<ShareAltOutlined />
|
||||
转给他人
|
||||
</div>
|
||||
<div className={styles.rightToolItem}>
|
||||
<MessageOutlined />
|
||||
聊天记录
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.inputArea}>
|
||||
<TextArea
|
||||
value={inputValue}
|
||||
onChange={e => setInputValue(e.target.value)}
|
||||
onKeyDown={handleKeyPress}
|
||||
placeholder="输入消息..."
|
||||
className={styles.messageInput}
|
||||
autoSize={{ minRows: 2, maxRows: 6 }}
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SendOutlined />}
|
||||
onClick={handleSend}
|
||||
disabled={!inputValue.trim()}
|
||||
className={styles.sendButton}
|
||||
>
|
||||
发送
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles.inputHint}>按下Ctrl+Enter换行</div>
|
||||
</div>
|
||||
</Footer>
|
||||
{/* 消息输入组件 */}
|
||||
<MessageEnter contract={contract} onSendMessage={onSendMessage} />
|
||||
</Layout>
|
||||
|
||||
{/* 右侧个人资料卡片 */}
|
||||
@@ -990,87 +784,6 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
|
||||
showProfile={showProfile}
|
||||
onToggleProfile={onToggleProfile}
|
||||
/>
|
||||
|
||||
{/* 素材选择模态框 */}
|
||||
<Modal
|
||||
title="选择素材"
|
||||
open={showMaterialModal}
|
||||
onCancel={() => setShowMaterialModal(false)}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={() => setShowMaterialModal(false)}>
|
||||
取消
|
||||
</Button>,
|
||||
<Button
|
||||
key="confirm"
|
||||
type="primary"
|
||||
onClick={() => setShowMaterialModal(false)}
|
||||
>
|
||||
确定
|
||||
</Button>,
|
||||
]}
|
||||
width={800}
|
||||
>
|
||||
<div style={{ display: "flex", height: "400px" }}>
|
||||
{/* 左侧素材分类 */}
|
||||
<div
|
||||
style={{
|
||||
width: "200px",
|
||||
background: "#f5f5f5",
|
||||
borderRight: "1px solid #e8e8e8",
|
||||
}}
|
||||
>
|
||||
<div style={{ padding: "16px", borderBottom: "1px solid #e8e8e8" }}>
|
||||
<h4 style={{ margin: 0, color: "#262626" }}>公共素材</h4>
|
||||
</div>
|
||||
<div style={{ padding: "8px 0" }}>
|
||||
<div
|
||||
style={{
|
||||
padding: "8px 16px",
|
||||
cursor: "pointer",
|
||||
background: "#e6f7ff",
|
||||
borderLeft: "3px solid #1890ff",
|
||||
color: "#1890ff",
|
||||
}}
|
||||
>
|
||||
暗黑4
|
||||
</div>
|
||||
<div style={{ padding: "8px 16px", cursor: "pointer" }}>
|
||||
针对老客户的...
|
||||
</div>
|
||||
<div style={{ padding: "8px 16px", cursor: "pointer" }}>
|
||||
D2辅助
|
||||
</div>
|
||||
<div style={{ padding: "8px 16px", cursor: "pointer" }}>
|
||||
ROS反馈演示...
|
||||
</div>
|
||||
<div style={{ padding: "8px 16px", cursor: "pointer" }}>
|
||||
一键宏产品素...
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ padding: "16px", borderTop: "1px solid #e8e8e8" }}>
|
||||
<h4 style={{ margin: 0, color: "#262626" }}>部门素材</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 右侧内容区域 */}
|
||||
<div style={{ flex: 1, padding: "16px" }}>
|
||||
<div style={{ marginBottom: "16px" }}>
|
||||
<Input.Search placeholder="昵称" style={{ width: "100%" }} />
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
height: "300px",
|
||||
color: "#8c8c8c",
|
||||
}}
|
||||
>
|
||||
请选择左侧素材分类
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -20,6 +20,22 @@ const messageHandlers: Record<string, MessageHandler> = {
|
||||
asyncKfUserList(kfUserList);
|
||||
},
|
||||
|
||||
// 发送消息响应
|
||||
CmdSendMessageResp: message => {
|
||||
console.log("发送消息响应", message);
|
||||
// 在这里添加具体的处理逻辑
|
||||
},
|
||||
// 接收消息响应
|
||||
CmdReceiveMessageResp: message => {
|
||||
console.log("接收消息响应", message);
|
||||
// 在这里添加具体的处理逻辑
|
||||
},
|
||||
//收到消息
|
||||
CmdNewMessage: message => {
|
||||
console.log("收到消息", message.friendMessage);
|
||||
// 在这里添加具体的处理逻辑
|
||||
},
|
||||
|
||||
// 登录响应
|
||||
CmdSignInResp: message => {
|
||||
console.log("登录响应", message);
|
||||
|
||||
Reference in New Issue
Block a user