объединенные ячейки

This commit is contained in:
2025-07-18 14:34:29 +03:00
parent 8b67db832c
commit 7ba38b3284
2 changed files with 142 additions and 4 deletions

View File

@@ -11,6 +11,7 @@ import {
} from 'react'
import { VariableSizeGrid } from 'react-window'
import { useMultiSheetEngine } from '../../hooks/useMultiSheetEngine'
import { MergedCell } from '../../types/template'
const ROW_COUNT = 90
const COL_COUNT = 20
@@ -26,9 +27,59 @@ type SheetType = keyof typeof SHEET_NAMES
// --- Helper Functions ---
const getColumnLabel = (index: number) => String.fromCharCode(65 + index)
// Функция для преобразования адреса ячейки в координаты
const cellAddressToCoords = (address: string): { row: number; col: number } => {
const match = address.match(/^([A-Z]+)(\d+)$/)
if (!match) return { row: 0, col: 0 }
const col = match[1].charCodeAt(0) - 65 // Простое преобразование для одной буквы
const row = parseInt(match[2]) - 1
return { row, col }
}
// Функция для проверки, является ли ячейка частью объединенной группы
const findMergedCellInfo = (
row: number,
col: number,
mergedCells: MergedCell[],
): {
isMerged: boolean
isMainCell: boolean
mergedCell?: MergedCell
spans?: { rowSpan: number; colSpan: number }
} => {
for (const merged of mergedCells) {
const fromCoords = cellAddressToCoords(merged.from)
const toCoords = cellAddressToCoords(merged.to)
// Проверяем, попадает ли текущая ячейка в диапазон объединения
if (
row >= fromCoords.row &&
row <= toCoords.row &&
col >= fromCoords.col &&
col <= toCoords.col
) {
const isMainCell = row === fromCoords.row && col === fromCoords.col
const rowSpan = toCoords.row - fromCoords.row + 1
const colSpan = toCoords.col - fromCoords.col + 1
return {
isMerged: true,
isMainCell,
mergedCell: merged,
spans: { rowSpan, colSpan },
}
}
}
return { isMerged: false, isMainCell: false }
}
// --- Interfaces ---
interface DualSpreadsheetProps {
templateData?: Record<string, Record<string, any>>
mergedCells?: MergedCell[] // Добавляем поддержку объединенных ячеек
}
// Кэшированные данные ячеек для оптимизации
@@ -45,6 +96,7 @@ interface CellProps {
rowIndex: number
colIndex: number
columnWidth: number
columnWidths: number[] // Добавляем массив всех ширин колонок
isSelected: boolean
isEditing: boolean
displayValue: string // Отображаемое значение (вычисленное)
@@ -58,6 +110,14 @@ interface CellProps {
setActiveSheet: (type: SheetType) => void
activeCellRef: React.RefObject<HTMLInputElement>
style: CSSProperties // Добавляем style для react-window
mergedCellInfo?: {
// Информация об объединенных ячейках
isMerged: boolean
isMainCell: boolean
rowSpan?: number
colSpan?: number
mergedValue?: any
}
}
const Cell = memo(
@@ -66,6 +126,7 @@ const Cell = memo(
rowIndex,
colIndex,
columnWidth,
columnWidths,
isSelected,
isEditing,
displayValue,
@@ -79,17 +140,63 @@ const Cell = memo(
setActiveSheet,
activeCellRef,
style, // Добавляем style для react-window
mergedCellInfo,
}: CellProps) => {
// Если ячейка объединена, но не является основной, не рендерим её
if (mergedCellInfo?.isMerged && !mergedCellInfo.isMainCell) {
return null
}
const className = `border border-border ${isSelected ? 'bg-accent' : ''} ${
isModified ? 'bg-yellow-50' : ''
}`
const cellStyle: CSSProperties = {
// Для объединенных ячеек нужно рассчитать правильные размеры
let cellStyle: CSSProperties = {
...style,
overflow: 'visible',
padding: 0,
}
// Если это основная ячейка объединенной группы, расширяем её размеры
if (
mergedCellInfo?.isMainCell &&
mergedCellInfo.rowSpan &&
mergedCellInfo.colSpan
) {
const { rowSpan, colSpan } = mergedCellInfo
// Рассчитываем общую ширину для объединенной ячейки
let totalWidth = 0
for (let i = 0; i < colSpan; i++) {
const currentColIndex = colIndex + i
if (currentColIndex < columnWidths.length) {
totalWidth += columnWidths[currentColIndex]
}
}
// Рассчитываем общую высоту для объединенной ячейки
const totalHeight = ROW_HEIGHT * rowSpan
cellStyle = {
...cellStyle,
width: totalWidth,
height: totalHeight,
zIndex: 10, // Поднимаем выше обычных ячеек
backgroundColor: isSelected
? 'hsl(var(--accent))'
: isModified
? '#fefce8'
: 'white',
}
}
// Используем значение из объединенной ячейки, если доступно
const cellDisplayValue =
mergedCellInfo?.isMainCell && mergedCellInfo.mergedValue !== undefined
? String(mergedCellInfo.mergedValue)
: displayValue
const handleClick = useCallback(() => {
setActiveSheet(sheetType)
onCellClick(rowIndex, colIndex)
@@ -145,7 +252,7 @@ const Cell = memo(
maxWidth: hasRightContent ? `${columnWidth - 16}px` : 'none',
}}
>
{displayValue}
{cellDisplayValue}
</div>
</div>
)}
@@ -271,6 +378,7 @@ const CellRenderer = ({
isEditing,
editingValue,
cachedData,
mergedCells,
handleCellClick,
handleCellDoubleClick,
handleCellChange,
@@ -293,6 +401,22 @@ const CellRenderer = ({
? '' // Не показывать вычисленное значение во время редактирования
: cellData?.displayValue || ''
// Получаем информацию об объединенных ячейках только для листа отчета
const mergedCellInfo =
sheetType === 'report' && mergedCells
? findMergedCellInfo(rowIndex, columnIndex, mergedCells)
: { isMerged: false, isMainCell: false }
const mergedCellProps = mergedCellInfo.isMerged
? {
isMerged: true,
isMainCell: mergedCellInfo.isMainCell,
rowSpan: mergedCellInfo.spans?.rowSpan,
colSpan: mergedCellInfo.spans?.colSpan,
mergedValue: mergedCellInfo.mergedCell?.value,
}
: undefined
return (
<Cell
style={style}
@@ -300,6 +424,7 @@ const CellRenderer = ({
rowIndex={rowIndex}
colIndex={columnIndex}
columnWidth={widths[columnIndex]}
columnWidths={widths}
isSelected={isCellSelected}
isEditing={isCellEditing}
displayValue={displayValue}
@@ -312,6 +437,7 @@ const CellRenderer = ({
stopEditing={stopEditing}
setActiveSheet={setActiveSheet}
activeCellRef={activeCellInputRef}
mergedCellInfo={mergedCellProps}
/>
)
}
@@ -349,6 +475,7 @@ interface SpreadsheetProps {
colIndex: number,
newWidth: number,
) => void
mergedCells?: MergedCell[] // Добавляем поддержку объединенных ячеек
}
// --- Spreadsheet Component ---
@@ -374,6 +501,7 @@ const Spreadsheet = ({
gridRefs,
onGridScroll,
handleColumnResize,
mergedCells,
}: SpreadsheetProps) => {
const widths = columnWidths
const cachedData = getCachedCellData(sheetType)
@@ -386,6 +514,7 @@ const Spreadsheet = ({
isEditing,
editingValue,
cachedData,
mergedCells,
handleCellClick,
handleCellDoubleClick,
handleCellChange,
@@ -530,7 +659,10 @@ const Spreadsheet = ({
)
}
const DualSpreadsheet: FC<DualSpreadsheetProps> = ({ templateData = {} }) => {
const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
templateData = {},
mergedCells = [],
}) => {
const { revision, updateRevision, ...multiSheetEngine } = useMultiSheetEngine(
{
sheets: [
@@ -996,6 +1128,7 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({ templateData = {} }) => {
gridRefs={gridRefs}
onGridScroll={onGridScroll}
handleColumnResize={handleColumnResize}
mergedCells={mergedCells}
/>
<div
ref={setSeparatorRef}
@@ -1025,6 +1158,7 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({ templateData = {} }) => {
gridRefs={gridRefs}
onGridScroll={onGridScroll}
handleColumnResize={handleColumnResize}
mergedCells={mergedCells}
/>
</div>
</div>

View File

@@ -240,7 +240,11 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
{/* Таблицы - теперь занимают всё оставшееся пространство */}
<div className="min-h-0 flex-1">
<DualSpreadsheet key={dataVersion} templateData={{ L: excelData }} />
<DualSpreadsheet
key={dataVersion}
templateData={{ L: excelData }}
mergedCells={editedTemplate.mergedCells}
/>
</div>
</div>
)