first commit

This commit is contained in:
2026-08-11 16:17:49 +08:00
commit 21d086aa23
170 changed files with 13204 additions and 0 deletions

View File

@@ -0,0 +1,283 @@
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,
}