page.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. "use client";
  2. import { useEffect, useState } from "react";
  3. import { useRouter } from "next/navigation";
  4. import { apiUrl } from "@/lib/config";
  5. import { Button } from "@/components/ui/button";
  6. // 对话类型定义
  7. interface Conversation {
  8. id: number;
  9. visitor_id: number;
  10. agent_id: number;
  11. status: string;
  12. created_at: string;
  13. updated_at: string;
  14. }
  15. export default function ConversationsPage() {
  16. const [conversations, setConversations] = useState<Conversation[]>([]);
  17. const [loading, setLoading] = useState(true);
  18. const [username, setUsername] = useState<string>("");
  19. const [role, setRole] = useState<string>("");
  20. const router = useRouter();
  21. // 检查是否已登录
  22. useEffect(() => {
  23. const userId = localStorage.getItem("agent_user_id");
  24. const savedUsername = localStorage.getItem("agent_username");
  25. const savedRole = localStorage.getItem("agent_role");
  26. if (!userId || !savedUsername) {
  27. // 未登录,跳转到登录页面
  28. router.push("/");
  29. return;
  30. }
  31. setUsername(savedUsername);
  32. setRole(savedRole || "");
  33. }, [router]);
  34. // 拉取对话列表
  35. const fetchConversations = async () => {
  36. try {
  37. const res = await fetch(apiUrl("/conversations"));
  38. if (res.ok) {
  39. const data = await res.json();
  40. if (Array.isArray(data)) {
  41. setConversations(data);
  42. }
  43. } else {
  44. console.error("获取对话列表失败");
  45. }
  46. } catch (error) {
  47. console.error("获取对话列表失败:", error);
  48. } finally {
  49. setLoading(false);
  50. }
  51. };
  52. // 页面加载时拉取对话列表
  53. useEffect(() => {
  54. const userId = localStorage.getItem("agent_user_id");
  55. if (userId) {
  56. fetchConversations();
  57. }
  58. }, []);
  59. // 退出登录
  60. const handleLogout = async () => {
  61. try {
  62. await fetch(apiUrl("/logout"), {
  63. method: "POST",
  64. });
  65. } catch (error) {
  66. console.error("退出登录失败:", error);
  67. } finally {
  68. // 清空本地存储
  69. localStorage.removeItem("agent_user_id");
  70. localStorage.removeItem("agent_username");
  71. localStorage.removeItem("agent_role");
  72. // 跳转到登录页面
  73. router.push("/");
  74. }
  75. };
  76. // 格式化时间显示
  77. const formatTime = (dateStr: string) => {
  78. const date = new Date(dateStr);
  79. const now = new Date();
  80. const diff = now.getTime() - date.getTime();
  81. // 今天:只显示时间
  82. if (diff < 24 * 3600 * 1000 && date.getDate() === now.getDate()) {
  83. return date.toLocaleTimeString("zh-CN", {
  84. hour: "2-digit",
  85. minute: "2-digit",
  86. });
  87. }
  88. // 更早:显示日期+时间
  89. return date.toLocaleString("zh-CN", {
  90. month: "2-digit",
  91. day: "2-digit",
  92. hour: "2-digit",
  93. minute: "2-digit",
  94. });
  95. };
  96. // 点击对话,跳转到聊天页面
  97. const handleConversationClick = (conversationId: number) => {
  98. router.push(`/agent/chat/${conversationId}`);
  99. };
  100. if (loading) {
  101. return (
  102. <div className="flex justify-center items-center min-h-screen">
  103. <div className="text-lg">加载中...</div>
  104. </div>
  105. );
  106. }
  107. return (
  108. <div className="flex flex-col h-screen bg-gray-50">
  109. {/* 顶部标题栏 */}
  110. <div className="bg-card border-b p-4 shadow-sm">
  111. <div className="flex justify-between items-center">
  112. <div>
  113. <h1 className="text-xl font-bold text-foreground">对话列表</h1>
  114. <div className="text-sm text-muted-foreground mt-1">
  115. {username} ({role === "admin" ? "管理员" : "客服"})
  116. </div>
  117. </div>
  118. <Button
  119. onClick={handleLogout}
  120. variant="outline"
  121. size="sm"
  122. >
  123. 退出登录
  124. </Button>
  125. </div>
  126. </div>
  127. {/* 对话列表区域 */}
  128. <div className="flex-1 overflow-y-auto p-4">
  129. {conversations.length === 0 ? (
  130. <div className="text-center text-gray-400 mt-8">
  131. 暂无对话
  132. </div>
  133. ) : (
  134. <div className="space-y-2">
  135. {conversations.map((conv) => (
  136. <div
  137. key={conv.id}
  138. onClick={() => handleConversationClick(conv.id)}
  139. className="bg-white p-4 rounded-lg shadow-sm border border-gray-200 hover:shadow-md cursor-pointer transition-shadow"
  140. >
  141. <div className="flex justify-between items-start">
  142. <div className="flex-1">
  143. <div className="flex items-center gap-2 mb-1">
  144. <span className="font-medium text-gray-800">
  145. 对话 #{conv.id}
  146. </span>
  147. <span
  148. className={`px-2 py-1 rounded text-xs ${
  149. conv.status === "open"
  150. ? "bg-green-100 text-green-700"
  151. : "bg-gray-100 text-gray-700"
  152. }`}
  153. >
  154. {conv.status === "open" ? "进行中" : conv.status}
  155. </span>
  156. </div>
  157. <div className="text-sm text-gray-600">
  158. 访客ID: {conv.visitor_id}
  159. </div>
  160. <div className="text-xs text-gray-400 mt-1">
  161. 创建时间: {formatTime(conv.created_at)}
  162. </div>
  163. </div>
  164. <div className="text-xs text-gray-400">
  165. 最后更新: {formatTime(conv.updated_at)}
  166. </div>
  167. </div>
  168. </div>
  169. ))}
  170. </div>
  171. )}
  172. </div>
  173. </div>
  174. );
  175. }