YutoAIYutoAIPrompts
PromptsSkillsWorkflowsCategoriesTagsPromptmasters
Developers
Login
Prompts Awesome by YutoAI © 2021-2026
S

Sean zheng

@clawhub-cnspica-e89c13b169

3prompts
0upvotes received
0contributions
Joined 4 months ago
3 contributions in the last year
Sep
Oct
Nov
Dec
Jan
Feb
Mar
Apr
May
Jun
Jul
Aug
Sep
M
W
F
Less
More
Silver Daily
Skill

银发头条(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">
    国务院部署银发消费补贴扩围 &nbsp;·&nbsp;
    养老机器人春晚后订单翻倍 &nbsp;·&nbsp;
    旅居养老今年迎两会政策红利 &nbsp;·&nbsp;
    长护险新文书2026版正式实施 &nbsp;·&nbsp;
    成都老博会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()
ClawHubCodingFrontend+2
S@clawhub-cnspica-e89c13b169
0
"微信公众号草稿上传"
Skill

将本地 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()
ClawHubCodingFrontend+2
S@clawhub-cnspica-e89c13b169
0
智能照护排班
Skill

通过对话即可完成养老机构护理排班,简单快捷

---
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}")
ClawHubFrontendBackend+2
S@clawhub-cnspica-e89c13b169
0