Sfoglia il codice sorgente

feat(export): 优化表格列宽计算和单元格渲染逻辑
从 block metadata 提取列宽
实现动态表格宽度计算
归一化列宽处理
重构单元格迭代逻辑
简化合并单元格处理
添加列宽分配的点数归一化
改进单元格定位逻辑
更新合并映射跟踪

chensiyu 1 mese fa
parent
commit
a8fe5b56ba
1 ha cambiato i file con 67 aggiunte e 34 eliminazioni
  1. 67 34
      app/services/export_service.py

+ 67 - 34
app/services/export_service.py

@@ -1019,7 +1019,10 @@ def _render_table_block(doc: Document, block: dict, style_map: dict):
1019 1019
         return
1020 1020
     
1021 1021
     # 使用 col_widths 确定真实列数(而不是第一行的单元格数)
1022
-    col_widths = table_data.get('col_widths', [])
1022
+    # 注意: col_widths 应该从 metadata 中获取, 而不是 content
1023
+    metadata = block.get('metadata', {})
1024
+    col_widths = metadata.get('col_widths', [])
1025
+
1023 1026
     if col_widths:
1024 1027
         num_cols = len(col_widths)
1025 1028
     else:
@@ -1058,10 +1061,47 @@ def _render_table_block(doc: Document, block: dict, style_map: dict):
1058 1061
         table.alignment = WD_TABLE_ALIGNMENT.RIGHT
1059 1062
     
1060 1063
     # 设置列宽
1064
+    # 注意: col_widths 中存储的是百分比值(如 [50, 50] 表示两列各占50%)
1065
+    # 需要根据表格总宽度计算实际列宽(磅值)
1061 1066
     if col_widths:
1062
-        for col_idx, width in enumerate(col_widths):
1067
+        # 获取表格总宽度设置
1068
+        table_width = metadata.get('table_width', 100)
1069
+        table_width_unit = metadata.get('table_width_unit', 'percent')
1070
+        
1071
+        # 计算表格实际宽度(磅)
1072
+        if table_width_unit == 'percent':
1073
+            # 百分比模式:基于页面可用宽度计算
1074
+            # 假设 A4 纸张,页面宽度约 595磅(21cm),左右边距各约71磅(2.5cm)
1075
+            # 可用宽度 = 595 - 71 - 71 = 453 磅
1076
+            page_available_width_pt = 453.0  # 可以从 style_data 中的 page_setup 获取更精确的值
1077
+            actual_table_width_pt = page_available_width_pt * (table_width / 100.0)
1078
+        elif table_width_unit == 'cm':
1079
+            # 厘米转磅: 1cm = 28.35磅
1080
+            actual_table_width_pt = table_width * 28.35
1081
+        elif table_width_unit == 'inch':
1082
+            # 英寸转磅: 1inch = 72磅
1083
+            actual_table_width_pt = table_width * 72.0
1084
+        else:
1085
+            # 默认使用百分比模式
1086
+            page_available_width_pt = 453.0
1087
+            actual_table_width_pt = page_available_width_pt * (table_width / 100.0)
1088
+        
1089
+        # 计算列宽百分比总和,用于归一化
1090
+        col_widths_sum = sum(col_widths)
1091
+        
1092
+        # 根据百分比计算每列的实际宽度并设置
1093
+        # 注意:为了避免因百分比总和不为100而导致的问题,我们基于实际总和进行归一化
1094
+        for col_idx, width_percent in enumerate(col_widths):
1063 1095
             if col_idx < len(table.columns):
1064
-                table.columns[col_idx].width = Pt(width)
1096
+                # 归一化:基于实际的百分比总和计算每列占表格宽度的比例
1097
+                # 例如:如果7列各14%,总和98%,则每列实际占 14/98 的表格宽度
1098
+                if col_widths_sum > 0:
1099
+                    col_width_pt = actual_table_width_pt * (width_percent / col_widths_sum)
1100
+                else:
1101
+                    # 如果总和为0,平均分配
1102
+                    col_width_pt = actual_table_width_pt / len(col_widths)
1103
+                
1104
+                table.columns[col_idx].width = Pt(col_width_pt)
1065 1105
     
1066 1106
     # 填充内容并处理合并单元格
1067 1107
     merge_map = {}  # {(row, col): (end_row, end_col)} 记录合并区域
@@ -1075,58 +1115,49 @@ def _render_table_block(doc: Document, block: dict, style_map: dict):
1075 1115
         
1076 1116
         cells_data = row_data.get('cells', [])
1077 1117
         
1078
-        # 构建一个映射:col_index -> cell_data
1079
-        cells_by_col = {}
1080
-        for cell_data in cells_data:
1081
-            col_idx = cell_data.get('col_index', None)
1082
-            if col_idx is not None:
1083
-                cells_by_col[col_idx] = cell_data
1084
-        
1085
-        # 遍历所有列
1086
-        for col_idx in range(num_cols):
1087
-            # 检查这个位置是否被占用(被上方的合并单元格占用)
1088
-            if occupied.get((r_idx, col_idx), False):
1089
-                continue  # 跳过被占用的位置
1090
-            
1091
-            # 检查是否有数据要填充到这个位置
1092
-            if col_idx not in cells_by_col:
1093
-                continue  # 这个位置没有数据
1118
+        # 遍历单元格数据
1119
+        # 注意:单元格在 cells 数组中的索引就是它的列索引(col_idx)
1120
+        col_offset = 0  # 当前应该填充到哪一列
1121
+        for cell_idx, cell_data in enumerate(cells_data):
1122
+            # 跳过被上方合并单元格占用的列
1123
+            while occupied.get((r_idx, col_offset), False):
1124
+                col_offset += 1
1125
+                if col_offset >= num_cols:
1126
+                    break
1094 1127
             
1095
-            cell_data = cells_by_col[col_idx]
1128
+            if col_offset >= num_cols:
1129
+                break  # 超出列数,停止处理
1096 1130
             
1097
-            # 跳过被合并的单元格(rowspan=0 表示这个单元格被上面的单元格合并了)
1131
+            # 跳过被合并的单元格(rowspan=0 或 colspan=0 表示这个单元格被合并了)
1098 1132
             rowspan = cell_data.get('rowspan', 1)
1099
-            if rowspan == 0:
1100
-                continue
1101
-            
1102 1133
             colspan = cell_data.get('colspan', 1)
1103 1134
             
1104
-            # 确保不越界
1105
-            if col_idx >= num_cols:
1135
+            if rowspan == 0 or colspan == 0:
1136
+                # 这个单元格已被合并,跳过
1106 1137
                 continue
1107 1138
             
1108 1139
             # 获取起始单元格
1109
-            start_cell = table.rows[r_idx].cells[col_idx]
1140
+            start_cell = table.rows[r_idx].cells[col_offset]
1110 1141
             
1111 1142
             # 处理合并单元格
1112 1143
             if colspan > 1 or rowspan > 1:
1113 1144
                 # 计算结束位置
1114
-                end_col = min(col_idx + colspan - 1, num_cols - 1)
1145
+                end_col = min(col_offset + colspan - 1, num_cols - 1)
1115 1146
                 end_row = min(r_idx + rowspan - 1, num_rows - 1)
1116 1147
                 
1117 1148
                 # 合并单元格
1118
-                if end_col > col_idx or end_row > r_idx:
1149
+                if end_col > col_offset or end_row > r_idx:
1119 1150
                     try:
1120 1151
                         end_cell = table.rows[end_row].cells[end_col]
1121 1152
                         start_cell.merge(end_cell)
1122
-                        merge_map[(r_idx, col_idx)] = (end_row, end_col)
1153
+                        merge_map[(r_idx, col_offset)] = (end_row, end_col)
1123 1154
                         
1124 1155
                         # 标记被合并的单元格位置为已占用
1125 1156
                         for merge_r in range(r_idx, end_row + 1):
1126
-                            for merge_c in range(col_idx, end_col + 1):
1127
-                                if merge_r != r_idx or merge_c != col_idx:  # 不标记起始单元格
1157
+                            for merge_c in range(col_offset, end_col + 1):
1158
+                                if merge_r != r_idx or merge_c != col_offset:  # 不标记起始单元格
1128 1159
                                     occupied[(merge_r, merge_c)] = True
1129
-                    except Exception:
1160
+                    except Exception as e:
1130 1161
                         pass  # 合并失败,继续
1131 1162
             
1132 1163
             # 设置单元格宽度(如果有)
@@ -1191,7 +1222,9 @@ def _render_table_block(doc: Document, block: dict, style_map: dict):
1191 1222
                 run = para.add_run(str(cell_text))
1192 1223
                 # 应用单元格 run 级样式
1193 1224
                 _apply_run_style(run, cell_style)
1194
-
1225
+            
1226
+            # 移动到下一列位置(考虑colspan)
1227
+            col_offset += colspan
1195 1228
 
1196 1229
 def _render_image_block(doc: Document, block: dict, style_map: dict = None):
1197 1230
     """渲染图片块(支持 Base64 Data URL 和 Word 样式)"""