Files
chan/.claude/workflows/build-views.wf.js
2026-08-11 16:17:49 +08:00

284 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export const meta = {
name: 'build-chanlun-views',
description: '并行构建缠论阅读站的 5 个页面视图并评审',
phases: [
{ title: 'Build', detail: '5 个页面视图并行构建' },
{ title: 'Review', detail: '设计一致性与暗色/响应式评审' },
],
}
// ============================================================
// 共享约定契约:所有构建 agent 必须遵守
// ============================================================
const CONV = `
你在为一个名为「缠论研习社」的 Vue 3 + Vite + Tailwind v4 + TypeScript 阅读站构建页面。
站点用途:展示缠中说禅《教你炒股票》原文与杂文的沉浸式阅读。设计风格=「现代融合」:玄鉴克制 UI + 衬线正文阅读优化,亮/暗双主题。
## 硬性规范
- 仅写你被分配的那一个 .vue 文件,用 Write 工具写到指定绝对路径。不要创建或修改其它文件。
- 不要运行 pnpm install / build / typecheck / dev 等命令(共享目录会冲突)。只写文件。
- 用 <script setup lang="ts">。类型导入必须用 import type。不要留未使用的变量/导入(会被 vue-tsc 拒绝)。
- UI 文案禁止 emoji。中文标点正确。语义化 HTML用 <section><article><h1> 等)。
- 颜色/间距/圆角全部用下方语义 Token禁止写死任意 hex/rgb 颜色值。
## 设计 TokenTailwind 工具类,已全局定义)
背景bg-app(应用底色) bg-background(卡片) bg-surface(次级面板) bg-surface-2(更浅悬浮) bg-hover
文本text-foreground(主) text-muted(次) text-subtle(辅) text-faint(占位/禁用)
品牌/语义text-primary bg-primary bg-primary-soft(浅蓝底) text-success/warning/danger及 bg-success/10 等 /10 透明
边框border-line(默认弱) border-line-strong(分割) border-line-card(卡片描边)
圆角rounded-sm(8) rounded-md(12) rounded-lg(16) rounded-xl(20) rounded-2xl(24) rounded-full
阴影shadow-xs shadow-sm shadow-soft shadow-md shadow-lg shadow-xl
字体font-sans(默认) font-serif(衬线,正文/标题点缀) font-mono
过渡缓动transition 配合 duration-150/200/300
留白要克制、克制、克制——玄鉴美学核心是「安静、轻、可信」:大量留白、弱边框、柔和阴影。
## 中文图标lucide-vue-next按名导入例如
import { BookOpen, Clock, ArrowRight, Search, ChevronRight } from 'lucide-vue-next'
用 <BookOpen class="size-5" /> 渲染size-4/5/[18px])。
## 数据访问useArticles()@/composables/useArticles
返回:{ chanlun, essaysList, latest, featured, all, totalCount, find, neighbors, search }
- chanlun / essaysList / latest / featured 都是 computed用 .value 在 script 内,模板内直接用)。
- find(category, slug) → Article | undefined
- neighbors(article) → { prev?: ArticleMeta, next?: ArticleMeta }
- search(query) → ArticleMeta[]
调用方式const { chanlun, latest } = useArticles()
## 类型(@/types/article
ArticleMeta: { id, slug, category:'chanlun'|'essay'|'notes', title, subtitle?, lesson?, date(ISO字符串), tags:string[], excerpt, readingTime(分钟) }
Article 继承 ArticleMeta 并含 content(markdown字符串)。
## 路由(@/router
首页 '/'(name:'home')、缠论列表 '/chanlun'(name:'chanlun')、杂谈 '/articles'(name:'articles')、关于 '/about'(name:'about')。
阅读页name 'reader',路径 '/read/:category/:slug',用 <RouterLink :to="{ name:'reader', params:{ category, slug } }">。
用 useRouter()/useRoute() 获取路由。RouterLink 从 'vue-router' 导入。
## 已有组件(按需导入使用,不要重造)
- @/components/ui/BaseButton.vue: props variant('primary'|'outline'|'surface'|'ghost'|'danger'), size('sm'|'md'|'lg'), block(bool), disabled(bool);默认 slot
- @/components/ui/Badge.vue: props tone('primary'|'neutral'|'success'|'warning'|'danger');默认 slot
- @/components/ui/Card.vue: props hover(bool), padded(bool,默认true);默认 slot
- @/components/ui/SearchInput.vue: v-model(modelValue) + placeholder
- @/components/ArticleCard.vue: props article(ArticleMeta), showExcerpt(bool,默认true)——已是 RouterLink 包裹的卡片,直接循环渲染
- @/components/TableOfContents.vue: props items(TocItem[])TocItem={id,text,level:2|3}
## 工具
- @/utils/cn: cn(...classes), formatDate(iso)->'YYYY年M月D日', formatDateShort(iso)->'MM-DD'
- @/composables/useMarkdown: renderMarkdown(mdString)->htmlString, extractToc(mdString)->TocItem[]
- @/stores/reading: useReadingStore() → { fontSize, fontFamily, showToc, focusMode, toggleToc()... }
- @/stores/theme: useThemeStore()
## 布局说明(重要)
- 首页/缠论列表/杂谈/关于 这 4 个视图,运行在 DefaultLayout 内——该布局已提供 AppHeader(顶栏)+AppSidebar(侧栏)+AppFooter。所以你的视图只写主体内容不要再加顶栏/侧栏/页脚。
- 阅读视图运行在 ReaderLayout 内——该布局已提供极简顶栏(返回/主题/阅读设置/目录开关)+ReadingProgress。所以阅读视图只写[目录 | 正文] 网格 + 文章头部 + 正文 + 上下篇导航。
`
const BUILD_SCHEMA = {
type: 'object',
additionalProperties: false,
properties: {
file: { type: 'string', description: '写入的文件绝对路径' },
summary: { type: 'string', description: '一句话说明你实现了什么' },
components_used: { type: 'array', items: { type: 'string' } },
notes: { type: 'string', description: '任何需要主流程注意的点' },
},
required: ['file', 'summary'],
}
// ============================================================
// 各页面规格
// ============================================================
const HOME_SPEC = `${CONV}
## 你的任务:构建首页 HomeView
写入文件D:/code/chan/src/views/HomeView.vue
首页是站点的门面,要精致、有呼吸感。包含以下区块(用 <section> 组织):
1. Hero 区(顶部,克制而有气质):
- 大标题「缠论研习社」(font-serif, text-4xl~5xl, font-semibold)。
- 副标题:一句简介,如「缠中说禅《教你炒股票》原文与杂文 · 沉浸式系统性研习」。
- 两个按钮BaseButton variant=primary「开始阅读」→ 跳转 featured 文章(用 useArticles().featured 的 category/slug)BaseButton variant=outline「了解缠论」→ /about。
- 可加极淡的装饰(如右上角半透明走势曲线 SVGopacity 很低,不抢戏)。
- Hero 下方可放一行小统计:文章总数(totalCount)、分类数等text-subtle。
2. 缠论体系概览4 个概念卡片):分型 / 笔与线段 / 走势中枢 / 背驰与买卖点。每个卡片lucide 图标(在 bg-primary-soft 圆角方块里)+ 标题 + 一句话。整组用 gridsm:grid-cols-2 lg:grid-cols-4。卡片用 Card 或自定义 div(bg-background border rounded-lg p-5)。点击可跳转第一课 /read/chanlun/ke-02-fenxing 之类(用 featured 或第一篇 chanlun
3. 分类入口(用 @/data/categories 的 categories 数组3 个缠论108课 / 杂谈随笔 / 我的理解。每个卡片显示 label、description、文章数(chanlun.length / essaysList.length / 0)。点击跳转对应路由。notes 分类显示「即将上线」徽标且不可点(或点向 /about)。
分类数据导入import { categories } from '@/data/categories'
4. 最新文章:取 useArticles().latestcomputed最多6篇用 ArticleCard 渲染grid sm:grid-cols-2。标题旁有「查看全部 →」链接到 /chanlun。
5. 页尾引言条一句大字衬线引言「走势无须预测只需应对。」居中bg-surface 或带 primary 左边框的引用样式。
区块之间用 py-12 / py-16 等充足纵向留白分隔。整体 max-w 容器用 mx-auto max-w-[1200px] px-4。
`
const CHANLUN_SPEC = `${CONV}
## 你的任务:构建缠论列表页 ChanlunListView
写入文件D:/code/chan/src/views/ChanlunListView.vue
这是「缠论 108 课」系列的文章列表页。
1. 页头:标题「缠论 108 课」(font-serif, text-3xl, font-semibold) + 副标题描述(取 categories 里 key='chanlun' 的 description+ 右侧或下方显示文章数「共 N 课」。
2. 搜索栏:用 SearchInputv-model 一个 query refplaceholder「搜索课程…」。过滤逻辑const filtered = computed(() => query.value ? search(query.value) : chanlun.value)。注意 search 返回 ArticleMeta[]。
3. 列表:用 ArticleCard 渲染 filtered。布局用 flex flex-col gap-3单列堆叠卡片适合课程列表的顺序阅读感
4. 空状态filtered 为空时显示一个居中提示(图标 + 「未找到相关课程」+ 清除按钮)。
5. 顶部可加一个简短的「学习路径」提示卡(可选):用 bg-primary-soft 或 bg-surface说明建议按课次顺序学习。
用 useArticles() 取 chanlun 与 search。用 @/data/categories 取描述。注意:模板里直接用 chanluncomputed 在模板自动解包script 里用 chanlun.value。
`
const READER_SPEC = `${CONV}
## 你的任务:构建阅读视图 ArticleReaderView最核心页面
写入文件D:/code/chan/src/views/ArticleReaderView.vue
这是沉浸式阅读页。运行在 ReaderLayout 内(已提供顶栏+进度条,不要再加)。
数据获取:
- const route = useRoute(); const { find, neighbors } = useArticles()
- const article = computed(() => find(route.params.category as ArticleCategory, route.params.slug as string))
- 文章切换时要滚回顶部watch(() => route.params.slug, () => window.scrollTo({top:0})) (或 watch article
找不到文章时:渲染一个居中的「未找到文章」状态 + 返回首页/列表的 RouterLink。
找到文章时,布局(网格):
<div class="mx-auto grid max-w-[1180px] gap-8 px-4 py-10 sm:px-6 lg:grid-cols-[220px_minmax(0,1fr)] lg:py-14">
<!-- 目录列:桌面端且 reading.showToc 为真时显示 -->
<aside class="hidden lg:block"> <div class="sticky top-20"> <TableOfContents :items="toc" v-if="toc.length"/> </div> </aside>
<!-- 正文列 -->
<article class="min-w-0 mx-auto w-full max-w-[760px]"> ... </article>
</div>
注意:目录列的显隐 = reading.showToclg 才显示)。当 reading.showToc 为 false 时,整个 aside 用 hidden为 true 时 hidden lg:block。可用 :class="reading.showToc ? 'hidden lg:block' : 'hidden'"。
文章头部(在 <article> 内,正文之前):
- 分类 Badgechanlun→「第 N 课」primaryessay→「杂谈」neutral
- 标题article.titlefont-serif text-3xl sm:text-4xl font-semiboldleading-tight。
- 副标题 article.subtitle若有text-muted。
- meta 行formatDate(article.date) · 阅读时间 article.readingTime 分钟 · 标签(#tag。用 text-subtle text-smlucide Clock 图标。
- 一条分割线 border-t border-line mt-6。
正文渲染(关键):
- const html = computed(() => renderMarkdown(article.value.content))
- const toc = computed(() => extractToc(article.value.content))
- 容器:<div class="prose-chan mt-8" :data-font="reading.fontFamily" :data-size="reading.fontSize" v-html="html"></div>
prose-chan 的样式与 data-font/data-size 已在 style.css 定义,会驱动衬线/字号)
底部(正文之后):
- 标签云(如有)。
- 分割线。
- 上一篇/下一篇导航neighbors(article.value) → { prev, next }。两个卡片横向排列grid sm:grid-cols-2 gap-4每张显示「上一篇/下一篇」小标 + 标题,用 RouterLink 指向对应 reader 路由。缺失的一侧显示占位「已是第一篇/最后一篇」(text-subtle)。
- 一个返回列表的链接(返回 /chanlun 或 /articles依据 article.category
务必:导入 ArticleCategory 类型用 import type。watch、computed 从 vue 导入。useRoute 从 vue-router 导入。
`
const ESSAYS_SPEC = `${CONV}
## 你的任务:构建杂谈列表页 OtherArticlesView
写入文件D:/code/chan/src/views/OtherArticlesView.vue
这是「杂谈随笔」的文章列表页(结构可参考缠论列表页,但气质更"随笔")。
1. 页头:标题「杂谈随笔」(font-serif text-3xl font-semibold) + 描述categories 里 key='essay' 的 description+ 文章数「共 N 篇」。
2. 搜索栏SearchInput v-model query过滤 essaysList。
3. 列表:用 ArticleCard 渲染。可用 flex flex-col gap-3 单列,或 grid sm:grid-cols-2 双列(杂谈更适合双列卡片网格)。自行选择更美观的。
4. 空状态:无结果提示。
5. 顶部可加一段引言卡片可选关于缠论作者或市场哲学的短引bg-surface + primary 左边框引用样式。
用 useArticles() 取 essaysList 与 search。模板内直接用 essaysListscript 内用 essaysList.value。
`
const ABOUT_SPEC = `${CONV}
## 你的任务:构建关于页 AboutView
写入文件D:/code/chan/src/views/AboutView.vue
关于本站页面。运行在 DefaultLayout 内(已有顶栏侧栏页脚)。
用居中窄栏max-w-[760px] mx-auto+ 衬线正文风格组织,分几个 <section>
1. 页头:标题「关于缠论研习社」(font-serif text-3xl font-semibold) + 一句定位。
2. 「什么是缠论」:用 2-3 段说明缠论是缠中说禅创立的市场分析体系,核心是形态学(分型/笔/线段/中枢/走势类型)+动力学(背驰/买卖点),强调"无须预测、只需应对"。可用 .prose-chan 排版或自定义段落样式。
3. 「关于缠中说禅」简述作者匿名网络ID2006-2008博客连载《教你炒股票》108课。提醒可点击「杂谈随笔」看相关文章——放一个 RouterLink。
4. 「关于本站」:说明本站用途(系统研习缠论原文与杂文)、内容来源(抓取整理,仅供学习)、后续计划(结合当下股市的个人理解与实战笔记,即「我的理解」分区即将上线)。
5. 免责声明:用一个小卡片(bg-surface rounded-lg p-4 text-sm text-muted) 说明「本站内容仅供学习研究,不构成任何投资建议;市场有风险,入市需谨慎。」
可以适当用 lucide 图标点缀小标题。排版要克制、有书卷气。不要 emoji。
`
// ============================================================
// 评审规格
// ============================================================
const REVIEW_SPEC = `${CONV}
## 你的任务:评审刚构建的 5 个页面视图
用 Read 工具逐一阅读这 5 个文件:
- D:/code/chan/src/views/HomeView.vue
- D:/code/chan/src/views/ChanlunListView.vue
- D:/code/chan/src/views/ArticleReaderView.vue
- D:/code/chan/src/views/OtherArticlesView.vue
- D:/code/chan/src/views/AboutView.vue
逐一检查并报告问题(不要修改文件,只报告):
1. TypeScript 正确性:是否有未使用的导入/变量vue-tsc noUnusedLocals 会报错、import type 是否用于纯类型导入、props 类型是否正确、computed/watch 用法是否正确。
2. 组件 API 用法BaseButton/Badge/Card/SearchInput/ArticleCard/TableOfContents 的 props 是否传对(尤其 ArticleCard 的 article 是 ArticleMetaTableOfContents 的 items 是 TocItem[])。
3. 设计一致性:是否只用了语义 Token无写死颜色、留白是否克制、圆角/阴影是否合规、是否符合玄鉴"安静克制"美学。
4. 暗色模式:所有颜色是否都用语义 Token能自动适配暗色有无硬编码深浅色。
5. 响应式:移动端(<lg)与桌面端布局是否合理grid 断点、hidden/sm:block 等)。
6. 数据访问useArticles 返回的 computed 在 script 中是否用 .value、模板中是否正确解包。
7. 路由RouterLink 的 :to 是否正确reader 用 {name,params})。
8. 可访问性:按钮有无 aria-label、语义化标签、标题层级。
按严重度 high/medium/low 报告,每条给出 file + issue + 具体修复建议(可粘贴代码)。最后给 overall 总评。
`
const REVIEW_SCHEMA = {
type: 'object',
additionalProperties: false,
properties: {
findings: {
type: 'array',
items: {
type: 'object',
additionalProperties: false,
properties: {
file: { type: 'string' },
severity: { type: 'string', enum: ['high', 'medium', 'low'] },
issue: { type: 'string' },
suggestion: { type: 'string' },
},
required: ['file', 'severity', 'issue', 'suggestion'],
},
},
overall: { type: 'string' },
},
required: ['findings', 'overall'],
}
// ============================================================
// 执行
// ============================================================
phase('Build')
const builds = await parallel([
() => agent(HOME_SPEC, { label: 'build:home', phase: 'Build', schema: BUILD_SCHEMA }),
() => agent(CHANLUN_SPEC, { label: 'build:chanlun', phase: 'Build', schema: BUILD_SCHEMA }),
() => agent(READER_SPEC, { label: 'build:reader', phase: 'Build', schema: BUILD_SCHEMA }),
() => agent(ESSAYS_SPEC, { label: 'build:essays', phase: 'Build', schema: BUILD_SCHEMA }),
() => agent(ABOUT_SPEC, { label: 'build:about', phase: 'Build', schema: BUILD_SCHEMA }),
])
const ok = builds.filter(Boolean)
log(`已构建 ${ok.length}/5 个视图:${ok.map((b) => b?.file?.split('/').pop()).join(', ')}`)
phase('Review')
const review = await agent(REVIEW_SPEC, { label: 'review:views', phase: 'Review', schema: REVIEW_SCHEMA })
return {
built: ok.map((b) => ({ file: b.file, summary: b.summary })),
review,
}