详细介绍如何在VitePress中开发一个高性能的瀑布流相册组件PhotoGallery，支持MD文件配置、分类切换、图片点击放大、砌墙式布局、暗色模式辉光、省流加载等特性。瀑布流相册组件开发组件需求为了在网站中展示相册，我们需要开发一个瀑布流相册组件，满足以下需求：支持在MD文件中直接配置分类和图片多个分类时支持标签切换砌墙式瀑布流布局（固定卡片宽度，随机高度）图片越后面添加的越显示在前面圆角矩形卡片，无框线鼠标悬浮放大效果暗色模式下有辉光效果点击图片可放大查看（Lightbox）响应式布局页面顶部显示大标题和副标题省流加载机制（每次加载30张，滚动到底部加载更多）组件实现创建文件 docs/.vitepress/theme/components/PhotoGallery.vue： 用图片记录生活 每一张图片都是一个故事，一段回忆，一种情感的表达。 1" class="category-tabs"> {{ category.name }} 往下划动加载更多... 已经到底啦~ <Teleport to="body"> × </Teleport> 组件注册在 docs/.vitepress/theme/index.ts 中注册组件：import PhotoGallery from "./components/PhotoGallery.vue"; export default { enhanceApp({ app }) { app.component('PhotoGallery', PhotoGallery); }, }页面使用在MD文件中使用组件，通过 :categories 属性配置分类和图片：--- title: Mcoo Servers 相册 permalink: /photo layout: page article: false sidebar: false --- <PhotoGallery :categories="[ { name: '新年', photos: [ { src: 'https://example.com/1.jpg', alt: '新年1' }, { src: 'https://example.com/2.jpg', alt: '新年2' }, { src: 'https://example.com/3.jpg', alt: '新年3' }, ] }, { name: '风景', photos: [ { src: 'https://example.com/4.jpg', alt: '风景1' }, { src: 'https://example.com/5.jpg', alt: '风景2' }, ] } ]" />关键实现点1. Props接收MD配置使用 withDefaults 和 defineProps 接收MD文件传入的分类数据：interface Props { categories?: Category[] } const props = withDefaults(defineProps<Props>(), { categories: () => [ { name: '默认', photos: [ { src: '默认图片链接', alt: '照片1' }, ] } ] })2. 砌墙式瀑布流（固定宽度+随机高度）使用JavaScript动态计算每张图片的位置，实现真正的错落有致效果：const CARD_WIDTH = 260 const gap = 16 const columns = Math.max(1, Math.floor((containerWidth + gap) / (CARD_WIDTH + gap))) photoItems.value = displayedPhotos.value.map((photo) => { const minHeight = Math.min(...columnHeights) const minColumn = columnHeights.indexOf(minHeight) const photoHeight = getRandomHeight() // 随机高度 const left = minColumn * (CARD_WIDTH + gap) const top = columnHeights[minColumn] columnHeights[minColumn] += photoHeight + gap return { src, alt, height: photoHeight, left, top } })关键点：卡片宽度固定为 260px高度随机生成（180/220/260/300/340px）使用绝对定位 position: absolute动态计算 left 和 top 位置3. 暗色模式辉光效果使用 :global() 选择器为暗色模式添加辉光：:global(.dark) .photo-item:hover { box-shadow: 0 0 25px rgba(64, 158, 255, 0.35); }4. 图片点击放大（Lightbox + Teleport）使用 Teleport 将灯箱渲染到body，解决定位问题：const showLightbox = ref(false) const openLightbox = (src: string) => { currentImage.value = src showLightbox.value = true }<Teleport to="body"> × </Teleport>5. 省流加载（无限滚动）首次加载45张图片，预加载阈值20张，滚动到底部时加载更多：const displayedCount = ref(45) const BATCH_SIZE = 30 const PRELOAD_THRESHOLD = 20 const handleScroll = () => { if (!hasMore.value) return const scrollHeight = document.documentElement.scrollHeight const scrollTop = document.documentElement.scrollTop const clientHeight = document.documentElement.clientHeight const remaining = currentPhotos.value.length - displayedCount.value if (remaining <= PRELOAD_THRESHOLD) { displayedCount.value = Math.min(displayedCount.value + BATCH_SIZE, currentPhotos.value.length) nextTick(() => { distributePhotos() }) } else if (scrollTop + clientHeight >= scrollHeight - 100) { displayedCount.value = Math.min(displayedCount.value + BATCH_SIZE, currentPhotos.value.length) nextTick(() => { distributePhotos() }) } }6. 初始化加载检测首次加载时，容器宽度可能为0（DOM未渲染完成），需要使用 requestAnimationFrame 循环检测：onMounted(() => { const initGallery = () => { if (masonryRef.value && masonryRef.value.offsetWidth > 0) { distributePhotos() } else { requestAnimationFrame(initGallery) } } requestAnimationFrame(initGallery) window.addEventListener('resize', distributePhotos) window.addEventListener('scroll', handleScroll) })Bug修复记录Bug 1: 首次加载显示单列问题：首次进入相册页面时，图片全部排列在左边，变成长长的一列，需要切换分类后再切回来显示。原因：组件首次挂载时，容器宽度为0，导致列数计算错误。修复：使用 requestAnimationFrame 循环检测容器宽度是否有效：const initGallery = () => { if (masonryRef.value && masonryRef.value.offsetWidth > 0) { distributePhotos() } else { requestAnimationFrame(initGallery) } } requestAnimationFrame(initGallery)Bug 2: 图片重叠问题：图片卡片重叠在一起。原因：CSS中卡片宽度未固定，且列数计算有问题。修复：固定卡片宽度为260px使用固定宽度计算列数给容器添加 overflow: hiddenBug 3: 加载提示位置错误问题："往下划动加载更多"出现在页面正中间，下面还有渐变色带。原因：加载提示放在了瀑布流容器外面。修复：将加载提示放在 masonry-wrapper 内部，紧跟在瀑布流图片后面。参数说明参数类型必填说明categoriesCategory[]否分类数组，默认有一个默认分类Category类型：字段类型说明namestring分类名称photosPhotoItem[]该分类下的图片数组PhotoItem类型：字段类型说明srcstring图片链接（必填）altstring图片描述（可选）效果展示砌墙式布局：固定宽度260px，随机高度，实现错落有致的砌墙效果分类切换：多个分类时显示标签，点击切换省流加载：初始加载30张，滚动到底部时加载更多30张悬浮效果：鼠标放上图片有放大效果和阴影暗色模式：暗色模式下悬浮有蓝色辉光效果点击放大：点击图片可查看大图，点击空白处或关闭按钮退出
详细介绍如何开发一个高性能的自助投稿表单组件SubmissionForm，用于在VitePress中收集用户投稿资源。涵盖组件需求分析、Vue3+TypeScript代码实现、组件注册方法、动态Markdown生成、头像自动匹配、图片与下载链接动态增删、表单验证等核心功能的完整开发流程。自助投稿页面开发组件需求为了方便用户自助投稿资源到资源下载页面，我们需要开发一个投稿表单组件，满足以下需求：收集作品信息（作品名、资源类型）支持可选的B站视频嵌入收集作者信息（作者名、游戏ID、个人主页链接）游戏ID自动匹配服务器成员头像轮播图支持动态增删（最多5张）作品介绍支持换行下载链接支持动态增删，每个链接可设置介绍根据链接类型自动识别下载名称许可证选择一键生成并下载MD文件自动生成符合规范的Frontmatter组件实现创建文件 docs/.vitepress/theme/components/SubmissionForm.vue： 作品信息 作品名 * 资源类型 * 红石 建筑 B站视频（可选） 作者信息 作者名 * 游戏ID * 头像预览： 作者个人主页链接 * 许可证 * CC BY-NC-SA 4.0 CC BY-SA 4.0 MIT 轮播图 *（最多5张） 1" @click="removeImage(index)" class="btn-remove" type="button">× <button v-if="images.length < 5" @click="addImage" class="btn-add" type="button">+ 添加图片 作品介绍 * 下载链接 * 1" @click="removeDownload(index)" class="btn-remove" type="button">× + 添加下载链接 📥 下载 MD 文件 请填写所有必填项后才能下载 组件注册在 docs/.vitepress/theme/index.ts 中注册组件：import SubmissionForm from "./components/SubmissionForm.vue"; export default { enhanceApp({ app }) { app.component('SubmissionForm', SubmissionForm); }, }页面使用创建 docs/@pages/自助投稿.md：--- title: 自助投稿 permalink: /submission --- <SubmissionForm />关键实现点1. 动态增删图片和下载链接使用数组和动态添加/删除函数实现：const images = ref<{ src: string; alt: string }[]>([{ src: '', alt: '' }]) const addImage = () => { if (images.value.length < 5) { images.value.push({ src: '', alt: '' }) } } const removeImage = (index: number) => { if (images.value.length > 1) { images.value.splice(index, 1) } }2. 游戏ID自动匹配头像使用 computed 属性根据游戏ID动态计算头像路径：const avatarPath = computed(() => { if (gameId.value) { return `/mcoo/${gameId.value}.png` } return '' }) const showAvatarPreview = computed(() => { return !!gameId.value })注意：这里需要使用 computed 属性来包装条件判断，直接在模板中使用 v-if="gameId" 可能不会正确触发响应式更新。3. 头像加载错误处理当头像文件不存在时隐藏图片：const handleAvatarError = (event: Event) => { const img = event.target as HTMLImageElement img.style.display = 'none' }4. 下载链接自动识别名称根据链接类型自动识别并生成下载名称：const getDownloadName = (url: string) => { if (url.includes('github.com') && !url.includes('gh-proxy')) { return 'Github' } else if (url.includes('gh-proxy')) { return 'Github 加速' } else if (url.includes('pan.baidu.com') || url.includes('baidu.com')) { return '百度网盘' } else if (url.includes('qm.qq.com') || url.includes('qq.com')) { return 'Mcoo 大型咕咕集散中心' } return '下载链接' }5. 生成带换行的Description作品介绍需要支持换行，在生成MD时转换为 标签：const descLines = workDescription.value.split('\n') descLines.forEach(line => { if (line.trim()) { md += ` ${line.trim()} \n` } else { md += ' \n' } })6. Description自动处理将换行符替换为空格，并截取前150字：const cleanDesc = workDescription.value.replace(/\n/g, ' ').replace(/ /g, ' ').trim() const desc = cleanDesc.length > 150 ? cleanDesc.substring(0, 150) + '...' : cleanDesc7. 本地时间生成使用本地时间而非UTC时间：const dateStr = now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0') + '-' + String(now.getDate()).padStart(2, '0') + ' ' + String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0') + ':' + String(now.getSeconds()).padStart(2, '0')8. 表单验证使用 computed 属性进行表单验证：const isValid = computed(() => { const hasValidImages = images.value.some(img => img.src.trim()) const hasValidDownloads = downloads.value.some(d => d.url.trim()) return workName.value.trim() && authorName.value.trim() && gameId.value.trim() && authorLink.value.trim() && workDescription.value.trim() && hasValidImages && hasValidDownloads })参数说明字段类型必填说明bvNumberstring否B站视频BV号workNamestring是作品名称resourceTypestring是资源类型（redstone/building）authorNamestring是作者名称gameIdstring是游戏ID，用于匹配头像authorLinkstring是作者个人主页链接workDescriptionstring是作品介绍licensestring是许可证类型imagesarray是轮播图数组，最多5个downloadsarray是下载链接数组
详细介绍如何开发一个高性能的下载卡片组件DownloadCard，用于在VitePress中展示资源下载链接。涵盖组件需求分析、Vue3+TypeScript代码实现、组件注册方法、随机马卡龙色背景、按钮文字动态切换、响应式布局与暗色模式适配等核心功能的完整开发流程。下载卡片组件开发组件需求在资源下载页面中，我们需要一个能够集中展示多个下载链接的卡片组件，满足以下需求：支持多个下载链接，每个链接单独显示为一个卡片每个卡片显示：名称、描述、下载按钮根据链接类型动态切换按钮文字（下载/前往）卡片使用随机马卡龙色背景，每次刷新或添加新卡片时颜色不同响应式设计，适配移动端和桌面端悬浮动效提升用户体验组件实现创建文件 docs/.vitepress/theme/components/DownloadCard.vue： 0" class="download-section"> <path fill="currentColor" d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/> {{ item.name }} {{ item.description }} {{ getBtnText(item.url) }} <path fill="currentColor" d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/> 组件注册在 docs/.vitepress/theme/index.ts 中注册组件：import DownloadCard from "./components/DownloadCard.vue"; export default { enhanceApp({ app }) { app.component('DownloadCard', DownloadCard); }, }组件使用在 Markdown 文件中使用：<DownloadCard :downloads="[ { name: 'Github', url: 'https://github.com/xxx/release.zip', description: '官方下载地址，可能需要代理' }, { name: 'Github 加速', url: 'https://gh-proxy.org/https://github.com/xxx/release.zip', description: '使用 Gh Proxy 加速下载' }, { name: '百度网盘', url: 'https://pan.baidu.com/s/xxx?pwd=xxxx', description: '使用百度网盘进行下载，提取码：xxxx' }, { name: 'QQ群', url: 'https://qm.qq.com/q/xxx', description: '加入 QQ 群下载资源' } ]" />关键实现点1. 随机马卡龙色背景通过数组存储多种马卡龙色，使用索引取模实现每个卡片不同的颜色：const macaronColors = [ 'rgba(255, 182, 193, 0.25)', // 粉色 'rgba(173, 216, 230, 0.25)', // 浅蓝色 'rgba(144, 238, 144, 0.25)', // 浅绿色 // ... 更多颜色 ] const getRandomColor = (index: number) => { return macaronColors[index % macaronColors.length] }2. 按钮文字动态切换根据链接类型动态显示不同的按钮文字，QQ链接显示"立即前往"，其他链接显示"立即下载"：const getBtnText = (url: string) => { if (url.includes('qm.qq.com') || url.includes('qq.com')) { return '立即前往' } return '立即下载' }3. 屏蔽链接图标通过 CSS 伪元素 ::before 屏蔽主题自带的链接图标：.download-name, .download-btn { &::before { display: none !important; } }4. 响应式设计使用媒体查询适配移动端：@media (max-width: 640px) { .download-card { padding: 12px 16px; gap: 12px; } .download-icon { width: 40px; height: 40px; } }参数说明参数类型必填说明downloadsDownloadItem[]是下载项数组DownloadItem 类型参数类型必填说明namestring是下载项名称urlstring是下载链接descriptionstring否下载项描述
详细介绍如何开发一个高性能的作者卡片组件AuthorCard，用于在VitePress中展示作者信息。涵盖组件需求分析、Vue3+TypeScript代码实现、组件注册方法、随机马卡龙色背景、插槽传值、响应式布局与暗色模式适配等核心功能的完整开发流程，还包含将长文本内容作为插槽传递以避免特殊字符解析问题的技巧。概述本文介绍如何开发一个美观的作者卡片组件 AuthorCard，用于在 VitePress 中展示作者信息、简介和许可证等内容。组件需求圆角矩形卡片设计左上角作者头像（支持本地图片和图链）作者名点击跳转链接副名称显示（斜体）简介内容（支持多行文本和链接）许可证显示随机马卡龙色渐变背景悬浮动效响应式适配组件代码创建文件 docs/.vitepress/theme/components/AuthorCard.vue： {{ name }} {{ name }} {{ subtitle }} <slot name="description" /> 许可证：{{ license }} 注册组件在 docs/.vitepress/theme/index.ts 中注册组件：import AuthorCard from "./components/AuthorCard.vue"; export default { enhanceApp({ app }: { app: any }) { app.component('AuthorCard', AuthorCard); }, };使用方法基础用法<AuthorCard avatar="/avatar.png" name="作者名称" link="https://example.com" subtitle="副名称" license="MIT" > 这里是多行简介内容 </AuthorCard>完整示例<AuthorCard avatar="/mcoo/avatar.png" name="凉宫山风" link="https://space.bilibili.com/39934370" subtitle="Y_Suzumiya" license="CC BY-NC-SA 4.0" > 来自@方块工程师杯官方 比赛瑞士轮第四轮的赛博马场，快来和xdm来一场紧张刺激的赛马罢* 感兴趣的兄弟们可以进群一起赤石awa~：927868234 ---------------------------- Mcoo服务器基于 Fabric 1.21 的原版生存玩法运行... </AuthorCard>关键实现点1. 使用插槽传递长文本当需要传递包含特殊字符的多行文本时，使用 prop 传值会导致解析错误。解决方案是使用 Vue 插槽： <slot name="description" /> 使用时通过 传递内容：<AuthorCard ...> 多行文本内容... </AuthorCard>2. 随机马卡龙色背景使用 computed 和 Math.random() 实现每次渲染随机选择颜色：const macaronColors = [ { bg: 'rgba(255, 182, 193, 0.25)', bgEnd: 'rgba(255, 182, 193, 0.05)' }, // ... 更多颜色 ] const randomColor = computed(() => { const index = Math.floor(Math.random() * macaronColors.length) return macaronColors[index] })背景使用渐变，从顶部颜色到底部透明：:style="{ background: `linear-gradient(180deg, ${randomColor.bg} 0%, ${randomColor.bgEnd} 100%)` }"3. 屏蔽链接图标由于我们之前为外部链接添加了图标，所以需要通过 CSS 屏蔽：.name { &::before { display: none !important; } }4. 响应式适配使用媒体查询调整移动端样式：@media (max-width: 640px) { .author-card { padding: 12px 16px; gap: 12px; } .avatar { width: 48px; height: 48px; } }组件参数说明参数说明类型默认值avatar头像图片地址string-name作者名称string-link作者链接string#subtitle副名称（斜体）string''license许可证文本string''插槽说明description简介内容，支持 HTML 标签
详细介绍如何开发一个高性能的图片轮播组件ImageGallery，用于在VitePress中展示图片集。涵盖组件需求分析、Vue3+TypeScript代码实现、组件注册方法、响应式布局与暗色模式适配、箭头切换与缩略图导航等核心功能的完整开发流程。概述本文介绍如何开发一个高性能的图片轮播组件 ImageGallery，用于在 VitePress 中展示图片集。组件需求大图展示（圆角矩形，与 BiliVideo 风格一致）底部缩略图列表鼠标悬停大图显示左右箭头切换图片自动缩放填满框支持本地图片和图链轻量快速，不影响加载速度组件代码创建文件 docs/.vitepress/theme/components/ImageGallery.vue： 0" class="image-gallery"> 1" class="arrow arrow-left" :class="{ visible: showArrows }" @click="prev"> <path fill="currentColor" d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/> 1" class="arrow arrow-right" :class="{ visible: showArrows }" @click="next"> <path fill="currentColor" d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/> 注册组件在 docs/.vitepress/theme/index.ts 中注册组件：import ImageGallery from "./components/ImageGallery.vue"; export default { enhanceApp({ app }: { app: any }) { app.component('ImageGallery', ImageGallery); }, };使用方法在 Markdown 文件中使用：<ImageGallery :images="[ { src: '图片1地址.jpg', alt: '图片1' }, { src: '图片2地址.jpg', alt: '图片2' }, { src: '图片3地址.jpg', alt: '图片3' } ]" />关键实现点1. 类型定义使用 TypeScript 接口定义图片数组结构：interface Props { images: { src: string alt?: string }[] }2. 图片填充使用 padding-bottom 百分比实现响应式 16:9 容器：.main-image { padding-bottom: 56.25%; // 16:9 比例 } img { object-fit: cover; // 图片填满容器 }3. 箭头切换使用 v-if 控制箭头显示，通过点击事件切换图片：const showArrows = ref(false) const prev = () => { activeIndex.value = activeIndex.value === 0 ? props.images.length - 1 : activeIndex.value - 1 }4. 暗色模式适配使用 CSS 变量和媒体查询适配暗色模式：@media (prefers-color-scheme: dark) { box-shadow: 0 0 20px rgba(255, 255, 255, 0.1), 0 0 10px rgba(255, 255, 255, 0.2); }
详细介绍如何在绿联NAS上通过Docker部署CloudImgs私人图床。CloudImgs是一款简洁强大的自建图床程序，支持图片上传、管理和分享。通过绿联NAS的Docker容器化部署，可以轻松搭建私人图床服务，支持密码保护、批量上传、API接口等功能。配合PicGo/PicList客户端，还可实现截图即传、粘贴即用的便捷体验。准备工作1. 安装 Docker在绿联NAS系统中，进入 应用中心 → 搜索 Docker → 安装2. 创建存储文件夹在NAS的文件管理器中：进入 Docker 文件夹新建 cloudimgs 文件夹在 cloudimgs 文件夹内再新建 uploads 文件夹（用于存放上传的图片）Docker/ └── cloudimgs/ └── uploads/ # 上传的图片会存这里部署步骤步骤 1：拉取镜像打开 Docker 应用点击左侧 镜像 → 添加镜像镜像名称填写：qazzxxx/cloudimgs:latest点击 拉取等待拉取完成（可能需要几分钟，取决于网络速度）。步骤 2：创建容器镜像拉取完成后，点击 创建容器基本设置：容器名称：cloudimgs勾选 ✅ 开机自启端口设置：本地端口：3001（也可以改成其他端口如8080）容器端口：3001协议：TCP卷（存储）设置：添加文件夹：选择 /docker/cloudimgs/uploads装载路径：/app/uploads环境变量：变量名变量值说明PUID1000用户IDPGID1000用户组IDUMASK002权限掩码NODE_ENVproduction运行模式PORT3001端口STORAGE_PATH/app/uploads存储路径步骤 3：（可选）设置密码保护如果需要密码保护，在环境变量中添加：变量名变量值PASSWORD你的密码点击 创建步骤 4：启动容器容器创建完成后会自动启动，也可以在容器列表中手动启动。验证部署查看运行状态打开 Docker 应用点击左侧 容器找到 cloudimgs 容器，状态显示为 运行中 即为成功查看日志点击容器名称 → 日志，看到以下信息说明成功：Initializing database... Server running on port 3001 Starting file system sync... Sync completed.访问图床打开浏览器访问：http://你的NAS IP:3001如果设置了密码，会先提示输入密码如果没有设置密码，直接进入图床界面使用说明上传图片点击 上传 按钮或直接拖拽图片到页面上传完成后，点击图片可复制链接支持批量上传获取图片链接点击图片会自动复制 Markdown 格式链接也可在图片详情页获取 URL 链接配置反向代理（可选）如果想要通过域名直接访问，需要在NAS的反向代理中配置：外部域名：img.yourdomain.com内部地址：http://localhost:3001常见问题Q: 访问不了图床？A: 检查以下几点：容器是否正在运行端口是否正确（3001）绿联NAS防火墙是否开放了3001端口Q: 上传的图片在哪里？A: 图片存储在 NAS 的 /docker/cloudimgs/uploads 文件夹中Q: 如何升级图床？A:停止当前容器删除容器（不要删除卷/数据）重新拉取最新镜像重新创建容器（使用相同的卷挂载）Q: 如何备份图片？A: 只需备份 /docker/cloudimgs/uploads 文件夹即可Q: 密码忘记了怎么办？A: 停止容器，编辑环境变量中的 PASSWORD 值，重启容器即可配合 PicGo 上传图片（可选）如果你不想每次打开浏览器上传图片，可以使用 PicGo/PicList 客户端在本地直接上传。什么是 PicGo/PicList？图床（NAS）：存放图片的地方PicGo/PicList（本地）：上传工具，装在你日常使用的电脑上你的电脑（PicGo/PicList） → 上传 → NAS图床（CloudImgs）安装 PicList（推荐）PicList 是 PicGo 的增强版，功能更强大。下载安装 PicList安装 cloudimgs-uploader 插件打开 PicList → 点击左侧 插件设置在搜索框输入 cloudimgs-uploader点击 安装配置插件安装完成后，在插件列表中找到 cloudimgs-uploader，点击 设置，配置如下：参数说明示例服务器域名你的图床地址（不要加后面的接口路径）http://192.168.1.100:3001此为示例链接密码你设置的访问密码你的密码存储路径图片存放文件夹（可选）my_folder使用方法在 PicList 左侧找到 上传区，选择 云图（cloudimgs） 作为默认图床截图后按 Ctrl+V 粘贴，或拖拽图片到窗口图片会自动上传到NAS图床，并自动复制Markdown链接直接在文档中 Ctrl+V 粘贴即可配合编辑器使用Typora：设置 → 图像 → 插入图片时 → 上传图片 → 验证PicGo-CoreVSCode：安装 PicGo 插件，配置同样的图床这样截图或粘贴图片时，会自动上传到你的NAS图床，非常方便！
本文详细介绍了如何开发一个更好用的 VitePress 团队页面组件。原生的 VPTeamMembers 组件存在两个痛点：GitHub 头像在国内网络环境下加载缓慢，以及添加链接图标需要编写大段 SVG 代码。通过使用本地资源文件、TypeScript 类型定义和 CSS 悬浮动效，我们开发了一个轻量级、易维护的 TeamMembers 组件。该组件支持灵活的成员管理、多种链接平台图标自动映射，并实现了管理员/普通成员和在职/离职成员的自动分类显示功能。前言在 VitePress 中，官方提供了 VPTeamMembers 组件来展示团队成员。然而在实际使用中，我发现它存在两个明显的痛点：头像加载缓慢 - 直接引用 GitHub 头像源，国内网络环境下经常加载失败或超时添加链接图标繁琐 - 需要手动编写大段 SVG 代码，不仅难以维护，还影响文章可读性为了解决这些问题，我开发了一个更加易用的团队页面组件。实现思路核心目标使用本地头像图片，告别加载慢的问题链接图标也使用本地 SVG 文件支持灵活的成员管理（管理员/普通成员、在职/离职）添加悬浮动效提升视觉体验技术方案创建一个可复用的 Vue 组件 TeamMembers.vue使用 TypeScript 定义成员数据结构通过 props 接收标题、描述和成员列表使用 CSS 实现卡片悬浮效果组件开发目录结构docs/ ├── public/ │ ├── mcoo/ # 成员头像目录 │ │ ├── DoubleSpirit121.png │ │ └── RSEGordon.png │ └── icons/ # 链接图标目录 │ ├── github.svg │ ├── bilibili.svg │ └── ... └── .vitepress/ └── theme/ └── components/ └── TeamMembers.vue组件代码首先创建 TeamMembers.vue 组件： {{ title }} {{ description }} {{ member.name }} {{ member.title }} 样式设计为组件添加悬浮动效和响应式布局：.member-card { border-radius: 12px; background: var(--vp-c-bg-soft); transition: all 0.3s ease; } .member-card:hover { transform: translateY(-4px); box-shadow: 0 8px 30px rgba(var(--vp-c-brand-rgb), 0.15); } .member-card:hover .avatar { transform: scale(1.05); } .member-grid { display: flex; flex-wrap: wrap; justify-content: center; gap: 20px; max-width: 1200px; }全局注册组件在 index.ts 中注册组件，使其可以在 Markdown 文件中直接使用：import TeamMembers from "./components/TeamMembers.vue"; export default { extends: Teek, enhanceApp({ app }: { app: any }) { app.component('TeamMembers', TeamMembers); }, }使用方法准备资源文件将成员头像图片放入 docs/public/mcoo/ 目录将链接图标放入 docs/public/icons/ 目录（GitHub、Bilibili、QQ 等图标已准备）编写团队页面在 Markdown 文件中使用组件：--- title: 团队介绍 permalink: /team layout: page article: false sidebar: false --- Mcoo 墨客小筑 人活一世，草木一秋，开心即可，不必讨好。 <TeamMembers title="核心成员" description="主要维护者和开发者。" :members="teamMembers.filter(m => m.role === 'admin' && m.active !== false)" /> <TeamMembers title="团队成员" description="排名不分先后，感谢以下成员的贡献。" :members="teamMembers.filter(m => m.role === 'member' && m.active !== false)" /> m.active === false)" class="inactive-section"> <TeamMembers title="曾任成员" description="感谢曾经的陪伴。" :members="teamMembers.filter(m => m.active === false)" /> 功能特性支持的链接平台平台参数值图标文件GitHubgithub/icons/github.svg哔哩哔哩bilibili/icons/bilibili.svgQQqq/icons/QQ.svg微信wechat/icons/wechat.svgYouTubeyoutube/icons/youtube.svg微博weibo/icons/weibo.svg爱发电aifadian/icons/aifadian.svg成员角色admin - 核心成员，卡片头像显示为 100pxmember - 普通成员，卡片头像显示为 80px离职成员管理将成员的 active 设为 false，该成员会自动显示在底部的"曾任成员"区域，无需手动移动位置。视觉效果鼠标悬停时卡片微微上浮卡片四周产生辉光效果头像图片放大动画响应式布局适配移动端总结通过使用本地资源，我们解决了 GitHub 头像加载慢的问题；通过图标映射表，我们简化了链接图标的添加方式。这种方案不仅提升了页面加载速度，也让后续维护变得更加简单。添加新成员只需在数组中添加一个对象，无需关心任何样式和布局代码。
使用 CloudFlare 免费服务搭建专属图床的完整教程。通过 CloudFlare-ImgBed 项目，利用 Cloudflare Pages 部署图床服务，配合 Workers KV 存储图片链接，实现零成本搭建个人图床。文章详细介绍了从 GitHub 复刻项目、Cloudflare 注册登录、Pages 部署、KV 命名空间配置、资源绑定到 Telegram 上传渠道设置的完整流程，每个步骤都配有截图说明。教程还提供了测试用的 Bot Token 和 Chat ID，并推荐读者使用自己的机器人信息。通过本教程，你可以在 10 分钟内拥有一个完全免费、稳定可靠的图片托管服务，支持通过 Telegram 机器人快速上传图片，获取可直接引用的图片链接。适用于个人博客、网站图片托管等场景，是建站必备的基础设施之一。项目地址：CloudFlare-ImgBed教程视频：Bilibili1.克隆Github仓库打开仓库： CloudFlare-ImgBed，点击右上角的复刻(Fork)，将该仓库复刻到自己的Github账号下2.打开Cloudflare打开Cloudflare，注册账号（可使用Github账号关联）点击右上角的个人简介，将主页修改为中文3.部署Pages在左侧侧边栏打开：构建→计算和AI→Workers 和 Pages点击右上角：创建应用程序选择：Connect Github选择：仅选定的仓库，在下拉列表中选择刚刚复刻的仓库点击：安装与授权此时会跳转回创建Worker界面点击最下方小字的Get started点击导入现有Git储存库后面的开始使用勾选刚刚授权的仓库，点击开始设置项目名称可自定义在构建命令处填写：npm install其他保持不变，点击保存并部署等待构建完成后，可以点击链接预览一下:::warning如果只显示Hellow World，那说明你有一步做错了，或者是将Pages部署成了Workers:::一切正常，点击继续处理项目即可4.配置Workers KV在左侧侧边栏打开：构建→存储和数据库→Workers KV点击右上角Create Instance命名空间名称必须填写：img_url，点击创建5.添加资源绑定回到Workers 和 Pages界面点击自己刚刚部署的Pages点击设置→绑定点击添加，选择KV命名空间变量名称必须填写img_urlKV命名空间选择img_url，点击保存回到部署，选择重试部署通过 Cloudflare 分配的域名（例如 xxx.pages.dev）加上 /dashboard 后缀，就可以访问管理后台，请设置一个密码6.配置上传渠道选择TG，点击右下角加号渠道名称：自定义Bot Token：8456312767:AAESpR1JhxvfBmYvM5FVxcypDPpBkZzjXNwChat ID：-1003372781094启用状态：开启:::tipBot Token和Chat ID建议用自己的，上面两个是用来测试的:::在TG的全局搜索里搜索@BotFather和@userinfobot这两个机器人，即可获取自己的Bot Token和Chat ID点击保存设置即可
为首页特性卡片添加悬停动画效果，增强交互体验。通过 CSS 伪类和变换效果，让首页的 VPFeature 特性卡片在鼠标悬停时产生放大、阴影加深或颜色变化等动画效果，提升用户交互感受和站点的专业度。给首页特性VPFeature加一个hover效果，添加如下代码即可/* .vitepress/theme/style/var.css */ /* VPFeatures 页卡悬浮效果 */ .VPFeatures .items .item { transition: transform 0.3s; } .VPFeatures .items .item:hover { transform: translateY(-5px); }
集成不蒜子统计插件，显示页面浏览量统计信息。不蒜子是一个轻量级的中国本土网站统计工具，相比 Google Analytics 更加简洁，可以显示站点总访问量、页面浏览次数等信息，适合中文站点使用。安装插件pnpm add -D busuanzi.pure.js// .vitepress/theme/index.ts import DefaultTheme from 'vitepress/theme' import { inBrowser } from 'vitepress' import busuanzi from 'busuanzi.pure.js' export default { extends: DefaultTheme, enhanceApp({ app , router }) { if (inBrowser) { router.onAfterRouteChanged = () => { busuanzi.fetch() } } }, }将其放置到团队页面中 本站总访问量 次 本站访客数 人次