page.tsx 6.6 KB

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