Merge changes from topic "revert-1445-revert-1443-FEGNGJECHO-ZDOINMGPHW"

* changes:
  Revert^2 "api忘改回来了"
  Revert^2 "11"
diff --git a/Merge/back_ljc/app.py b/Merge/back_ljc/app.py
index f672fc3..182a6e5 100644
--- a/Merge/back_ljc/app.py
+++ b/Merge/back_ljc/app.py
@@ -1,6 +1,7 @@
 from flask import Flask, jsonify, request, session
 from flask_sqlalchemy import SQLAlchemy
 from flask_cors import CORS
+from flask_jwt_extended import jwt_required, get_jwt_identity
 
 app = Flask(__name__)
 app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:123456@10.126.59.25/redbook'
@@ -230,9 +231,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 +266,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:
         # 获取收藏行为及其关联的帖子
@@ -351,9 +352,10 @@
     return jsonify(following_list)
 
 # 关注/取消关注用户
-@app.route('/api/follow/<int:followee_id>', methods=['POST', 'DELETE'])
-def follow_user(followee_id):
-    follower_id = session.get('user_id', 1)
+@app.route('/api/follow/<int:follower_id>/<int:followee_id>', methods=['POST', 'DELETE'])
+def follow_user(follower_id,followee_id):
+    # follower_id = session.get('user_id', 1)
+    print(follower_id)
     if follower_id == followee_id:
         return jsonify({'error': 'Cannot follow yourself'}), 400
     
@@ -449,13 +451,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
         
@@ -468,6 +470,130 @@
         app.logger.error(f"获取用户互动数据失败: {str(e)}")
         return jsonify({'error': '获取互动数据失败'}), 500
 
+# 点赞/取消点赞路由 - 修改为包含用户ID
+@app.route('/api/users/<int:user_id>/posts/<int:post_id>/like', methods=['POST', 'DELETE'])
+def handle_like(user_id, post_id):
+    # 检查用户是否登录
+    if 'user_id' not in session:
+        return jsonify({'error': '未登录'}), 401
+    
+    # 验证请求用户ID与登录用户ID是否一致
+    if session['user_id'] != user_id:
+        return jsonify({'error': '无权限操作'}), 403
+    
+    post = Post.query.get(post_id)
+    
+    if not post:
+        return jsonify({'error': '帖子不存在'}), 404
+    
+    # 检查行为类型
+    behavior = Behavior.query.filter_by(
+        user_id=user_id,
+        post_id=post_id,
+        type='like'
+    ).first()
+    
+    if request.method == 'POST':
+        # 点赞
+        if not behavior:
+            new_behavior = Behavior(
+                user_id=user_id,
+                post_id=post_id,
+                type='like',
+                value=1
+            )
+            db.session.add(new_behavior)
+            db.session.commit()
+            return jsonify({'message': '点赞成功', 'liked': True})
+        return jsonify({'message': '已点赞', 'liked': True})
+    
+    elif request.method == 'DELETE':
+        # 取消点赞
+        if behavior:
+            db.session.delete(behavior)
+            db.session.commit()
+            return jsonify({'message': '已取消点赞', 'liked': False})
+        return jsonify({'message': '未点赞', 'liked': False})
+
+# 收藏/取消收藏路由 - 修改为包含用户ID
+@app.route('/api/users/<int:user_id>/posts/<int:post_id>/favorite', methods=['POST', 'DELETE'])
+def handle_favorite(user_id, post_id):
+    # 检查用户是否登录
+    if 'user_id' not in session:
+        return jsonify({'error': '未登录'}), 401
+    
+    # 验证请求用户ID与登录用户ID是否一致
+    if session['user_id'] != user_id:
+        return jsonify({'error': '无权限操作'}), 403
+    
+    post = Post.query.get(post_id)
+    
+    if not post:
+        return jsonify({'error': '帖子不存在'}), 404
+    
+    # 检查行为类型
+    behavior = Behavior.query.filter_by(
+        user_id=user_id,
+        post_id=post_id,
+        type='favorite'
+    ).first()
+    
+    if request.method == 'POST':
+        # 收藏
+        if not behavior:
+            new_behavior = Behavior(
+                user_id=user_id,
+                post_id=post_id,
+                type='favorite',
+                value=1
+            )
+            db.session.add(new_behavior)
+            db.session.commit()
+            return jsonify({'message': '收藏成功', 'favorited': True})
+        return jsonify({'message': '已收藏', 'favorited': True})
+    
+    elif request.method == 'DELETE':
+        # 取消收藏
+        if behavior:
+            db.session.delete(behavior)
+            db.session.commit()
+            return jsonify({'message': '已取消收藏', 'favorited': False})
+        return jsonify({'message': '未收藏', 'favorited': False})
+
+# 获取帖子互动状态(是否点赞/收藏) - 修改为包含用户ID
+@app.route('/api/users/<int:user_id>/posts/<int:post_id>/interaction-status')
+def get_post_interaction_status(user_id, post_id):
+    # 检查用户是否登录
+    if 'user_id' not in session:
+        return jsonify({
+            'liked': False,
+            'favorited': False
+        })
+    
+    # 验证请求用户ID与登录用户ID是否一致
+    if session['user_id'] != user_id:
+        return jsonify({
+            'liked': False,
+            'favorited': False
+        })
+    
+    liked = Behavior.query.filter_by(
+        user_id=user_id,
+        post_id=post_id,
+        type='like'
+    ).first() is not None
+    
+    favorited = Behavior.query.filter_by(
+        user_id=user_id,
+        post_id=post_id,
+        type='favorite'
+    ).first() is not None
+    
+    return jsonify({
+        'liked': liked,
+        'favorited': favorited
+    })
+
 
 if __name__ == '__main__':
     app.run(debug=True,port='5715',host='0.0.0.0')
\ No newline at end of file
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/api/api_ljc.js b/Merge/front/src/api/api_ljc.js
index 1adea99..8c16245 100644
--- a/Merge/front/src/api/api_ljc.js
+++ b/Merge/front/src/api/api_ljc.js
@@ -3,6 +3,7 @@
 
 const api = axios.create({
   baseURL: 'http://10.126.59.25:5715/api/',
+  // baseURL: 'http://127.0.0.1:5715/api/',
   withCredentials: true
 });
 
@@ -15,11 +16,11 @@
 export const getFavorites = (userId) => api.get(`/user/${userId}/favorites`);
 
 // 关注相关API
-export const followUser = (followeeId) => {
-  return api.post(`/follow/${followeeId}`);
+export const followUser = (followerId,followeeId) => {
+  return api.post(`/follow/${followerId}/${followeeId}`);
 };
-export const unfollowUser = (followeeId) => {
-  return api.delete(`/follow/${followeeId}`);
+export const unfollowUser = (followerId,followeeId) => {
+  return api.delete(`/follow/${followerId}/${followeeId}`);
 };
 
 // 帖子相关API
@@ -32,5 +33,56 @@
 export const getUserInteractions = (userId) => api.get(`/user/${userId}/interactions`);
 // 获取粉丝
 export const getUserFollowers = (userId) => api.get(`/user/${userId}/followers`);
+// ================= 帖子互动API =================
+
+/**
+ * 点赞帖子
+ * @param {number} userId 用户ID
+ * @param {number} postId 帖子ID
+ * @returns 操作结果
+ */
+export const likePost = (userId, postId) => {
+  return api.post(`/users/${userId}/posts/${postId}/like`);
+};
+
+/**
+ * 取消点赞
+ * @param {number} userId 用户ID
+ * @param {number} postId 帖子ID
+ * @returns 操作结果
+ */
+export const unlikePost = (userId, postId) => {
+  return api.delete(`/users/${userId}/posts/${postId}/like`);
+};
+
+/**
+ * 收藏帖子
+ * @param {number} userId 用户ID
+ * @param {number} postId 帖子ID
+ * @returns 操作结果
+ */
+export const favoritePost = (userId, postId) => {
+  return api.post(`/users/${userId}/posts/${postId}/favorite`);
+};
+
+/**
+ * 取消收藏
+ * @param {number} userId 用户ID
+ * @param {number} postId 帖子ID
+ * @returns 操作结果
+ */
+export const unfavoritePost = (userId, postId) => {
+  return api.delete(`/users/${userId}/posts/${postId}/favorite`);
+};
+
+/**
+ * 获取当前用户对帖子的互动状态
+ * @param {number} userId 用户ID
+ * @param {number} postId 帖子ID
+ * @returns {object} { liked: boolean, favorited: boolean }
+ */
+export const getPostInteractionStatus = (userId, postId) => {
+  return api.get(`/users/${userId}/posts/${postId}/interaction-status`);
+};
 
 export default api;
\ No newline at end of file
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..2e54fac 100644
--- a/Merge/front/src/components/UserProfile.jsx
+++ b/Merge/front/src/components/UserProfile.jsx
@@ -53,7 +53,10 @@
   Collections,
   ChevronLeft,
   ChevronRight,
-  Close
+  Close,
+  Bookmark,
+  Group,
+  People
 } from '@mui/icons-material';
 import { createTheme, ThemeProvider } from '@mui/material/styles';
 import { Link, useNavigate } from 'react-router-dom';
@@ -66,7 +69,10 @@
   followUser as followUserApi,
   unfollowUser as unfollowUserApi,
   getUserPosts,
-  getUserInteractions
+  getUserInteractions,
+  getUserFollowers,
+  getFavorites,
+  getUserFollowing
 } from '../api/api_ljc';
 import { fetchPost } from '../api/posts_wzy';
 
@@ -119,6 +125,10 @@
   const navigate = useNavigate();
   const [activeTab, setActiveTab] = useState(0);
   const [isEditing, setIsEditing] = useState(false);
+  const [favorites, setFavorites] = useState([]);
+  const [following, setFollowing] = useState([]);
+  const [followers, setFollowers] = useState([]);
+  const [follower, setFollower] = useState([]);
   const [snackbar, setSnackbar] = useState({ open: false, message: '', severity: 'success' });
   const [anchorEl, setAnchorEl] = useState(null);
   
@@ -199,35 +209,13 @@
   
   fetchInteractions();
 
-    const handleFollowUser = async (followeeId) => {
-    try {
-      await followUserApi(followeeId);
-      showSnackbar('关注成功');
-      
-      // 更新粉丝列表状态(将刚关注的用户标记为已关注)
-      // setFollowers(prev => prev.map(user => 
-      //   user.id === followeeId ? { ...user, is_following: true } : user
-      // ));
-      
-      // 更新当前用户关注数
-      if (currentUser) {
-        setCurrentUser(prev => ({
-          ...prev,
-          following_count: prev.following_count + 1
-        }));
-      }
-      
-    } catch (error) {
-      console.error('关注操作失败:', error);
-      showSnackbar('关注失败,请重试', 'error');
-    }
-  };
+   
     const fetchData = async () => {
       try {
         setLoading(true);
         
         // 获取当前登录用户
-        const currentUserRes = await getCurrentUser();
+        const currentUserRes = await getUser(userId);
         setCurrentUser(currentUserRes.data);
         
         // 获取目标用户信息
@@ -241,6 +229,22 @@
           birthday: profileUserRes.data.birthday || '',
           location: profileUserRes.data.location || ''
         });
+
+        if (activeTab === 1) {
+                  // 加载收藏数据
+                  const favoritesRes = await getFavorites(userId);
+                  setFavorites(favoritesRes.data);
+                } else if (activeTab === 2) {
+                  // 加载关注列表
+                  const followingRes = await getUserFollowing(userId);
+                  setFollowing(followingRes.data);
+                  console.log("following",followingRes.data)
+                }  else if (activeTab === 3) {
+                  // 加载粉丝列表
+                  const followersRes = await getUserFollowers(userId);
+                  // 
+                  setFollowers(followersRes.data.data);
+                }
         
         // 获取用户帖子
         const postsRes = await getUserPosts(userId);
@@ -283,7 +287,7 @@
     };
     
     fetchData();
-  }, [userId]);
+  }, [activeTab,userId]);
 
   // 根据标签页加载数据
   useEffect(() => {
@@ -314,18 +318,40 @@
     }
   };
 
-  const handleFollowUser = async (followeeId) => {
+  const handleFollowUser = async (userId,followeeId) => {
     try {
-      await followUserApi(followeeId);
+      await followUserApi(userId,followeeId);
       showSnackbar('关注成功');
       
-      // 更新当前用户关注数
-      if (currentUser) {
-        setCurrentUser(prev => ({
-          ...prev,
-          following_count: prev.following_count + 1
-        }));
-      }
+      // 更新粉丝列表中的关注状态
+    setFollowers(prevFollowers => 
+      prevFollowers.map(follower => 
+        follower.id === followeeId 
+          ? { ...follower, is_following: true } 
+          : follower
+      )
+    );
+    
+    // 更新当前用户的关注数
+    if (currentUser) {
+      setCurrentUser(prev => ({
+        ...prev,
+        following_count: prev.following_count + 1
+      }));
+    }
+    
+    // 如果被关注的用户是当前用户的粉丝,更新粉丝数
+    setFollowers(prevFollowers => 
+      prevFollowers.map(follower => 
+        follower.id === followeeId 
+          ? { 
+              ...follower, 
+              followers_count: follower.followers_count + 1 
+            } 
+          : follower
+      )
+    );
+    
       
     } catch (error) {
       console.error('关注操作失败:', error);
@@ -333,13 +359,27 @@
     }
   };
 
-  const handleUnfollow = async (followeeId, e) => {
-    e.stopPropagation(); // 阻止事件冒泡
+  const handleUnfollow = async (userId,followeeId, e) => {
+    // e.stopPropagation(); // 阻止事件冒泡
     
     try {
-      await unfollowUserApi(followeeId);
+      await unfollowUserApi(userId,followeeId);
       showSnackbar('已取消关注');
       
+      // 更新关注列表 - 移除取消关注的用户
+    setFollowing(prevFollowing => 
+      prevFollowing.filter(user => user.id !== followeeId)
+    );
+    
+    // 更新粉丝列表 - 更新关注状态
+    setFollowers(prevFollowers => 
+      prevFollowers.map(follower => 
+        follower.id === followeeId 
+          ? { ...follower, is_following: false } 
+          : follower
+      )
+    );
+      
       // 更新当前用户关注数
       if (currentUser) {
         setCurrentUser(prev => ({
@@ -441,6 +481,7 @@
     );
   }
 
+  console.log(currentUser.id)
   const isOwnProfile = currentUser && currentUser.id === parseInt(userId);
 
   return (
@@ -616,52 +657,32 @@
               }}
             >
               <Tab icon={isMobile ? <Collections /> : null} label="笔记" />
+              <Tab icon={isMobile ? <Bookmark /> : null} label="收藏" />
+              <Tab icon={isMobile ? <Group /> : null} label="关注" />
+              <Tab icon={isMobile ? <People /> : null} label="粉丝" />
             </Tabs>
           </Box>
 
           {/* 内容区域 */}
           <Box sx={{ mt: 3 }}>
-            {/* 只保留笔记标签页 */}
-            <Grid container spacing={3}>
-              {tabLoading ? (
-                <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
-                  <CircularProgress />
-                </Grid>
-              ) : allPosts.length === 0 ? (
-                <Grid item xs={12}>
-                  <Box sx={{ 
-                    display: 'flex', 
-                    flexDirection: 'column', 
-                    alignItems: 'center', 
-                    py: 8,
-                    textAlign: 'center'
-                  }}>
-                    <Collections sx={{ fontSize: 60, color: 'grey.300', mb: 2 }} />
-                    <Typography variant="h6" sx={{ mb: 1 }}>
-                      还没有发布笔记
-                    </Typography>
-                    <Typography variant="body1" color="textSecondary" sx={{ mb: 3 }}>
-                      {isOwnProfile ? '分享你的生活点滴吧~' : '该用户还没有发布任何笔记'}
-                    </Typography>
-                    {isOwnProfile && (
-                      <Button variant="contained" color="primary">
-                        发布第一篇笔记
-                      </Button>
-                    )}
-                  </Box>
-                </Grid>
-              ) : (
-                // 显示当前页的帖子
-                posts.map((post, index) => (
-                  <Grid item xs={12} sm={6} lg={3} key={post.id}>
-                    <Card elevation={0} sx={{ 
-                      bgcolor: 'white', 
-                      borderRadius: 3,
-                      height: '100%',
-                      display: 'flex',
-                      flexDirection: 'column'
-                    }}>
-                      {/* 只有当帖子有 media_urls 时才显示图片 */}
+            
+            {activeTab === 0 && (
+              <Grid container spacing={3}>
+                {tabLoading ? (
+                  <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
+                    <CircularProgress />
+                  </Grid>
+                ) : posts.length > 0 ? (
+                  posts.map((post, index) => (
+                    <Grid item xs={12} sm={6} lg={3} key={post.id}>
+                      <Card elevation={0} sx={{ 
+                        bgcolor: 'white', 
+                        borderRadius: 3,
+                        height: '100%',
+                        display: 'flex',
+                        flexDirection: 'column'
+                      }}>
+                        {/* 只有当帖子有 media_urls 时才显示图片 */}
                       {post.media_urls && post.media_urls.length > 0 && (
                         <CardMedia
                           component="img"
@@ -670,63 +691,322 @@
                           alt={post.title}
                         />
                       )}
-                      <CardContent sx={{ flexGrow: 1 }}>
-                        <Typography gutterBottom variant="h6" component="div">
-                          {post.title}
-                        </Typography>
-                        <Typography variant="body2" color="text.secondary">
-                          {post.content ? post.content.substring(0, 60) + '...' : '暂无内容'}
-                        </Typography>
-                      </CardContent>
-                      <CardActions sx={{ justifyContent: 'space-between', px: 2, pb: 2 }}>
-                        <Box>
-                          <IconButton aria-label="add to favorites">
-                            <Favorite />
-                            <Typography variant="body2" sx={{ ml: 1 }}>
-                              {post.heat || Math.floor(Math.random() * 1000) + 1000}
-                            </Typography>
-                          </IconButton>
-                          <IconButton aria-label="share">
-                            <Share />
-                          </IconButton>
-                        </Box>
-                        <Chip 
-                          label={post.type === 'image' ? '图文' : post.type === 'video' ? '视频' : '文档'} 
-                          size="small" 
-                          color="primary" 
-                          variant="outlined" 
-                        />
-                      </CardActions>
-                    </Card>
+                        <CardContent sx={{ flexGrow: 1 }}>
+                          <Typography gutterBottom variant="h6" component="div">
+                            {post.title}
+                          </Typography>
+                          <Typography variant="body2" color="text.secondary">
+                            {post.content.substring(0, 60)}...
+                          </Typography>
+                        </CardContent>
+                        <CardActions sx={{ justifyContent: 'space-between', px: 2, pb: 2 }}>
+                          <Box>
+                            <IconButton aria-label="add to favorites">
+                              <Favorite />
+                              <Typography variant="body2" sx={{ ml: 1 }}>
+                                {post.heat || Math.floor(Math.random() * 1000) + 1000}
+                              </Typography>
+                            </IconButton>
+                            <IconButton aria-label="share">
+                              <Share />
+                            </IconButton>
+                          </Box>
+                          <Chip 
+                            label={post.type === 'image' ? '图文' : post.type === 'video' ? '视频' : '文档'} 
+                            size="small" 
+                            color="primary" 
+                            variant="outlined" 
+                          />
+                        </CardActions>
+                      </Card>
+                    </Grid>
+                  ))
+                ) : (
+                  <Grid item xs={12}>
+                    <Box sx={{ 
+                      display: 'flex', 
+                      flexDirection: 'column', 
+                      alignItems: 'center', 
+                      py: 8,
+                      textAlign: 'center'
+                    }}>
+                      <Collections sx={{ fontSize: 60, color: 'grey.300', mb: 2 }} />
+                      <Typography variant="h6" sx={{ mb: 1 }}>
+                        还没有发布笔记
+                      </Typography>
+                      <Typography variant="body1" color="textSecondary" sx={{ mb: 3 }}>
+                        {isOwnProfile ? '分享你的生活点滴吧~' : '该用户还没有发布任何笔记'}
+                      </Typography>
+                      {isOwnProfile && (
+                        <Button variant="contained" color="primary">
+                          发布第一篇笔记
+                        </Button>
+                      )}
+                    </Box>
                   </Grid>
-                ))
-              )}
-              
-              {/* 分页组件 */}
-              {allPosts.length > postsPerPage && (
-                <Grid item xs={12}>
-                  <Stack spacing={2} alignItems="center" sx={{ mt: 4 }}>
-                    <Typography variant="body2" color="textSecondary">
-                      共 {allPosts.length} 篇笔记,第 {currentPage} 页,共 {totalPages} 页
-                    </Typography>
-                    <Pagination 
-                      count={totalPages}
-                      page={currentPage}
-                      onChange={(event, page) => handlePageChange(page)}
-                      color="primary"
-                      size={isMobile ? "small" : "medium"}
-                      showFirstButton 
-                      showLastButton
-                      sx={{
-                        '& .MuiPaginationItem-root': {
-                          borderRadius: 2,
+                )}
+                
+                {posts.length > 0 && (
+                  <Grid item xs={12}>
+                    <Box sx={{ display: 'flex', justifyContent: 'center', mt: 3 }}>
+                      <Button 
+                        variant="outlined" 
+                        sx={{ 
+                          borderRadius: 20, 
+                          px: 4,
+                          display: 'flex',
+                          alignItems: 'center'
+                        }}
+                      >
+                        <ChevronLeft sx={{ mr: 1 }} />
+                        上一页
+                        <ChevronRight sx={{ ml: 2 }} />
+                      </Button>
+                    </Box>
+                  </Grid>
+                )}
+              </Grid>
+            )}
+            
+            {activeTab === 1 && (
+              <Grid container spacing={3}>
+                {tabLoading ? (
+                  <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
+                    <CircularProgress />
+                  </Grid>
+                ) : favorites.length > 0 ? (
+                  favorites.map((favorite) => (
+                    <Grid item xs={12} sm={6} md={4} lg={3} key={favorite.id}>
+                      <Card elevation={0} sx={{ 
+                        bgcolor: 'white', 
+                        borderRadius: 3,
+                        transition: 'transform 0.3s, box-shadow 0.3s',
+                        '&:hover': {
+                          transform: 'translateY(-5px)',
+                          boxShadow: 3
                         }
-                      }}
-                    />
-                  </Stack>
-                </Grid>
-              )}
-            </Grid>
+                      }}>
+                        <Box sx={{ 
+                          height: 160, 
+                          position: 'relative',
+                          borderTopLeftRadius: 16,
+                          borderTopRightRadius: 16,
+                          overflow: 'hidden'
+                        }}>
+                          <CardMedia
+                            component="img"
+                            height="160"
+                            image={`https://source.unsplash.com/random/400x300?${favorite.id}`}
+                            alt={favorite.title}
+                          />
+                          <Box sx={{
+                            position: 'absolute',
+                            top: 8,
+                            right: 8,
+                            bgcolor: 'rgba(0,0,0,0.6)',
+                            color: 'white',
+                            px: 1,
+                            py: 0.5,
+                            borderRadius: 4,
+                            fontSize: 12
+                          }}>
+                            {favorite.type === 'image' ? '图文' : favorite.type === 'video' ? '视频' : '文档'}
+                          </Box>
+                        </Box>
+                        <CardContent>
+                          <Typography gutterBottom variant="subtitle1" fontWeight="medium">
+                            {favorite.title}
+                          </Typography>
+                          <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
+                            <Box sx={{ display: 'flex', alignItems: 'center' }}>
+                              <Favorite fontSize="small" color="error" />
+                              <Typography variant="body2" sx={{ ml: 0.5 }}>
+                                {favorite.heat || Math.floor(Math.random() * 1000) + 1000}
+                              </Typography>
+                            </Box>
+                            <Box sx={{ display: 'flex', alignItems: 'center' }}>
+                              <Bookmark fontSize="small" color="primary" />
+                              <Typography variant="body2" sx={{ ml: 0.5 }}>
+                                {Math.floor(Math.random() * 500) + 100}
+                              </Typography>
+                            </Box>
+                          </Box>
+                        </CardContent>
+                      </Card>
+                    </Grid>
+                  ))
+                ) : (
+                  <Grid item xs={12}>
+                    <Box sx={{ 
+                      display: 'flex', 
+                      flexDirection: 'column', 
+                      alignItems: 'center', 
+                      py: 8,
+                      textAlign: 'center'
+                    }}>
+                      <Bookmark sx={{ fontSize: 60, color: 'grey.300', mb: 2 }} />
+                      <Typography variant="h6" sx={{ mb: 1 }}>
+                        {isOwnProfile ? '你还没有收藏内容' : '该用户没有收藏内容'}
+                      </Typography>
+                      <Typography variant="body1" color="textSecondary">
+                        {isOwnProfile ? '看到喜欢的笔记可以收藏起来哦~' : ''}
+                      </Typography>
+                    </Box>
+                  </Grid>
+                )}
+              </Grid>
+            )}
+            
+            {activeTab === 2 && (
+  <Grid container spacing={3}>
+    {tabLoading ? (
+      <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
+        <CircularProgress />
+      </Grid>
+    ) : following.length > 0 ? (
+      following.map((follow) => (
+        <Grid item xs={12} sm={6} md={4} key={follow.id}>
+          <Paper 
+            elevation={0} 
+            sx={{ 
+              bgcolor: 'white', 
+              borderRadius: 3,
+              p: 2,
+              display: 'flex',
+              alignItems: 'center',
+              cursor: 'pointer',
+              '&:hover': {
+                boxShadow: 1
+              }
+            }}
+            onClick={() => navigateToUserProfile(follow.id)}
+          >
+            <Avatar 
+              src={follow.avatar || 'https://randomuser.me/api/portraits/men/22.jpg'} 
+              sx={{ width: 60, height: 60 }} 
+            />
+            <Box sx={{ ml: 2, flexGrow: 1 }}>
+              <Typography fontWeight="medium">{follow.username}</Typography>
+              <Typography variant="body2" color="textSecondary">
+                {follow.followers_count || Math.floor(Math.random() * 100) + 10} 粉丝
+              </Typography>
+            </Box>
+            {isOwnProfile && (
+              <Button 
+                variant="outlined" 
+                size="small"
+                sx={{ borderRadius: 20 }}
+                onClick={(e) => {
+                  e.stopPropagation();
+                  handleUnfollow(userId,follow.id);
+                }}
+              >
+                已关注
+              </Button>
+            )}
+          </Paper>
+        </Grid>
+      ))
+    ) : (
+      <Grid item xs={12}>
+        <Box sx={{ 
+          display: 'flex', 
+          flexDirection: 'column', 
+          alignItems: 'center', 
+          py: 8,
+          textAlign: 'center'
+        }}>
+          <Group sx={{ fontSize: 60, color: 'grey.300', mb: 2 }} />
+          <Typography variant="h6" sx={{ mb: 1 }}>
+            {isOwnProfile ? '你还没有关注任何人' : '该用户还没有关注任何人'}
+          </Typography>
+          <Typography variant="body1" color="textSecondary">
+            {isOwnProfile ? '发现有趣的人并关注他们吧~' : ''}
+          </Typography>
+        </Box>
+      </Grid>
+    )}
+  </Grid>
+            )}
+            {activeTab === 3 && (
+              <Grid container spacing={3}>
+                {tabLoading ? (
+                  <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
+                    <CircularProgress />
+                  </Grid>
+                ) : followers.length > 0 ? (
+                  followers.map((follower) => (
+                    <Grid item xs={12} sm={6} md={4} key={follower.id}>
+                      <Paper 
+                        elevation={0} 
+                        sx={{ 
+                          bgcolor: 'white', 
+                          borderRadius: 3,
+                          p: 2,
+                          display: 'flex',
+                          alignItems: 'center',
+                          cursor: 'pointer',
+                          '&:hover': {
+                            boxShadow: 1
+                          }
+                        }}
+                        onClick={() => navigateToUserProfile(follower.id)}
+                      >
+                        <Avatar 
+                          src={follower.avatar || 'https://randomuser.me/api/portraits/men/22.jpg'} 
+                          sx={{ width: 60, height: 60 }} 
+                        />
+                        <Box sx={{ ml: 2, flexGrow: 1 }}>
+                          <Typography fontWeight="medium">{follower.username}</Typography>
+                          <Typography variant="body2" color="textSecondary">
+                            {follower.bio || '暂无简介'}
+                          </Typography>
+                          <Typography variant="body2" color="textSecondary">
+                            {follower.followers_count} 粉丝
+                          </Typography>
+                        </Box>
+                        {currentUser && currentUser.id !== follower.id && (
+                          <Button 
+                            variant={follower.is_following ? "outlined" : "contained"}
+                            color="primary"
+                            size="small"
+                            sx={{ borderRadius: 20 }}
+                            onClick={(e) => {
+                              e.stopPropagation();
+                              if (follower.is_following) {
+                                handleUnfollow(userId,follower.id);
+                              } else {
+                                handleFollowUser(userId,follower.id);
+                              }
+                            }}
+                          >
+                            {follower.is_following ? '已关注' : '关注'}
+                          </Button>
+                        )}
+                      </Paper>
+                    </Grid>
+                  ))
+                ) : (
+                  <Grid item xs={12}>
+                    <Box sx={{ 
+                      display: 'flex', 
+                      flexDirection: 'column', 
+                      alignItems: 'center', 
+                      py: 8,
+                      textAlign: 'center'
+                    }}>
+                      <People sx={{ fontSize: 60, color: 'grey.300', mb: 2 }} />
+                      <Typography variant="h6" sx={{ mb: 1 }}>
+                        {isOwnProfile ? '你还没有粉丝' : '该用户还没有粉丝'}
+                      </Typography>
+                      <Typography variant="body1" color="textSecondary">
+                        {isOwnProfile ? '分享更多内容来吸引粉丝吧~' : ''}
+                      </Typography>
+                    </Box>
+                  </Grid>
+                )}
+              </Grid>
+            )}
           </Box>
         </Container>
 
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);
+  }
+}