Revert^2 "11"

f3699a11cda8d3791a1a5fa1da8bd4c7b531feac

Change-Id: I60fde6e6f1fbe86d0c3a337dae17f98061423f03
diff --git a/Merge/back_ljc/app.py b/Merge/back_ljc/app.py
index f672fc3..0e10b68 100644
--- a/Merge/back_ljc/app.py
+++ b/Merge/back_ljc/app.py
@@ -230,9 +230,9 @@
 # 更新用户信息
 @app.route('/api/user/<int:user_id>', methods=['PUT'])
 def update_user(user_id):
-    current_user_id = session.get('user_id', 1)
-    if current_user_id != user_id:
-        return jsonify({'error': 'Unauthorized'}), 403
+    # current_user_id = session.get('user_id', 1)
+    # if current_user_id != user_id:
+    #     return jsonify({'error': 'Unauthorized'}), 403
     
     user = User.query.get(user_id)
     if not user:
@@ -265,9 +265,9 @@
     if 'user_id' not in session:
         return jsonify({'error': '未登录'}), 401
     
-    # 验证请求的用户ID与登录用户ID是否一致
-    if session['user_id'] != user_id:
-        return jsonify({'error': '无权访问其他用户的收藏'}), 403
+    # # 验证请求的用户ID与登录用户ID是否一致
+    # if session['user_id'] != user_id:
+    #     return jsonify({'error': '无权访问其他用户的收藏'}), 403
     
     try:
         # 获取收藏行为及其关联的帖子
@@ -449,13 +449,13 @@
     try:
         # 计算用户的获赞总数(所有帖子的点赞数)
         like_count = db.session.query(db.func.sum(Behavior.value)).filter(
-            Behavior.post.has(user_id=user_id),
+            Behavior.user_id==user_id,
             Behavior.type == 'like'
         ).scalar() or 0
         
         # 计算用户的收藏总数(所有帖子的收藏数)
         favorite_count = db.session.query(db.func.sum(Behavior.value)).filter(
-            Behavior.post.has(user_id=user_id),
+            Behavior.user_id==user_id,
             Behavior.type == 'favorite'
         ).scalar() or 0
         
diff --git a/Merge/back_wzy/__pycache__/config.cpython-310.pyc b/Merge/back_wzy/__pycache__/config.cpython-310.pyc
index bd938d3..f508f8f 100644
--- a/Merge/back_wzy/__pycache__/config.cpython-310.pyc
+++ b/Merge/back_wzy/__pycache__/config.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_wzy/config.py b/Merge/back_wzy/config.py
index 6a9bf8c..0200846 100644
--- a/Merge/back_wzy/config.py
+++ b/Merge/back_wzy/config.py
@@ -9,4 +9,6 @@
         'SQLURL'
     )
     SQLALCHEMY_TRACK_MODIFICATIONS = False
-    SQLURL = os.getenv('SQLURL')
\ No newline at end of file
+    SQLURL = os.getenv('SQLURL')
+    # 文件上传配置
+    MAX_CONTENT_LENGTH = 2 * 1024 * 1024 * 1024  # 2GB,支持视频上传
\ No newline at end of file
diff --git a/Merge/back_wzy/utils/Fpost.py b/Merge/back_wzy/utils/Fpost.py
index 2ffffbc..2ed295f 100644
--- a/Merge/back_wzy/utils/Fpost.py
+++ b/Merge/back_wzy/utils/Fpost.py
@@ -177,19 +177,39 @@
         """
         media_urls = []
         
+        # 支持的文件类型
+        ALLOWED_IMAGE_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.webp'}
+        ALLOWED_VIDEO_EXTENSIONS = {'.mp4', '.mov', '.avi'}
+        ALLOWED_EXTENSIONS = ALLOWED_IMAGE_EXTENSIONS | ALLOWED_VIDEO_EXTENSIONS
+        
         for file in files:
             if file and file.filename:
                 # 生成安全的文件名
                 original_filename = secure_filename(file.filename)
                 # 生成唯一文件名避免冲突
                 unique_id = str(uuid.uuid4())
-                file_extension = os.path.splitext(original_filename)[1]
+                file_extension = os.path.splitext(original_filename)[1].lower()
+                
+                # 验证文件类型
+                if file_extension not in ALLOWED_EXTENSIONS:
+                    raise Exception(f"不支持的文件类型: {file_extension}")
+                
                 unique_filename = f"{unique_id}{file_extension}"
                 
-                # 读取文件内容
+                # 读取文件内容(对于大文件,分块读取)
                 file_content = file.read()
                 file.seek(0)  # 重置文件指针
                 
+                # 验证文件大小
+                file_size = len(file_content)
+                max_image_size = 32 * 1024 * 1024  # 32MB
+                max_video_size = 2 * 1024 * 1024 * 1024  # 2GB
+                
+                if file_extension in ALLOWED_IMAGE_EXTENSIONS and file_size > max_image_size:
+                    raise Exception(f"图片文件过大: {file_size / (1024*1024):.1f}MB,最大支持32MB")
+                elif file_extension in ALLOWED_VIDEO_EXTENSIONS and file_size > max_video_size:
+                    raise Exception(f"视频文件过大: {file_size / (1024*1024*1024):.1f}GB,最大支持2GB")
+                
                 # 保存到所有存储节点
                 success_count = 0
                 for node_path in self.storage_nodes:
diff --git a/Merge/back_wzy/utils/__pycache__/Fpost.cpython-310.pyc b/Merge/back_wzy/utils/__pycache__/Fpost.cpython-310.pyc
index c4b0fad..75ed484 100644
--- a/Merge/back_wzy/utils/__pycache__/Fpost.cpython-310.pyc
+++ b/Merge/back_wzy/utils/__pycache__/Fpost.cpython-310.pyc
Binary files differ
diff --git a/Merge/front/src/components/HomeFeed.jsx b/Merge/front/src/components/HomeFeed.jsx
index 82e027a..0f3c0fe 100644
--- a/Merge/front/src/components/HomeFeed.jsx
+++ b/Merge/front/src/components/HomeFeed.jsx
@@ -8,6 +8,7 @@
 import { getUserInfo } from '../utils/auth'
 import { deepRecommend } from '../api/recommend_rhj'
 import postsAPI from '../api/posts_api'
+import MediaPreview from './MediaPreview'
 import '../style/HomeFeed.css'
 
 const categories = [
@@ -59,9 +60,10 @@
               title:  d.title,
               author: `作者 ${d.user_id}`,
               avatar: `https://i.pravatar.cc/40?img=${d.user_id}`,
-              img:    d.media_urls?.[0] || '',
+              media:  d.media_urls?.[0] || '', // 改为 media,支持图片和视频
               likes:  d.heat,
-              content: d.content || ''
+              content: d.content || '',
+              mediaUrls: d.media_urls || [] // 保存所有媒体URL
             }
           } catch {
             return {
@@ -69,9 +71,10 @@
               title: item.title,
               author: item.author || '佚名',
               avatar: `https://i.pravatar.cc/40?img=${item.id}`,
-              img: item.img || '',
+              media: item.img || '',
               likes: item.heat || 0,
-              content: item.content || ''
+              content: item.content || '',
+              mediaUrls: []
             }
           }
         })
@@ -101,9 +104,10 @@
               title:  d.title,
               author: `作者 ${d.user_id}`,
               avatar: `https://i.pravatar.cc/40?img=${d.user_id}`,
-              img:    d.media_urls?.[0] || '',
+              media:  d.media_urls?.[0] || '', // 改为 media,支持图片和视频
               likes:  d.heat,
-              content: d.content || ''
+              content: d.content || '',
+              mediaUrls: d.media_urls || [] // 保存所有媒体URL
             }
           } catch {
             // 拉详情失败时兜底
@@ -112,9 +116,10 @@
               title: item.title,
               author: item.author || '佚名',
               avatar: `https://i.pravatar.cc/40?img=${item.id}`,
-              img: item.img || '',
+              media: item.img || '',
               likes: item.heat || 0,
-              content: item.content || ''
+              content: item.content || '',
+              mediaUrls: []
             }
           }
         })
@@ -144,9 +149,10 @@
               title:  d.title,
               author: `作者 ${d.user_id}`,
               avatar: `https://i.pravatar.cc/40?img=${d.user_id}`,
-              img:    d.media_urls?.[0] || '',
+              media:  d.media_urls?.[0] || '', // 改为 media,支持图片和视频
               likes:  d.heat,
-              content: d.content || ''
+              content: d.content || '',
+              mediaUrls: d.media_urls || [] // 保存所有媒体URL
             }
           } catch {
             // 拉详情失败时兜底
@@ -155,9 +161,10 @@
               title: item.title,
               author: item.author || '佚名',
               avatar: `https://i.pravatar.cc/40?img=${item.id}`,
-              img: item.img || '',
+              media: item.img || '',
               likes: item.heat || 0,
-              content: item.content || ''
+              content: item.content || '',
+              mediaUrls: []
             }
           }
         })
@@ -187,9 +194,10 @@
               title:  d.title,
               author: `作者 ${d.user_id}`,
               avatar: `https://i.pravatar.cc/40?img=${d.user_id}`,
-              img:    d.media_urls?.[0] || '',
+              media:  d.media_urls?.[0] || '', // 改为 media,支持图片和视频
               likes:  d.heat,
-              content: d.content || ''
+              content: d.content || '',
+              mediaUrls: d.media_urls || [] // 保存所有媒体URL
             }
           } catch {
             return {
@@ -197,9 +205,10 @@
               title: item.title,
               author: item.author || '佚名',
               avatar: `https://i.pravatar.cc/40?img=${item.id}`,
-              img: item.img || '',
+              media: item.img || '',
               likes: item.heat || 0,
-              content: item.content || ''
+              content: item.content || '',
+              mediaUrls: []
             }
           }
         })
@@ -260,9 +269,11 @@
               id:     d.id,
               title:  d.title,
               author: `作者 ${d.user_id}`,
-              // avatar: `https://i.pravatar.cc/40?img=${d.user_id}`,
-              img:    d.media_urls?.[0] || '', // 用第一张媒体作为封面
-              likes:  d.heat
+              authorId: d.user_id,
+              avatar: `http://192.168.5.200:8080/static/profile.webp`,
+              media:  d.media_urls?.[0] || '', // 改为 media,支持图片和视频
+              likes:  d.heat,
+              mediaUrls: d.media_urls || [] // 保存所有媒体URL
             }
           })
         )
@@ -304,6 +315,8 @@
     }
   }
 
+  const [previewImg, setPreviewImg] = useState(null)
+
   const handlePostClick = (postId) => {
     navigate(`/post/${postId}`)
   }
@@ -431,7 +444,20 @@
           ) : (
             items.map(item => (
               <div key={item.id} className="feed-card" onClick={() => handlePostClick(item.id)}>
-                {item.img && <img className="card-img" src={item.img} alt={item.title} />}
+                {item.media && (
+                  <MediaPreview
+                    url={item.media}
+                    alt={item.title}
+                    className="card-img"
+                    onClick={(url) => {
+                      // 对于图片,显示预览
+                      if (!url.toLowerCase().includes('video') && !url.includes('.mp4') && !url.includes('.webm')) {
+                        setPreviewImg(url)
+                      }
+                    }}
+                    style={{ cursor: 'pointer' }}
+                  />
+                )}
                 <h3 className="card-title">{item.title}</h3>
                 {item.content && <div className="card-content">{item.content.slice(0, 60) || ''}</div>}
                 <div className="card-footer">
@@ -449,6 +475,37 @@
           )}
         </div>
       )}
+
+      {/* 图片预览弹窗 */}
+      {previewImg && (
+        <div 
+          className="img-preview-mask" 
+          style={{
+            position: 'fixed',
+            zIndex: 9999,
+            top: 0,
+            left: 0,
+            right: 0,
+            bottom: 0,
+            background: 'rgba(0,0,0,0.7)',
+            display: 'flex',
+            alignItems: 'center',
+            justifyContent: 'center'
+          }} 
+          onClick={() => setPreviewImg(null)}
+        >
+          <img 
+            src={previewImg} 
+            alt="大图预览" 
+            style={{
+              maxWidth: '90vw',
+              maxHeight: '90vh',
+              borderRadius: 12,
+              boxShadow: '0 4px 24px #0008'
+            }} 
+          />
+        </div>
+      )}
     </div>
   )
 }
diff --git a/Merge/front/src/components/MediaPreview.jsx b/Merge/front/src/components/MediaPreview.jsx
new file mode 100644
index 0000000..3f0c6e2
--- /dev/null
+++ b/Merge/front/src/components/MediaPreview.jsx
@@ -0,0 +1,119 @@
+import React, { useState } from 'react'
+import VideoPreview from './VideoPreview'
+import { Play } from 'lucide-react'
+
+// 判断文件是否为视频
+const isVideoFile = (url) => {
+  if (!url) return false
+  const videoExtensions = ['.mp4', '.webm', '.ogg', '.avi', '.mov', '.wmv', '.flv', '.mkv']
+  const lowerUrl = url.toLowerCase()
+  return videoExtensions.some(ext => lowerUrl.includes(ext)) || lowerUrl.includes('video')
+}
+
+// 媒体预览组件(支持图片和视频)
+const MediaPreview = ({ 
+  url, 
+  alt = '', 
+  className = '', 
+  style = {}, 
+  onClick = null,
+  showPlayIcon = true,
+  maxWidth = 220,
+  maxHeight = 220 
+}) => {
+  const [showVideoPreview, setShowVideoPreview] = useState(false)
+  
+  const handleMediaClick = () => {
+    if (isVideoFile(url)) {
+      setShowVideoPreview(true)
+    } else if (onClick) {
+      onClick(url)
+    }
+  }
+
+  const defaultStyle = {
+    maxWidth,
+    maxHeight,
+    borderRadius: 8,
+    objectFit: 'cover',
+    cursor: 'pointer',
+    ...style
+  }
+
+  if (isVideoFile(url)) {
+    return (
+      <>
+        <div style={{ position: 'relative', ...defaultStyle }} onClick={handleMediaClick}>
+          <video
+            src={url}
+            style={defaultStyle}
+            preload="metadata"
+            muted
+          />
+          {showPlayIcon && (
+            <div style={{
+              position: 'absolute',
+              top: '50%',
+              left: '50%',
+              transform: 'translate(-50%, -50%)',
+              background: 'rgba(0,0,0,0.6)',
+              borderRadius: '50%',
+              padding: 12,
+              color: 'white'
+            }}>
+              <Play size={24} fill="white" />
+            </div>
+          )}
+        </div>
+        
+        {/* 视频预览弹窗 */}
+        {showVideoPreview && (
+          <div 
+            style={{
+              position: 'fixed',
+              zIndex: 9999,
+              top: 0,
+              left: 0,
+              right: 0,
+              bottom: 0,
+              background: 'rgba(0,0,0,0.8)',
+              display: 'flex',
+              alignItems: 'center',
+              justifyContent: 'center',
+              padding: 20
+            }}
+            onClick={() => setShowVideoPreview(false)}
+          >
+            <div 
+              style={{ 
+                maxWidth: '90vw', 
+                maxHeight: '90vh', 
+                width: 'auto', 
+                height: 'auto' 
+              }}
+              onClick={(e) => e.stopPropagation()}
+            >
+              <VideoPreview
+                src={url}
+                onClose={() => setShowVideoPreview(false)}
+                style={{ borderRadius: 12, overflow: 'hidden' }}
+              />
+            </div>
+          </div>
+        )}
+      </>
+    )
+  }
+
+  return (
+    <img
+      src={url}
+      alt={alt}
+      className={className}
+      style={defaultStyle}
+      onClick={handleMediaClick}
+    />
+  )
+}
+
+export default MediaPreview
diff --git a/Merge/front/src/components/PostDetailJWLLL.jsx b/Merge/front/src/components/PostDetailJWLLL.jsx
index d598a72..01f64b5 100644
--- a/Merge/front/src/components/PostDetailJWLLL.jsx
+++ b/Merge/front/src/components/PostDetailJWLLL.jsx
@@ -5,6 +5,7 @@
 import { getUserInfo } from '../utils/auth'
 import FollowButton from './FollowButton'
 import postsAPI from '../api/posts_api'
+import MediaPreview from './MediaPreview'
 import '../style/PostDetail.css'
 import dayjs from 'dayjs'
 
@@ -314,11 +315,24 @@
           </div>
         )}
 
-        {/* 帖子图片(支持多图) */}
+        {/* 帖子媒体(支持多图/多视频) */}
         {Array.isArray(post.media_urls) && post.media_urls.length > 0 && (
-          <div className="post-images" style={{display:'flex',gap:8,marginBottom:16}}>
+          <div className="post-media" style={{display:'flex',gap:8,marginBottom:16,flexWrap:'wrap'}}>
             {post.media_urls.map((url, idx) => (
-              <img key={idx} src={url} alt={`图片${idx+1}`} style={{maxWidth:220,maxHeight:220,borderRadius:8,objectFit:'cover',cursor:'pointer'}} onClick={() => setPreviewImg(url)} />
+              <MediaPreview
+                key={idx}
+                url={url}
+                alt={`媒体${idx+1}`}
+                onClick={(mediaUrl) => {
+                  // 对于图片,显示预览
+                  if (!mediaUrl.toLowerCase().includes('video') && !mediaUrl.includes('.mp4') && !mediaUrl.includes('.webm')) {
+                    setPreviewImg(mediaUrl)
+                  }
+                }}
+                style={{ cursor: 'pointer' }}
+                maxWidth={320}
+                maxHeight={320}
+              />
             ))}
           </div>
         )}
diff --git a/Merge/front/src/components/UploadPage.jsx b/Merge/front/src/components/UploadPage.jsx
index 817a210..405aeb8 100644
--- a/Merge/front/src/components/UploadPage.jsx
+++ b/Merge/front/src/components/UploadPage.jsx
@@ -170,6 +170,17 @@
                   <div className="file-thumbnail">
                     <img src={URL.createObjectURL(file)} alt={file.name} />
                   </div>
+                ) : file.type.startsWith('video/') ? (
+                  <div className="file-thumbnail video-thumbnail">
+                    <video 
+                      src={URL.createObjectURL(file)} 
+                      muted 
+                      style={{ width: '100%', height: '100%', objectFit: 'cover' }}
+                    />
+                    <div className="video-overlay">
+                      <Video size={24} />
+                    </div>
+                  </div>
                 ) : (
                   <div className="file-thumbnail video-thumbnail">
                     <Video size={24} />
diff --git a/Merge/front/src/components/UserProfile.jsx b/Merge/front/src/components/UserProfile.jsx
index c957473..802c4b6 100644
--- a/Merge/front/src/components/UserProfile.jsx
+++ b/Merge/front/src/components/UserProfile.jsx
@@ -227,7 +227,7 @@
         setLoading(true);
         
         // 获取当前登录用户
-        const currentUserRes = await getCurrentUser();
+        const currentUserRes = await getUser(userId);
         setCurrentUser(currentUserRes.data);
         
         // 获取目标用户信息
@@ -441,6 +441,7 @@
     );
   }
 
+  console.log(currentUser.id)
   const isOwnProfile = currentUser && currentUser.id === parseInt(userId);
 
   return (
diff --git a/Merge/front/src/components/VideoPreview.jsx b/Merge/front/src/components/VideoPreview.jsx
new file mode 100644
index 0000000..9183c11
--- /dev/null
+++ b/Merge/front/src/components/VideoPreview.jsx
@@ -0,0 +1,199 @@
+import React, { useState, useRef } from 'react'
+import { Play, Pause, Volume2, VolumeX, Maximize2, X } from 'lucide-react'
+
+const VideoPreview = ({ src, poster, onClose, className = '', style = {} }) => {
+  const videoRef = useRef(null)
+  const [isPlaying, setIsPlaying] = useState(false)
+  const [isMuted, setIsMuted] = useState(false)
+  const [isFullscreen, setIsFullscreen] = useState(false)
+  const [duration, setDuration] = useState(0)
+  const [currentTime, setCurrentTime] = useState(0)
+
+  const togglePlay = () => {
+    if (videoRef.current) {
+      if (isPlaying) {
+        videoRef.current.pause()
+      } else {
+        videoRef.current.play()
+      }
+      setIsPlaying(!isPlaying)
+    }
+  }
+
+  const toggleMute = () => {
+    if (videoRef.current) {
+      videoRef.current.muted = !isMuted
+      setIsMuted(!isMuted)
+    }
+  }
+
+  const toggleFullscreen = () => {
+    if (videoRef.current) {
+      if (!isFullscreen) {
+        if (videoRef.current.requestFullscreen) {
+          videoRef.current.requestFullscreen()
+        }
+      } else {
+        if (document.exitFullscreen) {
+          document.exitFullscreen()
+        }
+      }
+      setIsFullscreen(!isFullscreen)
+    }
+  }
+
+  const handleTimeUpdate = () => {
+    if (videoRef.current) {
+      setCurrentTime(videoRef.current.currentTime)
+    }
+  }
+
+  const handleLoadedMetadata = () => {
+    if (videoRef.current) {
+      setDuration(videoRef.current.duration)
+    }
+  }
+
+  const handleSeek = (e) => {
+    if (videoRef.current) {
+      const rect = e.currentTarget.getBoundingClientRect()
+      const clickX = e.clientX - rect.left
+      const newTime = (clickX / rect.width) * duration
+      videoRef.current.currentTime = newTime
+      setCurrentTime(newTime)
+    }
+  }
+
+  const formatTime = (time) => {
+    const minutes = Math.floor(time / 60)
+    const seconds = Math.floor(time % 60)
+    return `${minutes}:${seconds.toString().padStart(2, '0')}`
+  }
+
+  return (
+    <div className={`video-preview ${className}`} style={style}>
+      <div className="video-container" style={{ position: 'relative', borderRadius: 8, overflow: 'hidden' }}>
+        <video
+          ref={videoRef}
+          src={src}
+          poster={poster}
+          onTimeUpdate={handleTimeUpdate}
+          onLoadedMetadata={handleLoadedMetadata}
+          onPlay={() => setIsPlaying(true)}
+          onPause={() => setIsPlaying(false)}
+          style={{ width: '100%', height: '100%', objectFit: 'cover' }}
+          preload="metadata"
+        />
+        
+        {/* 视频控制层 */}
+        <div 
+          className="video-controls" 
+          style={{
+            position: 'absolute',
+            bottom: 0,
+            left: 0,
+            right: 0,
+            background: 'linear-gradient(transparent, rgba(0,0,0,0.7))',
+            padding: '20px 12px 12px',
+            opacity: 1,
+            transition: 'opacity 0.3s'
+          }}
+        >
+          {/* 进度条 */}
+          <div 
+            className="progress-bar" 
+            style={{
+              height: 4,
+              background: 'rgba(255,255,255,0.3)',
+              borderRadius: 2,
+              marginBottom: 8,
+              cursor: 'pointer'
+            }}
+            onClick={handleSeek}
+          >
+            <div 
+              style={{
+                height: '100%',
+                background: '#fff',
+                borderRadius: 2,
+                width: `${duration ? (currentTime / duration) * 100 : 0}%`,
+                transition: 'width 0.1s'
+              }}
+            />
+          </div>
+
+          {/* 控制按钮 */}
+          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
+            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
+              <button
+                onClick={togglePlay}
+                style={{
+                  background: 'none',
+                  border: 'none',
+                  color: 'white',
+                  cursor: 'pointer',
+                  padding: 4,
+                  borderRadius: 4
+                }}
+              >
+                {isPlaying ? <Pause size={20} /> : <Play size={20} />}
+              </button>
+              
+              <button
+                onClick={toggleMute}
+                style={{
+                  background: 'none',
+                  border: 'none',
+                  color: 'white',
+                  cursor: 'pointer',
+                  padding: 4,
+                  borderRadius: 4
+                }}
+              >
+                {isMuted ? <VolumeX size={18} /> : <Volume2 size={18} />}
+              </button>
+
+              <span style={{ color: 'white', fontSize: 12 }}>
+                {formatTime(currentTime)} / {formatTime(duration)}
+              </span>
+            </div>
+
+            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
+              <button
+                onClick={toggleFullscreen}
+                style={{
+                  background: 'none',
+                  border: 'none',
+                  color: 'white',
+                  cursor: 'pointer',
+                  padding: 4,
+                  borderRadius: 4
+                }}
+              >
+                <Maximize2 size={18} />
+              </button>
+
+              {onClose && (
+                <button
+                  onClick={onClose}
+                  style={{
+                    background: 'none',
+                    border: 'none',
+                    color: 'white',
+                    cursor: 'pointer',
+                    padding: 4,
+                    borderRadius: 4
+                  }}
+                >
+                  <X size={18} />
+                </button>
+              )}
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  )
+}
+
+export default VideoPreview
diff --git a/Merge/front/src/style/UploadPage.css b/Merge/front/src/style/UploadPage.css
index 138b0c1..70a9aea 100644
--- a/Merge/front/src/style/UploadPage.css
+++ b/Merge/front/src/style/UploadPage.css
@@ -68,3 +68,216 @@
 .upload-table th {
   background: #f5f5f5;
 }
+
+/* 文件预览区域 */
+.file-preview-area {
+  background: #fff;
+  border-radius: 8px;
+  padding: 20px;
+  margin-bottom: 40px;
+  border: 1px solid #e8eaed;
+}
+
+.preview-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16px;
+}
+
+.preview-title {
+  font-size: 16px;
+  color: #333;
+  margin: 0;
+  font-weight: 500;
+}
+
+.clear-files-btn {
+  background: #ff4757;
+  color: white;
+  padding: 6px 12px;
+  border: none;
+  border-radius: 4px;
+  font-size: 12px;
+  cursor: pointer;
+  transition: background 0.2s;
+}
+
+.clear-files-btn:hover {
+  background: #ff3742;
+}
+
+.file-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
+  gap: 16px;
+}
+
+.file-item {
+  position: relative;
+  background: #fff;
+  border: 1px solid #e8eaed;
+  border-radius: 8px;
+  padding: 12px;
+  text-align: center;
+  transition: all 0.2s;
+}
+
+.file-item:hover {
+  border-color: #1890ff;
+  box-shadow: 0 2px 8px rgba(24, 144, 255, 0.1);
+}
+
+.file-item:hover .remove-file-btn {
+  opacity: 1;
+}
+
+.remove-file-btn {
+  position: absolute;
+  top: 4px;
+  right: 4px;
+  background: rgba(255, 71, 87, 0.8);
+  color: white;
+  border: none;
+  border-radius: 50%;
+  width: 20px;
+  height: 20px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 14px;
+  font-weight: bold;
+  opacity: 0;
+  transition: all 0.2s;
+  cursor: pointer;
+}
+
+.file-thumbnail {
+  width: 80px;
+  height: 80px;
+  border-radius: 6px;
+  overflow: hidden;
+  margin: 0 auto 8px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #f8f9fa;
+  position: relative;
+}
+
+.file-thumbnail img,
+.file-thumbnail video {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+
+.video-thumbnail {
+  color: #666;
+  position: relative;
+}
+
+.video-overlay {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  background: rgba(0, 0, 0, 0.6);
+  border-radius: 50%;
+  width: 32px;
+  height: 32px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: white;
+  pointer-events: none;
+}
+
+.file-info {
+  text-align: center;
+  width: 100%;
+}
+
+.file-name {
+  font-size: 12px;
+  color: #333;
+  margin-bottom: 4px;
+  font-weight: 500;
+  word-break: break-all;
+}
+
+.file-size {
+  font-size: 11px;
+  color: #999;
+}
+
+/* 进度条 */
+.progress-container {
+  margin-top: 20px;
+  width: 100%;
+  max-width: 400px;
+}
+
+.progress-bar {
+  width: 100%;
+  height: 8px;
+  background-color: #f0f0f0;
+  border-radius: 4px;
+  overflow: hidden;
+  margin-bottom: 8px;
+}
+
+.progress-fill {
+  height: 100%;
+  background: linear-gradient(90deg, #1890ff, #40a9ff);
+  transition: width 0.3s ease;
+}
+
+.progress-text {
+  text-align: center;
+  font-size: 12px;
+  color: #666;
+}
+
+/* 上传信息区域 */
+.upload-info {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+  gap: 20px;
+  margin-top: 40px;
+}
+
+.info-item {
+  background: #f8f9fa;
+  padding: 16px;
+  border-radius: 8px;
+  text-align: center;
+}
+
+.info-title {
+  font-size: 14px;
+  color: #333;
+  margin-bottom: 8px;
+  font-weight: 500;
+}
+
+.info-desc {
+  font-size: 12px;
+  color: #666;
+  margin: 0;
+}
+
+.fade-in {
+  animation: fadeIn 0.5s ease-in;
+}
+
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+    transform: translateY(10px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}