优化图片编辑器界面:全屏显示和工具栏替换
- 图片编辑器改为全屏显示,提供更大的操作空间 - 尺寸调整模式下,将图片标注工具栏替换为尺寸调整工具栏 - 移除画布区域的控制面板,为画布预留更多空间 - 优化界面布局,使操作更流畅
This commit is contained in:
parent
bbf9768bc9
commit
aab3b1e8ed
@ -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>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{/* 原图画布 */}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user