diff --git a/data/2026-06-08/cleaned/危大方案编审进度看板.pptx b/data/2026-06-08/cleaned/危大方案编审进度看板.pptx new file mode 100644 index 0000000..c5a6ecb Binary files /dev/null and b/data/2026-06-08/cleaned/危大方案编审进度看板.pptx differ diff --git a/src/gen_pptx.py b/src/gen_pptx.py new file mode 100644 index 0000000..79ff277 --- /dev/null +++ b/src/gen_pptx.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +"""HTML看板 → 可编辑PPTX (python-pptx) · CHEC规范 16:9""" +from pptx import Presentation +from pptx.util import Inches, Pt, Emu, Cm +from pptx.dml.color import RGBColor +from pptx.enum.text import PP_ALIGN, MSO_ANCHOR +from pptx.enum.shapes import MSO_SHAPE + +# ── CHEC品牌色 ── +BLUE = RGBColor(0x1A, 0x3A, 0x5C) +GOLD = RGBColor(0xC8, 0x96, 0x2E) +RED = RGBColor(0xD9, 0x4E, 0x34) +GRAY = RGBColor(0x88, 0x99, 0xAA) +BG = RGBColor(0xEB, 0xF0, 0xF7) +WHITE = RGBColor(0xFF, 0xFF, 0xFF) +BLACK = RGBColor(0x33, 0x33, 0x33) +GREEN = RGBColor(0x2E, 0x7D, 0x32) +BORDER = RGBColor(0xDB, 0xE2, 0xEA) + +prs = Presentation() +prs.slide_width = Inches(13.333) # 16:9 +prs.slide_height = Inches(7.5) + +def add_rect(slide, left, top, w, h, fill_color=None, line_color=None): + shape = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, left, top, w, h) + shape.line.fill.background() + if fill_color: + shape.fill.solid() + shape.fill.fore_color.rgb = fill_color + if line_color: + shape.line.color.rgb = line_color + shape.line.width = Pt(0.5) + else: + shape.line.fill.background() + return shape + +def add_text_box(slide, left, top, w, h, text, font_size=12, color=BLACK, bold=False, font_name='Microsoft YaHei', align=PP_ALIGN.LEFT): + txBox = slide.shapes.add_textbox(left, top, w, h) + txBox.text_frame.word_wrap = True + p = txBox.text_frame.paragraphs[0] + p.text = text + p.font.size = Pt(font_size) + p.font.color.rgb = color + p.font.bold = bold + p.font.name = font_name + p.alignment = align + return txBox + +def add_kpi_card(slide, left, top, w, h, number, label, num_color=BLUE, num_size=44): + """统一KPI卡片""" + card = add_rect(slide, left, top, w, h, fill_color=BG) + card.shadow.inherit = False + num_box = add_text_box(slide, left, top + Inches(0.08), w, Inches(0.55), str(number), + font_size=num_size, color=num_color, bold=True, align=PP_ALIGN.CENTER) + add_text_box(slide, left, top + Inches(0.58), w, Inches(0.2), label, + font_size=9, color=GRAY, align=PP_ALIGN.CENTER) + +# ═══════════════════════════════════════ +# Slide 1: 关键指标 +# ═══════════════════════════════════════ +s1 = prs.slides.add_slide(prs.slide_layouts[6]) # blank + +# Nav bar +nav = add_rect(s1, Inches(0), Inches(0), Inches(13.333), Inches(0.44), fill_color=BLUE) +add_text_box(s1, Inches(0.5), Inches(0.08), Inches(2), Inches(0.3), '★ 关键指标', font_size=11, color=GOLD, bold=True) +add_text_box(s1, Inches(2.5), Inches(0.08), Inches(2), Inches(0.3), '国别分布', font_size=11, color=GRAY) + +# Title +add_rect(s1, Inches(0.5), Inches(0.65), Inches(0.06), Inches(0.3), fill_color=GOLD) +add_text_box(s1, Inches(0.7), Inches(0.6), Inches(8), Inches(0.4), '危大方案编审进度看板', font_size=22, color=BLUE, bold=True) +add_text_box(s1, Inches(8.5), Inches(0.68), Inches(4.5), Inches(0.3), '中国港湾中东区域公司 技术部 · 2026年6月 · 数据截止 2026-06-08', font_size=10, color=GRAY, align=PP_ALIGN.RIGHT) + +# === ROW 1: Module 1 & 2 === +y_row1 = Inches(1.2) +mod_w = Inches(5.9) + +# Module 1: 年度认定 +m1 = add_rect(s1, Inches(0.5), y_row1, mod_w, Inches(1.7), fill_color=WHITE, line_color=BORDER) +add_rect(s1, Inches(0.5), y_row1, mod_w, Inches(0.38), fill_color=BLUE) +add_text_box(s1, Inches(0.7), y_row1 + Inches(0.05), Inches(3), Inches(0.28), '1. 年度认定', font_size=12, color=WHITE, bold=True) +add_text_box(s1, Inches(2.2), y_row1 + Inches(0.06), Inches(2.5), Inches(0.25), '中港科技便〔2026〕6号', font_size=9, color=GOLD) + +add_kpi_card(s1, Inches(0.7), y_row1 + Inches(0.5), Inches(1.2), Inches(0.95), '43', '安全专项', num_size=44) +add_text_box(s1, Inches(0.7), y_row1 + Inches(1.48), Inches(1.2), Inches(0.18), '覆盖 7 个项目', font_size=9, color=GRAY, align=PP_ALIGN.CENTER) + +# Donut chart legend +add_text_box(s1, Inches(2.3), y_row1 + Inches(0.6), Inches(1.5), Inches(0.3), '■ 一般类 27', font_size=12, color=BLUE) +add_text_box(s1, Inches(2.3), y_row1 + Inches(0.9), Inches(1.5), Inches(0.3), '■ 超规类 16', font_size=12, color=GOLD) + +# Module 2: OA有效登记 +x_m2 = Inches(6.93) +m2 = add_rect(s1, x_m2, y_row1, mod_w, Inches(1.7), fill_color=WHITE, line_color=BORDER) +add_rect(s1, x_m2, y_row1, mod_w, Inches(0.38), fill_color=BLUE) +add_text_box(s1, x_m2 + Inches(0.2), y_row1 + Inches(0.05), Inches(3), Inches(0.28), '2. OA有效登记', font_size=12, color=WHITE, bold=True) +add_text_box(s1, x_m2 + Inches(1.8), y_row1 + Inches(0.06), Inches(2), Inches(0.25), '排除已作废', font_size=9, color=GOLD) + +add_kpi_card(s1, x_m2 + Inches(0.2), y_row1 + Inches(0.5), Inches(1.2), Inches(0.95), '52', '有效登记', num_size=44) +add_text_box(s1, x_m2 + Inches(0.2), y_row1 + Inches(1.48), Inches(1.2), Inches(0.18), '登记率 121%', font_size=9, color=GRAY, align=PP_ALIGN.CENTER) +add_text_box(s1, x_m2 + Inches(2.3), y_row1 + Inches(0.6), Inches(1.5), Inches(0.3), '■ 一般类 30', font_size=12, color=BLUE) +add_text_box(s1, x_m2 + Inches(2.3), y_row1 + Inches(0.9), Inches(1.5), Inches(0.3), '■ 超规类 22', font_size=12, color=GOLD) + +# === ROW 2: Module 3 & 4 === +y_row2 = Inches(3.1) + +# Module 3: 国别 +m3 = add_rect(s1, Inches(0.5), y_row2, mod_w, Inches(2.0), fill_color=WHITE, line_color=BORDER) +add_rect(s1, Inches(0.5), y_row2, mod_w, Inches(0.36), fill_color=BLUE) +add_text_box(s1, Inches(0.7), y_row2 + Inches(0.04), Inches(4), Inches(0.28), '3. 按国别分布 · 分层条形图', font_size=11, color=WHITE, bold=True) + +# Bar chart - simplified table +countries = [('阿拉伯联合酋长国', 27, 18, 45), ('沙特阿拉伯', 3, 3, 6), ('卡塔尔', 0, 1, 1)] +y_bar = y_row2 + Inches(0.45) +for i, (name, gen, chao, tot) in enumerate(countries): + y = y_bar + Inches(i * 0.45) + add_text_box(s1, Inches(0.6), y, Inches(1.8), Inches(0.2), name, font_size=10, color=BLACK, align=PP_ALIGN.RIGHT) + bw = max(gen * 0.07, 0.05) if gen > 0 else 0 + if bw > 0: + add_rect(s1, Inches(2.5), y + Inches(0.02), Inches(bw), Inches(0.22), fill_color=BLUE) + cw = max(chao * 0.07, 0.05) if chao > 0 else 0 + if cw > 0: + add_rect(s1, Inches(2.5) + Inches(bw), y + Inches(0.02), Inches(cw), Inches(0.22), fill_color=GOLD) + add_text_box(s1, Inches(5.2), y, Inches(0.5), Inches(0.22), str(tot), font_size=12, color=BLUE, bold=True) + +add_text_box(s1, Inches(0.6), y_row2 + Inches(1.78), Inches(2), Inches(0.18), '■ 一般类', font_size=9, color=BLUE) +add_text_box(s1, Inches(1.5), y_row2 + Inches(1.78), Inches(2), Inches(0.18), '■ 超规类', font_size=9, color=GOLD) + +# Module 4: 审批进度 +m4 = add_rect(s1, x_m2, y_row2, mod_w, Inches(2.0), fill_color=WHITE, line_color=BORDER) +add_rect(s1, x_m2, y_row2, mod_w, Inches(0.36), fill_color=BLUE) +add_text_box(s1, x_m2 + Inches(0.2), y_row2 + Inches(0.04), Inches(4), Inches(0.28), '4. 审批进度 & 三色预警信号', font_size=11, color=WHITE, bold=True) + +# Row 1 cards +cy = y_row2 + Inches(0.5) +cw, ch = Inches(0.85), Inches(0.7) +gap = Inches(0.12) +for idx, (num, label, color) in enumerate([ + ('5', '预警总计', RED), ('🔴0', '红色', RED), ('🟠1', '橙色', RED), ('🟡4', '黄色', GOLD), ('23', '未完成审批', GRAY)]): + cx = x_m2 + Inches(0.15) + Inches(idx * 1.05) + add_kpi_card(s1, cx, cy, cw, ch, num, label, num_color=color, num_size=28 if idx > 0 else 36) + +# Legend +add_text_box(s1, x_m2 + Inches(4.8), cy, Inches(1.2), Inches(0.55), + '🔴 在实施未审批\n🟠 ≤30天\n🟡 ≤45天', font_size=8, color=GRAY, align=PP_ALIGN.RIGHT) + +# Row 2: 审批率 + progress bar +py = y_row2 + Inches(1.35) +add_kpi_card(s1, x_m2 + Inches(0.15), py, Inches(1.0), Inches(0.7), '56%', '审批完成率', num_color=GREEN, num_size=36) +# Progress bar +bar_bg = add_rect(s1, x_m2 + Inches(1.35), py + Inches(0.12), Inches(3.5), Inches(0.22), fill_color=BG) +bar_fg = add_rect(s1, x_m2 + Inches(1.35), py + Inches(0.12), Inches(3.5 * 0.56), Inches(0.22), fill_color=BLUE) +add_text_box(s1, x_m2 + Inches(1.35), py + Inches(0.38), Inches(3.5), Inches(0.18), + '已审批 29 未审批 23 / 总计 52', font_size=8, color=GRAY) + +# Footer +add_rect(s1, Inches(0), Inches(7.18), Inches(13.333), Inches(0.32), fill_color=RGBColor(0xF5, 0xF6, 0xF8)) +add_text_box(s1, Inches(0.5), Inches(7.2), Inches(4), Inches(0.25), '中国港湾中东区域公司 技术部', font_size=8, color=GRAY) +add_text_box(s1, Inches(11.5), Inches(7.2), Inches(1.5), Inches(0.25), '1 / 2', font_size=8, color=GRAY, align=PP_ALIGN.RIGHT) + +# ═══════════════════════════════════════ +# Slide 2: 预警明细 +# ═══════════════════════════════════════ +s2 = prs.slides.add_slide(prs.slide_layouts[6]) +add_rect(s2, Inches(0), Inches(0), Inches(13.333), Inches(0.44), fill_color=BLUE) +add_text_box(s2, Inches(0.5), Inches(0.08), Inches(2), Inches(0.3), '关键指标', font_size=11, color=GRAY) +add_text_box(s2, Inches(2.5), Inches(0.08), Inches(2), Inches(0.3), '★ 预警明细', font_size=11, color=GOLD, bold=True) + +add_rect(s2, Inches(0.5), Inches(0.65), Inches(0.06), Inches(0.3), fill_color=GOLD) +add_text_box(s2, Inches(0.7), Inches(0.6), Inches(8), Inches(0.4), '预警信号明细清单', font_size=22, color=BLUE, bold=True) +add_text_box(s2, Inches(8.5), Inches(0.68), Inches(4.5), Inches(0.3), '共 5 项预警', font_size=10, color=GRAY, align=PP_ALIGN.RIGHT) + +# Table +warnings = [ + ('🟠', '阿联酋迪拜马克图姆国际机场地下结构工程', 'BHS/GSE隧道现浇板(4包)', '已添加未实施', '2天'), + ('🟡', '阿联酋阿布扎比汽车基地房建项目', '模板支立工程', '未审批未实施', '32天'), + ('🟡', '阿联酋阿布扎比汽车基地房建项目', '深基坑开挖方案', '未审批未实施', '37天'), + ('🟡', '阿联酋迪拜马克图姆国际机场地下结构工程', '现浇倒T梁(4包)', '审批中未实施', '42天'), + ('🟡', '阿联酋迪拜马克图姆国际机场地下结构工程', 'T梁预制运输安装(4包)', '已添加未实施', '42天'), +] + +ty = Inches(1.15) +# Table header +th_h = Inches(0.32) +cols = [0.5, 3.0, 3.0, 3.0, 2.5] +headers = ['信号', '项目名称', '方案名称', '当前状态', '距开工'] +for i, (h, w) in enumerate(zip(headers, cols)): + add_rect(s2, Inches(0.5 + sum(cols[:i])), ty, Inches(w), th_h, fill_color=BLUE) + add_text_box(s2, Inches(0.55 + sum(cols[:i])), ty + Inches(0.02), Inches(w - 0.1), Inches(0.28), h, font_size=10, color=WHITE, bold=True) + +for j, (icon, proj, scheme, status, days) in enumerate(warnings): + y = ty + Inches(0.32 + j * 0.38) + bg_c = BG if j % 2 == 0 else WHITE + add_rect(s2, Inches(0.5), y, Inches(12.0), Inches(0.38), fill_color=bg_c) + add_text_box(s2, Inches(0.55), y + Inches(0.04), Inches(0.4), Inches(0.3), icon, font_size=14, align=PP_ALIGN.CENTER) + add_text_box(s2, Inches(1.0), y + Inches(0.04), Inches(3.8), Inches(0.3), proj, font_size=10) + add_text_box(s2, Inches(4.8), y + Inches(0.04), Inches(3.0), Inches(0.3), scheme, font_size=10) + add_text_box(s2, Inches(7.8), y + Inches(0.04), Inches(2.0), Inches(0.3), status, font_size=10) + add_text_box(s2, Inches(9.8), y + Inches(0.04), Inches(1.5), Inches(0.3), days, font_size=11, color=RED, bold=True, align=PP_ALIGN.RIGHT) + +# Rule box +add_rect(s2, Inches(0.5), Inches(4.0), Inches(12.0), Inches(0.5), fill_color=BG) +add_rect(s2, Inches(0.5), Inches(4.0), Inches(0.06), Inches(0.5), fill_color=GOLD) +add_text_box(s2, Inches(0.75), Inches(4.05), Inches(11.5), Inches(0.4), + '📐 预警规则:🟠 橙色 ≤30天未审批 · 🟡 黄色 ≤45天未审批 · 🔴 红色 在实施未审批(本月0项)', font_size=10, color=BLACK) + +# Footer +add_rect(s2, Inches(0), Inches(7.18), Inches(13.333), Inches(0.32), fill_color=RGBColor(0xF5, 0xF6, 0xF8)) +add_text_box(s2, Inches(0.5), Inches(7.2), Inches(4), Inches(0.25), '中国港湾中东区域公司 技术部', font_size=8, color=GRAY) +add_text_box(s2, Inches(11.5), Inches(7.2), Inches(1.5), Inches(0.25), '2 / 2', font_size=8, color=GRAY, align=PP_ALIGN.RIGHT) + +# ── Save ── +out = "/mnt/y/Openclaw_Hub/03.资源/实施项目 wiki/dashboard/data/2026-06-08/cleaned/危大方案编审进度看板.pptx" +prs.save(out) +print(f"✅ PPTX已生成: {out}") +print(f" Slide 1: 关键指标(4模块)") +print(f" Slide 2: 预警明细(5项)") +print(f" 所有文字可编辑 · 16:9 · CHEC规范配色")