Revert "11"
Revert submission 1443
Reason for revert: <合并错误>
Reverted changes: /q/submissionid:1443
Change-Id: Ifc281ddf07de4b2686e270d68d1a5144e8d1aac4
diff --git a/Merge/back_ljc/app.py b/Merge/back_ljc/app.py
index 0e10b68..f672fc3 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.user_id==user_id,
+ Behavior.post.has(user_id=user_id),
Behavior.type == 'like'
).scalar() or 0
# 计算用户的收藏总数(所有帖子的收藏数)
favorite_count = db.session.query(db.func.sum(Behavior.value)).filter(
- Behavior.user_id==user_id,
+ Behavior.post.has(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 f508f8f..bd938d3 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 0200846..6a9bf8c 100644
--- a/Merge/back_wzy/config.py
+++ b/Merge/back_wzy/config.py
@@ -9,6 +9,4 @@
'SQLURL'
)
SQLALCHEMY_TRACK_MODIFICATIONS = False
- SQLURL = os.getenv('SQLURL')
- # 文件上传配置
- MAX_CONTENT_LENGTH = 2 * 1024 * 1024 * 1024 # 2GB,支持视频上传
\ No newline at end of file
+ SQLURL = os.getenv('SQLURL')
\ No newline at end of file
diff --git a/Merge/back_wzy/utils/Fpost.py b/Merge/back_wzy/utils/Fpost.py
index 2ed295f..2ffffbc 100644
--- a/Merge/back_wzy/utils/Fpost.py
+++ b/Merge/back_wzy/utils/Fpost.py
@@ -177,39 +177,19 @@
"""
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].lower()
-
- # 验证文件类型
- if file_extension not in ALLOWED_EXTENSIONS:
- raise Exception(f"不支持的文件类型: {file_extension}")
-
+ file_extension = os.path.splitext(original_filename)[1]
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 75ed484..c4b0fad 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 0f3c0fe..82e027a 100644
--- a/Merge/front/src/components/HomeFeed.jsx
+++ b/Merge/front/src/components/HomeFeed.jsx
@@ -8,7 +8,6 @@
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 = [
@@ -60,10 +59,9 @@
title: d.title,
author: `作者 ${d.user_id}`,
avatar: `https://i.pravatar.cc/40?img=${d.user_id}`,
- media: d.media_urls?.[0] || '', // 改为 media,支持图片和视频
+ img: d.media_urls?.[0] || '',
likes: d.heat,
- content: d.content || '',
- mediaUrls: d.media_urls || [] // 保存所有媒体URL
+ content: d.content || ''
}
} catch {
return {
@@ -71,10 +69,9 @@
title: item.title,
author: item.author || '佚名',
avatar: `https://i.pravatar.cc/40?img=${item.id}`,
- media: item.img || '',
+ img: item.img || '',
likes: item.heat || 0,
- content: item.content || '',
- mediaUrls: []
+ content: item.content || ''
}
}
})
@@ -104,10 +101,9 @@
title: d.title,
author: `作者 ${d.user_id}`,
avatar: `https://i.pravatar.cc/40?img=${d.user_id}`,
- media: d.media_urls?.[0] || '', // 改为 media,支持图片和视频
+ img: d.media_urls?.[0] || '',
likes: d.heat,
- content: d.content || '',
- mediaUrls: d.media_urls || [] // 保存所有媒体URL
+ content: d.content || ''
}
} catch {
// 拉详情失败时兜底
@@ -116,10 +112,9 @@
title: item.title,
author: item.author || '佚名',
avatar: `https://i.pravatar.cc/40?img=${item.id}`,
- media: item.img || '',
+ img: item.img || '',
likes: item.heat || 0,
- content: item.content || '',
- mediaUrls: []
+ content: item.content || ''
}
}
})
@@ -149,10 +144,9 @@
title: d.title,
author: `作者 ${d.user_id}`,
avatar: `https://i.pravatar.cc/40?img=${d.user_id}`,
- media: d.media_urls?.[0] || '', // 改为 media,支持图片和视频
+ img: d.media_urls?.[0] || '',
likes: d.heat,
- content: d.content || '',
- mediaUrls: d.media_urls || [] // 保存所有媒体URL
+ content: d.content || ''
}
} catch {
// 拉详情失败时兜底
@@ -161,10 +155,9 @@
title: item.title,
author: item.author || '佚名',
avatar: `https://i.pravatar.cc/40?img=${item.id}`,
- media: item.img || '',
+ img: item.img || '',
likes: item.heat || 0,
- content: item.content || '',
- mediaUrls: []
+ content: item.content || ''
}
}
})
@@ -194,10 +187,9 @@
title: d.title,
author: `作者 ${d.user_id}`,
avatar: `https://i.pravatar.cc/40?img=${d.user_id}`,
- media: d.media_urls?.[0] || '', // 改为 media,支持图片和视频
+ img: d.media_urls?.[0] || '',
likes: d.heat,
- content: d.content || '',
- mediaUrls: d.media_urls || [] // 保存所有媒体URL
+ content: d.content || ''
}
} catch {
return {
@@ -205,10 +197,9 @@
title: item.title,
author: item.author || '佚名',
avatar: `https://i.pravatar.cc/40?img=${item.id}`,
- media: item.img || '',
+ img: item.img || '',
likes: item.heat || 0,
- content: item.content || '',
- mediaUrls: []
+ content: item.content || ''
}
}
})
@@ -269,11 +260,9 @@
id: d.id,
title: d.title,
author: `作者 ${d.user_id}`,
- 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
+ // avatar: `https://i.pravatar.cc/40?img=${d.user_id}`,
+ img: d.media_urls?.[0] || '', // 用第一张媒体作为封面
+ likes: d.heat
}
})
)
@@ -315,8 +304,6 @@
}
}
- const [previewImg, setPreviewImg] = useState(null)
-
const handlePostClick = (postId) => {
navigate(`/post/${postId}`)
}
@@ -444,20 +431,7 @@
) : (
items.map(item => (
<div key={item.id} className="feed-card" onClick={() => handlePostClick(item.id)}>
- {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' }}
- />
- )}
+ {item.img && <img className="card-img" src={item.img} alt={item.title} />}
<h3 className="card-title">{item.title}</h3>
{item.content && <div className="card-content">{item.content.slice(0, 60) || ''}</div>}
<div className="card-footer">
@@ -475,37 +449,6 @@
)}
</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
deleted file mode 100644
index 3f0c6e2..0000000
--- a/Merge/front/src/components/MediaPreview.jsx
+++ /dev/null
@@ -1,119 +0,0 @@
-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 01f64b5..d598a72 100644
--- a/Merge/front/src/components/PostDetailJWLLL.jsx
+++ b/Merge/front/src/components/PostDetailJWLLL.jsx
@@ -5,7 +5,6 @@
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'
@@ -315,24 +314,11 @@
</div>
)}
- {/* 帖子媒体(支持多图/多视频) */}
+ {/* 帖子图片(支持多图) */}
{Array.isArray(post.media_urls) && post.media_urls.length > 0 && (
- <div className="post-media" style={{display:'flex',gap:8,marginBottom:16,flexWrap:'wrap'}}>
+ <div className="post-images" style={{display:'flex',gap:8,marginBottom:16}}>
{post.media_urls.map((url, idx) => (
- <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}
- />
+ <img key={idx} src={url} alt={`图片${idx+1}`} style={{maxWidth:220,maxHeight:220,borderRadius:8,objectFit:'cover',cursor:'pointer'}} onClick={() => setPreviewImg(url)} />
))}
</div>
)}
diff --git a/Merge/front/src/components/UploadPage.jsx b/Merge/front/src/components/UploadPage.jsx
index 405aeb8..817a210 100644
--- a/Merge/front/src/components/UploadPage.jsx
+++ b/Merge/front/src/components/UploadPage.jsx
@@ -170,17 +170,6 @@
<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 802c4b6..c957473 100644
--- a/Merge/front/src/components/UserProfile.jsx
+++ b/Merge/front/src/components/UserProfile.jsx
@@ -227,7 +227,7 @@
setLoading(true);
// 获取当前登录用户
- const currentUserRes = await getUser(userId);
+ const currentUserRes = await getCurrentUser();
setCurrentUser(currentUserRes.data);
// 获取目标用户信息
@@ -441,7 +441,6 @@
);
}
- 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
deleted file mode 100644
index 9183c11..0000000
--- a/Merge/front/src/components/VideoPreview.jsx
+++ /dev/null
@@ -1,199 +0,0 @@
-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 70a9aea..138b0c1 100644
--- a/Merge/front/src/style/UploadPage.css
+++ b/Merge/front/src/style/UploadPage.css
@@ -68,216 +68,3 @@
.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);
- }
-}