diff --git a/packages/drawnix/src/components/ttd-dialog/batch-image-generation.scss b/packages/drawnix/src/components/ttd-dialog/batch-image-generation.scss index d6bad2ca..563d8358 100644 --- a/packages/drawnix/src/components/ttd-dialog/batch-image-generation.scss +++ b/packages/drawnix/src/components/ttd-dialog/batch-image-generation.scss @@ -104,6 +104,7 @@ border: 1px solid #e0e0e0; padding: 0; vertical-align: top; + height: 1px; // 让 td 高度由内容撑开,同时允许子元素 height: 100% 生效 } th { @@ -187,11 +188,14 @@ // 单元格 .excel-cell { min-height: 40px; + height: 100%; // 填满整个 td padding: 8px; cursor: cell; position: relative; display: flex; align-items: flex-start; + user-select: none; // 禁用文本选择,防止浏览器原生选择干扰单元格选择 + box-sizing: border-box; &.active { background: white; @@ -207,6 +211,7 @@ .cell-text { word-break: break-word; white-space: pre-wrap; + pointer-events: none; // 确保点击事件传递到父级 .excel-cell } input, textarea, select { @@ -218,6 +223,7 @@ font-family: inherit; padding: 0; resize: none; + user-select: text; // 编辑模式下允许文本选择 } textarea { diff --git a/packages/drawnix/src/components/ttd-dialog/batch-image-generation.tsx b/packages/drawnix/src/components/ttd-dialog/batch-image-generation.tsx index f368ccaa..734f8a10 100644 --- a/packages/drawnix/src/components/ttd-dialog/batch-image-generation.tsx +++ b/packages/drawnix/src/components/ttd-dialog/batch-image-generation.tsx @@ -838,6 +838,68 @@ const BatchImageGeneration: React.FC = ({ onSwitchToS return; } + // Ctrl+C 复制(支持多行) + if ((e.ctrlKey || e.metaKey) && e.key === 'c') { + e.preventDefault(); + // 收集所有选中单元格的值(按行排序) + const sortedCells = [...selectedCells].sort((a, b) => a.row - b.row); + const values = sortedCells.map(cell => { + const cellValue = (tasks[cell.row] as any)?.[cell.col]; + return { row: cell.row, col: cell.col, value: cellValue }; + }); + + if (values.length > 0) { + // 生成文本用于系统剪贴板(每行一个值) + const textToCopy = values.map(v => + v.col === 'images' ? JSON.stringify(v.value) : String(v.value ?? '') + ).join('\n'); + + navigator.clipboard.writeText(textToCopy).then(() => { + // 存储复制的单元格信息用于内部粘贴 + (window as any).__copiedCells = values; + }); + } + return; + } + + // Ctrl+V 粘贴(支持多行) + if ((e.ctrlKey || e.metaKey) && e.key === 'v') { + e.preventDefault(); + // 优先使用内部复制的单元格数据 + const copiedCells = (window as any).__copiedCells as Array<{row: number, col: string, value: any}> | undefined; + + if (copiedCells && copiedCells.length > 0 && copiedCells[0].col === col) { + // 从当前活动单元格开始粘贴多行 + copiedCells.forEach((copied, index) => { + const targetRow = row + index; + if (targetRow < tasks.length) { + updateCellValue(targetRow, col, copied.value); + } + }); + } else { + // 从系统剪贴板粘贴文本(支持多行) + navigator.clipboard.readText().then(text => { + const lines = text.split('\n'); + lines.forEach((line, index) => { + const targetRow = row + index; + if (targetRow >= tasks.length) return; + + if (col === 'prompt') { + updateCellValue(targetRow, col, line); + } else if (col === 'count') { + const num = parseInt(line); + if (!isNaN(num) && num >= 1) { + updateCellValue(targetRow, col, num); + } + } else if (col === 'size' && SIZE_OPTIONS.includes(line)) { + updateCellValue(targetRow, col, line); + } + }); + }); + } + return; + } + switch (e.key) { case 'ArrowUp': e.preventDefault(); @@ -878,7 +940,7 @@ const BatchImageGeneration: React.FC = ({ onSwitchToS document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); - }, [activeCell, editingCell, tasks.length, selectCell, enterEditMode, updateCellValue]); + }, [activeCell, editingCell, tasks, selectedCells, selectCell, enterEditMode, updateCellValue]); // 渲染单元格内容 const renderCellContent = (task: TaskRow, rowIndex: number, col: string) => {