research-evidence-reader

研究证据阅读器

youteacher_analyze 这个应用里,有一层很小的证据浏览层:一组 /data/* 路由,每条路由从磁盘上取一份抓取来的研究材料,交给一个懂得如何显示那种数据形状的 viewer 组件。目的,是让商业文档背后的原始材料——论坛帖子、对比分析、结构化报告——能在浏览器里直接读,而前面不必架一个数据库或 API。

从服务端读文件

一条 data 路由就是一个普通的 Next.js 服务端页面。它用 path.join(process.cwd(), ...) 拼出绝对路径,用 fs.readFileSync 同步读文件,再把结果作为 props 往下传。Reddit 证据那条路由从 public/data/ 读一份 JSON,JSON.parse 成一个帖子数组,交给 ForumPostsViewer 渲染;平台对比那条路由从 public/analysis/ 把一份 Markdown 当字符串读进来,交给 AnalysisReportViewer 渲染。两条路由都把 viewer 包在一层很窄的页面外壳里,外壳上唯一的装饰就是一个 "← Back to Business Plan" 的返回首页链接。所以这里的文件系统本身就是数据源:要发布一份新证据,就把文件丢进 public/,再让一条路由指向它。

两条读取路径,是刻意的

这一层其实有两种读取策略,而且是有意共存的。服务端路由在服务端渲染时就把文件读掉(fs.readFileSync)——访客的浏览器从不亲自去取那份文件。另一条路径是 DataViewer:一个可折叠的客户端控件,点击之前什么都不做,点了之后才按需 fetch 一个 URL,然后要么把它(作为 JSON)美化打印,要么把它(作为 Markdown)原样塞进一个可滚动的 <pre> 里。服务端路由用于承载一整页的证据;客户端 DataViewer 则用于那些你只想就地瞄一眼的证据,惰性加载,没人打开就不花代价。

专用 viewer 与通用兜底

viewer 组件也沿着同一条线分开。ForumPostsViewer 是为一种数据量身定做的:它接收一个论坛帖子数组,提供一个客户端搜索框(在标题、content、body、作者之间过滤),每二十条分一页,并把每条帖子渲染成一张卡片——序号徽章、作者、日期、标题、截断后的正文(在 500 字符处截断)、赞数与回复数、以及一个指向原帖的链接。它的 ForumPost 类型把每个字段都标成可选,还带一个索引签名,于是从不同来源抓来、形状各异的帖子都能渲染而不崩。AnalysisReportViewer 则是为分析报告定做的:它按 ## 标题把 Markdown 切成若干小节,再按关键词给每节打标签——"pain point""evidence"/"quote""frequency"/"severity"——给痛点小节配上红色边框、给每种类型配上各自的图标,然后用 react-markdownremark-gfm 渲染正文。

在这两者之下,是通用兜底JsonReader 渲染任意 JSON(数组变成一串编号条目,对象变成带标签的键/值行,嵌套对象则被字符串化塞进 <pre>),MarkdownReader 渲染任意 Markdown。专用 viewer 让某一份数据既清楚又好看;通用的这两个则让任何数据至少看得见。

两个 Markdown 渲染器

之所以值得一提,是因为这一层里确实存在一处不一致:Markdown 有两种渲染方式。AnalysisReportViewer 用的是 react-markdown 库,带 GitHub 风格 Markdown 支持以及逐元素的样式覆写;MarkdownReader 则跑一串手写的正则,把标题、粗体、斜体、链接、列表、代码、引用和分隔线转成一段 HTML 字符串,再用 dangerouslySetInnerHTML 注入。正则解析器更轻,但能力也弱得多,而且信任它的输入;它只会被喂仓库内部的证据文件,绝不会碰访客提交的东西。

本组件内容

about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →

research-evidence-reader