diff --git a/.gitignore b/.gitignore index 9083ece..af2f76b 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,4 @@ yarn-error.log* # ZCode local config .zcodeignore -design-refs/mockup/single/ +design-refs/mockup/single*/ diff --git a/design-refs/mockup/build.sh b/design-refs/mockup/build.sh index c001759..0ca7528 100755 --- a/design-refs/mockup/build.sh +++ b/design-refs/mockup/build.sh @@ -1,51 +1,68 @@ #!/usr/bin/env bash -# 课程书架设计稿构建:course-shelf.html → 5 个单页 PDF → 合并 course-shelf.pdf -# 依赖:pdf skill 的 html2poster.js + pypdf(playwright + chromium 需可用) +# 设计稿构建:把整页 HTML 按顶层 .page 拆成单页 → html2poster 逐页渲染 → 合并为同名 PDF +# 用法:bash build.sh [stem ...] # stem 为 design-refs/mockup 下的 html 文件名(不含扩展名) +# 默认构建 course-shelf 与 components 两个目标 set -euo pipefail cd "$(dirname "$0")" PDF_SKILL_DIR="${PDF_SKILL_DIR:-$HOME/.zcode/cli/plugins/cache/zcode-plugins-official/pdf/0.1.7/skills/pdf}" export PDF_SKILL_DIR -# 1. 按 .page 拆成单页 HTML(固定布局必须走 html2poster,不能用 html2pdf-next) -python3 - <<'EOF' -import os, re -html = open("course-shelf.html", encoding="utf-8").read() +STEMS=("$@") +if [ ${#STEMS[@]} -eq 0 ]; then + STEMS=(course-shelf components) +fi + +for STEM in "${STEMS[@]}"; do + SRC="${STEM}.html" + [ -f "$SRC" ] || { echo "!! 缺少 $SRC,跳过"; continue; } + OUT_DIR="single-${STEM}" + mkdir -p "$OUT_DIR" + + # 1. 按顶层 .page 拆单页(固定布局必须走 html2poster,不能用 html2pdf-next) + python3 - "$SRC" "$OUT_DIR" <<'EOF' +import re, sys +src, out_dir = sys.argv[1], sys.argv[2] +html = open(src, encoding="utf-8").read() head, rest = html.split("", 1) body_inner = rest.rsplit("", 1)[0] pat = re.compile(r'(?=
)') chunks = [c for c in pat.split(body_inner) if c.lstrip().startswith('
]+-->\s*$', '', page_html.rstrip()) + "\n" page_html = page_html.replace("page-break-after: always;", "page-break-after: auto;") - open(f"single/page-{i}.html", "w", encoding="utf-8").write(head + "\n" + page_html + "\n\n") -print(f"split {len(chunks)} pages") + open(f"{out_dir}/page-{i}.html", "w", encoding="utf-8").write(head + "\n" + page_html + "\n\n") +print(f"[{src}] split {len(chunks)} pages") EOF -# 2. 单页素材(img 相对路径以 single/ 为基准) -cp -r assets single/assets + # 2. 单页素材(img 相对路径以单页目录为基准) + [ -d assets ] && cp -r assets "$OUT_DIR/assets" -# 3. 逐页渲染 + 合并 -python3 - <<'EOF' -import glob, subprocess, sys -pages = sorted(glob.glob("single/page-*.html"), key=lambda p: int(__import__('re').search(r'(\d+)', p).group(1))) + # 3. 逐页渲染 + python3 - "$OUT_DIR" <<'EOF' +import glob, os, re, subprocess, sys +out_dir = sys.argv[1] +pages = sorted(glob.glob(f"{out_dir}/page-*.html"), key=lambda p: int(re.search(r"(\d+)", os.path.basename(p)).group(1))) for p in pages: out = p.replace(".html", ".pdf") - subprocess.run(["node", f"{__import__('os').environ['PDF_SKILL_DIR']}/scripts/html2poster.js", p, "--output", out, "--width", "1280px"], check=True, capture_output=True) - print("rendered", out) + subprocess.run(["node", f"{os.environ['PDF_SKILL_DIR']}/scripts/html2poster.js", p, "--output", out, "--width", "1280px"], check=True, capture_output=True) +print(f"[{out_dir}] rendered {len(pages)} pages") EOF -python3 - <<'EOF' -import re + # 4. 合并 + 元数据 + python3 - "$OUT_DIR" "${STEM}" <<'EOF' +import glob, os, re, sys from pypdf import PdfWriter +out_dir, stem = sys.argv[1], sys.argv[2] +pdfs = sorted(glob.glob(f"{out_dir}/page-*.pdf"), key=lambda p: int(re.search(r"(\d+)", os.path.basename(p)).group(1))) w = PdfWriter() -for i in range(1, 6): - w.append(f"single/page-{i}.pdf") -w.add_metadata({"/Title": "课程书架设计稿 · stack idealjs", "/Author": "Z.ai", "/Subject": "全栈工程师教程课程预览重构设计稿", "/Creator": "Z.ai"}) -with open("course-shelf.pdf", "wb") as f: +for p in pdfs: + w.append(p) +w.add_metadata({"/Title": f"课程书架设计稿 · {stem} · stack idealjs", "/Author": "Z.ai", "/Creator": "Z.ai"}) +with open(f"{stem}.pdf", "wb") as f: w.write(f) -print("merged -> course-shelf.pdf") +print(f"merged {len(pdfs)} pages -> {stem}.pdf") EOF -echo "done: course-shelf.pdf" + echo "done: ${STEM}.pdf" +done diff --git a/design-refs/mockup/components-page-1.png b/design-refs/mockup/components-page-1.png new file mode 100644 index 0000000..a78fc6b Binary files /dev/null and b/design-refs/mockup/components-page-1.png differ diff --git a/design-refs/mockup/components-page-2.png b/design-refs/mockup/components-page-2.png new file mode 100644 index 0000000..a40dcb5 Binary files /dev/null and b/design-refs/mockup/components-page-2.png differ diff --git a/design-refs/mockup/components-page-3.png b/design-refs/mockup/components-page-3.png new file mode 100644 index 0000000..4c94ec9 Binary files /dev/null and b/design-refs/mockup/components-page-3.png differ diff --git a/design-refs/mockup/components-page-4.png b/design-refs/mockup/components-page-4.png new file mode 100644 index 0000000..672b755 Binary files /dev/null and b/design-refs/mockup/components-page-4.png differ diff --git a/design-refs/mockup/components-page-5.png b/design-refs/mockup/components-page-5.png new file mode 100644 index 0000000..9e05a3c Binary files /dev/null and b/design-refs/mockup/components-page-5.png differ diff --git a/design-refs/mockup/components-page-6.png b/design-refs/mockup/components-page-6.png new file mode 100644 index 0000000..8f2e393 Binary files /dev/null and b/design-refs/mockup/components-page-6.png differ diff --git a/design-refs/mockup/components-page-7.png b/design-refs/mockup/components-page-7.png new file mode 100644 index 0000000..5f967d5 Binary files /dev/null and b/design-refs/mockup/components-page-7.png differ diff --git a/design-refs/mockup/components.html b/design-refs/mockup/components.html new file mode 100644 index 0000000..bb868ae --- /dev/null +++ b/design-refs/mockup/components.html @@ -0,0 +1,681 @@ + + + + +课程书架 组件设计稿 · Storybook 形式 · stack idealjs + + + + + +
+
+ stack·idealjs + 全栈教程 🎓工具使用博客 + +
+
STACK IDEALJS · 全栈工程师教程
+

组件设计稿 · Storybook 拆分

+
+ 整页设计稿(course-shelf v2)拆解为 8 个可独立实现与验收的组件,每组件一页:变体展示 + Args 表 + 规格。
+ 组件即实现单元:Docusaurus 侧按组件对应文件落码,Storybook 页即验收清单。 +
+
+
<TopBar />
黑顶栏
logo · 菜单 · 当前项高亮 · 🔔 · 头像
+
<IconRail />
左窄图标栏
40px 宽,图标导航
+
<SearchPill />
搜索胶囊
页面标题行右侧搜索
+
<StatsRow />
统计卡行
大数字 + 灰标签,淡彩底
+
<CourseCard />
课程白卡
图标 · 标题 · 预期一句话 · 状态 · 按钮
+
<CourseCardMedia />
悬停媒体卡
淡彩底 + 播放钮 + ⏱ GIF 循环
+
<EventsPanel />
事件栏
彩色条目:类型 · 日期 · 描述 · 时间
+
Atoms
原子组件
Pill · CircleButton · Chip · IconTile
+
+
+
8Components · 组件
+
7Stories · 页
+
6Variants · 课程变体
+
+
+ v2.1 · 2026-10 · 派生自 course-shelf v2
复刻视觉:Learning Plan Dashboard(仅风格参考) +
+
01 / 07
+
+ + +
+
+ <TopBar /> <IconRail /> <SearchPill /> +

骨架导航组件

+
页面骨架三件套:黑顶栏承载站点导航,左窄栏承载快捷入口,搜索胶囊附属于标题行。
+
+ +
+
+
+
+ + 全栈教程 🎓工具使用实践经历博客 + 🔔 + 初 +
+
default · active = 全栈教程
+
+
+
+ + 全栈教程 🎓工具使用实践经历博客 + 🔔 + 初 +
+
variant · active = 博客(随路由切换)
+
+
+
+ +
+
+
+
🏠📚🧩⚙️
+
<IconRail /> · w 40 · 4 icons
+
+
+
Search…
+
<SearchPill /> · default
+
+
+
快速开始
+
variant · 已输入
+
+
+
+ +
+
+

<TopBar /> Args

+ + + + + + +
Prop类型默认说明
logostringstack·idealjs左侧白色粗体站点名
itemsstring[]四项菜单全栈教程/工具使用/实践经历/博客
activestring全栈教程当前路由项,白色加粗
avatarstring初渐变圆头像,取首字
+
+
+

规格说明

+
1TopBar:高 46px,底 #111111,菜单居中,当前项 #FFF 600;圆角 12px(页面内嵌时)。
+
2IconRail:白底 40px 宽,图标 14px 灰 #8A8A8A,项间距 14px;首页/书架/积木/设置。
+
3SearchPill:胶囊白底描边,placeholder 灰;输入后文字转主黑。实现映射 Docusaurus navbar search。
+
+
+ +
组件设计稿 · Storybook 拆分 · stack idealjs
+
02 / 07
+
+ + +
+
+ Atoms · Pill / CircleButton / Chip / IconTile +

原子组件

+
构成卡片与面板的最小单元,四象限展示:每个原子给出全部变体与尺寸规格。
+
+ + +
+
<Pill /> 状态胶囊
+
+
✓ Completed 🎉
done · 紫底 #EFE6FB
+
Upcoming
wip · 白底描边
+
⏱ Watching
time · 白底描边·黑字
+
+
高度统一 24px(页面尺寸)/ 28px(卡片尺寸)· 圆角 999px · 字重 600
+
+ + +
+
<CircleButton /> 黑圆按钮
+
+
✓
done · 已完成章节
+
→
next · 写作中章节
+
▶
play · 媒体卡播放
+
+
卡内 26px / 解剖展示 34px · 背景 #1A1A1A · 点击进入章节
+
+ + +
+
<Chip /> 关系标签
+
+
前置:无
prerequisite
+
关联:快速开始 React
relation
+
关联章节整理中
empty · 占位虚线
+
+
数据源 courses.ts 的 relations 字段 · 未填时显示占位样式
+
+ + +
+
<IconTile /> 彩色图标块
+
+
🌐
快速开始
+
⚛️
React
+
🧱
更多示例
+
📦
更多
+
🔗
全栈基础
+
🧠
高级教程
+
+
26×26 · 圆角 9px · 淡彩底取自课程色映射表
+
+ +
+

原子组件规格速查

+ + + + + + +
原子尺寸颜色圆角用途
Pill高 24/28px紫 #EFE6FB / 白底描边999px完成度与播放状态徽标
CircleButton26 / 34px#1A1A1A + 白字50%进入章节 / 播放确认
Chip高 20px描边 #CFCFCD 灰字999px章节前置与关联关系
IconTile26×26px六课程淡彩底9px卡片左上角章节图标
+
+ +
组件设计稿 · Storybook 拆分 · stack idealjs
+
03 / 07
+
+ + +
+
+ <CourseCard /> +

课程白卡 · default 态

+
书架的核心单元:六个章节变体共用一个组件,数据全部来自 courses.ts。
+
+ +
+
+
+
+
🌐
+
快速开始
+
写出第一个网页并在浏览器打开,跑起你的第一个 HTTP 服务器。
+
前置:无
+
✓ Completed 🎉✓
+
+
hello-world
+
+
+
+
⚛️
+
快速开始 React
+
用脚手架创建 React 应用,亲手拼出第一个 Todo List。
+
前置:快速开始
+
✓ Completed 🎉✓
+
+
hello-react
+
+
+
+
🧱
+
更多示例
+
浮动布局等补充示例,巩固前面攒下的积木。
+
前置:快速开始
+
✓ Completed 🎉✓
+
+
more-example
+
+
+
+
+
+
📦
+
更多
+
把示例工程构建为可发布产物,看清 build 做了什么。
+
前置:更多示例
+
✓ Completed 🎉✓
+
+
more
+
+
+
+
🔗
+
全栈基础
+
从网页到服务与数据库,串起最小全栈闭环。
+
关联章节整理中
+
Upcoming→
+
+
full-stack · wip 变体
+
+
+
+
🧠
+
高级教程
+
数据流、纯函数、CAP——工程上的心智模型。
+
前置:全栈基础
+
✓ Completed 🎉✓
+
+
advance
+
+
+
+ +
+
+

Args

+ + + + + + + + +
Prop类型说明
titlestring章节名(分类 label)
descstring预期结果一句话
donebooleantrue → ✓ 胶囊 + ✓ 钮
relationsstring[]空 → 虚线占位 chip
icon / tintemoji / hexIconTile 底色与图标
hrefstring章节分类索引路由
+
+
+

交互

+
1整卡可点 → 进入章节;hover 切换为 <CourseCardMedia />(见下页)。
+
2Tab focus 与 hover 同态;触屏无预览态。
+
+
+ +
组件设计稿 · Storybook 拆分 · stack idealjs
+
04 / 07
+
+ + +
+
+ <CourseCardMedia /> +

悬停媒体卡 · hover 态

+
CourseCard 的 hover/focus 形态:淡彩整卡 + 黑色圆形播放钮 + ⏱ 胶囊,GIF 此刻才挂载并循环。
+
+ +
+
+
+
+
▶
+ ⏱ GIF loop + 预期成果 · 浏览器打开第一个网页 +
+
+
快速开始
+
GIF 来源 static/gifs/hello-world.gif · loop=0 · 悬停期间持续播放
+
+
+
default · tint = #E0E7FF(随章节主题色)
+
+
+ +
+
六章节 tint 变体(播放器区域)
+
+
▶
#E0E7FF
+
▶
#FCE7F3
+
▶
#FEF3C7
+
▶
#D1FAE5
+
▶
#E0F2FE
+
▶
#EDE9FE
+
+
+ +
+
+

播放器规格

+ + + + + + +
项规格
播放钮黑 #111 圆形 · 卡内 44px / 展示 56px · 阴影 0 4px 14px
⏱ 胶囊右上 GIF loop · 左下成果字幕,白底描边胶囊
GIF640×360 · loop=0 · ≤300KB · hover 时才挂载
触屏不进入此态,点击直达章节
+
+
+

状态转换

+
1default → hover:mouseenter/focus,卡底翻章节淡彩,播放钮 + 胶囊出现,GIF 挂载。
+
2hover → default:mouseleave/blur,还原白卡,GIF 卸载,HTTP 缓存保留。
+
3再次 hover:缓存命中,瞬时恢复播放,无二次流量。
+
+
+ +
组件设计稿 · Storybook 拆分 · stack idealjs
+
05 / 07
+
+ + +
+
+ <StatsRow /> · <EventsPanel /> +

数据组件 · 统计行与事件栏

+
统计行汇报教程规模,事件栏承载章节动态;两者数据均为静态 JSON,可后续接构建脚本生成。
+
+ +
+
<StatsRow /> 三变体
+
+
6Total Sections
total · 蓝
+
5Completed 🎉
done · 绿
+
1Upcoming
wip · 紫
+
+
数值由 courses.ts 推导:total=length · done=count(done) · wip=total-done
+
+ +
+
<EventsPanel /> 最近更新 · 四色变体
+
+
+
+
新增 · WebOCT 02
+
「更多示例」补充浮动布局一文
+
09:00
+
+
新增 · 蓝
+
+
+
+
章节 · 高级SEP 28
+
CAP 一讲更新配图与示例
+
14:30
+
+
更新 · 紫
+
+
+
+
计划 · 全栈SOON
+
「全栈基础」章节开写
+
即将开始
+
+
计划 · 黄
+
+
+
+
讨论OCT 01
+
讨论区新增 3 帖
+
20:00
+
+
讨论 · 绿
+
+
+
+ +
+
+

<EventsPanel /> Args

+ + + + + + +
Prop类型说明
items[]Event[]事件数组,倒序展示
type新增/更新/计划/讨论决定淡彩底色
datestring右上角日期(SOON 等特殊值可)
desc / timestring动态描述与时间行
+
+
+

数据与实现

+
1StatsRow:纯派生数据,无独立配置;卡宽 flex 均分,圆角 16px。
+
2EventsPanel:数据源 events.ts(新文件);类型→底色映射:新增蓝/更新紫/计划黄/讨论绿。
+
3事件可由 git log 或 RSS 后续自动生成,先手工维护。
+
+
+ +
组件设计稿 · Storybook 拆分 · stack idealjs
+
06 / 07
+
+ + +
+
+ <DashboardPage /> +

组装 · 组件如何拼成页面

+
页面 = TopBar + IconRail + 标题行(SearchPill) + StatsRow + CourseGrid(CourseCard × 6) + EventsPanel。
+
+ +
+
+ + 全栈教程 🎓工具使用博客 + 🔔初 +
+
+
🏠📚🧩⚙️
+
+
+
全栈工程师教程 📚
+
Search…
+
+
+
6Total
+
5Completed 🎉
+
1Upcoming
+
+
+
+
▶
+
快速开始
+
+
+
⚛️
+
快速开始 React
+
✓ Completed✓
+
+
+
🧱
+
更多示例
+
✓ Completed✓
+
+
+
🔗
+
全栈基础
+
Upcoming→
+
+
+
+
+
最近更新 😊
+
+
新增 · WebOCT 02
+
「更多示例」补充一文
+
+
+
章节 · 高级SEP 28
+
CAP 更新配图
+
+
+
讨论OCT 01
+
新增 3 帖
+
+
+
+
+ +
+
+

组件树(实现映射)

+ + + + + + + + + +
设计组件实现文件
DashboardPagepages/index.tsx
TopBarnavbar 主题化 docusaurus.config
IconRailCourseShelf/IconRail
StatsRowCourseShelf/StatsRow
CourseCard(/Media)CourseShelf/CourseCard
EventsPanelCourseShelf/EventsPanel
courses/events 数据courses.ts / events.ts
+
+
+

组装要点

+
1网格 auto-fill minmax(280px, 1fr);本页示意 2 列,≥1140px 实为 3 列。
+
2各组件可独立验收:本册 02–06 页即逐组件的验收清单。
+
3整页效果见 course-shelf.pdf(v2 整页版)。
+
+
+ +
组件设计稿 · Storybook 拆分 · stack idealjs
+
07 / 07
+
+ + + diff --git a/design-refs/mockup/components.pdf b/design-refs/mockup/components.pdf new file mode 100644 index 0000000..59d02c2 Binary files /dev/null and b/design-refs/mockup/components.pdf differ