fix: PPTX v2 - 原生饼图+三色卡修正+间距重算

This commit is contained in:
大师 2026-06-09 02:21:40 +08:00
parent 10a0748738
commit d29843231c
2 changed files with 173 additions and 169 deletions

View File

@ -1,216 +1,220 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""HTML看板 → 可编辑PPTX (python-pptx) · CHEC规范 16:9""" """HTML看板 → 可编辑PPTX v2 · 加饼图·修正三色卡·调间距"""
from pptx import Presentation from pptx import Presentation
from pptx.util import Inches, Pt, Emu, Cm from pptx.util import Inches, Pt, Cm
from pptx.dml.color import RGBColor from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR from pptx.enum.text import PP_ALIGN
from pptx.enum.shapes import MSO_SHAPE from pptx.enum.shapes import MSO_SHAPE
from pptx.chart.data import CategoryChartData
from pptx.enum.chart import XL_CHART_TYPE
# ── CHEC品牌色 ── # ── 品牌色 ──
BLUE = RGBColor(0x1A, 0x3A, 0x5C) BLUE = RGBColor(0x1A,0x3A,0x5C)
GOLD = RGBColor(0xC8, 0x96, 0x2E) GOLD = RGBColor(0xC8,0x96,0x2E)
RED = RGBColor(0xD9, 0x4E, 0x34) RED = RGBColor(0xD9,0x4E,0x34)
GRAY = RGBColor(0x88, 0x99, 0xAA) GRAY = RGBColor(0x88,0x99,0xAA)
BG = RGBColor(0xEB, 0xF0, 0xF7) BG = RGBColor(0xEB,0xF0,0xF7)
WHITE = RGBColor(0xFF, 0xFF, 0xFF) WHITE = RGBColor(0xFF,0xFF,0xFF)
BLACK = RGBColor(0x33, 0x33, 0x33) BLACK = RGBColor(0x33,0x33,0x33)
GREEN = RGBColor(0x2E, 0x7D, 0x32) GREEN = RGBColor(0x2E,0x7D,0x32)
BORDER = RGBColor(0xDB, 0xE2, 0xEA)
prs = Presentation() prs = Presentation()
prs.slide_width = Inches(13.333) # 16:9 prs.slide_width = Inches(13.333)
prs.slide_height = Inches(7.5) prs.slide_height = Inches(7.5)
def add_rect(slide, left, top, w, h, fill_color=None, line_color=None): def R(l,t,w,h,fill=None,line=None):
shape = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, left, top, w, h) s=prs.slides[-1].shapes.add_shape(MSO_SHAPE.RECTANGLE,l,t,w,h)
shape.line.fill.background() s.line.fill.background()
if fill_color: if fill: s.fill.solid(); s.fill.fore_color.rgb=fill
shape.fill.solid() if line: s.line.color.rgb=line; s.line.width=Pt(0.5)
shape.fill.fore_color.rgb = fill_color return s
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): def T(l,t,w,h,txt,sz=12,clr=BLACK,b=False,fn='Microsoft YaHei',al=PP_ALIGN.LEFT):
txBox = slide.shapes.add_textbox(left, top, w, h) tb=prs.slides[-1].shapes.add_textbox(l,t,w,h)
txBox.text_frame.word_wrap = True tb.text_frame.word_wrap=True
p = txBox.text_frame.paragraphs[0] p=tb.text_frame.paragraphs[0]; p.text=txt
p.text = text p.font.size=Pt(sz); p.font.color.rgb=clr; p.font.bold=b
p.font.size = Pt(font_size) p.font.name=fn; p.alignment=al
p.font.color.rgb = color return tb
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): def kpi(l,t,w,h,num,lbl,nc=BLUE,ns=44,ls=9):
"""统一KPI卡片""" """KPI卡片方块"""
card = add_rect(slide, left, top, w, h, fill_color=BG) R(l,t,w,h,fill=BG)
card.shadow.inherit = False T(l,t+Inches(0.06),w,Inches(0.5),str(num),sz=ns,clr=nc,b=True,al=PP_ALIGN.CENTER)
num_box = add_text_box(slide, left, top + Inches(0.08), w, Inches(0.55), str(number), T(l,t+Inches(0.55),w,Inches(0.18),lbl,sz=ls,clr=GRAY,al=PP_ALIGN.CENTER)
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, def donut(l,t,w,h,vals,labels,colors):
font_size=9, color=GRAY, align=PP_ALIGN.CENTER) """原生饼图"""
cd=CategoryChartData()
cd.categories=labels
cd.add_series('',vals)
ch=prs.slides[-1].shapes.add_chart(XL_CHART_TYPE.DOUGHNUT,l,t,w,h,cd)
plot=ch.chart.plots[0]
for i,clr in enumerate(colors):
plot.series[0].points[i].format.fill.solid()
plot.series[0].points[i].format.fill.fore_color.rgb=clr
ch.chart.has_legend=False
ch.chart.has_title=False
return ch
# ═══════════════════════════════════════ # ═══════════════════════════════════════
# Slide 1: 关键指标 # SLIDE 1
# ═══════════════════════════════════════ # ═══════════════════════════════════════
s1 = prs.slides.add_slide(prs.slide_layouts[6]) # blank s1=prs.slides.add_slide(prs.slide_layouts[6])
# Nav bar # Nav
nav = add_rect(s1, Inches(0), Inches(0), Inches(13.333), Inches(0.44), fill_color=BLUE) R(Inches(0),Inches(0),Inches(13.333),Inches(0.42),fill=BLUE)
add_text_box(s1, Inches(0.5), Inches(0.08), Inches(2), Inches(0.3), '★ 关键指标', font_size=11, color=GOLD, bold=True) T(Inches(0.5),Inches(0.07),Inches(2),Inches(0.28),'★ 关键指标',sz=11,clr=GOLD,b=True)
add_text_box(s1, Inches(2.5), Inches(0.08), Inches(2), Inches(0.3), '国别分布', font_size=11, color=GRAY) T(Inches(2.5),Inches(0.07),Inches(2),Inches(0.28),'国别分布',sz=11,clr=GRAY)
T(Inches(4.5),Inches(0.07),Inches(2),Inches(0.28),'预警明细',sz=11,clr=GRAY)
# Title # Title
add_rect(s1, Inches(0.5), Inches(0.65), Inches(0.06), Inches(0.3), fill_color=GOLD) R(Inches(0.5),Inches(0.58),Inches(0.06),Inches(0.28),fill=GOLD)
add_text_box(s1, Inches(0.7), Inches(0.6), Inches(8), Inches(0.4), '危大方案编审进度看板', font_size=22, color=BLUE, bold=True) T(Inches(0.7),Inches(0.55),Inches(8),Inches(0.36),'危大方案编审进度看板',sz=22,clr=BLUE,b=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) T(Inches(8.5),Inches(0.62),Inches(4.5),Inches(0.28),'中国港湾中东区域公司 技术部 · 2026年6月',sz=9,clr=GRAY,al=PP_ALIGN.RIGHT)
# === ROW 1: Module 1 & 2 === # === ROW 1: M1 + M2 ===
y_row1 = Inches(1.2) RY1=Inches(1.1); MW=Inches(5.9); MH=Inches(1.85)
mod_w = Inches(5.9)
# Module 1: 年度认定 # Module 1
m1 = add_rect(s1, Inches(0.5), y_row1, mod_w, Inches(1.7), fill_color=WHITE, line_color=BORDER) R(Inches(0.5),RY1,MW,MH,fill=WHITE,line=RGBColor(0xDB,0xE2,0xEA))
add_rect(s1, Inches(0.5), y_row1, mod_w, Inches(0.38), fill_color=BLUE) R(Inches(0.5),RY1,MW,Inches(0.38),fill=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) T(Inches(0.7),RY1+Inches(0.04),Inches(3),Inches(0.3),'1. 年度认定',sz=13,clr=WHITE,b=True)
add_text_box(s1, Inches(2.2), y_row1 + Inches(0.06), Inches(2.5), Inches(0.25), '中港科技便20266号', font_size=9, color=GOLD) T(Inches(2.2),RY1+Inches(0.05),Inches(2.5),Inches(0.28),'中港科技便20266号',sz=9,clr=GOLD)
add_kpi_card(s1, Inches(0.7), y_row1 + Inches(0.5), Inches(1.2), Inches(0.95), '43', '安全专项', num_size=44) kpi(Inches(0.7),RY1+Inches(0.58),Inches(1.1),Inches(1.0),'43','安全专项总数',ns=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(Inches(2.1),RY1+Inches(0.55),Inches(1.2),Inches(1.2),[27,16],['一般类','超规类'],[BLUE,GOLD])
T(Inches(3.5),RY1+Inches(0.7),Inches(1.5),Inches(0.25),'■ 一般类 27',sz=11,clr=BLUE)
T(Inches(3.5),RY1+Inches(1.0),Inches(1.5),Inches(0.25),'■ 超规类 16',sz=11,clr=GOLD)
T(Inches(3.5),RY1+Inches(1.3),Inches(1.5),Inches(0.25),'覆盖 7 个项目',sz=9,clr=GRAY)
# Donut chart legend # Module 2
add_text_box(s1, Inches(2.3), y_row1 + Inches(0.6), Inches(1.5), Inches(0.3), '■ 一般类 27', font_size=12, color=BLUE) MX2=Inches(6.93)
add_text_box(s1, Inches(2.3), y_row1 + Inches(0.9), Inches(1.5), Inches(0.3), '■ 超规类 16', font_size=12, color=GOLD) R(MX2,RY1,MW,MH,fill=WHITE,line=RGBColor(0xDB,0xE2,0xEA))
R(MX2,RY1,MW,Inches(0.38),fill=BLUE)
T(MX2+Inches(0.2),RY1+Inches(0.04),Inches(3),Inches(0.3),'2. OA有效登记',sz=13,clr=WHITE,b=True)
T(MX2+Inches(1.8),RY1+Inches(0.05),Inches(2),Inches(0.28),'排除已作废',sz=9,clr=GOLD)
# Module 2: OA有效登记 kpi(MX2+Inches(0.2),RY1+Inches(0.58),Inches(1.1),Inches(1.0),'52','有效登记总数',ns=44)
x_m2 = Inches(6.93) donut(MX2+Inches(1.6),RY1+Inches(0.55),Inches(1.2),Inches(1.2),[30,22],['一般类','超规类'],[BLUE,GOLD])
m2 = add_rect(s1, x_m2, y_row1, mod_w, Inches(1.7), fill_color=WHITE, line_color=BORDER) T(MX2+Inches(3.0),RY1+Inches(0.7),Inches(1.5),Inches(0.25),'■ 一般类 30',sz=11,clr=BLUE)
add_rect(s1, x_m2, y_row1, mod_w, Inches(0.38), fill_color=BLUE) T(MX2+Inches(3.0),RY1+Inches(1.0),Inches(1.5),Inches(0.25),'■ 超规类 22',sz=11,clr=GOLD)
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) T(MX2+Inches(3.0),RY1+Inches(1.3),Inches(1.5),Inches(0.25),'登记率 121%52/43',sz=9,clr=GRAY)
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) # === ROW 2: M3 + M4 ===
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) RY2=Inches(3.15)
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: 国别 # Module 3: 国别
m3 = add_rect(s1, Inches(0.5), y_row2, mod_w, Inches(2.0), fill_color=WHITE, line_color=BORDER) R(Inches(0.5),RY2,MW,Inches(1.85),fill=WHITE,line=RGBColor(0xDB,0xE2,0xEA))
add_rect(s1, Inches(0.5), y_row2, mod_w, Inches(0.36), fill_color=BLUE) R(Inches(0.5),RY2,MW,Inches(0.36),fill=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) T(Inches(0.7),RY2+Inches(0.03),Inches(4),Inches(0.28),'3. 按国别分布 · 分层条形图',sz=11,clr=WHITE,b=True)
# Bar chart - simplified table countries=[('阿拉伯联合酋长国',27,18,45),('沙特阿拉伯',3,3,6),('卡塔尔',0,1,1)]
countries = [('阿拉伯联合酋长国', 27, 18, 45), ('沙特阿拉伯', 3, 3, 6), ('卡塔尔', 0, 1, 1)] y0=RY2+Inches(0.5)
y_bar = y_row2 + Inches(0.45) for i,(name,gn,ch,tot) in enumerate(countries):
for i, (name, gen, chao, tot) in enumerate(countries): y=y0+Inches(i*0.42)
y = y_bar + Inches(i * 0.45) T(Inches(0.55),y,Inches(1.5),Inches(0.22),name,sz=10,clr=BLACK,al=PP_ALIGN.RIGHT)
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(gn*0.065,0.03) if gn>0 else 0
bw = max(gen * 0.07, 0.05) if gen > 0 else 0 if bw>0: R(Inches(2.15),y+Inches(0.02),Inches(bw),Inches(0.2),fill=BLUE)
if bw > 0: cw=max(ch*0.065,0.03) if ch>0 else 0
add_rect(s1, Inches(2.5), y + Inches(0.02), Inches(bw), Inches(0.22), fill_color=BLUE) if cw>0: R(Inches(2.15)+Inches(bw),y+Inches(0.02),Inches(cw),Inches(0.2),fill=GOLD)
cw = max(chao * 0.07, 0.05) if chao > 0 else 0 T(Inches(5.3),y,Inches(0.5),Inches(0.22),str(tot),sz=12,clr=BLUE,b=True)
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) T(Inches(0.55),RY2+Inches(1.58),Inches(2),Inches(0.18),'■ 一般类',sz=9,clr=BLUE)
add_text_box(s1, Inches(1.5), y_row2 + Inches(1.78), Inches(2), Inches(0.18), '■ 超规类', font_size=9, color=GOLD) T(Inches(1.3),RY2+Inches(1.58),Inches(2),Inches(0.18),'■ 超规类',sz=9,clr=GOLD)
# Module 4: 审批进度 # Module 4: 审批+预警
m4 = add_rect(s1, x_m2, y_row2, mod_w, Inches(2.0), fill_color=WHITE, line_color=BORDER) R(MX2,RY2,MW,Inches(1.85),fill=WHITE,line=RGBColor(0xDB,0xE2,0xEA))
add_rect(s1, x_m2, y_row2, mod_w, Inches(0.36), fill_color=BLUE) R(MX2,RY2,MW,Inches(0.36),fill=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) T(MX2+Inches(0.2),RY2+Inches(0.03),Inches(4),Inches(0.28),'4. 审批进度 & 三色预警信号',sz=11,clr=WHITE,b=True)
# Row 1 cards # Row 1: 预警指标卡片 — 统一样式
cy = y_row2 + Inches(0.5) CARD_W=Inches(0.85); CARD_H=Inches(0.7); CARD_GAP=Inches(0.08)
cw, ch = Inches(0.85), Inches(0.7) CY=RY2+Inches(0.48)
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 warn_data=[
add_text_box(s1, x_m2 + Inches(4.8), cy, Inches(1.2), Inches(0.55), ('5', '预警总计', RED, 36),
'🔴 在实施未审批\n🟠 ≤30天\n🟡 ≤45天', font_size=8, color=GRAY, align=PP_ALIGN.RIGHT) ('0', '红色🔴', RED, 28),
('1', '橙色🟠', RED, 28),
('4', '黄色🟡', GOLD, 28),
('23', '未完成审批', GRAY, 28),
]
for idx,(num,lbl,clr,ns) in enumerate(warn_data):
cx=MX2+Inches(0.12)+Inches(idx*(CARD_W+CARD_GAP))
kpi(cx,CY,CARD_W,CARD_H,num,lbl,nc=clr,ns=ns,ls=8)
# Row 2: 审批率 + progress bar # Legend right-aligned
py = y_row2 + Inches(1.35) T(MX2+Inches(4.9),CY+Inches(0.05),Inches(1.0),Inches(0.65),
add_kpi_card(s1, x_m2 + Inches(0.15), py, Inches(1.0), Inches(0.7), '56%', '审批完成率', num_color=GREEN, num_size=36) '🔴 在实施未审批\n🟠 ≤30天\n🟡 ≤45天',sz=7,clr=GRAY,al=PP_ALIGN.RIGHT)
# Row 2: 审批率 + 进度条
PY=RY2+Inches(1.3)
kpi(MX2+Inches(0.12),PY,Inches(0.95),Inches(0.5),'56%','审批完成率',nc=GREEN,ns=32,ls=8)
# Progress bar # 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) R(MX2+Inches(1.2),PY+Inches(0.08),Inches(3.6),Inches(0.18),fill=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) R(MX2+Inches(1.2),PY+Inches(0.08),Inches(3.6*0.56),Inches(0.18),fill=BLUE)
add_text_box(s1, x_m2 + Inches(1.35), py + Inches(0.38), Inches(3.5), Inches(0.18), T(MX2+Inches(1.2),PY+Inches(0.28),Inches(3.6),Inches(0.18),
'已审批 29 未审批 23 / 总计 52', font_size=8, color=GRAY) '已审批 29 未审批 23 / 总计 52',sz=7,clr=GRAY)
# Footer # Footer
add_rect(s1, Inches(0), Inches(7.18), Inches(13.333), Inches(0.32), fill_color=RGBColor(0xF5, 0xF6, 0xF8)) R(Inches(0),Inches(7.18),Inches(13.333),Inches(0.32),fill=RGBColor(0xF5,0xF6,0xF8))
add_text_box(s1, Inches(0.5), Inches(7.2), Inches(4), Inches(0.25), '中国港湾中东区域公司 技术部', font_size=8, color=GRAY) T(Inches(0.5),Inches(7.2),Inches(4),Inches(0.2),'中国港湾中东区域公司 技术部',sz=8,clr=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) T(Inches(11.5),Inches(7.2),Inches(1.5),Inches(0.2),'1 / 2',sz=8,clr=GRAY,al=PP_ALIGN.RIGHT)
# ═══════════════════════════════════════ # ═══════════════════════════════════════
# Slide 2: 预警明细 # SLIDE 2: 预警明细
# ═══════════════════════════════════════ # ═══════════════════════════════════════
s2 = prs.slides.add_slide(prs.slide_layouts[6]) 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) R(Inches(0),Inches(0),Inches(13.333),Inches(0.42),fill=BLUE)
add_text_box(s2, Inches(0.7), Inches(0.6), Inches(8), Inches(0.4), '预警信号明细清单', font_size=22, color=BLUE, bold=True) T(Inches(0.5),Inches(0.07),Inches(2),Inches(0.28),'关键指标',sz=11,clr=GRAY)
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) T(Inches(2.5),Inches(0.07),Inches(2),Inches(0.28),'国别分布',sz=11,clr=GRAY)
T(Inches(4.5),Inches(0.07),Inches(2),Inches(0.28),'★ 预警明细',sz=11,clr=GOLD,b=True)
# Table R(Inches(0.5),Inches(0.58),Inches(0.06),Inches(0.28),fill=GOLD)
warnings = [ T(Inches(0.7),Inches(0.55),Inches(8),Inches(0.36),'预警信号明细清单',sz=22,clr=BLUE,b=True)
('🟠', '阿联酋迪拜马克图姆国际机场地下结构工程', 'BHS/GSE隧道现浇板(4包)', '已添加未实施', '2天'), T(Inches(8.5),Inches(0.62),Inches(4.5),Inches(0.28),'共 5 项预警',sz=10,clr=GRAY,al=PP_ALIGN.RIGHT)
('🟡', '阿联酋阿布扎比汽车基地房建项目', '模板支立工程', '未审批未实施', '32天'),
('🟡', '阿联酋阿布扎比汽车基地房建项目', '深基坑开挖方案', '未审批未实施', '37天'), warnings=[
('🟡', '阿联酋迪拜马克图姆国际机场地下结构工程', '现浇倒T梁(4包)', '审批中未实施', '42天'), ('🟠','阿联酋迪拜马克图姆国际机场地下结构工程','BHS/GSE隧道现浇板(4包)','已添加未实施','2天'),
('🟡', '阿联酋迪拜马克图姆国际机场地下结构工程', 'T梁预制运输安装(4包)', '已添加未实施', '42天'), ('🟡','阿联酋阿布扎比汽车基地房建项目','模板支立工程','未审批未实施','32天'),
('🟡','阿联酋阿布扎比汽车基地房建项目','深基坑开挖方案','未审批未实施','37天'),
('🟡','阿联酋迪拜马克图姆国际机场地下结构工程','现浇倒T梁(4包)','审批中未实施','42天'),
('🟡','阿联酋迪拜马克图姆国际机场地下结构工程','T梁预制运输安装(4包)','已添加未实施','42天'),
] ]
ty = Inches(1.15)
# Table header # Table header
th_h = Inches(0.32) TY=Inches(1.1); TH=Inches(0.32)
cols = [0.5, 3.0, 3.0, 3.0, 2.5] CW=[Inches(0.6),Inches(3.8),Inches(3.0),Inches(2.2),Inches(1.0)]
headers = ['信号', '项目名称', '方案名称', '当前状态', '距开工'] HDS=['信号','项目名称','方案名称','当前状态','距开工']
for i, (h, w) in enumerate(zip(headers, cols)): x_off=Inches(0.5)
add_rect(s2, Inches(0.5 + sum(cols[:i])), ty, Inches(w), th_h, fill_color=BLUE) for i,(h,w) in enumerate(zip(HDS,CW)):
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) R(x_off+sum(CW[:i]),TY,w,TH,fill=BLUE)
T(x_off+Inches(0.05)+sum(CW[:i]),TY+Inches(0.02),w-Inches(0.1),Inches(0.28),h,sz=10,clr=WHITE,b=True)
for j, (icon, proj, scheme, status, days) in enumerate(warnings): for j,(icon,proj,scheme,status,days) in enumerate(warnings):
y = ty + Inches(0.32 + j * 0.38) y=TY+TH+Inches(j*0.38)
bg_c = BG if j % 2 == 0 else WHITE 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) R(x_off,y,sum(CW),Inches(0.38),fill=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) T(x_off+Inches(0.05),y+Inches(0.04),Inches(0.5),Inches(0.3),icon,sz=16,al=PP_ALIGN.CENTER)
add_text_box(s2, Inches(1.0), y + Inches(0.04), Inches(3.8), Inches(0.3), proj, font_size=10) T(x_off+CW[0],y+Inches(0.04),CW[1]-Inches(0.1),Inches(0.3),proj,sz=10)
add_text_box(s2, Inches(4.8), y + Inches(0.04), Inches(3.0), Inches(0.3), scheme, font_size=10) T(x_off+CW[0]+CW[1],y+Inches(0.04),CW[2]-Inches(0.1),Inches(0.3),scheme,sz=10)
add_text_box(s2, Inches(7.8), y + Inches(0.04), Inches(2.0), Inches(0.3), status, font_size=10) T(x_off+CW[0]+CW[1]+CW[2],y+Inches(0.04),CW[3]-Inches(0.1),Inches(0.3),status,sz=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) T(x_off+sum(CW[:4]),y+Inches(0.04),CW[4]-Inches(0.1),Inches(0.3),days,sz=11,clr=RED,b=True,al=PP_ALIGN.RIGHT)
# Rule box # Rule
add_rect(s2, Inches(0.5), Inches(4.0), Inches(12.0), Inches(0.5), fill_color=BG) R(x_off,TY+TH+Inches(5*0.38+0.2),sum(CW),Inches(0.45),fill=BG)
add_rect(s2, Inches(0.5), Inches(4.0), Inches(0.06), Inches(0.5), fill_color=GOLD) R(x_off,TY+TH+Inches(5*0.38+0.2),Inches(0.06),Inches(0.45),fill=GOLD)
add_text_box(s2, Inches(0.75), Inches(4.05), Inches(11.5), Inches(0.4), T(x_off+Inches(0.2),TY+TH+Inches(5*0.38+0.25),Inches(11),Inches(0.35),
'📐 预警规则:🟠 橙色 ≤30天未审批 · 🟡 黄色 ≤45天未审批 · 🔴 红色 在实施未审批本月0项', font_size=10, color=BLACK) '📐 预警规则:🟠 橙色 ≤30天未审批 · 🟡 黄色 ≤45天未审批 · 🔴 红色 在实施未审批本月0项',sz=10,clr=BLACK)
# Footer R(Inches(0),Inches(7.18),Inches(13.333),Inches(0.32),fill=RGBColor(0xF5,0xF6,0xF8))
add_rect(s2, Inches(0), Inches(7.18), Inches(13.333), Inches(0.32), fill_color=RGBColor(0xF5, 0xF6, 0xF8)) T(Inches(0.5),Inches(7.2),Inches(4),Inches(0.2),'中国港湾中东区域公司 技术部',sz=8,clr=GRAY)
add_text_box(s2, Inches(0.5), Inches(7.2), Inches(4), Inches(0.25), '中国港湾中东区域公司 技术部', font_size=8, color=GRAY) T(Inches(11.5),Inches(7.2),Inches(1.5),Inches(0.2),'2 / 2',sz=8,clr=GRAY,al=PP_ALIGN.RIGHT)
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"
out = "/mnt/y/Openclaw_Hub/03.资源/实施项目 wiki/dashboard/data/2026-06-08/cleaned/危大方案编审进度看板.pptx"
prs.save(out) prs.save(out)
print(f"PPTX已生成: {out}") print(f"v2 已生成: {out}")
print(f" Slide 1: 关键指标4模块") print(f" + 原生饼图(一般/超规)")
print(f" Slide 2: 预警明细5项") print(f" + 三色预警统一卡片样式")
print(f" 所有文字可编辑 · 16:9 · CHEC规范配色") print(f" + 模块间距重新计算")