一、项目背景
1.1 痛点分析
PPT制作是职场高频场景,但传统方式效率极低:
| 环节 | 手工方式 | 时间 |
|---|---|---|
| 找模板 | 搜索下载 | 2小时 |
| 填内容 | 手动输入 | 3小时 |
| 调排版 | 反复调整 | 3小时 |
| 做图表 | Excel+复制 | 2小时 |
| 改配色 | 逐页修改 | 1小时 |
| 总计 | - | 11小时 |
一份30页PPT就要一天半,改3版就是4天。
1.2 技术需求
核心需求:
- AI自动生成PPT内容大纲
- 自动创建幻灯片并填充内容
- 自动插入数据图表
- 支持自定义模板和配色
- 一键导出标准.pptx文件
二、技术架构
原始内容 → AI生成大纲 → 创建幻灯片 → 填充内容 → 插入图表 → 导出PPT
↑ ↑ ↑ ↑ ↑ ↑
文本/数据 DashScope python-pptx python-pptx matplotlib python-pptx
Qwen3
技术栈:
- **python-pptx**:PPT创建和编辑
- **DashScope/Qwen3**:AI生成内容大纲
- **matplotlib**:图表生成
- **pandas**:数据处理
- **Pillow**:图片处理
---
## 三、环境准备
### 3.1 安装依赖
```bash
pip install python-pptx dashscope matplotlib pandas pillow
3.2 配置
# config.py
DASHSCOPE_API_KEY = "your-api-key-here"
# PPT配色方案
COLOR_SCHEMES = {
'business': {
'primary': '2C3E50',
'secondary': '3498DB',
'accent': '2ECC71',
'text': '333333',
'background': 'FFFFFF'
},
'tech': {
'primary': '0D1B2A',
'secondary': '2196F3',
'accent': '00BCD4',
'text': 'EEEEEE',
'background': '1B2838'
},
'vibrant': {
'primary': 'FF6B6B',
'secondary': '4ECDC4',
'accent': '45B7D1',
'text': '333333',
'background': 'FFFFFF'
}
}
四、核心模块实现
4.1 AI内容生成模块
用Qwen3自动生成PPT大纲和每页内容:
import dashscope
import json
import re
class ContentGenerator:
def __init__(self, api_key):
dashscope.api_key = api_key
def generate_outline(self, topic, raw_content, page_count=12):
"""AI生成PPT大纲"""
prompt = f"""
你是一位PPT设计专家。请根据以下信息,生成PPT大纲。
主题:{topic}
原始内容:{raw_content[:3000]}
页数要求:{page_count}页
请返回JSON格式:
{{
"title": "PPT标题",
"subtitle": "副标题",
"slides": [
{{
"page": 1,
"type": "cover",
"title": "封面标题",
"subtitle": "副标题"
}},
{{
"page": 2,
"type": "toc",
"title": "目录",
"items": ["章节1", "章节2", "章节3"]
}},
{{
"page": 3,
"type": "content",
"title": "页面标题",
"bullets": ["要点1", "要点2", "要点3"]
}},
{{
"page": 4,
"type": "chart",
"title": "数据图表页标题",
"chart_type": "bar/line/pie",
"chart_data": {{"labels": [], "values": []}}
}},
{{
"page": 12,
"type": "end",
"title": "谢谢",
"subtitle": "Q&A"
}}
]
}}
规则:
1. 封面页1张、目录页1张、结尾页1张
2. 内容页每页3-5个要点
3. 至少2页数据图表页
4. 内容要有逻辑递进关系
5. 标题简洁有力,不超过15字
"""
response = dashscope.Generation.call(
model="qwen3-72b",
messages=[{"role": "user", "content": prompt}],
result_format="message"
)
content = response.output.choices[0].message.content
json_match = re.search(r'\{.*\}', content, re.DOTALL)
if json_match:
return json.loads(json_match.group())
return None
def generate_speaker_notes(self, slide_content):
"""AI生成演讲稿"""
prompt = f"""
请为以下PPT页面生成演讲稿(口语化,1-2分钟):
标题:{slide_content.get('title', '')}
要点:{slide_content.get('bullets', [])}
要求:
- 口语化,不要念稿感
- 每个要点展开1-2句
- 有过渡语连接各点
- 总时长控制在1-2分钟
"""
response = dashscope.Generation.call(
model="qwen3-72b",
messages=[{"role": "user", "content": prompt}],
result_format="message"
)
return response.output.choices[0].message.content
4.2 图表生成模块
用matplotlib生成图表并保存为图片,供PPT嵌入:
import matplotlib.pyplot as plt
import matplotlib
matplotlib.rcParams['font.sans-serif'] = ['SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False
class ChartFactory:
def __init__(self, color_scheme='business'):
self.colors = list(COLOR_SCHEMES[color_scheme].values())[:5]
def create_chart(self, chart_type, data, title, output_path):
"""根据类型生成图表"""
fig, ax = plt.subplots(figsize=(10, 6))
if chart_type == 'bar':
self._bar_chart(ax, data, title)
elif chart_type == 'line':
self._line_chart(ax, data, title)
elif chart_type == 'pie':
self._pie_chart(ax, data, title)
elif chart_type == 'radar':
plt.close()
fig = plt.figure(figsize=(8, 8))
ax = fig.add_subplot(111, projection='polar')
self._radar_chart(ax, data, title)
plt.tight_layout()
plt.savefig(output_path, dpi=200, bbox_inches='tight', transparent=True)
plt.close()
return output_path
def _bar_chart(self, ax, data, title):
"""柱状图"""
labels = data['labels']
values = data['values']
bars = ax.bar(labels, values, color=['#3498DB', '#2ECC71', '#E74C3C', '#F39C12', '#9B59B6'][:len(labels)])
ax.set_title(title, fontsize=16, fontweight='bold', pad=15)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
for bar in bars:
height = bar.get_height()
ax.text(bar.get_x() + bar.get_width()/2., height,
f'{height:,.0f}', ha='center', va='bottom', fontsize=12, fontweight='bold')
def _line_chart(self, ax, data, title):
"""折线图"""
labels = data['labels']
values = data['values']
ax.plot(labels, values, marker='o', color='#3498DB', linewidth=2.5, markersize=8)
ax.fill_between(range(len(labels)), values, alpha=0.15, color='#3498DB')
ax.set_title(title, fontsize=16, fontweight='bold', pad=15)
ax.grid(True, alpha=0.3)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
def _pie_chart(self, ax, data, title):
"""饼图"""
labels = data['labels']
values = data['values']
colors = ['#3498DB', '#2ECC71', '#E74C3C', '#F39C12', '#9B59B6'][:len(labels)]
wedges, texts, autotexts = ax.pie(
values, labels=labels, autopct='%1.1f%%',
colors=colors, startangle=90, textprops={'fontsize': 12}
)
for autotext in autotexts:
autotext.set_fontweight('bold')
ax.set_title(title, fontsize=16, fontweight='bold', pad=15)
4.3 PPT构建模块
用python-pptx创建专业级PPT:
from pptx import Presentation
from pptx.util import Inches, Pt, Emu
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.enum.shapes import MSO_SHAPE
class PPTBuilder:
def __init__(self, template_path=None, color_scheme='business'):
if template_path:
self.prs = Presentation(template_path)
else:
self.prs = Presentation()
self.prs.slide_width = Inches(13.33)
self.prs.slide_height = Inches(7.5)
self.scheme = COLOR_SCHEMES[color_scheme]
def add_cover(self, title, subtitle):
"""封面页"""
slide = self.prs.slides.add_slide(self.prs.slide_layouts[6]) # 空白布局
# 背景色
bg = slide.background
fill = bg.fill
fill.solid()
fill.fore_color.rgb = RGBColor.from_string(self.scheme['primary'])
# 标题
txBox = slide.shapes.add_textbox(Inches(1), Inches(2), Inches(11), Inches(2))
tf = txBox.text_frame
tf.word_wrap = True
p = tf.paragraphs[0]
p.text = title
p.font.size = Pt(44)
p.font.bold = True
p.font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF)
p.alignment = PP_ALIGN.CENTER
# 副标题
txBox2 = slide.shapes.add_textbox(Inches(1), Inches(4.2), Inches(11), Inches(1))
tf2 = txBox2.text_frame
p2 = tf2.paragraphs[0]
p2.text = subtitle
p2.font.size = Pt(22)
p2.font.color.rgb = RGBColor(0xCC, 0xCC, 0xCC)
p2.alignment = PP_ALIGN.CENTER
def add_toc(self, title, items):
"""目录页"""
slide = self.prs.slides.add_slide(self.prs.slide_layouts[6])
# 标题
self._add_title(slide, title)
# 目录项
for i, item in enumerate(items):
y_pos = Inches(2.2 + i * 0.8)
# 编号圆形
shape = slide.shapes.add_shape(
MSO_SHAPE.OVAL, Inches(1.5), y_pos, Inches(0.5), Inches(0.5)
)
shape.fill.solid()
shape.fill.fore_color.rgb = RGBColor.from_string(self.scheme['secondary'])
shape.line.fill.background()
tf = shape.text_frame
tf.paragraphs[0].text = str(i + 1)
tf.paragraphs[0].font.size = Pt(16)
tf.paragraphs[0].font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF)
tf.paragraphs[0].font.bold = True
tf.paragraphs[0].alignment = PP_ALIGN.CENTER
tf.vertical_anchor = MSO_ANCHOR.MIDDLE
# 文字
txBox = slide.shapes.add_textbox(Inches(2.3), y_pos, Inches(8), Inches(0.5))
tf = txBox.text_frame
tf.paragraphs[0].text = item
tf.paragraphs[0].font.size = Pt(20)
tf.paragraphs[0].font.color.rgb = RGBColor.from_string(self.scheme['text'])
tf.vertical_anchor = MSO_ANCHOR.MIDDLE
def add_content(self, title, bullets, notes=None):
"""内容页"""
slide = self.prs.slides.add_slide(self.prs.slide_layouts[6])
# 标题
self._add_title(slide, title)
# 要点
txBox = slide.shapes.add_textbox(Inches(1), Inches(2), Inches(11), Inches(4.5))
tf = txBox.text_frame
tf.word_wrap = True
for i, bullet in enumerate(bullets):
if i > 0:
p = tf.add_paragraph()
else:
p = tf.paragraphs[0]
p.text = f" ● {bullet}"
p.font.size = Pt(18)
p.font.color.rgb = RGBColor.from_string(self.scheme['text'])
p.space_after = Pt(16)
# 演讲稿备注
if notes:
slide.notes_slide.notes_text_frame.text = notes
def add_chart_slide(self, title, chart_image_path):
"""图表页"""
slide = self.prs.slides.add_slide(self.prs.slide_layouts[6])
# 标题
self._add_title(slide, title)
# 插入图表图片
slide.shapes.add_picture(
chart_image_path,
Inches(1.5), Inches(1.8),
Inches(10), Inches(5)
)
def add_end(self, title="谢谢", subtitle="Q&A"):
"""结尾页"""
slide = self.prs.slides.add_slide(self.prs.slide_layouts[6])
bg = slide.background
fill = bg.fill
fill.solid()
fill.fore_color.rgb = RGBColor.from_string(self.scheme['primary'])
txBox = slide.shapes.add_textbox(Inches(1), Inches(2.5), Inches(11), Inches(2))
tf = txBox.text_frame
p = tf.paragraphs[0]
p.text = title
p.font.size = Pt(48)
p.font.bold = True
...(truncated)...