2026-01-10 15:15:07
笔者维护的 VitePress 博客主题已经集成了非常多的功能,为便于在主题之外复用,因此有计划将其一部分功能分离出来,形成独立的插件。
现在又有AI加持,再已经有通用插件模板前提下,使用AI就能完成95%的插件工作量!
分离的 图片预览插件,效果如下:
组件样式实现参考了 Element Plus Image Viewer
接下来先简单介绍一下用法,再快速讲解核心原理。
插件开发基于之前创建的一个通用模板,vitepress-plugin-slot-inject-template,在模板的基础上,插件95%的代码由 Gemini 3.0 生成。
只需要 2 步:
pnpm add vitepress-plugin-image-preview
引入插件在 .vitepress/config.mts VitePress 配置文件中
import { defineConfig } from 'vitepress'
import { ImagePreviewPlugin } from 'vitepress-plugin-image-preview'
export default defineConfig({
vite: {
plugins: [
ImagePreviewPlugin()
]
}
})
这里只阐述关键点,细节与之前的公告插件类似,这里不做赘述。
VitePress 默认主题 Layout.vue 组件预设的一些插槽,只需将实现自定义组件注入到对应插槽为止即可。
所有的 slots 在 VitePress 文档里也有介绍
利用插件的 transform 钩子,将我们的 <ImagePreview /> 组件插入到 Layout.vue 的特定插槽位置
图片预览组件我这里使用的是 doc-before 和 page-top 两个插槽。
使用 alias 保证引入组件的路径正确映射。
// 仅包含关键代码
const componentName = 'ImagePreview'
const componentFile = `${componentName}.vue`
const aliasComponentFile = `${getDirname()}/components/${componentFile}`
function ImagePreviewPlugin(options = {}) {
return {
// 添加alias
config: () => {
return {
resolve: {
alias: {
[`./${componentFile}`]: aliasComponentFile
}
}
}
},
transform(code, id) {
// 筛选出 Layout.vue
if (id.endsWith('vitepress/dist/client/theme-default/Layout.vue')) {
let transformResult = code
// 插入组件
const slots = [options.slots || ['doc-before', 'page-top']].flat()
for (const slot of slots) {
const slotPosition = `<slot name="${slot}" />`
// 添加 ClientOnly 目的是避免组件在SSG的时候被渲染
transformResult = transformResult.replace(slotPosition, `${slotPosition}\n<ClientOnly><${componentName} /></ClientOnly>`)
}
// 导入组件
const setupPosition = '<script setup lang="ts">'
transformResult = transformResult.replace(setupPosition, `${setupPosition}\nimport ${componentName} from './${componentFile}'`)
return transformResult
}
},
}
}
采用虚拟模块的方式传递配置。
组件中导入配置:
import options from 'virtual:image-preview-options'
插件中处理虚拟模块:
const virtualModuleId = 'virtual:image-preview-options'
const resolvedVirtualModuleId = `\0${virtualModuleId}`
function ImagePreviewPlugin(options = {}) {
return {
// 省略其它无关代码...
resolveId(id) {
if (id === virtualModuleId) {
return resolvedVirtualModuleId
}
},
load(this, id) {
if (id === resolvedVirtualModuleId) {
return `export default ${stringify(options)}`
}
},
}
}
图片预览的核心逻辑在于监听图片的点击事件,获取图片列表,并显示预览遮罩。
onMounted时,给内容的容器注册点击事件,在点击的时候获取容器中所有的图片元素,然后做后续操作。onMounted(() => {
const wrapperId = imagePreviewOptions?.wrapperId || '#VPContent'
const docDomContainer = document.querySelector(wrapperId)
docDomContainer?.addEventListener('click', previewImage)
})
function previewImage(e: Event) {
const target = e.target as HTMLElement
const currentTarget = e.currentTarget as HTMLElement
if (target.tagName.toLowerCase() === 'img') {
const selector = imagePreviewOptions?.selector || '.content-container .main img,.VPPage img'
const imgs = currentTarget.querySelectorAll<HTMLImageElement>(selector)
const idx = Array.from(imgs).findIndex(el => el === target)
const urls = Array.from(imgs).map(el => el.src)
// 省略其它逻辑
}
}
在开发插件的过程中,笔者把此类基于 slot 位置注入的插件分离了一个模板 vitepress-plugin-slot-inject-template
有相关诉求的朋友,可以基于此模板,配合 AI 快速的开发各种基于插槽就可以实现的组件能力。
插件完整源码 vitepress-plugin-image-preview
最后再感叹一句,AI 太牛逼了,效率起飞。
欢迎评论区交流&指导。
2026-01-07 15:05:40
终于闯过最难的一关了,身体里的大患彻底刨除。
25年12月25日进行的胸腹主替换手术,时长约6小时,由心外顶尖医生"孙立忠"主刀,顺利拿下;近期状态恢复得还行,在这里记录分享一下相关细节。
25年2月4日,突发胸&背&腰痛,急诊去医院做了增强CT;确诊为主动脉夹层B型,整个降主动脉几乎完全撕裂。
当时我就在想是不是要做胸腹主置换了,工作了几年生活刚好起来就发难了。
最开始发病,急诊去了四川华西,但由于病情复杂,华西并没有给到可靠的治疗方案,给予常规止痛降血压治疗了1周,就给转到疗养医院(急性期内不让下床,让躺一个月)。
托朋友先去了北京安贞,找了朱俊明医生(顶尖的心外医生),得到的结论也是做胸腹主置换,让先床上躺够时间,等待血肿吸收。
于是在25年3月9日,就动身去了北京,线下面诊了医生,医生看过当下身体情况后,不是特别乐观,现在的状态不是很能支持这个手术,让考虑清楚再来找他。(安贞住院得排差不多1个月起的队)
3月14日的时候又突发心悸,120又拉到医院做了检查,病情又有所进展。
没招了,只好去最后一个选择了,上海德达医院,另一位心外科大佬“孙立忠”在这里坐镇。
"上海德达心血管医院" ,在心血管领域算是最后一站了,很多其他医院不收的复杂病例的都往这里跑。
这家医院是私立医院,有一些自主定价的额外费用不在医保范围内。
孙院长给了一个相对安全折中的治疗方案,难以操作的部分先通过介入处理,剩余部分再外科手术处理。
于是3月和9月做了两次介入,年底来复查的时候,未处置的部分进展过于迅速,就安排做外科手术,处理剩余病变部分。
常规的化验、各种超声、增强CT、脑部磁共振。
刚好也赶上年底病人相对较少,第四天就安排手术了!
备皮(头部以下,剃光光);灌肠(使用开塞露Plus😱)。
小概率会截瘫;术中大出血。
手术排在了第二台,上午没有排上,就安排了静脉补液。
下午大概 1:30 进的手术室,准备阶段我还清醒着,配合完整左右手的穿刺,还有背部脊柱侧的穿刺,说是术中必要的时候补充脑脊液。
这三针搞完,就准备推麻药了,医生告知准备睡觉了,松了一口气,不用醒着受罪了(非常有经验了,今年第三次全麻)。这次数了一下大概30次心跳左右就睡过去了。
晚上 7:30 左右手术结束,整个过程耗时6小时。
医生给到的答复是手术很成功,但肋间动脉质量差没缝合上,不过腰间的侧支循环供血比较好(前2次支架后长出了许多侧枝),有可能会起作用,不至于截瘫,一切要等我醒了之后活动一下才知道。
ICU 里一共呆了3天,第四天早上转入普通病房。
① 第一天:
大概是第二天早上被ICU护士唤醒的,醒来后第一时间就是感受一下腿脚哈哈。幸亏还是好的,护士也松了一口气。
身体状态如下:
醒了之后嘴里插着呼吸管,左右手都被绑着,避免抓挠呼吸管;护士说下午才能拔,这期间就没法说话,拔完4小时才能少量饮水,下午拔完呼吸管后,喉咙肿痛,声音沙哑。
下午家属进来探视的时候,我问护士可不可以喝点有味道的甜水,让家属买,护士说他这有冰红茶,说可以喝一点点。(一共喝了3次,每次10ml)
最难受的就是吸痰了,不过这次痰少,只吸了几次。
除了呼吸管,身上还有引流管,鼻子里有胃管,脖子/手/脚上都有穿刺的针。
痛其实还好,没啥感觉,第一天听护士说给了吗啡,还有一个止痛泵,会不停地提供止痛药,也可以在痛的厉害的时候自己手动按。
不过这药有致幻效果🍄,眼睛一闭上就各种幻象,睡觉也是不断地梦,人睡得浑浑噩噩的。
② 第二天
早上在超声复查身体的时候,胸腔右侧有许多积液,后背又挨了一小刀,穿刺引流。
今天的护士隔一段时间就给我翻一下身,然后使劲的拍背让咳痰。(哎哟,要了老命了)
③ 第三天
由于一直没有通气(放屁),今天给了一个促进肠道的药,中午晚上吃了两次,我感觉肚子里已经有气了,醒着的时候就不停地用手揉,累了就睡。
没通气就不能吃东西,水的话一小时也只能喝几十毫升
这两天脑脊液量差不多达标了,晚上把背上脑脊液引流的针拔了,需要平躺6小时,没搞好的话说会引起头疼。
④ 第四天
一大早ICU 医生看了一下状态指标都还可以,就吩咐护士把身上能拔的管子和针都拔了,留了左腹部引流和脖子上输液的针,然后转到了普通病房,
12月29日早上转回的普通病房。
护士交接的时候发现,背上有比较严重的压疮,在ICU里躺太久,皮肤压伤了,建议尽量侧着躺,背上搞了一些贴贴保护。
早上给伤口消毒的时候才看见伤口长啥样:
普通病房的床睡着非常不舒服,第一天有止痛泵还好,给人能整得迷迷糊糊的睡。
第二天止痛泵就用完了,白天没精神的时候就睡,伤口不算特别痛,但是两侧肩膀和左腰非常的酸胀,怎么躺都难受,只有特别疲惫的时候才能睡着,醒了就一身酸痛。
医生给搞了点膏药贴在几个痛处
第3/4天的时候状态稍微好点,肩膀不再酸胀了,偶尔床边坐坐,其它时间除了上厕所都是在床上躺着,在床上转辗反侧,滚来滚去。
第五天早上早床边坐了一会儿,突然腹股沟处的伤口刺痛,我赶紧上床准备躺一会儿,定睛一看伤口周围肿胀起来了,皮肤被撑得鼓鼓的。赶紧叫护士让医生过来看看。
医生来摸了一下,说有搏动,不排除“假性动脉瘤”的可能,如果确认是的话就得拉到手术室搞了。赶紧让超声来看看,由于当天还在元旦假期,只有一个超声医生值班,轮到我检查已经是过来好几个小时了。看完说里面没有血流信号,就把危险的情况排除了,说可能是血肿,内部伤口渗液,说会自行吸收。
次日就没下床活动了,担心给血肿搞大;早上医生查房,主任医生摸了一下说有可能是淋巴漏了,血管都是缝合过的不可能漏。
第七天早上查房发现腹部的伤口一直没有结痂,有脂肪液化的现象,说要挤出来,然后就由医生单独用手挤压伤口然后消毒,脂肪液化预估要挤好几天。
第八天继续搞脂肪液化的问题,但出现了小插曲,医生在用力挤的时候,我突然感觉腹股沟有一股热流,感觉什么东西破了一样,然后看到之前肿胀的位置变得更大了,给医生吓一跳,赶紧让超声第一时间来看什么情况。
让超声医生仔细看一下到底有没有血流,最后还是没有血流信号,不过下面的动/静脉都有一定程度受压,医生说得搞一下给里面东西抽出来看看到底是什么。
于是叫护士拿了一堆不同规格的针筒过来,先用 1ml 的扎了下去,抽出来是黄色的液体,医生松了一口气,确定就是淋巴液。
然后换成 10ml 的,这个针头明细感觉大一些,扎下去有明细疼痛,抽完后又准备换20ml的,我说哎要不就10ml的抽,20ml的扎着会更痛。医生赶紧说别担心,这次只换针筒不换针头,不用再挨一针,连着抽了2个20ml后,鼓包就消下去了,剩下的一点说让自行吸收,避免针头探太深,扎到动脉。
抽完就加压包扎了一下,说压几天,避免持续渗液。
第九天脂肪液化还是存在,导致伤口没法结痂一直渗液。今天医生说重新缝合一下这部分,然后就在病房里一顿操作,局部麻醉,先把原来的伤口剪开,然后消毒清理后,重新缝合,缝了6针,非常的紧实能感觉到,肚皮都绷紧了的感觉。
整个操作过程除了打麻药痛,缝合就是有感觉,但不痛,麻药劲儿过了之后才开始渐渐有点感觉,皮肤表面缝合的线至少要14天才能拆。
现在整体状态还不错,能够坐起来玩玩手机和电脑了(第一时间就把本篇小作文码了出来),快的话估计最多还有1周就会叫出院了,伤口拆线估计得回家自己找医院拆了。
2025-12-23 12:13:05
上周六 ✈️ 到上海来复查一下身体情况,做了一些检查。
周一上午找了专家面诊,给了治疗建议,尽快手术处理,拖着就是定时炸弹,回家也是提心吊胆的。
来之前有做心理准备,只是比预期来得早一点。
周一下午就办了入院,这次是个大手术了,医生保守估计住院天数在20天左右。
这几天就搞各种检查,快的话这周就能把手术排上。
希望一切顺利!
大伙儿也多关注一下身体的健康情况,有问题及时就医。
2025-12-07 17:55:47
欢迎来到第 128 期的【视野修炼 - 技术周刊】,下面是本期的精选内容简介
下面开始本期内容的介绍ღ( ´・ᴗ・` )比心。
Anthropic 是 Claude 大模型背后的公司。
Bun 作者发布的阐述博客内容中翻:Bun 被 Anthropic 收购
一个终端工具,功能很丰富!
分析:
清理:
系统状态:
所有指令
React Server Components(RSC)出现了一个最高级别(CVSS 10) 的安全漏洞。
攻击者可以直接在目标服务器上执行恶意代码。
笔者没有跑 Next.js 应用,没有受到影响。
更多信息↓:
Cloudflare 本周又挂掉:因防御 React Server Components 漏洞,Cloudflare 遭遇 25 分钟服务故障
bug 如何产生和修复看这里: React Server Components RCE 漏洞分析
钻了原型链漏洞,修复只需加上 hasOwnProperty 就行!
鱼皮阐述受到攻击:Next.js高危漏洞致服务器被黑,我已中招!
如何发现和利用漏洞插件:Next.js无条件RCE漏洞 - 浏览器插件
https://github.com/mrknow001/RSC_Detector
code996 是一个分析工具,它可以统计 Git 项目的 commit 时间分布,进而推导出项目的编码工作强度。
npx code996
由 Rolldown 驱动。
支持多种样式定制。
周刊部分内容来源如下渠道,推荐大家关注。
2025-11-16 17:40:58
欢迎来到第 127 期的【视野修炼 - 技术周刊】,下面是本期的精选内容简介
下面开始本期内容的介绍ღ( ´・ᴗ・` )比心。
import task from 'tasuku'
task('Task 1', async () => {
await someAsyncTask()
})
task('Task 2', async () => {
await someAsyncTask()
})
task('Task 3', async () => {
await someAsyncTask()
})
在做 CLI 的时候可以用上,尤其需要处理多个异步任务时。
还支持嵌套,多状态展示等。
感觉和 @clack/prompts 和非常搭配。
收藏 ⭐️!
进程大杀器,支持多种终止进程的方式👍🏻,跨平台可用。
import fkill from 'fkill'
await fkill(1337)
console.log('Killed process')
fkill('Safari')
fkill(':8080')
fkill([1337, 'Safari', ':8080'])
也支持 CLI 调用。
npm install --global fkill-cli
fkill 1337
fkill Safari
fkill :8080
还支持 交互式 CLI。
赶紧装上。
免费的测试文件生成器,支持视频/图片/音乐/文档等多种格式。
收藏⭐️,基本覆盖了常见的文件格式!
使用 TypeScript&TSX 编写 UI,可以直接编译成 iOS、Android 和 macOS 上 由 Native 渲染的视图应用,无 JS 中间层。
笔者拉仓库试了一下 Demo,效果如下。
感觉还行,就是才刚开源,文档还不是特别友好,项目初始化流程不是特别标准。
包含上百种 JS 引擎的基本信息 ES 支持程度,性能等对比信息。
使用 Error.cause 处理错误,保留更清晰的堆栈跟踪信息。
function fetchUserData() {
try {
JSON.parse('{ broken: true }') // ← This will fail
}
catch (parseError) {
throw new Error('Failed to fetch user data', { cause: parseError })
}
}
try {
fetchUserData()
}
catch (err) {
console.error(err.message) // "Failed to fetch user data"
console.error(err.cause) // [SyntaxError: Unexpected token b in JSON]
console.error(err.cause instanceof SyntaxError) // true
}
这个免费的工具可以对你的仓库进行分析,提供主题标签建议和其他推荐的行动项,帮助开发者更容易地找到你的项目。
提升你在 GitHub 搜索、Google 和大型语言模型(LLMs)中的仓库可见性。
周刊部分内容来源如下渠道,推荐大家关注。
2025-11-08 19:48:20
欢迎来到第 126 期的【视野修炼 - 技术周刊】,下面是本期的精选内容简介
养了一段时间身体,又断更了 2 月 😄。
一个框架无关的 Web 组件,用于创建带有粒子动画效果的隐藏文字内容。
<h1>
Beautiful
<spoiler-span>
spoiler effects
</spoiler-span>|
</h1>
⭐️!
2025 年 8 月,TypeScript 超越了 Python 和 JavaScript,TypeScript 现在是 GitHub 上最常用的语言 。
做复杂日历组件必备!收藏+1。
my-script --name John --age 20
import { typeFlag } from 'type-flag'
const parsed = typeFlag({
name: String,
age: {
type: Number,
alias: 'a'
}
})
console.log(parsed.flags.name) // 'John'
console.log(parsed.flags.age) // 20
生成由 SVG 驱动的打字动画。
先收藏,uni-app 开发小程序可以考虑考虑。
开源的 macOS 桌面应用,可以生成电脑屏幕一天的时间占用。它每秒截一次屏,然后交给 AI 分析,生成你一天活动的时间线。
本地使用阿里开源的 qwen 模型。
支持以视频的形式回顾屏幕历史。
windows 上印象也也有类似的工具(待我翻翻吃灰的收藏夹 再贴上来)。
获取图片的尺寸信息,支持现代的js运行环境(浏览器、Node.js、Bun、Deno等)
import { imageDimensionsFromStream } from 'image-dimensions'
// example1
const url = 'https://sindresorhus.com/unicorn'
const { body } = await fetch(url)
console.log(await imageDimensionsFromStream(body))
// example2
const data = getImage()
console.log(imageDimensionsFromData(data))
// => {width: 1920, height: 1080, type: 'png'}
Biome 是一个适用于 JavaScript、TypeScript、JSX等快速格式化工具,与 Prettier 有高达 97% 的兼容覆盖率,同时也是一个高性能的 linter。
现在支持 Vue、Svelte 和 Astro lint和格式化了!
提供匹配表情符号的正则
const emojiRegex = require('emoji-regex')
const regex = emojiRegex()
for (const match of text.matchAll(regex)) {
const emoji = match[0]
}
⭐️!
周刊部分内容来源如下渠道,推荐大家关注。