🔄 卡若AI 同步 2026-02-24 19:59 | 更新:总索引与入口、水溪整理归档、卡木、运营中枢、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 12 个

This commit is contained in:
2026-02-24 19:59:14 +08:00
parent 5d8d30ac7e
commit 726086eac6
14 changed files with 1963 additions and 22 deletions

6
.gitignore vendored
View File

@@ -24,6 +24,10 @@ sync_tokens.env
**/智能纪要/脚本/feishu_user_token.txt
**/智能纪要/脚本/cookie_minutes.txt
# 卡若AI 网关:多租户配置与访问日志(不入库)
运营中枢/scripts/karuo_ai_gateway/config/gateway.yaml
运营中枢/工作台/karuo_ai_gateway_access.jsonl
# Node / 前端
node_modules/
.next/
@@ -90,6 +94,7 @@ _大文件外置/财务管理_data/chat.snapshot_收集.db
# === 自动排除超过20MB的文件脚本自动管理勿手动修改===
.venv/lib/python3.14/site-packages/playwright/driver/node
.venv_mem0/lib/python3.14/site-packages/grpc/_cython/cygrpc.cpython-314-darwin.so
.venv_ppt/lib/python3.14/site-packages/playwright/driver/node
01_卡资/金仓_存储备份/大文件外置/消息中枢_dist/windows控制包.zip
01_卡资/金仓_存储备份/大文件外置/视频切片_models/ggml-small.bin
01_卡资/金仓_存储备份/大文件外置/财务管理_data/chat.snapshot_data.db
@@ -98,4 +103,5 @@ _大文件外置/财务管理_data/chat.snapshot_收集.db
03_卡木/木叶_视频内容/视频切片/切片动效包装/10秒视频/node_modules/.cache/webpack/remotion-production-4.0.427/a233e9cccba253c3b0157f54cad843b8/0.pack
03_卡木/木叶_视频内容/视频切片/切片动效包装/10秒视频/node_modules/.remotion/chrome-headless-shell/mac-x64/chrome-headless-shell-mac-x64/chrome-headless-shell
03_卡木/木叶_视频内容/视频切片/切片动效包装/10秒视频/node_modules/@rspack/binding-darwin-x64/rspack.darwin-x64.node
03_卡木/木果_项目模板/PPT制作/.venv/lib/python3.14/site-packages/playwright/driver/node
# === 自动排除结束 ===

View File

@@ -0,0 +1,622 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1280, height=720">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; -webkit-font-smoothing: antialiased; }
.slide {
width: 1280px; height: 720px;
display: flex; flex-direction: column;
padding: 64px 80px;
background: linear-gradient(160deg, #F5F5FA 0%, #EBEBF0 35%, #F0F0F5 100%);
position: relative; overflow: hidden;
}
.slide::before {
content: ''; position: absolute; top: -40%; right: -25%;
width: 70%; height: 90%;
background: radial-gradient(ellipse, rgba(0,122,255,0.10) 0%, transparent 60%);
pointer-events: none;
}
.slide::after {
content: ''; position: absolute; bottom: -45%; left: -22%;
width: 70%; height: 95%;
background: radial-gradient(ellipse, rgba(52,199,89,0.10) 0%, transparent 58%);
pointer-events: none;
}
.glass {
background: rgba(255,255,255,0.60);
backdrop-filter: blur(48px); -webkit-backdrop-filter: blur(48px);
border: 1px solid rgba(255,255,255,0.72);
border-radius: 28px;
box-shadow: 0 8px 32px rgba(0,0,0,0.04), 0 0 0 1px rgba(255,255,255,0.8) inset;
}
.glass-strong {
background: rgba(255,255,255,0.74);
backdrop-filter: blur(56px); -webkit-backdrop-filter: blur(56px);
border: 1px solid rgba(255,255,255,0.78);
border-radius: 32px;
box-shadow: 0 12px 40px rgba(0,0,0,0.06), 0 0 0 1px rgba(255,255,255,0.9) inset;
}
.text-dark { color: #1D1D1F; }
.text-muted { color: #8E8E93; }
h1 { font-size: 52px; font-weight: 800; letter-spacing: -0.03em; }
h2 { font-size: 18px; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase; color: #007AFF; }
.kicker { font-size: 14px; letter-spacing: 0.18em; text-transform: uppercase; color: rgba(0,0,0,0.45); }
.row { display: flex; gap: 36px; }
.col { display: flex; flex-direction: column; gap: 18px; }
.pill {
display: inline-flex; align-items: center; gap: 10px;
padding: 10px 14px; border-radius: 999px;
background: rgba(0,122,255,0.10);
color: #0B5ED7; font-weight: 700; font-size: 14px;
border: 1px solid rgba(0,122,255,0.14);
white-space: nowrap;
}
.tag {
display: inline-flex; align-items: center; gap: 8px;
font-size: 14px; font-weight: 700;
color: rgba(0,0,0,0.58);
padding: 8px 12px; border-radius: 12px;
background: rgba(255,255,255,0.55);
border: 1px solid rgba(255,255,255,0.7);
}
.icon { font-size: 42px; line-height: 1; }
.small { font-size: 16px; line-height: 1.7; color: rgba(0,0,0,0.68); }
.list p { font-size: 20px; line-height: 1.85; color: #1D1D1F; }
.list p span { color: #8E8E93; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.metric { padding: 18px 20px; border-radius: 18px; background: rgba(255,255,255,0.62); border: 1px solid rgba(255,255,255,0.75); }
.metric .v { font-size: 28px; font-weight: 900; color: #1D1D1F; letter-spacing: -0.02em; }
.metric .k { font-size: 13px; font-weight: 800; color: rgba(0,0,0,0.50); letter-spacing: 0.14em; text-transform: uppercase; margin-top: 6px; }
.img-card { width: 420px; height: 420px; border-radius: 22px; overflow: hidden; padding: 16px; }
.img-card.wide { width: 520px; height: 360px; }
.note { padding: 14px 16px; border-radius: 16px; background: rgba(255,149,0,0.12); border: 1px solid rgba(255,149,0,0.18); color: rgba(0,0,0,0.72); font-weight: 600; }
.ok { background: rgba(52,199,89,0.12); border-color: rgba(52,199,89,0.18); }
.bad { background: rgba(255,59,48,0.10); border-color: rgba(255,59,48,0.16); }
.mono { font-variant-numeric: tabular-nums; }
</style>
</head>
<body>
<!-- Slide 1: 封面 -->
<div class="slide" id="slide-1" style="justify-content:center; align-items:center;">
<div class="glass-strong" style="padding: 56px 72px; text-align:center; width: 980px;">
<div class="kicker" style="margin-bottom: 10px;">CFO Plain Talk · Multi-Dimensional</div>
<h1 class="text-dark" style="margin-bottom: 18px;">公司财务盘点与现金流体检</h1>
<p class="text-muted" style="font-size: 22px; font-weight: 700;">大白话版本:把钱从哪来、花到哪去、哪里不对劲,一次说清楚</p>
<div style="display:flex; justify-content:center; gap:12px; margin-top: 26px; flex-wrap: wrap;">
<span class="pill">🧾 统一口径</span>
<span class="pill">💳 银行-卡号</span>
<span class="pill">🧹 同卡号去重</span>
<span class="pill">📈 多维报表</span>
<span class="pill">🧠 CFO 结论</span>
</div>
<div class="small" style="margin-top: 18px;">数据口径:统一流水去重后(支付宝 + 短信银行 + 公司收支卡卡猫→中信0405</div>
</div>
</div>
<!-- Slide 2: 一句话结论 -->
<div class="slide" id="slide-2" style="justify-content:center;">
<h2 style="margin-bottom: 28px;">Executive Summary</h2>
<div class="row" style="align-items:center;">
<div class="col" style="flex: 1;">
<div class="glass-strong" style="padding: 34px 40px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: 14px;">
<span class="icon">🧠</span>
<span class="tag">一句话</span>
</div>
<div class="list">
<p>这份口径里:<span>净现金流是 -214 万</span></p>
<p>但最大问题不是“真亏这么多”,而是:<span>短信里混进了额度/提醒,被当成支出</span></p>
<p>我的结论:<span>先把口径变干净,再谈控制成本和增长</span></p>
</div>
</div>
<div class="note" style="margin-top: 14px;">
✅ 你真正要盯的:中信银行-0405卡卡猫回款节奏 + 固定支出节奏 + 现金余粮。
</div>
</div>
<div class="glass img-card" style="padding: 16px;">
<!-- 配图:抽象“漏斗 + 数据清洗” -->
<svg viewBox="0 0 420 420" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="g2" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#0A84FF" stop-opacity="0.18"/>
<stop offset="55%" stop-color="#34C759" stop-opacity="0.14"/>
<stop offset="100%" stop-color="#FF9500" stop-opacity="0.12"/>
</linearGradient>
<filter id="s2" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="14" stdDeviation="12" flood-color="#000" flood-opacity="0.12"/>
</filter>
</defs>
<rect x="0" y="0" width="420" height="420" rx="18" fill="url(#g2)"/>
<g filter="url(#s2)">
<rect x="70" y="70" width="280" height="140" rx="18" fill="rgba(255,255,255,0.75)" stroke="rgba(255,255,255,0.9)"/>
<text x="95" y="115" font-size="16" font-weight="800" fill="rgba(0,0,0,0.72)">输入:三路数据</text>
<text x="95" y="145" font-size="14" font-weight="700" fill="rgba(0,0,0,0.55)">支付宝 / 短信 / 公司收支</text>
<rect x="110" y="235" width="200" height="34" rx="12" fill="rgba(10,132,255,0.14)" stroke="rgba(10,132,255,0.18)"/>
<text x="125" y="258" font-size="14" font-weight="800" fill="rgba(0,0,0,0.70)">清洗 + 去重 + 合并</text>
<path d="M210 210 L170 235 L250 235 Z" fill="rgba(255,255,255,0.85)"/>
<rect x="92" y="300" width="236" height="78" rx="18" fill="rgba(255,255,255,0.78)" stroke="rgba(255,255,255,0.92)"/>
<text x="115" y="338" font-size="18" font-weight="900" fill="rgba(0,0,0,0.80)">输出:统一报表</text>
<text x="115" y="363" font-size="14" font-weight="700" fill="rgba(0,0,0,0.55)">年度 / 月度 / 账户 / Top 对方</text>
</g>
</svg>
</div>
</div>
</div>
<!-- Slide 3: 口径与规则 -->
<div class="slide" id="slide-3" style="justify-content:center;">
<h2 style="margin-bottom: 28px;">Data Rules</h2>
<div class="row" style="align-items:center;">
<div class="col" style="flex: 1;">
<div class="glass-strong" style="padding: 32px 36px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: 12px;">
<span class="icon">🧾</span>
<span class="tag">口径说人话</span>
</div>
<div class="list">
<p>① 银行:<span>按“银行-卡号”分开</span>(比如 中信银行-0405</p>
<p>② 合并:<span>卡卡猫 = 中信银行-0405</span>(短信和公司收支算同一户)。</p>
<p>③ 去重:<span>同日期 + 同金额 + 同方向 + 同对方</span> → 直接去掉重复。</p>
<p>④ 时间:<span>每一笔都强制带日期+时间节点</span></p>
</div>
</div>
<div class="glass" style="padding: 18px 22px;">
<div class="small mono">你现在的“统一流水去重后”总笔数6,446 笔</div>
</div>
</div>
<div class="glass img-card wide" style="padding: 16px;">
<!-- 配图:流程图 -->
<svg viewBox="0 0 520 360" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="g3" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#FFFFFF" stop-opacity="0.88"/>
<stop offset="100%" stop-color="#FFFFFF" stop-opacity="0.58"/>
</linearGradient>
</defs>
<rect x="0" y="0" width="520" height="360" rx="18" fill="url(#g3)"/>
<g font-family="Inter, -apple-system, sans-serif">
<rect x="36" y="46" width="148" height="64" rx="16" fill="rgba(10,132,255,0.12)" stroke="rgba(10,132,255,0.18)"/>
<text x="52" y="75" font-size="14" font-weight="900" fill="rgba(0,0,0,0.72)">短信银行</text>
<text x="52" y="98" font-size="12" font-weight="700" fill="rgba(0,0,0,0.52)">银行-卡号</text>
<rect x="36" y="134" width="148" height="64" rx="16" fill="rgba(52,199,89,0.12)" stroke="rgba(52,199,89,0.18)"/>
<text x="52" y="163" font-size="14" font-weight="900" fill="rgba(0,0,0,0.72)">公司收支</text>
<text x="52" y="186" font-size="12" font-weight="700" fill="rgba(0,0,0,0.52)">卡卡猫→0405</text>
<rect x="36" y="222" width="148" height="64" rx="16" fill="rgba(255,149,0,0.12)" stroke="rgba(255,149,0,0.18)"/>
<text x="52" y="251" font-size="14" font-weight="900" fill="rgba(0,0,0,0.72)">支付宝</text>
<text x="52" y="274" font-size="12" font-weight="700" fill="rgba(0,0,0,0.52)">明细交易</text>
<path d="M210 78 C250 78 250 78 290 78" stroke="rgba(0,0,0,0.22)" stroke-width="3" fill="none"/>
<path d="M210 166 C250 166 250 166 290 166" stroke="rgba(0,0,0,0.22)" stroke-width="3" fill="none"/>
<path d="M210 254 C250 254 250 254 290 254" stroke="rgba(0,0,0,0.22)" stroke-width="3" fill="none"/>
<rect x="294" y="88" width="190" height="112" rx="18" fill="rgba(255,255,255,0.8)" stroke="rgba(255,255,255,0.92)"/>
<text x="314" y="122" font-size="16" font-weight="900" fill="rgba(0,0,0,0.74)">清洗 + 合并</text>
<text x="314" y="148" font-size="12" font-weight="700" fill="rgba(0,0,0,0.50)">按卡号 / 抽对方名</text>
<text x="314" y="170" font-size="12" font-weight="700" fill="rgba(0,0,0,0.50)">同卡号去重</text>
<rect x="294" y="216" width="190" height="96" rx="18" fill="rgba(255,255,255,0.82)" stroke="rgba(255,255,255,0.92)"/>
<text x="314" y="250" font-size="16" font-weight="900" fill="rgba(0,0,0,0.74)">多维报表</text>
<text x="314" y="276" font-size="12" font-weight="700" fill="rgba(0,0,0,0.50)">年/季/月/账户/Top</text>
</g>
</svg>
</div>
</div>
</div>
<!-- Slide 4: 总览数字 -->
<div class="slide" id="slide-4" style="justify-content:center;">
<h2 style="margin-bottom: 18px;">Numbers</h2>
<div class="row" style="align-items:center;">
<div class="col" style="flex: 1;">
<div class="glass-strong" style="padding: 28px 32px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: 14px;">
<span class="icon">📈</span>
<span class="tag">先看四个数</span>
</div>
<div class="grid2">
<div class="metric">
<div class="v mono">2,198,237.84</div>
<div class="k">总收入(元)</div>
</div>
<div class="metric">
<div class="v mono">4,342,873.62</div>
<div class="k">总支出(元)</div>
</div>
<div class="metric">
<div class="v mono" style="color:#FF3B30;">-2,144,635.78</div>
<div class="k">净现金流(元)</div>
</div>
<div class="metric">
<div class="v mono">50.6%</div>
<div class="k">收支比</div>
</div>
</div>
<div class="small" style="margin-top: 10px;">大白话:<b>进来的钱 ≈ 220 万</b>,出去的钱 ≈ <b>434 万</b>,所以净流出 ≈ <b>214 万</b></div>
</div>
<div class="note bad" style="margin-top: 14px;">
⚠️ 重要提醒:支出里有大量“短信噪音”(额度/提醒/营销),这会把支出夸大。
</div>
</div>
<div class="glass img-card" style="padding: 16px;">
<!-- 配图:环形占比 -->
<svg viewBox="0 0 420 420" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="s4" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="16" stdDeviation="14" flood-color="#000" flood-opacity="0.10"/>
</filter>
</defs>
<rect x="0" y="0" width="420" height="420" rx="18" fill="rgba(255,255,255,0.38)"/>
<g filter="url(#s4)">
<circle cx="210" cy="210" r="130" fill="none" stroke="rgba(0,0,0,0.08)" stroke-width="26"/>
<!-- 收入弧(约 33.6% -->
<circle cx="210" cy="210" r="130" fill="none" stroke="rgba(10,132,255,0.65)" stroke-width="26"
stroke-dasharray="273 545" stroke-linecap="round" transform="rotate(-90 210 210)"/>
<!-- 支出弧(剩余) -->
<circle cx="210" cy="210" r="130" fill="none" stroke="rgba(255,59,48,0.55)" stroke-width="26"
stroke-dasharray="545 545" stroke-linecap="round" transform="rotate(31 210 210)"/>
<text x="210" y="204" text-anchor="middle" font-size="22" font-weight="900" fill="rgba(0,0,0,0.78)">收入 vs 支出</text>
<text x="210" y="236" text-anchor="middle" font-size="14" font-weight="800" fill="rgba(0,0,0,0.54)">收支比 50.6%</text>
</g>
<g>
<rect x="74" y="320" width="272" height="56" rx="16" fill="rgba(255,255,255,0.72)" stroke="rgba(255,255,255,0.9)"/>
<text x="100" y="352" font-size="14" font-weight="800" fill="rgba(0,0,0,0.70)">蓝:收入 220 万</text>
<text x="240" y="352" font-size="14" font-weight="800" fill="rgba(0,0,0,0.70)">红:支出 434 万</text>
</g>
</svg>
</div>
</div>
</div>
<!-- Slide 5: 年度趋势 -->
<div class="slide" id="slide-5" style="justify-content:center;">
<h2 style="margin-bottom: 18px;">Trend</h2>
<div class="row" style="align-items:center;">
<div class="col" style="flex: 1;">
<div class="glass-strong" style="padding: 28px 32px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: 14px;">
<span class="icon">📉</span>
<span class="tag">年度怎么走</span>
</div>
<div class="list">
<p>20172024<span>整体平稳(收支接近)</span></p>
<p>2025<span>波动大(收入 110.8 万,支出 145.6 万)</span></p>
<p>2026<span>支出异常大184.9 万)</span><b>优先核对短信噪音</b></p>
</div>
</div>
<div class="note" style="margin-top: 14px;">
CFO 大白话:趋势里只要出现“突然暴涨/暴跌”,第一件事不是惊慌,是先问:<b>数据口径是不是变了</b>
</div>
</div>
<div class="glass img-card wide" style="padding: 16px;">
<!-- 配图:柱状趋势(简化) -->
<svg viewBox="0 0 520 360" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="520" height="360" rx="18" fill="rgba(255,255,255,0.42)"/>
<g font-family="Inter, -apple-system, sans-serif">
<text x="28" y="42" font-size="14" font-weight="900" fill="rgba(0,0,0,0.70)">净额(越低越危险)</text>
<!-- baseline -->
<line x1="28" y1="290" x2="492" y2="290" stroke="rgba(0,0,0,0.10)" stroke-width="2"/>
<!-- bars: 2024, 2025, 2026 (net) -->
<g>
<rect x="70" y="242" width="90" height="48" rx="12" fill="rgba(52,199,89,0.18)" stroke="rgba(52,199,89,0.22)"/>
<text x="115" y="312" text-anchor="middle" font-size="12" font-weight="800" fill="rgba(0,0,0,0.55)">2024</text>
<text x="115" y="232" text-anchor="middle" font-size="12" font-weight="900" fill="rgba(0,0,0,0.62)">-0.01万</text>
</g>
<g>
<rect x="215" y="196" width="90" height="94" rx="12" fill="rgba(255,149,0,0.20)" stroke="rgba(255,149,0,0.24)"/>
<text x="260" y="312" text-anchor="middle" font-size="12" font-weight="800" fill="rgba(0,0,0,0.55)">2025</text>
<text x="260" y="186" text-anchor="middle" font-size="12" font-weight="900" fill="rgba(0,0,0,0.62)">-33.8万</text>
</g>
<g>
<rect x="360" y="72" width="90" height="218" rx="12" fill="rgba(255,59,48,0.22)" stroke="rgba(255,59,48,0.26)"/>
<text x="405" y="312" text-anchor="middle" font-size="12" font-weight="800" fill="rgba(0,0,0,0.55)">2026</text>
<text x="405" y="62" text-anchor="middle" font-size="12" font-weight="900" fill="rgba(0,0,0,0.62)">-182.9万</text>
</g>
<text x="28" y="336" font-size="12" font-weight="700" fill="rgba(0,0,0,0.48)">2026 异常值需先做短信噪音剔除</text>
</g>
</svg>
</div>
</div>
</div>
<!-- Slide 6: 账户结构 -->
<div class="slide" id="slide-6" style="justify-content:center;">
<h2 style="margin-bottom: 18px;">Accounts</h2>
<div class="row" style="align-items:center;">
<div class="col" style="flex: 1;">
<div class="glass-strong" style="padding: 28px 32px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: 14px;">
<span class="icon">💳</span>
<span class="tag">钱主要在哪两条线</span>
</div>
<div class="list">
<p>收入结构:<span>中信银行-0405 占 50.6%</span>,支付宝占 49.1%。</p>
<p>大白话:公司收入基本就是<span>两条腿走路</span></p>
<p>支出结构里:<span>“其他”非常大</span> → 先当“待核对”。</p>
</div>
</div>
<div class="note ok" style="margin-top: 14px;">
✅ 财务总监盯盘顺序先盯“现金进出最大的账户”也就是中信0405其次才是零碎账户。
</div>
</div>
<div class="glass img-card wide" style="padding: 16px;">
<!-- 配图:两条腿 + 饼图 -->
<svg viewBox="0 0 520 360" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="520" height="360" rx="18" fill="rgba(255,255,255,0.40)"/>
<g font-family="Inter, -apple-system, sans-serif">
<text x="28" y="42" font-size="14" font-weight="900" fill="rgba(0,0,0,0.70)">收入占比(两条腿)</text>
<circle cx="160" cy="190" r="90" fill="none" stroke="rgba(0,0,0,0.08)" stroke-width="22"/>
<circle cx="160" cy="190" r="90" fill="none" stroke="rgba(10,132,255,0.62)" stroke-width="22"
stroke-dasharray="283 565" stroke-linecap="round" transform="rotate(-90 160 190)"/>
<circle cx="160" cy="190" r="90" fill="none" stroke="rgba(52,199,89,0.58)" stroke-width="22"
stroke-dasharray="282 565" stroke-linecap="round" transform="rotate(12 160 190)"/>
<text x="160" y="196" text-anchor="middle" font-size="18" font-weight="900" fill="rgba(0,0,0,0.76)">≈ 50 / 50</text>
<text x="160" y="220" text-anchor="middle" font-size="12" font-weight="800" fill="rgba(0,0,0,0.52)">中信0405 / 支付宝</text>
<rect x="285" y="108" width="206" height="64" rx="16" fill="rgba(10,132,255,0.12)" stroke="rgba(10,132,255,0.18)"/>
<text x="305" y="140" font-size="14" font-weight="900" fill="rgba(0,0,0,0.70)">中信银行-0405</text>
<text x="305" y="162" font-size="12" font-weight="800" fill="rgba(0,0,0,0.52)">收入 111.3 万</text>
<rect x="285" y="186" width="206" height="64" rx="16" fill="rgba(52,199,89,0.12)" stroke="rgba(52,199,89,0.18)"/>
<text x="305" y="218" font-size="14" font-weight="900" fill="rgba(0,0,0,0.70)">支付宝</text>
<text x="305" y="240" font-size="12" font-weight="800" fill="rgba(0,0,0,0.52)">收入 108.0 万</text>
<text x="285" y="292" font-size="12" font-weight="800" fill="rgba(0,0,0,0.46)">支出里“其他”先当待核对项</text>
</g>
</svg>
</div>
</div>
</div>
<!-- Slide 7: 中信0405体检 -->
<div class="slide" id="slide-7" style="justify-content:center;">
<h2 style="margin-bottom: 18px;">CITIC 0405</h2>
<div class="row" style="align-items:center;">
<div class="col" style="flex: 1;">
<div class="glass-strong" style="padding: 28px 32px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: 14px;">
<span class="icon">🏦</span>
<span class="tag">卡卡猫 = 中信银行-0405</span>
</div>
<div class="list">
<p>收入:<span class="mono">1,112,568.09</span>(约 111.3 万)</p>
<p>支出:<span class="mono">1,013,829.59</span>(约 101.4 万)</p>
<p>净额:<span class="mono">+98,738.50</span>(约 +9.9 万)</p>
</div>
</div>
<div class="note" style="margin-top: 14px;">
CFO 大白话:这户总体是“能自己养活自己”的,但月度上会出现<b>回款空窗期</b>,需要现金阈值和预算上限。
</div>
</div>
<div class="glass img-card wide" style="padding: 16px;">
<!-- 配图:月度风险提示 -->
<svg viewBox="0 0 520 360" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="520" height="360" rx="18" fill="rgba(255,255,255,0.40)"/>
<g font-family="Inter, -apple-system, sans-serif">
<text x="28" y="42" font-size="14" font-weight="900" fill="rgba(0,0,0,0.70)">月度现金流(公司收支表)</text>
<rect x="28" y="72" width="464" height="78" rx="18" fill="rgba(52,199,89,0.10)" stroke="rgba(52,199,89,0.16)"/>
<text x="48" y="105" font-size="14" font-weight="900" fill="rgba(0,0,0,0.72)">2025-12净 +18.08 万</text>
<text x="48" y="130" font-size="12" font-weight="800" fill="rgba(0,0,0,0.52)">回款 30.05 万 / 支出 11.97 万</text>
<rect x="28" y="168" width="464" height="78" rx="18" fill="rgba(255,149,0,0.10)" stroke="rgba(255,149,0,0.16)"/>
<text x="48" y="201" font-size="14" font-weight="900" fill="rgba(0,0,0,0.72)">2026-01净 -17.99 万</text>
<text x="48" y="226" font-size="12" font-weight="800" fill="rgba(0,0,0,0.52)">无收入 / 支出 17.99 万(现金压力月)</text>
<rect x="28" y="264" width="464" height="78" rx="18" fill="rgba(255,59,48,0.08)" stroke="rgba(255,59,48,0.14)"/>
<text x="48" y="297" font-size="14" font-weight="900" fill="rgba(0,0,0,0.72)">建议:设“现金红线”</text>
<text x="48" y="322" font-size="12" font-weight="800" fill="rgba(0,0,0,0.52)">例如:余额低于 2 个月固定成本 → 自动降本 + 推回款</text>
</g>
</svg>
</div>
</div>
</div>
<!-- Slide 8: 支付宝体检 -->
<div class="slide" id="slide-8" style="justify-content:center;">
<h2 style="margin-bottom: 18px;">Alipay</h2>
<div class="row" style="align-items:center;">
<div class="col" style="flex: 1;">
<div class="glass-strong" style="padding: 28px 32px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: 14px;">
<span class="icon">📲</span>
<span class="tag">支付宝这条线</span>
</div>
<div class="list">
<p>收入:<span class="mono">1,080,038.40</span></p>
<p>支出:<span class="mono">1,070,871.17</span></p>
<p>净额:<span class="mono">+9,167.23</span></p>
<p>手续费:<span>约 2,154 元</span>(属于“交易成本”)</p>
</div>
</div>
<div class="note ok" style="margin-top: 14px;">
✅ 大白话:支付宝整体“基本打平”,它更像一个收款/分发通道,不是亏损黑洞。
</div>
</div>
<div class="glass img-card" style="padding: 16px;">
<!-- 配图:支付与手续费 -->
<svg viewBox="0 0 420 420" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="420" height="420" rx="18" fill="rgba(255,255,255,0.40)"/>
<g font-family="Inter, -apple-system, sans-serif">
<rect x="70" y="78" width="280" height="110" rx="18" fill="rgba(10,132,255,0.12)" stroke="rgba(10,132,255,0.18)"/>
<text x="95" y="120" font-size="16" font-weight="900" fill="rgba(0,0,0,0.72)">交易</text>
<text x="95" y="152" font-size="12" font-weight="800" fill="rgba(0,0,0,0.52)">收入 ≈ 108.0 万</text>
<text x="95" y="174" font-size="12" font-weight="800" fill="rgba(0,0,0,0.52)">支出 ≈ 107.1 万</text>
<rect x="70" y="224" width="280" height="110" rx="18" fill="rgba(255,149,0,0.10)" stroke="rgba(255,149,0,0.16)"/>
<text x="95" y="266" font-size="16" font-weight="900" fill="rgba(0,0,0,0.72)">手续费</text>
<text x="95" y="298" font-size="12" font-weight="800" fill="rgba(0,0,0,0.52)">≈ 2,154 元</text>
<text x="210" y="372" text-anchor="middle" font-size="12" font-weight="800" fill="rgba(0,0,0,0.48)">能接受的成本 ≠ 异常支出</text>
</g>
</svg>
</div>
</div>
</div>
<!-- Slide 9: 支出Top与异常 -->
<div class="slide" id="slide-9" style="justify-content:center;">
<h2 style="margin-bottom: 18px;">Expense Red Flags</h2>
<div class="row" style="align-items:center;">
<div class="col" style="flex: 1;">
<div class="glass-strong" style="padding: 28px 32px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: 14px;">
<span class="icon">🚨</span>
<span class="tag">支出 Top 的“真假判断”</span>
</div>
<div class="list">
<p>看到这些就先别慌:<span>它们很可能不是“真支出”</span></p>
<p>例子:<span>“授予额度 398000”</span><span>“剩余额度 0.17 元”</span><span>营销提醒</span></p>
<p>我的建议:<span>把“短信提醒类”单独一栏,别和真实流水混在一起</span></p>
</div>
</div>
<div class="note" style="margin-top: 14px;">
🧹 下一次迭代:给短信解析加“过滤规则”(额度/授信/提醒/预测/账单非交易),让报表更接近真实。
</div>
</div>
<div class="glass img-card wide" style="padding: 16px;">
<!-- 配图:红旗清单 -->
<svg viewBox="0 0 520 360" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="520" height="360" rx="18" fill="rgba(255,255,255,0.40)"/>
<g font-family="Inter, -apple-system, sans-serif">
<text x="28" y="42" font-size="14" font-weight="900" fill="rgba(0,0,0,0.70)">容易误判的短信关键词(建议过滤)</text>
<g>
<rect x="28" y="72" width="464" height="54" rx="16" fill="rgba(255,59,48,0.10)" stroke="rgba(255,59,48,0.16)"/>
<text x="48" y="104" font-size="13" font-weight="900" fill="rgba(0,0,0,0.72)">授予 / 额度 / 预授信 / 可用额度 / 剩余可用额度</text>
</g>
<g>
<rect x="28" y="138" width="464" height="54" rx="16" fill="rgba(255,149,0,0.10)" stroke="rgba(255,149,0,0.16)"/>
<text x="48" y="170" font-size="13" font-weight="900" fill="rgba(0,0,0,0.72)">提醒 / 通知 / 预测 / 将于到期 / 即将到期</text>
</g>
<g>
<rect x="28" y="204" width="464" height="54" rx="16" fill="rgba(10,132,255,0.10)" stroke="rgba(10,132,255,0.16)"/>
<text x="48" y="236" font-size="13" font-weight="900" fill="rgba(0,0,0,0.72)">账单:应还 / 最低还款 / 本期无需还款(非交易)</text>
</g>
<g>
<rect x="28" y="270" width="464" height="70" rx="16" fill="rgba(52,199,89,0.10)" stroke="rgba(52,199,89,0.16)"/>
<text x="48" y="302" font-size="13" font-weight="900" fill="rgba(0,0,0,0.72)">最终目标:把“真实交易”与“提醒噪音”分两张表</text>
<text x="48" y="326" font-size="12" font-weight="800" fill="rgba(0,0,0,0.52)">这样你才能拿报表做决策(降本/预算/回款/投资)</text>
</g>
</g>
</svg>
</div>
</div>
</div>
<!-- Slide 10: CFO三件事 -->
<div class="slide" id="slide-10" style="justify-content:center;">
<h2 style="margin-bottom: 18px;">CFO Playbook</h2>
<div class="row" style="align-items:center;">
<div class="col" style="flex: 1;">
<div class="glass-strong" style="padding: 28px 32px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: 14px;">
<span class="icon">🧩</span>
<span class="tag">先保命,再增长</span>
</div>
<div class="list">
<p>第 1 件:<span>现金能撑多久</span>(按“固定成本/月”算)。</p>
<p>第 2 件:<span>支出有没有上限</span>(预算 + 审批)。</p>
<p>第 3 件:<span>回款有没有节奏</span>(每周盯应收)。</p>
</div>
</div>
<div class="note ok" style="margin-top: 14px;">
✅ 大白话:财务总监最怕的不是“利润低”,是“现金断”。现金断了,业务再好也没用。
</div>
</div>
<div class="glass img-card" style="padding: 16px;">
<!-- 配图:仪表盘 -->
<svg viewBox="0 0 420 420" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="420" height="420" rx="18" fill="rgba(255,255,255,0.40)"/>
<g font-family="Inter, -apple-system, sans-serif">
<text x="40" y="66" font-size="16" font-weight="900" fill="rgba(0,0,0,0.72)">每周必看的 3 个仪表</text>
<rect x="40" y="92" width="340" height="84" rx="18" fill="rgba(10,132,255,0.10)" stroke="rgba(10,132,255,0.16)"/>
<text x="64" y="126" font-size="14" font-weight="900" fill="rgba(0,0,0,0.70)">现金余粮</text>
<text x="64" y="152" font-size="12" font-weight="800" fill="rgba(0,0,0,0.52)">余额 / 月固定成本 = 还能活几个月</text>
<rect x="40" y="192" width="340" height="84" rx="18" fill="rgba(52,199,89,0.10)" stroke="rgba(52,199,89,0.16)"/>
<text x="64" y="226" font-size="14" font-weight="900" fill="rgba(0,0,0,0.70)">预算执行</text>
<text x="64" y="252" font-size="12" font-weight="800" fill="rgba(0,0,0,0.52)">本月已花 / 预算上限(超了立刻刹车)</text>
<rect x="40" y="292" width="340" height="84" rx="18" fill="rgba(255,149,0,0.10)" stroke="rgba(255,149,0,0.16)"/>
<text x="64" y="326" font-size="14" font-weight="900" fill="rgba(0,0,0,0.70)">应收回款</text>
<text x="64" y="352" font-size="12" font-weight="800" fill="rgba(0,0,0,0.52)">本周应收 / 已收 / 逾期(谁没付)</text>
</g>
</svg>
</div>
</div>
</div>
<!-- Slide 11: 30天行动 -->
<div class="slide" id="slide-11" style="justify-content:center;">
<h2 style="margin-bottom: 18px;">30-Day Plan</h2>
<div class="row" style="align-items:center;">
<div class="col" style="flex: 1;">
<div class="glass-strong" style="padding: 28px 32px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: 14px;">
<span class="icon">🗓️</span>
<span class="tag">30 天迭代计划</span>
</div>
<div class="list">
<p>第 1 周:<span>把短信噪音剔除</span>(额度/提醒/营销)→ 报表不再“虚胖”。</p>
<p>第 2 周:<span>建立预算科目</span>(工资/税/云/房租/外包/营销)。</p>
<p>第 3 周:<span>做 13 周现金预测</span>(每周滚动)。</p>
<p>第 4 周:<span>固定仪表盘</span>(每周 10 分钟看一次)。</p>
</div>
</div>
<div class="note ok" style="margin-top: 14px;">
✅ 你最终要的是“自动化财务雷达”:一眼发现异常,一键追溯明细。
</div>
</div>
<div class="glass img-card wide" style="padding: 16px;">
<!-- 配图:路线图 -->
<svg viewBox="0 0 520 360" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="520" height="360" rx="18" fill="rgba(255,255,255,0.40)"/>
<g font-family="Inter, -apple-system, sans-serif">
<text x="28" y="42" font-size="14" font-weight="900" fill="rgba(0,0,0,0.70)">从“报表”升级到“财务系统”</text>
<path d="M56 100 C160 60, 260 140, 360 100 C420 80, 450 120, 468 150" stroke="rgba(0,0,0,0.18)" stroke-width="6" fill="none" stroke-linecap="round"/>
<circle cx="56" cy="100" r="10" fill="rgba(10,132,255,0.70)"/>
<circle cx="186" cy="86" r="10" fill="rgba(52,199,89,0.70)"/>
<circle cx="306" cy="126" r="10" fill="rgba(255,149,0,0.75)"/>
<circle cx="468" cy="150" r="10" fill="rgba(255,59,48,0.70)"/>
<text x="40" y="142" font-size="12" font-weight="900" fill="rgba(0,0,0,0.62)">W1</text>
<text x="170" y="128" font-size="12" font-weight="900" fill="rgba(0,0,0,0.62)">W2</text>
<text x="292" y="168" font-size="12" font-weight="900" fill="rgba(0,0,0,0.62)">W3</text>
<text x="452" y="192" font-size="12" font-weight="900" fill="rgba(0,0,0,0.62)">W4</text>
<rect x="28" y="210" width="464" height="120" rx="18" fill="rgba(255,255,255,0.72)" stroke="rgba(255,255,255,0.92)"/>
<text x="48" y="242" font-size="13" font-weight="900" fill="rgba(0,0,0,0.72)">W1 清口径</text>
<text x="48" y="266" font-size="12" font-weight="800" fill="rgba(0,0,0,0.52)">过滤短信噪音,真实交易单独统计</text>
<text x="48" y="294" font-size="13" font-weight="900" fill="rgba(0,0,0,0.72)">W2 建预算</text>
<text x="160" y="294" font-size="13" font-weight="900" fill="rgba(0,0,0,0.72)">W3 做预测</text>
<text x="284" y="294" font-size="13" font-weight="900" fill="rgba(0,0,0,0.72)">W4 固仪表</text>
</g>
</svg>
</div>
</div>
</div>
<!-- Slide 12: 结尾 -->
<div class="slide" id="slide-12" style="justify-content:center; align-items:center;">
<div class="glass-strong" style="padding: 56px 72px; text-align:center; width: 980px;">
<span class="icon"></span>
<h1 class="text-dark" style="margin: 18px 0 10px;">最后一句大白话</h1>
<p class="text-muted" style="font-size: 22px; font-weight: 800; line-height: 1.6;">
报表不是用来“看热闹”的,<br>
是用来“提前发现风险、提前踩刹车、提前安排回款”的。
</p>
<div style="display:flex; justify-content:center; gap:12px; margin-top: 26px; flex-wrap: wrap;">
<span class="pill">🧾 口径干净</span>
<span class="pill">💳 账户清楚</span>
<span class="pill">🧠 结论能落地</span>
<span class="pill">📈 每周滚动预测</span>
</div>
<div class="small" style="margin-top: 18px;">下一步:我可以继续把“短信噪音过滤规则”做成自动更新,让 2026 年异常支出回归真实。</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,449 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1280, height=720">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; -webkit-font-smoothing: antialiased; }
.slide {
width: 1280px; height: 720px;
display: flex; flex-direction: column;
padding: 64px 80px;
background: linear-gradient(160deg, #F5F5FA 0%, #EBEBF0 35%, #F0F0F5 100%);
position: relative; overflow: hidden;
}
.slide::before {
content: ''; position: absolute; top: -40%; right: -25%;
width: 70%; height: 90%;
background: radial-gradient(ellipse, rgba(0,122,255,0.10) 0%, transparent 60%);
pointer-events: none;
}
.slide::after {
content: ''; position: absolute; bottom: -45%; left: -22%;
width: 70%; height: 95%;
background: radial-gradient(ellipse, rgba(52,199,89,0.10) 0%, transparent 58%);
pointer-events: none;
}
.glass {
background: rgba(255,255,255,0.60);
backdrop-filter: blur(48px); -webkit-backdrop-filter: blur(48px);
border: 1px solid rgba(255,255,255,0.72);
border-radius: 28px;
box-shadow: 0 8px 32px rgba(0,0,0,0.04), 0 0 0 1px rgba(255,255,255,0.8) inset;
}
.glass-strong {
background: rgba(255,255,255,0.74);
backdrop-filter: blur(56px); -webkit-backdrop-filter: blur(56px);
border: 1px solid rgba(255,255,255,0.78);
border-radius: 32px;
box-shadow: 0 12px 40px rgba(0,0,0,0.06), 0 0 0 1px rgba(255,255,255,0.9) inset;
}
.text-dark { color: #1D1D1F; }
.text-muted { color: #8E8E93; }
h1 { font-size: 52px; font-weight: 900; letter-spacing: -0.03em; }
h2 { font-size: 18px; font-weight: 900; letter-spacing: 0.12em; text-transform: uppercase; color: #007AFF; }
.kicker { font-size: 14px; letter-spacing: 0.18em; text-transform: uppercase; color: rgba(0,0,0,0.45); }
.row { display: flex; gap: 36px; }
.col { display: flex; flex-direction: column; gap: 18px; }
.pill {
display: inline-flex; align-items: center; gap: 10px;
padding: 10px 14px; border-radius: 999px;
background: rgba(0,122,255,0.10);
color: #0B5ED7; font-weight: 800; font-size: 14px;
border: 1px solid rgba(0,122,255,0.14);
white-space: nowrap;
}
.tag {
display: inline-flex; align-items: center; gap: 8px;
font-size: 14px; font-weight: 800;
color: rgba(0,0,0,0.58);
padding: 8px 12px; border-radius: 12px;
background: rgba(255,255,255,0.55);
border: 1px solid rgba(255,255,255,0.7);
}
.icon { font-size: 42px; line-height: 1; }
.small { font-size: 16px; line-height: 1.7; color: rgba(0,0,0,0.68); }
.list p { font-size: 20px; line-height: 1.85; color: #1D1D1F; }
.list p span { color: #8E8E93; font-weight: 700; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.metric { padding: 18px 20px; border-radius: 18px; background: rgba(255,255,255,0.62); border: 1px solid rgba(255,255,255,0.75); }
.metric .v { font-size: 28px; font-weight: 950; color: #1D1D1F; letter-spacing: -0.02em; }
.metric .k { font-size: 13px; font-weight: 900; color: rgba(0,0,0,0.50); letter-spacing: 0.14em; text-transform: uppercase; margin-top: 6px; }
.img-card { width: 420px; height: 420px; border-radius: 22px; overflow: hidden; padding: 16px; }
.img-card.wide { width: 520px; height: 360px; }
.note { padding: 14px 16px; border-radius: 16px; background: rgba(255,149,0,0.12); border: 1px solid rgba(255,149,0,0.18); color: rgba(0,0,0,0.72); font-weight: 700; }
.ok { background: rgba(52,199,89,0.12); border-color: rgba(52,199,89,0.18); }
.bad { background: rgba(255,59,48,0.10); border-color: rgba(255,59,48,0.16); }
.mono { font-variant-numeric: tabular-nums; }
table { width: 100%; border-collapse: separate; border-spacing: 0 10px; }
td { padding: 12px 14px; font-size: 18px; color: rgba(0,0,0,0.78); }
.trow { background: rgba(255,255,255,0.62); border: 1px solid rgba(255,255,255,0.78); border-radius: 16px; }
.trow td:first-child { border-top-left-radius: 16px; border-bottom-left-radius: 16px; font-weight: 900; }
.trow td:last-child { border-top-right-radius: 16px; border-bottom-right-radius: 16px; text-align: right; font-weight: 950; }
</style>
</head>
<body>
<!-- Slide 1: 封面 -->
<div class="slide" id="slide-1" style="justify-content:center; align-items:center;">
<div class="glass-strong" style="padding: 56px 72px; text-align:center; width: 980px;">
<div class="kicker" style="margin-bottom: 10px;">Monthly CFO Report · Plain Talk</div>
<h1 class="text-dark" style="margin-bottom: 18px;">2026年1月 · 公司财务月报</h1>
<p class="text-muted" style="font-size: 22px; font-weight: 800;">十页左右|把“钱从哪来、花到哪去、下月怎么管”讲明白</p>
<div style="display:flex; justify-content:center; gap:12px; margin-top: 26px; flex-wrap: wrap;">
<span class="pill">🏦 卡卡猫·中信0405</span>
<span class="pill">🧾 支付宝·企业</span>
<span class="pill">🌊 芸归喜·网商9532</span>
<span class="pill">📩 短信口径</span>
</div>
<div class="small" style="margin-top: 18px;">数据来源:`2026年1月财务报表_完整表格.md`生成2026-02-08</div>
</div>
</div>
<!-- Slide 2: 本月一句话 + 关键数字 -->
<div class="slide" id="slide-2" style="justify-content:center;">
<h2 style="margin-bottom: 18px;">Executive Summary</h2>
<div class="row" style="align-items:center;">
<div class="col" style="flex: 1;">
<div class="glass-strong" style="padding: 28px 32px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: 14px;">
<span class="icon">🧠</span>
<span class="tag">一句话结论</span>
</div>
<div class="list">
<p>1月公司账户核心结论就是一句话<span>卡卡猫中信0405单月净流出 17.99 万</span></p>
<p>支付宝企业本月是<span>小幅净流入 +240.71</span>;芸归喜只有<span>收入 5.94</span></p>
</div>
</div>
<div class="note ok" style="margin-top: 14px;">
✅ CFO 大白话:这月不是“赚不赚钱”的问题,是“现金流怎么扛”的问题。
</div>
</div>
<div class="glass img-card wide" style="padding: 16px;">
<svg viewBox="0 0 520 360" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="520" height="360" rx="18" fill="rgba(255,255,255,0.42)"/>
<g font-family="Inter, -apple-system, sans-serif">
<text x="28" y="42" font-size="14" font-weight="900" fill="rgba(0,0,0,0.70)">本月三条线(净额)</text>
<rect x="28" y="70" width="464" height="76" rx="18" fill="rgba(255,59,48,0.10)" stroke="rgba(255,59,48,0.16)"/>
<text x="48" y="104" font-size="16" font-weight="950" fill="rgba(0,0,0,0.76)">卡卡猫中信0405</text>
<text x="48" y="130" font-size="14" font-weight="900" fill="rgba(0,0,0,0.56)">净 -179,853.23</text>
<rect x="28" y="158" width="464" height="76" rx="18" fill="rgba(52,199,89,0.10)" stroke="rgba(52,199,89,0.16)"/>
<text x="48" y="192" font-size="16" font-weight="950" fill="rgba(0,0,0,0.76)">支付宝(企业)</text>
<text x="48" y="218" font-size="14" font-weight="900" fill="rgba(0,0,0,0.56)">净 +240.71</text>
<rect x="28" y="246" width="464" height="76" rx="18" fill="rgba(10,132,255,0.10)" stroke="rgba(10,132,255,0.16)"/>
<text x="48" y="280" font-size="16" font-weight="950" fill="rgba(0,0,0,0.76)">短信口径(实时)</text>
<text x="48" y="306" font-size="14" font-weight="900" fill="rgba(0,0,0,0.56)">净 +3,715</text>
</g>
</svg>
</div>
</div>
</div>
<!-- Slide 3: 口径说明与待补项 -->
<div class="slide" id="slide-3" style="justify-content:center;">
<h2 style="margin-bottom: 18px;">Scope</h2>
<div class="row" style="align-items:center;">
<div class="col" style="flex: 1;">
<div class="glass-strong" style="padding: 28px 32px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: 14px;">
<span class="icon">🧾</span>
<span class="tag">这月报表怎么用</span>
</div>
<div class="list">
<p>我们优先把“可确认”的钱算清楚:<span>卡卡猫中信0405</span><span>支付宝企业</span><span>芸归喜网商9532</span></p>
<p>短信口径是“实时提醒”:<span>更像雷达</span>,不是最终账。</p>
<p>腾讯云消费:<span>本月待补</span>(通常次月出账单)。</p>
</div>
</div>
<div class="note" style="margin-top: 14px;">
⚠️ 待补项(本月):云消费(腾讯云)、飞书报销/付款明细。
</div>
</div>
<div class="glass img-card" style="padding: 16px;">
<svg viewBox="0 0 420 420" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="420" height="420" rx="18" fill="rgba(255,255,255,0.38)"/>
<g font-family="Inter, -apple-system, sans-serif">
<text x="36" y="66" font-size="16" font-weight="950" fill="rgba(0,0,0,0.76)">本月数据来源</text>
<rect x="36" y="92" width="348" height="84" rx="18" fill="rgba(52,199,89,0.10)" stroke="rgba(52,199,89,0.16)"/>
<text x="60" y="126" font-size="14" font-weight="900" fill="rgba(0,0,0,0.70)">重点机构交易明细</text>
<text x="60" y="152" font-size="12" font-weight="800" fill="rgba(0,0,0,0.52)">卡卡猫 / 芸归喜</text>
<rect x="36" y="192" width="348" height="84" rx="18" fill="rgba(10,132,255,0.10)" stroke="rgba(10,132,255,0.16)"/>
<text x="60" y="226" font-size="14" font-weight="900" fill="rgba(0,0,0,0.70)">支付宝 API</text>
<text x="60" y="252" font-size="12" font-weight="800" fill="rgba(0,0,0,0.52)">企业号 signcustomer</text>
<rect x="36" y="292" width="348" height="92" rx="18" fill="rgba(255,149,0,0.10)" stroke="rgba(255,149,0,0.16)"/>
<text x="60" y="326" font-size="14" font-weight="900" fill="rgba(0,0,0,0.70)">iPhone 短信</text>
<text x="60" y="352" font-size="12" font-weight="800" fill="rgba(0,0,0,0.52)">实时提醒(需过滤噪音)</text>
</g>
</svg>
</div>
</div>
</div>
<!-- Slide 4: 总览表(本月) -->
<div class="slide" id="slide-4" style="justify-content:center;">
<h2 style="margin-bottom: 18px;">Overview</h2>
<div class="row" style="align-items:flex-start;">
<div class="col" style="flex: 1;">
<div class="glass-strong" style="padding: 26px 28px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: 10px;">
<span class="icon">📋</span>
<span class="tag">总览表(大白话)</span>
</div>
<table>
<tr class="trow"><td>短信口径(实时)</td><td class="mono">净 +3,715</td></tr>
<tr class="trow"><td>卡卡猫中信0405</td><td class="mono" style="color:#FF3B30;">净 -179,853.23</td></tr>
<tr class="trow"><td>芸归喜网商9532</td><td class="mono">收入 5.94</td></tr>
<tr class="trow"><td>支付宝(企业)</td><td class="mono">净 +240.71</td></tr>
<tr class="trow"><td>飞书工资(参考)</td><td class="mono">65,500</td></tr>
<tr class="trow"><td>云消费(腾讯云)</td><td class="mono">待补</td></tr>
</table>
<div class="small" style="margin-top: 10px;">
大白话:<b>这月现金压力主要来自卡卡猫</b>;支付宝这条线是“小打小闹”;短信口径是“雷达提示”。
</div>
</div>
</div>
<div class="glass img-card wide" style="padding: 16px;">
<svg viewBox="0 0 520 360" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="520" height="360" rx="18" fill="rgba(255,255,255,0.42)"/>
<g font-family="Inter, -apple-system, sans-serif">
<text x="28" y="42" font-size="14" font-weight="900" fill="rgba(0,0,0,0.70)">现金流“重心”</text>
<rect x="28" y="70" width="464" height="112" rx="22" fill="rgba(255,59,48,0.10)" stroke="rgba(255,59,48,0.16)"/>
<text x="48" y="112" font-size="18" font-weight="950" fill="rgba(0,0,0,0.76)">重心卡卡猫中信0405</text>
<text x="48" y="142" font-size="13" font-weight="900" fill="rgba(0,0,0,0.52)">本月净流出 17.99 万(需要预算上限 + 回款节奏)</text>
<rect x="28" y="198" width="464" height="132" rx="22" fill="rgba(10,132,255,0.10)" stroke="rgba(10,132,255,0.16)"/>
<text x="48" y="240" font-size="16" font-weight="950" fill="rgba(0,0,0,0.76)">小项:支付宝 / 芸归喜 / 短信</text>
<text x="48" y="268" font-size="13" font-weight="900" fill="rgba(0,0,0,0.52)">这些更适合做“监控与预警”,不是现金流主引擎</text>
</g>
</svg>
</div>
</div>
</div>
<!-- Slide 5: 卡卡猫中信0405 -->
<div class="slide" id="slide-5" style="justify-content:center;">
<h2 style="margin-bottom: 18px;">CITIC 0405</h2>
<div class="row" style="align-items:center;">
<div class="col" style="flex: 1;">
<div class="glass-strong" style="padding: 28px 32px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: 14px;">
<span class="icon">🏦</span>
<span class="tag">卡卡猫中信0405</span>
</div>
<div class="list">
<p>收入:<span class="mono">0</span>(本月无转入)</p>
<p>支出:<span class="mono">179,853.23</span></p>
<p>净现金流:<span class="mono" style="color:#FF3B30;">-179,853.23</span></p>
</div>
</div>
<div class="note" style="margin-top: 14px;">
CFO 大白话:<b>没有收入的月份</b>,支出再“合理”也会把现金吃掉。这个月就是典型“现金压力月”。
</div>
</div>
<div class="glass img-card wide" style="padding: 16px;">
<svg viewBox="0 0 520 360" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="520" height="360" rx="18" fill="rgba(255,255,255,0.42)"/>
<g font-family="Inter, -apple-system, sans-serif">
<text x="28" y="42" font-size="14" font-weight="900" fill="rgba(0,0,0,0.70)">支出构成(节选)</text>
<rect x="28" y="70" width="464" height="56" rx="16" fill="rgba(10,132,255,0.10)" stroke="rgba(10,132,255,0.16)"/>
<text x="48" y="104" font-size="14" font-weight="950" fill="rgba(0,0,0,0.74)">厦门存客宝60,000</text>
<rect x="28" y="136" width="464" height="56" rx="16" fill="rgba(52,199,89,0.10)" stroke="rgba(52,199,89,0.16)"/>
<text x="48" y="170" font-size="14" font-weight="950" fill="rgba(0,0,0,0.74)">苏培玉26,660.74</text>
<rect x="28" y="202" width="464" height="56" rx="16" fill="rgba(255,149,0,0.10)" stroke="rgba(255,149,0,0.16)"/>
<text x="48" y="236" font-size="14" font-weight="950" fill="rgba(0,0,0,0.74)">好帖云16,100</text>
<rect x="28" y="268" width="464" height="68" rx="16" fill="rgba(255,59,48,0.08)" stroke="rgba(255,59,48,0.14)"/>
<text x="48" y="302" font-size="13" font-weight="900" fill="rgba(0,0,0,0.72)">缴税TIPS+ 银行费 + 多笔打款</text>
<text x="48" y="324" font-size="12" font-weight="800" fill="rgba(0,0,0,0.52)">典型:税费、工资/打款、服务费、供应商付款</text>
</g>
</svg>
</div>
</div>
</div>
<!-- Slide 6: 支付宝企业 -->
<div class="slide" id="slide-6" style="justify-content:center;">
<h2 style="margin-bottom: 18px;">Alipay (Enterprise)</h2>
<div class="row" style="align-items:center;">
<div class="col" style="flex: 1;">
<div class="glass-strong" style="padding: 28px 32px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: 14px;">
<span class="icon">📲</span>
<span class="tag">支付宝企业号</span>
</div>
<div class="list">
<p>收入:<span class="mono">1,301.90</span>5 笔)</p>
<p>支出:<span class="mono">1,061.19</span>10 笔)</p>
<p>净额:<span class="mono">+240.71</span></p>
</div>
</div>
<div class="note ok" style="margin-top: 14px;">
✅ 大白话:支付宝这条线本月是“<b>小幅净流入</b>”,它更像收款/分发通道。
</div>
</div>
<div class="glass img-card" style="padding: 16px;">
<svg viewBox="0 0 420 420" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="420" height="420" rx="18" fill="rgba(255,255,255,0.40)"/>
<g font-family="Inter, -apple-system, sans-serif">
<text x="36" y="66" font-size="16" font-weight="950" fill="rgba(0,0,0,0.76)">一月特点</text>
<rect x="36" y="92" width="348" height="92" rx="18" fill="rgba(10,132,255,0.10)" stroke="rgba(10,132,255,0.16)"/>
<text x="60" y="126" font-size="14" font-weight="900" fill="rgba(0,0,0,0.70)">收入暗黑4 / Cursor / 充值</text>
<text x="60" y="152" font-size="12" font-weight="800" fill="rgba(0,0,0,0.52)">小额多笔,偏零售型</text>
<rect x="36" y="206" width="348" height="92" rx="18" fill="rgba(255,149,0,0.10)" stroke="rgba(255,149,0,0.16)"/>
<text x="60" y="240" font-size="14" font-weight="900" fill="rgba(0,0,0,0.70)">支出:服务费/转账/平台消费</text>
<text x="60" y="266" font-size="12" font-weight="800" fill="rgba(0,0,0,0.52)">更适合做“费用科目化”</text>
<text x="210" y="356" text-anchor="middle" font-size="12" font-weight="800" fill="rgba(0,0,0,0.48)">建议:把“平台消费”单独列一科目</text>
</g>
</svg>
</div>
</div>
</div>
<!-- Slide 7: 芸归喜 & 飞书工资 -->
<div class="slide" id="slide-7" style="justify-content:center;">
<h2 style="margin-bottom: 18px;">Small Lines</h2>
<div class="row" style="align-items:center;">
<div class="col" style="flex: 1;">
<div class="glass-strong" style="padding: 28px 32px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: 14px;">
<span class="icon">🌊</span>
<span class="tag">芸归喜 & 飞书</span>
</div>
<div class="list">
<p>芸归喜网商9532<span class="mono">收入 5.94</span>(体量很小)。</p>
<p>飞书工资(参考):<span class="mono">65,500</span>(若属公司刚性成本,需纳入预算)。</p>
</div>
</div>
<div class="note" style="margin-top: 14px;">
CFO 大白话:小账户别花太多精力,<b>重点是把刚性成本“预算化”</b>,避免现金月爆雷。
</div>
</div>
<div class="glass img-card wide" style="padding: 16px;">
<svg viewBox="0 0 520 360" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="520" height="360" rx="18" fill="rgba(255,255,255,0.42)"/>
<g font-family="Inter, -apple-system, sans-serif">
<text x="28" y="42" font-size="14" font-weight="900" fill="rgba(0,0,0,0.70)">刚性成本提醒</text>
<rect x="28" y="70" width="464" height="120" rx="22" fill="rgba(255,149,0,0.10)" stroke="rgba(255,149,0,0.16)"/>
<text x="48" y="108" font-size="16" font-weight="950" fill="rgba(0,0,0,0.76)">工资/税/云/房租/外包</text>
<text x="48" y="136" font-size="13" font-weight="900" fill="rgba(0,0,0,0.52)">这些最好在月初就“先占用预算”</text>
<text x="48" y="162" font-size="13" font-weight="900" fill="rgba(0,0,0,0.52)">剩下的钱才是你能自由支配的</text>
<rect x="28" y="206" width="464" height="124" rx="22" fill="rgba(52,199,89,0.10)" stroke="rgba(52,199,89,0.16)"/>
<text x="48" y="244" font-size="16" font-weight="950" fill="rgba(0,0,0,0.76)">建议:设“现金红线”</text>
<text x="48" y="272" font-size="13" font-weight="900" fill="rgba(0,0,0,0.52)">比如:现金余额 < 2个月固定成本 自动降本/停非必要支出</text>
</g>
</svg>
</div>
</div>
</div>
<!-- Slide 8: 短信口径怎么用 -->
<div class="slide" id="slide-8" style="justify-content:center;">
<h2 style="margin-bottom: 18px;">SMS Radar</h2>
<div class="row" style="align-items:center;">
<div class="col" style="flex: 1;">
<div class="glass-strong" style="padding: 28px 32px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: 14px;">
<span class="icon">📩</span>
<span class="tag">短信口径(实时)</span>
</div>
<div class="list">
<p>本月短信口径:<span>收入 4,885 / 支出 1,170 / 净 3,715</span></p>
<p>大白话:短信更像<span>“预警雷达”</span>,帮你发现“突然发生了什么”。</p>
<p>但它不适合当最终账:<span>可能混入提醒/营销/额度</span></p>
</div>
</div>
<div class="note" style="margin-top: 14px;">
✅ 用法建议:短信用来“抓异常”;最终对账以重点机构交易明细 & 银行对账单为准。
</div>
</div>
<div class="glass img-card" style="padding: 16px;">
<svg viewBox="0 0 420 420" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="420" height="420" rx="18" fill="rgba(255,255,255,0.40)"/>
<g font-family="Inter, -apple-system, sans-serif">
<text x="36" y="66" font-size="16" font-weight="950" fill="rgba(0,0,0,0.76)">短信的正确姿势</text>
<rect x="36" y="92" width="348" height="86" rx="18" fill="rgba(10,132,255,0.10)" stroke="rgba(10,132,255,0.16)"/>
<text x="60" y="126" font-size="14" font-weight="900" fill="rgba(0,0,0,0.70)">✅ 看“有没有发生”</text>
<text x="60" y="152" font-size="12" font-weight="800" fill="rgba(0,0,0,0.52)">比如:转入/转出/缴税/大额打款</text>
<rect x="36" y="192" width="348" height="86" rx="18" fill="rgba(255,59,48,0.08)" stroke="rgba(255,59,48,0.14)"/>
<text x="60" y="226" font-size="14" font-weight="900" fill="rgba(0,0,0,0.70)">❌ 别当“最终账”</text>
<text x="60" y="252" font-size="12" font-weight="800" fill="rgba(0,0,0,0.52)">提醒/营销/额度会把数字带偏</text>
<text x="210" y="356" text-anchor="middle" font-size="12" font-weight="800" fill="rgba(0,0,0,0.48)">一句话:短信抓异常,明细做结账</text>
</g>
</svg>
</div>
</div>
</div>
<!-- Slide 9: 风险与建议 -->
<div class="slide" id="slide-9" style="justify-content:center;">
<h2 style="margin-bottom: 18px;">Risks & Actions</h2>
<div class="row" style="align-items:center;">
<div class="col" style="flex: 1;">
<div class="glass-strong" style="padding: 28px 32px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: 14px;">
<span class="icon">🚨</span>
<span class="tag">本月风险点</span>
</div>
<div class="list">
<p>风险 1<span>中信0405 无收入但支出 17.99 万</span> → 现金压力月。</p>
<p>风险 2<span>云消费待补</span> → 月末可能再来一刀。</p>
<p>风险 3<span>工资/税/固定服务费</span> → 刚性成本必须预算化。</p>
</div>
</div>
<div class="note ok" style="margin-top: 14px;">
✅ CFO 行动:下月把“预算上限 + 回款节奏 + 现金红线”三件事落到制度里。
</div>
</div>
<div class="glass img-card wide" style="padding: 16px;">
<svg viewBox="0 0 520 360" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="520" height="360" rx="18" fill="rgba(255,255,255,0.42)"/>
<g font-family="Inter, -apple-system, sans-serif">
<text x="28" y="42" font-size="14" font-weight="900" fill="rgba(0,0,0,0.70)">下月三条硬动作</text>
<rect x="28" y="70" width="464" height="76" rx="18" fill="rgba(10,132,255,0.10)" stroke="rgba(10,132,255,0.16)"/>
<text x="48" y="104" font-size="15" font-weight="950" fill="rgba(0,0,0,0.76)">1) 预算上限</text>
<text x="48" y="130" font-size="12" font-weight="800" fill="rgba(0,0,0,0.52)">工资/税/云/外包/营销:每科目一个上限</text>
<rect x="28" y="160" width="464" height="76" rx="18" fill="rgba(52,199,89,0.10)" stroke="rgba(52,199,89,0.16)"/>
<text x="48" y="194" font-size="15" font-weight="950" fill="rgba(0,0,0,0.76)">2) 回款节奏</text>
<text x="48" y="220" font-size="12" font-weight="800" fill="rgba(0,0,0,0.52)">每周盯“应收/已收/逾期”,减少空窗期</text>
<rect x="28" y="250" width="464" height="84" rx="18" fill="rgba(255,149,0,0.10)" stroke="rgba(255,149,0,0.16)"/>
<text x="48" y="284" font-size="15" font-weight="950" fill="rgba(0,0,0,0.76)">3) 现金红线</text>
<text x="48" y="310" font-size="12" font-weight="800" fill="rgba(0,0,0,0.52)">现金 < 2个月固定成本 自动降本/暂停非必要支出</text>
</g>
</svg>
</div>
</div>
</div>
<!-- Slide 10: 结尾与下次迭代 -->
<div class="slide" id="slide-10" style="justify-content:center; align-items:center;">
<div class="glass-strong" style="padding: 56px 72px; text-align:center; width: 980px;">
<span class="icon"></span>
<h1 class="text-dark" style="margin: 18px 0 10px;">本月最后一句大白话</h1>
<p class="text-muted" style="font-size: 22px; font-weight: 850; line-height: 1.6;">
1月最关键不是“算得多细”<br>
是先把卡卡猫中信0405的现金流稳住<br>
<b>预算有上限、回款有节奏、现金有红线。</b>
</p>
<div style="display:flex; justify-content:center; gap:12px; margin-top: 26px; flex-wrap: wrap;">
<span class="pill">🧾 云消费补齐</span>
<span class="pill">📊 费用科目化</span>
<span class="pill">🧠 每周滚动预测</span>
<span class="pill">🚨 异常预警</span>
</div>
<div class="small" style="margin-top: 18px;">下一步迭代:把“云消费(腾讯云)”与“飞书报销/付款”补齐,形成完整月结。</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,423 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1280, height=720">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Inter', -apple-system, sans-serif; -webkit-font-smoothing: antialiased; }
.slide {
width: 1280px; height: 720px;
display: flex; flex-direction: column;
padding: 64px 80px;
background: linear-gradient(160deg, #F5F5FA 0%, #EBEBF0 35%, #F0F0F5 100%);
position: relative; overflow: hidden;
}
.slide::before {
content: ''; position: absolute; top: -40%; right: -25%;
width: 70%; height: 90%;
background: radial-gradient(ellipse, rgba(0,122,255,0.08) 0%, transparent 60%);
pointer-events: none;
}
.glass {
background: rgba(255,255,255,0.6);
backdrop-filter: blur(48px); -webkit-backdrop-filter: blur(48px);
border: 1px solid rgba(255,255,255,0.7);
border-radius: 28px;
box-shadow: 0 8px 32px rgba(0,0,0,0.04), 0 0 0 1px rgba(255,255,255,0.8) inset;
}
.glass-strong {
background: rgba(255,255,255,0.72);
backdrop-filter: blur(56px); -webkit-backdrop-filter: blur(56px);
border: 1px solid rgba(255,255,255,0.75);
border-radius: 32px;
box-shadow: 0 12px 40px rgba(0,0,0,0.06), 0 0 0 1px rgba(255,255,255,0.9) inset;
}
.text-dark { color: #1D1D1F; }
.text-muted { color: #8E8E93; }
.accent { color: #007AFF; }
h1 { font-size: 52px; font-weight: 700; letter-spacing: -0.03em; }
h2 { font-size: 18px; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; color: #007AFF; }
.kicker { font-size: 14px; letter-spacing: 0.18em; text-transform: uppercase; color: rgba(0,0,0,0.45); }
.row { display: flex; gap: 36px; }
.col { display: flex; flex-direction: column; gap: 18px; }
.pill {
display: inline-flex; align-items: center; gap: 10px;
padding: 10px 14px; border-radius: 999px;
background: rgba(0,122,255,0.10);
color: #0B5ED7; font-weight: 600; font-size: 14px;
border: 1px solid rgba(0,122,255,0.12);
}
.list p { font-size: 20px; line-height: 1.8; color: #1D1D1F; }
.list p span { color: #8E8E93; }
.icon { font-size: 42px; line-height: 1; }
.img-card { width: 420px; height: 420px; border-radius: 22px; overflow: hidden; }
.img-card.wide { width: 520px; height: 360px; }
.tag {
display: inline-flex; align-items: center; gap: 8px;
font-size: 14px; font-weight: 600;
color: rgba(0,0,0,0.55);
padding: 8px 12px; border-radius: 12px;
background: rgba(255,255,255,0.55);
border: 1px solid rgba(255,255,255,0.7);
}
.small { font-size: 16px; line-height: 1.65; color: rgba(0,0,0,0.65); }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.metric { padding: 18px 20px; border-radius: 18px; background: rgba(255,255,255,0.62); border: 1px solid rgba(255,255,255,0.75); }
.metric .v { font-size: 26px; font-weight: 800; color: #1D1D1F; letter-spacing: -0.02em; }
.metric .k { font-size: 14px; font-weight: 700; color: rgba(0,0,0,0.50); letter-spacing: 0.12em; text-transform: uppercase; margin-top: 6px; }
</style>
</head>
<body>
<!-- Slide 1: 封面 -->
<div class="slide" id="slide-1" style="justify-content:center; align-items:center;">
<div class="glass-strong" style="padding: 56px 72px; text-align:center; width: 920px;">
<div class="kicker" style="margin-bottom: 10px;">Conversation → SOP → Skill</div>
<h1 class="text-dark" style="margin-bottom: 18px;">家里 NAS 对话描述</h1>
<p class="text-muted" style="font-size: 22px; font-weight: 600;">DiskStation · Time Machine · 外网挂载 · 自动沉淀</p>
<div style="display:flex; justify-content:center; gap:12px; margin-top: 26px;">
<span class="pill">💧 诊断</span>
<span class="pill">🪙 运维</span>
<span class="pill">🌱 产出</span>
<span class="pill">🔥 复盘</span>
</div>
</div>
</div>
<!-- Slide 2: 对话起点(问题) -->
<div class="slide" id="slide-2" style="justify-content:center;">
<h2 style="margin-bottom: 28px;">What Happened</h2>
<div class="row" style="align-items:center;">
<div class="col" style="flex: 1;">
<div class="glass-strong" style="padding: 34px 40px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: 14px;">
<span class="icon">⏱️</span>
<span class="tag">对话触发</span>
</div>
<div class="list">
<p>① 时间机器:<span>红点 + 等待首次备份</span></p>
<p>② 弹窗:<span>未识别备份磁盘DiskStation.local</span></p>
<p>③ 诉求:<span>外网挂载 1TB 共享到 Finder「位置」</span></p>
</div>
</div>
<div class="glass" style="padding: 22px 28px;">
<div class="small">关键目标:能自动就自动;做不了就落材料;最后沉淀到 Skill可复用。</div>
</div>
</div>
<div class="glass img-card" style="padding: 16px;">
<!-- 配图:抽象 NAS + 云 -->
<svg viewBox="0 0 420 420" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="g1" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#0A84FF" stop-opacity="0.18"/>
<stop offset="100%" stop-color="#34C759" stop-opacity="0.14"/>
</linearGradient>
<filter id="s1" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="14" stdDeviation="12" flood-color="#000" flood-opacity="0.10"/>
</filter>
</defs>
<rect x="0" y="0" width="420" height="420" rx="18" fill="url(#g1)"/>
<g filter="url(#s1)">
<rect x="72" y="130" width="276" height="170" rx="18" fill="rgba(255,255,255,0.72)" stroke="rgba(255,255,255,0.75)"/>
<rect x="98" y="160" width="224" height="20" rx="10" fill="rgba(0,0,0,0.08)"/>
<rect x="98" y="194" width="224" height="20" rx="10" fill="rgba(0,0,0,0.08)"/>
<rect x="98" y="228" width="224" height="20" rx="10" fill="rgba(0,0,0,0.08)"/>
<circle cx="108" cy="276" r="6" fill="#34C759"/>
<circle cx="130" cy="276" r="6" fill="#FF9F0A"/>
<circle cx="152" cy="276" r="6" fill="#FF453A"/>
</g>
<g>
<path d="M274 92c18 0 33 14 33 31 0 2 0 4-1 6h4c18 0 33 14 33 31s-15 31-33 31H149c-20 0-36-15-36-34s16-33 36-33c4-18 21-32 40-32 13 0 25 6 33 15 6-9 15-15 27-15z"
fill="rgba(255,255,255,0.75)" stroke="rgba(255,255,255,0.72)"/>
<text x="210" y="170" text-anchor="middle" font-size="16" font-weight="700" fill="rgba(0,0,0,0.55)">DiskStation ↔ 外网</text>
</g>
</svg>
</div>
</div>
</div>
<!-- Slide 3: 资产与路径(两台 NAS -->
<div class="slide" id="slide-3" style="justify-content:center;">
<h2 style="margin-bottom: 28px;">Assets Map</h2>
<div class="row" style="align-items:center;">
<div class="glass-strong" style="flex:1; padding: 34px 40px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: 16px;">
<span class="icon">🗺️</span>
<span class="tag">公司 / 家里 两套 NAS</span>
</div>
<div class="grid2">
<div class="metric">
<div class="v">CKB NAS</div>
<div class="k">192.168.1.201</div>
<div class="small" style="margin-top:10px;">外网open.quwanzhi.com</div>
<div class="small">Gitea:3000已穿透</div>
</div>
<div class="metric">
<div class="v">家里 DiskStation</div>
<div class="k">192.168.110.29</div>
<div class="small" style="margin-top:10px;">外网opennas2.quwanzhi.com</div>
<div class="small">Time Machine共享</div>
</div>
</div>
<div class="glass" style="padding: 18px 22px; margin-top: 16px;">
<div class="small">对话中所有“定位/修复/挂载”都先区分110 网段=家里1 网段=公司。</div>
</div>
</div>
<div class="glass img-card wide" style="padding: 16px;">
<!-- 配图双节点结构图SVG -->
<svg viewBox="0 0 520 360" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="g2" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#FF9F0A" stop-opacity="0.12"/>
<stop offset="100%" stop-color="#0A84FF" stop-opacity="0.14"/>
</linearGradient>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto">
<path d="M0,0 L8,3 L0,6 Z" fill="rgba(0,0,0,0.35)"/>
</marker>
</defs>
<rect x="0" y="0" width="520" height="360" rx="18" fill="url(#g2)"/>
<rect x="48" y="92" width="190" height="176" rx="18" fill="rgba(255,255,255,0.74)"/>
<rect x="282" y="92" width="190" height="176" rx="18" fill="rgba(255,255,255,0.74)"/>
<text x="143" y="132" text-anchor="middle" font-size="16" font-weight="800" fill="rgba(0,0,0,0.62)">CKB NAS</text>
<text x="377" y="132" text-anchor="middle" font-size="16" font-weight="800" fill="rgba(0,0,0,0.62)">家里 NAS</text>
<text x="143" y="162" text-anchor="middle" font-size="13" font-weight="700" fill="rgba(0,0,0,0.48)">open.quwanzhi.com</text>
<text x="377" y="162" text-anchor="middle" font-size="13" font-weight="700" fill="rgba(0,0,0,0.48)">opennas2.quwanzhi.com</text>
<g stroke="rgba(0,0,0,0.35)" stroke-width="2" marker-end="url(#arrow)">
<line x1="238" y1="180" x2="282" y2="180"/>
<line x1="282" y1="180" x2="238" y2="180"/>
</g>
<text x="260" y="206" text-anchor="middle" font-size="12" fill="rgba(0,0,0,0.45)">对外入口 / 穿透</text>
<g>
<rect x="78" y="196" width="130" height="48" rx="14" fill="rgba(0,122,255,0.10)" stroke="rgba(0,122,255,0.14)"/>
<text x="143" y="226" text-anchor="middle" font-size="13" font-weight="800" fill="rgba(0,0,0,0.55)">Gitea / 同步</text>
</g>
<g>
<rect x="312" y="196" width="130" height="48" rx="14" fill="rgba(52,199,89,0.10)" stroke="rgba(52,199,89,0.14)"/>
<text x="377" y="226" text-anchor="middle" font-size="13" font-weight="800" fill="rgba(0,0,0,0.55)">Time Machine</text>
</g>
</svg>
</div>
</div>
</div>
<!-- Slide 4: 流程图(核心) -->
<div class="slide" id="slide-4" style="justify-content:center;">
<h2 style="margin-bottom: 22px;">Flowchart</h2>
<div class="row" style="align-items:center;">
<div class="col" style="flex:1;">
<div class="glass-strong" style="padding: 28px 34px;">
<div style="display:flex; align-items:center; justify-content:space-between;">
<span class="icon">🔁</span>
<span class="tag">从对话 → SOP → Skill</span>
</div>
<div class="small" style="margin-top: 12px;">
这页是“对话行动链”:诊断 → 修复 → 验证 → 沉淀。后续同类问题直接复用。
</div>
</div>
<div class="glass" style="padding: 18px 22px;">
<div class="small">产出物检测脚本、排查文档、Skill 小节、外网挂载脚本、复盘规则。</div>
</div>
</div>
<div class="glass img-card wide" style="padding: 16px;">
<!-- 配图:流程图 SVG -->
<svg viewBox="0 0 520 360" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="g3" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#0A84FF" stop-opacity="0.10"/>
<stop offset="100%" stop-color="#BF5AF2" stop-opacity="0.10"/>
</linearGradient>
<marker id="arrow2" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto">
<path d="M0,0 L10,6 L0,12 Z" fill="rgba(0,0,0,0.35)"/>
</marker>
</defs>
<rect x="0" y="0" width="520" height="360" rx="18" fill="url(#g3)"/>
<!-- nodes -->
<g font-family="Inter, -apple-system, sans-serif" font-weight="800">
<rect x="48" y="52" width="190" height="56" rx="16" fill="rgba(255,255,255,0.76)"/>
<text x="143" y="86" text-anchor="middle" font-size="14" fill="rgba(0,0,0,0.60)">识别问题(对话触发)</text>
<rect x="282" y="52" width="190" height="56" rx="16" fill="rgba(255,255,255,0.76)"/>
<text x="377" y="86" text-anchor="middle" font-size="14" fill="rgba(0,0,0,0.60)">自动检测(脚本)</text>
<rect x="48" y="136" width="190" height="56" rx="16" fill="rgba(255,255,255,0.76)"/>
<text x="143" y="170" text-anchor="middle" font-size="14" fill="rgba(0,0,0,0.60)">Time Machine 修复</text>
<rect x="282" y="136" width="190" height="56" rx="16" fill="rgba(255,255,255,0.76)"/>
<text x="377" y="170" text-anchor="middle" font-size="14" fill="rgba(0,0,0,0.60)">外网挂载SMB+frp</text>
<rect x="48" y="220" width="190" height="56" rx="16" fill="rgba(255,255,255,0.76)"/>
<text x="143" y="254" text-anchor="middle" font-size="14" fill="rgba(0,0,0,0.60)">验证(再跑脚本)</text>
<rect x="282" y="220" width="190" height="56" rx="16" fill="rgba(255,255,255,0.76)"/>
<text x="377" y="254" text-anchor="middle" font-size="14" fill="rgba(0,0,0,0.60)">沉淀:文档+Skill</text>
</g>
<!-- arrows -->
<g stroke="rgba(0,0,0,0.35)" stroke-width="2.2" marker-end="url(#arrow2)" fill="none">
<path d="M238 80 L282 80"/>
<path d="M377 108 L377 136"/>
<path d="M282 164 L238 164"/>
<path d="M143 192 L143 220"/>
<path d="M238 248 L282 248"/>
</g>
<g font-family="Inter, -apple-system, sans-serif" font-size="12" fill="rgba(0,0,0,0.48)" font-weight="700">
<text x="260" y="70" text-anchor="middle">查IP/端口/状态</text>
<text x="377" y="126" text-anchor="middle">错误→按材料</text>
<text x="260" y="156" text-anchor="middle">移除→重加</text>
<text x="143" y="216" text-anchor="middle">确认红点消失</text>
<text x="260" y="240" text-anchor="middle">写入 SOP/Skill</text>
</g>
</svg>
</div>
</div>
</div>
<!-- Slide 5: 自动化产出清单 -->
<div class="slide" id="slide-5" style="justify-content:center;">
<h2 style="margin-bottom: 26px;">Deliverables</h2>
<div class="row" style="align-items:center;">
<div class="glass-strong" style="flex:1; padding: 32px 38px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: 12px;">
<span class="icon">🧰</span>
<span class="tag">可复用资产</span>
</div>
<div class="list">
<p>① 检测脚本:<span>time_machine_diskstation_auto.sh</span></p>
<p>② 排查文档:<span>Time_Machine_DiskStation_错误排查.md</span></p>
<p>③ Skill<span>群晖NAS管理含 Time Machine 小节)</span></p>
<p>④ 外网挂载:<span>mount_diskstation_1tb.sh + 参考资料</span></p>
<p>⑤ 复盘规则:<span>目标每句≤30字+必须含%</span></p>
</div>
</div>
<div class="glass img-card" style="padding: 16px;">
<!-- 配图:工具箱 -->
<svg viewBox="0 0 420 420" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="g4" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#34C759" stop-opacity="0.12"/>
<stop offset="100%" stop-color="#0A84FF" stop-opacity="0.12"/>
</linearGradient>
</defs>
<rect x="0" y="0" width="420" height="420" rx="18" fill="url(#g4)"/>
<g>
<rect x="76" y="160" width="268" height="168" rx="22" fill="rgba(255,255,255,0.78)" stroke="rgba(255,255,255,0.75)"/>
<rect x="130" y="126" width="160" height="60" rx="18" fill="rgba(255,255,255,0.78)" stroke="rgba(255,255,255,0.75)"/>
<rect x="152" y="144" width="116" height="24" rx="12" fill="rgba(0,0,0,0.07)"/>
<rect x="110" y="204" width="200" height="18" rx="9" fill="rgba(0,0,0,0.07)"/>
<rect x="110" y="234" width="200" height="18" rx="9" fill="rgba(0,0,0,0.07)"/>
<rect x="110" y="264" width="200" height="18" rx="9" fill="rgba(0,0,0,0.07)"/>
<text x="210" y="310" text-anchor="middle" font-size="14" font-weight="800" fill="rgba(0,0,0,0.55)">Scripts · Docs · Skills</text>
</g>
</svg>
</div>
</div>
</div>
<!-- Slide 6: 家里 NAS 关键参数 -->
<div class="slide" id="slide-6" style="justify-content:center;">
<h2 style="margin-bottom: 26px;">Home NAS Snapshot</h2>
<div class="row" style="align-items:center;">
<div class="glass-strong" style="flex:1; padding: 32px 38px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: 14px;">
<span class="icon">🪙</span>
<span class="tag">资产摘要</span>
</div>
<div class="grid2">
<div class="metric"><div class="v">DS213j</div><div class="k">MODEL</div></div>
<div class="metric"><div class="v">armv7l</div><div class="k">ARCH</div></div>
<div class="metric"><div class="v">497MB</div><div class="k">RAM</div></div>
<div class="metric"><div class="v">5.4TB</div><div class="k">DISK</div></div>
</div>
<div class="glass" style="padding: 18px 22px; margin-top: 16px;">
<div class="small">开放端口22 / 80 / 443 / 139 / 445 / 5000 / 5001对外域名opennas2.quwanzhi.com。</div>
</div>
</div>
<div class="glass img-card" style="padding: 16px;">
<!-- 配图:指标卡 -->
<svg viewBox="0 0 420 420" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="g5" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#BF5AF2" stop-opacity="0.10"/>
<stop offset="100%" stop-color="#FF9F0A" stop-opacity="0.10"/>
</linearGradient>
</defs>
<rect x="0" y="0" width="420" height="420" rx="18" fill="url(#g5)"/>
<g>
<rect x="62" y="86" width="296" height="248" rx="22" fill="rgba(255,255,255,0.78)" stroke="rgba(255,255,255,0.75)"/>
<text x="210" y="130" text-anchor="middle" font-size="16" font-weight="900" fill="rgba(0,0,0,0.60)">Home NAS</text>
<rect x="96" y="154" width="228" height="14" rx="7" fill="rgba(0,0,0,0.07)"/>
<rect x="96" y="182" width="228" height="14" rx="7" fill="rgba(0,0,0,0.07)"/>
<rect x="96" y="210" width="228" height="14" rx="7" fill="rgba(0,0,0,0.07)"/>
<rect x="96" y="238" width="228" height="14" rx="7" fill="rgba(0,0,0,0.07)"/>
<rect x="96" y="266" width="228" height="14" rx="7" fill="rgba(0,0,0,0.07)"/>
<text x="210" y="314" text-anchor="middle" font-size="13" font-weight="800" fill="rgba(0,0,0,0.52)">Time Machine / SMB / FRP</text>
</g>
</svg>
</div>
</div>
</div>
<!-- Slide 7: 下一步行动(用户可执行) -->
<div class="slide" id="slide-7" style="justify-content:center;">
<h2 style="margin-bottom: 26px;">Next Actions</h2>
<div class="row" style="align-items:center;">
<div class="glass-strong" style="flex:1; padding: 32px 38px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: 12px;">
<span class="icon">▶️</span>
<span class="tag">按顺序执行</span>
</div>
<div class="list">
<p>① Time Machine<span>移除“共享”→重新添加</span></p>
<p>② 外网挂载:<span>frpc 加 SMB 4452 → Finder ⌘K</span></p>
<p>③ 自动化:<span>遇到同类问题直接跑检测脚本</span></p>
<p>④ 复盘:<span>对话结尾统一按复盘块输出</span></p>
</div>
<div class="glass" style="padding: 18px 22px; margin-top: 16px;">
<div class="small">提示Finder 复制文件会显示速率;侧栏固定:挂载后拖到「位置」。</div>
</div>
</div>
<div class="glass img-card" style="padding: 16px;">
<!-- 配图:箭头与侧栏 -->
<svg viewBox="0 0 420 420" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="g6" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#0A84FF" stop-opacity="0.10"/>
<stop offset="100%" stop-color="#34C759" stop-opacity="0.10"/>
</linearGradient>
<marker id="arrow3" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto">
<path d="M0,0 L10,6 L0,12 Z" fill="rgba(0,0,0,0.35)"/>
</marker>
</defs>
<rect x="0" y="0" width="420" height="420" rx="18" fill="url(#g6)"/>
<g>
<rect x="64" y="86" width="292" height="248" rx="22" fill="rgba(255,255,255,0.78)" stroke="rgba(255,255,255,0.75)"/>
<rect x="86" y="110" width="86" height="200" rx="16" fill="rgba(0,0,0,0.05)"/>
<rect x="182" y="110" width="152" height="38" rx="14" fill="rgba(0,122,255,0.12)"/>
<text x="258" y="135" text-anchor="middle" font-size="12" font-weight="900" fill="rgba(0,0,0,0.55)">Finder 位置</text>
<rect x="182" y="162" width="152" height="38" rx="14" fill="rgba(52,199,89,0.12)"/>
<text x="258" y="187" text-anchor="middle" font-size="12" font-weight="900" fill="rgba(0,0,0,0.55)">DiskStation-1TB</text>
<path d="M120 306 C180 320, 240 320, 300 306" stroke="rgba(0,0,0,0.35)" stroke-width="2.4" fill="none" marker-end="url(#arrow3)"/>
</g>
</svg>
</div>
</div>
</div>
<!-- Slide 8: 结尾(对话→复用) -->
<div class="slide" id="slide-8" style="justify-content:center; align-items:center;">
<div class="glass-strong" style="padding: 46px 60px; width: 980px;">
<div style="display:flex; align-items:center; justify-content:space-between;">
<span class="icon">🏁</span>
<span class="tag">结尾</span>
</div>
<h1 class="text-dark" style="font-size: 44px; margin: 16px 0 10px;">把一次对话变成可复用系统</h1>
<p class="text-muted" style="font-size: 20px; font-weight: 600;">诊断 → 修复 → 验证 → 文档/Skill 沉淀</p>
<div class="glass" style="margin-top: 18px; padding: 18px 22px;">
<div class="small">PPT 用途:对外汇报、团队交接、下次直接照流程做,不再重复沟通成本。</div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -63,19 +63,34 @@ def build_ppt(imgs, out_ppt):
def main():
import argparse
ap = argparse.ArgumentParser()
ap.add_argument("--html", default="复盘", choices=["复盘", "卡若人设", "纳瓦尔访谈", "天恩乖乖", "今日日志总结"])
ap.add_argument("--html", default="复盘", choices=["复盘", "卡若人设", "纳瓦尔访谈", "天恩乖乖", "今日日志总结", "家里NAS对话描述", "公司财务分析", "公司财务月报_2026-01"])
args = ap.parse_args()
if args.html == "卡若人设":
html = BASE / "卡若人设PPT_毛玻璃.html"
out_slides = OUT_ROOT / "卡若人设_毛玻璃_slides"
out_ppt = OUT_ROOT / "卡若人设介绍_毛玻璃.pptx"
max_slides = 5
elif args.html == "家里NAS对话描述":
html = BASE / "家里NAS对话描述PPT_毛玻璃.html"
out_slides = OUT_ROOT / "家里NAS对话描述_毛玻璃_slides"
out_ppt = OUT_ROOT / "家里NAS_对话描述_毛玻璃.pptx"
max_slides = 8
elif args.html == "纳瓦尔访谈":
html = BASE / "纳瓦尔访谈PPT_毛玻璃.html"
# v2扩展页数含方法/问答/流程图/行动清单),避免覆盖旧版
out_slides = OUT_ROOT / "纳瓦尔访谈_毛玻璃_slides_v2"
out_ppt = OUT_ROOT / "纳瓦尔访谈_读书笔记_毛玻璃_v2.pptx"
max_slides = 15
elif args.html == "公司财务分析":
html = BASE / "公司财务分析PPT_毛玻璃.html"
out_slides = OUT_ROOT / "公司财务分析_毛玻璃_slides"
out_ppt = OUT_ROOT / "公司财务_多维分析_CFO大白话_毛玻璃.pptx"
max_slides = 12
elif args.html == "公司财务月报_2026-01":
html = BASE / "公司财务月报_2026年1月PPT_毛玻璃.html"
out_slides = OUT_ROOT / "公司财务月报_2026-01_毛玻璃_slides"
out_ppt = OUT_ROOT / "公司财务月报_2026年1月_CFO大白话_毛玻璃.pptx"
max_slides = 10
elif args.html == "天恩乖乖":
html = BASE / "天恩乖乖PPT_毛玻璃.html"
out_slides = TIANEN_DIR / "乖乖_毛玻璃_slides"

View File

@@ -15,6 +15,62 @@ uvicorn main:app --host 0.0.0.0 --port 8000
- `OPENAI_API_KEY`OpenAI 或兼容 API 的密钥,配置后使用真实 LLM 生成回复。
- `OPENAI_API_BASE`:兼容接口地址,默认 `https://api.openai.com/v1`
- `OPENAI_MODEL`:模型名,默认 `gpt-4o-mini`
- `KARUO_GATEWAY_CONFIG`:网关配置路径(默认 `config/gateway.yaml`)。
- `KARUO_GATEWAY_SALT`:部门 Key 的 salt用于 sha256 校验;不写入仓库)。
## 部门/科室鉴权与白名单(推荐启用)
网关支持“每部门一个 Key + 技能白名单”,用于:
- 科室/部门直接调用接口,不互相影响
- 外网暴露时避免“全能力裸奔”
- 能按部门做限流/审计日志
### 1) 准备配置文件
从示例复制一份(`gateway.yaml` 建议不要提交到仓库):
- `config/gateway.example.yaml``config/gateway.yaml`
### 2) 准备 salt只在环境变量
在运行环境里设置:
```bash
export KARUO_GATEWAY_SALT="一个足够长的随机字符串"
```
### 3) 生成部门 Key 与 hash
```bash
python tools/generate_dept_key.py --tenant-id finance --tenant-name "财务科"
```
把输出里的 `api_key_sha256` 写入 `config/gateway.yaml` 对应 tenant明文 `dept_key` 只出现一次,保存到部门系统的安全配置里。
### 4) 调用方式
#### 4.1 /v1/chat
```bash
curl -s -X POST "http://127.0.0.1:8000/v1/chat" \
-H "Content-Type: application/json" \
-H "X-Karuo-Api-Key: <dept_key>" \
-d '{"prompt":"你的问题"}'
```
#### 4.2 /v1/skills部门自查
```bash
curl -s "http://127.0.0.1:8000/v1/skills" \
-H "X-Karuo-Api-Key: <dept_key>"
```
#### 4.3 /v1/health
```bash
curl -s "http://127.0.0.1:8000/v1/health"
```
## 外网暴露
@@ -28,6 +84,7 @@ uvicorn main:app --host 0.0.0.0 --port 8000
```bash
curl -s -X POST "https://YOUR_DOMAIN/v1/chat" \
-H "Content-Type: application/json" \
-H "X-Karuo-Api-Key: <dept_key>" \
-d '{"prompt":"你的问题"}' | jq -r '.reply'
```

View File

@@ -0,0 +1,40 @@
version: 1
auth:
# 外部调用请求头名:每个部门一个 Key
header_name: X-Karuo-Api-Key
# 只存 hash不存明文 keysalt 从环境变量读取
salt_env: KARUO_GATEWAY_SALT
tenants:
- id: finance
name: 财务科
# sha256(dept_key + salt) 的 hex用 tools/generate_dept_key.py 生成
api_key_sha256: "REPLACE_ME"
# 允许调用的技能支持填技能ID如 E05a或 SKILL 路径(如 05_卡土/.../SKILL.md
allowed_skills:
- E05a
- M07
limits:
rpm: 60
max_prompt_chars: 12000
skills:
registry_path: SKILL_REGISTRY.md
match_strategy: trigger_contains
# 未匹配到技能时的策略deny | allow_general
on_no_match: deny
llm:
provider: openai_compatible
api_key_env: OPENAI_API_KEY
api_base_env: OPENAI_API_BASE
model_env: OPENAI_MODEL
timeout_seconds: 60
max_tokens: 2000
logging:
enabled: true
# 既可填绝对路径,也可填相对仓库根目录的路径
path: 运营中枢/工作台/karuo_ai_gateway_access.jsonl
log_request_body: false

View File

@@ -5,8 +5,14 @@
from pathlib import Path
import os
import re
from typing import Tuple
from fastapi import FastAPI
import time
import json
import hashlib
import hmac
from typing import Any, Dict, List, Optional, Tuple
import yaml
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import HTMLResponse
from pydantic import BaseModel
@@ -15,6 +21,103 @@ REPO_ROOT = Path(__file__).resolve().parents[3]
app = FastAPI(title="卡若AI 网关", version="1.0")
DEFAULT_CONFIG_PATH = Path(__file__).resolve().parent / "config" / "gateway.yaml"
def _is_abs_path(p: str) -> bool:
try:
return Path(p).is_absolute()
except Exception:
return False
def _resolve_path(p: str) -> Path:
if _is_abs_path(p):
return Path(p)
return REPO_ROOT / p
def _read_yaml(path: Path) -> Dict[str, Any]:
return yaml.safe_load(path.read_text(encoding="utf-8")) or {}
def load_config() -> Dict[str, Any]:
"""
读取网关配置(多租户/鉴权/白名单等)。
- 默认路径config/gateway.yaml不入库你可以从 gateway.example.yaml 复制一份)
- 也支持通过环境变量 KARUO_GATEWAY_CONFIG 指定绝对/相对路径
"""
p = os.environ.get("KARUO_GATEWAY_CONFIG", "").strip()
cfg_path = _resolve_path(p) if p else DEFAULT_CONFIG_PATH
if not cfg_path.exists():
return {}
try:
return _read_yaml(cfg_path)
except Exception:
# 配置读失败时不要“悄悄放行”,避免外网误用
raise
def _sha256_hex(s: str) -> str:
return hashlib.sha256(s.encode("utf-8")).hexdigest()
def _get_salt(cfg: Dict[str, Any]) -> str:
auth = (cfg or {}).get("auth") or {}
salt_env = auth.get("salt_env", "KARUO_GATEWAY_SALT")
return os.environ.get(salt_env, "")
def _auth_header_name(cfg: Dict[str, Any]) -> str:
auth = (cfg or {}).get("auth") or {}
return auth.get("header_name", "X-Karuo-Api-Key")
def _tenant_by_key(cfg: Dict[str, Any], api_key_plain: str) -> Optional[Dict[str, Any]]:
tenants = (cfg or {}).get("tenants") or []
if not api_key_plain:
return None
salt = _get_salt(cfg)
if not salt:
return None
key_hash = _sha256_hex(api_key_plain + salt)
for t in tenants:
if not isinstance(t, dict):
continue
stored = str(t.get("api_key_sha256", "")).strip()
if stored and hmac.compare_digest(stored, key_hash):
return t
return None
def _rpm_allow(tenant_id: str, rpm: int) -> bool:
"""
极简内存限流(单进程);够用就行。
- 生产建议用 Nginx/网关层限流
"""
if rpm <= 0:
return True
now = time.time()
window = int(now // 60)
key = f"{tenant_id}:{window}"
bucket = app.state.__dict__.setdefault("_rpm_bucket", {})
cnt = bucket.get(key, 0) + 1
bucket[key] = cnt
return cnt <= rpm
def _log_access(cfg: Dict[str, Any], record: Dict[str, Any]) -> None:
logging_cfg = (cfg or {}).get("logging") or {}
if not logging_cfg.get("enabled", False):
return
path_raw = str(logging_cfg.get("path", "")).strip()
if not path_raw:
return
p = _resolve_path(path_raw)
p.parent.mkdir(parents=True, exist_ok=True)
with p.open("a", encoding="utf-8") as f:
f.write(json.dumps(record, ensure_ascii=False) + "\n")
def load_bootstrap() -> str:
p = REPO_ROOT / "BOOTSTRAP.md"
@@ -24,14 +127,31 @@ def load_bootstrap() -> str:
def load_registry() -> str:
p = REPO_ROOT / "SKILL_REGISTRY.md"
cfg = load_config()
skills_cfg = (cfg or {}).get("skills") or {}
reg_path = skills_cfg.get("registry_path", "SKILL_REGISTRY.md")
p = _resolve_path(reg_path)
if p.exists():
return p.read_text(encoding="utf-8")
return "技能注册表未找到。"
def match_skill(prompt: str) -> Tuple[str, str]:
"""根据 prompt 在 SKILL_REGISTRY 中匹配技能,返回 (技能名, 路径)。"""
def _normalize_trigger_token(t: str) -> str:
t = t.strip()
t = t.replace("**", "").replace("*", "")
t = t.replace("`", "")
return t.strip()
def _split_triggers(triggers: str) -> List[str]:
s = triggers or ""
for ch in ["", "", ",", "", ";", "|", "/"]:
s = s.replace(ch, " ")
return [tok for tok in (_normalize_trigger_token(x) for x in s.split()) if tok]
def match_skill(prompt: str, cfg: Optional[Dict[str, Any]] = None) -> Tuple[str, str, str]:
"""根据 prompt 在 SKILL_REGISTRY 中匹配技能,返回 (技能ID, 技能名, 路径)。"""
text = load_registry()
lines = text.split("\n")
for line in lines:
@@ -39,13 +159,21 @@ def match_skill(prompt: str) -> Tuple[str, str]:
continue
parts = [p.strip() for p in line.split("|")]
# 表列:| # | 技能 | 成员 | 触发词 | SKILL 路径 | 一句话 |
if len(parts) < 6:
if len(parts) < 7:
continue
skill_name, triggers, path = parts[2], parts[4], parts[5].strip("`")
for t in triggers.replace("", " ").split():
skill_id = parts[1]
skill_name = parts[2]
triggers = parts[4]
path = parts[5].strip("`")
for t in _split_triggers(triggers):
if t and t in prompt:
return skill_name, path
return "通用", "总索引.md"
return skill_id, skill_name, path
skills_cfg = (cfg or load_config() or {}).get("skills") or {}
on_no_match = skills_cfg.get("on_no_match", "allow_general")
if on_no_match == "deny":
return "", "", ""
return "GENERAL", "通用", "总索引.md"
class ChatRequest(BaseModel):
@@ -54,11 +182,18 @@ class ChatRequest(BaseModel):
class ChatResponse(BaseModel):
reply: str
tenant_id: str = ""
tenant_name: str = ""
skill_id: str = ""
matched_skill: str
skill_path: str
def build_reply_with_llm(prompt: str, matched_skill: str, skill_path: str) -> str:
def _llm_settings(cfg: Dict[str, Any]) -> Dict[str, Any]:
return (cfg or {}).get("llm") or {}
def build_reply_with_llm(prompt: str, cfg: Dict[str, Any], matched_skill: str, skill_path: str) -> str:
"""调用 LLM 生成回复OpenAI 兼容)。未配置则返回模板回复。"""
bootstrap = load_bootstrap()
system = (
@@ -66,8 +201,9 @@ def build_reply_with_llm(prompt: str, matched_skill: str, skill_path: str) -> st
f"当前匹配技能:{matched_skill},路径:{skill_path}"
"先简短思考并输出,再给执行要点,最后必须带「[卡若复盘]」块(含目标·结果·达成率、过程 1 2 3、反思、总结、下一步"
)
api_key = os.environ.get("OPENAI_API_KEY")
base_url = os.environ.get("OPENAI_API_BASE", "https://api.openai.com/v1")
llm_cfg = _llm_settings(cfg)
api_key = os.environ.get(llm_cfg.get("api_key_env", "OPENAI_API_KEY"))
base_url = os.environ.get(llm_cfg.get("api_base_env", "OPENAI_API_BASE"), "https://api.openai.com/v1")
if api_key:
try:
import httpx
@@ -75,11 +211,11 @@ def build_reply_with_llm(prompt: str, matched_skill: str, skill_path: str) -> st
f"{base_url.rstrip('/')}/chat/completions",
headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
json={
"model": os.environ.get("OPENAI_MODEL", "gpt-4o-mini"),
"model": os.environ.get(llm_cfg.get("model_env", "OPENAI_MODEL"), "gpt-4o-mini"),
"messages": [{"role": "system", "content": system}, {"role": "user", "content": prompt}],
"max_tokens": 2000,
"max_tokens": int(llm_cfg.get("max_tokens", 2000)),
},
timeout=60.0,
timeout=float(llm_cfg.get("timeout_seconds", 60)),
)
if r.status_code == 200:
data = r.json()
@@ -131,11 +267,101 @@ def index():
@app.post("/v1/chat", response_model=ChatResponse)
def chat(req: ChatRequest):
async def chat(req: ChatRequest, request: Request):
"""外部调用入口:传入 prompt返回按卡若AI 流程生成的 reply。"""
matched_skill, skill_path = match_skill(req.prompt)
reply = build_reply_with_llm(req.prompt, matched_skill, skill_path)
return ChatResponse(reply=reply, matched_skill=matched_skill, skill_path=skill_path)
cfg = load_config()
# 1) 鉴权(如果有配置文件就强制开启)
tenant: Optional[Dict[str, Any]] = None
if cfg:
header_name = _auth_header_name(cfg)
api_key = request.headers.get(header_name, "")
tenant = _tenant_by_key(cfg, api_key)
if not tenant:
raise HTTPException(status_code=401, detail="invalid api key")
tenant_id = str((tenant or {}).get("id", "")).strip()
tenant_name = str((tenant or {}).get("name", "")).strip()
# 2) 限流/输入限制(可选)
limits = (tenant or {}).get("limits") or {}
max_prompt_chars = int(limits.get("max_prompt_chars", 0) or 0)
if max_prompt_chars and len(req.prompt) > max_prompt_chars:
raise HTTPException(status_code=413, detail="prompt too large")
rpm = int(limits.get("rpm", 0) or 0)
if tenant_id and rpm and not _rpm_allow(tenant_id, rpm):
raise HTTPException(status_code=429, detail="rate limit exceeded")
# 3) 技能匹配 + 白名单校验
skill_id, matched_skill, skill_path = match_skill(req.prompt, cfg=cfg)
if cfg and not (skill_id and matched_skill and skill_path):
raise HTTPException(status_code=404, detail="no skill matched")
if tenant:
allowed = (tenant.get("allowed_skills") or []) if isinstance(tenant, dict) else []
allowed = [str(x).strip() for x in allowed if str(x).strip()]
if allowed:
# 同时支持“技能ID白名单”和“SKILL路径白名单”
if (skill_id not in allowed) and (skill_path not in allowed):
raise HTTPException(status_code=403, detail="skill not allowed for tenant")
reply = build_reply_with_llm(req.prompt, cfg, matched_skill, skill_path)
# 4) 访问日志(默认不落 prompt 内容)
logging_cfg = (cfg or {}).get("logging") or {}
record: Dict[str, Any] = {
"ts": int(time.time()),
"tenant_id": tenant_id,
"tenant_name": tenant_name,
"skill_id": skill_id,
"matched_skill": matched_skill,
"skill_path": skill_path,
"client": request.client.host if request.client else "",
"ua": request.headers.get("user-agent", ""),
}
if bool(logging_cfg.get("log_request_body", False)):
record["prompt"] = req.prompt
_log_access(cfg, record)
return ChatResponse(
reply=reply,
tenant_id=tenant_id,
tenant_name=tenant_name,
skill_id=skill_id,
matched_skill=matched_skill,
skill_path=skill_path,
)
@app.get("/v1/health")
def health():
return {"ok": True}
@app.get("/v1/skills")
def allowed_skills(request: Request):
"""
返回该 tenant 允许的技能清单(需要 key
用途:部门侧自查权限/联调。
"""
cfg = load_config()
if not cfg:
return {"tenants_enabled": False, "allowed_skills": []}
header_name = _auth_header_name(cfg)
api_key = request.headers.get(header_name, "")
tenant = _tenant_by_key(cfg, api_key)
if not tenant:
raise HTTPException(status_code=401, detail="invalid api key")
allowed = tenant.get("allowed_skills") or []
allowed = [str(x).strip() for x in allowed if str(x).strip()]
return {
"tenants_enabled": True,
"tenant_id": str(tenant.get("id", "")).strip(),
"tenant_name": str(tenant.get("name", "")).strip(),
"allowed_skills": allowed,
"header_name": header_name,
}
if __name__ == "__main__":

View File

@@ -2,3 +2,4 @@ fastapi>=0.100.0
uvicorn>=0.22.0
httpx>=0.24.0
pydantic>=2.0
PyYAML>=6.0

View File

@@ -0,0 +1,64 @@
#!/usr/bin/env python3
"""
生成“部门/科室”的 API Key明文只输出一次并给出写入 gateway.yaml 的 sha256。
用法:
export KARUO_GATEWAY_SALT="your-long-random-salt"
python tools/generate_dept_key.py --tenant-id finance --tenant-name "财务科"
"""
from __future__ import annotations
import argparse
import hashlib
import os
import secrets
import sys
def sha256_hex(s: str) -> str:
return hashlib.sha256(s.encode("utf-8")).hexdigest()
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--tenant-id", required=True, help="tenant id, e.g. finance")
parser.add_argument("--tenant-name", default="", help='tenant name, e.g. "财务科"')
parser.add_argument(
"--key",
default="",
help="可选:自定义明文 key不建议。为空则自动生成。",
)
parser.add_argument(
"--salt-env",
default="KARUO_GATEWAY_SALT",
help="salt 的环境变量名,默认 KARUO_GATEWAY_SALT",
)
args = parser.parse_args()
salt = os.environ.get(args.salt_env, "")
if not salt:
print(f"[ERROR] 未读取到 salt 环境变量:{args.salt_env}", file=sys.stderr)
print("建议export KARUO_GATEWAY_SALT=\"一个足够长的随机字符串\"", file=sys.stderr)
return 2
dept_key = args.key or secrets.token_urlsafe(32)
key_hash = sha256_hex(dept_key + salt)
# 明文 key 只输出一次;请在生成后立即给到部门系统的安全配置里。
print("tenant:")
print(f" id: {args.tenant_id}")
if args.tenant_name:
print(f" name: {args.tenant_name}")
print("")
print("dept_key (plaintext, show once):")
print(dept_key)
print("")
print("api_key_sha256 (write into gateway.yaml):")
print(key_hash)
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -82,6 +82,42 @@ uvicorn main:app --host 0.0.0.0 --port 8000
---
## 四点五、接口配置化(科室/部门可复制)
> 目标:让以后任何科室/部门/合作方都能“拿到一套配置 + 一个 key”直接调用卡若AI 网关,不需要改代码。
### 你需要提前准备什么(一次性)
1. **一个 salt**(只放环境变量,不写入仓库):`KARUO_GATEWAY_SALT`
2. (可选)如果要真实 LLM 输出:`OPENAI_API_KEY`(以及 `OPENAI_API_BASE``OPENAI_MODEL`
3. 外网场景:域名/反代已就绪(宝塔/Nginx或 ngrok 临时暴露
### 配置文件在哪里
- 示例:`运营中枢/scripts/karuo_ai_gateway/config/gateway.example.yaml`
- 实际:`运营中枢/scripts/karuo_ai_gateway/config/gateway.yaml`(建议不提交到仓库)
- 也可用环境变量指定:`KARUO_GATEWAY_CONFIG=/path/to/gateway.yaml`
### 新增一个科室/部门(标准步骤)
1. 设置 salt运行环境
- `export KARUO_GATEWAY_SALT="一个足够长的随机字符串"`
2. 生成部门 key明文只输出一次与 hash
- `python 运营中枢/scripts/karuo_ai_gateway/tools/generate_dept_key.py --tenant-id finance --tenant-name "财务科"`
3. 将输出的 `api_key_sha256` 写入 `config/gateway.yaml` 的对应 tenant
4. 配置该 tenant 的 `allowed_skills`技能白名单支持技能ID如 `E05a`,或 SKILL 路径)
5. 重启网关服务
### 调用方式(必须带部门 key
- `POST /v1/chat`
- Header`X-Karuo-Api-Key: <dept_key>`
- Body`{"prompt":"你的问题"}`
- `GET /v1/skills`:部门自查当前允许技能(同样需要 key
- `GET /v1/health`:健康检查(无需 key
---
## 五、最终:执行命令与链接(给 Cursor / 其他 AI 用)
**固定域名**`https://kr-ai.quwanzhi.com`部署与配置见「内网穿透与域名配置_卡若AI标准方案.md」

View File

@@ -125,3 +125,4 @@
| 2026-02-24 05:49:20 | 🔄 卡若AI 同步 2026-02-24 05:49 | 更新:卡木、总索引与入口、运营中枢工作台 | 排除 >20MB: 10 个 |
| 2026-02-24 11:42:10 | 🔄 卡若AI 同步 2026-02-24 11:42 | 更新:金仓、水桥平台对接、运营中枢工作台 | 排除 >20MB: 10 个 |
| 2026-02-24 16:28:06 | 🔄 卡若AI 同步 2026-02-24 16:28 | 更新:水桥平台对接、卡木、卡土、运营中枢工作台 | 排除 >20MB: 10 个 |
| 2026-02-24 16:49:15 | 🔄 卡若AI 同步 2026-02-24 16:49 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 10 个 |

View File

@@ -128,3 +128,4 @@
| 2026-02-24 05:49:20 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-24 05:49 | 更新:卡木、总索引与入口、运营中枢工作台 | 排除 >20MB: 10 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-02-24 11:42:10 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-24 11:42 | 更新:金仓、水桥平台对接、运营中枢工作台 | 排除 >20MB: 10 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-02-24 16:28:06 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-24 16:28 | 更新:水桥平台对接、卡木、卡土、运营中枢工作台 | 排除 >20MB: 10 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-02-24 16:49:15 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-24 16:49 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 10 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |