feat: 多页面拖拽上传、滚动条精简、UI优化

- 剧本评测/分辨率提升/数字人/角色迁移/图片工作台/去水印/电商:新增外部拖拽文件上传
- 电商:爆款图复刻上传框支持拖拽+大滚动条,短视频/模特图/详情图滚动条精简回退
- 图片工作台:右侧输出面板移至左侧提示词上方,删除局部重绘遮罩/结果框
- 数字人:生成按钮改为「开始生成」
- 局部重绘:编辑遮罩→编辑页面
- 对话框生成器:新增对话/视频模式、模型/速度/深度选择按钮
- 视频时长默认改为5秒
- 工具箱页面空状态logo统一绿底亮色图标
- 多处CSS滚动条和布局优化
This commit is contained in:
OmniAI Developer
2026-06-05 18:01:55 +08:00
parent 8fbb2ec95e
commit 5b87594e36
22 changed files with 1796 additions and 195 deletions
@@ -16,7 +16,7 @@ import {
ThunderboltOutlined,
VideoCameraOutlined,
} from "@ant-design/icons";
import { useCallback, useEffect, useRef, useState } from "react";
import { useCallback, useEffect, useRef, useState, type DragEvent } from "react";
import StudioToolLayout from "../../components/StudioToolLayout";
import type { WebImageWorkbenchTool, WebViewKey } from "../../types";
import { aiGenerationClient } from "../../api/aiGenerationClient";
@@ -58,6 +58,7 @@ function CharacterMixPage({
const [resultUrl, setResultUrl] = useState<string | null>(null);
const abortRef = useRef(false);
const taskIdRef = useRef<string | null>(null);
const [isDragging, setIsDragging] = useState(false);
useEffect(() => {
return () => {
@@ -233,6 +234,32 @@ function CharacterMixPage({
}
};
const handleDragOver = (e: DragEvent) => { e.preventDefault(); if (e.dataTransfer?.types?.includes("Files")) setIsDragging(true); };
const handleDragLeave = (e: DragEvent) => { e.preventDefault(); if (!e.currentTarget.contains(e.relatedTarget as Node)) setIsDragging(false); };
const handleDrop = (e: DragEvent) => {
e.preventDefault();
setIsDragging(false);
const file = e.dataTransfer?.files?.[0];
if (!file) return;
if (file.type.startsWith("image/")) {
if (characterPreview) URL.revokeObjectURL(characterPreview);
setCharacterFile(file.name);
setCharacterPreview(URL.createObjectURL(file));
const reader = new FileReader();
reader.onload = () => { if (typeof reader.result === "string") setCharacterDataUrl(reader.result); };
reader.readAsDataURL(file);
setNotice(`已选择人物图 ${file.name}`);
} else if (file.type.startsWith("video/")) {
if (videoPreview) URL.revokeObjectURL(videoPreview);
setVideoFile(file.name);
setVideoPreview(URL.createObjectURL(file));
const reader2 = new FileReader();
reader2.onload = () => { if (typeof reader2.result === "string") setVideoDataUrl(reader2.result); };
reader2.readAsDataURL(file);
setNotice(`已选择参考视频 ${file.name}`);
}
};
return (
<section className="image-workbench-page character-mix-page" aria-label="角色迁移">
<header className="image-workbench-topbar">
@@ -292,7 +319,17 @@ function CharacterMixPage({
<StudioToolLayout
noTop
leftPanel={
<>
<div
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
style={{ position: "relative" }}
>
{isDragging ? (
<div style={{ position: "absolute", inset: 0, zIndex: 100, display: "flex", alignItems: "center", justifyContent: "center", background: "rgba(0,0,0,0.55)", border: "2px dashed var(--primary, #4a9eff)", borderRadius: 12, pointerEvents: "none" }}>
<span style={{ fontSize: 18, color: "#fff", fontWeight: 600 }}></span>
</div>
) : null}
<div className="studio-panel__section">
<div className="studio-panel__section-head">
<span className="studio-panel__section-title"></span>
@@ -370,7 +407,7 @@ function CharacterMixPage({
</label>
</div>
</div>
</>
</div>
}
canvas={
isCreating ? (