ソースを参照

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

chensiyu 1 ヶ月 前
コミット
a8fe5b56ba
共有1 個のファイルを変更した67 個の追加34 個の削除を含む
  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
         return
1019
         return
1020
     
1020
     
1021
     # 使用 col_widths 确定真实列数(而不是第一行的单元格数)
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
     if col_widths:
1026
     if col_widths:
1024
         num_cols = len(col_widths)
1027
         num_cols = len(col_widths)
1025
     else:
1028
     else:
@@ -1058,10 +1061,47 @@ def _render_table_block(doc: Document, block: dict, style_map: dict):
1058
         table.alignment = WD_TABLE_ALIGNMENT.RIGHT
1061
         table.alignment = WD_TABLE_ALIGNMENT.RIGHT
1059
     
1062
     
1060
     # 设置列宽
1063
     # 设置列宽
1064
+    # 注意: col_widths 中存储的是百分比值(如 [50, 50] 表示两列各占50%)
1065
+    # 需要根据表格总宽度计算实际列宽(磅值)
1061
     if col_widths:
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
             if col_idx < len(table.columns):
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
     merge_map = {}  # {(row, col): (end_row, end_col)} 记录合并区域
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
         cells_data = row_data.get('cells', [])
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
             rowspan = cell_data.get('rowspan', 1)
1132
             rowspan = cell_data.get('rowspan', 1)
1099
-            if rowspan == 0:
1100
-                continue
1101
-            
1102
             colspan = cell_data.get('colspan', 1)
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
                 continue
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
             if colspan > 1 or rowspan > 1:
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
                 end_row = min(r_idx + rowspan - 1, num_rows - 1)
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
                     try:
1150
                     try:
1120
                         end_cell = table.rows[end_row].cells[end_col]
1151
                         end_cell = table.rows[end_row].cells[end_col]
1121
                         start_cell.merge(end_cell)
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
                         for merge_r in range(r_idx, end_row + 1):
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
                                     occupied[(merge_r, merge_c)] = True
1159
                                     occupied[(merge_r, merge_c)] = True
1129
-                    except Exception:
1160
+                    except Exception as e:
1130
                         pass  # 合并失败,继续
1161
                         pass  # 合并失败,继续
1131
             
1162
             
1132
             # 设置单元格宽度(如果有)
1163
             # 设置单元格宽度(如果有)
@@ -1191,7 +1222,9 @@ def _render_table_block(doc: Document, block: dict, style_map: dict):
1191
                 run = para.add_run(str(cell_text))
1222
                 run = para.add_run(str(cell_text))
1192
                 # 应用单元格 run 级样式
1223
                 # 应用单元格 run 级样式
1193
                 _apply_run_style(run, cell_style)
1224
                 _apply_run_style(run, cell_style)
1194
-
1225
+            
1226
+            # 移动到下一列位置(考虑colspan)
1227
+            col_offset += colspan
1195
 
1228
 
1196
 def _render_image_block(doc: Document, block: dict, style_map: dict = None):
1229
 def _render_image_block(doc: Document, block: dict, style_map: dict = None):
1197
     """渲染图片块(支持 Base64 Data URL 和 Word 样式)"""
1230
     """渲染图片块(支持 Base64 Data URL 和 Word 样式)"""