Files
jiaowu-test/src/pages/ExpertSupportPage/index.jsx
KQL 561d5c286d feat: 实现日历课程点击跳转到直播间功能
- 添加日历课程详情弹窗的点击跳转功能
- 公共课直播间和课程直播间支持URL参数自动选中课程
- 优化岗位详情页面样式,复用简洁卡片样式
- 为岗位详情标题添加图标
- 调整不同类型课程的跳转逻辑

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-11 14:14:45 +08:00

544 lines
18 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect, useRef } from "react";
import { expertQAData, expertInfo } from "@/data/expertQAData";
import "./index.css";
const ExpertSupportPage = () => {
// 对话管理
const [selectedConversation, setSelectedConversation] = useState(null);
const [messages, setMessages] = useState([]);
const [inputMessage, setInputMessage] = useState("");
const [showResolutionModal, setShowResolutionModal] = useState(null);
const [isNewConversation, setIsNewConversation] = useState(false);
// 用于跟踪最后活动时间
// 初始对话数据 - 整合文旅产业问答内容
const [conversationGroups, setConversationGroups] = useState({
今天: [], // 清空今天板块的对话记录
"7天内": [
// 导入文旅产业问答数据中的最近记录
...expertQAData.slice(0, 3).map(qa => ({
...qa,
lastActivityTime: Date.now() - (Math.floor(Math.random() * 6) + 1) * 24 * 60 * 60 * 1000
})),
{
id: 7,
title: "文创产品开发咨询",
lastMessage: "如何设计有文化内涵的旅游纪念品",
time: "01-03",
status: "已解决",
statusType: "resolved",
hasInactivityAlert: false,
lastActivityTime: Date.now() - 5 * 24 * 60 * 60 * 1000,
messages: []
},
],
"30天内": [
// 导入文旅产业问答数据中的其余记录
...expertQAData.slice(3, 6).map(qa => ({
...qa,
lastActivityTime: Date.now() - (Math.floor(Math.random() * 10) + 15) * 24 * 60 * 60 * 1000
})),
{
id: 11,
title: "智慧旅游技术应用",
lastMessage: "如何利用数字技术提升游客体验",
time: "12-12",
status: "已解决",
statusType: "resolved",
hasInactivityAlert: false,
lastActivityTime: Date.now() - 26 * 24 * 60 * 60 * 1000,
messages: []
},
],
});
// 当前活跃对话的消息记录
const conversationMessages = [
{
id: 1,
type: "user",
content: "您好,我遇到了一些问题",
time: "2025-01-08 16:45",
avatar: "👤",
},
{
id: 2,
type: "expert",
content:
"你好我是多多ai智能问答小助手有什么问题就来问我吧",
time: "2025-01-08 16:46",
avatar: "👨‍💻",
expertName: "多多机器人",
},
{
id: 3,
type: "user",
content: "我需要了解更多关于泛型的使用,特别是在复杂数据结构中的应用",
time: "2025-01-08 16:48",
avatar: "👤",
},
];
// 创建新对话
const createNewConversation = () => {
const newConversation = {
id: Date.now(),
title: "新的问题咨询",
lastMessage: "",
time: new Date().toLocaleTimeString("zh-CN", {
hour: "2-digit",
minute: "2-digit",
}),
status: "进行中",
statusType: "in-progress",
hasInactivityAlert: false,
lastActivityTime: Date.now(),
};
setConversationGroups((prev) => ({
...prev,
今天: [newConversation, ...prev["今天"]],
}));
setSelectedConversation(newConversation);
setMessages([]);
setIsNewConversation(true);
};
// 检查非活跃状态
const checkInactivityStatus = () => {
const currentTime = Date.now();
const INACTIVITY_THRESHOLD = 10 * 60 * 1000; // 10分钟
setConversationGroups((prev) => {
const updated = { ...prev };
Object.keys(updated).forEach((groupKey) => {
updated[groupKey] = updated[groupKey].map((conversation) => {
if (conversation.statusType === "in-progress") {
const timeSinceLastActivity =
currentTime - conversation.lastActivityTime;
const shouldShowAlert =
timeSinceLastActivity >= INACTIVITY_THRESHOLD;
if (shouldShowAlert !== conversation.hasInactivityAlert) {
return { ...conversation, hasInactivityAlert: shouldShowAlert };
}
}
return conversation;
});
});
return updated;
});
};
// 处理对话选择
const handleConversationSelect = (conversation) => {
// 如果是带有红点的对话,点击时询问是否已解决
if (
conversation.hasInactivityAlert &&
conversation.statusType === "in-progress"
) {
setShowResolutionModal(conversation);
} else {
setSelectedConversation(conversation);
// 如果对话有预设的消息记录,使用它们;否则使用默认消息
if (conversation.messages && conversation.messages.length > 0) {
setMessages(conversation.messages);
} else {
setMessages(conversationMessages);
}
setIsNewConversation(false);
}
};
// 标记问题为已解决
const markAsResolved = (conversationId) => {
setConversationGroups((prev) => {
const updated = { ...prev };
Object.keys(updated).forEach((groupKey) => {
updated[groupKey] = updated[groupKey].map((conversation) => {
if (conversation.id === conversationId) {
return {
...conversation,
status: "已解决",
statusType: "resolved",
hasInactivityAlert: false,
lastMessage: "问题已解决",
};
}
return conversation;
});
});
return updated;
});
setShowResolutionModal(null);
// 如果当前选中的是这个对话,取消选择
if (selectedConversation?.id === conversationId) {
setSelectedConversation(null);
setMessages([]);
}
};
// 继续对话
const continueConversation = (conversation) => {
// 更新最后活动时间
setConversationGroups((prev) => {
const updated = { ...prev };
Object.keys(updated).forEach((groupKey) => {
updated[groupKey] = updated[groupKey].map((conv) => {
if (conv.id === conversation.id) {
return {
...conv,
hasInactivityAlert: false,
lastActivityTime: Date.now(),
};
}
return conv;
});
});
return updated;
});
setSelectedConversation(conversation);
// 如果对话有预设的消息记录,使用它们;否则使用默认消息
if (conversation.messages && conversation.messages.length > 0) {
setMessages(conversation.messages);
} else {
setMessages(conversationMessages);
}
setShowResolutionModal(null);
setIsNewConversation(false);
};
// 发送消息
const handleSendMessage = () => {
if (!inputMessage.trim()) return;
const newMessage = {
id: messages.length + 1,
type: "user",
content: inputMessage,
time: new Date().toLocaleString("zh-CN"),
avatar: "👤",
};
setMessages([...messages, newMessage]);
setInputMessage("");
// 更新当前对话的标题和最后消息(如果是新对话)
if (selectedConversation && isNewConversation && messages.length === 0) {
const title =
inputMessage.length > 20
? inputMessage.substring(0, 20) + "..."
: inputMessage;
setConversationGroups((prev) => {
const updated = { ...prev };
Object.keys(updated).forEach((groupKey) => {
updated[groupKey] = updated[groupKey].map((conversation) => {
if (conversation.id === selectedConversation.id) {
return {
...conversation,
title: title,
lastMessage: inputMessage,
lastActivityTime: Date.now(),
};
}
return conversation;
});
});
return updated;
});
setSelectedConversation((prev) => ({
...prev,
title: title,
lastMessage: inputMessage,
lastActivityTime: Date.now(),
}));
setIsNewConversation(false);
} else if (selectedConversation) {
// 更新现有对话的最后活动时间
setConversationGroups((prev) => {
const updated = { ...prev };
Object.keys(updated).forEach((groupKey) => {
updated[groupKey] = updated[groupKey].map((conversation) => {
if (conversation.id === selectedConversation.id) {
return {
...conversation,
lastMessage: inputMessage,
lastActivityTime: Date.now(),
hasInactivityAlert: false,
};
}
return conversation;
});
});
return updated;
});
}
};
// 组件生命周期
useEffect(() => {
// 页面加载时自动创建新对话
createNewConversation();
// 定时检查非活跃状态
const interval = setInterval(checkInactivityStatus, 30000); // 每30秒检查一次
return () => clearInterval(interval);
}, []);
// 组件挂载后进行初始检查
useEffect(() => {
const timeoutId = setTimeout(checkInactivityStatus, 1000);
return () => clearTimeout(timeoutId);
}, []);
// 转专家服务
const handleTransferToExpert = () => {
if (
selectedConversation &&
selectedConversation.statusType === "in-progress"
) {
// 更新对话状态逻辑
alert("已转交人工专家,将有专业老师为您服务");
}
};
return (
<div className="expert-support-page">
{/* 左侧对话记录区域 */}
<div className="conversation-sidebar">
<div className="sidebar-header">
<h2 className="expert-support-sidebar-title">对话记录</h2>
<div className="new-conversation-btn-wrapper" style={{ position: 'relative', display: 'inline-block' }}>
<button
className="new-conversation-btn disabled"
onClick={(e) => e.preventDefault()}
disabled
style={{
cursor: 'not-allowed',
opacity: 0.6,
backgroundColor: '#f5f5f5'
}}
>
<span className="btn-icon">💬</span>
<span className="btn-text">新建对话</span>
</button>
<div className="hover-tooltip" style={{
position: 'absolute',
top: '-35px',
left: '50%',
transform: 'translateX(-50%)',
padding: '6px 12px',
backgroundColor: 'rgba(0, 0, 0, 0.8)',
color: '#fff',
borderRadius: '4px',
fontSize: '12px',
whiteSpace: 'nowrap',
display: 'none',
zIndex: 1000
}}>
非学员无权限
</div>
</div>
</div>
{/* 按时间分组的对话列表 */}
<div className="conversation-groups">
{Object.entries(conversationGroups).map(
([groupName, conversations]) => (
<div key={groupName} className="conversation-group">
<div className="group-header">
<h3 className="group-title">{groupName}</h3>
</div>
<div className="conversation-list">
{conversations.map((conversation) => (
<div
key={conversation.id}
className={`conversation-item ${
selectedConversation?.id === conversation.id
? "selected"
: ""
} ${conversation.hasInactivityAlert ? "has-alert" : ""}`}
onClick={() => handleConversationSelect(conversation)}
>
<div className="conversation-content">
<div className="conversation-title-row">
<h4 className="conversation-title">
{conversation.title}
</h4>
{conversation.hasInactivityAlert && (
<div className="inactivity-alert">
<span className="alert-dot"></span>
</div>
)}
</div>
<p className="conversation-preview">
{conversation.lastMessage}
</p>
</div>
<div className="conversation-meta">
<span className="conversation-time">
{conversation.time}
</span>
<span
className={`conversation-status status-${conversation.statusType}`}
>
{conversation.status}
</span>
</div>
</div>
))}
</div>
</div>
)
)}
</div>
</div>
{/* 右侧问答界面 */}
<div className="chat-interface">
{/* 顶部信息栏 */}
<div className="chat-header">
<div className="header-info">
<div className="service-badge">
<span className="badge-icon">🎓</span>
<span className="badge-text">专家客服</span>
</div>
<div className="service-title">学有所问向必有答</div>
<div className="service-subtitle">
我们有创新学习习惯养成方案
</div>
</div>
<div className="service-actions">
<div className="service-tag">专家答疑</div>
<div className="service-tag">快速响应</div>
<div className="service-tag">24小时服务</div>
</div>
</div>
{/* 对话内容区域 */}
<div className="chat-content">
{selectedConversation ? (
<>
<div className="chat-title-bar">
<h3 className="active-conversation-title">
{selectedConversation.title}
</h3>
{selectedConversation.statusType === "in-progress" && (
<button
className="transfer-expert-btn"
onClick={handleTransferToExpert}
>
转专家服务
</button>
)}
</div>
<div className="messages-container">
{messages.map((message) => (
<div
key={message.id}
className={`message ${message.type}-message`}
>
<div className="message-avatar">{message.avatar}</div>
<div className="message-bubble">
{message.type === "expert" && (
<div className="expert-name">{message.expertName}</div>
)}
<div className="message-text">{message.content}</div>
<div className="message-time">{message.time}</div>
</div>
</div>
))}
</div>
</>
) : (
<div className="no-conversation-selected">
<div className="placeholder-icon">💬</div>
<div className="placeholder-text">请选择一个对话开始</div>
<div className="placeholder-subtitle">
从左侧列表中选择对话记录
</div>
</div>
)}
</div>
{/* 输入区域 */}
{selectedConversation && (
<div className="chat-input-area">
<div className="input-container">
<input
type="text"
value={inputMessage}
onChange={(e) => setInputMessage(e.target.value)}
placeholder="请输入您的问题..."
className="message-input"
onKeyPress={(e) => e.key === "Enter" && handleSendMessage()}
/>
<button
className="send-button"
onClick={handleSendMessage}
disabled={!inputMessage.trim()}
>
发送
</button>
</div>
<div className="input-tips">
<span className="tip-text">
Shift + Enter 可以换行Enter 发送消息
</span>
</div>
</div>
)}
</div>
{/* 问题解决确认模态框 */}
{showResolutionModal && (
<div className="resolution-modal-overlay">
<div className="resolution-modal">
<div className="modal-header">
<h3 className="modal-title">问题解决确认</h3>
<button
className="modal-close"
onClick={() => setShowResolutionModal(null)}
>
×
</button>
</div>
<div className="modal-content">
<div className="modal-icon"></div>
<p className="modal-text">
您在{showResolutionModal.title}中已经超过10分钟没有活动
请问这个问题是否已经得到解决
</p>
</div>
<div className="modal-actions">
<button
className="btn-secondary"
onClick={() => continueConversation(showResolutionModal)}
>
继续讨论
</button>
<button
className="btn-primary"
onClick={() => markAsResolved(showResolutionModal.id)}
>
已解决
</button>
</div>
</div>
</div>
)}
</div>
);
};
export default ExpertSupportPage;