[{"data":1,"prerenderedAt":795},["ShallowReactive",2],{"lab-/labs/zhdr":3},{"id":4,"title":5,"author":6,"body":7,"category":780,"date":781,"description":23,"extension":782,"featured":783,"home_position":784,"image":784,"meta":785,"navigation":786,"order":787,"path":788,"seo":789,"status":790,"stem":791,"tags":792,"__hash__":794},"content/labs/ZHDR.md","ZHDR","sibuchen",{"type":8,"value":9,"toc":758},"minimark",[10,15,20,24,38,41,45,134,136,140,150,152,156,161,172,178,181,185,196,204,249,257,275,279,286,348,352,383,385,389,393,453,457,460,535,539,586,590,620,624,654,658,694,696,700],[11,12,14],"h1",{"id":13},"zhdr-知乎日报移动端-web-应用","ZHDR - 知乎日报（移动端 Web 应用）",[16,17,19],"h2",{"id":18},"_1-项目概述","1. 项目概述",[21,22,23],"p",{},"知乎日报是一款面向移动端的新闻资讯阅读应用，基于知乎日报公开 API，实现了新闻浏览、详情阅读、用户登录、收藏、点赞、评论等完整功能。项目采用 Vue 3 全家桶开发，注重移动端适配体验与交互细节。",[21,25,26,30,31,34,37],{},[27,28,29],"strong",{},"项目类型："," 移动端 SPA（Single Page Application）",[32,33],"br",{},[27,35,36],{},"项目规模："," 17 个源文件，7 个页面视图，3 个状态管理模块",[39,40],"hr",{},[16,42,44],{"id":43},"_2-技术栈","2. 技术栈",[46,47,48,61],"table",{},[49,50,51],"thead",{},[52,53,54,58],"tr",{},[55,56,57],"th",{},"分类",[55,59,60],{},"技术选型",[62,63,64,78,86,94,102,110,118,126],"tbody",{},[52,65,66,70],{},[67,68,69],"td",{},"前端框架",[67,71,72,73,77],{},"Vue 3（Composition API + ",[74,75,76],"code",{},"\u003Cscript setup>","）",[52,79,80,83],{},[67,81,82],{},"构建工具",[67,84,85],{},"Vite",[52,87,88,91],{},[67,89,90],{},"状态管理",[67,92,93],{},"Pinia",[52,95,96,99],{},[67,97,98],{},"路由管理",[67,100,101],{},"Vue Router 4",[52,103,104,107],{},[67,105,106],{},"UI 组件库",[67,108,109],{},"Vant 4（移动端）",[52,111,112,115],{},[67,113,114],{},"HTTP 请求",[67,116,117],{},"Axios（封装拦截器）",[52,119,120,123],{},[67,121,122],{},"样式方案",[67,124,125],{},"SCSS + CSS 变量（主题系统）",[52,127,128,131],{},[67,129,130],{},"移动端适配",[67,132,133],{},"amfe-flexible + postcss-pxtorem（px 自动转 rem）",[39,135],{},[16,137,139],{"id":138},"_3-项目结构","3. 项目结构",[141,142,147],"pre",{"className":143,"code":145,"language":146},[144],"language-text","src/\n├── main.js                    # 应用入口\n├── App.vue                    # 根组件\n├── router/\n│   └── index.js               # 路由配置（7 条路由，全部懒加载）\n├── request/\n│   └── index.js               # Axios 实例封装（拦截器 + Loading 管理）\n├── stores/\n│   ├── user.js                # 用户状态（登录态、收藏、点赞、评论）\n│   ├── theme.js               # 主题状态（亮色/暗黑模式）\n│   └── counter.js             # 计数器状态\n├── views/\n│   ├── Home/                  # 首页（轮播 + 新闻列表 + 无限滚动）\n│   │   ├── Home.vue\n│   │   ├── Head.vue           # 粘性导航栏（滚动毛玻璃效果）\n│   │   └── NewsItem.vue       # 新闻卡片组件\n│   ├── Detail/                # 新闻详情页（核心页面）\n│   │   └── Detail.vue\n│   ├── Login/                 # 登录页（SVG Logo + 动效）\n│   │   └── Login.vue\n│   ├── Personal/              # 个人中心\n│   │   └── Personal.vue\n│   ├── Favorite/              # 我的收藏\n│   │   └── Favorite.vue\n│   ├── Likes/                 # 我的点赞\n│   │   └── Likes.vue\n│   └── Comments/              # 我的评论\n│       └── Comments.vue\n└── assets/\n    ├── images/\n    └── styles/\n        └── theme.css          # 暗黑模式全局样式\n","text",[74,148,145],{"__ignoreMap":149},"",[39,151],{},[16,153,155],{"id":154},"_4-架构设计","4. 架构设计",[157,158,160],"h3",{"id":159},"_41-路由与页面导航","4.1 路由与页面导航",[21,162,163,164,167,168,171],{},"采用 Vue Router 4 的 ",[74,165,166],{},"createWebHistory"," 模式，所有路由组件均使用 ",[27,169,170],{},"动态 import（懒加载）","，减少首屏加载体积：",[141,173,176],{"className":174,"code":175,"language":146},[144],"/              → Home（首页新闻列表）\n/detail/:id?   → Detail（新闻详情）\n/personal      → Personal（个人中心）\n/login         → Login（登录页）\n/favorite      → Favorite（我的收藏）\n/likes         → Likes（我的点赞）\n/comments      → Comments（我的评论）\n",[74,177,175],{"__ignoreMap":149},[21,179,180],{},"页面导航关系：首页通过新闻卡片进入详情页，个人中心可跳转至收藏/点赞/评论子页面，详情页内可跳转至登录页。",[157,182,184],{"id":183},"_42-状态管理pinia","4.2 状态管理（Pinia）",[21,186,187,188,191,192,195],{},"使用 Pinia 的 Composition API 风格（",[74,189,190],{},"defineStore"," + ",[74,193,194],{},"setup"," 函数），划分为三个独立 Store：",[21,197,198],{},[27,199,200,203],{},[74,201,202],{},"user.js"," — 用户核心状态",[205,206,207,214,220,226,236,242],"ul",{},[208,209,210,213],"li",{},[74,211,212],{},"userInfo","：用户信息（手机号、头像、登录时间）",[208,215,216,219],{},[74,217,218],{},"favorites","：收藏的新闻 ID 列表",[208,221,222,225],{},[74,223,224],{},"likes","：点赞的新闻 ID 列表",[208,227,228,231,232,235],{},[74,229,230],{},"likeCounts","：点赞计数（使用 ",[74,233,234],{},"reactive({})"," 解决动态 key 响应式问题）",[208,237,238,241],{},[74,239,240],{},"myComments","：用户评论列表（含新闻 ID、标题、内容、时间戳）",[208,243,244,245,248],{},"所有状态变更后即时同步写入 ",[74,246,247],{},"localStorage","，实现持久化",[21,250,251],{},[27,252,253,256],{},[74,254,255],{},"theme.js"," — 主题状态",[205,258,259,268],{},[208,260,261,262,264,265],{},"优先读取 ",[74,263,247],{}," 缓存，无缓存时检测系统偏好 ",[74,266,267],{},"prefers-color-scheme",[208,269,270,271,274],{},"通过 ",[74,272,273],{},"document.documentElement.setAttribute('data-theme', ...)"," 切换 CSS 变量",[157,276,278],{"id":277},"_43-网络请求层","4.3 网络请求层",[21,280,281,282,285],{},"对 Axios 进行统一封装（",[74,283,284],{},"request/index.js","）：",[205,287,288,298,312,318,328,334],{},[208,289,290,293,294,297],{},[27,291,292],{},"请求计数器模式管理 Loading","：维护 ",[74,295,296],{},"requestCount"," 计数器，多个并发请求时只显示一次 Loading Toast，所有请求结束后才关闭，避免 Loading 闪烁",[208,299,300,307,308,311],{},[27,301,302,303,306],{},"支持 ",[74,304,305],{},"skipLoading"," 配置","：通过 ",[74,309,310],{},"config.skipLoading"," 跳过特定请求的 Loading 显示",[208,313,314,317],{},[27,315,316],{},"请求拦截器","：自动展示全局 Loading",[208,319,320,323,324,327],{},[27,321,322],{},"响应拦截器","：自动关闭 Loading、解包 ",[74,325,326],{},"response.data","、处理 401 未授权状态",[208,329,330,333],{},[27,331,332],{},"超时控制","：6 秒超时",[208,335,336,339,340,343,344,347],{},[27,337,338],{},"API 代理","：Vite 开发服务器将 ",[74,341,342],{},"/api"," 代理至 ",[74,345,346],{},"https://news-at.zhihu.com/api/4","，解决跨域问题",[157,349,351],{"id":350},"_44-移动端适配方案","4.4 移动端适配方案",[205,353,354,368,377],{},[208,355,356,359,360,363,364,367],{},[27,357,358],{},"rem 适配","：",[74,361,362],{},"amfe-flexible"," 动态计算根字体大小 + ",[74,365,366],{},"postcss-pxtorem","（rootValue: 75）自动将 px 转换为 rem",[208,369,370,359,373,376],{},[27,371,372],{},"Vite 插件",[74,374,375],{},"unplugin-auto-import"," 实现 Vue、Vue Router、Pinia API 的自动导入，减少样板代码",[208,378,379,382],{},[27,380,381],{},"Vant 组件库","：提供符合移动端交互规范的 UI 组件",[39,384],{},[16,386,388],{"id":387},"_5-核心功能与技术实现","5. 核心功能与技术实现",[157,390,392],{"id":391},"_51-首页新闻流home","5.1 首页新闻流（Home）",[205,394,395,409,430,436],{},[208,396,397,400,401,404,405,408],{},[27,398,399],{},"轮播推荐","：使用 ",[74,402,403],{},"van-swipe"," 展示 ",[74,406,407],{},"top_stories"," 热门新闻，3 秒自动轮播",[208,410,411,400,414,417,418,421,422,425,426,429],{},[27,412,413],{},"无限滚动加载",[74,415,416],{},"van-list"," 组件的 ",[74,419,420],{},"@load"," 事件，滚动到底部自动请求前一天新闻（",[74,423,424],{},"/api/news/before/{date}","），通过维护 ",[74,427,428],{},"date"," 链实现按日期倒序的无限加载",[208,431,432,435],{},[27,433,434],{},"骨架屏","：初始加载时展示带 shimmer 动画的骨架屏，避免白屏",[208,437,438,441,442,445,446,449,450,77],{},[27,439,440],{},"粘性导航栏","（Head.vue）：",[74,443,444],{},"position: sticky"," 固定顶部，监听 ",[74,447,448],{},"scroll"," 事件实现滚动后毛玻璃背景效果（",[74,451,452],{},"backdrop-filter: blur",[157,454,456],{"id":455},"_52-新闻详情页detail","5.2 新闻详情页（Detail）",[21,458,459],{},"详情页是功能最复杂的页面，包含以下技术要点：",[205,461,462,480,494,503,517,526],{},[208,463,464,467,468,471,472,475,476,479],{},[27,465,466],{},"动态样式注入","：知乎 API 返回的新闻包含外部 CSS 链接，通过 ",[74,469,470],{},"document.createElement('link')"," 动态注入到 ",[74,473,474],{},"\u003Chead>","，并在页面卸载时（",[74,477,478],{},"onUnmounted","）清理，防止样式污染",[208,481,482,485,486,489,490,493],{},[27,483,484],{},"图片占位处理","：知乎新闻正文的首图通过 ",[74,487,488],{},".img-place-holder"," 占位元素定位，使用 ",[74,491,492],{},"Image"," 对象预加载后插入 DOM，处理加载失败的降级逻辑",[208,495,496,400,499,502],{},[27,497,498],{},"HTML 正文渲染",[74,500,501],{},"v-html"," 渲染 API 返回的 HTML 格式正文内容",[208,504,505,508,509,512,513,516],{},[27,506,507],{},"点赞粒子动画","：点赞时触发 ",[74,510,511],{},"createParticleEffect","，在点赞按钮周围生成多个随机位置、旋转、缩放、透明度的粒子元素，通过 ",[74,514,515],{},"requestAnimationFrame"," 驱动动画循环，实现视觉反馈",[208,518,519,400,522,525],{},[27,520,521],{},"评论弹窗",[74,523,524],{},"van-popup"," 底部弹出层，支持发表评论、查看评论列表、删除自己的评论，未登录时引导跳转登录页",[208,527,528,307,531,534],{},[27,529,530],{},"收藏/点赞状态",[74,532,533],{},"computed"," 响应式读取 Pinia store 中的状态，点赞计数合并 API 返回的基础计数与用户本地增量",[157,536,538],{"id":537},"_53-登录页login","5.3 登录页（Login）",[205,540,541,551,565,571,580],{},[208,542,543,546,547,550],{},[27,544,545],{},"SVG 组件化 Logo","：使用 Vue 的 ",[74,548,549],{},"h()"," 渲染函数动态创建 SVG 知乎 Logo，无需外部图片资源",[208,552,553,556,557,560,561,564],{},[27,554,555],{},"登录表单动画","：点击\"手机号登录\"后，Logo 区域通过 CSS ",[74,558,559],{},"transform: translateY"," 上移，表单区域使用 ",[74,562,563],{},"transition"," 组件滑入，底部按钮区域绝对定位",[208,566,567,570],{},[27,568,569],{},"验证码倒计时","：模拟发送验证码流程，60 秒倒计时，自动填入随机 4 位验证码（演示用途）",[208,572,573,359,576,579],{},[27,574,575],{},"手机号正则校验",[74,577,578],{},"/^1[3-9]\\d{9}$/"," 验证中国大陆手机号格式",[208,581,582,585],{},[27,583,584],{},"视觉效果","：渐变背景 + 浮动装饰元素 + 毛玻璃 Logo 容器 + 呼吸灯动画",[157,587,589],{"id":588},"_54-个人中心personal","5.4 个人中心（Personal）",[205,591,592,598,611],{},[208,593,594,597],{},[27,595,596],{},"用户统计面板","：展示收藏、点赞、评论数量，可点击跳转对应列表页",[208,599,600,359,603,606,607,610],{},[27,601,602],{},"暗黑模式切换",[74,604,605],{},"van-switch"," 绑定 ",[74,608,609],{},"themeStore.isDark","，切换时同步更新 DOM 属性和 localStorage",[208,612,613,359,616,619],{},[27,614,615],{},"退出登录",[74,617,618],{},"showConfirmDialog"," 二次确认后清除用户状态",[157,621,623],{"id":622},"_55-收藏点赞评论列表","5.5 收藏/点赞/评论列表",[205,625,626,639,648],{},[208,627,628,631,632,635,636,638],{},[27,629,630],{},"分批加载策略","：列表页仅存储新闻 ID，通过 ",[74,633,634],{},"Promise.all"," 并发请求每批 5 条新闻详情，结合 ",[74,637,416],{}," 的无限滚动实现渐进加载，避免一次性请求过多接口",[208,640,641,359,644,647],{},[27,642,643],{},"响应式监听",[74,645,646],{},"watch"," 监听 store 中对应数组的变化，数据变更时自动重新加载列表",[208,649,650,653],{},[27,651,652],{},"相对时间显示","：评论时间格式化为\"刚刚/X分钟前/X小时前/X天前\"的相对时间",[157,655,657],{"id":656},"_56-暗黑模式系统","5.6 暗黑模式系统",[205,659,660,674,684],{},[208,661,662,665,666,669,670,673],{},[27,663,664],{},"CSS 变量方案","：在 ",[74,667,668],{},"theme.css"," 中通过 ",[74,671,672],{},"[data-theme=\"dark\"]"," 选择器覆盖 Vant 组件的 CSS 变量（背景色、文字色、边框色等）",[208,675,676,679,680,683],{},[27,677,678],{},"组件级适配","：各页面样式中使用 ",[74,681,682],{},"var(--van-xxx, fallback)"," 引用变量，确保主题切换时全局生效",[208,685,686,689,690,693],{},[27,687,688],{},"系统偏好检测","：首次访问时通过 ",[74,691,692],{},"window.matchMedia('(prefers-color-scheme: dark)')"," 检测系统主题偏好",[39,695],{},[16,697,699],{"id":698},"_6-技术亮点总结","6. 技术亮点总结",[701,702,703,709,715,723,732,741,747,752],"ol",{},[208,704,705,708],{},[27,706,707],{},"请求并发管理","：请求计数器模式解决多并发请求下 Loading 闪烁问题",[208,710,711,714],{},[27,712,713],{},"动态资源注入/清理","：详情页外部 CSS 的动态注入与卸载清理，避免样式污染",[208,716,717,359,720,722],{},[27,718,719],{},"响应式数据设计",[74,721,234],{}," 解决 Pinia 中动态 key 对象的响应式追踪问题",[208,724,725,728,729,731],{},[27,726,727],{},"粒子动画系统","：基于 ",[74,730,515],{}," 的点赞粒子特效，无第三方动画库依赖",[208,733,734,737,738,740],{},[27,735,736],{},"分批并发加载","：收藏/点赞列表的 ",[74,739,634],{}," + 分页策略，平衡加载速度与接口压力",[208,742,743,746],{},[27,744,745],{},"主题系统","：CSS 变量 + data 属性的轻量级暗黑模式方案，覆盖 Vant 组件默认样式",[208,748,749,751],{},[27,750,130],{},"：rem 方案 + Vant 组件 + 视口配置，完整的移动端适配链路",[208,753,754,757],{},[27,755,756],{},"SVG 组件化","：使用 Vue 渲染函数创建 SVG，零图片依赖",{"title":149,"searchDepth":759,"depth":759,"links":760},2,[761,762,763,764,771,779],{"id":18,"depth":759,"text":19},{"id":43,"depth":759,"text":44},{"id":138,"depth":759,"text":139},{"id":154,"depth":759,"text":155,"children":765},[766,768,769,770],{"id":159,"depth":767,"text":160},3,{"id":183,"depth":767,"text":184},{"id":277,"depth":767,"text":278},{"id":350,"depth":767,"text":351},{"id":387,"depth":759,"text":388,"children":772},[773,774,775,776,777,778],{"id":391,"depth":767,"text":392},{"id":455,"depth":767,"text":456},{"id":537,"depth":767,"text":538},{"id":588,"depth":767,"text":589},{"id":622,"depth":767,"text":623},{"id":656,"depth":767,"text":657},{"id":698,"depth":759,"text":699},"web","2026-01-05","md",false,null,{},true,6,"/labs/zhdr",{"title":5,"description":23},"ARCHIVED","labs/ZHDR",[780,793,6],"移动端","24E_BTTXeNFJFupLfUGMOCkjfTxYyCP4V6zh4776KOU",1781104361513]