🔄 卡若AI 同步 2026-03-17 19:37 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 11 个
This commit is contained in:
@@ -1,453 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{title}} - 产研会议纪要</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", "Helvetica Neue", Arial, sans-serif;
|
||||
background: #f0f2f5;
|
||||
color: #1d2129;
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.page {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* ========== HEADER(蓝色渐变横幅) ========== */
|
||||
.header {
|
||||
background: linear-gradient(135deg, #2463EB 0%, #1a4fd4 50%, #1342b8 100%);
|
||||
padding: 32px 40px 28px;
|
||||
color: #fff;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.header::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -40%;
|
||||
right: -10%;
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
background: radial-gradient(circle, rgba(255,255,255,0.08) 0%, transparent 70%);
|
||||
border-radius: 50%;
|
||||
}
|
||||
.header-label {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
background: rgba(255,255,255,0.18);
|
||||
padding: 4px 14px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 14px;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
.header h1 {
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
line-height: 1.35;
|
||||
margin-bottom: 16px;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
.header-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 24px;
|
||||
font-size: 13px;
|
||||
opacity: 0.92;
|
||||
}
|
||||
.header-meta span { display: flex; align-items: center; gap: 6px; }
|
||||
|
||||
/* ========== BODY ========== */
|
||||
.body { padding: 28px 32px 20px; }
|
||||
|
||||
/* ========== CARD GRID(双栏卡片) ========== */
|
||||
.card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #fff;
|
||||
border: 1px solid #e5e8ed;
|
||||
border-radius: 12px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.04), 0 4px 12px rgba(0,0,0,0.03);
|
||||
}
|
||||
.card-header {
|
||||
padding: 16px 20px 12px;
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
.card-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
flex-shrink: 0;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.card-icon.blue { background: #2463EB; }
|
||||
.card-icon.green { background: #16a34a; }
|
||||
.card-icon.orange { background: #ea580c; }
|
||||
.card-icon.purple { background: #7c3aed; }
|
||||
.card-icon.red { background: #dc2626; }
|
||||
.card-icon.teal { background: #0d9488; }
|
||||
|
||||
.card-header h3 {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #1d2129;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.card-header .card-sub {
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.card-body { padding: 14px 20px 18px; }
|
||||
|
||||
.card-body h4 {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #4b5563;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
/* 要点列表 */
|
||||
.point-list { list-style: none; margin-bottom: 14px; }
|
||||
.point-list:last-child { margin-bottom: 0; }
|
||||
.point-list li {
|
||||
font-size: 13px;
|
||||
line-height: 1.65;
|
||||
padding: 3px 0;
|
||||
padding-left: 16px;
|
||||
position: relative;
|
||||
color: #374151;
|
||||
}
|
||||
.point-list li::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 11px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: #c4c9d4;
|
||||
}
|
||||
.point-list li.highlight::before { background: #2463EB; }
|
||||
.point-list li.warn::before { background: #ea580c; }
|
||||
.point-list li.done::before { background: #16a34a; }
|
||||
.point-list li.block::before { background: #dc2626; }
|
||||
|
||||
/* 状态标签 */
|
||||
.tag {
|
||||
display: inline-block;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
margin-left: 6px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.tag-green { background: #dcfce7; color: #15803d; }
|
||||
.tag-red { background: #fee2e2; color: #b91c1c; }
|
||||
.tag-yellow { background: #fef9c3; color: #a16207; }
|
||||
.tag-orange { background: #ffedd5; color: #c2410c; }
|
||||
.tag-blue { background: #dbeafe; color: #1d4ed8; }
|
||||
.tag-purple { background: #f3e8ff; color: #7c3aed; }
|
||||
.tag-gray { background: #f3f4f6; color: #6b7280; }
|
||||
|
||||
/* 决策块 */
|
||||
.decision {
|
||||
background: #f8fafc;
|
||||
border-left: 3px solid #2463EB;
|
||||
padding: 10px 14px;
|
||||
border-radius: 0 8px 8px 0;
|
||||
margin-top: 10px;
|
||||
font-size: 13px;
|
||||
color: #374151;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.decision strong { color: #1d2129; }
|
||||
|
||||
/* 数据表格 */
|
||||
.progress-table {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
font-size: 12px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.progress-table th {
|
||||
background: #f8fafc;
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
color: #6b7280;
|
||||
border-bottom: 1px solid #e5e8ed;
|
||||
}
|
||||
.progress-table td {
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #374151;
|
||||
}
|
||||
.progress-table tr:last-child td { border-bottom: none; }
|
||||
|
||||
/* ========== SECTION DIVIDER ========== */
|
||||
.section-divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin: 32px 0 20px;
|
||||
padding: 0;
|
||||
}
|
||||
.section-divider .divider-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
background: #2463EB;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.section-divider h2 {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #1d2129;
|
||||
}
|
||||
.section-divider::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: #e5e8ed;
|
||||
}
|
||||
|
||||
/* ========== ACTION ITEMS 行动项表格 ========== */
|
||||
.action-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0;
|
||||
border: 1px solid #e5e8ed;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
|
||||
}
|
||||
.action-row {
|
||||
display: grid;
|
||||
grid-template-columns: 50px 1fr 90px 90px;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
font-size: 13px;
|
||||
}
|
||||
.action-row:last-child { border-bottom: none; }
|
||||
.action-row.header-row {
|
||||
background: #f8fafc;
|
||||
font-weight: 700;
|
||||
color: #6b7280;
|
||||
font-size: 12px;
|
||||
}
|
||||
.action-row > div { padding: 10px 14px; }
|
||||
.action-num { text-align: center; font-weight: 700; color: #8c8c8c; }
|
||||
.action-content { color: #374151; line-height: 1.5; }
|
||||
.action-owner { text-align: center; font-weight: 600; color: #4b5563; }
|
||||
.action-status { text-align: center; }
|
||||
|
||||
/* ========== INSIGHT CARD 洞察卡片 ========== */
|
||||
.insight-card {
|
||||
background: #fafbfc;
|
||||
border: 1px solid #e5e8ed;
|
||||
border-radius: 12px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.03);
|
||||
}
|
||||
.insight-card h3 {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #1d2129;
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.insight-content {
|
||||
font-size: 13px;
|
||||
color: #4b5563;
|
||||
line-height: 1.7;
|
||||
}
|
||||
.insight-content p { margin-bottom: 6px; }
|
||||
|
||||
/* ========== FOOTER ========== */
|
||||
.footer {
|
||||
background: #f8fafc;
|
||||
border-top: 1px solid #e5e8ed;
|
||||
padding: 20px 40px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
}
|
||||
.footer-brand { font-weight: 600; color: #2463EB; }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.card-grid { grid-template-columns: 1fr; }
|
||||
.header h1 { font-size: 20px; }
|
||||
.body { padding: 20px 16px; }
|
||||
.action-row { grid-template-columns: 40px 1fr 70px 70px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
|
||||
<!-- ===== HEADER ===== -->
|
||||
<!--
|
||||
替换变量:
|
||||
{{label}} = 会议类型标签,如 "F 产研组会 · 产品团队"
|
||||
{{title}} = 会议标题,如 "第50场 · 进度优先拍板 + 代付功能 + 排名算法"
|
||||
{{date}} = 日期,如 "2026年3月17日 周一 17:32"
|
||||
{{duration}} = 时长,如 "1小时34分钟"
|
||||
{{participants}}= 参与人,如 "卡若、永平、老王、豆子、远志"
|
||||
-->
|
||||
<div class="header">
|
||||
<div class="header-label">{{label}}</div>
|
||||
<h1>{{title}}</h1>
|
||||
<div class="header-meta">
|
||||
<span>📅 {{date}}</span>
|
||||
<span>⏱ 时长:{{duration}}</span>
|
||||
<span>👥 参与:{{participants}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="body">
|
||||
|
||||
<!-- ===== 主要议题卡片(双栏) ===== -->
|
||||
<!--
|
||||
每张卡片结构:
|
||||
.card > .card-header(图标+标题+副标题)+ .card-body(小标题+要点列表+决策块)
|
||||
|
||||
图标颜色:blue/green/orange/purple/red/teal
|
||||
要点样式:highlight(蓝点)/ done(绿点)/ warn(橙点)/ block(红点)/ 无class(灰点)
|
||||
标签样式:tag-green/tag-red/tag-yellow/tag-orange/tag-blue/tag-purple/tag-gray
|
||||
决策块:.decision 蓝色左边框
|
||||
-->
|
||||
<div class="card-grid">
|
||||
|
||||
<!-- 卡片示例 -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-icon blue">①</div>
|
||||
<div>
|
||||
<h3>{{topic_title}}</h3>
|
||||
<div class="card-sub">{{topic_owner}} · {{topic_scope}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h4>📋 {{section_title}}</h4>
|
||||
<ul class="point-list">
|
||||
<li class="done">要点内容 <span class="tag tag-green">已完成</span></li>
|
||||
<li class="highlight">要点内容</li>
|
||||
<li class="warn">要点内容 <span class="tag tag-yellow">进行中</span></li>
|
||||
<li class="block">要点内容 <span class="tag tag-red">阻塞</span></li>
|
||||
</ul>
|
||||
<div class="decision">
|
||||
<strong>🎯 决策:</strong>决策内容描述
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 更多卡片... -->
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ===== 商业洞察/扩展讨论 ===== -->
|
||||
<div class="section-divider">
|
||||
<div class="divider-icon">💡</div>
|
||||
<h2>{{insight_section_title}}</h2>
|
||||
</div>
|
||||
|
||||
<div class="insight-card">
|
||||
<h3>{{insight_title}}</h3>
|
||||
<div class="insight-content">
|
||||
<p>洞察内容段落</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== 行动推进 ===== -->
|
||||
<div class="section-divider">
|
||||
<div class="divider-icon">▶</div>
|
||||
<h2>行动推进</h2>
|
||||
</div>
|
||||
|
||||
<div class="action-grid">
|
||||
<div class="action-row header-row">
|
||||
<div class="action-num">#</div>
|
||||
<div class="action-content">任务</div>
|
||||
<div class="action-owner">负责人</div>
|
||||
<div class="action-status">状态</div>
|
||||
</div>
|
||||
<!-- 行动项:复制此行并修改 -->
|
||||
<div class="action-row">
|
||||
<div class="action-num">1</div>
|
||||
<div class="action-content">任务描述</div>
|
||||
<div class="action-owner">负责人</div>
|
||||
<div class="action-status"><span class="tag tag-yellow">状态</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== 旁线记录(可选) ===== -->
|
||||
<div class="section-divider">
|
||||
<div class="divider-icon">📌</div>
|
||||
<h2>旁线记录</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-grid">
|
||||
<div class="insight-card" style="margin-bottom:0">
|
||||
<h3>{{side_topic_title}}</h3>
|
||||
<div class="insight-content">
|
||||
<p>旁线话题内容</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ===== FOOTER ===== -->
|
||||
<div class="footer">
|
||||
<div><span class="footer-brand">卡若AI</span> · 产研会议纪要 · {{generate_date}} · 自动生成</div>
|
||||
<div>{{team_name}} · {{meeting_info}}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
310
02_卡人(水)/水桥_平台对接/智能纪要/脚本/generate_meeting_image.py
Normal file
310
02_卡人(水)/水桥_平台对接/智能纪要/脚本/generate_meeting_image.py
Normal file
@@ -0,0 +1,310 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
产研会议纪要 → 苹果毛玻璃白底图片(直接生成,不落HTML文件)
|
||||
|
||||
用法:
|
||||
python3 generate_meeting_image.py --json data.json --output output.png
|
||||
或在代码中直接调用 generate_meeting_image(data_dict, output_path)
|
||||
|
||||
五大板块(固定结构):
|
||||
1. 🎯 目标&结果
|
||||
2. 📌 过程&问题
|
||||
3. 💡 反思&想法
|
||||
4. 📝 总结&优化
|
||||
5. ▶ 下一步执行
|
||||
"""
|
||||
|
||||
import json, sys, os, tempfile, argparse
|
||||
from pathlib import Path
|
||||
|
||||
TEMPLATE = """<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
* {{ margin:0; padding:0; box-sizing:border-box; }}
|
||||
body {{
|
||||
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text",
|
||||
"Helvetica Neue", Arial, sans-serif;
|
||||
background: #ffffff;
|
||||
color: #1d1d1f;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
padding: 0;
|
||||
}}
|
||||
.page {{
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
padding: 36px 40px 32px;
|
||||
}}
|
||||
|
||||
/* Header */
|
||||
.header {{
|
||||
background: rgba(245,245,247,0.72);
|
||||
backdrop-filter: blur(20px) saturate(1.2);
|
||||
-webkit-backdrop-filter: blur(20px) saturate(1.2);
|
||||
border: 1px solid rgba(0,0,0,0.06);
|
||||
border-radius: 18px;
|
||||
padding: 28px 32px 24px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow:
|
||||
0 1px 3px rgba(0,0,0,0.04),
|
||||
0 8px 24px rgba(0,0,0,0.06),
|
||||
inset 0 1px 0 rgba(255,255,255,0.8);
|
||||
}}
|
||||
.header-label {{
|
||||
display: inline-block;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1.5px;
|
||||
text-transform: uppercase;
|
||||
color: #0071e3;
|
||||
background: rgba(0,113,227,0.08);
|
||||
padding: 4px 12px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 12px;
|
||||
}}
|
||||
.header h1 {{
|
||||
font-size: 24px;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.03em;
|
||||
line-height: 1.35;
|
||||
margin-bottom: 14px;
|
||||
color: #1d1d1f;
|
||||
}}
|
||||
.header-meta {{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 18px;
|
||||
font-size: 13px;
|
||||
color: #6e6e73;
|
||||
font-weight: 500;
|
||||
}}
|
||||
|
||||
/* Section */
|
||||
.section {{
|
||||
background: rgba(245,245,247,0.55);
|
||||
backdrop-filter: blur(16px) saturate(1.1);
|
||||
-webkit-backdrop-filter: blur(16px) saturate(1.1);
|
||||
border: 1px solid rgba(0,0,0,0.05);
|
||||
border-radius: 16px;
|
||||
padding: 22px 28px 20px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow:
|
||||
0 1px 2px rgba(0,0,0,0.03),
|
||||
0 4px 16px rgba(0,0,0,0.04),
|
||||
inset 0 1px 0 rgba(255,255,255,0.7);
|
||||
}}
|
||||
.section-title {{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #1d1d1f;
|
||||
margin-bottom: 14px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid rgba(0,0,0,0.06);
|
||||
}}
|
||||
.section-icon {{
|
||||
font-size: 18px;
|
||||
}}
|
||||
.section-content {{
|
||||
font-size: 13.5px;
|
||||
line-height: 1.75;
|
||||
color: #2c2c2e;
|
||||
}}
|
||||
.section-content ul {{
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}}
|
||||
.section-content li {{
|
||||
padding: 4px 0 4px 20px;
|
||||
position: relative;
|
||||
}}
|
||||
.section-content li::before {{
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 13px;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
}}
|
||||
.section-content li.blue::before {{ background: #0071e3; }}
|
||||
.section-content li.green::before {{ background: #34c759; }}
|
||||
.section-content li.orange::before {{ background: #ff9f0a; }}
|
||||
.section-content li.red::before {{ background: #ff3b30; }}
|
||||
.section-content li.purple::before {{ background: #af52de; }}
|
||||
.section-content li.gray::before {{ background: #aeaeb2; }}
|
||||
.section-content li.teal::before {{ background: #30b0c7; }}
|
||||
|
||||
.tag {{
|
||||
display: inline-block;
|
||||
font-size: 10.5px;
|
||||
font-weight: 600;
|
||||
padding: 2px 8px;
|
||||
border-radius: 5px;
|
||||
margin-left: 6px;
|
||||
vertical-align: middle;
|
||||
}}
|
||||
.t-green {{ background: rgba(52,199,89,0.12); color: #248a3d; }}
|
||||
.t-red {{ background: rgba(255,59,48,0.1); color: #d70015; }}
|
||||
.t-yellow {{ background: rgba(255,159,10,0.12); color: #c93400; }}
|
||||
.t-blue {{ background: rgba(0,113,227,0.1); color: #0071e3; }}
|
||||
.t-gray {{ background: rgba(142,142,147,0.12); color: #636366; }}
|
||||
.t-purple {{ background: rgba(175,82,222,0.1); color: #8944ab; }}
|
||||
|
||||
.sub-section {{
|
||||
margin-top: 12px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid rgba(0,0,0,0.04);
|
||||
}}
|
||||
.sub-title {{
|
||||
font-size: 12.5px;
|
||||
font-weight: 700;
|
||||
color: #6e6e73;
|
||||
margin-bottom: 6px;
|
||||
}}
|
||||
|
||||
/* Two-col grid inside section */
|
||||
.two-col {{
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 14px;
|
||||
margin-top: 8px;
|
||||
}}
|
||||
.col-card {{
|
||||
background: rgba(255,255,255,0.6);
|
||||
border: 1px solid rgba(0,0,0,0.04);
|
||||
border-radius: 12px;
|
||||
padding: 14px 16px;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.03);
|
||||
}}
|
||||
.col-card h4 {{
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #1d1d1f;
|
||||
margin-bottom: 8px;
|
||||
}}
|
||||
|
||||
/* Footer */
|
||||
.footer {{
|
||||
text-align: center;
|
||||
padding: 20px 0 8px;
|
||||
font-size: 11.5px;
|
||||
color: #aeaeb2;
|
||||
font-weight: 500;
|
||||
}}
|
||||
.footer .brand {{ color: #0071e3; font-weight: 600; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
{header_html}
|
||||
{sections_html}
|
||||
<div class="footer">
|
||||
<span class="brand">卡若AI</span> · 产研会议纪要 · {generate_date} · 自动生成
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
|
||||
def build_header(data: dict) -> str:
|
||||
label = data.get("label", "产研组会 · 产品团队")
|
||||
title = data.get("title", "会议纪要")
|
||||
date = data.get("date", "")
|
||||
duration = data.get("duration", "")
|
||||
participants = data.get("participants", "")
|
||||
|
||||
meta_items = []
|
||||
if date:
|
||||
meta_items.append(f"<span>📅 {date}</span>")
|
||||
if duration:
|
||||
meta_items.append(f"<span>⏱ {duration}</span>")
|
||||
if participants:
|
||||
meta_items.append(f"<span>👥 {participants}</span>")
|
||||
|
||||
return f"""<div class="header">
|
||||
<div class="header-label">{label}</div>
|
||||
<h1>{title}</h1>
|
||||
<div class="header-meta">{''.join(meta_items)}</div>
|
||||
</div>"""
|
||||
|
||||
|
||||
def build_section(icon: str, title: str, items: list) -> str:
|
||||
li_html = ""
|
||||
for item in items:
|
||||
if isinstance(item, dict):
|
||||
text = item.get("text", "")
|
||||
color = item.get("color", "gray")
|
||||
tag = item.get("tag", "")
|
||||
tag_color = item.get("tag_color", "gray")
|
||||
tag_html = f' <span class="tag t-{tag_color}">{tag}</span>' if tag else ""
|
||||
li_html += f'<li class="{color}">{text}{tag_html}</li>\n'
|
||||
else:
|
||||
li_html += f'<li class="gray">{item}</li>\n'
|
||||
|
||||
return f"""<div class="section">
|
||||
<div class="section-title"><span class="section-icon">{icon}</span> {title}</div>
|
||||
<div class="section-content"><ul>{li_html}</ul></div>
|
||||
</div>"""
|
||||
|
||||
|
||||
def generate_meeting_image(data: dict, output_path: str):
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
header_html = build_header(data)
|
||||
|
||||
sections = [
|
||||
("🎯", "目标 & 结果", data.get("goals", [])),
|
||||
("📌", "过程 & 问题", data.get("process", [])),
|
||||
("💡", "反思 & 想法", data.get("reflection", [])),
|
||||
("📝", "总结 & 优化", data.get("summary", [])),
|
||||
("▶", "下一步执行", data.get("next_steps", [])),
|
||||
]
|
||||
|
||||
sections_html = "\n".join(
|
||||
build_section(icon, title, items)
|
||||
for icon, title, items in sections
|
||||
if items
|
||||
)
|
||||
|
||||
from datetime import datetime
|
||||
gen_date = datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
full_html = TEMPLATE.format(
|
||||
header_html=header_html,
|
||||
sections_html=sections_html,
|
||||
generate_date=gen_date,
|
||||
)
|
||||
|
||||
tmp = tempfile.NamedTemporaryFile(suffix=".html", delete=False, mode="w", encoding="utf-8")
|
||||
tmp.write(full_html)
|
||||
tmp.close()
|
||||
|
||||
try:
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch()
|
||||
page = browser.new_page(viewport={"width": 1000, "height": 800})
|
||||
page.goto(f"file://{tmp.name}")
|
||||
page.wait_for_timeout(500)
|
||||
page.screenshot(path=output_path, full_page=True)
|
||||
browser.close()
|
||||
finally:
|
||||
os.unlink(tmp.name)
|
||||
|
||||
print(f"✅ 已生成: {output_path} ({os.path.getsize(output_path)} bytes)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="产研会议纪要 → 毛玻璃白底图片")
|
||||
parser.add_argument("--json", required=True, help="JSON 数据文件路径")
|
||||
parser.add_argument("--output", "-o", required=True, help="输出 PNG 路径")
|
||||
args = parser.parse_args()
|
||||
|
||||
with open(args.json, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
generate_meeting_image(data, args.output)
|
||||
@@ -386,3 +386,4 @@
|
||||
| 2026-03-17 13:29:48 | 🔄 卡若AI 同步 2026-03-17 13:29 | 更新:金仓、水桥平台对接、运营中枢工作台 | 排除 >20MB: 11 个 |
|
||||
| 2026-03-17 15:00:32 | 🔄 卡若AI 同步 2026-03-17 15:00 | 更新:运营中枢工作台 | 排除 >20MB: 11 个 |
|
||||
| 2026-03-17 16:40:55 | 🔄 卡若AI 同步 2026-03-17 16:35 | 更新:金仓、总索引与入口、运营中枢工作台 | 排除 >20MB: 11 个 |
|
||||
| 2026-03-17 19:34:19 | 🔄 卡若AI 同步 2026-03-17 19:34 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 11 个 |
|
||||
|
||||
@@ -389,3 +389,4 @@
|
||||
| 2026-03-17 13:29:48 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-17 13:29 | 更新:金仓、水桥平台对接、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||
| 2026-03-17 15:00:32 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-17 15:00 | 更新:运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||
| 2026-03-17 16:40:55 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-17 16:35 | 更新:金仓、总索引与入口、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||
| 2026-03-17 19:34:19 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-17 19:34 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||
|
||||
Reference in New Issue
Block a user