组件 · COMPONENT
系列卡 SeriesCard
课程总览里的一个系列:头图、名称、两行简介、节数与总时长,再加一排每课一格的进度方块——完成的格子是朱色打勾。
import { SeriesCard } from './screens/v4/course/SeriesCard.jsx'
export function Progress() {
return (
<div style={{ width: 300 }}>
<SeriesCard cat={CAT} hist={HIST} />
</div>
)
}<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>/* 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);
}import { SeriesCard } from './screens/v4/course/SeriesCard.jsx'
export function Selected() {
return (
<div style={{ width: 300 }}>
<SeriesCard cat={CAT} hist={HIST} selected />
</div>
)
}<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>/* tokens.css · 用到的令牌 */
:root {
--zzm-accent: #A6402F;
--zzm-mono: ui-monospace, "SF Mono", Menlo, monospace;
}
/* 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);
}import { SeriesCard } from './screens/v4/course/SeriesCard.jsx'
export function Loading() {
return (
<div style={{ width: 300 }}>
<SeriesCard loading />
</div>
)
}<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>/* base.css */
.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%) } }属性
| 属性 | 取值 | 默认 | 说明 |
|---|---|---|---|
| cat | { name, sub, cover?, ls: 课节数组[] } | — | 系列数据;头图优先 cover,再回退首个有封面的课节,再回退线稿 |
| hist | { doneIds: Set } | — | 完成的课节 ID 集合 |
| selected | boolean | false | 选中描边 |
| onClick | () => void | — | 整卡点击 |
| loading | boolean | false | 骨架态 |