f0eed0ca3f
Deploy / Deploy Staging (push) Failing after 192h26m58s
CI/CD Pipeline / Frontend Lint (push) Failing after 192h27m37s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 192h27m46s
Deploy / Production Browser E2E (push) Failing after 1851h1m12s
Deploy / Deploy Production (push) Failing after 1851h1m14s
Deploy / Build Production Runtime Images (push) Failing after 1851h1m17s
247 lines
7.5 KiB
TypeScript
247 lines
7.5 KiB
TypeScript
/**
|
|
* 查重上传页面 — V21 设计系统
|
|
* 左右分栏:拖拽上传区 + 格式说明
|
|
* 零 antd 依赖
|
|
*/
|
|
import React, { useState, useRef, useCallback } from "react";
|
|
import { useMutation } from "@tanstack/react-query";
|
|
import { Button, Card, Tag } from "@/components/ui";
|
|
import { uploadForDuplication } from "@/api/duplication";
|
|
import { useNavigate } from "react-router-dom";
|
|
import "./duplication.css";
|
|
import PageHead from "@/components/layout/PageHead";
|
|
|
|
/** 支持的视频格式 */
|
|
const ACCEPT_FORMATS = ".mp4,.avi,.mov,.mkv,.wmv,.flv,.webm";
|
|
const FORMAT_LIST = ["MP4", "AVI", "MOV", "MKV", "WMV", "FLV", "WebM"];
|
|
/** 最大文件大小:2GB */
|
|
const MAX_FILE_SIZE = 2 * 1024 * 1024 * 1024;
|
|
|
|
/** 简易 toast */
|
|
interface ToastState {
|
|
message: string;
|
|
type: "success" | "error" | "warning";
|
|
}
|
|
|
|
const DuplicationUpload: React.FC = () => {
|
|
const navigate = useNavigate();
|
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
const [dragging, setDragging] = useState(false);
|
|
const [uploading, setUploading] = useState(false);
|
|
const [uploadResult, setUploadResult] = useState<{
|
|
id: string;
|
|
message: string;
|
|
} | null>(null);
|
|
const [toast, setToast] = useState<ToastState | null>(null);
|
|
|
|
/** 显示 toast */
|
|
const showToast = useCallback(
|
|
(message: string, type: "success" | "error" | "warning") => {
|
|
setToast({ message, type });
|
|
setTimeout(() => setToast(null), 3000);
|
|
},
|
|
[],
|
|
);
|
|
|
|
// 上传查重 mutation
|
|
const uploadMutation = useMutation({
|
|
mutationFn: (file: File) => uploadForDuplication(file),
|
|
onSuccess: (data) => {
|
|
setUploading(false);
|
|
setUploadResult({ id: data.id, message: data.message });
|
|
showToast("查重任务已提交", "success");
|
|
},
|
|
onError: () => {
|
|
setUploading(false);
|
|
showToast("上传失败,请重试", "error");
|
|
},
|
|
});
|
|
|
|
/** 校验并上传文件 */
|
|
const handleFile = useCallback(
|
|
(file: File) => {
|
|
if (file.size > MAX_FILE_SIZE) {
|
|
showToast("文件大小不能超过 2GB", "error");
|
|
return;
|
|
}
|
|
const ext = file.name.toLowerCase().split(".").pop();
|
|
const allowedExts = ACCEPT_FORMATS.replace(/\./g, "").split(",");
|
|
if (!allowedExts.includes(ext || "")) {
|
|
showToast(`不支持的文件格式,支持:${FORMAT_LIST.join("、")}`, "error");
|
|
return;
|
|
}
|
|
setUploading(true);
|
|
setUploadResult(null);
|
|
uploadMutation.mutate(file);
|
|
},
|
|
[uploadMutation, showToast],
|
|
);
|
|
|
|
/** 拖拽事件 */
|
|
const handleDragOver = useCallback((e: React.DragEvent) => {
|
|
e.preventDefault();
|
|
setDragging(true);
|
|
}, []);
|
|
|
|
const handleDragLeave = useCallback(() => {
|
|
setDragging(false);
|
|
}, []);
|
|
|
|
const handleDrop = useCallback(
|
|
(e: React.DragEvent) => {
|
|
e.preventDefault();
|
|
setDragging(false);
|
|
const file = e.dataTransfer.files[0];
|
|
if (file) handleFile(file);
|
|
},
|
|
[handleFile],
|
|
);
|
|
|
|
/** 点击选择文件 */
|
|
const handleSelectFile = () => {
|
|
fileInputRef.current?.click();
|
|
};
|
|
|
|
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = e.target.files?.[0];
|
|
if (file) handleFile(file);
|
|
// 重置 input 以便重复选择同一文件
|
|
e.target.value = "";
|
|
};
|
|
|
|
/** 重置状态 */
|
|
const handleReset = () => {
|
|
setUploadResult(null);
|
|
setUploading(false);
|
|
};
|
|
|
|
return (
|
|
<div className="dup-page">
|
|
{/* Toast */}
|
|
{toast && (
|
|
<div className={`dup-toast dup-toast--${toast.type}`}>
|
|
{toast.message}
|
|
</div>
|
|
)}
|
|
|
|
<PageHead
|
|
title="视频查重"
|
|
description="上传视频文件,系统将自动检测与已有素材的重复片段,帮助您避免重复内容。"
|
|
/>
|
|
|
|
<div className="dup-upload-grid">
|
|
{/* 左侧:上传区域 */}
|
|
<Card>
|
|
{/* 拖拽上传区 */}
|
|
<div
|
|
className={`dup-upload-zone ${dragging ? "dragging" : ""} ${uploading ? "disabled" : ""}`}
|
|
onDragOver={handleDragOver}
|
|
onDragLeave={handleDragLeave}
|
|
onDrop={handleDrop}
|
|
onClick={uploading ? undefined : handleSelectFile}
|
|
>
|
|
<div className="dup-upload-icon">{uploading ? "⏳" : "📁"}</div>
|
|
<h3>
|
|
{uploading ? "正在上传并查重..." : "点击或拖拽视频文件到此区域"}
|
|
</h3>
|
|
<p>支持 MP4、AVI、MOV、MKV 等格式,单个文件不超过 2GB</p>
|
|
<div className="dup-upload-formats">
|
|
{FORMAT_LIST.map((fmt) => (
|
|
<Tag key={fmt} variant="info">
|
|
{fmt}
|
|
</Tag>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 隐藏的文件 input */}
|
|
<input
|
|
ref={fileInputRef}
|
|
type="file"
|
|
accept={ACCEPT_FORMATS}
|
|
style={{ display: "none" }}
|
|
onChange={handleFileChange}
|
|
/>
|
|
|
|
{/* 上传按钮 */}
|
|
<div className="dup-upload-actions">
|
|
<Button
|
|
buttonType="primary"
|
|
buttonSize="md"
|
|
onClick={handleSelectFile}
|
|
disabled={uploading}
|
|
>
|
|
📂 选择文件
|
|
</Button>
|
|
</div>
|
|
|
|
{/* 上传进度 */}
|
|
{uploading && (
|
|
<div className="dup-progress">
|
|
<div className="dup-progress-circle">
|
|
<span className="dup-progress-icon">⏳</span>
|
|
<span className="dup-progress-text">查重中...</span>
|
|
</div>
|
|
<p>正在分析视频内容,请稍候...</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* 上传结果 */}
|
|
{uploadResult && !uploading && (
|
|
<div className="dup-result">
|
|
<div className="dup-result-icon">✅</div>
|
|
<h3>查重任务已提交</h3>
|
|
<p>{uploadResult.message}</p>
|
|
<div className="dup-result-actions">
|
|
<Button
|
|
buttonType="primary"
|
|
buttonSize="md"
|
|
onClick={() => navigate("/duplication/results")}
|
|
>
|
|
查看结果
|
|
</Button>
|
|
<Button
|
|
buttonType="secondary"
|
|
buttonSize="md"
|
|
onClick={handleReset}
|
|
>
|
|
继续上传
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</Card>
|
|
|
|
{/* 右侧:格式说明 + 提示 */}
|
|
<div className="dup-info-card">
|
|
<h3>📋 查重说明</h3>
|
|
<ul className="dup-info-list">
|
|
<li>系统会对比您上传的视频与素材库中的已有视频</li>
|
|
<li>查重完成后,可查看重复片段的具体位置</li>
|
|
<li>查重过程通常需要几分钟,取决于视频大小</li>
|
|
<li>高相似度片段建议进行替换或裁剪</li>
|
|
</ul>
|
|
|
|
<h3 style={{ marginTop: 24 }}>🎬 支持格式</h3>
|
|
<div className="dup-format-tags">
|
|
{FORMAT_LIST.map((fmt) => (
|
|
<Tag key={fmt} variant="info">
|
|
{fmt}
|
|
</Tag>
|
|
))}
|
|
</div>
|
|
|
|
<h3 style={{ marginTop: 24 }}>💡 温馨提示</h3>
|
|
<ul className="dup-info-list">
|
|
<li>单个文件不超过 2GB</li>
|
|
<li>视频时长建议不超过 60 分钟</li>
|
|
<li>查重结果可在「查重记录」中随时查看</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default DuplicationUpload;
|