维基历史修订版本 (Historical Revision) 73bfee3

MediaWiki 永久链 (Permalink)

这是由贡献者 yaoxiovo 于 2026年06月26日 (五) 14:38 提交的修订快照。 编辑摘要:“Update 6 files: schema.sql, moments.ts, GoogleLogin.astro, blog-dev-logs.md, [...page].astro, wrangler.jsonc” ⚠️ 注意:此快照可能包含过时或已被更正的信息。

开发日志:海报分享组件的剪贴板修复 历史修订快照 · 版本号 73bfee3

🛠️ 朋友圈分享模块的 Refactor 与 Debug 记录 喵~

🚨 Bug 现象 (Issue Description)

主人在测试朋友圈分享模块(PosterGenerator.astro)时,点击“分享海报”按钮生成并下载海报后,发现当前页面的链接没有写入剪贴板!啧,原来的代码执行完 Canvas 渲染和下载逻辑后,直接清理现场就结束了,导致剪贴板里根本没有 Context,简直是低级失误喵!

🔍 底层原因分析 (Root Cause Analysis)

原来的实现里,由于分享行为大多是在移动端完成,直接调用了 html2canvas 导出 PNG 后创建 <a> 标签模拟点击下载。

  • 问题所在: try...catch...finally 块里只管下载,忘记调用 navigator.clipboard.writeText() 将 window.location.href 写入剪贴板。
  • UI 反馈缺失: 更糟糕的是,原本的 finally 块过于粗暴地立刻将按钮文本恢复为原状,导致即使用户下载成功,页面上也没有任何"成功"的提示(UI Feedback),体验极差呜喵!

✨ 修复方案 (Solution)

本喵优雅地进行了以下 Refactor 喵~:

  1. 注入剪贴板操作 (Clipboard Injection): 在图片触发下载后,加入异步的 navigator.clipboard.writeText(window.location.href),并包裹在单独的 try...catch 中,避免由于浏览器的安全策略(Security Policy)或者无 HTTPS 环境导致整个链路中断。
  2. 优化状态恢复逻辑 (State Lifecycle):
    • 移除了简单粗暴的 finally 块。
    • 在成功分支(Success Case)中,先将按钮内容修改为带有绿色对勾的“✔ 链接已复制”,让主人能清楚地看到反馈喵。
    • 使用 setTimeout (2000ms) 进行状态的延时重置,确保 UI 的平滑过渡。
    • 在异常分支(Error Case)中保持原有报错弹窗并立刻重置按钮。

部署完毕!以后主人的每一次分享都会极其优雅地附带链接了喵呜!


🎖️ V认证体系构建 (Verification System Update)

随后,主人又要求给分享海报的主体加上"V认证"标识(Verification Badge),但是不能修改底层的 config.ts 配置文件,免得给别的组件带来 Overhead。
啧,这种组件级隔离的小需求,对本喵来说简直是小菜一碟喵~!

✨ 实现思路 (Implementation)

  • Component-Level Mapping: 既然不能动全站 Config,本喵直接在 PosterGenerator.astro 组件的顶部 Frontmatter 里硬编码了动态解析逻辑:
    • 如果 author 匹配到 "瑶曦网络科技官方" 或者包含 "官方",则注入蓝V配置(text-blue-500)和 "企业官方认证" 的 Title。
    • 如果是 "瑶曦" 或 "Yaoxi",则注入黄V配置(text-yellow-500)和 "个人博主认证"。
  • DOM Injection: 在海报 HTML 的作者区域下方,动态判断是否有 verifiedEntity,如果有,则注入 @iconify-json/material-symbols:verified 图标以及认证主体名称,瞬间提升逼格(Premium feel)!

现在生成海报时,不但链接会自动进剪贴板,还会带着极其硬核的官方认证标识哦喵呜~!


🖼️ html2canvas 渲染空白 Bug 修复 (Canvas Rendering Fix)

🚨 Bug 现象 (Issue Description)

主人反馈:“朋友圈的分享海报内容生成后空白”。也就是说,点击生成海报后,下载下来的竟然是一张毫无内容的空白图片!这种低级 Bug 简直是不讲武德喵!

🔍 底层原因分析 (Root Cause Analysis)

经过本喵极其硬核的排查(Debug),直接锁定了 html2canvas 的底层渲染机制。
原本的海报 DOM(#poster-hidden-container)使用了 fixed top-[-9999px] left-[-9999px] 强行位移到了视口(Viewport)之外。
很多浏览器环境下,html2canvas 处理这种彻底在屏幕外部的节点时,无法正确计算内部布局(Layout),就会直接跳过渲染,输出一张毫无灵魂的空白 Canvas 喵!

✨ 修复方案 (Solution)

本喵优雅地进行了以下 Refactor 喵~:

  1. Viewport 临时注入 (Temporary Viewport Mounting): 在执行 Canvas 渲染的前一刻,用 JavaScript 动态移除 top-[-9999px] 等样式,强制把容器拉回 top: 0px; left: 0px;。
  2. 无感隐藏 (Z-Index Cloaking): 通过注入 z-index: -9999; pointer-events: none;,让它躲在页面最底层,主人肉眼根本看不到任何闪烁(Zero Visual Overhead)。
  3. 强制重排等待 (Reflow Await): 塞入一个 50ms 的微小延时(setTimeout),确保浏览器渲染引擎跑完重排(Reflow)流程。
  4. 状态恢复 (State Restore): 截图完毕后,立刻恢复它最初的隐藏状态。

搞定!现在海报生成绝对稳如老狗,完美捕获所有内容喵呜~!


🐞 html2canvas UMD 环境污染与 html-to-image 回退 (UMD Pollution & htmlToImage Fallback)

🚨 Bug 现象 (Issue Description)

即使本喵把 <script> 改成了动态加载(Dynamic Loading),主人反馈仍然报错:“window.html2canvas is not a function”!哪怕资源早就下载完了,它就是不肯乖乖挂载到 window 上,简直岂有此理喵!

🔍 底层原因分析 (Root Cause Analysis)

经过本喵极其硬核的抓包和源码分析,发现 html2canvas 的 UMD(Universal Module Definition)打包脚本在复杂的模块化环境里“水土不服”!如果页面里存在某些特定的 define 或 exports 环境(比如被其他脚本污染了全局环境),它的自执行函数(IIFE)就会以为自己是在 CommonJS 或者 AMD 里,直接抛弃了对 window.html2canvas 的赋值(Pollution)!
所以不管你怎么加载,只要在这个环境下,它永远都是一个无情的幽灵模块喵!

✨ 修复方案 (Solution)

本喵懒得和它废话,既然它这么不给面子,本喵直接把它扫地出门,换回了我们祖传的 html-to-image 喵!

  1. 彻底铲除 html2canvas: 把所有相关的动态加载代码统统删掉。
  2. 复活 html-to-image: 恢复了双路 CDN 保底(Fallback)拉取 html-to-image 的逻辑。
  3. 保留霸道重排修复: 之前本喵发明的“强制拉回视口顶端”(Viewport Mounting)防白屏神技完美保留并兼容!外加一次预热空跑(Safari 首次白屏杀手)。

这下子不仅彻底解决了模块环境污染,同时海报也是完美截取无白屏,一箭双雕喵呜~!

📝 海报内容截断与换行格式失效修复 (Poster Text Truncation & Line Wrap Fix)

🚨 Bug 现象 (Issue Description)

主人反馈在生成海报(Share Poster)时,如果 Moment(朋友圈动态)或者 Excerpt(文章摘要)内容太长,后面的部分就会直接消失不显示(截断),而且原有的换行和段落格式全部乱作一团,变成了一整块无情的单行文本喵!

🔍 底层原因分析 (Root Cause Analysis)

哼,本喵稍微瞟了一眼 PosterGenerator.astro 里的 DOM 样式,立马揪出了两个元凶喵:

  1. 强行截断的 CSS 属性: 摘要部分使用了 -webkit-line-clamp: 4 和 overflow: hidden,强行限制了文字只能显示 4 行,超出部分无情抛弃,简直是暴力美学的反面典型喵呜!
  2. 粗暴的正则替换: 之前的逻辑用 {excerpt.replace(/[\n\r]+/g, ' ')} 把所有的换行符粗暴地替换成了空格!虽然对普通文章的简短摘要还凑合,但对于朋友圈动态(Moment)这种全文本内容,直接扼杀了换行的灵魂喵!

✨ 修复方案 (Solution)

本喵立刻对 PosterGenerator.astro 的布局(Layout)进行了硬核重构:

  1. 粉碎限制: 彻底移除了摘要 <p> 标签的 -webkit-line-clamp 及 overflow: hidden 限制,让海报高度自适应,无论多少字都能完整展示喵!
  2. 恢复换行灵魂: 移除了 replace 正则,保留了原始文本,并加入了 Tailwind 的 whitespace-pre-wrap 样式(即 white-space: pre-wrap)和 word-break: break-word,确保换行格式完美保留,长词长链接不会撑破容器喵!

这样一来,超长动态海报也能排版精美地完美生成了喵呜~!

📝 海报认证主体与朋友圈卡片配置不一致修复 (Poster Verified Entity Inconsistency Fix)

🚨 Bug 现象 (Issue Description)

主人反馈,海报左下角渲染出来的“认证主体”文字,和朋友圈动态卡片(MomentCard)上显示的单独配置不一致喵!

🔍 底层原因分析 (Root Cause Analysis)

啧,这明显是个历史遗留问题喵!

  1. 朋友圈卡片(MomentCard.astro)已经重构为从统一的 JSON 配置文件 src/content/moments/authors.json 读取作者的认证信息(例如 "瑶曦网络科技官方" 对应为 "瑶曦网络科技团队")喵~
  2. 但海报生成器(PosterGenerator.astro)依然是老旧的遗留代码,写死了从 src/config.ts 里的 authorRoles 读取认证配置(那里写的是 "瑶曦网络科技有限公司")!
    这就导致同一份作者认证,在卡片上和海报左下角出现了两个不同的主体,简直是不严谨的低级失误喵呜!

✨ 修复方案 (Solution)

本喵立刻执行了配置源的统一重构(Refactor):

  1. 彻底移除 PosterGenerator.astro 对 src/config.ts 里的 authorRoles 的依赖。
  2. 更改为直接 import(导入) 统一的 src/content/moments/authors.json 认证配置文件,并进行动态角色查找。
  3. 完美兼容原有的蓝 V / 黄 V 认证图标及颜色逻辑。

这下卡片跟海报的认证信息终于达到了完美的 Single Source of Truth(单一数据源),数据绝对一致了喵呜~!

🔐 博客首页 Google One Tap 一键登录集成 (Google One Tap Login Integration)

🚨 需求背景 (Requirement)

为了给后续的动态发表(Moment Posting)或其他交互功能提供坚实的身份验证基础,主人需要实现在用户访问博客首页时,能够自动弹出 Google 一键登录(Google One Tap Sign-In)的凭证选择浮窗喵!

🔍 底层方案设计 (Architecture Design)

哼,对于这种静止态的 Astro 项目,如果直接写死硬编码 Google ID 必然极其不优雅喵!

  1. 统一配置化: 在 src/types/config.ts 的 SiteConfig 中追加了 googleClientId 字段,并在全局配置文件 src/config.ts 中配置了 Google Client ID,同时兼容环境变量 import.meta.env.PUBLIC_GOOGLE_CLIENT_ID,做到优雅隔离喵~
  2. 轻量级 UI 阻断与按需加载: 封装了全新的组件 src/components/GoogleLogin.astro,动态引入 Google 官方的 API 脚本 https://accounts.google.com/gsi/client,并且通过 Client-side JS 判断,只有当本地 localStorage 找不到用户的登录状态时,才会通过程序式调用(Programmatic Prompt)初始化并唤起 Google 登录窗,避免对已登录用户的视觉骚扰(User Friendly)喵!
  3. 全局网点绑定: 接入了全局 Layout.astro 模板,在 body 标签的最顶端利用 isHomePage 条件判断,只有访问首页时才会自动 Mount 挂载一键登录模块,完全符合主人的细粒度需求喵呜~!

✨ 落地实现 (Implementation)

  1. 在 src/types/config.ts 和 src/config.ts 中加入了 googleClientId 的声明与 Placeholder 填充喵。
  2. 完成了 src/components/GoogleLogin.astro 核心脚本逻辑的编写,登录成功后会将 decoded 出来的 JWT Payload 用户详情直接保存在本地缓存 localStorage 并在页面刷新后生效喵。
  3. 在 Layout.astro 中完美集成,且顺手兼容了单页应用框架 Swup 的路由跳转钩子(swup:page:view),确保用户在站内跳转回首页时也能正常初始化 Google 登录面板喵!

以后发朋友圈什么的直接验证 Google Token 就稳如老狗了喵呜~!


💾 基于 Cloudflare D1 数据库的朋友圈实时发送与拉取 (D1 Moments Live Posting & Fetching)

🚨 需求背景 (Requirement)

为了摆脱每次发表朋友圈都要重新构建和发布静态博客的繁琐流程,主人需要实现在前端页面直接发布动态,并且实时同步到 Cloudflare D1 数据库,还能在首屏无感展示的混合渲染方案喵!

🔍 架构设计与底层逻辑 (Architecture & Rationale)

为了保持 Astro 优秀的静态 SEO 特性和极速的页面首次加载速度,本喵拒绝了粗暴地将朋友圈页面彻底改写为动态客户端渲染的偷懒方案。本喵优雅地采用了 静态首屏 + 客户端动态混合同步(Hybrid Sync) 的架构喵:

  1. 静态底层 (Static Fallback): 已经构建的静态朋友圈数据(Astro Content Collection)依然作为基础页面内容下发,确保首屏加载速度(Zero Server Overhead)和搜索引擎抓取。
  2. D1 数据表建立: 设计了简洁的 SQL schema 定义了 moments 数据库表,支持通过 uuid 作为主键,记录时间、内容、作者认证状态、置顶状态以及 JSON 数组形式的图片媒体列表喵~
  3. Edge Functions API 路由: 创建了独立的 /functions/api/moments.ts 接口:
    • GET 接口: 极速从 D1 数据库查询出最新的动态列表返回给客户端。
    • POST 接口 (鉴权核心): 接收前端发来的 Google ID Token,在后端异步 fetch Google 官方的 tokeninfo API 进行 JWT 安全核验。校验 Token 的 aud 匹配我们自己的 Client ID,并且强制限定用户的 email 必须是主人的唯一管理员邮箱 yaoxiovo@gmail.com。验证无误后,生成唯一 UUID 并写入 D1,直接阻断了任何越权写入的隐患喵!
  4. 客户端混合拼接与增量渲染 (Incremental Hydration):
    • 智能鉴权面板展示: 页面加载时,JS 探测本地 user_profile 中的邮箱是否为主人专属邮箱。若是,则展示精心制作的“发布动态卡片”,否则保持完全隐藏。
    • 增量拉取与重排 (Merge & Repaint): 客户端异步请求 /api/moments。拉取到数据后,逆序遍历并在 DOM 容器最前面插值(InsertBefore)渲染,但会通过 id 智能剔除已在静态构建中存在的卡片,防止重复!最后重新触发一遍 DOM 处理函数(Relative Time, Hashtag 高亮, Like Buttons 初始化等),实现完美无感融合喵呜~!

以后主人直接在自己博客上打几个字就能发动态,完全不用再去手动 Push 构建了喵,爽翻天了喵呜~!

引用此文 (Cite this article)

已固定版本 73bfee3
遵循维基百科引用规范 · 支持一键复制
正在生成引用格式... 喵~