BINARY GARDEN
2026-01-05 // web · 移动端 · sibuchen

ZHDR

ZHDR - 知乎日报(移动端 Web 应用)

1. 项目概述

知乎日报是一款面向移动端的新闻资讯阅读应用,基于知乎日报公开 API,实现了新闻浏览、详情阅读、用户登录、收藏、点赞、评论等完整功能。项目采用 Vue 3 全家桶开发,注重移动端适配体验与交互细节。

项目类型: 移动端 SPA(Single Page Application)
项目规模: 17 个源文件,7 个页面视图,3 个状态管理模块


2. 技术栈

分类技术选型
前端框架Vue 3(Composition API + <script setup>
构建工具Vite
状态管理Pinia
路由管理Vue Router 4
UI 组件库Vant 4(移动端)
HTTP 请求Axios(封装拦截器)
样式方案SCSS + CSS 变量(主题系统)
移动端适配amfe-flexible + postcss-pxtorem(px 自动转 rem)

3. 项目结构

src/
├── main.js                    # 应用入口
├── App.vue                    # 根组件
├── router/
│   └── index.js               # 路由配置(7 条路由,全部懒加载)
├── request/
│   └── index.js               # Axios 实例封装(拦截器 + Loading 管理)
├── stores/
│   ├── user.js                # 用户状态(登录态、收藏、点赞、评论)
│   ├── theme.js               # 主题状态(亮色/暗黑模式)
│   └── counter.js             # 计数器状态
├── views/
│   ├── Home/                  # 首页(轮播 + 新闻列表 + 无限滚动)
│   │   ├── Home.vue
│   │   ├── Head.vue           # 粘性导航栏(滚动毛玻璃效果)
│   │   └── NewsItem.vue       # 新闻卡片组件
│   ├── Detail/                # 新闻详情页(核心页面)
│   │   └── Detail.vue
│   ├── Login/                 # 登录页(SVG Logo + 动效)
│   │   └── Login.vue
│   ├── Personal/              # 个人中心
│   │   └── Personal.vue
│   ├── Favorite/              # 我的收藏
│   │   └── Favorite.vue
│   ├── Likes/                 # 我的点赞
│   │   └── Likes.vue
│   └── Comments/              # 我的评论
│       └── Comments.vue
└── assets/
    ├── images/
    └── styles/
        └── theme.css          # 暗黑模式全局样式

4. 架构设计

4.1 路由与页面导航

采用 Vue Router 4 的 createWebHistory 模式,所有路由组件均使用 动态 import(懒加载),减少首屏加载体积:

/              → Home(首页新闻列表)
/detail/:id?   → Detail(新闻详情)
/personal      → Personal(个人中心)
/login         → Login(登录页)
/favorite      → Favorite(我的收藏)
/likes         → Likes(我的点赞)
/comments      → Comments(我的评论)

页面导航关系:首页通过新闻卡片进入详情页,个人中心可跳转至收藏/点赞/评论子页面,详情页内可跳转至登录页。

4.2 状态管理(Pinia)

使用 Pinia 的 Composition API 风格(defineStore + setup 函数),划分为三个独立 Store:

user.js — 用户核心状态

  • userInfo:用户信息(手机号、头像、登录时间)
  • favorites:收藏的新闻 ID 列表
  • likes:点赞的新闻 ID 列表
  • likeCounts:点赞计数(使用 reactive({}) 解决动态 key 响应式问题)
  • myComments:用户评论列表(含新闻 ID、标题、内容、时间戳)
  • 所有状态变更后即时同步写入 localStorage,实现持久化

theme.js — 主题状态

  • 优先读取 localStorage 缓存,无缓存时检测系统偏好 prefers-color-scheme
  • 通过 document.documentElement.setAttribute('data-theme', ...) 切换 CSS 变量

4.3 网络请求层

对 Axios 进行统一封装(request/index.js):

  • 请求计数器模式管理 Loading:维护 requestCount 计数器,多个并发请求时只显示一次 Loading Toast,所有请求结束后才关闭,避免 Loading 闪烁
  • 支持 skipLoading 配置:通过 config.skipLoading 跳过特定请求的 Loading 显示
  • 请求拦截器:自动展示全局 Loading
  • 响应拦截器:自动关闭 Loading、解包 response.data、处理 401 未授权状态
  • 超时控制:6 秒超时
  • API 代理:Vite 开发服务器将 /api 代理至 https://news-at.zhihu.com/api/4,解决跨域问题

4.4 移动端适配方案

  • rem 适配amfe-flexible 动态计算根字体大小 + postcss-pxtorem(rootValue: 75)自动将 px 转换为 rem
  • Vite 插件unplugin-auto-import 实现 Vue、Vue Router、Pinia API 的自动导入,减少样板代码
  • Vant 组件库:提供符合移动端交互规范的 UI 组件

5. 核心功能与技术实现

5.1 首页新闻流(Home)

  • 轮播推荐:使用 van-swipe 展示 top_stories 热门新闻,3 秒自动轮播
  • 无限滚动加载:使用 van-list 组件的 @load 事件,滚动到底部自动请求前一天新闻(/api/news/before/{date}),通过维护 date 链实现按日期倒序的无限加载
  • 骨架屏:初始加载时展示带 shimmer 动画的骨架屏,避免白屏
  • 粘性导航栏(Head.vue):position: sticky 固定顶部,监听 scroll 事件实现滚动后毛玻璃背景效果(backdrop-filter: blur

5.2 新闻详情页(Detail)

详情页是功能最复杂的页面,包含以下技术要点:

  • 动态样式注入:知乎 API 返回的新闻包含外部 CSS 链接,通过 document.createElement('link') 动态注入到 <head>,并在页面卸载时(onUnmounted)清理,防止样式污染
  • 图片占位处理:知乎新闻正文的首图通过 .img-place-holder 占位元素定位,使用 Image 对象预加载后插入 DOM,处理加载失败的降级逻辑
  • HTML 正文渲染:使用 v-html 渲染 API 返回的 HTML 格式正文内容
  • 点赞粒子动画:点赞时触发 createParticleEffect,在点赞按钮周围生成多个随机位置、旋转、缩放、透明度的粒子元素,通过 requestAnimationFrame 驱动动画循环,实现视觉反馈
  • 评论弹窗:使用 van-popup 底部弹出层,支持发表评论、查看评论列表、删除自己的评论,未登录时引导跳转登录页
  • 收藏/点赞状态:通过 computed 响应式读取 Pinia store 中的状态,点赞计数合并 API 返回的基础计数与用户本地增量

5.3 登录页(Login)

  • SVG 组件化 Logo:使用 Vue 的 h() 渲染函数动态创建 SVG 知乎 Logo,无需外部图片资源
  • 登录表单动画:点击"手机号登录"后,Logo 区域通过 CSS transform: translateY 上移,表单区域使用 transition 组件滑入,底部按钮区域绝对定位
  • 验证码倒计时:模拟发送验证码流程,60 秒倒计时,自动填入随机 4 位验证码(演示用途)
  • 手机号正则校验/^1[3-9]\d{9}$/ 验证中国大陆手机号格式
  • 视觉效果:渐变背景 + 浮动装饰元素 + 毛玻璃 Logo 容器 + 呼吸灯动画

5.4 个人中心(Personal)

  • 用户统计面板:展示收藏、点赞、评论数量,可点击跳转对应列表页
  • 暗黑模式切换van-switch 绑定 themeStore.isDark,切换时同步更新 DOM 属性和 localStorage
  • 退出登录showConfirmDialog 二次确认后清除用户状态

5.5 收藏/点赞/评论列表

  • 分批加载策略:列表页仅存储新闻 ID,通过 Promise.all 并发请求每批 5 条新闻详情,结合 van-list 的无限滚动实现渐进加载,避免一次性请求过多接口
  • 响应式监听watch 监听 store 中对应数组的变化,数据变更时自动重新加载列表
  • 相对时间显示:评论时间格式化为"刚刚/X分钟前/X小时前/X天前"的相对时间

5.6 暗黑模式系统

  • CSS 变量方案:在 theme.css 中通过 [data-theme="dark"] 选择器覆盖 Vant 组件的 CSS 变量(背景色、文字色、边框色等)
  • 组件级适配:各页面样式中使用 var(--van-xxx, fallback) 引用变量,确保主题切换时全局生效
  • 系统偏好检测:首次访问时通过 window.matchMedia('(prefers-color-scheme: dark)') 检测系统主题偏好

6. 技术亮点总结

  1. 请求并发管理:请求计数器模式解决多并发请求下 Loading 闪烁问题
  2. 动态资源注入/清理:详情页外部 CSS 的动态注入与卸载清理,避免样式污染
  3. 响应式数据设计reactive({}) 解决 Pinia 中动态 key 对象的响应式追踪问题
  4. 粒子动画系统:基于 requestAnimationFrame 的点赞粒子特效,无第三方动画库依赖
  5. 分批并发加载:收藏/点赞列表的 Promise.all + 分页策略,平衡加载速度与接口压力
  6. 主题系统:CSS 变量 + data 属性的轻量级暗黑模式方案,覆盖 Vant 组件默认样式
  7. 移动端适配:rem 方案 + Vant 组件 + 视口配置,完整的移动端适配链路
  8. SVG 组件化:使用 Vue 渲染函数创建 SVG,零图片依赖