---
title: 系列卡 SeriesCard
url: https://ui.zhaozimin.cn/components/series-card/
markdown: https://ui.zhaozimin.cn/components/series-card.md
group: 组件
origin: 设计系统源码（src/system），本仓库是唯一源头
source:
  - src/system/screens/v4/course/SeriesCard.jsx  # https://ui.zhaozimin.cn/src/system/screens/v4/course/SeriesCard.jsx
version: v0.2.0  # 设计系统版本，发布于 2026-09-23
scope: .zzm-v4   # 根节点必须带这个类，否则样式不生效
css: https://ui.zhaozimin.cn/v/0.2.0/zzm.css   # 锁版本地址，本仓库再改也不影响你
---

# 系列卡 SeriesCard

> 课程总览里的一个系列：头图、名称、两行简介、节数与总时长，再加一排每课一格的进度方块——完成的格子是朱色打勾。

### 样张 · 完成 3 / 6

总时长由各课 duration_minutes 求和

React：

```jsx
import { SeriesCard } from './screens/v4/course/SeriesCard.jsx'

export function Progress() {
  return (
    <div style={{ width: 300 }}>
      <SeriesCard cat={CAT} hist={HIST} />
    </div>
  )
}
```

HTML（构建时由上面的 React 渲染得到，可直接粘贴）：

```html
<div style="width: 300px">
  <div
    class="zzm-hovercard"
    style="
      border-radius: 16px;
      background: #ffffff;
      overflow: hidden;
      border: 1px solid rgba(0, 0, 0, 0.09);
      cursor: pointer;
      box-shadow: none;
    "
  >
    <div style="aspect-ratio: 16/9; background: #131316; position: relative; overflow: hidden">
      <svg
        viewBox="0 0 240 135"
        preserveAspectRatio="xMidYMid slice"
        style="position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0.5"
      >
        <polyline
          points="18,110 62,48 120,92 178,40 224,104"
          fill="none"
          stroke="rgba(255,255,255,.09)"
          stroke-width="1"
        ></polyline>
        <circle cx="62" cy="48" r="2" fill="rgba(255,255,255,.18)"></circle>
        <circle cx="120" cy="92" r="2" fill="rgba(255,255,255,.18)"></circle>
        <circle cx="178" cy="40" r="2" fill="rgba(255,255,255,.18)"></circle>
      </svg>
    </div>
    <div style="padding: 15px 17px 16px">
      <div style="font-size: 16px; font-weight: 650">笔记系统</div>
      <div
        style="
          margin-top: 5px;
          font-size: 12.5px;
          line-height: 1.6;
          color: #72726e;
          display: -webkit-box;
          -webkit-line-clamp: 2;
          -webkit-box-orient: vertical;
          overflow: hidden;
        "
      >
        从收集到输出的流水线：卡片盒方法论、标签与检索、从收集到内化、让笔记长成文章。
      </div>
      <div
        style="
          margin-top: 10px;
          display: flex;
          align-items: center;
          gap: 8px;
          font-family: var(--zzm-mono);
          font-size: 10.5px;
          color: #8b8b85;
        "
      >
        <span>6 节课</span>
        <span aria-hidden="true">·</span>
        <span>总时长 1 小时 24 分</span>
      </div>
      <div style="margin-top: 12px">
        <div style="display: flex; flex-wrap: wrap; gap: 4px">
          <span
            title="第 1 节 · 已完成"
            style="
              width: 16px;
              height: 16px;
              border-radius: 4px;
              background: var(--zzm-accent, #a6402f);
              border: 1px solid var(--zzm-accent, #a6402f);
              display: flex;
              align-items: center;
              justify-content: center;
              color: #ffffff;
              font-size: 10px;
              line-height: 1;
            "
          >
            ✓
          </span>
          <span
            title="第 2 节 · 已完成"
            style="
              width: 16px;
              height: 16px;
              border-radius: 4px;
              background: var(--zzm-accent, #a6402f);
              border: 1px solid var(--zzm-accent, #a6402f);
              display: flex;
              align-items: center;
              justify-content: center;
              color: #ffffff;
              font-size: 10px;
              line-height: 1;
            "
          >
            ✓
          </span>
          <span
            title="第 3 节 · 已完成"
            style="
              width: 16px;
              height: 16px;
              border-radius: 4px;
              background: var(--zzm-accent, #a6402f);
              border: 1px solid var(--zzm-accent, #a6402f);
              display: flex;
              align-items: center;
              justify-content: center;
              color: #ffffff;
              font-size: 10px;
              line-height: 1;
            "
          >
            ✓
          </span>
          <span
            title="第 4 节"
            style="
              width: 16px;
              height: 16px;
              border-radius: 4px;
              background: transparent;
              border: 1px solid rgba(0, 0, 0, 0.14);
              display: flex;
              align-items: center;
              justify-content: center;
              color: #ffffff;
              font-size: 10px;
              line-height: 1;
            "
          ></span>
          <span
            title="第 5 节"
            style="
              width: 16px;
              height: 16px;
              border-radius: 4px;
              background: transparent;
              border: 1px solid rgba(0, 0, 0, 0.14);
              display: flex;
              align-items: center;
              justify-content: center;
              color: #ffffff;
              font-size: 10px;
              line-height: 1;
            "
          ></span>
          <span
            title="第 6 节"
            style="
              width: 16px;
              height: 16px;
              border-radius: 4px;
              background: transparent;
              border: 1px solid rgba(0, 0, 0, 0.14);
              display: flex;
              align-items: center;
              justify-content: center;
              color: #ffffff;
              font-size: 10px;
              line-height: 1;
            "
          ></span>
        </div>
        <div style="margin-top: 7px; font-family: var(--zzm-mono); font-size: 10px; color: #a2a29c">
          已完成 3/6
        </div>
      </div>
    </div>
  </div>
</div>
```

### 样张 · 选中态

朱色 1px 描边 + 1px 外环

React：

```jsx
import { SeriesCard } from './screens/v4/course/SeriesCard.jsx'

export function Selected() {
  return (
    <div style={{ width: 300 }}>
      <SeriesCard cat={CAT} hist={HIST} selected />
    </div>
  )
}
```

HTML（构建时由上面的 React 渲染得到，可直接粘贴）：

```html
<div style="width: 300px">
  <div
    class="zzm-hovercard"
    style="
      border-radius: 16px;
      background: #ffffff;
      overflow: hidden;
      border: 1px solid var(--zzm-accent, #a6402f);
      cursor: pointer;
      box-shadow: 0 0 0 1px var(--zzm-accent, #a6402f);
    "
  >
    <div style="aspect-ratio: 16/9; background: #131316; position: relative; overflow: hidden">
      <svg
        viewBox="0 0 240 135"
        preserveAspectRatio="xMidYMid slice"
        style="position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0.5"
      >
        <polyline
          points="18,110 62,48 120,92 178,40 224,104"
          fill="none"
          stroke="rgba(255,255,255,.09)"
          stroke-width="1"
        ></polyline>
        <circle cx="62" cy="48" r="2" fill="rgba(255,255,255,.18)"></circle>
        <circle cx="120" cy="92" r="2" fill="rgba(255,255,255,.18)"></circle>
        <circle cx="178" cy="40" r="2" fill="rgba(255,255,255,.18)"></circle>
      </svg>
    </div>
    <div style="padding: 15px 17px 16px">
      <div style="font-size: 16px; font-weight: 650">笔记系统</div>
      <div
        style="
          margin-top: 5px;
          font-size: 12.5px;
          line-height: 1.6;
          color: #72726e;
          display: -webkit-box;
          -webkit-line-clamp: 2;
          -webkit-box-orient: vertical;
          overflow: hidden;
        "
      >
        从收集到输出的流水线：卡片盒方法论、标签与检索、从收集到内化、让笔记长成文章。
      </div>
      <div
        style="
          margin-top: 10px;
          display: flex;
          align-items: center;
          gap: 8px;
          font-family: var(--zzm-mono);
          font-size: 10.5px;
          color: #8b8b85;
        "
      >
        <span>6 节课</span>
        <span aria-hidden="true">·</span>
        <span>总时长 1 小时 24 分</span>
      </div>
      <div style="margin-top: 12px">
        <div style="display: flex; flex-wrap: wrap; gap: 4px">
          <span
            title="第 1 节 · 已完成"
            style="
              width: 16px;
              height: 16px;
              border-radius: 4px;
              background: var(--zzm-accent, #a6402f);
              border: 1px solid var(--zzm-accent, #a6402f);
              display: flex;
              align-items: center;
              justify-content: center;
              color: #ffffff;
              font-size: 10px;
              line-height: 1;
            "
          >
            ✓
          </span>
          <span
            title="第 2 节 · 已完成"
            style="
              width: 16px;
              height: 16px;
              border-radius: 4px;
              background: var(--zzm-accent, #a6402f);
              border: 1px solid var(--zzm-accent, #a6402f);
              display: flex;
              align-items: center;
              justify-content: center;
              color: #ffffff;
              font-size: 10px;
              line-height: 1;
            "
          >
            ✓
          </span>
          <span
            title="第 3 节 · 已完成"
            style="
              width: 16px;
              height: 16px;
              border-radius: 4px;
              background: var(--zzm-accent, #a6402f);
              border: 1px solid var(--zzm-accent, #a6402f);
              display: flex;
              align-items: center;
              justify-content: center;
              color: #ffffff;
              font-size: 10px;
              line-height: 1;
            "
          >
            ✓
          </span>
          <span
            title="第 4 节"
            style="
              width: 16px;
              height: 16px;
              border-radius: 4px;
              background: transparent;
              border: 1px solid rgba(0, 0, 0, 0.14);
              display: flex;
              align-items: center;
              justify-content: center;
              color: #ffffff;
              font-size: 10px;
              line-height: 1;
            "
          ></span>
          <span
            title="第 5 节"
            style="
              width: 16px;
              height: 16px;
              border-radius: 4px;
              background: transparent;
              border: 1px solid rgba(0, 0, 0, 0.14);
              display: flex;
              align-items: center;
              justify-content: center;
              color: #ffffff;
              font-size: 10px;
              line-height: 1;
            "
          ></span>
          <span
            title="第 6 节"
            style="
              width: 16px;
              height: 16px;
              border-radius: 4px;
              background: transparent;
              border: 1px solid rgba(0, 0, 0, 0.14);
              display: flex;
              align-items: center;
              justify-content: center;
              color: #ffffff;
              font-size: 10px;
              line-height: 1;
            "
          ></span>
        </div>
        <div style="margin-top: 7px; font-family: var(--zzm-mono); font-size: 10px; color: #a2a29c">
          已完成 3/6
        </div>
      </div>
    </div>
  </div>
</div>
```

### 样张 · 骨架态

热力格下方「已完成 N/M」行也有等高占位，数据到达时不跳

React：

```jsx
import { SeriesCard } from './screens/v4/course/SeriesCard.jsx'

export function Loading() {
  return (
    <div style={{ width: 300 }}>
      <SeriesCard loading />
    </div>
  )
}
```

HTML（构建时由上面的 React 渲染得到，可直接粘贴）：

```html
<div style="width: 300px">
  <div
    style="
      border-radius: 16px;
      background: #ffffff;
      overflow: hidden;
      border: 1px solid rgba(0, 0, 0, 0.09);
    "
  >
    <div
      style="aspect-ratio: 16/9; background: #131316; position: relative; overflow: hidden"
    ></div>
    <div style="padding: 15px 17px 16px">
      <div class="zzm-skel" style="width: 58%; height: 16px; border-radius: 8px"></div>
      <div
        class="zzm-skel"
        style="width: 94%; height: 12.5px; border-radius: 8px; margin-top: 5px"
      ></div>
      <div
        class="zzm-skel"
        style="width: 70%; height: 12.5px; border-radius: 8px; margin-top: 5px"
      ></div>
      <div
        class="zzm-skel"
        style="width: 150px; height: 10.5px; border-radius: 8px; margin-top: 10px"
      ></div>
      <div style="margin-top: 12px; display: flex; flex-wrap: wrap; gap: 4px">
        <div class="zzm-skel" style="width: 16px; height: 16px; border-radius: 4px"></div>
        <div class="zzm-skel" style="width: 16px; height: 16px; border-radius: 4px"></div>
        <div class="zzm-skel" style="width: 16px; height: 16px; border-radius: 4px"></div>
        <div class="zzm-skel" style="width: 16px; height: 16px; border-radius: 4px"></div>
        <div class="zzm-skel" style="width: 16px; height: 16px; border-radius: 4px"></div>
        <div class="zzm-skel" style="width: 16px; height: 16px; border-radius: 4px"></div>
        <div class="zzm-skel" style="width: 16px; height: 16px; border-radius: 4px"></div>
        <div class="zzm-skel" style="width: 16px; height: 16px; border-radius: 4px"></div>
      </div>
      <div
        class="zzm-skel"
        style="width: 60px; height: 10px; border-radius: 8px; margin-top: 7px"
      ></div>
    </div>
  </div>
</div>
```

## 属性

| 属性 | 取值 | 默认 | 说明 |
|---|---|---|---|
| `cat` | { name, sub, cover?, ls: 课节数组[] } | — | 系列数据；头图优先 cover，再回退首个有封面的课节，再回退线稿 |
| `hist` | { doneIds: Set } | — | 完成的课节 ID 集合 |
| `selected` | boolean | false | 选中描边 |
| `onClick` | () => void | — | 整卡点击 |
| `loading` | boolean | false | 骨架态 |

## 本页用到的 CSS

按样张里出现的类名，从源文件原样裁出（完整版见 zzm.css）：

```css
/* tokens.css · 用到的令牌 */
:root {
  --zzm-mono: ui-monospace, "SF Mono", Menlo, monospace;
  --zzm-accent: #A6402F;
}

/* base.css */

.zzm-v4 .zzm-hovercard { transition: all .16s ease; }

.zzm-v4 .zzm-hovercard:hover {
  transform: translateY(-3px);
  border-color: rgba(0, 0, 0, .3) !important;
  box-shadow: 0 10px 30px rgba(0, 0, 0, .07);
}

.zzm-v4 .zzm-skel {
  position: relative;
  overflow: hidden;
  background: #EFEFEC;
  border-radius: 8px;
}

.zzm-v4 .zzm-skel::after {
  content: '';
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .65), transparent);
  animation: zzmShimmer 1.4s ease infinite;
}

/* keyframes */
@keyframes zzmShimmer { 100% { transform: translateX(100%) } }
```

---

上一页：[课程卡](https://ui.zhaozimin.cn/components/course-card.md) · 下一页：[课节卡](https://ui.zhaozimin.cn/components/lesson-card.md) · 全站目录：https://ui.zhaozimin.cn/llms.txt
