Переглянути джерело

feat(table-cells): Improve cell alignment and preserve styles during merge/split operations

- Change default vertical alignment from top to middle in table cells
- Add center text alignment to table cell styling
- Extend CellStyleOverrides type to preserve original styles in merged cells
- Update mergeCells function to capture and store cell styles alongside content
- Modify splitCell function to restore both content and original styles when unmerging
- Create cell data map to track text and style for each merged cell position
- Apply original styles to recovered cells after split operation
- Update cellStyleToCSS utility to enforce center text alignment by default
- Enhance JSDoc comments to reflect style preservation in merge/split workflows
Zhang Yice 1 місяць тому
батько
коміт
c3297c01ee

+ 2 - 1
src/components/Editor/blocks/TableCell.css

@@ -5,7 +5,8 @@
5
 .table-cell {
5
 .table-cell {
6
   border: 1px solid #d9d9d9;
6
   border: 1px solid #d9d9d9;
7
   padding: 8px 12px;
7
   padding: 8px 12px;
8
-  vertical-align: top;
8
+  vertical-align: middle;
9
+  text-align: center;
9
   position: relative;
10
   position: relative;
10
   transition: all 0.2s ease;
11
   transition: all 0.2s ease;
11
 }
12
 }

+ 1 - 0
src/components/Editor/blocks/TableCell.tsx

@@ -93,6 +93,7 @@ export const TableCell: React.FC<TableCellProps> = ({
93
         baseStyle={{
93
         baseStyle={{
94
           fontSize: '12px',
94
           fontSize: '12px',
95
           lineHeight: 1.5,
95
           lineHeight: 1.5,
96
+          textAlign: 'center', // 强制居中对齐
96
         }}
97
         }}
97
       />
98
       />
98
     </td>
99
     </td>

+ 2 - 1
src/types/editor.ts

@@ -104,11 +104,12 @@ export interface CellStyleOverrides extends StyleOverrides {
104
   border_color?: string;
104
   border_color?: string;
105
   border_width?: number;
105
   border_width?: number;
106
   vertical_align?: 'top' | 'center' | 'bottom';
106
   vertical_align?: 'top' | 'center' | 'bottom';
107
-  // 合并单元格时保存的原始数据
107
+  // 合并单元格时保存的原始数据(包含text和style)
108
   _mergedCells?: Array<{
108
   _mergedCells?: Array<{
109
     rowOffset: number;
109
     rowOffset: number;
110
     colOffset: number;
110
     colOffset: number;
111
     text: string | RichText[];
111
     text: string | RichText[];
112
+    style?: CellStyleOverrides; // 保存原始样式
112
   }>;
113
   }>;
113
 }
114
 }
114
 
115
 

+ 27 - 19
src/utils/blockOperations.ts

@@ -241,7 +241,7 @@ export function deleteTableColumn(table: TableBlock, colIndex: number): TableBlo
241
  * 合并单元格
241
  * 合并单元格
242
  * 
242
  * 
243
  * 支持横向合并(colspan)和纵向合并(rowspan)
243
  * 支持横向合并(colspan)和纵向合并(rowspan)
244
- * 合并时会保存所有被合并单元格的原始内容,以便拆分时恢复
244
+ * 合并时会保存所有被合并单元格的原始内容和样式,以便拆分时恢复
245
  * 
245
  * 
246
  * @param table 表格块
246
  * @param table 表格块
247
  * @param startRow 起始行
247
  * @param startRow 起始行
@@ -254,11 +254,11 @@ export function deleteTableColumn(table: TableBlock, colIndex: number): TableBlo
254
  * ```ts
254
  * ```ts
255
  * // 横向合并: a + b (第0行,第0-1列)
255
  * // 横向合并: a + b (第0行,第0-1列)
256
  * mergeCells(table, 0, 0, 0, 1);
256
  * mergeCells(table, 0, 0, 0, 1);
257
- * // 结果: a单元格 colspan=2, 保存b的内容到 _mergedCells
257
+ * // 结果: a单元格 colspan=2, 保存b的内容和样式到 _mergedCells
258
  * 
258
  * 
259
  * // 纵向合并: a + d (第0-1行,第0列)
259
  * // 纵向合并: a + d (第0-1行,第0列)
260
  * mergeCells(table, 0, 0, 1, 0);
260
  * mergeCells(table, 0, 0, 1, 0);
261
- * // 结果: a单元格 rowspan=2, 保存d的内容到 _mergedCells
261
+ * // 结果: a单元格 rowspan=2, 保存d的内容和样式到 _mergedCells
262
  * ```
262
  * ```
263
  */
263
  */
264
 export function mergeCells(
264
 export function mergeCells(
@@ -268,11 +268,12 @@ export function mergeCells(
268
   endRow: number,
268
   endRow: number,
269
   endCol: number
269
   endCol: number
270
 ): TableBlock {
270
 ): TableBlock {
271
-  // 收集所有被合并单元格的内容(保存位置偏移量)
271
+  // 收集所有被合并单元格的内容和样式(保存位置偏移量)
272
   const mergedCells: Array<{
272
   const mergedCells: Array<{
273
     rowOffset: number;
273
     rowOffset: number;
274
     colOffset: number;
274
     colOffset: number;
275
     text: string | RichText[];
275
     text: string | RichText[];
276
+    style: any; // 保存原始样式
276
   }> = [];
277
   }> = [];
277
   
278
   
278
   // 收集主单元格内容用于显示
279
   // 收集主单元格内容用于显示
@@ -286,11 +287,12 @@ export function mergeCells(
286
             ? cell.text 
287
             ? cell.text 
287
             : cell.text.map(seg => seg.text).join('');
288
             : cell.text.map(seg => seg.text).join('');
288
           
289
           
289
-          // 保存所有单元格的原始数据(包括主单元格)
290
+          // 保存所有单元格的原始数据和样式(包括主单元格)
290
           mergedCells.push({
291
           mergedCells.push({
291
             rowOffset: rowIdx - startRow,
292
             rowOffset: rowIdx - startRow,
292
             colOffset: colIdx - startCol,
293
             colOffset: colIdx - startCol,
293
             text: cell.text,
294
             text: cell.text,
295
+            style: { ...cell.style }, // 深拷贝样式
294
           });
296
           });
295
           
297
           
296
           // 用于显示的文本
298
           // 用于显示的文本
@@ -321,7 +323,7 @@ export function mergeCells(
321
           colspan: endCol - startCol + 1,
323
           colspan: endCol - startCol + 1,
322
           style: {
324
           style: {
323
             ...cell.style,
325
             ...cell.style,
324
-            _mergedCells: mergedCells, // 保存所有单元格的原始内容
326
+            _mergedCells: mergedCells, // 保存所有单元格的原始内容和样式
325
           },
327
           },
326
         };
328
         };
327
       }
329
       }
@@ -347,7 +349,7 @@ export function mergeCells(
347
 /**
349
 /**
348
  * 拆分单元格
350
  * 拆分单元格
349
  * 
351
  * 
350
- * 将已合并的单元格拆分回独立单元格,并恢复原始内容到对应位置
352
+ * 将已合并的单元格拆分回独立单元格,并恢复原始内容和样式到对应位置
351
  * 
353
  * 
352
  * @param table 表格块
354
  * @param table 表格块
353
  * @param rowIndex 单元格所在行
355
  * @param rowIndex 单元格所在行
@@ -358,11 +360,11 @@ export function mergeCells(
358
  * ```ts
360
  * ```ts
359
  * // 拆分横向合并的单元格 (a+b)
361
  * // 拆分横向合并的单元格 (a+b)
360
  * splitCell(table, 0, 0);
362
  * splitCell(table, 0, 0);
361
- * // 结果: a单元格恢复为普通单元格(内容为原始a), b单元格恢复(内容为原始b)
363
+ * // 结果: a单元格恢复为普通单元格(内容为原始a,样式为原始a样式), b单元格恢复(内容为原始b,样式为原始b样式)
362
  * 
364
  * 
363
  * // 拆分纵向合并的单元格 (a+d)  
365
  * // 拆分纵向合并的单元格 (a+d)  
364
  * splitCell(table, 0, 0);
366
  * splitCell(table, 0, 0);
365
- * // 结果: a单元格恢复为普通单元格(内容为原始a), d单元格恢复(内容为原始d)
367
+ * // 结果: a单元格恢复为普通单元格(内容为原始a,样式为原始a样式), d单元格恢复(内容为原始d,样式为原始d样式)
366
  * ```
368
  * ```
367
  */
369
  */
368
 export function splitCell(
370
 export function splitCell(
@@ -384,14 +386,14 @@ export function splitCell(
384
   const rowspan = targetCell.rowspan || 1;
386
   const rowspan = targetCell.rowspan || 1;
385
   const colspan = targetCell.colspan || 1;
387
   const colspan = targetCell.colspan || 1;
386
   
388
   
387
-  // 获取保存的原始单元格数据
389
+  // 获取保存的原始单元格数据(包含text和style)
388
   const mergedCells = targetCell.style._mergedCells || [];
390
   const mergedCells = targetCell.style._mergedCells || [];
389
   
391
   
390
-  // 创建一个映射,用于快速查找原始内容
391
-  const cellDataMap = new Map<string, string | RichText[]>();
392
-  mergedCells.forEach(({ rowOffset, colOffset, text }) => {
392
+  // 创建一个映射,用于快速查找原始内容和样式
393
+  const cellDataMap = new Map<string, { text: string | RichText[]; style: any }>();
394
+  mergedCells.forEach(({ rowOffset, colOffset, text, style }) => {
393
     const key = `${rowOffset}-${colOffset}`;
395
     const key = `${rowOffset}-${colOffset}`;
394
-    cellDataMap.set(key, text);
396
+    cellDataMap.set(key, { text, style: style || {} });
395
   });
397
   });
396
   
398
   
397
   const rows = table.content.rows.map((row, rowIdx) => {
399
   const rows = table.content.rows.map((row, rowIdx) => {
@@ -411,12 +413,14 @@ export function splitCell(
411
       const colOffset = colIdx - colIndex;
413
       const colOffset = colIdx - colIndex;
412
       const key = `${rowOffset}-${colOffset}`;
414
       const key = `${rowOffset}-${colOffset}`;
413
       
415
       
414
-      // 从保存的数据中恢复原始内容
415
-      const originalText = cellDataMap.get(key) || '';
416
+      // 从保存的数据中恢复原始内容和样式
417
+      const cellData = cellDataMap.get(key);
418
+      const originalText = cellData?.text || '';
419
+      const originalStyle = cellData?.style || {};
416
       
420
       
417
       // 主单元格:恢复为普通单元格,清除合并标记
421
       // 主单元格:恢复为普通单元格,清除合并标记
418
       if (rowIdx === rowIndex && colIdx === colIndex) {
422
       if (rowIdx === rowIndex && colIdx === colIndex) {
419
-        const newStyle = { ...cell.style };
423
+        const newStyle = { ...originalStyle };
420
         delete newStyle._mergedCells; // 清除保存的合并数据
424
         delete newStyle._mergedCells; // 清除保存的合并数据
421
         
425
         
422
         return {
426
         return {
@@ -424,14 +428,18 @@ export function splitCell(
424
           text: originalText,
428
           text: originalText,
425
           rowspan: 1,
429
           rowspan: 1,
426
           colspan: 1,
430
           colspan: 1,
427
-          style: newStyle,
431
+          style: newStyle, // 使用原始样式
428
         };
432
         };
429
       }
433
       }
430
       
434
       
431
-      // 被合并的单元格:恢复为独立单元格,并恢复原始内容
435
+      // 被合并的单元格:恢复为独立单元格,并恢复原始内容和样式
436
+      const recoveredStyle = { ...originalStyle };
437
+      delete recoveredStyle._mergedCells;
438
+      
432
       return {
439
       return {
433
         ...createEmptyCell(),
440
         ...createEmptyCell(),
434
         text: originalText,
441
         text: originalText,
442
+        style: recoveredStyle, // 使用原始样式
435
       };
443
       };
436
     });
444
     });
437
     
445
     

+ 7 - 1
src/utils/styleResolver.ts

@@ -144,7 +144,13 @@ export function cellStyleToCSS(style: CellStyleOverrides): React.CSSProperties {
144
   if (style.background_color) css.backgroundColor = `#${style.background_color}`;
144
   if (style.background_color) css.backgroundColor = `#${style.background_color}`;
145
   if (style.border_color) css.borderColor = `#${style.border_color}`;
145
   if (style.border_color) css.borderColor = `#${style.border_color}`;
146
   if (style.border_width) css.borderWidth = `${style.border_width}px`;
146
   if (style.border_width) css.borderWidth = `${style.border_width}px`;
147
-  if (style.vertical_align) css.verticalAlign = style.vertical_align;
147
+  
148
+  // 垂直对齐:默认居中
149
+  css.verticalAlign = style.vertical_align || 'middle';
150
+  
151
+  // 水平对齐:强制覆盖为居中(忽略单元格原有的 align 样式)
152
+  // 注释:如果希望保留原有的对齐方式,可以改为:css.textAlign = style.align || 'center';
153
+  css.textAlign = 'center';
148
   
154
   
149
   return css;
155
   return css;
150
 }
156
 }