优化图片编辑器界面:全屏显示和工具栏替换
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

- 图片编辑器改为全屏显示,提供更大的操作空间
- 尺寸调整模式下,将图片标注工具栏替换为尺寸调整工具栏
- 移除画布区域的控制面板,为画布预留更多空间
- 优化界面布局,使操作更流畅
This commit is contained in:
Your Name 2025-12-29 23:27:21 +08:00
parent bbf9768bc9
commit aab3b1e8ed

View File

@ -2,7 +2,7 @@
import React, { useRef, useState, useEffect, useCallback } from "react";
import { createPortal } from "react-dom";
import { X, Pencil, Circle, ArrowRight, Type, Check, Undo, Trash2, Scissors, Sparkles, Download, Expand, Move, Maximize2 } from "lucide-react";
import { X, Pencil, Circle, ArrowRight, Type, Check, Undo, Trash2, Scissors, Sparkles, Download, Expand, Move, Maximize2, ZoomIn, ZoomOut } from "lucide-react";
import { Button } from "./ui/button";
import { Input } from "./ui/input";
import { toast } from "sonner";
@ -539,6 +539,18 @@ export function ImageAnnotator({ imageUrl, onConfirm, onCancel }: ImageAnnotator
setDragStart(null);
};
// 尺寸调整:放大图片
const handleResizeZoomIn = () => {
if (!isResizeMode) return;
setResizeImageScale((prev) => Math.min(prev * 1.2, 5)); // 最大5倍
};
// 尺寸调整:缩小图片
const handleResizeZoomOut = () => {
if (!isResizeMode) return;
setResizeImageScale((prev) => Math.max(prev / 1.2, 0.1)); // 最小0.1倍
};
// 尺寸调整:处理图片缩放(鼠标滚轮)
const handleResizeImageWheel = (e: React.WheelEvent) => {
if (!isResizeMode || !image) return;
@ -883,18 +895,12 @@ export function ImageAnnotator({ imageUrl, onConfirm, onCancel }: ImageAnnotator
// 弹窗内容
const modalContent = (
<div
className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/70 backdrop-blur-md p-4"
className="fixed inset-0 z-[9999] flex items-center justify-center bg-background"
onClick={onCancel}
>
{/* 弹窗容器 - 使用与preview-attachment相同的居中方式 */}
{/* 弹窗容器 - 全屏显示 */}
<div
className="flex flex-col overflow-hidden rounded-xl border border-border bg-background shadow-2xl"
style={{
width: "1200px",
height: "550px",
maxWidth: "95vw",
maxHeight: "90vh"
}}
className="flex flex-col overflow-hidden w-full h-full"
onClick={(e) => e.stopPropagation()}
>
{/* 顶部标题栏 */}
@ -960,73 +966,163 @@ export function ImageAnnotator({ imageUrl, onConfirm, onCancel }: ImageAnnotator
</div>
</div>
{/* 工具选择栏 */}
<div className="flex flex-wrap items-center justify-center gap-3 border-b px-4 py-2">
{/* 工具按钮 */}
<div className="flex items-center gap-1 rounded-lg bg-muted p-1">
<button
type="button"
className={`rounded-md p-1.5 transition-colors ${tool === "pen" ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted-foreground/10 hover:text-foreground"}`}
onClick={() => setTool("pen")}
title="画笔"
>
<Pencil className="h-4 w-4" />
</button>
<button
type="button"
className={`rounded-md p-1.5 transition-colors ${tool === "arrow" ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted-foreground/10 hover:text-foreground"}`}
onClick={() => setTool("arrow")}
title="箭头"
>
<ArrowRight className="h-4 w-4" />
</button>
<button
type="button"
className={`rounded-md p-1.5 transition-colors ${tool === "circle" ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted-foreground/10 hover:text-foreground"}`}
onClick={() => setTool("circle")}
title="圆圈"
>
<Circle className="h-4 w-4" />
</button>
<button
type="button"
className={`rounded-md p-1.5 transition-colors ${tool === "text" ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted-foreground/10 hover:text-foreground"}`}
onClick={() => setTool("text")}
title="文字"
>
<Type className="h-4 w-4" />
</button>
</div>
{/* 颜色选择 */}
<div className="flex items-center gap-1 rounded-lg bg-muted p-1">
{COLORS.map((c) => (
<button
key={c}
type="button"
className={`h-6 w-6 rounded-full border-2 transition-transform ${color === c ? "scale-110 border-primary" : "border-transparent hover:scale-105"}`}
style={{ backgroundColor: c }}
onClick={() => setColor(c)}
title={c}
{/* 工具选择栏 / 尺寸调整工具栏 */}
{isResizeMode ? (
/* 尺寸调整模式下显示尺寸调整工具栏 */
<div className="flex items-center gap-4 border-b px-4 py-3 bg-muted/30">
<div className="flex items-center gap-2">
<label className="text-sm font-medium">宽度:</label>
<Input
type="number"
value={resizeCanvasWidth}
onChange={(e) => setResizeCanvasWidth(Number.parseInt(e.target.value) || 1)}
className="w-24 h-8"
min={1}
max={10000}
/>
))}
</div>
{/* 线宽选择 */}
<div className="flex items-center gap-1 rounded-lg bg-muted p-1">
{LINE_WIDTHS.map((w) => (
<button
key={w}
type="button"
className={`flex h-7 w-7 items-center justify-center rounded-md transition-colors ${lineWidth === w ? "bg-primary/20" : "hover:bg-muted-foreground/10"}`}
onClick={() => setLineWidth(w)}
title={`线宽 ${w}px`}
<span className="text-xs text-muted-foreground">px</span>
</div>
<div className="flex items-center gap-2">
<label className="text-sm font-medium">高度:</label>
<Input
type="number"
value={resizeCanvasHeight}
onChange={(e) => setResizeCanvasHeight(Number.parseInt(e.target.value) || 1)}
className="w-24 h-8"
min={1}
max={10000}
/>
<span className="text-xs text-muted-foreground">px</span>
</div>
<div className="flex items-center gap-2">
<label className="text-sm font-medium">背景:</label>
<input
type="color"
value={resizeBackgroundColor}
onChange={(e) => setResizeBackgroundColor(e.target.value)}
disabled={resizeBackgroundTransparent}
className="w-12 h-8 rounded border cursor-pointer disabled:opacity-50"
/>
</div>
<div className="flex items-center gap-2">
<label className="text-sm font-medium flex items-center gap-1">
<input
type="checkbox"
checked={resizeBackgroundTransparent}
onChange={(e) => setResizeBackgroundTransparent(e.target.checked)}
className="w-4 h-4"
/>
透明
</label>
</div>
<div className="flex items-center gap-2 ml-auto">
<Button
variant="outline"
size="sm"
onClick={handleResizeZoomOut}
title="缩小"
className="h-8 w-8 p-0"
>
<div className="rounded-full bg-foreground" style={{ width: w + 2, height: w + 2 }} />
</button>
))}
<ZoomOut className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={handleResizeZoomIn}
title="放大"
className="h-8 w-8 p-0"
>
<ZoomIn className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => {
setIsResizeMode(false);
setResizeImageScale(1);
setResizeImagePosition({ x: 0, y: 0 });
}}
>
取消
</Button>
<Button
size="sm"
onClick={handleResizeConfirm}
className="bg-primary text-primary-foreground hover:bg-primary/90"
>
确认调整
</Button>
</div>
</div>
</div>
) : (
/* 非尺寸调整模式下显示图片标注工具栏 */
<div className="flex flex-wrap items-center justify-center gap-3 border-b px-4 py-2">
{/* 工具按钮 */}
<div className="flex items-center gap-1 rounded-lg bg-muted p-1">
<button
type="button"
className={`rounded-md p-1.5 transition-colors ${tool === "pen" ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted-foreground/10 hover:text-foreground"}`}
onClick={() => setTool("pen")}
title="画笔"
>
<Pencil className="h-4 w-4" />
</button>
<button
type="button"
className={`rounded-md p-1.5 transition-colors ${tool === "arrow" ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted-foreground/10 hover:text-foreground"}`}
onClick={() => setTool("arrow")}
title="箭头"
>
<ArrowRight className="h-4 w-4" />
</button>
<button
type="button"
className={`rounded-md p-1.5 transition-colors ${tool === "circle" ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted-foreground/10 hover:text-foreground"}`}
onClick={() => setTool("circle")}
title="圆圈"
>
<Circle className="h-4 w-4" />
</button>
<button
type="button"
className={`rounded-md p-1.5 transition-colors ${tool === "text" ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted-foreground/10 hover:text-foreground"}`}
onClick={() => setTool("text")}
title="文字"
>
<Type className="h-4 w-4" />
</button>
</div>
{/* 颜色选择 */}
<div className="flex items-center gap-1 rounded-lg bg-muted p-1">
{COLORS.map((c) => (
<button
key={c}
type="button"
className={`h-6 w-6 rounded-full border-2 transition-transform ${color === c ? "scale-110 border-primary" : "border-transparent hover:scale-105"}`}
style={{ backgroundColor: c }}
onClick={() => setColor(c)}
title={c}
/>
))}
</div>
{/* 线宽选择 */}
<div className="flex items-center gap-1 rounded-lg bg-muted p-1">
{LINE_WIDTHS.map((w) => (
<button
key={w}
type="button"
className={`flex h-7 w-7 items-center justify-center rounded-md transition-colors ${lineWidth === w ? "bg-primary/20" : "hover:bg-muted-foreground/10"}`}
onClick={() => setLineWidth(w)}
title={`线宽 ${w}px`}
>
<div className="rounded-full bg-foreground" style={{ width: w + 2, height: w + 2 }} />
</button>
))}
</div>
</div>
)}
{/* 画布区域 - 并排显示原图和处理后的图片 */}
<div ref={containerRef} className="flex flex-1 items-center justify-center gap-4 overflow-hidden bg-muted/30 p-4">
@ -1050,101 +1146,50 @@ export function ImageAnnotator({ imageUrl, onConfirm, onCancel }: ImageAnnotator
<span className="text-sm text-muted-foreground">正在加载图片...</span>
</div>
) : isResizeMode ? (
/* 尺寸调整模式 */
<div className="flex flex-1 flex-col items-center justify-center gap-4 w-full h-full">
{/* 尺寸调整控制面板 */}
<div className="flex items-center gap-4 p-4 bg-muted/50 rounded-lg w-full max-w-2xl">
<div className="flex items-center gap-2">
<label className="text-sm font-medium">宽度:</label>
<Input
type="number"
value={resizeCanvasWidth}
onChange={(e) => setResizeCanvasWidth(Number.parseInt(e.target.value) || 1)}
className="w-24 h-8"
min={1}
max={10000}
/>
<span className="text-xs text-muted-foreground">px</span>
</div>
<div className="flex items-center gap-2">
<label className="text-sm font-medium">高度:</label>
<Input
type="number"
value={resizeCanvasHeight}
onChange={(e) => setResizeCanvasHeight(Number.parseInt(e.target.value) || 1)}
className="w-24 h-8"
min={1}
max={10000}
/>
<span className="text-xs text-muted-foreground">px</span>
</div>
<div className="flex items-center gap-2">
<label className="text-sm font-medium">背景:</label>
<input
type="color"
value={resizeBackgroundColor}
onChange={(e) => setResizeBackgroundColor(e.target.value)}
disabled={resizeBackgroundTransparent}
className="w-12 h-8 rounded border cursor-pointer disabled:opacity-50"
/>
</div>
<div className="flex items-center gap-2">
<label className="text-sm font-medium flex items-center gap-1">
<input
type="checkbox"
checked={resizeBackgroundTransparent}
onChange={(e) => setResizeBackgroundTransparent(e.target.checked)}
className="w-4 h-4"
/* 尺寸调整模式 - 左右对比布局 */
<>
{/* 左侧:原图 */}
<div className="flex flex-1 flex-col border-2 border-dashed border-border rounded-lg bg-muted/20 p-4 overflow-hidden">
<div className="mb-2 text-sm font-medium text-muted-foreground text-center">原图</div>
<div className="flex-1 flex items-center justify-center overflow-auto">
{image && (
<img
src={image.src}
alt="原图"
className="max-w-full max-h-full rounded-lg shadow-lg"
style={{ objectFit: "contain" }}
/>
透明
</label>
</div>
<div className="flex items-center gap-2 ml-auto">
<Button
variant="ghost"
size="sm"
onClick={() => {
setIsResizeMode(false);
setResizeImageScale(1);
setResizeImagePosition({ x: 0, y: 0 });
}}
>
取消
</Button>
<Button
size="sm"
onClick={handleResizeConfirm}
className="bg-primary text-primary-foreground hover:bg-primary/90"
>
确认调整
</Button>
)}
</div>
</div>
{/* 尺寸调整预览画布 */}
<div
ref={resizeContainerRef}
className="flex-1 w-full max-w-4xl border-2 border-dashed border-primary/50 rounded-lg bg-muted/30 p-4 overflow-auto"
onWheel={handleResizeImageWheel}
>
<div className="relative inline-block">
<canvas
ref={resizeCanvasRef}
className="cursor-move rounded-lg shadow-lg"
onMouseDown={handleResizeImageDragStart}
onMouseMove={handleResizeImageDrag}
onMouseUp={handleResizeImageDragEnd}
onMouseLeave={handleResizeImageDragEnd}
onTouchStart={handleResizeImageDragStart}
onTouchMove={handleResizeImageDrag}
onTouchEnd={handleResizeImageDragEnd}
/>
<div className="absolute top-2 left-2 bg-black/70 text-white text-xs px-2 py-1 rounded">
画布: {resizeCanvasWidth} x {resizeCanvasHeight} | 缩放: {(resizeImageScale * 100).toFixed(0)}% | 拖拽移动图片,滚轮缩放
{/* 右侧:尺寸调整预览画布 */}
<div className="flex flex-1 flex-col border-2 border-dashed border-primary/50 rounded-lg bg-muted/30 p-4 overflow-hidden">
<div className="mb-2 text-sm font-medium text-muted-foreground text-center">调整尺寸</div>
<div
ref={resizeContainerRef}
className="flex-1 flex items-center justify-center overflow-auto"
onWheel={handleResizeImageWheel}
>
<div className="relative inline-block">
<canvas
ref={resizeCanvasRef}
className="cursor-move rounded-lg shadow-lg"
onMouseDown={handleResizeImageDragStart}
onMouseMove={handleResizeImageDrag}
onMouseUp={handleResizeImageDragEnd}
onMouseLeave={handleResizeImageDragEnd}
onTouchStart={handleResizeImageDragStart}
onTouchMove={handleResizeImageDrag}
onTouchEnd={handleResizeImageDragEnd}
/>
<div className="absolute top-2 left-2 bg-black/70 text-white text-xs px-2 py-1 rounded">
画布: {resizeCanvasWidth} x {resizeCanvasHeight} | 缩放: {(resizeImageScale * 100).toFixed(0)}% | 拖拽移动图片,滚轮缩放
</div>
</div>
</div>
</div>
</div>
</>
) : (
<>
{/* 原图画布 */}