- 添加 target/ 到 .gitignore - 从 git 暂存区移除已追踪的 target 目录 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
477 lines
19 KiB
Python
477 lines
19 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
幼儿阅读教学服务平台 - 产品介绍与功能说明 PDF生成脚本
|
||
"""
|
||
|
||
from reportlab.lib.pagesizes import A4
|
||
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||
from reportlab.lib.units import cm, mm
|
||
from reportlab.lib.colors import HexColor, white, black
|
||
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
|
||
from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer, PageBreak,
|
||
Table, TableStyle, Image, ListFlowable, ListItem)
|
||
from reportlab.pdfbase import pdfmetrics
|
||
from reportlab.pdfbase.ttfonts import TTFont
|
||
from reportlab.lib import colors
|
||
import os
|
||
|
||
# 注册中文字体
|
||
FONT_PATHS = [
|
||
'/System/Library/Fonts/PingFang.ttc',
|
||
'/System/Library/Fonts/STHeiti Light.ttc',
|
||
'/System/Library/Fonts/Hiragino Sans GB.ttc',
|
||
'/Library/Fonts/Arial Unicode.ttf',
|
||
]
|
||
|
||
CHINESE_FONT = None
|
||
for font_path in FONT_PATHS:
|
||
if os.path.exists(font_path):
|
||
try:
|
||
pdfmetrics.registerFont(TTFont('ChineseFont', font_path, subfontIndex=0))
|
||
CHINESE_FONT = 'ChineseFont'
|
||
print(f"使用字体: {font_path}")
|
||
break
|
||
except:
|
||
continue
|
||
|
||
if not CHINESE_FONT:
|
||
CHINESE_FONT = 'Helvetica'
|
||
print("警告: 未找到中文字体,使用默认字体")
|
||
|
||
# 颜色定义
|
||
PRIMARY_COLOR = HexColor('#1E3A5F')
|
||
SECONDARY_COLOR = HexColor('#3E7ABF')
|
||
ACCENT_COLOR = HexColor('#2E5A8F')
|
||
LIGHT_BG = HexColor('#F5F8FA')
|
||
TABLE_HEADER_BG = HexColor('#1E3A5F')
|
||
|
||
# 页面设置
|
||
PAGE_WIDTH, PAGE_HEIGHT = A4
|
||
MARGIN = 2 * cm
|
||
|
||
# 创建样式
|
||
def create_styles():
|
||
styles = getSampleStyleSheet()
|
||
|
||
# 封面标题
|
||
styles.add(ParagraphStyle(
|
||
name='CoverTitle',
|
||
fontName=CHINESE_FONT,
|
||
fontSize=32,
|
||
leading=40,
|
||
alignment=TA_CENTER,
|
||
textColor=PRIMARY_COLOR,
|
||
spaceAfter=20
|
||
))
|
||
|
||
# 封面副标题
|
||
styles.add(ParagraphStyle(
|
||
name='CoverSubtitle',
|
||
fontName=CHINESE_FONT,
|
||
fontSize=18,
|
||
leading=24,
|
||
alignment=TA_CENTER,
|
||
textColor=SECONDARY_COLOR,
|
||
spaceAfter=40
|
||
))
|
||
|
||
# 章节标题
|
||
styles.add(ParagraphStyle(
|
||
name='ChapterTitle',
|
||
fontName=CHINESE_FONT,
|
||
fontSize=22,
|
||
leading=30,
|
||
alignment=TA_LEFT,
|
||
textColor=PRIMARY_COLOR,
|
||
spaceBefore=20,
|
||
spaceAfter=15
|
||
))
|
||
|
||
# 节标题
|
||
styles.add(ParagraphStyle(
|
||
name='SectionTitle',
|
||
fontName=CHINESE_FONT,
|
||
fontSize=14,
|
||
leading=20,
|
||
alignment=TA_LEFT,
|
||
textColor=ACCENT_COLOR,
|
||
spaceBefore=15,
|
||
spaceAfter=10
|
||
))
|
||
|
||
# 正文
|
||
styles.add(ParagraphStyle(
|
||
name='ChineseBody',
|
||
fontName=CHINESE_FONT,
|
||
fontSize=11,
|
||
leading=18,
|
||
alignment=TA_JUSTIFY,
|
||
textColor=black,
|
||
spaceBefore=6,
|
||
spaceAfter=6
|
||
))
|
||
|
||
# 图片说明
|
||
styles.add(ParagraphStyle(
|
||
name='ImageCaption',
|
||
fontName=CHINESE_FONT,
|
||
fontSize=10,
|
||
leading=14,
|
||
alignment=TA_CENTER,
|
||
textColor=HexColor('#666666'),
|
||
spaceBefore=5,
|
||
spaceAfter=15
|
||
))
|
||
|
||
# 列表项
|
||
styles.add(ParagraphStyle(
|
||
name='ListItem',
|
||
fontName=CHINESE_FONT,
|
||
fontSize=11,
|
||
leading=16,
|
||
alignment=TA_LEFT,
|
||
textColor=black,
|
||
leftIndent=20,
|
||
spaceBefore=3,
|
||
spaceAfter=3
|
||
))
|
||
|
||
return styles
|
||
|
||
def create_table(headers, rows, col_widths=None):
|
||
"""创建美观的表格"""
|
||
data = [headers] + rows
|
||
|
||
if col_widths is None:
|
||
col_widths = [PAGE_WIDTH - 2*MARGIN] / len(headers) * len(headers)
|
||
|
||
table = Table(data, colWidths=col_widths)
|
||
|
||
style = TableStyle([
|
||
# 表头样式
|
||
('BACKGROUND', (0, 0), (-1, 0), TABLE_HEADER_BG),
|
||
('TEXTCOLOR', (0, 0), (-1, 0), white),
|
||
('FONTNAME', (0, 0), (-1, 0), CHINESE_FONT),
|
||
('FONTSIZE', (0, 0), (-1, 0), 11),
|
||
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
|
||
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
|
||
('BOTTOMPADDING', (0, 0), (-1, 0), 10),
|
||
('TOPPADDING', (0, 0), (-1, 0), 10),
|
||
|
||
# 数据行样式
|
||
('FONTNAME', (0, 1), (-1, -1), CHINESE_FONT),
|
||
('FONTSIZE', (0, 1), (-1, -1), 10),
|
||
('ALIGN', (0, 1), (-1, -1), 'LEFT'),
|
||
('BOTTOMPADDING', (0, 1), (-1, -1), 8),
|
||
('TOPPADDING', (0, 1), (-1, -1), 8),
|
||
|
||
# 边框
|
||
('GRID', (0, 0), (-1, -1), 0.5, HexColor('#CCCCCC')),
|
||
('BOX', (0, 0), (-1, -1), 1, TABLE_HEADER_BG),
|
||
|
||
# 交替行背景
|
||
('ROWBACKGROUNDS', (0, 1), (-1, -1), [white, LIGHT_BG]),
|
||
])
|
||
|
||
table.setStyle(style)
|
||
return table
|
||
|
||
def add_image_with_caption(story, image_path, caption, styles, max_width=15*cm, max_height=10*cm):
|
||
"""添加图片和说明"""
|
||
if os.path.exists(image_path):
|
||
try:
|
||
img = Image(image_path)
|
||
# 计算缩放比例
|
||
w, h = img.drawWidth, img.drawHeight
|
||
scale = min(max_width/w, max_height/h, 1)
|
||
img._restrictSize(max_width, max_height)
|
||
img.hAlign = 'CENTER'
|
||
story.append(img)
|
||
story.append(Paragraph(caption, styles['ImageCaption']))
|
||
except Exception as e:
|
||
story.append(Paragraph(f"[图片加载失败: {image_path}]", styles['ChineseBody']))
|
||
else:
|
||
story.append(Paragraph(f"[图片不存在: {image_path}]", styles['ChineseBody']))
|
||
|
||
def build_document():
|
||
"""构建PDF文档"""
|
||
output_path = '/Users/retirado/ccProgram/docs/幼儿阅读教学服务平台-产品介绍与功能说明.pdf'
|
||
screenshots_dir = '/Users/retirado/ccProgram/docs/screenshots'
|
||
|
||
doc = SimpleDocTemplate(
|
||
output_path,
|
||
pagesize=A4,
|
||
leftMargin=MARGIN,
|
||
rightMargin=MARGIN,
|
||
topMargin=MARGIN,
|
||
bottomMargin=MARGIN
|
||
)
|
||
|
||
styles = create_styles()
|
||
story = []
|
||
|
||
# ===== 封面 =====
|
||
story.append(Spacer(1, 4*cm))
|
||
story.append(Paragraph('幼儿阅读教学服务平台', styles['CoverTitle']))
|
||
story.append(Paragraph('产品介绍与功能说明', styles['CoverSubtitle']))
|
||
story.append(Spacer(1, 3*cm))
|
||
story.append(Paragraph('版本:v1.0', styles['CoverSubtitle']))
|
||
story.append(Paragraph('日期:2026年2月24日', styles['CoverSubtitle']))
|
||
story.append(PageBreak())
|
||
|
||
# ===== 第一章:产品概述 =====
|
||
story.append(Paragraph('第一章 产品概述', styles['ChapterTitle']))
|
||
|
||
story.append(Paragraph('1.1 产品定位', styles['SectionTitle']))
|
||
story.append(Paragraph(
|
||
'幼儿阅读教学服务平台是一款面向幼儿园的B2B2C综合性阅读教学管理系统,致力于为幼儿园、教师和家长提供全方位的绘本阅读教学服务。平台采用多端协同架构,打通超管、学校、教师、家长四方角色,实现从课程创作、教学管理到家校互动的完整闭环。',
|
||
styles['ChineseBody']
|
||
))
|
||
|
||
story.append(Paragraph('1.2 目标用户', styles['SectionTitle']))
|
||
story.append(create_table(
|
||
['用户角色', '使用场景', '核心需求'],
|
||
[
|
||
['平台超管', '运营管理', '课程内容管理、租户服务、平台运营'],
|
||
['学校管理员', '园所管理', '教师学生管理、课程授权、数据统计'],
|
||
['教师', '教学实施', '课程备课、课堂教学、任务布置、成长记录'],
|
||
['家长', '家校共育', '查看任务、提交反馈、了解孩子成长']
|
||
],
|
||
[4*cm, 3*cm, 8*cm]
|
||
))
|
||
story.append(Spacer(1, 10))
|
||
|
||
story.append(Paragraph('1.3 核心价值', styles['SectionTitle']))
|
||
values = [
|
||
'• 标准化教学:专业绘本课程包,标准化教学流程',
|
||
'• 效率提升:备课上课一体化,教学管理智能化',
|
||
'• 家校联动:任务布置与反馈,家校共育更紧密',
|
||
'• 成长可视:多维度数据记录,成长轨迹清晰可见',
|
||
'• 灵活管理:多租户架构,满足不同规模园所需求'
|
||
]
|
||
for v in values:
|
||
story.append(Paragraph(v, styles['ListItem']))
|
||
|
||
story.append(PageBreak())
|
||
|
||
# ===== 第二章:技术架构 =====
|
||
story.append(Paragraph('第二章 技术架构', styles['ChapterTitle']))
|
||
|
||
story.append(Paragraph('2.1 系统架构', styles['SectionTitle']))
|
||
story.append(Paragraph(
|
||
'系统采用前后端分离架构,前端使用Vue 3框架,后端使用NestJS框架,通过JWT进行身份认证。数据层使用Prisma ORM,支持SQLite(开发)和PostgreSQL(生产)数据库。文件存储支持本地存储和云存储两种方案。',
|
||
styles['ChineseBody']
|
||
))
|
||
|
||
story.append(Paragraph('2.2 技术选型', styles['SectionTitle']))
|
||
story.append(create_table(
|
||
['层级', '技术栈', '说明'],
|
||
[
|
||
['前端框架', 'Vue 3 + Vite', '现代化前端开发框架'],
|
||
['UI组件', 'Ant Design Vue 4.x', '企业级UI组件库'],
|
||
['状态管理', 'Pinia', 'Vue官方状态管理'],
|
||
['后端框架', 'NestJS', '企业级Node.js框架'],
|
||
['ORM', 'Prisma', '现代化数据库工具'],
|
||
['数据库', 'SQLite / PostgreSQL', '开发/生产数据库'],
|
||
['认证', 'JWT + Passport', '安全认证方案']
|
||
],
|
||
[3*cm, 5*cm, 7*cm]
|
||
))
|
||
story.append(PageBreak())
|
||
|
||
# ===== 第三章:超管端功能 =====
|
||
story.append(Paragraph('第三章 超管端功能', styles['ChapterTitle']))
|
||
story.append(Paragraph(
|
||
'超管端是平台的运营管理中心,负责课程内容生产、租户服务和平台运营。',
|
||
styles['ChineseBody']
|
||
))
|
||
|
||
story.append(Paragraph('3.1 数据看板', styles['SectionTitle']))
|
||
story.append(Paragraph(
|
||
'提供平台整体运营数据的可视化展示,包括核心指标(租户数量、课程数量、授课次数、学生总数)、趋势图表和快捷入口。',
|
||
styles['ChineseBody']
|
||
))
|
||
add_image_with_caption(story, f'{screenshots_dir}/02-admin-dashboard.png',
|
||
'图3-1 超管端数据看板 - 平台运营数据一目了然', styles)
|
||
|
||
story.append(Paragraph('3.2 课程包管理', styles['SectionTitle']))
|
||
story.append(Paragraph(
|
||
'完整的课程内容生产与发布流程,支持基础信息、资源上传、教学环节、逐页脚本、延伸活动等完整内容。课程状态包括:草稿、待审核、已发布、已下架。',
|
||
styles['ChineseBody']
|
||
))
|
||
add_image_with_caption(story, f'{screenshots_dir}/03-admin-courses.png',
|
||
'图3-2 课程包列表 - 管理所有课程内容', styles)
|
||
add_image_with_caption(story, f'{screenshots_dir}/04-course-detail.png',
|
||
'图3-3 课程详情 - 完整的教学设计展示', styles, max_height=12*cm)
|
||
|
||
story.append(Paragraph('3.3 租户管理', styles['SectionTitle']))
|
||
story.append(Paragraph(
|
||
'多租户服务的核心管理功能,包括租户列表、套餐配置、课程授权、服务管理(暂停/恢复)、密码重置等。',
|
||
styles['ChineseBody']
|
||
))
|
||
add_image_with_caption(story, f'{screenshots_dir}/05-admin-tenants.png',
|
||
'图3-4 租户管理 - 管理所有签约园所', styles)
|
||
story.append(PageBreak())
|
||
|
||
# ===== 第四章:学校端功能 =====
|
||
story.append(Paragraph('第四章 学校端功能', styles['ChapterTitle']))
|
||
story.append(Paragraph(
|
||
'学校端是园所管理员的管理后台,负责本园的教师、学生、班级和教学管理。',
|
||
styles['ChineseBody']
|
||
))
|
||
|
||
story.append(Paragraph('4.1 数据概览', styles['SectionTitle']))
|
||
story.append(Paragraph(
|
||
'园所运营数据一目了然,包括人员统计(教师数、学生数、班级数)、教学统计(授课次数、任务完成率)和图表展示。',
|
||
styles['ChineseBody']
|
||
))
|
||
add_image_with_caption(story, f'{screenshots_dir}/06-school-dashboard.png',
|
||
'图4-1 学校端数据概览 - 园所运营数据统计', styles)
|
||
|
||
story.append(Paragraph('4.2 人员管理', styles['SectionTitle']))
|
||
story.append(Paragraph(
|
||
'支持教师和学生的增删改查、批量导入、调班等功能。教师管理包括添加、编辑、删除、重置密码;学生管理包括添加、编辑、删除、批量导入、调班。',
|
||
styles['ChineseBody']
|
||
))
|
||
add_image_with_caption(story, f'{screenshots_dir}/07-school-teachers.png',
|
||
'图4-2 教师管理 - 管理本园教师信息', styles)
|
||
|
||
story.append(Paragraph('4.3 课程排期', styles['SectionTitle']))
|
||
story.append(Paragraph(
|
||
'日历视图展示排课情况,支持创建、编辑、删除排课,以及批量排课功能。',
|
||
styles['ChineseBody']
|
||
))
|
||
add_image_with_caption(story, f'{screenshots_dir}/08-school-schedule.png',
|
||
'图4-3 课程排期 - 日历视图管理排课', styles)
|
||
story.append(PageBreak())
|
||
|
||
# ===== 第五章:教师端功能 =====
|
||
story.append(Paragraph('第五章 教师端功能', styles['ChapterTitle']))
|
||
story.append(Paragraph(
|
||
'教师端是教师日常教学的核心工具,覆盖备课、上课、课后全流程。',
|
||
styles['ChineseBody']
|
||
))
|
||
|
||
story.append(Paragraph('5.1 课程中心', styles['SectionTitle']))
|
||
story.append(Paragraph(
|
||
'浏览和使用已授权的课程,支持年级筛选(小班/中班/大班)、领域筛选(语言、社会、科学等)、关键词搜索。',
|
||
styles['ChineseBody']
|
||
))
|
||
add_image_with_caption(story, f'{screenshots_dir}/09-teacher-courses.png',
|
||
'图5-1 课程中心 - 浏览可用课程', styles)
|
||
|
||
story.append(Paragraph('5.2 备课模式', styles['SectionTitle']))
|
||
story.append(Paragraph(
|
||
'课前准备工作,包括教学流程、教师讲稿、逐页脚本、备课笔记、教学材料等。支持选择授课班级。',
|
||
styles['ChineseBody']
|
||
))
|
||
add_image_with_caption(story, f'{screenshots_dir}/10-teacher-prepare.png',
|
||
'图5-2 备课模式 - 完整的备课支持', styles, max_height=12*cm)
|
||
|
||
story.append(Paragraph('5.3 上课记录', styles['SectionTitle']))
|
||
story.append(Paragraph(
|
||
'历史授课记录管理,支持状态筛选(已计划/进行中/已完成)、日期筛选、查看详情、课后记录补充。',
|
||
styles['ChineseBody']
|
||
))
|
||
add_image_with_caption(story, f'{screenshots_dir}/11-teacher-lessons.png',
|
||
'图5-3 上课记录 - 授课历史管理', styles)
|
||
story.append(PageBreak())
|
||
|
||
# ===== 第六章:家长端功能 =====
|
||
story.append(Paragraph('第六章 家长端功能', styles['ChapterTitle']))
|
||
story.append(Paragraph(
|
||
'家长端是家校互动的桥梁,让家长了解并参与孩子的阅读成长。',
|
||
styles['ChineseBody']
|
||
))
|
||
|
||
story.append(Paragraph('6.1 首页', styles['SectionTitle']))
|
||
story.append(Paragraph(
|
||
'孩子信息概览,包括孩子卡片(姓名、班级、阅读次数)、最近任务、成长档案等。',
|
||
styles['ChineseBody']
|
||
))
|
||
add_image_with_caption(story, f'{screenshots_dir}/12-parent-dashboard.png',
|
||
'图6-1 家长端首页 - 孩子信息概览', styles)
|
||
|
||
story.append(Paragraph('6.2 阅读任务', styles['SectionTitle']))
|
||
story.append(Paragraph(
|
||
'完成教师布置的任务,支持查看任务详情、截止日期、提交反馈、查看反馈记录。',
|
||
styles['ChineseBody']
|
||
))
|
||
add_image_with_caption(story, f'{screenshots_dir}/13-parent-tasks.png',
|
||
'图6-2 阅读任务 - 查看并完成阅读任务', styles)
|
||
story.append(PageBreak())
|
||
|
||
# ===== 第七章:核心业务流程 =====
|
||
story.append(Paragraph('第七章 核心业务流程', styles['ChapterTitle']))
|
||
|
||
story.append(Paragraph('7.1 课程生产流程', styles['SectionTitle']))
|
||
story.append(Paragraph(
|
||
'超管创建课程 → 填写基础信息 → 上传资源 → 设计教学环节 → 添加延伸活动 → 提交审核 → 审核通过 → 发布课程 → 授权给租户 → 学校/教师使用',
|
||
styles['ChineseBody']
|
||
))
|
||
|
||
story.append(Paragraph('7.2 教学实施流程', styles['SectionTitle']))
|
||
story.append(Paragraph(
|
||
'教师浏览课程 → 进入备课模式 → 记录备课笔记 → 选择班级 → 开始上课 → 按流程教学 → 课堂评价 → 结束课程 → 填写课堂记录 → 查看上课记录',
|
||
styles['ChineseBody']
|
||
))
|
||
|
||
story.append(Paragraph('7.3 家校互动流程', styles['SectionTitle']))
|
||
story.append(Paragraph(
|
||
'教师布置任务 → 分配给学生/班级 → 家长查看任务 → 亲子完成阅读 → 家长提交反馈 → 教师查看反馈',
|
||
styles['ChineseBody']
|
||
))
|
||
|
||
story.append(Paragraph('7.4 成长记录流程', styles['SectionTitle']))
|
||
story.append(Paragraph(
|
||
'教师观察学生 → 创建成长档案 → 选择学生 → 填写内容 → 上传图片 → 保存档案 → 家长查看',
|
||
styles['ChineseBody']
|
||
))
|
||
story.append(PageBreak())
|
||
|
||
# ===== 第八章:部署与运维 =====
|
||
story.append(Paragraph('第八章 部署与运维', styles['ChapterTitle']))
|
||
|
||
story.append(Paragraph('8.1 环境配置', styles['SectionTitle']))
|
||
story.append(create_table(
|
||
['环境', '前端地址', '后端地址', '数据库'],
|
||
[
|
||
['开发', 'localhost:5173', 'localhost:3000', 'SQLite'],
|
||
['生产', '域名/CDN', '域名/API', 'PostgreSQL']
|
||
],
|
||
[2.5*cm, 4*cm, 4*cm, 4.5*cm]
|
||
))
|
||
story.append(Spacer(1, 15))
|
||
|
||
story.append(Paragraph('8.2 启动命令', styles['SectionTitle']))
|
||
story.append(Paragraph('# 开发环境启动', styles['ChineseBody']))
|
||
story.append(Paragraph('./start-all.sh', styles['ListItem']))
|
||
story.append(Paragraph('# 停止服务', styles['ChineseBody']))
|
||
story.append(Paragraph('./stop-all.sh', styles['ListItem']))
|
||
story.append(Spacer(1, 15))
|
||
|
||
story.append(Paragraph('8.3 测试账号', styles['SectionTitle']))
|
||
story.append(create_table(
|
||
['角色', '账号', '密码'],
|
||
[
|
||
['超管', 'admin', '123456'],
|
||
['学校', 'school', '123456'],
|
||
['教师', 'teacher1', '123456'],
|
||
['家长', 'parent1', '123456']
|
||
],
|
||
[4*cm, 5.5*cm, 5.5*cm]
|
||
))
|
||
|
||
# 结束
|
||
story.append(Spacer(1, 2*cm))
|
||
story.append(Paragraph('— 文档结束 —', styles['ImageCaption']))
|
||
|
||
# 生成PDF
|
||
doc.build(story)
|
||
print(f'PDF文档已生成: {output_path}')
|
||
return output_path
|
||
|
||
if __name__ == '__main__':
|
||
build_document()
|