收敛上传弹窗工作台样式

This commit is contained in:
WuYongJie 2026-05-28 18:24:24 +08:00
parent bf80d3d801
commit fbe6e9ffce
3 changed files with 34 additions and 35 deletions

View File

@ -51,6 +51,8 @@ export default function UploadModal({ onClose, onSuccess }: UploadModalProps) {
const modalControlClass =
'w-full px-4 py-2.5 text-sm rounded-lg outline-none transition-colors border bg-white dark:bg-slate-800 text-slate-900 dark:text-white border-slate-300 dark:border-slate-700 focus-visible:ring-2 focus-visible:ring-slate-200 dark:focus-visible:ring-slate-700 focus-visible:border-slate-900 dark:focus-visible:border-slate-500 placeholder:text-slate-400 dark:placeholder:text-slate-500'
const requiredControlClass = 'bg-slate-50 dark:bg-slate-800 border-slate-400 dark:border-slate-600'
const uploadZoneClass = 'border border-dashed border-slate-300 dark:border-slate-600 rounded-lg p-4 transition-colors hover:border-slate-400 dark:hover:border-slate-500 hover:bg-slate-50 dark:hover:bg-slate-800 cursor-pointer'
const uploadTileClass = 'flex-shrink-0 rounded-lg border border-dashed border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 flex items-center justify-center text-slate-500 dark:text-slate-400'
const [previewImages, setPreviewImages] = useState<File[]>([]) // 多预览图(最多5张)
const [previewUrls, setPreviewUrls] = useState<string[]>([]) // 多预览图预览URLs
const [displayImage, setDisplayImage] = useState<File | null>(null) // PSD展示图片
@ -1073,7 +1075,7 @@ const isVideoFile = (file: File) => {
if (xhr.status === 413) {
errorMsg = '文件过大,请联系管理员增加上传限制'
} else if (xhr.status === 408 || xhr.status === 504) {
errorMsg = '上传超时,建议:\n1. 压缩文件后再上传\n2. 使用内网地址(更快)\n3. 分批上传大文件'
errorMsg = '上传超时,请压缩文件、使用内网地址或分批上传后重试'
} else if (xhr.status >= 500) {
errorMsg = `服务器错误 (${xhr.status}):${errorMsg}`
}
@ -1081,7 +1083,7 @@ const isVideoFile = (file: File) => {
setError(errorMsg)
} catch (parseError) {
console.error('[UploadModal] Failed to parse error response:', parseError)
setError(`上传失败 (状态码: ${xhr.status})\n响应内容: ${xhr.responseText.substring(0, 200)}`)
setError(`上传失败,服务器返回异常(状态码:${xhr.status}),请稍后重试或联系管理员`)
}
setUploading(false)
setRetryCount(0)
@ -1101,14 +1103,14 @@ const isVideoFile = (file: File) => {
// 如果上传进度已达到100%,可能实际已成功
if (uploadProgress === 100 && isConnectionReset) {
errorMsg = '上传可能已完成。\n\n由于网络原因,无法确认最终状态。\n\n请执行以下操作:\n1. 刷新页面查看文件是否已上传\n2. 如果文件已存在,说明上传成功\n3. 如果未看到文件,请重新上传\n\n提示:使用内网地址上传更稳定'
errorMsg = '上传结果暂未确认,请刷新素材列表检查是否已经保存'
// 3秒后自动关闭弹窗并刷新
setTimeout(() => {
onClose()
window.location.reload()
}, 5000)
} else if (isConnectionReset) {
errorMsg = '网络连接中断,可能原因:\n• 文件过大导致超时\n• 网络不稳定\n• 服务器响应慢\n\n建议:\n1. 检查文件大小(压缩包建议<1GB)\n2. 使用内网地址上传(更快更稳定)\n3. 如果是大文件,尝试压缩后再上传\n4. 刷新页面检查文件是否已上传成功'
errorMsg = '网络连接中断,上传结果暂未确认。请刷新列表检查;如未出现,请使用内网地址或压缩后重试'
}
setError(errorMsg)
@ -1195,10 +1197,10 @@ const isVideoFile = (file: File) => {
>
{savedUploadData && (
<div className="space-y-4">
<h3 className="text-xl font-bold text-slate-900 dark:text-white">
📎 检测到未完成的上传
<h3 className="text-base font-semibold text-slate-900 dark:text-white">
检测到未完成的上传
</h3>
<div className="space-y-2 rounded-2xl border border-slate-200/70 dark:border-slate-700/70 bg-slate-50 dark:bg-slate-800/40 p-4">
<div className="space-y-2 rounded-lg border border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800/40 p-4">
<p className="text-xs text-slate-600 dark:text-slate-400">
<strong>素材类型:</strong>{savedUploadData.materialType || '未知'}
</p>
@ -1246,8 +1248,7 @@ const isVideoFile = (file: File) => {
>
<div
className={cn(
'rounded-xl max-h-[80vh] shadow-lg flex flex-col relative w-full max-w-2xl mx-auto',
styles.cardStyles.base
'max-h-[80vh] flex flex-col relative w-full max-w-2xl mx-auto rounded-lg border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900'
)}
style={{ overflow: 'hidden' }}
>
@ -1349,7 +1350,7 @@ const isVideoFile = (file: File) => {
</label>
<div
ref={previewDropZoneRef}
className="border border-dashed border-slate-300/60 dark:border-slate-600/60 rounded-2xl p-4 transition-colors hover:border-slate-400/80 dark:hover:border-slate-500/80 hover:bg-slate-50/50 dark:hover:bg-slate-700/30 cursor-pointer"
className={uploadZoneClass}
onDragEnter={handlePreviewDragEnter}
onDragLeave={handlePreviewDragLeave}
onDragOver={handlePreviewDragOver}
@ -1366,19 +1367,19 @@ const isVideoFile = (file: File) => {
/>
{previewImages.length === 0 ? (
<div className="flex flex-wrap items-center gap-4 sm:flex-nowrap">
<div className="w-16 h-16 sm:w-20 sm:h-20 flex-shrink-0 rounded-xl border-2 border-dashed border-slate-300/80 dark:border-slate-600/80 flex items-center justify-center text-slate-500 dark:text-slate-400 bg-white/40 dark:bg-slate-800/40">
<div className={cn(uploadTileClass, 'w-14 h-14 sm:w-16 sm:h-16')}>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
</div>
<div className="text-left flex-1 min-w-[200px] space-y-1">
<p className="text-xs font-medium text-slate-700 dark:text-slate-300">
点击或拖拽上传{isVideoTemplate ? '视频封面(可选,最多5张)' : '预览图(最多5张)'}
上传{isVideoTemplate ? '视频封面(可选,最多5张)' : '预览图(最多5张)'}
</p>
<p className="text-[10px] text-slate-500 dark:text-slate-500">
{isVideoTemplate
? '支持 JPG、PNG、WebP 等格式。如不上传,系统将自动从视频中截取封面图'
: '支持 JPG、PNG、WebP 等格式,可直接拖拽图片到此区域'}
? '支持 JPG、PNG、WebP;不上传时系统会尝试自动截取封面'
: '支持 JPG、PNG、WebP,可拖拽上传'}
</p>
<button
type="button"
@ -1410,10 +1411,10 @@ const isVideoFile = (file: File) => {
className={cn(
'relative group cursor-grab active:cursor-grabbing',
draggingPreviewIndex === index &&
'ring-2 ring-slate-500 rounded-xl'
'ring-2 ring-slate-500 rounded-lg'
)}
>
<div className="relative w-full aspect-square rounded-xl overflow-hidden border-2 border-slate-200 dark:border-slate-600 bg-white dark:bg-slate-800">
<div className="relative w-full aspect-square rounded-lg overflow-hidden border border-slate-200 dark:border-slate-600 bg-white dark:bg-slate-800">
<Image
src={previewUrls[index]}
alt={`Preview ${index + 1}`}
@ -1432,7 +1433,7 @@ const isVideoFile = (file: File) => {
e.stopPropagation()
removePreviewImage(index)
}}
className="absolute top-1 right-1 w-5 h-5 bg-red-500 text-white rounded-full opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center text-xs"
className="absolute top-1 right-1 w-5 h-5 bg-red-500 text-white rounded-md opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center text-xs"
>
×
</button>
@ -1446,7 +1447,7 @@ const isVideoFile = (file: File) => {
e.stopPropagation()
previewInputRef.current?.click()
}}
className="w-full aspect-square rounded-xl border-2 border-dashed border-slate-300/80 dark:border-slate-600/80 flex flex-col items-center justify-center gap-2 text-slate-500 dark:text-slate-400 bg-white/40 dark:bg-slate-800/40 hover:border-blue-400 hover:text-blue-500 dark:hover:border-blue-500 dark:hover:text-blue-400 transition-colors"
className="w-full aspect-square rounded-lg border border-dashed border-slate-300 dark:border-slate-600 flex flex-col items-center justify-center gap-2 text-slate-500 dark:text-slate-400 bg-white dark:bg-slate-800 hover:border-slate-400 hover:text-slate-700 dark:hover:border-slate-500 dark:hover:text-slate-200 transition-colors"
>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
@ -1463,7 +1464,7 @@ const isVideoFile = (file: File) => {
已上传 {previewImages.length}/5 张,第一张自动设为{isVideoTemplate ? '视频封面' : '卡片封面'}
</p>
<p className="text-[10px] text-slate-500 dark:text-slate-500 mt-0.5">
支持 JPG、PNG、WebP 等格式,可直接拖拽图片到此区域
支持 JPG、PNG、WebP,可拖拽调整顺序
</p>
</div>
</>
@ -1481,8 +1482,8 @@ const isVideoFile = (file: File) => {
<div
ref={displayImageDropZoneRef}
className={cn(
'border border-dashed rounded-2xl p-4 transition-colors hover:border-slate-400/80 dark:hover:border-slate-500/80 hover:bg-slate-50/50 dark:hover:bg-slate-700/30 cursor-pointer',
isDisplayImageDragging ? 'border-blue-500 bg-blue-50/50 dark:bg-blue-900/20' : 'border-slate-300/60 dark:border-slate-600/60'
uploadZoneClass,
isDisplayImageDragging ? 'border-slate-500 bg-slate-50 dark:bg-slate-800' : ''
)}
onDragEnter={handleDisplayImageDragEnter}
onDragLeave={handleDisplayImageDragLeave}
@ -1492,12 +1493,12 @@ const isVideoFile = (file: File) => {
>
<div className="flex items-center justify-center space-x-4">
{displayImageUrl ? (
<div className="relative w-20 h-20 md:w-24 md:h-24 rounded-2xl overflow-hidden border-2 border-slate-200 dark:border-slate-600 flex-shrink-0">
<div className="relative w-16 h-16 md:w-20 md:h-20 rounded-lg overflow-hidden border border-slate-200 dark:border-slate-600 flex-shrink-0">
<Image src={displayImageUrl} alt="PSD 展示图" fill unoptimized className="object-cover" />
</div>
) : (
<div className="w-20 h-20 md:w-24 md:h-24 rounded-2xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center flex-shrink-0 shadow-inner">
<svg className="w-10 h-10 text-slate-400 dark:text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div className="w-16 h-16 md:w-20 md:h-20 rounded-lg bg-slate-100 dark:bg-slate-800 flex items-center justify-center flex-shrink-0">
<svg className="w-8 h-8 text-slate-400 dark:text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
</div>
@ -1510,9 +1511,8 @@ const isVideoFile = (file: File) => {
accept="image/*"
className="hidden"
/>
<p className="text-xs font-medium text-slate-700 dark:text-slate-300">点击或拖拽上传 PSD 展示图</p>
<p className="text-[10px] text-slate-500 dark:text-slate-500 mt-0.5">建议上传详情页第一屏,支持 JPG/PNG/WebP</p>
<p className="text-[10px] text-blue-600 dark:text-blue-400 mt-1">第一张图会自动作为预览图,超高图片会自动切割</p>
<p className="text-xs font-medium text-slate-700 dark:text-slate-300">上传 PSD 展示图</p>
<p className="text-[10px] text-slate-500 dark:text-slate-500 mt-0.5">支持 JPG、PNG、WebP;超高图片会自动切割</p>
</div>
</div>
</div>
@ -1534,7 +1534,7 @@ const isVideoFile = (file: File) => {
</label>
<div
ref={dropZoneRef}
className="border border-dashed border-slate-300/60 dark:border-slate-600/60 rounded-2xl p-4 transition-colors hover:border-slate-400/80 dark:hover:border-slate-500/80 hover:bg-slate-50/50 dark:hover:bg-slate-700/30 cursor-pointer"
className={uploadZoneClass}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
onDragOver={handleDragOver}
@ -1572,7 +1572,7 @@ const isVideoFile = (file: File) => {
className="hidden"
/>
<p className="text-xs text-slate-600 dark:text-slate-400">
点击或拖拽上传{isVideoTemplate ? '视频文件' : isAudioTemplate ? '音频文件' : '素材压缩包'}
上传{isVideoTemplate ? '视频文件' : isAudioTemplate ? '音频文件' : '素材压缩包'}
</p>
<p className="text-[10px] text-slate-500 dark:text-slate-500 mt-0.5">
{isVideoTemplate ? '支持 MP4、MOV、AVI、MKV、WebM 等常见视频格式' : isAudioTemplate ? '支持 MP3、WAV、FLAC、AAC、M4A 等常见音频格式' : '支持 ZIP、RAR、7Z 等压缩格式'}
@ -1612,7 +1612,7 @@ const isVideoFile = (file: File) => {
)}
{uploadComplete && (
<div className="text-sm text-green-600 dark:text-green-400 text-center font-medium">
✓ 上传成功,正在关闭...
上传成功,正在关闭...
</div>
)}
</div>

View File

@ -18,7 +18,7 @@ import { useThemeStyles, cn } from '@/lib/ui-styles'
export type ModalSize = 'xs' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge' | 'fullscreen'
const MODAL_BORDER_RADIUS = 32
const MODAL_BORDER_RADIUS = 12
const sizeStyleMap: Record<ModalSize, CSSProperties> = {
xs: { width: 'min(90vw, 20rem)', maxWidth: '20rem' },

View File

@ -1062,10 +1062,9 @@ NAS 私有化系统可能在平板或窄屏浏览器里使用。模板编辑器
证据:
- `components/UploadModal.tsx:50`:已完成第一轮收口,动态模板字段统一使用中性输入框样式。
- `components/UploadModal.tsx:1196`:恢复上传提示已去掉可见 emoji。
- `components/UploadModal.tsx:1233`:上传弹窗内部外壳已从 `rounded-apple`、`shadow-apple-lg` 收为普通圆角和轻阴影。
- `components/UploadModal.tsx:1245`:上传提示已改为普通文字,不再使用 emoji。
- `components/UploadModal.tsx:1499`:PSD 展示图提示已去掉 emoji。
- `components/ui/Modal.tsx`:统一弹窗硬编码圆角已从 32px 收到 12px,避免默认弹窗继续呈现大圆角展示页风格。
- `components/UploadModal.tsx`:已完成第二轮收口,恢复上传提示去掉 emoji,内部外壳去掉额外重阴影和卡片套卡片,上传区从大圆角虚线卡片收为普通 `rounded-lg` dropzone。
- `components/UploadModal.tsx`:预览图、PSD 展示图和素材文件上传说明已压短,不再重复“点击或拖拽/支持格式”长提示;网络失败提示也改为一句明确结果和下一步。
- `components/UploadModal.tsx:1661`、`components/UploadModal.tsx:1812`:必填字段不再使用蓝色渐变背景和大阴影,改为边框/浅底提示。
- `components/EditMaterialModal.tsx:240`:编辑弹窗必填标识同样使用紫蓝渐变胶囊。
- `components/EditMaterialModal.tsx:265`:编辑输入框也有必填渐变背景。