fix: refine angle preview panel styling

This commit is contained in:
Your Name 2026-05-08 16:15:28 +08:00
parent f7e6958315
commit 46b8c23975

View File

@ -468,12 +468,12 @@ function AngleEditPanelSlider({
valueLabel: string;
}) {
return (
<div className="space-y-2.5">
<div className="space-y-3">
<div className="flex items-center justify-between gap-4">
<span className="text-[18px] font-semibold tracking-[0] text-[#202124]">
<span className="text-[18px] font-semibold tracking-[0] text-[#26272b]">
{label}
</span>
<span className="text-[17px] font-medium tabular-nums text-[#8a8a8a]">
<span className="text-[17px] font-medium tabular-nums text-[#7d7f86]">
{valueLabel}
</span>
</div>
@ -482,16 +482,28 @@ function AngleEditPanelSlider({
max={max}
min={min}
onValueChange={([nextValue]) => onChange(nextValue ?? value)}
rangeClassName="bg-[#2f3133]"
rangeClassName="bg-[#2d3035]"
step={step}
thumbClassName="h-5 w-5 border-[5px] border-[#2f3133] bg-white shadow-[0_1px_3px_rgba(0,0,0,0.18)]"
trackClassName="h-[4px] bg-[#e6e6e6]"
thumbClassName="h-[22px] w-[22px] border-[5px] border-[#2d3035] bg-white shadow-[0_2px_10px_rgba(24,24,27,0.14)]"
trackClassName="h-[4px] bg-[#e7e8ec]"
value={[value]}
/>
</div>
);
}
function AngleCameraModelGlyph() {
return (
<div className="absolute left-1/2 top-1/2 h-[26px] w-[26px] -translate-x-1/2 -translate-y-1/2 [transform-style:preserve-3d]">
<div className="absolute left-[3px] top-[8px] h-[13px] w-[18px] rounded-[5px] border border-[#6a707a] bg-[linear-gradient(180deg,#f9fafc,#dce2ea)] shadow-[0_2px_6px_rgba(15,23,42,0.12)]" />
<div className="absolute left-[6px] top-[5px] h-[5px] w-[8px] rounded-[3px] border border-[#6f7680] bg-[linear-gradient(180deg,#ffffff,#e1e6ee)]" />
<div className="absolute left-[11px] top-[11px] h-[7px] w-[7px] rounded-full border border-[#5d636d] bg-[radial-gradient(circle_at_35%_35%,#fefefe_0%,#d7dde6_38%,#8c95a1_65%,#5c6470_100%)]" />
<div className="absolute left-[18px] top-[11px] h-[2px] w-[2px] rounded-full bg-[#69707b]" />
<div className="absolute left-[6px] top-[10px] h-[9px] w-[2px] rounded-full bg-[#c4ccd7]" />
</div>
);
}
const clampNumber = (value: number, min: number, max: number) =>
Math.min(max, Math.max(min, value));
@ -2056,6 +2068,25 @@ export function ImageNode({
z: Number(z.toFixed(2)),
};
}, [angleHorizontalAngle, angleVerticalAngle]);
const angleCameraConnection = useMemo(() => {
const length = Math.sqrt(
angleCameraOrbitPosition.x * angleCameraOrbitPosition.x +
angleCameraOrbitPosition.y * angleCameraOrbitPosition.y
);
const angle =
(Math.atan2(angleCameraOrbitPosition.y, angleCameraOrbitPosition.x) *
180) /
Math.PI;
return {
angle: Number(angle.toFixed(2)),
length: Number(length.toFixed(2)),
};
}, [angleCameraOrbitPosition]);
const angleCameraSceneScale = useMemo(
() => Number((0.9 + (angleZoom / 100) * 0.24).toFixed(3)),
[angleZoom]
);
const cropImageMetrics = useMemo(() => {
if (boxCutoutMetrics) {
return {
@ -8087,7 +8118,7 @@ export function ImageNode({
typeof document !== "undefined" &&
createPortal(
<div
className="fixed z-[9999] w-[372px] rounded-[28px] border border-[#ebe7df] bg-[#fffdfa] p-5 shadow-[0_24px_64px_rgba(17,24,39,0.14)]"
className="fixed z-[9999] w-[372px] rounded-[28px] border border-[#ececef] bg-white p-5 shadow-[0_24px_64px_rgba(15,23,42,0.12)]"
onClick={(event) => event.stopPropagation()}
onMouseDown={(event) => event.stopPropagation()}
onPointerDown={(event) => event.stopPropagation()}
@ -8099,11 +8130,11 @@ export function ImageNode({
}}
>
<div className="flex items-center justify-between">
<h3 className="text-[20px] font-semibold tracking-[0] text-[#18181b]">
多视角
<h3 className="text-[20px] font-semibold tracking-[0] text-[#17181c]">
多角度
</h3>
<button
className="inline-flex h-10 w-10 items-center justify-center rounded-full text-[#2b2d31] transition-colors hover:bg-[#f3f1ec]"
className="inline-flex h-10 w-10 items-center justify-center rounded-full text-[#1f2024] transition-colors hover:bg-[#f5f6f8]"
disabled={isApplyingAngleEdit}
onClick={handleResetAnglePanel}
type="button"
@ -8112,7 +8143,7 @@ export function ImageNode({
</button>
</div>
<div className="mt-5 rounded-[18px] bg-[#f3f1ec] p-1">
<div className="mt-5 rounded-[18px] border border-[#ededf0] bg-[#f5f5f6] p-1">
<div className="grid grid-cols-2 gap-1">
{(
[
@ -8126,8 +8157,8 @@ export function ImageNode({
className={cn(
"h-14 rounded-[16px] text-[16px] font-semibold tracking-[0] transition-all",
active
? "bg-white text-[#1f1f21] shadow-[0_1px_2px_rgba(0,0,0,0.05)]"
: "text-[#757575] hover:text-[#2d2d31]"
? "bg-white text-[#1f2023] shadow-[0_1px_2px_rgba(16,24,40,0.06)]"
: "text-[#767982] hover:text-[#2d3036]"
)}
disabled={isApplyingAngleEdit}
key={item.id}
@ -8141,7 +8172,7 @@ export function ImageNode({
</div>
</div>
<div className="relative mt-6 overflow-hidden rounded-[26px] bg-[#faf8f4] px-4 py-5">
<div className="relative mt-6 overflow-hidden rounded-[26px] border border-[#efeff2] bg-[#fcfcfd] px-4 py-5">
<div
className={cn(
"relative flex h-[410px] items-center justify-center overflow-hidden rounded-[22px] [perspective:1200px]",
@ -8160,59 +8191,76 @@ export function ImageNode({
>
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<svg
className="h-[300px] w-[300px]"
className="h-[312px] w-[312px]"
fill="none"
viewBox="0 0 320 320"
>
<ellipse
cx="160"
cy="160"
rx="116"
ry="116"
stroke="#ebe5dc"
strokeWidth="1.4"
/>
<ellipse
cx="160"
cy="160"
rx="116"
ry="116"
stroke="#efe8de"
rx="118"
ry="118"
stroke="#e6e8ee"
strokeWidth="1.2"
transform="rotate(28 160 160)"
/>
<ellipse
cx="160"
cy="160"
rx="116"
ry="86"
stroke="#efe8de"
strokeWidth="1.2"
transform="rotate(56 160 160)"
/>
<ellipse
cx="160"
cy="160"
rx="116"
ry="76"
stroke="#efe8de"
strokeWidth="1.2"
transform="rotate(92 160 160)"
/>
<ellipse
cx="160"
cy="160"
rx="86"
ry="116"
stroke="#f2ece3"
strokeDasharray="4 6"
rx="118"
ry="118"
stroke="#e8eaf0"
strokeWidth="1.1"
transform="rotate(22 160 160)"
/>
<ellipse
cx="160"
cy="160"
rx="118"
ry="118"
stroke="#e8eaf0"
strokeWidth="1.1"
transform="rotate(44 160 160)"
/>
<ellipse
cx="160"
cy="160"
rx="118"
ry="118"
stroke="#e8eaf0"
strokeWidth="1.1"
transform="rotate(66 160 160)"
/>
<ellipse
cx="160"
cy="160"
rx="118"
ry="94"
stroke="#eceef4"
strokeWidth="1.05"
/>
<ellipse
cx="160"
cy="160"
rx="118"
ry="72"
stroke="#eceef4"
strokeWidth="1.05"
/>
<ellipse
cx="160"
cy="160"
rx="94"
ry="118"
stroke="#eceef4"
strokeDasharray="4 6"
strokeWidth="1.05"
transform="rotate(90 160 160)"
/>
</svg>
</div>
<button
className="absolute top-1 left-1/2 z-[3] inline-flex h-8 w-8 -translate-x-1/2 items-center justify-center rounded-full text-[#a6a6a6] transition-colors hover:bg-white hover:text-[#2f3133]"
className="absolute top-[24px] left-1/2 z-[3] inline-flex h-8 w-8 -translate-x-1/2 items-center justify-center rounded-full text-[#a0a3ab] transition-colors hover:bg-white hover:text-[#30333a]"
disabled={isApplyingAngleEdit}
onClick={() =>
setAngleVerticalAngle((prev) => clampNumber(prev + 5, -30, 60))
@ -8223,7 +8271,7 @@ export function ImageNode({
<ChevronUp className="h-4 w-4" />
</button>
<button
className="absolute bottom-1 left-1/2 z-[3] inline-flex h-8 w-8 -translate-x-1/2 items-center justify-center rounded-full text-[#a6a6a6] transition-colors hover:bg-white hover:text-[#2f3133]"
className="absolute bottom-[24px] left-1/2 z-[3] inline-flex h-8 w-8 -translate-x-1/2 items-center justify-center rounded-full text-[#a0a3ab] transition-colors hover:bg-white hover:text-[#30333a]"
disabled={isApplyingAngleEdit}
onClick={() =>
setAngleVerticalAngle((prev) => clampNumber(prev - 5, -30, 60))
@ -8234,7 +8282,7 @@ export function ImageNode({
<ChevronDown className="h-4 w-4" />
</button>
<button
className="absolute top-1/2 left-2 z-[3] inline-flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-full text-[#a6a6a6] transition-colors hover:bg-white hover:text-[#2f3133]"
className="absolute top-1/2 left-[18px] z-[3] inline-flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-full text-[#a0a3ab] transition-colors hover:bg-white hover:text-[#30333a]"
disabled={isApplyingAngleEdit}
onClick={() =>
setAngleHorizontalAngle((prev) =>
@ -8247,7 +8295,7 @@ export function ImageNode({
<ChevronLeft className="h-4 w-4" />
</button>
<button
className="absolute top-1/2 right-2 z-[3] inline-flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-full text-[#a6a6a6] transition-colors hover:bg-white hover:text-[#2f3133]"
className="absolute top-1/2 right-[18px] z-[3] inline-flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-full text-[#a0a3ab] transition-colors hover:bg-white hover:text-[#30333a]"
disabled={isApplyingAngleEdit}
onClick={() =>
setAngleHorizontalAngle((prev) =>
@ -8280,12 +8328,12 @@ export function ImageNode({
}}
/>
<div
className="absolute inset-0 rounded-[26px] border border-white/90 bg-white shadow-[0_16px_34px_rgba(15,23,42,0.14)]"
className="absolute inset-0 rounded-[26px] border border-[#eef0f4] bg-white shadow-[0_16px_34px_rgba(15,23,42,0.08)]"
style={{
transform: `translateZ(${anglePreviewDepth / 2}px)`,
}}
>
<div className="absolute inset-[6px] overflow-hidden rounded-[20px] bg-[#fffdfa]">
<div className="absolute inset-[6px] overflow-hidden rounded-[20px] bg-[#f9fafc]">
<img
alt={displayTitle}
className="h-full w-full object-contain"
@ -8295,13 +8343,13 @@ export function ImageNode({
</div>
</div>
<div
className="absolute inset-0 rounded-[26px] border border-[#ebe2d8] bg-[linear-gradient(180deg,#f6efe5,#eadfce)]"
className="absolute inset-0 rounded-[26px] border border-[#e5e8ef] bg-[linear-gradient(180deg,#f7f8fb,#e6e9f0)]"
style={{
transform: `translateZ(-${anglePreviewDepth / 2}px)`,
}}
/>
<div
className="absolute left-0 top-[10px] bottom-[10px] rounded-l-[18px] border border-[#e6dccc] bg-[linear-gradient(180deg,#f3ebdf,#dccfbc)]"
className="absolute left-0 top-[10px] bottom-[10px] rounded-l-[18px] border border-[#dde1e9] bg-[linear-gradient(180deg,#eff2f7,#dde2ea)]"
style={{
width: `${anglePreviewDepth}px`,
transform: `translateX(-${anglePreviewDepth / 2}px) rotateY(-90deg)`,
@ -8309,7 +8357,7 @@ export function ImageNode({
}}
/>
<div
className="absolute right-0 top-[10px] bottom-[10px] rounded-r-[18px] border border-[#e6dccc] bg-[linear-gradient(180deg,#f2e9dd,#dbceb9)]"
className="absolute right-0 top-[10px] bottom-[10px] rounded-r-[18px] border border-[#dde1e9] bg-[linear-gradient(180deg,#eef1f6,#dde2ea)]"
style={{
width: `${anglePreviewDepth}px`,
transform: `translateX(${anglePreviewDepth / 2}px) rotateY(90deg)`,
@ -8317,7 +8365,7 @@ export function ImageNode({
}}
/>
<div
className="absolute left-[10px] right-[10px] top-0 rounded-t-[16px] border border-white/90 bg-[linear-gradient(180deg,#fffefe,#ece1d0)]"
className="absolute left-[10px] right-[10px] top-0 rounded-t-[16px] border border-[#eef0f4] bg-[linear-gradient(180deg,#ffffff,#edf0f5)]"
style={{
height: `${anglePreviewDepth}px`,
transform: `translateY(-${anglePreviewDepth / 2}px) rotateX(90deg)`,
@ -8325,7 +8373,7 @@ export function ImageNode({
}}
/>
<div
className="absolute left-[10px] right-[10px] bottom-0 rounded-b-[16px] border border-[#e9dfd1] bg-[linear-gradient(180deg,#e2d6c5,#d3c5b0)]"
className="absolute left-[10px] right-[10px] bottom-0 rounded-b-[16px] border border-[#dde1ea] bg-[linear-gradient(180deg,#e4e8ef,#d5dbe5)]"
style={{
height: `${anglePreviewDepth}px`,
transform: `translateY(${anglePreviewDepth / 2}px) rotateX(-90deg)`,
@ -8334,23 +8382,34 @@ export function ImageNode({
/>
</div>
) : (
<>
<div
className="relative z-[2] h-[320px] w-[320px]"
style={{
transform: `scale(${angleCameraSceneScale})`,
transition: isAnglePreviewDragging
? "none"
: "transform 180ms cubic-bezier(0.22, 1, 0.36, 1)",
}}
>
<div
className="relative z-[2] overflow-hidden rounded-[24px] border border-white/90 bg-white p-[6px] shadow-[0_16px_34px_rgba(15,23,42,0.12)]"
className="absolute left-1/2 top-1/2 z-[2] h-[90px] w-[90px] -translate-x-1/2 -translate-y-1/2 overflow-hidden rounded-[18px] border border-[#eceff4] bg-white/96 shadow-[0_12px_28px_rgba(15,23,42,0.08)]"
style={{
width: `${anglePreviewFrameSize.width}px`,
height: `${anglePreviewFrameSize.height}px`,
transform: `translateZ(${10 + angleZoom * 0.14}px) rotateX(${-angleVerticalAngle * 0.08}deg) rotateY(${angleHorizontalAngle * 0.18}deg) scale(${anglePreviewScale})`,
transformStyle: "preserve-3d",
transition: isAnglePreviewDragging
? "none"
: "transform 180ms cubic-bezier(0.22, 1, 0.36, 1)",
backdropFilter: "blur(8px)",
}}
>
<div className="h-full w-full overflow-hidden rounded-[18px] bg-[#fffdfa]">
<div
className="absolute inset-[8px] rounded-[14px] border border-[#eef1f5]"
style={{
backgroundImage:
"linear-gradient(180deg,#fcfcfd,#f5f6f8), linear-gradient(90deg,rgba(227,230,236,0.55) 1px,transparent 1px), linear-gradient(rgba(227,230,236,0.55) 1px,transparent 1px)",
backgroundPosition: "0 0, 0 0, 0 0",
backgroundSize: "100% 100%, 14px 14px, 14px 14px",
backgroundBlendMode: "normal",
}}
>
<img
alt={displayTitle}
className="h-full w-full object-contain"
className="absolute left-1/2 top-1/2 max-h-[58px] max-w-[58px] -translate-x-1/2 -translate-y-1/2 object-contain"
draggable={false}
src={anglePreviewImageSrc}
/>
@ -8358,7 +8417,16 @@ export function ImageNode({
</div>
<div
className="absolute z-[3]"
className="absolute left-1/2 top-1/2 z-[1] h-px bg-[#dfe4ec]"
style={{
width: `${Math.max(32, angleCameraConnection.length - 18)}px`,
transform: `translateY(-0.5px) rotate(${angleCameraConnection.angle}deg)`,
transformOrigin: "0 50%",
}}
/>
<div
className="absolute left-1/2 top-1/2 z-[3]"
style={{
transform: `translate3d(${angleCameraOrbitPosition.x}px, ${angleCameraOrbitPosition.y}px, ${angleCameraOrbitPosition.z}px)`,
transformStyle: "preserve-3d",
@ -8370,50 +8438,47 @@ export function ImageNode({
<div
className="relative"
style={{
width: "54px",
height: "54px",
transform: `rotateX(${12 - angleVerticalAngle * 0.12}deg) rotateY(${angleHorizontalAngle + 26}deg)`,
width: "56px",
height: "56px",
transform:
"translate(-50%, -50%) rotateX(-18deg) rotateY(28deg)",
transformStyle: "preserve-3d",
}}
>
<div
className="absolute inset-0 overflow-hidden rounded-[16px] border border-white/90 bg-white shadow-[0_10px_24px_rgba(15,23,42,0.12)]"
className="absolute inset-0 rounded-[15px] border border-[#e5e9f0] bg-[linear-gradient(180deg,#ffffff,#edf1f5)] shadow-[0_12px_24px_rgba(15,23,42,0.12)]"
style={{ transform: "translateZ(10px)" }}
>
<img
alt=""
className="h-full w-full object-cover"
draggable={false}
src={anglePreviewImageSrc}
/>
<span className="absolute right-[6px] bottom-[6px] h-[9px] w-[9px] rounded-full border border-white/90 bg-[#2f3136]/85" />
<div className="absolute inset-[10px] rounded-[10px] border border-[#edf1f6] bg-[linear-gradient(180deg,#f9fafc,#eef1f5)]">
<AngleCameraModelGlyph />
</div>
</div>
<div
className="absolute inset-y-[4px] right-0 rounded-r-[12px] border border-[#e6dccf] bg-[linear-gradient(180deg,#f6f0e8,#e4d9ca)]"
className="absolute inset-y-[5px] -right-[11px] rounded-r-[12px] border border-[#dde2ea] bg-[linear-gradient(180deg,#eef2f7,#dfe4ec)]"
style={{
width: "20px",
transform: "translateX(10px) rotateY(90deg)",
width: "22px",
transform: "rotateY(90deg)",
transformOrigin: "left center",
}}
/>
<div
className="absolute inset-x-[4px] top-0 rounded-t-[12px] border border-white/90 bg-[linear-gradient(180deg,#ffffff,#ece2d6)]"
className="absolute inset-x-[5px] -top-[11px] rounded-t-[12px] border border-[#e8ecf2] bg-[linear-gradient(180deg,#ffffff,#edf1f6)]"
style={{
height: "20px",
transform: "translateY(-10px) rotateX(90deg)",
height: "22px",
transform: "rotateX(90deg)",
transformOrigin: "center bottom",
}}
/>
</div>
</div>
</>
</div>
)}
<div className="pointer-events-none absolute bottom-[54px] left-[34px] z-[3] flex h-[48px] w-[48px] items-center justify-center rounded-[16px] border border-[#ece7df] bg-white shadow-[0_10px_24px_rgba(15,23,42,0.08)]">
{anglePanelMode === "subject" ? (
<Box className="h-5 w-5 text-[#949494]" />
) : (
<Camera className="h-5 w-5 text-[#949494]" />
)}
</div>
{anglePanelMode === "subject" ? (
<div className="pointer-events-none absolute bottom-[54px] left-[34px] z-[3] flex h-[48px] w-[48px] items-center justify-center rounded-[16px] border border-[#eceef3] bg-white shadow-[0_10px_24px_rgba(15,23,42,0.07)]">
<Box className="h-5 w-5 text-[#8a8f98]" />
</div>
) : null}
</div>
</div>
@ -8446,7 +8511,7 @@ export function ImageNode({
<div className="mt-7 grid grid-cols-2 gap-3">
<button
className="inline-flex h-14 items-center justify-center rounded-[18px] border border-[#e3ddd3] bg-white text-[18px] font-medium text-[#26262a] transition-colors hover:bg-[#faf7f1]"
className="inline-flex h-14 items-center justify-center rounded-[18px] border border-[#dfe2e8] bg-white text-[18px] font-medium text-[#27292f] transition-colors hover:bg-[#f7f8fa]"
disabled={isApplyingAngleEdit}
onClick={() => setAnglePanelOpen(false)}
type="button"
@ -8454,7 +8519,7 @@ export function ImageNode({
取消
</button>
<button
className="inline-flex h-14 items-center justify-center gap-2 rounded-[18px] bg-[#2c2c2f] text-[18px] font-semibold text-white transition-colors hover:bg-[#202023] disabled:cursor-not-allowed disabled:bg-[#c9c6bf] disabled:text-white/80"
className="inline-flex h-14 items-center justify-center gap-2 rounded-[18px] bg-[#2d2f34] text-[18px] font-semibold text-white transition-colors hover:bg-[#23252a] disabled:cursor-not-allowed disabled:bg-[#cfd2d8] disabled:text-white/80"
disabled={isApplyingAngleEdit}
onClick={() => void handleApplyAngleEdit()}
type="button"