# 如何将公司PPT模板变成一劳永逸的Skill?
作者:小莴AI实测 | 发布于 2026年6月
阅读提示: 全文约 3500 字,包含完整代码实现。预计阅读时间 8 分钟。文中提供了可直接使用的脚本,复制到本地即可运行。

每次做汇报都要从零开始套格式?
周报、月报、季度总结,每次改个标题就要折腾半小时?项目汇报改了数据忘了改日期,封面标题和内容对不上号?
这件事本身就值得被自动化。
核心思路很简单:把 PPT 模板分析一遍,识别每个占位符的含义,然后写一个 OpenClaw Skill,让你用自然语言驱动填充过程。
以后只要说"帮我生成一份Q2工作总结",AI 自动把内容塞进正确的位置。
这套方案的分工非常清晰:
OpenClaw Skill 负责理解你的意图——你说什么,它就调什么。
python-pptx 脚本负责操作 PPT——打开模板、找到占位符、写入内容、输出文件。
Skill 只是个"翻译层",把自然语言转成脚本调用。
整个流程分四步走:
分析模板 → 写填充脚本 → 打包成 Skill → 测试触发
先用 python-pptx 把你现有的模板"读"一遍,搞清楚每个占位符在哪里、叫什么、是什么类型。
安装依赖:
pip install python-pptx -i https://mirrors.aliyun.com/pypi/simple/
分析脚本:
from pptx import Presentation
from pptx.enum.shapes import MSO_SHAPE_TYPE
def analyze_template(pptx_path):
prs = Presentation(pptx_path)
for slide_idx, slide in enumerate(prs.slides):
print(f"\n=== 第 {slide_idx + 1} 页 ===")
# 读取幻灯片布局名称
layout_name = slide.slide_layout.name if slide.slide_layout else "无布局"
print(f"布局: {layout_name}")
for shape in slide.shapes:
if not shape.has_text_frame and not shape.has_table:
continue
# 获取占位符信息
if shape.is_placeholder:
ph = shape.placeholder_format
print(f" 占位符 idx={ph.idx} | 类型={ph.type} | 名称={shape.name}")
else:
print(f" 形状 | 类型={shape.shape_type} | 名称={shape.name}")
# 读取已有文本内容作为参考
if shape.has_text_frame:
text = shape.text_frame.text[:50].replace('\n', ' ')
if text.strip():
print(f" 文本预览: {text}")
# 表格信息
if shape.has_table:
table = shape.table
print(f" 表格: {len(table.rows)}行 x {len(table.columns)}列")
运行后会看到类似这样的输出:
=== 第 1 页 ===
布局: 封面
占位符 idx=0 | 类型=TITLE (1) | 名称=标题占位符
文本预览: 公司名称
占位符 idx=1 | 类型=BODY (2) | 名称=副标题占位符
文本预览: 文档标题
=== 第 2 页 ===
布局: 目录
占位符 idx=0 | 类型=TITLE (1) | 名称=标题占位符
文本预览: 目录
占位符 idx=1 | 类型=BODY (2) | 名称=内容占位符
文本预览: 1. 第一章
把这些信息记录下来,下一步要用。
拿到模板结构之后,写核心脚本。思路是:
- 加载模板(不修改原文件,复制一份再改)
- 按索引或名称找到对应占位符
- 写入内容
- 保存
填充器类:
from pptx import Presentation
from pptx.util import Inches, Pt
from copy import deepcopy
import os
class PPTTemplateFiller:
"""PPT模板填充器"""
def __init__(self, template_path):
self.template_path = template_path
self.prs = None
def load(self):
"""加载模板文件"""
self.prs = Presentation(self.template_path)
return self
def set_text(self, slide_idx, placeholder_idx, text, font_size=None):
"""在指定页的占位符中写入文本"""
slide = self.prs.slides[slide_idx]
for shape in slide.shapes:
if shape.is_placeholder:
ph = shape.placeholder_format
if ph.idx == placeholder_idx:
tf = shape.text_frame
tf.paragraphs[0].text = text
if font_size:
for run in tf.paragraphs[0].runs:
run.font.size = Pt(font_size)
return True
return False
def set_text_by_name(self, slide_idx, placeholder_name, text):
"""通过名称查找占位符并写入"""
slide = self.prs.slides[slide_idx]
for shape in slide.shapes:
if shape.is_placeholder and shape.name == placeholder_name:
tf = shape.text_frame
tf.paragraphs[0].text = text
return True
return False
def fill_table(self, slide_idx, placeholder_name, data):
"""
填充表格
data: 二维列表,如 [["姓名", "部门"], ["张三", "技术部"]]
"""
slide = self.prs.slides[slide_idx]
for shape in slide.shapes:
if shape.has_table and shape.name == placeholder_name:
table = shape.table
for row_idx, row_data in enumerate(data):
if row_idx >= len(table.rows):
break
for col_idx, cell_text in enumerate(row_data):
if col_idx >= len(table.columns):
break
cell = table.cell(row_idx, col_idx)
cell.text = str(cell_text)
return True
return False
def add_slide_from_layout(self, layout_idx):
"""从指定布局添加新幻灯片"""
layout = self.prs.slide_layouts[layout_idx]
slide = self.prs.slides.add_slide(layout)
return slide
def save(self, output_path):
"""保存到新文件"""
self.prs.save(output_path)
return output_path
# 快速使用示例
def generate_report(template_path, output_path, data):
"""生成报告的快捷函数"""
filler = PPTTemplateFiller(template_path).load()
# 填充封面
filler.set_text_by_name(0, "标题占位符", data["title"])
filler.set_text_by_name(0, "副标题占位符", data["subtitle"])
# 填充正文页
filler.set_text_by_name(1, "内容占位符", data["content"])
# 填充表格
filler.fill_table(2, "表格占位符", data["table"])
return filler.save(output_path)
这个脚本已经能独立工作了。但要让它真正"一劳永逸",还需要把它封装成 Skill,让 AI 理解什么情况下该调它、怎么调。
OpenClaw Skill 的核心是一个 SKILL.md 文件,放在目录里就行。
目录结构:
ppt-report-skill/
├── SKILL.md # 技能说明书
├── scripts/
│ ├── __init__.py
│ └── fill_ppt.py # 核心填充逻辑
└── assets/
└── template.pptx # 模板文件
name: ppt-report-generator
description: "根据公司模板生成PPT报告,支持封面、目录、正文、表格自动填充"
# PPT 报告生成
根据公司标准模板自动生成PPT报告。适用场景:周报、月报、季度总结、项目汇报。
用户说"帮我生成XX报告"时:
1. 询问报告基本信息(标题、副标题、部门/姓名、日期)
2. 询问报告核心内容要点
3. 组装数据后调用脚本
cd ~/.openclaw/skills/ppt-report-skill/scripts
python fill_ppt.py \
--template /path/to/template.pptx \
--output /path/to/output.pptx \
--title "报告标题" \
--subtitle "部门 · 姓名" \
--content "正文内容(可用换行分隔多段)" \
--table "表头1,表头2;数据1,数据2;数据3,数据4"
- table 参数使用分号分隔行,逗号分隔列,第一行为表头
- content 参数使用 | 分隔段落
- 模板文件建议放在 ~/.openclaw/skills/ppt-report-skill/assets/
默认输出到当前工作目录,文件名格式:{标题}_{姓名}_{日期}.pptx
- 不修改原始模板文件
- 如果模板有更新,重新运行分析脚本确认占位符结构
- 表格最多支持10行,超出部分自动截断
#!/usr/bin/env python3
"""
PPT模板填充脚本
用法: python fill_ppt.py --template 模板.pptx --output 输出.pptx --title 标题 ...
"""
import argparse
import os
import sys
from datetime import datetime
from pptx import Presentation
from pptx.util import Pt
def parse_table(table_str):
"""解析表格参数字符串"""
rows = table_str.split(";")
return [row.split(",") for row in rows if row.strip()]
def parse_content(content_str):
"""解析内容参数字符串"""
paragraphs = content_str.split("|")
return [p.strip() for p in paragraphs if p.strip()]
def fill_ppt(template_path, output_path, title, subtitle, content, table, date_str):
"""执行填充"""
if not os.path.exists(template_path):
print(f"错误:模板文件不存在 {template_path}", file=sys.stderr)
sys.exit(1)
prs = Presentation(template_path)
# 第1页:封面
slide_0 = prs.slides[0]
for shape in slide_0.shapes:
if shape.is_placeholder:
ph = shape.placeholder_format
if ph.idx == 0: # 标题
shape.text_frame.paragraphs[0].text = title
elif ph.idx == 1: # 副标题/日期
shape.text_frame.paragraphs[0].text = f"{subtitle} · {date_str}"
# 第3页:正文
slide_2 = prs.slides[2]
for shape in slide_2.shapes:
if shape.is_placeholder and shape.name == "内容占位符":
tf = shape.text_frame
paragraphs = parse_content(content)
for i, para_text in enumerate(paragraphs):
if i < len(tf.paragraphs):
tf.paragraphs[i].text = para_text
# 第4页:表格
if table:
slide_3 = prs.slides[3]
for shape in slide_3.shapes:
if shape.has_table:
table_data = parse_table(table)
ppt_table = shape.table
for row_idx, row_data in enumerate(table_data):
if row_idx >= len(ppt_table.rows):
break
for col_idx, cell_text in enumerate(row_data):
if col_idx >= len(ppt_table.columns):
break
cell = ppt_table.cell(row_idx, col_idx)
cell.text = cell_text
prs.save(output_path)
return output_path
def main():
parser = argparse.ArgumentParser(description="PPT模板填充工具")
parser.add_argument("--template", required=True, help="模板文件路径")
parser.add_argument("--output", required=True, help="输出文件路径")
parser.add_argument("--title", required=True, help="报告标题")
parser.add_argument("--subtitle", default="", help="副标题(部门+姓名)")
parser.add_argument("--content", default="", help="正文内容,用|分隔段落")
parser.add_argument("--table", default="", help="表格数据,用;分隔行,用,分隔列")
parser.add_argument("--date", default="", help="日期,默认为今天")
args = parser.parse_args()
date_str = args.date or datetime.now().strftime("%Y年%m月%d日")
result = fill_ppt(
args.template,
args.output,
args.title,
args.subtitle,
args.content,
args.table,
date_str
)
print(f"生成成功: {result}")
if __name__ == "__main__":
main()
把目录放到正确位置,然后重启 OpenClaw 载入:
# 放到共享 skills 目录(所有 agent 都能用)
cp -r ppt-report-skill ~/.openclaw/skills/
# 或者放到工作区目录(仅当前工作区可用)
cp -r ppt-report-skill ~/.openclaw/workspace/skills/
# 重启让 OpenClaw 加载新 Skill
openclaw gateway restart
验证加载成功:
openclaw skills list | grep ppt
看到 ppt-report-generator 就说明加载成功。
现在你可以直接说:
OpenClaw 会识别出这个意图,调用 Skill 里的脚本,自动生成一份套好格式的 PPT。
这套方案的价值不是"省了10分钟套格式",而是把格式这件事彻底从你的工作流里剔除。
模板更新了,就重新分析一遍占位符;新的报告类型需要新模板,就新建一个 Skill。AI 的工作就是执行,你的工作就是说话。
核心就三行配置文件和两个脚本,改改占位符映射关系,能适配市面上绝大多数公司模板。
往期推荐:
- 《做这个号之前,我实测了50款AI工具》
- 《Trae 测评:国产AI编程工具能不能打?》
END
如果觉得有用,欢迎转发给需要做PPT的朋友。关注「小莴AI实测」,解锁更多AI工具实测与效率提升方法。
📍 本文为「小莴AI实测」原创#PPT自动化 #OpenClaw #AI办公 #效率工具 #python-pptx #Skill开发