Showing
2 changed files
with
117 additions
and
26 deletions
| @@ -1178,6 +1178,11 @@ class HTMLRenderer: | @@ -1178,6 +1178,11 @@ class HTMLRenderer: | ||
| 1178 | def _render_swot_table(self, block: Dict[str, Any]) -> str: | 1178 | def _render_swot_table(self, block: Dict[str, Any]) -> str: |
| 1179 | """ | 1179 | """ |
| 1180 | 渲染四象限的SWOT专用表格,兼顾HTML与PDF的可读性。 | 1180 | 渲染四象限的SWOT专用表格,兼顾HTML与PDF的可读性。 |
| 1181 | + | ||
| 1182 | + PDF分页策略: | ||
| 1183 | + - 每个S/W/O/T象限内部禁止分页(break-inside: avoid) | ||
| 1184 | + - 允许在象限之间分页 | ||
| 1185 | + - 卡片标题与第一个象限尽量保持在一起 | ||
| 1181 | """ | 1186 | """ |
| 1182 | title = block.get("title") or "SWOT 分析" | 1187 | title = block.get("title") or "SWOT 分析" |
| 1183 | summary = block.get("summary") | 1188 | summary = block.get("summary") |
| @@ -1188,12 +1193,14 @@ class HTMLRenderer: | @@ -1188,12 +1193,14 @@ class HTMLRenderer: | ||
| 1188 | ("threats", "威胁 Threats", "T", "threat"), | 1193 | ("threats", "威胁 Threats", "T", "threat"), |
| 1189 | ] | 1194 | ] |
| 1190 | cells_html = "" | 1195 | cells_html = "" |
| 1191 | - for key, label, code, css in quadrants: | 1196 | + for idx, (key, label, code, css) in enumerate(quadrants): |
| 1192 | items = self._normalize_swot_items(block.get(key)) | 1197 | items = self._normalize_swot_items(block.get(key)) |
| 1193 | caption_text = f"{len(items)} 条要点" if items else "待补充" | 1198 | caption_text = f"{len(items)} 条要点" if items else "待补充" |
| 1194 | list_html = "".join(self._render_swot_item(item) for item in items) if items else '<li class="swot-empty">尚未填入要点</li>' | 1199 | list_html = "".join(self._render_swot_item(item) for item in items) if items else '<li class="swot-empty">尚未填入要点</li>' |
| 1200 | + # 第一个象限添加特殊类以便与标题保持在一起 | ||
| 1201 | + first_cell_class = " swot-cell--first" if idx == 0 else "" | ||
| 1195 | cells_html += f""" | 1202 | cells_html += f""" |
| 1196 | - <div class="swot-cell {css}"> | 1203 | + <div class="swot-cell swot-cell--pageable {css}{first_cell_class}" data-swot-key="{key}"> |
| 1197 | <div class="swot-cell__meta"> | 1204 | <div class="swot-cell__meta"> |
| 1198 | <span class="swot-pill {css}">{self._escape_html(code)}</span> | 1205 | <span class="swot-pill {css}">{self._escape_html(code)}</span> |
| 1199 | <div> | 1206 | <div> |
| @@ -3208,20 +3215,61 @@ table th {{ | @@ -3208,20 +3215,61 @@ table th {{ | ||
| 3208 | color: var(--swot-muted); | 3215 | color: var(--swot-muted); |
| 3209 | opacity: 0.7; | 3216 | opacity: 0.7; |
| 3210 | }} | 3217 | }} |
| 3211 | -/* PDF/导出时的SWOT专用布局,避免圆角框重叠 */ | 3218 | +/* PDF/导出时的SWOT专用布局,支持分页且避免圆角框重叠 */ |
| 3212 | body.exporting .swot-legend {{ | 3219 | body.exporting .swot-legend {{ |
| 3213 | display: none !important; | 3220 | display: none !important; |
| 3214 | }} | 3221 | }} |
| 3215 | body.exporting .swot-grid {{ | 3222 | body.exporting .swot-grid {{ |
| 3216 | display: flex; | 3223 | display: flex; |
| 3217 | - flex-wrap: wrap; | ||
| 3218 | - gap: 12px; | ||
| 3219 | - align-items: stretch; | 3224 | + flex-direction: column; |
| 3225 | + gap: 16px; | ||
| 3220 | }} | 3226 | }} |
| 3221 | body.exporting .swot-cell {{ | 3227 | body.exporting .swot-cell {{ |
| 3222 | - flex: 1 1 320px; | ||
| 3223 | - min-width: 240px; | 3228 | + width: 100%; |
| 3224 | height: auto; | 3229 | height: auto; |
| 3230 | + page-break-inside: avoid; | ||
| 3231 | + break-inside: avoid; | ||
| 3232 | +}} | ||
| 3233 | +body.exporting .swot-cell--first {{ | ||
| 3234 | + page-break-before: avoid; | ||
| 3235 | + break-before: avoid; | ||
| 3236 | +}} | ||
| 3237 | +/* 打印模式下的SWOT分页控制 */ | ||
| 3238 | +@media print {{ | ||
| 3239 | + .swot-card {{ | ||
| 3240 | + break-inside: auto; | ||
| 3241 | + page-break-inside: auto; | ||
| 3242 | + }} | ||
| 3243 | + .swot-card__head {{ | ||
| 3244 | + break-after: avoid; | ||
| 3245 | + page-break-after: avoid; | ||
| 3246 | + }} | ||
| 3247 | + .swot-grid {{ | ||
| 3248 | + display: flex; | ||
| 3249 | + flex-direction: column; | ||
| 3250 | + gap: 16px; | ||
| 3251 | + }} | ||
| 3252 | + .swot-cell {{ | ||
| 3253 | + break-inside: avoid; | ||
| 3254 | + page-break-inside: avoid; | ||
| 3255 | + width: 100%; | ||
| 3256 | + }} | ||
| 3257 | + .swot-cell--first {{ | ||
| 3258 | + break-before: avoid; | ||
| 3259 | + page-break-before: avoid; | ||
| 3260 | + }} | ||
| 3261 | + .swot-cell__meta {{ | ||
| 3262 | + break-after: avoid; | ||
| 3263 | + page-break-after: avoid; | ||
| 3264 | + }} | ||
| 3265 | + .swot-list {{ | ||
| 3266 | + break-inside: avoid; | ||
| 3267 | + page-break-inside: avoid; | ||
| 3268 | + }} | ||
| 3269 | + .swot-item {{ | ||
| 3270 | + break-inside: avoid; | ||
| 3271 | + page-break-inside: avoid; | ||
| 3272 | + }} | ||
| 3225 | }} | 3273 | }} |
| 3226 | .callout {{ | 3274 | .callout {{ |
| 3227 | border-left: 4px solid var(--primary-color); | 3275 | border-left: 4px solid var(--primary-color); |
| @@ -1049,35 +1049,78 @@ body {{ | @@ -1049,35 +1049,78 @@ body {{ | ||
| 1049 | min-height: 400px; | 1049 | min-height: 400px; |
| 1050 | }} | 1050 | }} |
| 1051 | 1051 | ||
| 1052 | -/* SWOT:PDF导出隐藏四象限标注,并使用自适应布局避免重叠 */ | 1052 | +/* ========== SWOT PDF分页优化 ========== */ |
| 1053 | +/* 核心策略:S/W/O/T四个象限各自内部禁止分页,但允许象限之间分页 */ | ||
| 1054 | + | ||
| 1055 | +/* 隐藏四象限标注图例 */ | ||
| 1053 | .swot-legend {{ | 1056 | .swot-legend {{ |
| 1054 | display: none !important; | 1057 | display: none !important; |
| 1055 | }} | 1058 | }} |
| 1059 | + | ||
| 1060 | +/* SWOT卡片容器:允许内部分页 */ | ||
| 1056 | .swot-card {{ | 1061 | .swot-card {{ |
| 1057 | - /* 允许卡片内容在必要时分页,避免整体被抬到下一页 */ | ||
| 1058 | break-inside: auto !important; | 1062 | break-inside: auto !important; |
| 1059 | page-break-inside: auto !important; | 1063 | page-break-inside: auto !important; |
| 1064 | + margin: 20px 0; | ||
| 1060 | }} | 1065 | }} |
| 1066 | + | ||
| 1067 | +/* 卡片头部(标题+摘要):避免紧跟其后分页,尽量与第一个象限保持在一起 */ | ||
| 1061 | .swot-card__head {{ | 1068 | .swot-card__head {{ |
| 1062 | - break-after: avoid; | ||
| 1063 | - page-break-after: avoid; | 1069 | + break-after: avoid !important; |
| 1070 | + page-break-after: avoid !important; | ||
| 1071 | + break-inside: avoid !important; | ||
| 1072 | + page-break-inside: avoid !important; | ||
| 1064 | }} | 1073 | }} |
| 1074 | + | ||
| 1075 | +/* 网格容器:PDF模式下使用纵向flex布局,允许子元素间分页 */ | ||
| 1065 | .swot-grid {{ | 1076 | .swot-grid {{ |
| 1066 | - display: flex; | ||
| 1067 | - flex-wrap: wrap; | ||
| 1068 | - gap: 12px; | ||
| 1069 | - align-items: stretch; | ||
| 1070 | - break-before: avoid; | ||
| 1071 | - page-break-before: avoid; | ||
| 1072 | - break-inside: auto; | ||
| 1073 | - page-break-inside: auto; | 1077 | + display: flex !important; |
| 1078 | + flex-direction: column !important; | ||
| 1079 | + gap: 16px !important; | ||
| 1080 | + break-inside: auto !important; | ||
| 1081 | + page-break-inside: auto !important; | ||
| 1074 | }} | 1082 | }} |
| 1075 | -.swot-grid .swot-cell {{ | ||
| 1076 | - flex: 1 1 320px; | ||
| 1077 | - min-width: 240px; | ||
| 1078 | - height: auto; | ||
| 1079 | - page-break-inside: avoid; | ||
| 1080 | - break-inside: avoid; | 1083 | + |
| 1084 | +/* 每个SWOT象限单元格:禁止内部分页,允许前后分页 */ | ||
| 1085 | +.swot-cell {{ | ||
| 1086 | + break-inside: avoid !important; | ||
| 1087 | + page-break-inside: avoid !important; | ||
| 1088 | + break-before: auto; | ||
| 1089 | + page-break-before: auto; | ||
| 1090 | + break-after: auto; | ||
| 1091 | + page-break-after: auto; | ||
| 1092 | + width: 100% !important; | ||
| 1093 | + max-width: 100% !important; | ||
| 1094 | + flex: none !important; | ||
| 1095 | + min-height: auto !important; | ||
| 1096 | + height: auto !important; | ||
| 1097 | + box-sizing: border-box; | ||
| 1098 | +}} | ||
| 1099 | + | ||
| 1100 | +/* 第一个象限:避免在标题后立即分页 */ | ||
| 1101 | +.swot-cell--first {{ | ||
| 1102 | + break-before: avoid !important; | ||
| 1103 | + page-break-before: avoid !important; | ||
| 1104 | +}} | ||
| 1105 | + | ||
| 1106 | +/* 象限内的meta区域(图标+标题):避免被分页切开 */ | ||
| 1107 | +.swot-cell__meta {{ | ||
| 1108 | + break-inside: avoid !important; | ||
| 1109 | + page-break-inside: avoid !important; | ||
| 1110 | + break-after: avoid !important; | ||
| 1111 | + page-break-after: avoid !important; | ||
| 1112 | +}} | ||
| 1113 | + | ||
| 1114 | +/* 条目列表:允许列表整体分页 */ | ||
| 1115 | +.swot-list {{ | ||
| 1116 | + break-inside: avoid !important; | ||
| 1117 | + page-break-inside: avoid !important; | ||
| 1118 | +}} | ||
| 1119 | + | ||
| 1120 | +/* 单个条目:避免被分页切开 */ | ||
| 1121 | +.swot-item {{ | ||
| 1122 | + break-inside: avoid !important; | ||
| 1123 | + page-break-inside: avoid !important; | ||
| 1081 | }} | 1124 | }} |
| 1082 | 1125 | ||
| 1083 | {optimized_css} | 1126 | {optimized_css} |
-
Please register or login to post a comment