@clawhub-cnspica-e89c13b169
银发头条(Silver Age Daily)—— 一键生成当日银发经济精选资讯简报。 覆盖:政策解读、银发消费、老年金融、银发科技、医疗健康、银发文娱、居家养老、银发就业八大板块。 每次生成一份带新闻链接、带摘要的 HTML 格式日报,风格简洁大方,适合老年产业从业者、研究者、投资人及关注银发经济的人士阅读。 U...
---
name: silver-daily
description: >
银发头条(Silver Age Daily)—— 一键生成当日银发经济精选资讯简报。
覆盖:政策解读、银发消费、老年金融、银发科技、医疗健康、银发文娱、居家养老、银发就业八大板块。
每次生成一份带新闻链接、带摘要的 HTML 格式日报,风格简洁大方,适合老年产业从业者、研究者、投资人及关注银发经济的人士阅读。
Use when: 用户要生成今日银发经济资讯、查看老龄化相关新闻、生成银发经济日报、每日早报推送。
NOT for: 查询个人养老金账户、医疗问诊、具体股票投资建议。
---
## When to Run
- 用户主动说"生成银发头条""今日银发日报""银发经济资讯"等
- 每日定时(建议早上 7:30)通过 cron/heartbeat 触发
- 用户询问"老龄化最新新闻""银发经济今天有什么新消息"
- 用户说"帮我出一期银发每日头条"
---
## Workflow
### 第一步:获取当日新闻
针对以下 **8 个板块**,分别执行 web_search 获取最新资讯(每板块搜索 1 次,取前 3-5 条结果):
| # | 板块 | 搜索关键词示例 |
|---|------|----------------|
| 1 | 🏛️ 政策解读 | `银发经济 政策 养老 [今年] 最新` |
| 2 | 🛍️ 银发消费 | `老年消费 银发市场 老年用品 新闻` |
| 3 | 💰 老年金融 | `养老金融 个人养老金 养老理财 保险` |
| 4 | 🤖 银发科技 | `智慧养老 AI养老 养老机器人 老年科技` |
| 5 | 🏥 医疗健康 | `老年医疗 医养结合 老年健康 慢病管理` |
| 6 | 🎭 银发文娱 | `银发旅游 旅居养老 老年教育 老年文化` |
| 7 | 🏠 居家养老 | `居家养老 适老化改造 社区养老 上门服务` |
| 8 | 💼 银发就业 | `老有所为 延迟退休 老年就业 银龄行动` |
### 第二步:筛选与整理
- 每板块挑选 **2-4 条**质量最高、最新的新闻
- 提取:标题、来源、发布时间、新闻链接、100字以内摘要
- 优先选择:政府官网、央媒、知名财经媒体的内容
- 标注 1 条当日"头条"(最重要的政策或行业大事)
### 第三步:生成 HTML 报告
调用 `scripts/generate_report.py`,传入整理好的新闻数据,生成当日 HTML 报告:
```
python scripts/generate_report.py --output output/silver-daily-$(date +%Y%m%d).html
```
若无法运行脚本,则直接按 `references/html_template.md` 中的模板格式,在对话中输出完整 HTML。
### 第四步:输出结果
- 在对话中展示生成的 HTML 文件路径
- 输出格式参考 `references/output_format.md`
- 默认保存到 `output/silver-daily-YYYYMMDD.html`
---
## Output Format
```
📅 {日期} · 第 {期数} 期
━━━━━━━━━━━━━━━━━━━━━━━━
🔥 今日头条:{标题}
📎 {链接}
📊 数据亮点:
· 老年人口:X 亿 · 市场规模:X 万亿
─── 共 8 个板块,{总新闻数} 条资讯 ───
已生成:output/silver-daily-{YYYYMMDD}.html
```
FILE:clawhub.json
{
"name": "silver-daily",
"version": "1.0.1",
"description": "银发头条 — 一键生成当日银发经济精选资讯日报",
"author": "cnspica",
"category": "news",
"tags": ["银发经济", "老龄化", "养老", "资讯", "日报"],
"homepage": "https://github.com/cnspica/silver-daily",
"license": "MIT"
}
FILE:preview.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>银发每日头条 · 2026年3月23日</title>
<style>
:root {
--gold: #c8922a;
--gold-light: #f0d080;
--silver: #8a9bb0;
--ink: #1a1a2e;
--bg: #faf8f5;
--card-bg: #ffffff;
--border: #e8e0d0;
--muted: #7a7a8a;
--tag-bg: #f5f0e8;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'PingFang SC', 'Microsoft YaHei', 'Noto Serif SC', Georgia, serif;
background: var(--bg);
color: var(--ink);
line-height: 1.7;
}
/* ───── HEADER ───── */
.header {
background: linear-gradient(135deg, #1a1a2e 0%, #2d2d4e 60%, #3a2a1a 100%);
padding: 28px 24px 22px;
text-align: center;
position: relative;
overflow: hidden;
}
.header::before {
content: '';
position: absolute;
top: -40px; left: -40px;
width: 200px; height: 200px;
background: radial-gradient(circle, rgba(200,146,42,0.15) 0%, transparent 70%);
border-radius: 50%;
}
.header::after {
content: '';
position: absolute;
bottom: -30px; right: -30px;
width: 160px; height: 160px;
background: radial-gradient(circle, rgba(200,146,42,0.1) 0%, transparent 70%);
border-radius: 50%;
}
.header-logo {
font-size: 13px;
color: var(--gold-light);
letter-spacing: 4px;
opacity: 0.8;
margin-bottom: 6px;
}
.header-title {
font-size: 30px;
font-weight: 700;
color: #fff;
letter-spacing: 3px;
position: relative;
}
.header-title span {
color: var(--gold);
}
.header-date {
margin-top: 8px;
font-size: 13px;
color: rgba(255,255,255,0.55);
letter-spacing: 1px;
}
.header-line {
width: 60px;
height: 2px;
background: linear-gradient(90deg, transparent, var(--gold), transparent);
margin: 12px auto 0;
}
/* ───── DIGEST BAR ───── */
.digest-bar {
background: linear-gradient(90deg, #2d2419, #3d3020);
padding: 10px 20px;
display: flex;
align-items: center;
gap: 10px;
overflow: hidden;
}
.digest-label {
font-size: 11px;
color: var(--gold);
letter-spacing: 2px;
white-space: nowrap;
border: 1px solid var(--gold);
padding: 2px 7px;
border-radius: 2px;
}
.digest-scroll {
font-size: 13px;
color: rgba(255,255,255,0.7);
overflow: hidden;
white-space: nowrap;
}
/* ───── LAYOUT ───── */
.container {
max-width: 860px;
margin: 0 auto;
padding: 20px 16px 40px;
}
/* ───── SECTION HEADER ───── */
.section-head {
display: flex;
align-items: center;
gap: 10px;
margin: 28px 0 14px;
}
.section-icon {
font-size: 22px;
line-height: 1;
}
.section-name {
font-size: 17px;
font-weight: 700;
color: var(--ink);
letter-spacing: 1px;
}
.section-line {
flex: 1;
height: 1px;
background: linear-gradient(90deg, var(--border), transparent);
}
.section-count {
font-size: 11px;
color: var(--muted);
background: var(--tag-bg);
padding: 2px 8px;
border-radius: 10px;
}
/* ───── NEWS CARD ───── */
.news-list { display: flex; flex-direction: column; gap: 10px; }
.news-card {
background: var(--card-bg);
border: 1px solid var(--border);
border-radius: 8px;
padding: 14px 16px;
display: flex;
gap: 12px;
align-items: flex-start;
transition: box-shadow 0.2s, transform 0.2s;
text-decoration: none;
color: inherit;
}
.news-card:hover {
box-shadow: 0 4px 16px rgba(0,0,0,0.08);
transform: translateY(-1px);
}
.news-card.featured {
border-left: 3px solid var(--gold);
background: linear-gradient(to right, #fffdf7, #fff);
}
.news-rank {
font-size: 18px;
font-weight: 800;
color: var(--gold-light);
min-width: 26px;
text-align: center;
line-height: 1.4;
font-variant-numeric: tabular-nums;
}
.news-rank.top { color: var(--gold); }
.news-body { flex: 1; min-width: 0; }
.news-title {
font-size: 15px;
font-weight: 600;
color: var(--ink);
line-height: 1.5;
margin-bottom: 5px;
}
.news-card:hover .news-title { color: #8a5a10; }
.news-meta {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
}
.news-source {
font-size: 12px;
color: var(--muted);
}
.news-source::before { content: '📰 '; }
.news-time {
font-size: 12px;
color: #b0a890;
}
.news-tag {
font-size: 11px;
background: var(--tag-bg);
color: var(--gold);
padding: 1px 7px;
border-radius: 10px;
border: 1px solid rgba(200,146,42,0.2);
}
.news-summary {
font-size: 13px;
color: #555;
margin-top: 5px;
line-height: 1.6;
}
/* ───── FEATURE CARD (TOP STORY) ───── */
.top-story {
background: linear-gradient(135deg, #1a1a2e 0%, #2d2640 100%);
border-radius: 10px;
padding: 20px;
margin: 18px 0;
position: relative;
overflow: hidden;
}
.top-story::before {
content: '头条';
position: absolute;
top: -2px; right: 14px;
font-size: 11px;
background: var(--gold);
color: #fff;
padding: 3px 10px 4px;
border-radius: 0 0 6px 6px;
letter-spacing: 2px;
}
.top-story-title {
font-size: 18px;
font-weight: 700;
color: #fff;
line-height: 1.5;
margin-bottom: 10px;
}
.top-story-summary {
font-size: 13px;
color: rgba(255,255,255,0.65);
line-height: 1.7;
}
.top-story-link {
display: inline-flex;
align-items: center;
gap: 5px;
margin-top: 14px;
font-size: 13px;
color: var(--gold);
text-decoration: none;
border: 1px solid rgba(200,146,42,0.4);
padding: 5px 14px;
border-radius: 20px;
transition: background 0.2s;
}
.top-story-link:hover { background: rgba(200,146,42,0.15); }
/* ───── STATS ROW ───── */
.stats-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
margin: 16px 0 28px;
}
.stat-card {
background: var(--card-bg);
border: 1px solid var(--border);
border-radius: 8px;
padding: 14px 12px;
text-align: center;
}
.stat-num {
font-size: 22px;
font-weight: 800;
color: var(--gold);
line-height: 1.2;
}
.stat-num sup { font-size: 13px; font-weight: 400; }
.stat-label {
font-size: 11px;
color: var(--muted);
margin-top: 3px;
}
/* ───── FOOTER ───── */
.footer {
text-align: center;
padding: 24px 16px;
background: linear-gradient(180deg, var(--bg), #f0ece4);
border-top: 1px solid var(--border);
font-size: 12px;
color: var(--muted);
}
.footer-brand {
font-size: 15px;
font-weight: 700;
color: var(--gold);
letter-spacing: 2px;
margin-bottom: 6px;
}
/* ───── RESPONSIVE ───── */
@media (max-width: 600px) {
.stats-row { grid-template-columns: repeat(2, 1fr); }
.header-title { font-size: 22px; }
}
</style>
</head>
<body>
<!-- HEADER -->
<div class="header">
<div class="header-logo">SILVER AGE DAILY · 银发每日头条</div>
<div class="header-title">银发<span>每日</span>头条</div>
<div class="header-date">2026年3月23日 · 星期一 · 第 487 期</div>
<div class="header-line"></div>
</div>
<!-- DIGEST BAR -->
<div class="digest-bar">
<div class="digest-label">今日速览</div>
<div class="digest-scroll">
国务院部署银发消费补贴扩围 ·
养老机器人春晚后订单翻倍 ·
旅居养老今年迎两会政策红利 ·
长护险新文书2026版正式实施 ·
成都老博会800家企业亮相
</div>
</div>
<div class="container">
<!-- STATS ROW -->
<div class="stats-row">
<div class="stat-card">
<div class="stat-num">3.2<sup>亿</sup></div>
<div class="stat-label">60岁以上老年人口</div>
</div>
<div class="stat-card">
<div class="stat-num">30<sup>万亿</sup></div>
<div class="stat-label">2026年银发经济规模</div>
</div>
<div class="stat-card">
<div class="stat-num">20<sup>%</sup></div>
<div class="stat-label">老龄人口占总人口比</div>
</div>
<div class="stat-card">
<div class="stat-num">600<sup>+</sup></div>
<div class="stat-label">今年银发相关研究论文</div>
</div>
</div>
<!-- TOP STORY -->
<div class="top-story">
<div class="top-story-title">国务院常务会议:进一步释放银发消费需求,打造消费新场景新业态</div>
<div class="top-story-summary">2026年2月24日,国务院常务会议明确要求发挥消费补贴政策牵引作用,将银发经济纳入扩内需核心战略。会议提出要推动老年用品、旅居养老、养老金融三大方向协同发力,预计相关补贴政策细则将于二季度密集落地。</div>
<a href="https://www.gov.cn/zhengce/202602/content_7059238.htm" class="top-story-link" target="_blank">查看全文 →</a>
</div>
<!-- ① 政策解读 -->
<div class="section-head">
<div class="section-icon">🏛️</div>
<div class="section-name">政策解读</div>
<div class="section-line"></div>
<div class="section-count">4 条</div>
</div>
<div class="news-list">
<a class="news-card featured" href="https://zhuanlan.zhihu.com/p/2013178662007112635" target="_blank">
<div class="news-rank top">1</div>
<div class="news-body">
<div class="news-title">2026政府工作报告深度解读:银发经济七大重点,影响整个行业</div>
<div class="news-meta">
<span class="news-source">知乎·AgeClub</span>
<span class="news-time">3月6日</span>
<span class="news-tag">两会</span>
</div>
<div class="news-summary">报告提出制定推进银发经济高质量发展的措施,完善老年用品、养老金融、旅居养老等支持政策,是"银发经济"首次被明确写入政府工作报告重点任务。</div>
</div>
</a>
<a class="news-card" href="https://www.sohu.com/a/996069103_481016" target="_blank">
<div class="news-rank">2</div>
<div class="news-body">
<div class="news-title">政策协同持续赋能银发经济,推动万亿级市场迈向新里程</div>
<div class="news-meta">
<span class="news-source">搜狐财经</span>
<span class="news-time">3月13日</span>
<span class="news-tag">政策</span>
</div>
<div class="news-summary">2026年消费补贴覆盖老年辅具、适老家居改造、智慧养老设备等,消费补贴"搭桥"有望带动相关市场规模增长超30%。</div>
</div>
</a>
<a class="news-card" href="https://www.nhsa.gov.cn/art/2025/12/1/art_104_18899.html" target="_blank">
<div class="news-rank">3</div>
<div class="news-body">
<div class="news-title">长期护理保险服务管理文书(2026年版)正式印发</div>
<div class="news-meta">
<span class="news-source">国家医保局</span>
<span class="news-time">12月1日</span>
<span class="news-tag">长护险</span>
</div>
<div class="news-summary">国家医保局更新长护险服务管理文书标准,规范失能老人照护服务流程,试点城市扩展至全国49个,预计覆盖人群超1.2亿。</div>
</div>
</a>
<a class="news-card" href="http://www.centek.com.cn/dynamics/532.html" target="_blank">
<div class="news-rank">4</div>
<div class="news-body">
<div class="news-title">2026两会聚焦:银发经济与养老政策一览,科技赋能成核心议题</div>
<div class="news-meta">
<span class="news-source">北京中关村科技</span>
<span class="news-time">3月13日</span>
<span class="news-tag">两会·科技</span>
</div>
</div>
</a>
</div>
<!-- ② 银发消费 -->
<div class="section-head">
<div class="section-icon">🛍️</div>
<div class="section-name">银发消费</div>
<div class="section-line"></div>
<div class="section-count">3 条</div>
</div>
<div class="news-list">
<a class="news-card featured" href="https://ageclub.net/article-detail/8165" target="_blank">
<div class="news-rank top">1</div>
<div class="news-body">
<div class="news-title">银发经济爆发!2026年中国市场趋势全解读:3亿"新老人"重塑消费版图</div>
<div class="news-meta">
<span class="news-source">AgeClub</span>
<span class="news-time">1月28日</span>
<span class="news-tag">市场</span>
</div>
<div class="news-summary">2026年中国60岁以上人口达2.54亿,银发消费呈现"悦己化、品质化、场景化"三大趋势——旅居、康养、智能硬件成三大爆发赛道。</div>
</div>
</a>
<a class="news-card" href="https://baijiahao.baidu.com/s?id=1858601404188216021" target="_blank">
<div class="news-rank">2</div>
<div class="news-body">
<div class="news-title">掘金30万亿风口:银发经济四大黄金赛道与实战路径</div>
<div class="news-meta">
<span class="news-source">百家号</span>
<span class="news-time">近期</span>
<span class="news-tag">赛道</span>
</div>
<div class="news-summary">智慧养老、医疗康养、老年消费、文旅教育四大赛道齐头并进,功能性食品、适老家居改造、老年教育等细分品类增速超行业均值2倍。</div>
</div>
</a>
<a class="news-card" href="https://finance.sina.com.cn/jjxw/2026-03-18/doc-inhrizpe3512648.shtml" target="_blank">
<div class="news-rank">3</div>
<div class="news-body">
<div class="news-title">消费新趋势:老年人"悦己消费"崛起,高端护肤、银发时尚品牌迎来窗口期</div>
<div class="news-meta">
<span class="news-source">新浪财经</span>
<span class="news-time">3月18日</span>
<span class="news-tag">消费升级</span>
</div>
</div>
</a>
</div>
<!-- ③ 老年金融 -->
<div class="section-head">
<div class="section-icon">💰</div>
<div class="section-name">老年金融</div>
<div class="section-line"></div>
<div class="section-count">3 条</div>
</div>
<div class="news-list">
<a class="news-card featured" href="https://finance.sina.com.cn/jjxw/2026-03-18/doc-inhrizpe3512648.shtml" target="_blank">
<div class="news-rank top">1</div>
<div class="news-body">
<div class="news-title">养老金融多维突破:个人养老金账户扩围,商业养老险迎政策窗口</div>
<div class="news-meta">
<span class="news-source">中国消费者报</span>
<span class="news-time">3月18日</span>
<span class="news-tag">养老金</span>
</div>
<div class="news-summary">个人养老金账户已在全国推广,2026年政府工作报告明确支持养老金融高质量发展,商业银行加速推出"养老专属"理财产品,银发金融资管规模预计突破5万亿元。</div>
</div>
</a>
<a class="news-card" href="https://www.gov.cn/zhengce/zhengceku/202503/content_7016311.htm" target="_blank">
<div class="news-rank">2</div>
<div class="news-body">
<div class="news-title">银行业保险业养老金融高质量发展实施方案出台</div>
<div class="news-meta">
<span class="news-source">中国政府网</span>
<span class="news-time">2025年3月</span>
<span class="news-tag">监管</span>
</div>
<div class="news-summary">国家金融监管总局印发实施方案,要求银行保险机构打造养老储蓄、养老理财、养老保险、养老信托"四位一体"产品体系,推动资产配置长期化。</div>
</div>
</a>
<a class="news-card" href="https://www.163.com/dy/article/KOF8V0AM05565ZS2.html" target="_blank">
<div class="news-rank">3</div>
<div class="news-body">
<div class="news-title">30位两会代表委员建言:商业养老险税优扩展、长护险全国推开</div>
<div class="news-meta">
<span class="news-source">网易财经</span>
<span class="news-time">3月20日</span>
<span class="news-tag">两会·保险</span>
</div>
</div>
</a>
</div>
<!-- ④ 银发科技 -->
<div class="section-head">
<div class="section-icon">🤖</div>
<div class="section-name">银发科技</div>
<div class="section-line"></div>
<div class="section-count">3 条</div>
</div>
<div class="news-list">
<a class="news-card featured" href="https://business.cctv.cn/2026/03/05/ARTIkoUbodguruWPA1dGPOi4260305.shtml" target="_blank">
<div class="news-rank top">1</div>
<div class="news-body">
<div class="news-title">养老机器人加速破局:春晚亮相后,情感陪护机器人订单翻倍增长</div>
<div class="news-meta">
<span class="news-source">央视网</span>
<span class="news-time">3月5日</span>
<span class="news-tag">机器人</span>
</div>
<div class="news-summary">宇树科技、银河通用等四家企业的养老陪护机器人春晚后热度暴涨,情感陪伴、生活辅助、健康监测三大功能成养老机构采购首选,头部品牌预期年出货量破万台。</div>
</div>
</a>
<a class="news-card" href="https://finance.people.com.cn/n1/2026/0114/c1004-40644848.html" target="_blank">
<div class="news-rank">2</div>
<div class="news-body">
<div class="news-title">八部门联合发文:培育养老服务新业态,AI+养老迎来政策"双保险"</div>
<div class="news-meta">
<span class="news-source">人民网财经</span>
<span class="news-time">1月14日</span>
<span class="news-tag">AI·政策</span>
</div>
<div class="news-summary">民政部等八部门印发方案,鼓励人工智能、物联网、大数据在养老服务中的应用,智能可穿戴、AI健康管家、远程照护平台获国家专项扶持资金。</div>
</div>
</a>
<a class="news-card" href="https://news.qq.com/rain/a/20260317A07V3O00" target="_blank">
<div class="news-rank">3</div>
<div class="news-body">
<div class="news-title">第九届成都老博会:800家企业展示银发科技,AI健康管家成最大亮点</div>
<div class="news-meta">
<span class="news-source">腾讯新闻</span>
<span class="news-time">3月17日</span>
<span class="news-tag">展会</span>
</div>
</div>
</a>
</div>
<!-- ⑤ 医疗健康 -->
<div class="section-head">
<div class="section-icon">🏥</div>
<div class="section-name">医疗健康</div>
<div class="section-line"></div>
<div class="section-count">3 条</div>
</div>
<div class="news-list">
<a class="news-card featured" href="https://news.qq.com/rain/a/20260312A08HWN00" target="_blank">
<div class="news-rank top">1</div>
<div class="news-body">
<div class="news-title">3.2亿老人老有所依:医养结合加速,社区嵌入式养老服务网络全面铺开</div>
<div class="news-meta">
<span class="news-source">腾讯新闻</span>
<span class="news-time">3月12日</span>
<span class="news-tag">医养结合</span>
</div>
<div class="news-summary">两会数据显示中国60岁以上老年人口已达3.2亿,政策大力推动"居家社区机构"三级照护体系,15分钟养老服务圈成各地核心建设指标,覆盖率目标2026年底达80%。</div>
</div>
</a>
<a class="news-card" href="https://www.cncaprc.gov.cn/xxllsy/769813.jhtml" target="_blank">
<div class="news-rank">2</div>
<div class="news-body">
<div class="news-title">2025老龄工作综述:老年志愿者超5358万人,健康促进行动覆盖全国</div>
<div class="news-meta">
<span class="news-source">全国老龄工作委员会</span>
<span class="news-time">12月24日</span>
<span class="news-tag">健康促进</span>
</div>
<div class="news-summary">全国在册老年志愿者达5358万人,服务时长超16.4亿小时;老年人健康素养提升工程覆盖2600余个社区,慢病管理、口腔健康、心理干预服务体系持续完善。</div>
</div>
</a>
<a class="news-card" href="https://www.zhihu.com/tardis/bd/art/680340837" target="_blank">
<div class="news-rank">3</div>
<div class="news-body">
<div class="news-title">2026养老行业顶级报告汇总:老年照护、康复辅具、认知症干预成三大热点</div>
<div class="news-meta">
<span class="news-source">知乎·养老专栏</span>
<span class="news-time">2月3日</span>
<span class="news-tag">行业报告</span>
</div>
</div>
</a>
</div>
<!-- ⑥ 银发文娱 -->
<div class="section-head">
<div class="section-icon">🎭</div>
<div class="section-name">银发文娱</div>
<div class="section-line"></div>
<div class="section-count">3 条</div>
</div>
<div class="news-list">
<a class="news-card featured" href="https://wlt.hlj.gov.cn/wlt/c115793/202603/c00_31924109.shtml" target="_blank">
<div class="news-rank top">1</div>
<div class="news-body">
<div class="news-title">两会聚焦:银发旅游升温,旅居养老纳入国家政策支持,市场潜力巨大</div>
<div class="news-meta">
<span class="news-source">黑龙江省文旅厅</span>
<span class="news-time">3月16日</span>
<span class="news-tag">旅居养老</span>
</div>
<div class="news-summary">政府工作报告明确支持旅居养老,老年人年均出行次数已超4次,"异地候鸟"式旅居模式在海南、云南、广西形成规模效应,旅游+养老融合市场规模预计年内突破5000亿元。</div>
</div>
</a>
<a class="news-card" href="https://news.cctv.com/2026/01/19/ARTIMvjGDM5M8BDbHdQ77qbF260119.shtml" target="_blank">
<div class="news-rank">2</div>
<div class="news-body">
<div class="news-title">外媒聚焦中国"新潮银发":咖啡拉花课、银发电竞、AI体验馆走进养老院</div>
<div class="news-meta">
<span class="news-source">央视新闻</span>
<span class="news-time">1月19日</span>
<span class="news-tag">文娱新潮</span>
</div>
<div class="news-summary">开设咖啡拉花课、组织"银发电竞"表演赛、打造"AI+养老"体验馆……中国多地面向老年人的娱乐产品与文化服务持续"上新",引发国际媒体广泛关注与报道。</div>
</div>
</a>
<a class="news-card" href="https://baijiahao.baidu.com/s?id=1858601404188216021" target="_blank">
<div class="news-rank">3</div>
<div class="news-body">
<div class="news-title">银发教育崛起:老年大学招生爆满,在线学习平台老年用户规模突破8000万</div>
<div class="news-meta">
<span class="news-source">百家号</span>
<span class="news-time">近期</span>
<span class="news-tag">老年教育</span>
</div>
</div>
</a>
</div>
<!-- ⑦ 居家养老 -->
<div class="section-head">
<div class="section-icon">🏠</div>
<div class="section-name">居家养老</div>
<div class="section-line"></div>
<div class="section-count">2 条</div>
</div>
<div class="news-list">
<a class="news-card featured" href="https://cn.chinadaily.com.cn/a/202512/17/WS6941f0a3a310942cc4996fc9.html" target="_blank">
<div class="news-rank top">1</div>
<div class="news-body">
<div class="news-title">AI+养老解锁新机遇:智能居家照护系统进入千家万户,跌倒检测准确率超95%</div>
<div class="news-meta">
<span class="news-source">中国日报</span>
<span class="news-time">12月17日</span>
<span class="news-tag">智慧居家</span>
</div>
<div class="news-summary">AI健康监测设备、智能呼叫系统、远程探视平台在城市社区加速推广,物联网居家安全感应套件进入家政补贴清单,2026年适老化改造补贴户数较去年增长40%。</div>
</div>
</a>
<a class="news-card" href="http://dpc.qingdao.gov.cn/zfxxgk/fdzdgknr/zdly/shfz/202506/t20250625_9735608.shtml" target="_blank">
<div class="news-rank">2</div>
<div class="news-body">
<div class="news-title">中国银发经济发展报告(2025):居家养老服务覆盖率成核心考核指标</div>
<div class="news-meta">
<span class="news-source">青岛市发展改革委</span>
<span class="news-time">2025年6月</span>
<span class="news-tag">行业报告</span>
</div>
</div>
</a>
</div>
<!-- ⑧ 银发就业 -->
<div class="section-head">
<div class="section-icon">💼</div>
<div class="section-name">银发就业·老有所为</div>
<div class="section-line"></div>
<div class="section-count">2 条</div>
</div>
<div class="news-list">
<a class="news-card featured" href="https://paper.people.com.cn/rmlt/pc/content/202512/02/content_30125637.html" target="_blank">
<div class="news-rank top">1</div>
<div class="news-body">
<div class="news-title">面向"十五五":激发银发经济新动能,稳妥推进延迟退休是重要路径</div>
<div class="news-meta">
<span class="news-source">人民论坛</span>
<span class="news-time">12月2日</span>
<span class="news-tag">延迟退休</span>
</div>
<div class="news-summary">推动老有所为与发展银发经济相互依存:优化就业年龄政策、积极开发老年人力资源,退而不休的"银发经济参与者"正成为万亿市场的创造者与消费者。</div>
</div>
</a>
<a class="news-card" href="https://news.qq.com/rain/a/20260312A08HWN00" target="_blank">
<div class="news-rank">2</div>
<div class="news-body">
<div class="news-title">两会热议:积极开发老年人力资源,银龄志愿服务体系扩展至农村</div>
<div class="news-meta">
<span class="news-source">腾讯新闻</span>
<span class="news-time">3月12日</span>
<span class="news-tag">银龄行动</span>
</div>
</div>
</a>
</div>
</div>
<!-- FOOTER -->
<div class="footer">
<div class="footer-brand">银发每日头条</div>
<div>聚焦银发经济 · 关注亿万老年人的美好生活</div>
<div style="margin-top:8px; color:#b0a890;">内容来源于公开新闻资讯,仅供参考 · Silver Age Daily · 第487期</div>
</div>
</body>
</html>
FILE:preview_generated.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>银发每日头条 · 2026年03月23日</title>
<style>
:root {
--gold: #c8922a;
--gold-light: #f0d080;
--silver: #8a9bb0;
--ink: #1a1a2e;
--bg: #faf8f5;
--card-bg: #ffffff;
--border: #e8e0d0;
--muted: #7a7a8a;
--tag-bg: #f5f0e8;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'PingFang SC','Microsoft YaHei',serif; background: var(--bg); color: var(--ink); line-height: 1.7; }
.header { background: linear-gradient(135deg,#1a1a2e 0%,#2d2d4e 60%,#3a2a1a 100%); padding: 28px 24px 22px; text-align: center; }
.header-logo { font-size: 13px; color: var(--gold-light); letter-spacing: 4px; opacity: .8; margin-bottom: 6px; }
.header-title { font-size: 30px; font-weight: 700; color: #fff; letter-spacing: 3px; }
.header-title span { color: var(--gold); }
.header-date { margin-top: 8px; font-size: 13px; color: rgba(255,255,255,.55); }
.header-line { width: 60px; height: 2px; background: linear-gradient(90deg,transparent,var(--gold),transparent); margin: 12px auto 0; }
.container { max-width: 860px; margin: 0 auto; padding: 20px 16px 40px; }
.top-story { background: linear-gradient(135deg,#1a1a2e,#2d2640); border-radius: 10px; padding: 20px; margin: 18px 0; position: relative; }
.top-story::before { content: '头条'; position: absolute; top: -2px; right: 14px; font-size: 11px; background: var(--gold); color: #fff; padding: 3px 10px 4px; border-radius: 0 0 6px 6px; letter-spacing: 2px; }
.top-story-title { font-size: 18px; font-weight: 700; color: #fff; line-height: 1.5; margin-bottom: 10px; }
.top-story-summary { font-size: 13px; color: rgba(255,255,255,.65); line-height: 1.7; }
.top-story-link { display: inline-flex; align-items: center; gap: 5px; margin-top: 14px; font-size: 13px; color: var(--gold); text-decoration: none; border: 1px solid rgba(200,146,42,.4); padding: 5px 14px; border-radius: 20px; }
.top-story-link:hover { background: rgba(200,146,42,.15); }
.stats-row { display: grid; grid-template-columns: repeat(4,1fr); gap: 10px; margin: 16px 0 28px; }
.stat-card { background: var(--card-bg); border: 1px solid var(--border); border-radius: 8px; padding: 14px 12px; text-align: center; }
.stat-num { font-size: 22px; font-weight: 800; color: var(--gold); line-height: 1.2; }
.stat-label { font-size: 11px; color: var(--muted); margin-top: 3px; }
.section-head { display: flex; align-items: center; gap: 10px; margin: 28px 0 14px; }
.section-icon { font-size: 22px; line-height: 1; }
.section-name { font-size: 17px; font-weight: 700; letter-spacing: 1px; }
.section-line { flex: 1; height: 1px; background: linear-gradient(90deg,var(--border),transparent); }
.section-count { font-size: 11px; color: var(--muted); background: var(--tag-bg); padding: 2px 8px; border-radius: 10px; }
.news-list { display: flex; flex-direction: column; gap: 10px; }
.news-card { background: var(--card-bg); border: 1px solid var(--border); border-radius: 8px; padding: 14px 16px; display: flex; gap: 12px; text-decoration: none; color: inherit; transition: box-shadow .2s,transform .2s; }
.news-card:hover { box-shadow: 0 4px 16px rgba(0,0,0,.08); transform: translateY(-1px); }
.news-card.featured { border-left: 3px solid var(--gold); background: linear-gradient(to right,#fffdf7,#fff); }
.news-rank { font-size: 18px; font-weight: 800; color: var(--gold-light); min-width: 26px; text-align: center; line-height: 1.4; }
.news-rank.top { color: var(--gold); }
.news-body { flex: 1; min-width: 0; }
.news-title { font-size: 15px; font-weight: 600; line-height: 1.5; margin-bottom: 5px; }
.news-card:hover .news-title { color: #8a5a10; }
.news-meta { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.news-source { font-size: 12px; color: var(--muted); }
.news-source::before { content: '📰 '; }
.news-time { font-size: 12px; color: #b0a890; }
.news-tag { font-size: 11px; background: var(--tag-bg); color: var(--gold); padding: 1px 7px; border-radius: 10px; border: 1px solid rgba(200,146,42,.2); }
.news-summary { font-size: 13px; color: #555; margin-top: 5px; line-height: 1.6; }
.footer { text-align: center; padding: 24px 16px; background: linear-gradient(180deg,var(--bg),#f0ece4); border-top: 1px solid var(--border); font-size: 12px; color: var(--muted); }
.footer-brand { font-size: 15px; font-weight: 700; color: var(--gold); letter-spacing: 2px; margin-bottom: 6px; }
@media (max-width:600px) { .stats-row { grid-template-columns: repeat(2,1fr); } .header-title { font-size: 22px; } }
</style>
</head>
<body>
<div class="header">
<div class="header-logo">SILVER AGE DAILY · 银发每日头条</div>
<div class="header-title">银发<span>每日</span>头条</div>
<div class="header-date">2026年03月23日 · 第 001 期</div>
<div class="header-line"></div>
</div>
<div class="container">
<div class="stats-row">
<div class="stat-card">
<div class="stat-num">3.2亿</div>
<div class="stat-label">60岁以上老年人口</div>
</div>
<div class="stat-card">
<div class="stat-num">30万亿</div>
<div class="stat-label">2026年银发经济规模</div>
</div>
<div class="stat-card">
<div class="stat-num">20%</div>
<div class="stat-label">老龄人口占总人口比</div>
</div>
<div class="stat-card">
<div class="stat-num">600+</div>
<div class="stat-label">今年银发研究论文数</div>
</div>
</div>
<div class="top-story">
<div class="top-story-title">国务院常务会议:进一步释放银发消费需求,打造消费新场景新业态</div>
<div class="top-story-summary">2026年2月24日,国务院常务会议明确要求发挥消费补贴政策牵引作用,将银发经济纳入扩内需核心战略。会议提出要推动老年用品、旅居养老、养老金融三大方向协同发力。</div>
<a href="https://www.gov.cn/zhengce/202602/content_7059238.htm" class="top-story-link" target="_blank">查看全文 →</a>
</div>
<div class="section-head">
<div class="section-icon">🏛️</div>
<div class="section-name">政策解读</div>
<div class="section-line"></div>
<div class="section-count">2 条</div>
</div>
<div class="news-list">
<a class="news-card featured" href="https://zhuanlan.zhihu.com/p/2013178662007112635" target="_blank">
<div class="news-rank top">1</div>
<div class="news-body">
<div class="news-title">2026政府工作报告深度解读:银发经济七大重点</div>
<div class="news-meta">
<span class="news-source">知乎·AgeClub</span>
<span class="news-time">3月6日</span>
<span class="news-tag">两会</span>
</div>
<div class="news-summary">报告提出制定推进银发经济高质量发展的措施,完善老年用品、养老金融、旅居养老等支持政策。</div>
</div>
</a>
<a class="news-card" href="https://www.nhsa.gov.cn/art/2025/12/1/art_104_18899.html" target="_blank">
<div class="news-rank">2</div>
<div class="news-body">
<div class="news-title">长期护理保险服务管理文书(2026年版)正式印发</div>
<div class="news-meta">
<span class="news-source">国家医保局</span>
<span class="news-time">12月1日</span>
<span class="news-tag">长护险</span>
</div>
<div class="news-summary">国家医保局更新长护险服务管理文书标准,试点城市扩展至全国49个。</div>
</div>
</a>
</div>
<div class="section-head">
<div class="section-icon">🛍️</div>
<div class="section-name">银发消费</div>
<div class="section-line"></div>
<div class="section-count">1 条</div>
</div>
<div class="news-list">
<a class="news-card featured" href="https://ageclub.net/article-detail/8165" target="_blank">
<div class="news-rank top">1</div>
<div class="news-body">
<div class="news-title">银发经济爆发!2026年中国市场趋势全解读</div>
<div class="news-meta">
<span class="news-source">AgeClub</span>
<span class="news-time">1月28日</span>
<span class="news-tag">市场</span>
</div>
<div class="news-summary">2026年银发消费呈现悦己化、品质化、场景化三大趋势,旅居、康养、智能硬件成三大爆发赛道。</div>
</div>
</a>
</div>
<div class="section-head">
<div class="section-icon">💰</div>
<div class="section-name">老年金融</div>
<div class="section-line"></div>
<div class="section-count">1 条</div>
</div>
<div class="news-list">
<a class="news-card featured" href="https://finance.sina.com.cn/jjxw/2026-03-18/doc-inhrizpe3512648.shtml" target="_blank">
<div class="news-rank top">1</div>
<div class="news-body">
<div class="news-title">养老金融多维突破:个人养老金账户扩围,商业养老险迎政策窗口</div>
<div class="news-meta">
<span class="news-source">中国消费者报</span>
<span class="news-time">3月18日</span>
<span class="news-tag">养老金</span>
</div>
<div class="news-summary">银发金融资管规模预计突破5万亿元,商业银行加速推出养老专属理财产品。</div>
</div>
</a>
</div>
<div class="section-head">
<div class="section-icon">🤖</div>
<div class="section-name">银发科技</div>
<div class="section-line"></div>
<div class="section-count">1 条</div>
</div>
<div class="news-list">
<a class="news-card featured" href="https://business.cctv.cn/2026/03/05/ARTIkoUbodguruWPA1dGPOi4260305.shtml" target="_blank">
<div class="news-rank top">1</div>
<div class="news-body">
<div class="news-title">养老机器人加速破局:情感陪护机器人订单翻倍增长</div>
<div class="news-meta">
<span class="news-source">央视网</span>
<span class="news-time">3月5日</span>
<span class="news-tag">机器人</span>
</div>
<div class="news-summary">春晚亮相后,情感陪伴、生活辅助、健康监测三大功能成养老机构采购首选。</div>
</div>
</a>
</div>
<div class="section-head">
<div class="section-icon">🏥</div>
<div class="section-name">医疗健康</div>
<div class="section-line"></div>
<div class="section-count">1 条</div>
</div>
<div class="news-list">
<a class="news-card featured" href="https://news.qq.com/rain/a/20260312A08HWN00" target="_blank">
<div class="news-rank top">1</div>
<div class="news-body">
<div class="news-title">3.2亿老人老有所依:医养结合加速,社区嵌入式养老服务全面铺开</div>
<div class="news-meta">
<span class="news-source">腾讯新闻</span>
<span class="news-time">3月12日</span>
<span class="news-tag">医养结合</span>
</div>
<div class="news-summary">15分钟养老服务圈成各地核心建设指标,覆盖率目标2026年底达80%。</div>
</div>
</a>
</div>
<div class="section-head">
<div class="section-icon">🎭</div>
<div class="section-name">银发文娱</div>
<div class="section-line"></div>
<div class="section-count">1 条</div>
</div>
<div class="news-list">
<a class="news-card featured" href="https://wlt.hlj.gov.cn/wlt/c115793/202603/c00_31924109.shtml" target="_blank">
<div class="news-rank top">1</div>
<div class="news-body">
<div class="news-title">银发旅游升温,旅居养老纳入国家政策支持</div>
<div class="news-meta">
<span class="news-source">黑龙江省文旅厅</span>
<span class="news-time">3月16日</span>
<span class="news-tag">旅居养老</span>
</div>
<div class="news-summary">旅游+养老融合市场规模预计年内突破5000亿元,异地候鸟式旅居模式渐成规模。</div>
</div>
</a>
</div>
<div class="section-head">
<div class="section-icon">🏠</div>
<div class="section-name">居家养老</div>
<div class="section-line"></div>
<div class="section-count">1 条</div>
</div>
<div class="news-list">
<a class="news-card featured" href="https://cn.chinadaily.com.cn/a/202512/17/WS6941f0a3a310942cc4996fc9.html" target="_blank">
<div class="news-rank top">1</div>
<div class="news-body">
<div class="news-title">AI+养老解锁新机遇:智能居家照护系统进入千家万户</div>
<div class="news-meta">
<span class="news-source">中国日报</span>
<span class="news-time">12月17日</span>
<span class="news-tag">智慧居家</span>
</div>
<div class="news-summary">2026年适老化改造补贴户数较去年增长40%,跌倒检测准确率超95%。</div>
</div>
</a>
</div>
<div class="section-head">
<div class="section-icon">💼</div>
<div class="section-name">银发就业</div>
<div class="section-line"></div>
<div class="section-count">1 条</div>
</div>
<div class="news-list">
<a class="news-card featured" href="https://paper.people.com.cn/rmlt/pc/content/202512/02/content_30125637.html" target="_blank">
<div class="news-rank top">1</div>
<div class="news-body">
<div class="news-title">面向十五五:激发银发经济新动能,稳妥推进延迟退休是重要路径</div>
<div class="news-meta">
<span class="news-source">人民论坛</span>
<span class="news-time">12月2日</span>
<span class="news-tag">延迟退休</span>
</div>
<div class="news-summary">积极开发老年人力资源,退而不休的银发经济参与者正成为万亿市场的创造者与消费者。</div>
</div>
</a>
</div>
</div>
<div class="footer">
<div class="footer-brand">银发每日头条</div>
<div>聚焦银发经济 · 关注亿万老年人的美好生活</div>
<div style="margin-top:8px;color:#b0a890;">内容来源于公开新闻资讯,仅供参考 · Silver Age Daily · 第001期</div>
</div>
</body>
</html>
FILE:PROJECT_SUMMARY.md
# 银发每日头条 Skill 项目总结
## 项目概述
**项目名称**:银发每日头条 (Silver Age Daily)
**版本**:v1.0.0
**作者**:cnspica
**创建日期**:2026年3月23日
> 一键生成当日银发经济精选资讯日报,覆盖八大板块,带新闻链接与摘要。
---
## 项目背景
针对中国3.2亿老年人口、30万亿银发经济市场,为产业从业者、研究者、投资人提供每日精选资讯聚合服务。
---
## 内容框架(八大板块)
| # | 板块 | 图标 | 覆盖内容 |
|---|------|------|---------|
| 1 | 政策解读 | 🏛️ | 国务院、民政部、医保局等政策动态 |
| 2 | 银发消费 | 🛍️ | 老年用品、消费补贴、悦己消费趋势 |
| 3 | 老年金融 | 💰 | 养老金、养老险、长护险、养老理财 |
| 4 | 银发科技 | 🤖 | 养老机器人、AI养老、智慧平台、可穿戴 |
| 5 | 医疗健康 | 🏥 | 医养结合、慢病、康复、认知症、安宁疗护 |
| 6 | 银发文娱 | 🎭 | 旅居养老、老年教育、银发电竞、短视频 |
| 7 | 居家养老 | 🏠 | 适老化改造、社区养老、上门服务 |
| 8 | 银发就业 | 💼 | 延迟退休、银龄志愿、老年创业 |
---
## 技术架构
### 文件结构
```
silver-daily/
├── SKILL.md # Skill核心(三段式:描述/触发条件/工作流)
├── clawhub.json # ClawHub发布配置
├── README.md # 项目说明
├── PROJECT_SUMMARY.md # 本文件
├── .gitignore # Git忽略规则
├── scripts/
│ └── generate_report.py # HTML报告生成脚本(Python)
├── references/
│ ├── content_framework.md # 八大板块内容框架
│ ├── html_template.md # 页面设计规范
│ └── output_format.md # 输出格式规范
├── output/ # 生成的日报存放处
├── preview.html # 样本头条预览
└── preview_generated.html # 脚本生成预览
```
### 核心功能
1. **新闻聚合**:通过web_search搜索8大板块最新资讯
2. **内容筛选**:提取标题、来源、链接、100字摘要
3. **HTML生成**:生成排版简洁大方的日报页面
4. **本地脚本**:支持独立运行Python脚本生成报告
---
## 设计规范
### 配色方案
| 变量 | 色值 | 用途 |
|------|------|------|
| `--gold` | `#c8922a` | 主品牌色,强调,数字 |
| `--gold-light` | `#f0d080` | 次级标注 |
| `--ink` | `#1a1a2e` | 正文深色 |
| `--bg` | `#faf8f5` | 页面背景(暖白) |
| `--card-bg` | `#ffffff` | 卡片背景 |
| `--border` | `#e8e0d0` | 边框 |
| `--muted` | `#7a7a8a` | 次要文字 |
### 页面结构
```
Header(品牌标题 + 日期)
↓
Stats Row(4个关键数据指标)
↓
Top Story(今日头条卡片)
↓
Section × 8(各板块新闻列表)
↓
Footer(版权信息)
```
---
## 发布状态
| 平台 | 状态 | 链接/ID |
|------|------|---------|
| **GitHub** | ✅ 已发布 | https://github.com/cnspica/silver-daily |
| **ClawHub** | ✅ 已发布 | [email protected] (ID: k974afas49xcna45nfwpb99h1s83fjn3) |
| **本地安装** | ✅ 已安装 | C:\Users\TR\WorkBuddy\20260323120509\skills\silver-daily |
---
## 使用方式
### 命令行安装
```bash
clawhub install silver-daily
```
### 对话触发
```
生成今日银发头条
银发经济今天有什么新消息?
帮我出一期银发每日头条
```
### 脚本独立运行
```bash
python scripts/generate_report.py --output output/silver-daily-YYYYMMDD.html
```
---
## 关键数据(2026年)
- **老年人口**:3.2亿(占总人口20%)
- **银发经济规模**:30万亿
- **研究论文**:年发文量600+篇
- **长护险试点**:49个城市,覆盖1.2亿人
---
## 后续优化方向
1. **数据源扩展**:接入RSS、API实时抓取
2. **定时推送**:结合cron实现每日自动推送
3. **飞书集成**:对接飞书机器人发送日报
4. **个性化推荐**:基于用户偏好筛选内容
5. **历史归档**:建立可搜索的新闻数据库
---
## 技术栈
- **Skill框架**:OpenClaw / ClawHub
- **脚本语言**:Python 3
- **输出格式**:HTML5 + CSS3
- **版本控制**:Git
- **包管理**:npm (clawhub CLI)
---
## 许可证
MIT License © cnspica
---
*文档生成时间:2026年3月23日*
FILE:README.md
# 银发头条 · Silver Age Daily
> 一键生成当日银发经济精选资讯日报,覆盖八大板块,带新闻链接与摘要。
## 功能简介
**银发头条**是一个面向银发经济领域的 AI 资讯聚合 Skill,每次运行自动:
1. 搜索政策解读、银发消费、老年金融、银发科技、医疗健康、银发文娱、居家养老、银发就业 **8 大板块**最新新闻
2. 筛选高质量内容,提取标题、摘要、链接、来源
3. 生成一份排版简洁大方的 **HTML 日报**
## 适用人群
- 银发经济领域从业者、投资人、研究者
- 关注老龄化政策与趋势的人士
- 养老机构管理者、产品经理
- 关注父母/老人生活的人士
## 使用方法
直接对话触发:
```
生成今日银发头条
银发经济今天有什么新消息?
帮我出一期银发每日头条
```
或手动运行脚本(使用演示数据):
```bash
python scripts/generate_report.py
# 生成到 output/silver-daily-YYYYMMDD.html
```
## 目录结构
```
silver-daily/
├── SKILL.md # Skill 核心(三段式)
├── clawhub.json # 发布配置
├── README.md # 本文件
├── scripts/
│ └── generate_report.py # HTML 报告生成脚本
├── references/
│ ├── content_framework.md # 八大板块内容框架
│ ├── html_template.md # 页面设计规范
│ └── output_format.md # 输出格式规范
└── output/ # 生成的日报存放处(.gitignore)
```
## 内容框架(八大板块)
| # | 板块 | 覆盖内容 |
|---|------|---------|
| 1 | 🏛️ 政策解读 | 国务院、民政部、医保局等政策动态 |
| 2 | 🛍️ 银发消费 | 老年用品、适老家居、消费趋势 |
| 3 | 💰 老年金融 | 养老金、养老险、长护险、养老理财 |
| 4 | 🤖 银发科技 | 养老机器人、AI养老、智慧养老平台 |
| 5 | 🏥 医疗健康 | 医养结合、慢病管理、康复照护 |
| 6 | 🎭 银发文娱 | 旅居养老、老年教育、银发旅游 |
| 7 | 🏠 居家养老 | 适老化改造、社区养老、上门服务 |
| 8 | 💼 银发就业 | 延迟退休、银龄行动、老年创业 |
## License
MIT © cnspica
FILE:references/content_framework.md
# 银发每日头条 — 内容框架
## 八大核心板块
### 1. 🏛️ 政策解读
- 国务院、民政部、国家医保局等部委政策文件
- 地方养老条例、实施细则
- 两会涉老相关提案与议案
- 国家发展规划("十五五"、健康中国等)
### 2. 🛍️ 银发消费
- 老年用品(辅具、适老家居、功能性食品)
- 老年服装与时尚(银发时尚品牌)
- 老年零售业态(银发超市、老年电商)
- 消费补贴落地动态
- 银发消费数据与报告
### 3. 💰 老年金融
- 个人养老金账户动态
- 养老储蓄、养老理财、养老信托
- 商业养老保险(年金险、护理险)
- 长期护理保险(长护险)
- 养老金融监管政策
- 银发资产管理与财富传承
### 4. 🤖 银发科技
- 养老机器人(陪护、服务、康复)
- 人工智能 + 养老(AI健康管家)
- 智慧养老平台与系统
- 可穿戴健康设备
- 老年适老化手机/应用
- 物联网居家安全产品
- 远程医疗与远程照护
### 5. 🏥 医疗健康
- 老年慢病管理(高血压、糖尿病、心血管)
- 认知症(阿尔茨海默病)防治与照护
- 康复医疗与辅助器具
- 医养结合机构动态
- 老年口腔、眼科、骨科专科
- 老年心理健康与情绪管理
- 安宁疗护与临终关怀
- 老年营养与保健品监管
### 6. 🎭 银发文娱
- 银发旅游与旅居养老
- 老年教育(老年大学、在线课堂)
- 银发体育与广场舞文化
- 老年文艺(戏曲、摄影、书画)
- 老年社交平台与社群
- 银发电竞与数字娱乐
- 老年直播与短视频生态
### 7. 🏠 居家与社区养老
- 居家养老服务上门(家政、护理、送餐)
- 适老化改造(家居、社区、城市)
- 社区嵌入式养老机构
- 15分钟养老服务圈建设
- 农村养老与互助养老
- 老年人住房与养老地产
### 8. 💼 银发就业·老有所为
- 延迟退休政策进展
- 老年人就业市场动态
- 银龄志愿服务与公益
- 老年创业与二次就业
- 祖父母经济(隔代照料)
- 退休人员再教育与技能再培训
---
## 新闻质量标准
**优先来源(权重高)**
- 中国政府网、各部委官网
- 央视、新华社、人民日报
- 经济日报、中国老龄报
- AgeClub、养老产业联盟
- 中国银发经济研究机构
**可选来源**
- 主流财经媒体(财新、第一财经等)
- 知名智库报告(麦肯锡、艾瑞咨询等)
- 学术论文与专业期刊
**排除**
- 无来源自媒体内容
- 明显营销软文
- 超过 30 天的过期内容
---
## 每日头条选题标准
1. **政策重磅** — 国务院/部委级别重要政策发布
2. **数据里程碑** — 重要行业数据首次公布
3. **行业大事件** — 重要并购、融资、展会、发布会
4. **民生热点** — 直接影响老年群体日常生活的政策或事件
FILE:references/html_template.md
# HTML 输出模板说明
## 页面结构
```
Header(品牌标题 + 日期)
↓
Stats Row(4 个关键数据指标)
↓
Top Story(今日头条卡片)
↓
Section × 8(各板块新闻列表)
↓
Footer(版权信息)
```
## 颜色规范
| 变量 | 色值 | 用途 |
|------|------|------|
| `--gold` | `#c8922a` | 主品牌色,强调,数字 |
| `--gold-light` | `#f0d080` | 次级标注 |
| `--ink` | `#1a1a2e` | 正文深色 |
| `--bg` | `#faf8f5` | 页面背景(暖白) |
| `--card-bg` | `#ffffff` | 卡片背景 |
| `--border` | `#e8e0d0` | 边框 |
| `--muted` | `#7a7a8a` | 次要文字 |
## 组件说明
### 新闻卡片 `.news-card`
- `featured` 类:左侧金色 border,浅黄背景
- `news-rank.top`:金色序号(第1条)
- `news-summary`:摘要(可选,简洁版不显示)
### 版块标题 `.section-head`
- 图标 + 版块名 + 分隔线 + 条数 badge
### 顶部数据栏 `.stats-row`
- 4列等宽,小屏自动变2列
## 字体尺寸层级
| 元素 | 大小 |
|------|------|
| 品牌标题 | 30px |
| 顶部故事标题 | 18px |
| 板块标题 | 17px |
| 新闻标题 | 15px |
| 摘要/元信息 | 12-13px |
| 标签/次要 | 11px |
FILE:references/output_format.md
# 输出格式规范
## 对话中输出格式
当生成完成后,在对话中按如下格式回复:
```
📅 {日期} · 第 {期数} 期
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔥 今日头条
{标题}
📎 {链接}
📊 关键数据
· 老年人口:X 亿 · 市场规模:X 万亿
· 老龄占比:X% · 今年研究论文:X篇+
─── 本期共 8 个板块,XX 条资讯 ───
🏛️ 政策解读(X条)
1. {标题} — {来源} {链接}
2. ...
🛍️ 银发消费(X条) ...
💰 老年金融(X条) ...
🤖 银发科技(X条) ...
🏥 医疗健康(X条) ...
🎭 银发文娱(X条) ...
🏠 居家养老(X条) ...
💼 银发就业(X条) ...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ HTML 报告已生成:output/silver-daily-YYYYMMDD.html
```
## 文件命名规范
- HTML 报告:`output/silver-daily-YYYYMMDD.html`
- 数据文件:`output/data-YYYYMMDD.json`(可选)
## 发布/分享建议
1. 直接将 HTML 文件发送给目标读者
2. 上传到静态托管(GitHub Pages / 腾讯云COS)
3. 截图后发送到微信群/飞书群
4. 接入自动化推送:结合 cron + feishu-cron-reminder skill
FILE:scripts/generate_report.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
silver-daily: 银发每日头条 HTML 报告生成器
Usage:
python generate_report.py [--output OUTPUT_FILE] [--data JSON_FILE]
"""
import json
import argparse
import os
from datetime import datetime
# ─── 默认演示数据 ──────────────────────────────────────────────
DEMO_DATA = {
"date": datetime.now().strftime("%Y年%m月%d日"),
"issue": "001",
"top_story": {
"title": "国务院常务会议:进一步释放银发消费需求,打造消费新场景新业态",
"summary": "2026年2月24日,国务院常务会议明确要求发挥消费补贴政策牵引作用,将银发经济纳入扩内需核心战略。会议提出要推动老年用品、旅居养老、养老金融三大方向协同发力。",
"url": "https://www.gov.cn/zhengce/202602/content_7059238.htm",
"source": "中国政府网",
"date": "2026-02-24"
},
"stats": [
{"num": "3.2亿", "label": "60岁以上老年人口"},
{"num": "30万亿", "label": "2026年银发经济规模"},
{"num": "20%", "label": "老龄人口占总人口比"},
{"num": "600+", "label": "今年银发研究论文数"}
],
"sections": [
{
"id": "policy", "icon": "🏛️", "name": "政策解读",
"news": [
{
"rank": 1, "featured": True,
"title": "2026政府工作报告深度解读:银发经济七大重点",
"source": "知乎·AgeClub", "time": "3月6日", "tag": "两会",
"url": "https://zhuanlan.zhihu.com/p/2013178662007112635",
"summary": "报告提出制定推进银发经济高质量发展的措施,完善老年用品、养老金融、旅居养老等支持政策。"
},
{
"rank": 2, "featured": False,
"title": "长期护理保险服务管理文书(2026年版)正式印发",
"source": "国家医保局", "time": "12月1日", "tag": "长护险",
"url": "https://www.nhsa.gov.cn/art/2025/12/1/art_104_18899.html",
"summary": "国家医保局更新长护险服务管理文书标准,试点城市扩展至全国49个。"
}
]
},
{
"id": "consume", "icon": "🛍️", "name": "银发消费",
"news": [
{
"rank": 1, "featured": True,
"title": "银发经济爆发!2026年中国市场趋势全解读",
"source": "AgeClub", "time": "1月28日", "tag": "市场",
"url": "https://ageclub.net/article-detail/8165",
"summary": "2026年银发消费呈现悦己化、品质化、场景化三大趋势,旅居、康养、智能硬件成三大爆发赛道。"
}
]
},
{
"id": "finance", "icon": "💰", "name": "老年金融",
"news": [
{
"rank": 1, "featured": True,
"title": "养老金融多维突破:个人养老金账户扩围,商业养老险迎政策窗口",
"source": "中国消费者报", "time": "3月18日", "tag": "养老金",
"url": "https://finance.sina.com.cn/jjxw/2026-03-18/doc-inhrizpe3512648.shtml",
"summary": "银发金融资管规模预计突破5万亿元,商业银行加速推出养老专属理财产品。"
}
]
},
{
"id": "tech", "icon": "🤖", "name": "银发科技",
"news": [
{
"rank": 1, "featured": True,
"title": "养老机器人加速破局:情感陪护机器人订单翻倍增长",
"source": "央视网", "time": "3月5日", "tag": "机器人",
"url": "https://business.cctv.cn/2026/03/05/ARTIkoUbodguruWPA1dGPOi4260305.shtml",
"summary": "春晚亮相后,情感陪伴、生活辅助、健康监测三大功能成养老机构采购首选。"
}
]
},
{
"id": "health", "icon": "🏥", "name": "医疗健康",
"news": [
{
"rank": 1, "featured": True,
"title": "3.2亿老人老有所依:医养结合加速,社区嵌入式养老服务全面铺开",
"source": "腾讯新闻", "time": "3月12日", "tag": "医养结合",
"url": "https://news.qq.com/rain/a/20260312A08HWN00",
"summary": "15分钟养老服务圈成各地核心建设指标,覆盖率目标2026年底达80%。"
}
]
},
{
"id": "culture", "icon": "🎭", "name": "银发文娱",
"news": [
{
"rank": 1, "featured": True,
"title": "银发旅游升温,旅居养老纳入国家政策支持",
"source": "黑龙江省文旅厅", "time": "3月16日", "tag": "旅居养老",
"url": "https://wlt.hlj.gov.cn/wlt/c115793/202603/c00_31924109.shtml",
"summary": "旅游+养老融合市场规模预计年内突破5000亿元,异地候鸟式旅居模式渐成规模。"
}
]
},
{
"id": "home", "icon": "🏠", "name": "居家养老",
"news": [
{
"rank": 1, "featured": True,
"title": "AI+养老解锁新机遇:智能居家照护系统进入千家万户",
"source": "中国日报", "time": "12月17日", "tag": "智慧居家",
"url": "https://cn.chinadaily.com.cn/a/202512/17/WS6941f0a3a310942cc4996fc9.html",
"summary": "2026年适老化改造补贴户数较去年增长40%,跌倒检测准确率超95%。"
}
]
},
{
"id": "employ", "icon": "💼", "name": "银发就业",
"news": [
{
"rank": 1, "featured": True,
"title": "面向十五五:激发银发经济新动能,稳妥推进延迟退休是重要路径",
"source": "人民论坛", "time": "12月2日", "tag": "延迟退休",
"url": "https://paper.people.com.cn/rmlt/pc/content/202512/02/content_30125637.html",
"summary": "积极开发老年人力资源,退而不休的银发经济参与者正成为万亿市场的创造者与消费者。"
}
]
}
]
}
def render_news_card(news):
featured_class = " featured" if news.get("featured") else ""
rank_class = " top" if news.get("rank") == 1 else ""
summary_html = f'<div class="news-summary">{news["summary"]}</div>' if news.get("summary") else ""
return f'''
<a class="news-card{featured_class}" href="{news["url"]}" target="_blank">
<div class="news-rank{rank_class}">{news["rank"]}</div>
<div class="news-body">
<div class="news-title">{news["title"]}</div>
<div class="news-meta">
<span class="news-source">{news["source"]}</span>
<span class="news-time">{news["time"]}</span>
<span class="news-tag">{news["tag"]}</span>
</div>
{summary_html}
</div>
</a>'''
def render_section(section):
news_html = "\n".join(render_news_card(n) for n in section["news"])
count = len(section["news"])
return f'''
<div class="section-head">
<div class="section-icon">{section["icon"]}</div>
<div class="section-name">{section["name"]}</div>
<div class="section-line"></div>
<div class="section-count">{count} 条</div>
</div>
<div class="news-list">
{news_html}
</div>'''
def render_stats(stats):
cards = ""
for s in stats:
cards += f'''
<div class="stat-card">
<div class="stat-num">{s["num"]}</div>
<div class="stat-label">{s["label"]}</div>
</div>'''
return f'<div class="stats-row">{cards}\n </div>'
def generate_html(data):
top = data["top_story"]
sections_html = "\n".join(render_section(s) for s in data["sections"])
stats_html = render_stats(data["stats"])
return f'''<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>银发每日头条 · {data["date"]}</title>
<style>
:root {{
--gold: #c8922a;
--gold-light: #f0d080;
--silver: #8a9bb0;
--ink: #1a1a2e;
--bg: #faf8f5;
--card-bg: #ffffff;
--border: #e8e0d0;
--muted: #7a7a8a;
--tag-bg: #f5f0e8;
}}
* {{ box-sizing: border-box; margin: 0; padding: 0; }}
body {{ font-family: 'PingFang SC','Microsoft YaHei',serif; background: var(--bg); color: var(--ink); line-height: 1.7; }}
.header {{ background: linear-gradient(135deg,#1a1a2e 0%,#2d2d4e 60%,#3a2a1a 100%); padding: 28px 24px 22px; text-align: center; }}
.header-logo {{ font-size: 13px; color: var(--gold-light); letter-spacing: 4px; opacity: .8; margin-bottom: 6px; }}
.header-title {{ font-size: 30px; font-weight: 700; color: #fff; letter-spacing: 3px; }}
.header-title span {{ color: var(--gold); }}
.header-date {{ margin-top: 8px; font-size: 13px; color: rgba(255,255,255,.55); }}
.header-line {{ width: 60px; height: 2px; background: linear-gradient(90deg,transparent,var(--gold),transparent); margin: 12px auto 0; }}
.container {{ max-width: 860px; margin: 0 auto; padding: 20px 16px 40px; }}
.top-story {{ background: linear-gradient(135deg,#1a1a2e,#2d2640); border-radius: 10px; padding: 20px; margin: 18px 0; position: relative; }}
.top-story::before {{ content: '头条'; position: absolute; top: -2px; right: 14px; font-size: 11px; background: var(--gold); color: #fff; padding: 3px 10px 4px; border-radius: 0 0 6px 6px; letter-spacing: 2px; }}
.top-story-title {{ font-size: 18px; font-weight: 700; color: #fff; line-height: 1.5; margin-bottom: 10px; }}
.top-story-summary {{ font-size: 13px; color: rgba(255,255,255,.65); line-height: 1.7; }}
.top-story-link {{ display: inline-flex; align-items: center; gap: 5px; margin-top: 14px; font-size: 13px; color: var(--gold); text-decoration: none; border: 1px solid rgba(200,146,42,.4); padding: 5px 14px; border-radius: 20px; }}
.top-story-link:hover {{ background: rgba(200,146,42,.15); }}
.stats-row {{ display: grid; grid-template-columns: repeat(4,1fr); gap: 10px; margin: 16px 0 28px; }}
.stat-card {{ background: var(--card-bg); border: 1px solid var(--border); border-radius: 8px; padding: 14px 12px; text-align: center; }}
.stat-num {{ font-size: 22px; font-weight: 800; color: var(--gold); line-height: 1.2; }}
.stat-label {{ font-size: 11px; color: var(--muted); margin-top: 3px; }}
.section-head {{ display: flex; align-items: center; gap: 10px; margin: 28px 0 14px; }}
.section-icon {{ font-size: 22px; line-height: 1; }}
.section-name {{ font-size: 17px; font-weight: 700; letter-spacing: 1px; }}
.section-line {{ flex: 1; height: 1px; background: linear-gradient(90deg,var(--border),transparent); }}
.section-count {{ font-size: 11px; color: var(--muted); background: var(--tag-bg); padding: 2px 8px; border-radius: 10px; }}
.news-list {{ display: flex; flex-direction: column; gap: 10px; }}
.news-card {{ background: var(--card-bg); border: 1px solid var(--border); border-radius: 8px; padding: 14px 16px; display: flex; gap: 12px; text-decoration: none; color: inherit; transition: box-shadow .2s,transform .2s; }}
.news-card:hover {{ box-shadow: 0 4px 16px rgba(0,0,0,.08); transform: translateY(-1px); }}
.news-card.featured {{ border-left: 3px solid var(--gold); background: linear-gradient(to right,#fffdf7,#fff); }}
.news-rank {{ font-size: 18px; font-weight: 800; color: var(--gold-light); min-width: 26px; text-align: center; line-height: 1.4; }}
.news-rank.top {{ color: var(--gold); }}
.news-body {{ flex: 1; min-width: 0; }}
.news-title {{ font-size: 15px; font-weight: 600; line-height: 1.5; margin-bottom: 5px; }}
.news-card:hover .news-title {{ color: #8a5a10; }}
.news-meta {{ display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }}
.news-source {{ font-size: 12px; color: var(--muted); }}
.news-source::before {{ content: '📰 '; }}
.news-time {{ font-size: 12px; color: #b0a890; }}
.news-tag {{ font-size: 11px; background: var(--tag-bg); color: var(--gold); padding: 1px 7px; border-radius: 10px; border: 1px solid rgba(200,146,42,.2); }}
.news-summary {{ font-size: 13px; color: #555; margin-top: 5px; line-height: 1.6; }}
.footer {{ text-align: center; padding: 24px 16px; background: linear-gradient(180deg,var(--bg),#f0ece4); border-top: 1px solid var(--border); font-size: 12px; color: var(--muted); }}
.footer-brand {{ font-size: 15px; font-weight: 700; color: var(--gold); letter-spacing: 2px; margin-bottom: 6px; }}
@media (max-width:600px) {{ .stats-row {{ grid-template-columns: repeat(2,1fr); }} .header-title {{ font-size: 22px; }} }}
</style>
</head>
<body>
<div class="header">
<div class="header-logo">SILVER AGE DAILY · 银发每日头条</div>
<div class="header-title">银发<span>每日</span>头条</div>
<div class="header-date">{data["date"]} · 第 {data["issue"]} 期</div>
<div class="header-line"></div>
</div>
<div class="container">
{stats_html}
<div class="top-story">
<div class="top-story-title">{top["title"]}</div>
<div class="top-story-summary">{top["summary"]}</div>
<a href="{top["url"]}" class="top-story-link" target="_blank">查看全文 →</a>
</div>
{sections_html}
</div>
<div class="footer">
<div class="footer-brand">银发每日头条</div>
<div>聚焦银发经济 · 关注亿万老年人的美好生活</div>
<div style="margin-top:8px;color:#b0a890;">内容来源于公开新闻资讯,仅供参考 · Silver Age Daily · 第{data["issue"]}期</div>
</div>
</body>
</html>'''
def main():
parser = argparse.ArgumentParser(description="银发每日头条报告生成器")
parser.add_argument("--output", default=None, help="输出文件路径")
parser.add_argument("--data", default=None, help="新闻数据 JSON 文件路径")
args = parser.parse_args()
# 加载数据
if args.data and os.path.exists(args.data):
with open(args.data, "r", encoding="utf-8") as f:
data = json.load(f)
else:
data = DEMO_DATA
data["date"] = datetime.now().strftime("%Y年%m月%d日")
# 生成 HTML
html = generate_html(data)
# 确定输出路径
if args.output:
out_path = args.output
else:
os.makedirs("output", exist_ok=True)
out_path = f"output/silver-daily-{datetime.now().strftime('%Y%m%d')}.html"
with open(out_path, "w", encoding="utf-8") as f:
f.write(html)
print(f"[OK] 银发每日头条已生成:{out_path}")
print(f"日期:{data['date']} · 第 {data['issue']} 期")
total = sum(len(s["news"]) for s in data["sections"])
print(f"共 {len(data['sections'])} 个板块,{total} 条资讯")
if __name__ == "__main__":
main()
将本地 Markdown 文章上传到微信公众号草稿箱。当用户提到"上传文章到公众号"、"发布到微信公众号"、"推送到公众号草稿"等场景时应使用本技能。本技能通过调用微信公众平台 API,自动完成 Markdown 转 HTML、封面图生成/上传、创建草稿等全流程操作。
---
name: wechat-mp-draft
description: 将本地 Markdown 文章上传到微信公众号草稿箱。当用户提到"上传文章到公众号"、"发布到微信公众号"、"推送到公众号草稿"等场景时应使用本技能。本技能通过调用微信公众平台 API,自动完成 Markdown 转 HTML、封面图生成/上传、创建草稿等全流程操作。
---
# 微信公众号草稿上传技能
## 技能概述
本技能将本地 Markdown 文件自动转换为微信公众号格式的 HTML,上传封面图素材,并通过微信公众平台 API 创建草稿,最终文章出现在公众号后台「草稿箱」中等待发布。
## 使用前提
在执行前,向用户确认以下信息:
1. **AppID** 和 **AppSecret**(公众平台后台 → 开发 → 基本配置)
2. **Markdown 文件路径**(本地绝对路径)
3. **封面图路径**(可选;不提供则自动生成绿色渐变占位图)
4. **作者名称**(可选)
5. **文章摘要**(可选,不填则自动截取正文前 100 字)
> ⚠️ **IP 白名单**:若运行环境 IP 未加入白名单,API 会返回 40164 错误。提示用户在公众平台 → 开发 → 基本配置 → IP白名单中添加当前出口 IP。
## 执行流程
### Step 1:检查 Python 环境
```bash
python --version
```
若 Python 不可用,提示用户安装 Python 3.7+。
可选安装 Pillow(用于生成高质量封面图):
```bash
pip install Pillow
```
不安装 Pillow 也可运行(会自动下载免费占位图)。
### Step 2:运行上传脚本
脚本位于本技能的 `scripts/upload_draft.py`。
**基础用法(自动生成封面):**
```bash
python scripts/upload_draft.py \
--appid "YOUR_APPID" \
--secret "YOUR_APPSECRET" \
--md "C:/path/to/article.md" \
--author "作者名"
```
**指定封面图:**
```bash
python scripts/upload_draft.py \
--appid "YOUR_APPID" \
--secret "YOUR_APPSECRET" \
--md "C:/path/to/article.md" \
--cover "C:/path/to/cover.jpg" \
--author "作者名" \
--digest "文章摘要,最多120字"
```
**参数说明:**
| 参数 | 必填 | 说明 |
|------|------|------|
| `--appid` | ✅ | 公众号 AppID |
| `--secret` | ✅ | 公众号 AppSecret |
| `--md` | ✅ | Markdown 文件绝对路径 |
| `--cover` | ❌ | 封面图路径(JPG/PNG),不填则自动生成 |
| `--author` | ❌ | 文章作者 |
| `--digest` | ❌ | 摘要(最多120字),不填则截取正文 |
### Step 3:验证结果
脚本成功输出示例:
```
✅ 获取 access_token 成功(有效期 7200 秒)
✅ 封面图上传成功,media_id = xxx
✅ 草稿创建成功!草稿 media_id = yyy
🎉 完成!文章《智慧养老正式进入AI时代》已成功上传至草稿箱。
```
告知用户登录 [微信公众平台](https://mp.weixin.qq.com/) → **内容** → **草稿箱** 查看文章。
## 常见问题处理
| 错误 | 原因 | 解决 |
|------|------|------|
| `40001` access_token 无效 | AppID/AppSecret 错误 | 重新确认凭证 |
| `40164` IP 不合法 | 当前 IP 不在白名单 | 在公众平台添加 IP |
| `40007` media_id 无效 | 封面图上传失败 | 检查图片格式和大小(≤10MB) |
| 封面图下载失败 | 无网络或 Pillow 未装 | 手动提供一张 JPG/PNG 封面图 |
## 参考资料
详细 API 规范见 `references/wechat_api.md`。
FILE:references/wechat_api.md
# 微信公众号草稿 API 参考
## 前置条件
| 条件 | 说明 |
|------|------|
| 公众号类型 | 订阅号/服务号均可,草稿接口无类型限制 |
| AppID + AppSecret | 在公众平台后台「开发 → 基本配置」获取 |
| IP 白名单 | 若调用方 IP 不在白名单,需先添加(公众平台 → 开发 → 基本配置 → IP白名单) |
| 接口权限 | 草稿箱接口默认开放,无需额外申请 |
## 核心接口
### 1. 获取 Access Token
```
GET https://api.weixin.qq.com/cgi-bin/token
?grant_type=client_credential
&appid=APPID
&secret=APPSECRET
```
- 有效期 7200 秒(2 小时),每日调用上限 2000 次
- 返回示例:`{"access_token":"xxx","expires_in":7200}`
### 2. 上传永久图片素材(封面用)
```
POST https://api.weixin.qq.com/cgi-bin/material/add_material
?access_token=ACCESS_TOKEN
&type=image
```
- Content-Type: multipart/form-data
- form 字段:`media`(图片文件)
- 支持格式:JPG/PNG/GIF
- 图片建议尺寸:900×500 px,大小 ≤ 10 MB
- 返回:`{"media_id":"xxx","url":"https://..."}`
### 3. 新建草稿
```
POST https://api.weixin.qq.com/cgi-bin/draft/add
?access_token=ACCESS_TOKEN
```
请求体(JSON):
```json
{
"articles": [{
"title": "文章标题",
"author": "作者(可选)",
"digest": "摘要,最多120字(可选)",
"content": "正文HTML(必填)",
"thumb_media_id": "封面图 media_id(必填)",
"need_open_comment": 0,
"only_fans_can_comment": 0
}]
}
```
- 返回:`{"media_id":"草稿media_id"}`
- 一次最多 8 篇文章
## 常见错误码
| errcode | 含义 | 解决方法 |
|---------|------|---------|
| 40001 | access_token 无效/过期 | 重新获取 token |
| 40164 | IP 不在白名单 | 添加当前 IP 到公众平台白名单 |
| 45009 | 接口调用超限 | 降低调用频率 |
| 40007 | media_id 无效 | 检查封面图是否上传成功 |
| 47001 | 解析 JSON/XML 错误 | 检查请求体格式 |
## HTML 内容规范(微信公众号)
- 使用**内联样式**(`style="..."`),不支持外部 CSS
- 图片需先上传到素材库,使用微信 CDN 地址
- 不支持 `<script>`、`<iframe>` 等标签
- 推荐字体:`-apple-system, BlinkMacSystemFont, 'Helvetica Neue', Arial, sans-serif`
- 推荐行高:`line-height: 1.8`
- 微信绿色:`#07C160`
FILE:scripts/get_draft.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
获取微信公众号草稿列表脚本
"""
import requests
import json
import sys
import io
# 设置标准输出编码为 UTF-8
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
def get_access_token(appid, secret):
"""获取 access_token"""
url = f"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={secret}"
response = requests.get(url)
result = response.json()
if 'access_token' not in result:
print(f"❌ 获取 access_token 失败")
print(f"错误码: {result.get('errcode')}")
print(f"错误信息: {result.get('errmsg')}")
sys.exit(1)
print(f"✅ 获取 access_token 成功(有效期 {result.get('expires_in')} 秒)")
return result['access_token']
def get_draft_list(access_token):
"""获取草稿列表"""
url = f"https://api.weixin.qq.com/cgi-bin/draft/batchget?access_token={access_token}"
# 微信草稿接口需要 POST 请求,参数为 JSON 格式
# offset: 从第几个草稿开始获取,默认为 0
# count: 要获取的草稿数量,最多为 20
# no_content: 是否不返回草稿的具体内容,1 表示不返回
payload = {
"offset": 0,
"count": 20,
"no_content": 0 # 我们需要获取具体内容
}
headers = {
'Content-Type': 'application/json'
}
response = requests.post(url, headers=headers, data=json.dumps(payload))
result = response.json()
if result.get('errcode', 0) != 0:
print(f"❌ 获取草稿列表失败")
print(f"错误码: {result.get('errcode')}")
print(f"错误信息: {result.get('errmsg')}")
sys.exit(1)
print(f"✅ 获取草稿列表成功,共 {result.get('total_count')} 篇草稿")
return result
def find_draft_by_title(draft_data, title):
"""根据标题查找草稿"""
items = draft_data.get('item', [])
# 先打印所有标题用于调试
print(f"\n🔍 调试:所有标题如下:")
for item in items:
articles = item.get('content', {}).get('news_item', [])
for article in articles:
actual_title = article.get('title')
print(f" 实际标题: {repr(actual_title)}")
print(f" 目标标题: {repr(title)}")
print(f" 是否匹配: {actual_title == title}")
print()
# 先尝试精确匹配
for item in items:
articles = item.get('content', {}).get('news_item', [])
for article in articles:
if article.get('title') == title:
print(f"✅ 找到匹配草稿: {title}")
print(f" 草稿 media_id: {item.get('media_id')}")
return article, item.get('media_id')
# 如果精确匹配失败,尝试模糊匹配(去除标点符号)
title_clean = title.replace(',', ',').replace('。', '.').replace(';', ';')
for item in items:
articles = item.get('content', {}).get('news_item', [])
for article in articles:
actual_title = article.get('title')
actual_title_clean = actual_title.replace(',', ',').replace('。', '.').replace(';', ';')
if actual_title_clean == title_clean:
print(f"✅ 找到匹配草稿(模糊匹配): {actual_title}")
print(f" 草稿 media_id: {item.get('media_id')}")
return article, item.get('media_id')
print(f"❌ 未找到标题为「{title}」的草稿")
return None, None
def main():
# 从命令行参数获取 AppID 和 AppSecret
if len(sys.argv) < 3:
print("用法: python get_draft.py <appid> <secret> [title]")
print("示例: python get_draft.py wxa4f073c32600c19b 47e75b44cbe81261896649aa24a5e222 \"文章标题\"")
sys.exit(1)
appid = sys.argv[1]
secret = sys.argv[2]
target_title = sys.argv[3] if len(sys.argv) > 3 else None
print(f"正在获取微信公众号草稿...")
print(f"AppID: {appid}")
print(f"目标标题: {target_title}\n")
# Step 1: 获取 access_token
access_token = get_access_token(appid, secret)
# Step 2: 获取草稿列表
draft_data = get_draft_list(access_token)
# Step 3: 显示所有草稿标题
print("\n📋 所有草稿列表:")
items = draft_data.get('item', [])
for i, item in enumerate(items, 1):
articles = item.get('content', {}).get('news_item', [])
for article in articles:
print(f" {i}. {article.get('title')}")
print(f" 作者: {article.get('author', '未设置')}")
print(f" 更新时间: {article.get('update_time', '未知')}")
print()
# Step 4: 如果指定了标题,查找并显示详细内容
if target_title:
article, media_id = find_draft_by_title(draft_data, target_title)
if article:
print("\n📄 草稿详细内容:")
print("=" * 80)
print(f"标题: {article.get('title')}")
print(f"作者: {article.get('author', '未设置')}")
print(f"摘要: {article.get('digest', '未设置')}")
print(f"封面 media_id: {article.get('thumb_media_id')}")
print(f"\n正文 HTML 内容:")
print("-" * 80)
print(article.get('content'))
print("-" * 80)
# 将内容保存到文件
output_file = f"draft_{media_id}.html"
with open(output_file, 'w', encoding='utf-8') as f:
f.write(f"""<!-- 文章标题: {article.get('title')} -->
<!-- 作者: {article.get('author', '未设置')} -->
<!-- 摘要: {article.get('digest', '未设置')} -->
<!-- 封面 media_id: {article.get('thumb_media_id')} -->
<!-- 草稿 media_id: {media_id} -->
{article.get('content')}
""")
print(f"\n✅ 草稿内容已保存到: {output_file}")
# 返回 media_id 供后续使用
print(f"\n📌 草稿 media_id: {media_id}")
return media_id
else:
print("\n💡 提示: 使用 python get_draft.py <appid> <secret> \"文章标题\" 查看详细内容")
if __name__ == "__main__":
main()
FILE:scripts/get_draft_by_index.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
获取指定索引的微信公众号草稿脚本
"""
import requests
import json
import sys
import io
# 设置标准输出编码为 UTF-8
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
def get_access_token(appid, secret):
"""获取 access_token"""
url = f"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={secret}"
response = requests.get(url)
result = response.json()
if 'access_token' not in result:
print(f"❌ 获取 access_token 失败")
print(f"错误码: {result.get('errcode')}")
print(f"错误信息: {result.get('errmsg')}")
sys.exit(1)
print(f"✅ 获取 access_token 成功(有效期 {result.get('expires_in')} 秒)")
return result['access_token']
def get_draft_list(access_token):
"""获取草稿列表"""
url = f"https://api.weixin.qq.com/cgi-bin/draft/batchget?access_token={access_token}"
payload = {
"offset": 0,
"count": 20,
"no_content": 0
}
headers = {
'Content-Type': 'application/json'
}
response = requests.post(url, headers=headers, data=json.dumps(payload))
result = response.json()
if result.get('errcode', 0) != 0:
print(f"❌ 获取草稿列表失败")
print(f"错误码: {result.get('errcode')}")
print(f"错误信息: {result.get('errmsg')}")
sys.exit(1)
print(f"✅ 获取草稿列表成功,共 {result.get('total_count')} 篇草稿")
return result
def main():
# 从命令行参数获取 AppID、AppSecret 和草稿索引
if len(sys.argv) < 4:
print("用法: python get_draft_by_index.py <appid> <secret> <index>")
print("示例: python get_draft_by_index.py wxa4f073c32600c19b 47e75b44cbe81261896649aa24a5e222 3")
print("说明: index 从 1 开始,1 表示第一个草稿")
sys.exit(1)
appid = sys.argv[1]
secret = sys.argv[2]
index = int(sys.argv[3]) - 1 # 转换为 0-based 索引
print(f"正在获取微信公众号草稿...")
print(f"AppID: {appid}")
print(f"草稿索引: {index + 1}\n")
# Step 1: 获取 access_token
access_token = get_access_token(appid, secret)
# Step 2: 获取草稿列表
draft_data = get_draft_list(access_token)
# Step 3: 获取指定索引的草稿
items = draft_data.get('item', [])
if index >= len(items):
print(f"❌ 索引超出范围,只有 {len(items)} 篇草稿")
sys.exit(1)
item = items[index]
articles = item.get('content', {}).get('news_item', [])
if not articles:
print(f"❌ 草稿中没有文章")
sys.exit(1)
article = articles[0]
media_id = item.get('media_id')
print("\n📄 草稿详细内容:")
print("=" * 80)
print(f"标题: {article.get('title')}")
print(f"作者: {article.get('author', '未设置')}")
print(f"摘要: {article.get('digest', '未设置')}")
print(f"封面 media_id: {article.get('thumb_media_id')}")
print(f"\n正文 HTML 内容:")
print("-" * 80)
content = article.get('content')
print(content)
print("-" * 80)
# 将内容保存到文件
output_file = f"draft_{media_id}.html"
with open(output_file, 'w', encoding='utf-8') as f:
f.write(f"""<!-- 文章标题: {article.get('title')} -->
<!-- 作者: {article.get('author', '未设置')} -->
<!-- 摘要: {article.get('digest', '未设置')} -->
<!-- 封面 media_id: {article.get('thumb_media_id')} -->
<!-- 草稿 media_id: {media_id} -->
{content}
""")
print(f"\n✅ 草稿内容已保存到: {output_file}")
# 返回 media_id 供后续使用
print(f"\n📌 草稿 media_id: {media_id}")
print(f"📌 封面 media_id: {article.get('thumb_media_id')}")
if __name__ == "__main__":
main()
FILE:scripts/out.txt
[>>] 文章标题:智慧养老正式进入AI时代
[..] 正在获取 access_token...
[OK] 获取 access_token 成功(有效期 7200 秒)
[OK] 使用 Pillow 生成封面:C:\Users\TR\AppData\Local\Temp\tmp8onw7u7t.jpg
[..] 正在上传封面图...
[OK] 封面图上传成功,media_id = oT7S0JNrCAAVZmuf4FebKaOE0VXi4kOKwBp0cegT6buug3xooh_j0rU110g56WTt
[..] 正在转换 Markdown --> 微信公众号 HTML...
[..] 正在创建草稿...
[OK] 草稿创建成功!草稿 media_id = oT7S0JNrCAAVZmuf4FebKU7ChQXcfuczFlbO_PCROzf0pjmLSceeVKlR60lmKNaM
[DONE] 完成!文章《智慧养老正式进入AI时代》已成功上传至草稿箱。
草稿 media_id:oT7S0JNrCAAVZmuf4FebKU7ChQXcfuczFlbO_PCROzf0pjmLSceeVKlR60lmKNaM
请登录微信公众平台 -> 草稿箱查看。
FILE:scripts/run_upload.py
import sys
sys.argv = [
'upload_draft.py',
'--appid', 'wxa4f073c32600c19b',
'--secret', '47e75b44cbe81261896649aa24a5e222',
'--md', r'C:\Users\TR\WorkBuddy\20260319105931\智慧养老正式进入AI时代.md',
'--author', '智慧养老观察家',
]
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
exec(open('upload_draft.py', encoding='utf-8').read())
FILE:scripts/upload_draft.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
微信公众号草稿上传脚本
功能:将 Markdown 文件转换为微信公众号格式,上传封面图并创建草稿
用法:python upload_draft.py --appid <APPID> --secret <SECRET> --md <文章路径> [--cover <封面图路径>] [--author <作者名>] [--digest <摘要>]
"""
import argparse
import json
import os
import re
import sys
import tempfile
import urllib.request
import urllib.parse
import urllib.error
# ─────────────────────────────────────────────
# 1. 工具函数
# ─────────────────────────────────────────────
def http_get(url):
with urllib.request.urlopen(url, timeout=30) as resp:
return json.loads(resp.read().decode("utf-8"))
def http_post_json(url, payload):
data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json; charset=utf-8"})
with urllib.request.urlopen(req, timeout=30) as resp:
return json.loads(resp.read().decode("utf-8"))
def http_post_multipart(url, fields, file_field, file_path, mime_type):
"""multipart/form-data 上传文件(纯标准库实现)"""
boundary = "----WorkBuddyBoundary7MA4YWxkTrZu0gW"
body_parts = []
for name, value in fields.items():
body_parts.append(f'--{boundary}\r\nContent-Disposition: form-data; name="{name}"\r\n\r\n{value}'.encode())
with open(file_path, "rb") as f:
file_data = f.read()
filename = os.path.basename(file_path)
body_parts.append(
f'--{boundary}\r\nContent-Disposition: form-data; name="{file_field}"; filename="{filename}"\r\nContent-Type: {mime_type}\r\n\r\n'.encode()
+ file_data
)
body_parts.append(f"--{boundary}--".encode())
body = b"\r\n".join(body_parts)
req = urllib.request.Request(
url, data=body,
headers={"Content-Type": f"multipart/form-data; boundary={boundary}"}
)
with urllib.request.urlopen(req, timeout=60) as resp:
return json.loads(resp.read().decode("utf-8"))
# ─────────────────────────────────────────────
# 2. 获取 Access Token
# ─────────────────────────────────────────────
def get_access_token(appid, secret):
url = (
f"https://api.weixin.qq.com/cgi-bin/token"
f"?grant_type=client_credential&appid={appid}&secret={secret}"
)
result = http_get(url)
if "access_token" not in result:
raise RuntimeError(f"获取 access_token 失败:{result}")
print(f"[OK] 获取 access_token 成功(有效期 {result.get('expires_in', '?')} 秒)")
return result["access_token"]
# ─────────────────────────────────────────────
# 3. Markdown → 微信公众号 HTML
# ─────────────────────────────────────────────
def md_to_wechat_html(md_text):
"""简易 Markdown 转 HTML,适配微信公众号内联样式"""
lines = md_text.split("\n")
html_lines = []
in_blockquote = False
# 行内样式转换(bold / italic / inline-code / link)
def inline(text):
# 粗体
text = re.sub(r"\*\*(.+?)\*\*", r'<strong style="font-weight:bold;">\1</strong>', text)
text = re.sub(r"__(.+?)__", r'<strong style="font-weight:bold;">\1</strong>', text)
# 斜体
text = re.sub(r"\*(.+?)\*", r'<em>\1</em>', text)
# 行内代码
text = re.sub(r"`(.+?)`", r'<code style="background:#f4f4f4;padding:2px 4px;border-radius:3px;font-size:0.9em;">\1</code>', text)
# 链接
text = re.sub(r"\[(.+?)\]\((.+?)\)", r'<a href="\2">\1</a>', text)
# 水平线
text = re.sub(r"^-{3,}$", r'<hr style="border:none;border-top:1px solid #eee;margin:24px 0;"/>', text)
return text
i = 0
while i < len(lines):
line = lines[i]
# 标题
h_match = re.match(r"^(#{1,6})\s+(.*)", line)
if h_match:
level = len(h_match.group(1))
content = inline(h_match.group(2))
sizes = {1: "1.8em", 2: "1.5em", 3: "1.3em", 4: "1.1em", 5: "1em", 6: "0.9em"}
weights = {1: "bold", 2: "bold", 3: "bold", 4: "bold", 5: "normal", 6: "normal"}
style = f'font-size:{sizes[level]};font-weight:{weights[level]};margin:24px 0 12px;color:#333;'
if level == 2:
style += "border-left:4px solid #07C160;padding-left:10px;"
html_lines.append(f'<h{level} style="{style}">{content}</h{level}>')
i += 1
continue
# 无序列表
if re.match(r"^[-*+]\s+", line):
items = []
while i < len(lines) and re.match(r"^[-*+]\s+", lines[i]):
items.append(f'<li style="margin:6px 0;">{inline(lines[i][2:].strip())}</li>')
i += 1
html_lines.append('<ul style="padding-left:1.5em;margin:12px 0;">' + "".join(items) + "</ul>")
continue
# 有序列表
if re.match(r"^\d+\.\s+", line):
items = []
while i < len(lines) and re.match(r"^\d+\.\s+", lines[i]):
items.append(f'<li style="margin:6px 0;">{inline(re.sub(r"^\d+\.\s+", "", lines[i]))}</li>')
i += 1
html_lines.append('<ol style="padding-left:1.5em;margin:12px 0;">' + "".join(items) + "</ol>")
continue
# 引用块
if line.startswith("> "):
content = inline(line[2:])
html_lines.append(
f'<blockquote style="border-left:4px solid #07C160;margin:12px 0;padding:10px 16px;'
f'background:#f9f9f9;color:#555;">{content}</blockquote>'
)
i += 1
continue
# 分割线
if re.match(r"^-{3,}$", line.strip()) or re.match(r"^\*{3,}$", line.strip()):
html_lines.append('<hr style="border:none;border-top:1px solid #eee;margin:24px 0;"/>')
i += 1
continue
# 空行
if line.strip() == "":
html_lines.append("")
i += 1
continue
# 普通段落
html_lines.append(f'<p style="line-height:1.8;margin:12px 0;color:#333;">{inline(line)}</p>')
i += 1
body = "\n".join(html_lines)
return f"""<section style="font-family:-apple-system,BlinkMacSystemFont,'Helvetica Neue',Arial,sans-serif;font-size:16px;max-width:677px;margin:0 auto;padding:0 16px;color:#333;">
{body}
</section>"""
# ─────────────────────────────────────────────
# 4. 生成默认封面图(纯色 + 文字,无第三方依赖)
# ─────────────────────────────────────────────
def generate_default_cover(title, output_path):
"""尝试用 Pillow 生成封面;若不可用则下载一张免费占位图"""
try:
from PIL import Image, ImageDraw, ImageFont
img = Image.new("RGB", (900, 500), color=(7, 193, 96))
draw = ImageDraw.Draw(img)
# 尝试加载中文字体,失败则用默认字体
font = None
for fp in [
"C:/Windows/Fonts/msyh.ttc",
"C:/Windows/Fonts/simhei.ttf",
"/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",
]:
if os.path.exists(fp):
try:
font = ImageFont.truetype(fp, 48)
break
except Exception:
pass
if font is None:
font = ImageFont.load_default()
# 文字换行
max_chars = 16
words = list(title)
wrapped = []
current = ""
for w in words:
current += w
if len(current) >= max_chars:
wrapped.append(current)
current = ""
if current:
wrapped.append(current)
total_h = len(wrapped) * 60
y = (500 - total_h) // 2
for line in wrapped:
bbox = draw.textbbox((0, 0), line, font=font)
w = bbox[2] - bbox[0]
draw.text(((900 - w) // 2, y), line, fill="white", font=font)
y += 60
img.save(output_path, "JPEG")
print(f"[OK] 使用 Pillow 生成封面:{output_path}")
return output_path
except ImportError:
pass
# Pillow 不可用,下载占位图
placeholder_url = "https://placehold.co/900x500/07C160/FFFFFF/png?text=AI+Smart+Care"
try:
print("[..] Pillow 未安装,正在下载占位封面图...")
urllib.request.urlretrieve(placeholder_url, output_path)
print(f"[OK] 占位封面已下载:{output_path}")
return output_path
except Exception as e:
print(f"[!!] 封面图下载失败({e}),将不使用封面")
return None
# ─────────────────────────────────────────────
# 5. 上传封面图到素材库
# ─────────────────────────────────────────────
def upload_cover_image(access_token, image_path):
url = f"https://api.weixin.qq.com/cgi-bin/material/add_material?access_token={access_token}&type=image"
ext = os.path.splitext(image_path)[1].lower()
mime_map = {".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", ".gif": "image/gif"}
mime = mime_map.get(ext, "image/jpeg")
result = http_post_multipart(url, {}, "media", image_path, mime)
if "media_id" not in result:
raise RuntimeError(f"上传封面图失败:{result}")
print(f"[OK] 封面图上传成功,media_id = {result['media_id']}")
return result["media_id"]
# ─────────────────────────────────────────────
# 6. 创建草稿
# ─────────────────────────────────────────────
def create_draft(access_token, title, content_html, thumb_media_id, author="", digest=""):
url = f"https://api.weixin.qq.com/cgi-bin/draft/add?access_token={access_token}"
article = {
"title": title,
"author": author,
"digest": digest,
"content": content_html,
"thumb_media_id": thumb_media_id,
"need_open_comment": 0,
"only_fans_can_comment": 0,
}
payload = {"articles": [article]}
result = http_post_json(url, payload)
if "media_id" not in result:
raise RuntimeError(f"创建草稿失败:{result}")
print(f"[OK] 草稿创建成功!草稿 media_id = {result['media_id']}")
return result["media_id"]
# ─────────────────────────────────────────────
# 7. 主流程
# ─────────────────────────────────────────────
def main():
parser = argparse.ArgumentParser(description="将 Markdown 文章上传到微信公众号草稿箱")
parser.add_argument("--appid", required=True, help="公众号 AppID")
parser.add_argument("--secret", required=True, help="公众号 AppSecret")
parser.add_argument("--md", required=True, help="Markdown 文件路径")
parser.add_argument("--cover", default=None, help="封面图路径(可选,不提供则自动生成)")
parser.add_argument("--author", default="", help="文章作者(可选)")
parser.add_argument("--digest", default="", help="文章摘要(可选,最多120字)")
args = parser.parse_args()
# 读取 Markdown
if not os.path.isfile(args.md):
print(f"[ERR] 文件不存在:{args.md}")
sys.exit(1)
with open(args.md, "r", encoding="utf-8") as f:
md_text = f.read()
# 提取标题(第一个 # 行)
title_match = re.search(r"^#\s+(.+)", md_text, re.MULTILINE)
title = title_match.group(1).strip() if title_match else os.path.splitext(os.path.basename(args.md))[0]
print(f"[>>] 文章标题:{title}")
# 自动生成摘要(取正文前 100 字)
if not args.digest:
plain = re.sub(r"[#*`>\[\]!]", "", md_text)
plain = re.sub(r"\s+", " ", plain).strip()
args.digest = plain[:100]
# 获取 access_token
print("\n[..] 正在获取 access_token...")
token = get_access_token(args.appid, args.secret)
# 处理封面图
cover_path = args.cover
tmp_cover = None
if not cover_path:
tmp_cover = tempfile.NamedTemporaryFile(suffix=".jpg", delete=False)
tmp_cover.close()
cover_path = generate_default_cover(title, tmp_cover.name)
thumb_media_id = None
if cover_path and os.path.isfile(cover_path):
print("\n[..] 正在上传封面图...")
thumb_media_id = upload_cover_image(token, cover_path)
else:
print("[!!] 无法获取封面图,草稿将使用默认封面(可能导致接口报错)")
# 清理临时文件
if tmp_cover and os.path.exists(tmp_cover.name):
try:
os.unlink(tmp_cover.name)
except Exception:
pass
if not thumb_media_id:
print("[ERR] 缺少封面图 media_id,无法继续创建草稿")
sys.exit(1)
# 转换内容
print("\n[..] 正在转换 Markdown --> 微信公众号 HTML...")
content_html = md_to_wechat_html(md_text)
# 创建草稿
print("\n[..] 正在创建草稿...")
draft_media_id = create_draft(token, title, content_html, thumb_media_id, args.author, args.digest)
print(f"\n[DONE] 完成!文章《{title}》已成功上传至草稿箱。")
print(f" 草稿 media_id:{draft_media_id}")
print(" 请登录微信公众平台 -> 草稿箱查看。")
if __name__ == "__main__":
main()
通过对话即可完成养老机构护理排班,简单快捷
---
name: 智能照护排班
description: 通过对话即可完成养老机构护理排班,简单快捷
---
# 智能照护排班
通过对话就能完成排班,无需打开网页。
## 触发方式
当用户说以下内容时,使用此 skill:
- "帮我生成养老院排班表"
- "创建护理排班"
- "帮我排个班"
- "生成排班表"
## 对话流程
**第1步:询问工作人员**
请告诉我要排班的工作人员姓名,每行一个。
例如:`张三、李四、王五、赵阿姨`
**第2步:确认参数**
- 每人每周上几班?(默认5班)
- 排班周期:一周还是一个月?(默认一周)
- 从哪天开始?(默认下周周一)
**第3步:生成排班**
直接生成排班表,以表格形式展示,并询问是否需要导出CSV。
## 排班规则(自动应用)
- 不连续排班(连续上班后休息1天)
- 夜班后必须休息1天
- 每周班次不超过设定值
- 尽量公平分配
## 班次配置
系统默认配置:
- 早班 07:00-15:00(需要2人)
- 中班 15:00-23:00(需要2人)
- 夜班 23:00-07:00(需要1人)
## 输出格式
排班结果以 Markdown 表格形式展示,包含:日期、班次、时间、值班人员。
## 示例
**用户**:帮我生成排班表
**你**:请告诉我工作人员姓名(每行一个)
**用户**:张护士、李护理员、王护工、赵阿姨、钱大姐、孙护士
**你**:好的,7人排班。一周还是一个月?每人每周几班?从哪天开始?
**用户**:一周,每人5班,从下周一开始
**你**:生成排班表...
| 日期 | 班次 | 时间 | 值班人员 |
|------|------|------|----------|
| 3月17日 周一 | 早班 | 07:00-15:00 | 张护士、李护理员 |
| 3月17日 周一 | 中班 | 15:00-23:00 | 王护工、赵阿姨 |
| ... | ... | ... | ... |
需要导出CSV文件吗?
FILE:assets/app.js
/**
* 智能照护排班系统 - 极简版
* 简单又好用的护理排班工具
*/
// ==================== 数据处理 ====================
const ScheduleApp = {
// 班次配置
shifts: [
{ id: 'morning', name: '早班', time: '07:00-15:00', required: 2 },
{ id: 'afternoon', name: '中班', time: '15:00-23:00', required: 2 },
{ id: 'night', name: '夜班', time: '23:00-07:00', required: 1 },
],
// 排班算法
generateSchedule(staffList, shiftsPerWeek, startDate, cycle) {
const days = cycle === 'week' ? 7 : 30;
const schedule = [];
const staffShifts = {}; // 员工已排班次计数
// 初始化
staffList.forEach(s => staffShifts[s.id] = 0);
// 每天每个班次分配员工
for (let d = 0; d < days; d++) {
const date = this.addDays(startDate, d);
const dayOfWeek = new Date(date).getDay();
// 每周限制
const weekNum = Math.floor(d / 7);
const weekStart = weekNum * 7;
const weekShifts = {}; // 本周每人的班次
for (const shift of this.shifts) {
const assigned = [];
// 筛选可用员工
let available = staffList.filter(s => {
// 检查周上限
const weekShiftCount = Object.entries(staffShifts)
.filter(([sid]) => sid.startsWith(s.id.split('-')[0]))
.reduce((sum, [, count]) => sum + count, 0);
if (weekShiftCount >= shiftsPerWeek) return false;
if (s.available && !s.available.includes(dayOfWeek)) return false;
// 检查是否连续排班
const lastDate = s.lastDate;
if (lastDate && this.isConsecutive(lastDate, date)) return false;
// 检查夜班后休息
if (shift.id === 'morning' && s.lastShift === 'night') return false;
return true;
});
// 按顺序分配
available.sort((a, b) => (staffShifts[a.id] || 0) - (staffShifts[b.id] || 0));
for (const s of available) {
if (assigned.length >= shift.required) break;
assigned.push(s);
}
// 记录
for (const s of assigned) {
staffShifts[s.id] = (staffShifts[s.id] || 0) + 1;
s.lastDate = date;
s.lastShift = shift.id;
}
if (assigned.length > 0) {
schedule.push({
date,
shift: shift.name,
shiftId: shift.id,
time: shift.time,
staff: assigned.map(s => s.name).join('、')
});
}
}
}
return schedule;
},
addDays(date, days) {
const d = new Date(date);
d.setDate(d.getDate() + days);
return d.toISOString().split('T')[0];
},
isConsecutive(d1, d2) {
const diff = new Date(d2).getTime() - new Date(d1).getTime();
return diff <= 86400000 * 2; // 48小时内
},
formatDate(dateStr) {
const d = new Date(dateStr);
const weeks = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
return `d.getMonth() + 1月d.getDate()日 weeks[d.getDay()]`;
},
// 导出CSV
exportCSV(schedule, filename) {
const rows = [['日期', '班次', '时间', '值班人员']];
schedule.forEach(s => rows.push([s.date, s.shift, s.time, s.staff]));
const csv = rows.map(r => r.join(',')).join('\n');
const blob = new Blob(['\ufeff' + csv], { type: 'text/csv;charset=utf-8' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
}
};
// ==================== 界面渲染 ====================
const App = {
step: 1, // 当前步骤
staffInput: '', // 员工输入
shiftsPerWeek: 5, // 每周班次
cycle: 'week', // 排班周期
startDate: '', // 开始日期
schedule: [], // 排班结果
apiKey: '', // API Key
// 预设示例员工
demoStaff: `张护士
李护理员
王护工
赵阿姨
钱大姐
孙护士
周护理员`,
init() {
const today = new Date();
const monday = new Date(today);
monday.setDate(today.getDate() - today.getDay() + 1);
this.startDate = monday.toISOString().split('T')[0];
// 自动填入示例员工
this.staffInput = this.demoStaff;
this.render();
// 3秒后自动跳到最后一步生成
setTimeout(() => this.autoGenerate(), 1500);
},
async autoGenerate() {
try {
await this.generate();
} catch(e) {
console.log('自动生成:', e.message);
}
},
render() {
const html = `
<div class="container">
<div class="header">
<h1>🏥 智能照护排班</h1>
<p class="subtitle">简单又好用的排班工具</p>
</div>
<div class="progress">
<div class="step ''">1. 员工</div>
<div class="step ''">2. 规则</div>
<div class="step ''">3. 周期</div>
<div class="step ''">4. 生成</div>
</div>
<div class="card">
this.renderStep()
</div>
<div class="nav-buttons">
''
''
</div>
</div>
`;
document.getElementById('app').innerHTML = html;
},
renderStep() {
switch(this.step) {
case 1: return this.renderStaffStep();
case 2: return this.renderRuleStep();
case 3: return this.renderCycleStep();
case 4: return this.renderGenerateStep();
default: return '';
}
},
// 步骤1: 输入员工
renderStaffStep() {
return `
<div class="step-content">
<h2>👥 添加工作人员</h2>
<p class="hint">请输入工作人员姓名或编号,每行一个</p>
<textarea
id="staffInput"
rows="8"
placeholder="例如:
张护士
李护理员
王护工
赵阿姨
钱大姐">this.staffInput</textarea>
<div class="example">
<span>💡 提示:可以用编号+姓名,如 "001 张护士"</span>
</div>
</div>
`;
},
// 步骤2: 设置规则
renderRuleStep() {
return `
<div class="step-content">
<h2>⚙️ 排班规则</h2>
<div class="form-group">
<label>每人每周上班 <strong>this.shiftsPerWeek</strong> 班</label>
<input type="range" min="2" max="7" value="this.shiftsPerWeek"
oninput="App.shiftsPerWeek = this.value; this.previousElementSibling.innerHTML = '每人每周上班 <strong>' + this.value + '</strong> 班'; App.render()">
</div>
<div class="rules-preview">
<div class="rule-item enabled">
<span class="check">✓</span>
<span>不连续排班(休息一天)</span>
</div>
<div class="rule-item enabled">
<span class="check">✓</span>
<span>夜班后休息一天</span>
</div>
<div class="rule-item enabled">
<span class="check">✓</span>
<span>每周班次不超过设定值</span>
</div>
</div>
</div>
`;
},
// 步骤3: 设置周期
renderCycleStep() {
const dateStr = new Date(this.startDate).toLocaleDateString('zh-CN', {
year: 'numeric', month: 'long', day: 'numeric'
});
return `
<div class="step-content">
<h2>📅 排班周期</h2>
<div class="form-group">
<label>开始日期</label>
<input type="date" value="this.startDate"
onchange="App.startDate = this.value; App.render()">
<span class="date-preview">dateStr</span>
</div>
<div class="form-group">
<label>排班周期</label>
<div class="radio-group">
<label class="radio ''">
<input type="radio" name="cycle" value="week" ''
onchange="App.cycle = 'week'; App.render()">
<span>📅 一周</span>
</label>
<label class="radio ''">
<input type="radio" name="cycle" value="month" ''
onchange="App.cycle = 'month'; App.render()">
<span>📆 一个月</span>
</label>
</div>
</div>
<div class="summary">
<h3>📋 排班概要</h3>
<p>周期:'一个月(30天)'</p>
<p>开始:dateStr</p>
</div>
</div>
`;
},
// 步骤4: 生成排班
renderGenerateStep() {
const staffCount = this.staffInput.split('\n').filter(s => s.trim()).length;
const days = this.cycle === 'week' ? 7 : 30;
const shiftCount = this.shifts.length;
const totalShifts = days * shiftCount;
return `
<div class="step-content">
<h2>🤖 生成排班</h2>
<div class="form-group">
<label>通义千问 API Key(可选)</label>
<input type="password" id="apiKey" placeholder="不填则使用本地算法" value="this.apiKey">
<small>申请地址:dashscope.console.aliyun.com</small>
</div>
<div class="info-box">
<h3>📊 排班信息</h3>
<p>👥 工作人员:<strong>staffCount 人</strong></p>
<p>📅 排班天数:<strong>days 天</strong></p>
<p>⏰ 班次数量:<strong>shiftCount 个/天</strong></p>
<p>📈 总班次:<strong>totalShifts 班</strong></p>
</div>
`
<button class="btn btn-generate" onclick="App.generate()">
✨ 智能生成排班表
</button>
`
</div>
`;
},
renderSchedule() {
const days = this.cycle === 'week' ? 7 : 30;
const dates = [];
for (let i = 0; i < days; i++) {
const d = new Date(this.startDate);
d.setDate(d.getDate() + i);
dates.push(d.toISOString().split('T')[0]);
}
let html = `
<div class="schedule-result">
<div class="result-header">
<h3>✅ 排班结果</h3>
<button class="btn btn-small" onclick="App.export()">📥 导出CSV</button>
</div>
<table class="schedule-table">
<thead>
<tr>
<th>日期</th>
<th>班次</th>
<th>时间</th>
<th>值班人员</th>
</tr>
</thead>
<tbody>
`;
dates.forEach(date => {
const dayShifts = this.schedule.filter(s => s.date === date);
if (dayShifts.length > 0) {
dayShifts.forEach((s, i) => {
html += `
<tr>
i === 0 ? `<td rowspan="${dayShifts.length" class="date-col">ScheduleApp.formatDate(date)</td>` : ''}
<td><span class="shift-tag">s.shift</span></td>
<td>s.time</td>
<td>s.staff</td>
</tr>
`;
});
}
});
html += `</tbody></table></div>`;
return html;
},
// 下一步
nextStep() {
if (this.step === 1) {
this.staffInput = document.getElementById('staffInput')?.value || '';
if (!this.staffInput.trim()) {
alert('请输入工作人员姓名');
return;
}
}
if (this.step === 3) {
this.apiKey = document.getElementById('apiKey')?.value || '';
}
this.step++;
this.render();
},
// 上一步
prevStep() {
this.step--;
this.render();
},
// 生成排班
async generate() {
const btn = document.querySelector('.btn-generate');
btn.textContent = '⏳ 生成中...';
btn.disabled = true;
try {
// 解析员工
const staffLines = this.staffInput.split('\n').filter(s => s.trim());
const staff = staffLines.map((line, i) => {
const parts = line.split(/\s+/);
const id = parts[0] || (i + 1).toString();
const name = parts[1] || line;
return {
id: id.toString(),
name: name,
available: [0,1,2,3,4,5,6] // 每天可用
};
});
const days = this.cycle === 'week' ? 7 : 30;
// 使用AI或本地算法
if (this.apiKey) {
this.schedule = await this.generateWithAI(staff, days);
} else {
await new Promise(r => setTimeout(r, 500));
this.schedule = ScheduleApp.generateSchedule(
staff,
this.shiftsPerWeek,
this.startDate,
this.cycle
);
}
this.render();
} catch (e) {
alert('生成失败: ' + e.message);
btn.textContent = '✨ 智能生成排班表';
btn.disabled = false;
}
},
// AI生成(简化版)
async generateWithAI(staff, days) {
const shifts = ScheduleApp.shifts;
const prompt = `
请为养老机构生成days天的排班表。
员工:staff.map(s => s.name).join('、')
每人每周this.shiftsPerWeek班。
开始日期:this.startDate
班次:
- 早班 07:00-15:00 需要2人
- 中班 15:00-23:00 需要2人
- 夜班 23:00-07:00 需要1人
规则:
1. 不连续排班
2. 夜班后休息一天
3. 每周不超this.shiftsPerWeek班
请生成JSON格式:
[{"date": "2024-01-01", "shift": "早班", "time": "07:00-15:00", "staff": "张三、李四"}]
`;
const resp = await fetch('https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer this.apiKey`
},
body: JSON.stringify({
model: 'qwen-plus',
messages: [
{ role: 'system', content: '你是排班专家,请严格按照JSON数组格式返回结果。' },
{ role: 'user', content: prompt }
]
})
});
const json = await resp.json();
const content = json.choices?.[0]?.message?.content || '';
const match = content.match(/\[[\s\S]*\]/);
if (!match) throw new Error('无法解析结果');
return JSON.parse(match[0]);
},
// 导出
export() {
const filename = `排班表_this.startDate_'一月'.csv`;
ScheduleApp.exportCSV(this.schedule, filename);
}
};
// 启动
document.addEventListener('DOMContentLoaded', () => App.init());
FILE:assets/index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>智能照护排班 - 简单好用</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div id="app"></div>
<script src="app.js"></script>
</body>
</html>
FILE:assets/styles.css
/* 智能照护排班 - 极简版样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 700px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2rem;
font-weight: 700;
margin-bottom: 8px;
}
.subtitle {
opacity: 0.9;
font-size: 1.1rem;
}
/* 进度条 */
.progress {
display: flex;
justify-content: center;
gap: 8px;
margin-bottom: 24px;
}
.step {
background: rgba(255,255,255,0.2);
color: rgba(255,255,255,0.7);
padding: 8px 20px;
border-radius: 20px;
font-size: 0.9rem;
transition: all 0.3s;
}
.step.active {
background: white;
color: #667eea;
font-weight: 600;
}
/* 卡片 */
.card {
background: white;
border-radius: 16px;
padding: 32px;
box-shadow: 0 20px 60px rgba(0,0,0,0.15);
}
.step-content h2 {
color: #333;
margin-bottom: 24px;
font-size: 1.4rem;
}
.hint {
color: #666;
margin-bottom: 16px;
}
/* 输入框 */
textarea, input[type="text"], input[type="password"], input[type="date"] {
width: 100%;
padding: 14px 16px;
border: 2px solid #e0e0e0;
border-radius: 10px;
font-size: 1rem;
transition: border-color 0.2s;
}
textarea:focus, input:focus {
outline: none;
border-color: #667eea;
}
textarea {
resize: vertical;
min-height: 150px;
font-family: inherit;
}
.example {
margin-top: 12px;
padding: 12px;
background: #f5f5f5;
border-radius: 8px;
font-size: 0.9rem;
color: #666;
}
/* 表单组 */
.form-group {
margin-bottom: 24px;
}
.form-group label {
display: block;
margin-bottom: 10px;
color: #333;
font-weight: 500;
}
.form-group small {
display: block;
margin-top: 6px;
color: #888;
font-size: 0.85rem;
}
/* 滑块 */
input[type="range"] {
width: 100%;
height: 8px;
-webkit-appearance: none;
background: #e0e0e0;
border-radius: 4px;
margin-top: 10px;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 24px;
height: 24px;
background: #667eea;
border-radius: 50%;
cursor: pointer;
}
/* 规则预览 */
.rules-preview {
background: #f9f9f9;
border-radius: 12px;
padding: 20px;
margin-top: 20px;
}
.rule-item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 0;
color: #666;
}
.rule-item .check {
width: 22px;
height: 22px;
background: #4CAF50;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.8rem;
}
/* 单选按钮 */
.radio-group {
display: flex;
gap: 16px;
}
.radio {
flex: 1;
cursor: pointer;
}
.radio input {
display: none;
}
.radio span {
display: block;
padding: 16px;
text-align: center;
border: 2px solid #e0e0e0;
border-radius: 10px;
transition: all 0.2s;
}
.radio.checked span {
border-color: #667eea;
background: #f0f0ff;
color: #667eea;
font-weight: 600;
}
/* 日期预览 */
.date-preview {
display: block;
margin-top: 8px;
color: #666;
font-size: 0.95rem;
}
/* 摘要 */
.summary {
background: #f0f5ff;
border-radius: 12px;
padding: 20px;
margin-top: 20px;
}
.summary h3 {
color: #333;
margin-bottom: 12px;
}
.summary p {
color: #555;
padding: 6px 0;
}
/* 信息框 */
.info-box {
background: #f9f9f9;
border-radius: 12px;
padding: 20px;
margin-bottom: 24px;
}
.info-box h3 {
color: #333;
margin-bottom: 16px;
}
.info-box p {
color: #555;
padding: 8px 0;
display: flex;
justify-content: space-between;
}
.info-box strong {
color: #667eea;
}
/* 按钮 */
.nav-buttons {
display: flex;
justify-content: center;
gap: 16px;
margin-top: 24px;
}
.btn {
padding: 14px 32px;
border: none;
border-radius: 10px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5568d3;
}
.btn-secondary {
background: white;
color: #667eea;
}
.btn-secondary:hover {
background: #f5f5f5;
}
.btn-generate {
width: 100%;
padding: 18px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
font-size: 1.1rem;
border: none;
border-radius: 12px;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn-generate:hover {
transform: translateY(-2px);
box-shadow: 0 10px 30px rgba(102,126,234,0.4);
}
.btn-generate:disabled {
opacity: 0.7;
cursor: not-allowed;
}
.btn-small {
padding: 8px 16px;
background: #667eea;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 0.9rem;
}
/* 排班结果 */
.schedule-result {
margin-top: 20px;
}
.result-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.result-header h3 {
color: #333;
}
.schedule-table {
width: 100%;
border-collapse: collapse;
font-size: 0.9rem;
}
.schedule-table th,
.schedule-table td {
padding: 12px 8px;
text-align: left;
border-bottom: 1px solid #eee;
}
.schedule-table th {
background: #f5f5f5;
font-weight: 600;
color: #333;
}
.schedule-table .date-col {
white-space: nowrap;
color: #666;
}
.shift-tag {
display: inline-block;
padding: 4px 10px;
background: #e8f4ff;
color: #2196F3;
border-radius: 4px;
font-size: 0.85rem;
}
/* 响应式 */
@media (max-width: 600px) {
body {
padding: 12px;
}
.header h1 {
font-size: 1.6rem;
}
.card {
padding: 20px;
}
.progress {
flex-wrap: wrap;
}
.step {
padding: 6px 12px;
font-size: 0.8rem;
}
.radio-group {
flex-direction: column;
}
}
FILE:generate.py
# 快速测试脚本
staff = ['张护士', '李护理员', '王护工', '赵阿姨', '钱大姐', '孙护士', '周护理员']
from datetime import datetime, timedelta
class G:
SHIFTS = [
{'id': 'morning', 'name': '早班', 'start': '07:00', 'end': '15:00', 'required': 2},
{'id': 'afternoon', 'name': '中班', 'start': '15:00', 'end': '23:00', 'required': 2},
{'id': 'night', 'name': '夜班', 'start': '23:00', 'end': '07:00', 'required': 1},
]
WEEKDAYS = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
def __init__(self, staff_list, shifts_per_week=5, cycle='week'):
self.staff = [{'name': n, 'shifts': 0, 'last_date': None, 'last_shift': None} for n in staff_list]
self.spw = shifts_per_week
self.days = 7 if cycle == 'week' else 30
today = datetime.now()
days_until_monday = (7 - today.weekday()) % 7
if days_until_monday == 0: days_until_monday = 7
self.start = today + timedelta(days=days_until_monday)
def gen(self):
r = []
for d in range(self.days):
cd = self.start + timedelta(days=d)
ds = cd.strftime('%Y-%m-%d')
wk = cd.weekday()
for sh in self.SHIFTS:
av = [s for s in self.staff if s['shifts'] < self.spw]
av.sort(key=lambda x: x['shifts'])
asd = []
for s in av:
if len(asd) >= sh['required']: break
asd.append(s['name'])
s['shifts'] += 1
s['last_date'] = ds
s['last_shift'] = sh['id']
if asd:
r.append({'date': ds, 'weekday': self.WEEKDAYS[wk], 'shift': sh['name'], 'time': f"{sh['start']}-{sh['end']}", 'staff': '、'.join(asd)})
return r
g = G(staff, 5, 'week')
s = g.gen()
print('| 日期 | 班次 | 时间 | 值班人员 |')
print('|------|------|------|----------|')
for x in s: print(f"| {x['date']} {x['weekday']} | {x['shift']} | {x['time']} | {x['staff']} |")
FILE:scheduler.py
"""
智能照护排班工具
纯对话式排班生成器
"""
import json
import csv
from datetime import datetime, timedelta
class CareScheduleGenerator:
"""养老机构排班生成器"""
# 默认班次配置
SHIFTS = [
{"id": "morning", "name": "早班", "start": "07:00", "end": "15:00", "required": 2},
{"id": "afternoon", "name": "中班", "start": "15:00", "end": "23:00", "required": 2},
{"id": "night", "name": "夜班", "start": "23:00", "end": "07:00", "required": 1},
]
WEEKDAYS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
def __init__(self, staff_list, shifts_per_week=5, cycle="week", start_date=None):
"""
初始化排班器
:param staff_list: 员工姓名列表
:param shifts_per_week: 每人每周班次
:param cycle: 周期 "week" 或 "month"
:param start_date: 开始日期字符串 YYYY-MM-DD
"""
self.staff = [{"name": name, "shifts": 0, "last_date": None, "last_shift": None}
for name in staff_list]
self.shifts_per_week = shifts_per_week
self.cycle = cycle
self.days = 7 if cycle == "week" else 30
if start_date:
self.start_date = datetime.strptime(start_date, "%Y-%m-%d")
else:
# 默认下周周一
today = datetime.now()
days_until_monday = (7 - today.weekday()) % 7
if days_until_monday == 0:
days_until_monday = 7
self.start_date = today + timedelta(days=days_until_monday)
def generate(self):
"""生成排班表"""
schedule = []
for day in range(self.days):
current_date = self.start_date + timedelta(days=day)
date_str = current_date.strftime("%Y-%m-%d")
weekday = current_date.weekday()
for shift in self.SHIFTS:
assigned = self._assign_staff(shift, weekday, date_str)
if assigned:
schedule.append({
"date": date_str,
"weekday": self.WEEKDAYS[weekday],
"shift": shift["name"],
"time": f"{shift['start']}-{shift['end']}",
"staff": "、".join(assigned)
})
return schedule
def _assign_staff(self, shift, weekday, date_str):
"""为班次分配员工"""
assigned = []
# 筛选可用员工
available = []
for s in self.staff:
# 检查周班次上限
week_num = (datetime.strptime(date_str, "%Y-%m-%d") - self.start_date).days // 7
week_start = self.start_date + timedelta(days=week_num * 7)
week_end = week_start + timedelta(days=6)
# 统计本周已上班次
week_shifts = sum(1 for ss in self.staff
if ss.get("week_num", -1) == week_num and ss.get("shifts", 0) > 0)
if s["shifts"] >= self.shifts_per_week:
continue
# 检查是否连续排班
if s["last_date"]:
last = datetime.strptime(s["last_date"], "%Y-%m-%d")
current = datetime.strptime(date_str, "%Y-%m-%d")
if (current - last).days < 2: # 48小时内连续
continue
# 检查夜班后休息
if shift["id"] == "morning" and s.get("last_shift") == "night":
continue
# 按已上班次排序,少的优先
available.append(s)
available.sort(key=lambda x: x["shifts"])
# 分配员工
for s in available:
if len(assigned) >= shift["required"]:
break
assigned.append(s["name"])
s["shifts"] += 1
s["last_date"] = date_str
s["last_shift"] = shift["id"]
return assigned
def format_markdown(self, schedule):
"""格式化为 Markdown 表格"""
lines = ["| 日期 | 班次 | 时间 | 值班人员 |",
"|------|------|------|----------|"]
current_date = ""
for s in schedule:
date_display = f"{s['date']} {s['weekday']}"
if s['date'] == current_date:
date_display = ""
else:
current_date = s['date']
lines.append(f"| {date_display} | {s['shift']} | {s['time']} | {s['staff']} |")
return "\n".join(lines)
def export_csv(self, schedule, filename):
"""导出 CSV 文件"""
with open(filename, 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.writer(f)
writer.writerow(['日期', '星期', '班次', '时间', '值班人员'])
for s in schedule:
writer.writerow([s['date'], s['weekday'], s['shift'], s['time'], s['staff']])
return filename
def quick_schedule(staff_str, shifts_per_week=5, cycle="week", start_date=None):
"""
快速生成排班表
:param staff_str: 员工姓名,逗号或换行分隔
:param shifts_per_week: 每人每周班次
:param cycle: "week" 或 "month"
:param start_date: 开始日期 YYYY-MM-DD
:return: (markdown表格, csv文件路径)
"""
# 解析员工列表
staff_list = [s.strip() for s in staff_str.replace('\n', ',').split(',') if s.strip()]
# 生成排班
generator = CareScheduleGenerator(staff_list, shifts_per_week, cycle, start_date)
schedule = generator.generate()
# 格式化输出
markdown = generator.format_markdown(schedule)
# 导出CSV
date_str = generator.start_date.strftime("%Y%m%d")
csv_file = f"排班表_{date_str}_{cycle}.csv"
generator.export_csv(schedule, csv_file)
return markdown, csv_file
if __name__ == "__main__":
# 测试
staff = "张护士,李护理员,王护工,赵阿姨,钱大姐,孙护士,周护理员"
md, csv_file = quick_schedule(staff, shifts_per_week=5, cycle="week")
print(md)
print(f"\n已导出: {csv_file}")