LOGO
首页 网站广场 站长动态 活跃度榜 审核查询 逛逛好站 留言交流 提交申请 关于本站

站长动态

站长动态所展示的是已加入好站网成员站长所撰写的文章
共同步 584 篇博文
(每2小时更新一次)
小十
入驻不足1年
郑州入冬后的第一场雪
难得天气预报准确一次,这暴雪说来就来。 截至昨天(周五)中午雪还没怎么下,露天跟着同事去了食堂路上连积雪的样子都没有,天倒是冷了不少,但那时候的氛围还不像是要下暴雪的样子。 到下午 3 点以后,眼瞅着外面白茫茫一片,当时就知道下班的路不好走。 15时04分拍摄的窗外 25-12-12 · 7mm · f/1.8 · 1/121s · ISO50 · iPhone 16时49分拍摄的窗外 25-12-12 · 7mm · f/1.8 · 1/50s · ISO125 · iPhone 果然,下班的时候,雪正大,17 点 30 分准时离开单位,刚出门单位门口的小路因没来得及清理,就滑的不行,全程龟速行驶,到大路后稍好一些,路上可见的各种事故现场,造成下班的路非常拥堵。接上媳妇后,全程不敢上高架,就在高架下面走着。原 1 个小时的路程,持续开了 2 小时 59 分钟。
小十
入驻不足1年
Hugo 优化:为图片添加正在加载动画
一直以来都想在图片加载时添加一个加载动画,以增强用户体验。使用 Hugo 以来,关于图片的添加方式也进行了自定义。针对历史数据,没功夫一遍遍改了,就采用 Markdown 原生语法进行图片的插入,这样形成的图片就是一行一个简单进行罗列。后来为了让自己的照片显示 exif 信息,以及实现一行多图的方式,通过自定义短代码的方式实现,详细可见 通过Hugo短代码功能实现图片及其EXIF信息展示 。 因此,本站加入图片的方式除了 Markdown 语法以外,还有 figure 和 figure-group 短代码的方式进行插入。此外,还有近期做的足迹地图里面的坐标卡片中,也有图片,当然已经在 Github 中实现了卡片内图片加载动画效果。在这次 Hugo 的优化中,也一并将对应的效果应用进来。 文章图片加载动画 文章图片放大加载动画 足迹地图卡片加载动画 目标场景 我的博客中有两种主要的图片展示场景: 文章页面:Markdown 图片、figure 短代码、figure-group 图片组 足迹地图:地图卡片中的缩略图、点击缩略图后的全屏查看 实现方式 1. HTML 层:添加懒加载属性 对于 Markdown 图片,使用 Hugo 的 Render Hook 自动添加 loading="lazy" 属性: {{- $src := .Destination | safeURL -}} {{- $alt := .Text -}} {{- $title := .Title -}} <img src="{{ $src }}" alt="{{ $alt }}" {{- with $title }} title="{{ . }}"{{ end }} loading="lazy"> 剩余 3 行代码 展开剩余代码 位置:layouts/_default/_markup/render-image.html
小十
入驻不足1年
三刷只有河南·戏剧幻城
11月30日,我第三次来“只有河南·戏剧幻城”,前两次分别是2021年12月19日(跟我爸)、2023年1月26日(跟我爸妈和外甥),这次是跟我姐。这次发现园区内的部分硬件设施已明显老化。三大主剧保持原貌,小剧场则进行了部分更迭,例如此次观看的《苏轼的河南》《曹操的麦田》和《薛怀义》均为新剧目。热门剧目如《红庙学校》排队依然困难,这次仍未排上。 只有河南·戏剧幻城(图源:百度百科) 示意图(图源:官方小程序) 总体来看,园区的三大主剧风格均比较严肃,《李家村》和《火车站》互动性较高,《幻城》舞美效果更好;而小剧场如《红庙学校》《唱吧跳吧》《薛怀义》等,氛围相对轻松,说教感较弱,更易被年轻观众接受。若是亲子游览,选择严肃类剧目对儿童相对更有教育意义。 夜景图(图源:百度百科) 夜景图(图源:百度百科) 由于起床稍晚,接上我姐然后抵达园区已近中午11点半。虽然剧场外的公共区域非常出片,但为了尽可能多看剧目,没有怎么在外停留拍照,而且上两次来也没有带相机,确实比较遗憾。 椅阵 25-11-30 · 20mm · f/4.0 · 1/1250s · ISO250 · ILCE-7CM2 路牌阵 25-11-30 · 85mm · f/1.4 · 1/1000s · ISO64 · ILCE-7CM2 总共看了8个剧目,其中1个主剧、7个小剧。按照观看顺序如下:
小十
入驻不足1年
为 Hugo 站点实现外部链接跳转访问
最近,我为博客增加了一个新功能:外部链接跳转页。现在,点击站外链接会先进入一个中转页,经用户确认点击 继续访问 后才前往目标网站。当然这功能并不少见,多数网站都有此功能。 为何需要跳转页? 明确告知:清晰告知访客“您即将离开本站”,避免意外跳转。 提升安全:为访客提供一道心理屏障,降低链接被篡改的风险。 统一体验:统一站外跳转的设计规范,并同步了主站的深浅色模式。 日间模式 夜间模式 实现方法 该功能主要依赖 Hugo 的 Render Hook 与 JavaScript 协同工作: 服务端预处理 (Hugo):网站构建时,Hugo 通过 Link Render Hook 为所有外部链接添加 target="_blank" 等属性,确保其在新标签页打开,完成基础优化。此时链接的 href 保持不变。 客户端动态改写 (JavaScript):页面加载后,external-links.js 脚本开始运行。它会遍历所有 <a> 标签,识别出未被排除的外部链接,并将其 href 属性动态地重写,指向我们自定义的跳转页 (/pages/redirect?target=[原始链接])。脚本还能通过 MutationObserver 监听动态内容(如评论区),确保所有外部链接都被处理。 中转页等待用户确认:用户点击链接后会访问 redirect.html 页面。该页面的脚本会解析 URL 参数,并等待用户手动点击“继续访问”按钮后,才会将用户重定向至目标网站。 总之,通过服务端预处理、客户端动态修改方式,将链接重写的逻辑完全放在浏览器端,避免了在 Hugo 构建时对 Markdown 内容的修改,实现方式更为灵活。 如何使用? 若想让特定链接“豁免”跳转,操作非常简单,有两种方式可选: 方法一:URL 标记法 (推荐) 在 Markdown 中,只需在链接 URL 的末尾加上 #no-redirect 即可。
小十
入驻不足1年
用 Wayback Machine 翻了翻博客的“黑历史”
最近心血来潮,上 Wayback Machine 搜了一下自己的域名 xiaoten.com。 距离2011年2月2日,已经14.8个年头,虽然手里存着一些备份数据,但大多是后面几年的存档,前几年的存档因种种原因,没有留存下来。 Wayback Machine 存下来的这些快照,就像是给网站打的 git commit 记录,展示了这个站点的演变路径。 1. 追求“形式感”的折腾期 一开始,我似乎对“网站入口”这种东西非常着迷。 快照里留存了好几个版本的“首页引导页”。比如蓝底白字的 “PLAIN FOAM” 页面,还有写着“十分之十的小十”的蓝色大屏入口。那时候觉得,在进入博客文章列表前,先来个极简的引导页显得很“酷”,很有设计感。 最初版本入口 第二版,具体时间不详 2011年12月1日存档 2014年8月20日存档 2. 纯粹的“技术笔记”时期 翻看最早几年的快照,那时候博客的主要功能就是充当“云端笔记本”。 看看那时候的文章标题:《SVN服务器的配置》、《IE版本注释用法》、《百度地图API》。那时候互联网开发环境还比较粗糙,我们要花大量时间去解决 IE 浏览器的兼容性问题,SVN 还是主流的版本控制工具,Git 还没像今天这样一统天下。 那时候的排版非常密集,侧边栏塞满了各种功能:日历、访问统计、友情链接、甚至还有当时的“微博秀”。现在看来,那时候的页面恨不得把所有信息都塞进首屏,典型的 Web 1.0 向 Web 2.0 过渡时期的审美。
小十
入驻不足1年
基于高德地图实现 Hugo 足迹地图短代码
依然是受 王叨叨:基于高德地图做足迹管理插件 文章启发,实在眼馋足迹功能,但苦于没有清晰的思路,看到叨叨大神发了这个插件,借助AI,终于抽空完成了这一功能,并融入了一些个人想法: 与站点主题同步,支持亮/暗模式自适应; 增加标签筛选功能; 在地图卡片中,若有多张图片,支持水平滚动并集成灯箱放大查看; 标记点智能集群,缩小地图时自动合并附近位置; 集群开关控制,可随时启用或关闭集群功能。 其余部分则为常规足迹功能的实现。 本文将简要介绍实现方式,完整的源码已在 GitHub 开源,包含所有 JS、CSS 和示例数据。具体样例在本站"关于"页面,如下所示: 正在加载足迹... 主要功能 短代码嵌入:{{< footprintmap >}} 自动注入脚本与样式,按需加载数据。 JSON 数据支持:所有足迹数据集中保存在 footprints.json 文件中,便于在 Git 里维护与复用。 自动标签筛选:脚本会扫描全部 categories 字段,生成可点击的筛选按钮。 智能标记集群:缩小地图时自动将附近位置合并显示,点击集群标记可放大查看详情。 集群开关控制:地图底部提供开关按钮,支持亮/暗主题自动适配。 文章关联:通过手动设置 url 字段,可添加关联的文章。 照片轻量查看器:支持多张图的横向轮播,点击后用自定义 Viewer 放大,适配移动端手势。 亮/暗主题联动:跟随站点 dark class 切换高德地图底图与 InfoWindow 样式。 自定义 Marker 颜色:通过 markerColor 选择预设(sunset / ocean / violet / forest / amber / citrus),也支持直接写 #RRGGBB。 移动端优化:信息卡宽度移动端自适应、字号/间距重排、点击空白区域自动收起 InfoWindow。 JSON 数据格式 footprints.json 中维护一个 locations 数组,字段解释如下:
Tree Hole
入驻不足1年
Windows Server 允许多个用户同时远程桌面登录
Windows Server 2012/2016/2019/2022服务器操作系统默认仅支持一个用户远程,如果 […]
小十
入驻不足1年
解决 Hugo 分页器返回空页面的诡异问题:.Paginate 不能被多次调用
问题背景 在将博客的文章列表从 JavaScript 分页迁移到 Hugo 官方分页时,遇到了一个非常诡异的问题: ✅ Hugo 构建成功,显示生成了 42 个分页页面 ✅ .RegularPages 有 360 篇文章 ✅ 直接 range .RegularPages 可以正常显示文章 ❌ 但是 .Paginator.Pages 始终返回 0 {{/* 这样可以看到 360 篇文章 */}} {{ range .RegularPages }} {{ .Title }} {{ end }} {{/* 但分页器是空的! */}} {{ $paginator := .Paginator }} 分页器中的文章数: {{ len $paginator.Pages }} {{/* 输出:0 */}} 剩余 3 行代码 展开剩余代码 排查过程 第一步:检查配置 首先怀疑是配置问题。Hugo v0.128+ 的分页配置格式发生了变化:
小十
入驻不足1年
徐州一日纪行
16日早上,从郑州出发,乘坐高铁约一个半小时到达徐州东站。由于交通便利,行程安排为当天往返。 出行前参考了一些攻略,徐州的几个主要景点相对集中,因此整体行程比较紧凑。 依然是流水帐的记录形式: 上午10点02分到达徐州东站,搭乘地铁一号线,经9站到达民主北路站。出站后步行约十分钟,前往提前在大众点评上选定的一家评分较高的餐厅。午餐点了地锅鸡和乡村大席小炒。味道确实不错。 饭店后厨,据说不是预制菜 25-11-15 · 85mm · f/1.4 · 1/320s · ISO320 · ILCE-7CM2 地锅鸡👍 25-11-15 · 50mm · f/1.8 · 1/100s · ISO320 · ILCE-7CM2 乡村大席小炒(要的免🌶,不然会更好吃) 25-11-15 · 50mm · f/1.8 · 1/60s · ISO320 · ILCE-7CM2 徐州为历史悠久的城市,古称彭城,是汉代文化的重要发源地之一,留有大量两汉时期遗迹。因此,徐州博物馆被列为行程之一。前几天因预约人数已满未能成功预约,当天上午九点左右,系统重新放出名额,顺利预约到12点以后的下午场。
小十
入驻不足1年
借助Github Action实现友圈动态自动获取——小十友圈RSS聚合工具
回归博客内容创作也有几月有余,这期间认识了很多独立博客站长,也重新联系上了一些老朋友。友链作为独立博客的宝贵财富,在快节奏的今天,能够保留这种传统意义上的网友社交形式,显得格外有意义。 这次回归以来,我发现了很多友链展示的新形式,其中通过RSS自动获取并展示好友动态就是其中之一。市面上已有一些成熟的工具,最初我使用的是 Rock-Candy-Tea/hexo-circle-of-friends 这个开源项目。但该项目功能较为复杂,而我只需要核心的RSS聚合功能,通过定时更新data.json文件,再配合前端代码解析展示朋友动态。 对我来说,需求其实很简单:通过友链页面上的网站链接以及自定义的一些链接,自动获取相关动态并生成到json文件中。因此,我开发了一个轻量级但功能完整的RSS聚合工具:小十友圈RSS聚合工具,并利用GitHub Actions实现了全自动化运行,现已在GitHub上开源。 项目地址: https://github.com/Jiosanity/xiaoten-rss.git 概述 小十友圈RSS聚合工具 是一个轻量级但功能完整的RSS聚合工具,它能够自动从友链页面抓取友链信息,发现并验证RSS源,聚合最新文章,生成标准化的JSON数据文件。通过GitHub Actions,定时自动运行,确保数据的实时性。 主要实现方式 模块化设计 小十友圈RSS聚合工具采用高度模块化的设计,确保各组件职责单一且易于维护: FriendRSSAggregator (主控制器) ├── ConfigParser (配置解析器) ├── SiteFilter (站点过滤器) ├── LinkPageScraper (友链爬取器) ├── RSSFetcher (RSS获取器) ├── DataAggregator (数据聚合器) └── CacheManager (缓存管理器) 剩余 2 行代码 展开剩余代码 数据处理流程 配置加载 → 读取YAML配置文件 友链发现 → 从友链页面爬取 + 手动配置合并 站点过滤 → 黑白名单机制去噪 RSS探测 → 多级回退的Feed URL发现 内容获取 → 解析RSS/Atom源 数据聚合 → 时间排序、去重、格式化 结果输出 → 生成标准JSON文件 核心技术实现 1. 友链获取方式 系统支持两种友链获取方式:
小十
入驻不足1年
成都两日纪行
10月20日,读研期间的导师在群里发布活动通知,计划于11月8日在成都望江宾馆举办“电力系统稳定与高压直流输电团队校友大家庭欢聚雅集”。自2017年毕业至今,虽然我曾两次到过成都——一次是因参加QC发布会,一次是假期来游玩——但都未能与导师见上一面。正好借此次活动,利用周末两天时间,与导师见面叙旧。 购票后被周基佬告知,“天府机场”到哪儿都要两个小时(包括成都市区)。打开地图才意识到,上学时天府机场尚未启用,如今它已承接大部分航线,距离本次活动场地56公里,而双流机场仅19公里。往返路程漫长,不免有些无奈。 活动通知 天府机场距离市区非常远 早晨5点起床,最终抵达成都市区已是中午。办理入住后,14点赶到会场,领取伴手礼、签名后入座。 学院吉祥物(到家后补拍) 25-11-11 · 85mm · f/1.4 · 1/320s · ISO320 · ILCE-7CM2 签到墙 25-11-8 · 0mm · f/0.0 · 1/160s · ISO2000 · Canon EOS R6m2 签到的我 会场 25-11-8 · 22mm · f/4.0 · 1/160s · ISO1600 · Canon EOS R6m2 李老师的字画 25-11-8 · 40mm · f/4.0 · 1/160s · ISO6400 · Canon EOS R6m2 多数师兄师姐、师弟师妹都已八年未见,还有曹师兄十多年没见。不过,与我同级的同学在会场并未见到。
小十
入驻不足1年
通过Hugo短代码功能实现图片及其EXIF信息展示
我有一个习惯就是在网络上分享图片时不管是调整尺寸还是压缩,都习惯保留元数据,当然也希望在分析图片时能够展示元数据。本文介绍如何在 Hugo 静态博客中借助短代码实现图片展示功能,包括单张图片和图片组的短代码实现,以及自动获取并格式化显示图片 EXIF 信息的方法。 具体效果如下图所示: 也可见 万圣节和亳都·新象的一些照片 和 天津两日纪行 等文章。 需实现的功能 支持单张图片和图片组两种展示方式 自动从图片 EXIF 数据中提取拍摄信息 格式化显示焦距、光圈、快门、ISO、相机品牌和镜头型号 支持自定义标题和多种参数传递方式 短代码实现 1. 单张图片短代码 (figure.html) 创建 layouts/shortcodes/figure.html 文件: <!-- layouts/shortcodes/figure.html --> {{ $page := .Page }} <!-- 支持两种语法:位置参数和命名参数 --> {{ $imageParam := .Get 0 }} {{ $src := .Get "src" }} {{ $title := .Get "title" }} {{ $alt := .Get "alt" }} {{ $class := .Get "class" }} <!-- 如果使用了位置参数(新语法) --> {{ if $imageParam }} {{ $imagePath := $imageParam }} {{ $customTitle := "" }} <!-- 检查参数是否包含自定义标题(使用冒号分隔) --> {{ if in $imageParam ":" }} {{ $parts := split $imageParam ":" }} {{ $imagePath = index $parts 0 }} {{ $customTitle = index $parts 1 }} {{ end }} {{ $src = $imagePath }} {{ $title = $customTitle }} {{ end }} {{ $imageResource := $page.Resources.GetMatch $src }} {{ if $imageResource }} {{ $exif := $imageResource.Exif }} {{ $autoTitle := $title | default $exif.Tags.ImageDescription | default (humanize (path.Base $src | replaceRE "\\..*$" "" | replaceRE "-|_" " ")) }} {{ $autoAlt := $alt | default $autoTitle }} <figure class="{{ $class }}"> <a href="{{ $imageResource.RelPermalink }}" class="no-a-style" data-fancybox="global-gallery" data-caption="{{ $autoTitle }}"> <img title="{{ $autoTitle }}" alt="{{ $autoAlt }}" src="{{ $imageResource.RelPermalink }}" loading="lazy" > </a> {{ if or $autoTitle $exif }} <figcaption> {{ if $autoTitle }}<span class="title">{{ $autoTitle }}</span>{{ end }} {{ with $exif }} {{ $exifParts := slice }} <!-- 1. 焦距 --> {{ with .Tags.FocalLength }} {{ $focalLength := . }} {{ if findRE "^[0-9]+/[0-9]+$" $focalLength }} {{ $parts := split $focalLength "/" }} {{ $numerator := float (index $parts 0) }} {{ $denominator := float (index $parts 1) }} {{ if gt $denominator 0 }} {{ $value := div $numerator $denominator }} {{ if eq (mod $value 1) 0 }} {{ $focalLength = printf "%.0fmm" $value }} {{ else }} {{ $focalLength = printf "%.1fmm" $value }} {{ end }} {{ end }} {{ else }} {{ $focalLength = printf "%smm" $focalLength }} {{ end }} {{ $exifParts = $exifParts | append $focalLength }} {{ end }} <!-- 2. 光圈 --> {{ with .Tags.FNumber }} {{ $fnumber := . }} {{ if findRE "^[0-9]+/[0-9]+$" $fnumber }} {{ $parts := split $fnumber "/" }} {{ $numerator := float (index $parts 0) }} {{ $denominator := float (index $parts 1) }} {{ if gt $denominator 0 }} {{ $fnumber = printf "f/%.1f" (div $numerator $denominator) }} {{ end }} {{ else }} {{ $fnumber = printf "f/%s" $fnumber }} {{ end }} {{ $exifParts = $exifParts | append $fnumber }} {{ end }} <!-- 3. 快门速度 --> {{ with .Tags.ExposureTime }} {{ $exposureTime := . }} {{ if findRE "^[0-9]+/[0-9]+$" $exposureTime }} {{ $parts := split $exposureTime "/" }} {{ $numerator := float (index $parts 0) }} {{ $denominator := float (index $parts 1) }} {{ if gt $denominator 0 }} {{ $value := div $numerator $denominator }} {{ if ge $value 1 }} {{ $exposureTime = printf "%.0fs" $value }} {{ else }} {{ $exposureTime = printf "1/%.0fs" (div 1 $value) }} {{ end }} {{ end }} {{ else }} {{ $exposureTime = printf "%ss" $exposureTime }} {{ end }} {{ $exifParts = $exifParts | append $exposureTime }} {{ end }} <!-- 4. ISO - 尝试多个可能的标签 --> {{ $isoValue := "" }} {{ with .Tags.ISOSpeedRatings }}{{ $isoValue = . }}{{ end }} {{ if not $isoValue }}{{ with .Tags.ISOSpeed }}{{ $isoValue = . }}{{ end }}{{ end }} {{ if not $isoValue }}{{ with .Tags.ISO }}{{ $isoValue = . }}{{ end }}{{ end }} {{ if not $isoValue }}{{ with .Tags.PhotographicSensitivity }}{{ $isoValue = . }}{{ end }}{{ end }} {{ with $isoValue }} {{ $isoInt := int . }} {{ if gt $isoInt 0 }} {{ $exifParts = $exifParts | append (printf "ISO%d" $isoInt) }} {{ end }} {{ end }} <!-- 5. 相机品牌 --> {{ with .Tags.Model }} {{ $cameraModel := . }} {{ if findRE "iPhone" $cameraModel }} {{ $cameraModel = "iPhone" }} {{ else if findRE "iPad" $cameraModel }} {{ $cameraModel = "iPad" }} {{ else if findRE "Canon" $cameraModel }} {{ $cameraModel = "Canon" }} {{ else if findRE "Nikon" $cameraModel }} {{ $cameraModel = "Nikon" }} {{ else if findRE "Sony" $cameraModel }} {{ $cameraModel = "Sony" }} {{ else if findRE "ILCE-" $cameraModel }} {{ $cameraModel = "Sony" }} {{ else if findRE "FUJIFILM" $cameraModel }} {{ $cameraModel = "Fujifilm" }} {{ else if findRE "X-T" $cameraModel }} {{ $cameraModel = "Fujifilm" }} {{ end }} {{ $exifParts = $exifParts | append $cameraModel }} {{ end }} <!-- 6. 镜头型号(完整显示) --> {{ with .Tags.LensModel }} {{ $lensModel := . }} {{ $lensModel = trim $lensModel " " }} {{ if and $lensModel (ne $lensModel "") }} {{ $exifParts = $exifParts | append $lensModel }} {{ end }} {{ end }} {{ if gt (len $exifParts) 0 }} <span class="exif">{{ delimit $exifParts " · " }}</span> {{ end }} {{ end }} </figcaption> {{ end }} </figure> {{ else }} {{ errorf "图片未找到: %s" $src }} {{ end }} 剩余 157 行代码 展开剩余代码 使用方法:
小十
入驻不足1年
万圣节和亳都·新象的一些照片
按照体感估算,感觉郑州已持续了两个月的阴雨天气,在这长时间恶劣天气之前还是实打实的夏季,就突然进入了冬天。 本来以为阴雨已经结束,结果又来了持续一周的大雾天气,从早浓到晚的大雾,长久不见阳光,“豫犬吠日”实至名归。 难得周六、周日太阳出现,刚好11月1日是万圣节,这天的前两天,临时起意想在这个周六去感受下年轻人的氛围,决定去看一下方特精怪夜活动。 11月2日周日这天便去了开业不久的“亳都 · 新象”凑了凑热闹。 万圣节那天 结果人是真的多,带上才到手第二天的85 1.4镜头,也没有拍到几张满意的照片。 难得第一次参与了万圣节,放出一些当日照片留个纪念。 到达方特门口时的落日 25-11-1 · 85mm · f/1.4 · 1/4000s · ISO160 · ILCE-7CM2 方特城堡 25-11-1 · 85mm · f/1.4 · 1/500s · ISO320 · ILCE-7CM2 盛装出游的小孩 25-11-1 · 85mm · f/1.4 · 1/250s · ISO500 · ILCE-7CM2 树上的挂件 25-11-1 · 85mm · f/1.4 · 1/160s · ISO500 · ILCE-7CM2 树上的挂件 25-11-1 · 85mm · f/1.4 · 1/250s · ISO500 · ILCE-7CM2 被求合影的NPC 25-11-1 · 85mm · f/1.4 · 1/250s · ISO800 · ILCE-7CM2 被求合影的NPC 25-11-1 · 85mm · f/1.4 · 1/125s · ISO800 · ILCE-7CM2 被求合影的NPC 25-11-1 · 85mm · f/1.4 · 1/50s · ISO800 · ILCE-7CM2 丧尸禁地 25-11-1 · 85mm · f/1.4 · 1/320s · ISO800 · ILCE-7CM2 争夺银票 25-11-1 · 85mm · f/1.4 · 1/320s · ISO1250 · ILCE-7CM2 快到手了 25-11-1 · 85mm · f/1.4 · 1/250s · ISO1250 · ILCE-7CM2 我喜欢这个显示器 25-11-1 · 85mm · f/1.4 · 1/80s · ISO800 · ILCE-7CM2 可爱的小女孩 25-11-1 · 85mm · f/1.4 · 1/200s · ISO1250 · ILCE-7CM2 可爱的小男孩 25-11-1 · 85mm · f/1.4 · 1/160s · ISO1600 · ILCE-7CM2 某种仪式 25-11-1 · 85mm · f/1.4 · 1/160s · ISO1250 · ILCE-7CM2 NPC 25-11-1 · 85mm · f/1.4 · 1/160s · ISO1000 · ILCE-7CM2 不想上班 25-11-1 · 85mm · f/1.4 · 1/400s · ISO1000 · ILCE-7CM2 冰糖葫芦 25-11-1 · 85mm · f/1.4 · 1/2000s · ISO1000 · ILCE-7CM2 聊斋剧场1 25-11-1 · 85mm · f/1.4 · 1/60s · ISO1000 · ILCE-7CM2 聊斋剧场2 25-11-1 · 85mm · f/1.4 · 1/20s · ISO1000 · ILCE-7CM2 聊斋剧场3 25-11-1 · 85mm · f/1.4 · 1/30s · ISO1000 · ILCE-7CM2 旋转木马 25-11-1 · 85mm · f/1.4 · 1/500s · ISO1600 · ILCE-7CM2 亳都 · 新象那天 亳都 · 新象位于郑州市管城区,东临商代城垣遗址,西接郑州文庙,由15所主题院落组成,总建筑面积约4万平方米,是郑州市首个中原传统文化主题街区。项目占地面积46亩,包含3条主街、多条巷道及4个文化主题广场,融合传统街巷肌理与现代建筑技术,采用老砖、青石板与玻璃砖等材料,体现“城、街、巷、院、景”五维布局。院墙采用“四色混拼”施工方法搭配新旧青砖,屋面瓦片使用“压五露五”等传统工艺铺设。
小十
入驻不足1年
利用短代码在hugo博客中加入bilibili播放器插件
王叨叨在 开源Bilibili播放器插件 一文中,分享了一个Typecho插件,用于将Bilibili默认播放器替换为HTML5移动端播放器,并提供更多自定义选项。 基于他的源代码,考虑将其移植到Hugo中,并通过短代码来实现bilibili视频的调用。 短代码是Hugo的一项强大功能,允许我们在Markdown中嵌入HTML片段,实现复杂的布局和功能。 王叨叨的Typecho插件主要通过以下方式工作: 通过正则表达式匹配和替换Bilibili播放器iframe 提供丰富的配置选项(宽度、高度、自动播放、弹幕开关等) 替换默认播放器为HTML5移动端播放器,提升用户体验 在上述的基础上,借助AI实现了一定程度的高度自适应和自定义封面功能,为什么说是一定程度上的高度自适应,其实是新加入了ratio参数,即明确了视频宽高比,再根据视频宽高比,使用 padding-bottom 技巧实现真正的宽高比自适应。 示例 .bilibili-player-wrapper { position: relative; width: 100%; height: 0; padding-bottom: 56.25%; margin: 1rem 0; border-radius: 8px; overflow: hidden; background-color: #f5f5f5; } .bilibili-player-wrapper iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none; border-radius: 8px; } .bilibili-custom-cover { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-size: cover; background-position: center; background-repeat: no-repeat; cursor: pointer; display: flex; align-items: center; justify-content: center; border-radius: 8px; z-index: 10; transition: opacity 0.3s ease; } .bilibili-custom-cover:hover .bilibili-play-button { transform: scale(1.1); background-color: #00a1d6; } .bilibili-play-button { width: 70px; height: 70px; background-color: rgba(0, 161, 214, 0.9); border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-size: 24px; transition: all 0.3s ease; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); } .bilibili-play-button::after { content: "▶"; margin-left: 4px; } .bilibili-player-loading { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background-color: rgba(0, 0, 0, 0.7); color: white; font-size: 16px; z-index: 5; border-radius: 8px; } .bilibili-player-loading.hidden { display: none; } @media (max-width: 768px) { .bilibili-play-button { width: 60px; height: 60px; font-size: 20px; } } 视频加载中... function loadBilibiliPlayer(playerId) { const playerWrapper = document.getElementById(playerId); const cover = playerWrapper.querySelector('.bilibili-custom-cover'); const iframe = playerWrapper.querySelector('iframe'); const loading = playerWrapper.querySelector('.bilibili-player-loading'); if (iframe && iframe.hasAttribute('data-src')) { loading.classList.remove('hidden'); iframe.src = iframe.getAttribute('data-src'); iframe.style.display = 'block'; cover.style.opacity = '0'; iframe.onload = function() { loading.classList.add('hidden'); setTimeout(function() { cover.style.display = 'none'; }, 300); }; } } document.addEventListener('DOMContentLoaded', function() { const players = document.querySelectorAll('.bilibili-player-wrapper'); players.forEach(function(player) { const cover = player.querySelector('.bilibili-custom-cover'); const iframe = player.querySelector('iframe'); if (!cover && iframe && iframe.hasAttribute('data-src')) { iframe.src = iframe.getAttribute('data-src'); iframe.style.display = 'block'; } }); }); 具体实现 创建短代码文件 在Hugo项目的 layouts/shortcodes/ 目录下创建 bilibili.html 文件:
小十
入驻不足1年
天津两日纪行
10月25—26日,趁着周末两天,去了一趟天津。 第一天 早上5点半起床,上午约10点30分,抵达天津西站。出站后,搭乘出租车前往位于滨江道步行街内的酒店。 滨江道步行街是天津市最为繁华的商业街之一,其历史可追溯至20世纪初,汇集了众多商场与老字号品牌。午餐选择了“醉喜楼津菜馆”,尝试了特色菜八珍豆腐与皮皮虾陷的锅贴,菜品份量充足。 传统八珍豆腐 25-10-25 · 9mm · f/2.8 · 1/100s · ISO64 · iPhone 皮皮虾扇贝锅贴 25-10-25 · 50mm · f/1.8 · 1/341s · ISO80 · iPhone 午后,首先参观了西开教堂。其全称为天主教西开总堂,始建于1916年,是天津市规模最大的罗马风貌建筑群,建筑风格为罗曼式。
小十
入驻不足1年
解决林木木老师哔哔广场中同一Memos实例下多用户头像昵称显示错误问题
问题描述 非常感谢 林木木 老师编写的 哔哔广场 ,日常用于调取Memos网站的内容,并实现了同步订阅其他Memos用户及发表 memos 等功能。 但是,我这里有一个较为小众的需求,就是在使用 Memos 广场功能时,如下图界面: 当多个用户来自同一个 Memos 实例时,其动态内容能够正常显示,但用户信息(头像和昵称)却全部显示为同一个用户的信息。 例如,在我的订阅列表中: 小十 (ID: 1, 头像: avatar.png) 泥鳅胡子 (ID: 3, 头像: myh.jpg) 上述两个用户都使用同一个 Memos 实例 https://memos.xiaoten.com,但在广场模式下,所有动态都显示为泥鳅胡子的头像和昵称,即使这些动态实际上是小十发布的。 原因分析 主要原因 用户信息映射逻辑未覆盖这种特殊情况,在原始的代码实现中: 获取用户动态时,代码使用了同一个 Memos 实例下的第一个用户信息来覆盖所有用户的动态信息。 最初使用 creatorName 作为映射键,但当多个用户有相同的显示名时会导致冲突。 在合并用户信息和动态数据时,属性覆盖的顺序问题,会使用户信息被动态数据中的空值覆盖。 技术细节 在 Promise 内部处理动态数据时,原始代码使用了 matchedMemo 而不是当前用户 u 的信息: // 原因 for (let key in matchedMemo) { if (matchedMemo.hasOwnProperty(key)) { item[key] = matchedMemo[key]; } } 剩余 1 行代码 展开剩余代码 这会使同一个 Memos 实例下的所有用户都使用了第一个匹配的用户信息。 解决方式 1. 修改 Promise 内部的用户信息赋值 在获取每个用户的动态时,使用当前用户 u 的信息而不是 matchedMemo:
小十
入驻不足1年
在 Hugo 中集成 Memos 多用户微博系统
摘要 本文详细阐述在 Hugo 静态网站生成器中集成 Memos 0.18.2 多用户微博系统的完整技术方案。通过 RESTful API 调用、Twikoo 评论系统集成、图片资源处理等关键技术,实现了在静态网站中展示动态微博内容的功能。本文涵盖系统架构设计、核心代码实现、性能优化策略以及部署配置指南,为开发者提供完整的实施参考。样例请见本站 说说页面 。 1. 主要流程 1.1 系统组成 前端框架: Hugo 静态网站生成器 微博服务: Memos 0.18.2 RESTful API 评论系统: Twikoo 评论服务 图片处理: ViewImage 灯箱 + Lozad 懒加载 内容渲染: Marked.js Markdown 解析器 1.2 数据流架构 Memos API → 数据获取 → 内容处理 → 评论统计 → HTML 渲染 → 前端展示2. 核心实现方案 2.1 多用户配置管理 在 Hugo 模板中定义用户配置数组: var memosMyList = [ { "creatorName": "用户A", "website": "https://example.com", "link": "https://memos.example.com", "creatorId": "1", "avatar": "/avatars/user-a.png", "twikoo": "https://twikoo.example.com" }, { "creatorName": "用户B", "website": "https://example.org", "link": "https://memos.example.com", "creatorId": "2", "avatar": "/avatars/user-b.png", "twikoo": "https://twikoo.example.com" } ]; 剩余 13 行代码 展开剩余代码 2.2 API 集成与数据处理 2.2.1 并发数据获取 async function getAllUsersMemos() { const userPromises = currentUsers.map(user => getUserMemos(user.link, user.creatorId, user.creatorName, user.avatar) ); const allUserResults = await Promise.allSettled(userPromises); const successfulResults = []; allUserResults.forEach((result) => { if (result.status === 'fulfilled' && Array.isArray(result.value)) { successfulResults.push(...result.value); } }); return successfulResults; } 剩余 11 行代码 展开剩余代码 2.2.2 数据验证与标准化 function validateUserConfig() { const validUsers = currentUsers.filter(user => user.creatorId && user.link && user.creatorName ); return validUsers.length > 0; } function normalizeUrl(baseUrl, path) { const normalizedBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl; const normalizedPath = path.startsWith('/') ? path : '/' + path; return normalizedBase + normalizedPath; } 剩余 8 行代码 展开剩余代码 2.3 评论系统集成 2.3.1 批量评论统计 async function getMemoCount(memos) { const twikooGroups = {}; // 按 Twikoo 环境分组处理 memos.forEach(item => { if (!item?.twikoo) return; const envId = item.twikoo; const memoUrl = normalizeUrl(item.link, `/m/${item.id}`); if (!twikooGroups[envId]) { twikooGroups[envId] = []; } twikooGroups[envId].push({ url: memoUrl }); }); const allTwikooCount = []; for (const [envId, items] of Object.entries(twikooGroups)) { try { const urls = items.map(item => item.url); const res = await twikoo.getCommentsCount({ envId: envId, urls: urls, includeReply: false }); if (Array.isArray(res)) { allTwikooCount.push(...res); } } catch (error) { console.error(`Twikoo 环境 ${envId} 评论数获取失败:`, error); } } // 关联评论数到对应 Memo memos.forEach(item => { if (!item?.twikoo) { item.count = 0; return; } const url = normalizeUrl(item.link, `/m/${item.id}`); const countData = allTwikooCount.find(o => o?.url === url); item.count = countData?.count || 0; }); return memos; } 剩余 44 行代码 展开剩余代码 2.4 内容渲染引擎 2.4.1 Markdown 内容处理 function processMemoContent(content) { const TAG_REGEX = /#([^#\s!.,;:?"'()]+)(?= )/g; const IMAGE_REGEX = /\!\[(.*?)\]\((.*?)\)/g; const LINK_REGEX = /(?<!!)\[(.*?)\]\((.*?)\)/g; let processed = content .replace(TAG_REGEX, '') .replace(IMAGE_REGEX, '') .replace(LINK_REGEX, '<a class="primary" href="$2" target="_blank">$1</a>'); return marked.parse(processed); } 剩余 7 行代码 展开剩余代码 2.4.2 图片资源处理 function processImageResources(content, memo) { let imageHtml = ''; // 处理内联图片 const inlineImages = content.match(IMAGE_REGEX); if (inlineImages?.length > 0) { const imageString = inlineImages.join('').replace(/,/g, ''); imageHtml = imageString.replace(IMAGE_REGEX, '<div class="memo-resource width-100">' + '<img class="lozad" data-src="$2" alt="$1">' + '</div>' ); } // 处理附件图片 if (memo.resourceList?.length > 0) { memo.resourceList.forEach(resource => { if (resource.type?.startsWith('image')) { const imageUrl = resource.externalLink || normalizeUrl(memo.link, `/o/r/${resource.uid || resource.id}`); imageHtml += '<div class="memo-resource w-100">' + '<img class="lozad" data-src="${imageUrl}">' + '</div>'; } }); } return imageHtml ? '<div class="resource-wrapper">' + '<div class="images-wrapper my-2" view-image>' + imageHtml + '</div>' + '</div>' : ''; } 剩余 27 行代码 展开剩余代码 3. 性能优化策略 3.1 分页加载机制 function pagination(data, page, limit) { const startIndex = (page - 1) * limit; const endIndex = startIndex + limit; return data.slice(startIndex, endIndex); } function updateData(data) { const validData = data.filter(item => item && typeof item === 'object'); validData.sort((a, b) => b.createdTs - a.createdTs); const pageData = pagination(validData, currentPage, itemsPerPage); renderMemoList(pageData); const totalPages = Math.ceil(validData.length / itemsPerPage); updatePaginationControls(currentPage, totalPages); } 剩余 11 行代码 展开剩余代码 3.2 图片性能优化 function initializeImageOptimizations() { // 图片懒加载 if (typeof lozad !== 'undefined') { const imageObserver = lozad('.lozad', { rootMargin: '50px 0px', threshold: 0.1 }); imageObserver.observe(); } // 图片灯箱初始化 if (typeof ViewImage !== 'undefined') { ViewImage.init('.images-wrapper img'); } } 剩余 10 行代码 展开剩余代码 3.3 缓存策略 class MemosCache { constructor() { this.cacheKey = 'memos-data-cache'; this.cacheTimeout = 5 * 60 * 1000; // 5分钟 } getCachedData() { const cached = localStorage.getItem(this.cacheKey); if (!cached) return null; const { data, timestamp } = JSON.parse(cached); if (Date.now() - timestamp > this.cacheTimeout) { localStorage.removeItem(this.cacheKey); return null; } return data; } setCachedData(data) { const cacheObject = { data: data, timestamp: Date.now() }; localStorage.setItem(this.cacheKey, JSON.stringify(cacheObject)); } } 剩余 22 行代码 展开剩余代码 4. 错误处理与监控 4.1 健壮性设计 async function getUserMemos(link, userId, userName, userAvatar) { try { // 参数验证 if (!link || !userId) { throw new Error('缺少必要参数'); } const normalizedLink = link.endsWith('/') ? link : link + '/'; const apiUrl = `${normalizedLink}api/v1/memo?creatorId=${userId}&rowStatus=NORMAL&limit=50`; const response = await fetch(apiUrl); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); if (!Array.isArray(data)) { throw new Error('API 返回数据格式错误'); } return data.filter(item => item && typeof item === 'object') .map(item => ({ ...item, link: normalizedLink, avatar: userAvatar, creatorName: userName })); } catch (error) { console.error(`获取用户 ${userName} 数据失败:`, error); return []; // 优雅降级 } } 剩余 29 行代码 展开剩余代码 4.2 性能监控 class PerformanceMonitor { static async measureApiCall(apiCall) { const startTime = performance.now(); try { const result = await apiCall(); const duration = performance.now() - startTime; if (duration > 1000) { // 超过1秒记录警告 console.warn(`API 调用耗时较长: ${duration.toFixed(2)}ms`); } return result; } catch (error) { const duration = performance.now() - startTime; console.error(`API 调用失败,耗时 ${duration.toFixed(2)}ms:`, error); throw error; } } } 剩余 14 行代码 展开剩余代码 5. 部署配置指南 5.1 文件结构规范 hugo-site/ ├── layouts/ │ └── _default/ │ └── memos.html ├── static/ │ └── memos/ │ ├── js/ │ │ ├── memos-core.js │ │ ├── twikoo.min.js │ │ ├── marked.min.js │ │ └── lozad.min.js │ └── css/ │ └── memos-styles.css └── content/ └── memos.md 剩余 10 行代码 展开剩余代码 5.2 Hugo 模板配置 <!-- layouts/_default/memos.html --> {{ define "main" }} <div class="memos-container"> <header class="memos-header"> <h1>{{ .Title }}</h1> </header> <div class="memos-content"> <div id="memo-list" class="memo-list-container"></div> <button id="load-more" class="load-more-button">加载更多</button> </div> </div> <script src="/memos/js/marked.min.js"></script> <script src="/memos/js/lozad.min.js"></script> <script src="/memos/js/twikoo.min.js"></script> <script src="/memos/js/memos-core.js"></script> {{ end }} 剩余 13 行代码 展开剩余代码 5.3 环境变量配置 // 生产环境配置 const MEMOS_CONFIG = { apiEndpoints: { memos: 'https://memos.example.com/api/v1', twikoo: 'https://twikoo.example.com' }, performance: { cacheTimeout: 300000, paginationSize: 10, imageLazyLoad: true }, features: { multiUser: true, comments: true, imageZoom: true } }; 剩余 12 行代码 展开剩余代码 6. 安全考虑 6.1 输入验证 function sanitizeUserInput(input) { if (typeof input !== 'string') return ''; return input .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;') .replace(/'/g, '&#x27;') .replace(/\//g, '&#x2F;'); } 剩余 5 行代码 展开剩余代码 6.2 CSP 配置建议 <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://memos.example.com https://twikoo.example.com; img-src 'self' https: data:; style-src 'self' 'unsafe-inline'; connect-src 'self' https://memos.example.com https://twikoo.example.com"> 剩余 1 行代码 展开剩余代码 7. 测试方案 7.1 单元测试示例 describe('Memos Integration', () => { test('URL normalization', () => { expect(normalizeUrl('https://example.com/', '/m/123')) .toBe('https://example.com/m/123'); expect(normalizeUrl('https://example.com', 'm/123')) .toBe('https://example.com/m/123'); }); test('Content processing', () => { const input = 'Hello [world](https://example.com)'; const processed = processMemoContent(input); expect(processed).toContain('href="https://example.com"'); }); }); 剩余 10 行代码 展开剩余代码 8. 结论 本文提出的 Hugo 与 Memos 多用户微博系统集成方案,通过系统的架构设计和严谨的代码实现,成功在静态网站环境中引入了动态社交功能。关键技术贡献包括:
小十
入驻不足1年
借助 GitHub Actions 自动化部署 Hugo 网站到自有服务器
从手动部署到自动化的工作流演进 在网站开发的初期阶段,为了深入理解整个发布流程,我一直在本地环境中使用 Hugo 手动生成静态网站。这个过程包括:运行 hugo 命令生成静态文件,手动打包 public 目录,然后通过 SCP 或 FTP 上传到服务器,最后在服务器上解压并配置。虽然这种方法让我对网站部署的每个环节都有了清晰的认识,但随着内容的增多,这种重复性工作变得越来越耗时。 虽然现在有 Vercel、Netlify 等优秀的静态网站托管平台,但由于我已经拥有一台国内的轻量级云服务器(主要用于 frp 内网穿透服务),我决定充分利用现有资源,直接在这台服务器上托管我的 Hugo 网站。 自动化部署方案设计 最终实现的解决方案是:通过 GitHub 私有仓库存储 Hugo 源码,利用 GitHub Actions 实现自动化构建和部署,使用 rsync 将生成的静态文件同步到自己的服务器。 这种方案的优势在于: 自动化:代码推送后自动完成整个构建和部署流程 版本控制:所有变更都有完整的历史记录 安全性:私有仓库保护源代码,SSH 密钥保证传输安全 成本效益:充分利用现有服务器资源 服务器端配置 环境准备 我使用 1Panel 面板管理服务器,网站目录为: /opt/1panel/apps/openresty/openresty/www/sites/xiaoten.com/index安装 rsync 由于同步方案使用 rsync,需要在服务器上安装: # CentOS/RHEL 系统 sudo yum install -y rsync # Ubuntu/Debian 系统 sudo apt-get update && sudo apt-get install -y rsync安全配置:SSH 密钥认证 为了实现安全的自动化部署,需要配置 GitHub Actions 对服务器的访问权限。
小十
入驻不足1年
终于初步完成了从Typecho到Hugo的网站迁移
整体历程 2011年2月2日,本站启用并使用WordPress; 2021年7月29日,从WordPress迁移至Typecho; 2025年10月16日,终于正式把域名指向到了Hugo生成的静态页面。 主要过程 因为涉及到html转markdown,使用自动化的转换工具带了一些排版混乱、不合理的转义等等,本来想仿照有些博主,写个脚本自动优化,但随后又转念一想,还是都过一遍吧,于是在工作中见缝插针,以及下班后沉浸到半夜,持续两天枯燥的html转markdown的过程。 虽然当时WordPress转Typecho时已进行了一些格式上的适配,但这次回过头看,问题特别多。 因此还是启用了21年转Typecho时wordpress的备份,好在迁移到Typecho,并没有写过几篇文章,迁移到Typecho以后的文章手动添加就好了。 静态博客生成工具的确定 决定采用静态博客的目的,还是想学习现在的博客主流模式,让更多的注意力在写作本身上面,摆脱臃肿的博客程序,虽说typecho已经非常轻量了,但可能是使用主题的问题,总会有遇到各种各样的使用问题,自身也没有精力去学着去写一个精简的主题,另外静态博客可以很方便的托管到一些GitHub等这样的一些开源平台上,不用再受制于服务器等这些问题。 目前,整个的服务器都已迁移到家庭的nas里,通过购买阿里云的轻量云服务器仅用来内网穿透+反向代理使用,整体成本很低。 除了个人博客这个站点以外的其他网站应用或服务应用还得依托于家里的服务器。 经过浏览了多个业内大佬的网站之后,发现Hugo的覆盖率还挺高,另外Hugo官网的一些主题也都是偏简洁的风格,在B站看了Hugo的实现原理及教程,也很容易上手,因此确定了Hugo作为生成工具。 Hugo模板的确定 最终在官网上对比了几个比较主流的模板之后,最终目光还是集中在了一个比较简洁的一个主题: Hugo blog awesome 。 决定开始使用这个主题是在10月11日,截至写这篇文章已经调试使用了5天时间,目前整体使用起来没有大的问题,但还是有一些自己不满意且比较棘手的问题。不知道后续会不会再换模板,目前先这样用着。 字体 在原有模板的基础上应用了思源宋体,并通过 cn-font-split 实现了字体切割,实现网页字体的快速加载。 分页功能 另外想通过hugo自带的分页功能实现文章列表的分页,但尝试很多种方法,并借助deepseek都没有办法实现。因此最终通过了JavaScript脚本实现了分页功能,这个看后续有没有更好的解决办法。 已解决分页问题,详见文章: 解决 Hugo 分页器返回空页面的诡异问题:.Paginate 不能被多次调用 。 全文搜索 静态博客如果想实现全文搜索,方法也比较多,我采用了比较成熟的工具: Pagefind 。 代码高亮的自动转换 默认hugo自带的代码高亮功能,一般只能设置一个样式,固定下来之后,不管主题是白色还是黑色,都是统一的一个代码高亮样式,为了实现白色/黑色主题切换时代码高亮主题也自动进行更换,通过脚本实现不同主题下不同的代码高亮方案。 识别外部链接自动启用新窗口打开 通过在layouts/_default/_markup/目录下创建render-link.html文件,并写入代码: {{ $url := urls.Parse .Destination }} {{ $isExternal := eq $url.Scheme "http" "https" }} <a href="{{ .Destination | safeURL }}" {{ if $isExternal }}target="_blank" rel="noopener noreferrer"{{ end }} {{ with .Title }}title="{{ . }}"{{ end }}> {{ .Text | safeHTML }} </a> 剩余 3 行代码 展开剩余代码 实现自动识别非本站链接采用新窗口打开的功能。
Tree Hole
入驻不足1年
链路聚合负载不均导致部分链路拥塞
问题描述 核心交换机之间通过一条4*10G Eth-Trunk(链路聚合组)互联。监控发现,该聚合组的总流量并 […]

© 2026 好站网HaoZhan.wang 1.5 版权所有

苏ICP备19065220号-4 萌ICP备20269980号 本站数据 版本历史 关于本站