优化全能创作

This commit is contained in:
Azmat@qq.com
2026-09-11 19:11:27 +08:00
parent dbcac8a44b
commit 02e5dbfb60
25 changed files with 2398 additions and 462 deletions
+96 -44
View File
@@ -1,4 +1,5 @@
import { useEffect, useMemo, useRef, useState } from "react";
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { ChevronDown, SlidersHorizontal, Sparkles } from "lucide-react";
import { CustomSelect } from "./custom-select";
import { modelDurations, modelResolutions } from "./free-create/constants";
@@ -102,8 +103,10 @@ export function OmniParamBar({
onDuration: (value: string) => void;
}) {
const [menuOpen, setMenuOpen] = useState(false);
const [menuPos, setMenuPos] = useState<{ top?: number; bottom?: number; left: number; width: number } | null>(null);
const [customOn, setCustomOn] = useState(isVideo ? duration !== "智能时长" : true);
const wrapRef = useRef<HTMLDivElement>(null);
const menuRef = useRef<HTMLDivElement>(null);
const capability = isVideo ? "video" as const : "image" as const;
const selected = useMemo(
@@ -123,13 +126,45 @@ export function OmniParamBar({
setCustomOn(isVideo ? duration !== "智能时长" : true);
}, [isVideo, duration]);
function placeMenu() {
const el = wrapRef.current;
if (!el) return;
const rect = el.getBoundingClientRect();
const gutter = 8;
const width = Math.min(330, window.innerWidth - gutter * 2);
const spaceBelow = window.innerHeight - rect.bottom - gutter;
const spaceAbove = rect.top - gutter;
const up = spaceBelow < 280 && spaceAbove > spaceBelow;
let left = rect.right - width;
left = Math.min(Math.max(gutter, left), window.innerWidth - width - gutter);
if (up) setMenuPos({ bottom: window.innerHeight - rect.top + 8, left, width });
else setMenuPos({ top: rect.bottom + 8, left, width });
}
useLayoutEffect(() => {
if (!menuOpen) {
setMenuPos(null);
return;
}
placeMenu();
}, [menuOpen, customOn]);
useEffect(() => {
if (!menuOpen) return;
const onDown = (event: MouseEvent) => {
if (!wrapRef.current?.contains(event.target as Node)) setMenuOpen(false);
const target = event.target as Node;
if (wrapRef.current?.contains(target) || menuRef.current?.contains(target)) return;
setMenuOpen(false);
};
const onReposition = () => placeMenu();
document.addEventListener("mousedown", onDown);
return () => document.removeEventListener("mousedown", onDown);
window.addEventListener("resize", onReposition);
window.addEventListener("scroll", onReposition, true);
return () => {
document.removeEventListener("mousedown", onDown);
window.removeEventListener("resize", onReposition);
window.removeEventListener("scroll", onReposition, true);
};
}, [menuOpen]);
useEffect(() => {
@@ -191,49 +226,66 @@ export function OmniParamBar({
<span>{duration}</span>
<ChevronDown />
</button>
<div className="omni-duration-menu" hidden={!menuOpen}>
<span className="omni-duration-title">{isVideo ? "时长" : "生成张数"}</span>
{isVideo ? (
<div className="omni-duration-modes">
<button
type="button"
className={!customOn ? "active" : ""}
onClick={() => {
setCustomOn(false);
onDuration("智能时长");
setMenuOpen(false);
{menuOpen && menuPos
? createPortal(
<div
ref={menuRef}
className="omni-duration-menu is-portal"
style={{
position: "fixed",
top: menuPos.top,
bottom: menuPos.bottom,
left: menuPos.left,
width: menuPos.width,
right: "auto",
zIndex: 10000,
}}
>
<Sparkles />
<span></span>
</button>
<button
type="button"
className={customOn ? "active" : ""}
onClick={() => setCustomOn(true)}
>
<SlidersHorizontal />
<span></span>
</button>
</div>
) : null}
<div className="omni-duration-values" hidden={isVideo && !customOn}>
{(isVideo ? durations.filter((value) => value !== "智能时长") : durations).map((value) => (
<button
type="button"
key={value}
className={duration === value ? "active" : ""}
onClick={() => {
onDuration(value);
setCustomOn(true);
setMenuOpen(false);
}}
>
{value}
</button>
))}
</div>
</div>
<span className="omni-duration-title">{isVideo ? "时长" : "生成张数"}</span>
{isVideo ? (
<div className="omni-duration-modes">
<button
type="button"
className={!customOn ? "active" : ""}
onClick={() => {
setCustomOn(false);
onDuration("智能时长");
setMenuOpen(false);
}}
>
<Sparkles />
<span></span>
</button>
<button
type="button"
className={customOn ? "active" : ""}
onClick={() => setCustomOn(true)}
>
<SlidersHorizontal />
<span></span>
</button>
</div>
) : null}
<div className="omni-duration-values" hidden={isVideo && !customOn}>
{(isVideo ? durations.filter((value) => value !== "智能时长") : durations).map((value) => (
<button
type="button"
key={value}
className={duration === value ? "active" : ""}
onClick={() => {
onDuration(value);
setCustomOn(true);
setMenuOpen(false);
}}
>
{value}
</button>
))}
</div>
</div>,
document.body,
)
: null}
</div>
</>
);