Преглед изворни кода

✨ feat(chat): 增强图片消息显示功能

- 添加图片预览组件ImagePreview,支持图片加载失败处理
- 在消息列表中为图片类型消息显示缩略图预览
- 在消息详情中展示完整图片预览及图片信息
- 显示图片文件名、类型、大小和文件ID等详细信息
- 添加FileImage图标用于图片加载失败时的占位显示
yourname пре 6 месеци
родитељ
комит
e2aa229876
1 измењених фајлова са 98 додато и 13 уклоњено
  1. 98 13
      src/client/admin/pages/ChatMessages.tsx

+ 98 - 13
src/client/admin/pages/ChatMessages.tsx

@@ -3,7 +3,7 @@ import { useQuery } from '@tanstack/react-query';
 import { format } from 'date-fns';
 import { zhCN } from 'date-fns/locale';
 import { toast } from 'sonner';
-import { Search, Trash2, Eye, MessageSquare, Image, Settings } from 'lucide-react';
+import { Search, Trash2, Eye, MessageSquare, Image, Settings, FileImage } from 'lucide-react';
 
 import { chatMessageClient } from '@/client/api';
 import type { InferResponseType, InferRequestType } from 'hono/client';
@@ -20,6 +20,32 @@ import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent,
 
 import { DataTablePagination } from '@/client/admin/components/DataTablePagination';
 
+// 图片预览组件
+const ImagePreview = ({ src, alt, className = '' }: { src: string; alt: string; className?: string }) => {
+  const [error, setError] = useState(false);
+
+  if (error) {
+    return (
+      <div className={`flex items-center justify-center bg-muted rounded-md ${className}`}>
+        <div className="text-center p-4">
+          <FileImage className="h-8 w-8 text-muted-foreground mx-auto mb-2" />
+          <p className="text-xs text-muted-foreground">图片加载失败</p>
+        </div>
+      </div>
+    );
+  }
+
+  return (
+    <img
+      src={src}
+      alt={alt}
+      className={`object-cover rounded-md ${className}`}
+      onError={() => setError(true)}
+      loading="lazy"
+    />
+  );
+};
+
 type ChatMessageListResponse = InferResponseType<typeof chatMessageClient.$get, 200>;
 type ChatMessageDetailResponse = InferResponseType<typeof chatMessageClient[':id']['$get'], 200>;
 type CreateChatMessageRequest = InferRequestType<typeof chatMessageClient.$post>['json'];
@@ -204,10 +230,23 @@ export const ChatMessagesPage = () => {
                           {messageTypeMap[message.type]?.label || message.type}
                         </Badge>
                       </TableCell>
-                      <TableCell title={message.content}>
-                        <div className="max-w-xs truncate">
-                          {truncateText(message.content)}
-                        </div>
+                      <TableCell>
+                        {message.type === 'image' && message.file ? (
+                          <div className="flex items-center gap-2">
+                            <div className="w-8 h-8 rounded overflow-hidden">
+                              <ImagePreview
+                                src={message.file.fullUrl}
+                                alt="图片消息"
+                                className="w-full h-full"
+                              />
+                            </div>
+                            <span className="text-sm text-muted-foreground">图片消息</span>
+                          </div>
+                        ) : (
+                          <div title={message.content} className="max-w-xs truncate">
+                            {truncateText(message.content)}
+                          </div>
+                        )}
                       </TableCell>
                       <TableCell>
                         <div className="text-sm">
@@ -327,9 +366,24 @@ export const ChatMessagesPage = () => {
                 <label className="text-sm font-medium text-muted-foreground">消息内容</label>
                 <div className="mt-1 p-3 bg-muted rounded-md">
                   {selectedMessage.type === 'image' ? (
-                    <div className="text-sm text-muted-foreground">
-                      图片消息(内容可能包含图片URL或文件ID)
-                    </div>
+                    selectedMessage.file ? (
+                      <div className="space-y-3">
+                        <div className="flex justify-center">
+                          <ImagePreview
+                            src={selectedMessage.file.fullUrl}
+                            alt="图片消息"
+                            className="max-w-full max-h-64 rounded-md"
+                          />
+                        </div>
+                        <div className="text-sm text-muted-foreground text-center">
+                          图片消息预览
+                        </div>
+                      </div>
+                    ) : (
+                      <div className="text-sm text-muted-foreground">
+                        图片消息(文件信息缺失)
+                      </div>
+                    )
                   ) : (
                     <p className="text-sm whitespace-pre-wrap break-words">
                       {selectedMessage.content}
@@ -338,13 +392,44 @@ export const ChatMessagesPage = () => {
                 </div>
               </div>
 
-              {selectedMessage.type === 'image' && (
+              {selectedMessage.type === 'image' && selectedMessage.file && (
                 <div>
                   <label className="text-sm font-medium text-muted-foreground">图片信息</label>
-                  <div className="mt-1 p-3 bg-muted rounded-md">
-                    <p className="text-sm text-muted-foreground">
-                      这是一个图片消息,可能需要特殊处理来显示图片内容
-                    </p>
+                  <div className="mt-1 p-3 bg-muted rounded-md space-y-2">
+                    <div className="grid grid-cols-2 gap-4 text-sm">
+                      <div>
+                        <span className="text-muted-foreground">文件名:</span>
+                        <span>{selectedMessage.file.name}</span>
+                      </div>
+                      <div>
+                        <span className="text-muted-foreground">文件类型:</span>
+                        <span>{selectedMessage.file.type || '未知'}</span>
+                      </div>
+                      <div>
+                        <span className="text-muted-foreground">文件大小:</span>
+                        <span>
+                          {selectedMessage.file.size 
+                            ? `${(selectedMessage.file.size / 1024).toFixed(1)} KB` 
+                            : '未知'
+                          }
+                        </span>
+                      </div>
+                      <div>
+                        <span className="text-muted-foreground">文件ID:</span>
+                        <span>{selectedMessage.file.id}</span>
+                      </div>
+                    </div>
+                    <div className="text-sm">
+                      <span className="text-muted-foreground">文件URL:</span>
+                      <a 
+                        href={selectedMessage.file.fullUrl} 
+                        target="_blank" 
+                        rel="noopener noreferrer"
+                        className="text-blue-600 hover:underline break-all"
+                      >
+                        {selectedMessage.file.fullUrl}
+                      </a>
+                    </div>
                   </div>
                 </div>
               )}