按照惯例,如果没有特别说明,css样式都是在 assets/scss/custom.scss 文件里修改,短代码的html文件放在 layouts/shortcodes 文件夹里。
谷歌robots
【建站技术】在你的 Hugo 博客中使用 Google Analytics 来统计和分析流量数据
链接样式
偶然看到了一个自己很喜欢的链接样式: https://www.shirakii.com/post/latex-chinese-record/
但是作者没有公开它的配置,于是自己手搓了一版:
1a.link {
2 color: dodgerblue;
3 font-weight: 600;
4 padding: 0 2px;
5 box-shadow: none;
6 cursor: url('/img/link.png'), pointer !important;
7 position: relative;
8 display: inline;
9 box-decoration-break: clone;
10 -webkit-box-decoration-break: clone;
11
12 background-image: linear-gradient(to right, dodgerblue, dodgerblue);
13 background-size: 0 2px;
14 background-position: left 100%;
15 background-repeat: no-repeat;
16 transition: background-size 0.3s ease, background-position 0s ease 0.3s;
17 padding-bottom: 2px; /* 增加底部内边距 */
18}
19
20a.link:hover {
21 background-size: 100% 2px;
22 box-shadow: none;
23 text-decoration: none;
24 background-position: left 100%;
25 transition: background-size 0.3s ease;
26}
27
28a.link:not(:hover) {
29 background-position: right 100%;
30 background-size: 0 2px;
31 transition: background-size 0.3s ease;
32}
33
34[data-scheme="light"] a.link {
35 color: deepskyblue;
36 background-image: linear-gradient(to right, deepskyblue, deepskyblue);
37}
不过发现用这种跳转链接不会出现上面的scss效果:<a href="/shortcodes" target="_blank">点我看看</a>
点我看看。修改代码:
1a[linkscss="true"],
2a.link {
3 color: dodgerblue;
4 font-weight: 600;
5 padding: 0 2px;
6 box-shadow: none;
7 cursor: url('/img/link.png'), pointer !important;
8 position: relative;
9 display: inline;
10 box-decoration-break: clone;
11 -webkit-box-decoration-break: clone;
12
13 background-image: linear-gradient(to right, dodgerblue, dodgerblue);
14 background-size: 0 2px;
15 background-position: left 100%;
16 background-repeat: no-repeat;
17 transition: background-size 0.3s ease, background-position 0s ease 0.3s;
18 padding-bottom: 4px; /* 增加底部内边距 */
19}
20
21a[linkscss="true"]:hover,
22a.link:hover {
23 background-size: 100% 2px;
24 box-shadow: none;
25 text-decoration: none;
26 background-position: left 100%;
27 transition: background-size 0.3s ease;
28}
29
30a[linkscss="true"]:not(:hover),
31a.link:not(:hover) {
32 background-position: right 100%;
33 background-size: 0 2px;
34 transition: background-size 0.3s ease;
35}
36
37[data-scheme="light"] a[linkscss="true"],
38[data-scheme="light"] a.link {
39 color: deepskyblue;
40 background-image: linear-gradient(to right, deepskyblue, deepskyblue);
41}
于是 <a href="/shortcodes" target="_blank" linkscss="true">点这里</a> 就能出现scss效果了:点这里
文章添加点赞按钮
参考■■Loading:《hugo装修日志10》■■,说的很详细了。
系列文章
参考在 Hugo 里显示系列文章,新建 layouts/partials/series.html:
1{{ range $index, $series := .Params.series }}
2<div class="article-list article-series">
3 <header>
4 <div class="series-section-title">
5 {{ i18n "partOf" }}
6 <a href="{{ "/series/" | relLangURL }}{{ . | urlize }}" linkscss="true">{{ $series }}</a>
7 {{ i18n "series" }}:
8 </div>
9 </header>
10
11 <div class="article-series-container">
12 <ol>
13 {{ range $ind, $post := $.Site.Pages.ByDate }}
14 {{ if in $post.Params.series $series }}
15 <li class="article-series-item">
16 {{ if eq $post.Permalink $.Page.Permalink }}
17 <span class="article-series-active">{{ $post.Params.title }} ({{ i18n "currentArticle" }})</span>
18 {{ else }}
19 <a href="{{ $post.Permalink }}" linkscss="true">{{ $post.Params.title }}</a>
20 {{ end }}
21 </li>
22 {{ end }}
23 {{ end }}
24 </ol>
25 </div>
26</div>
27{{ end }}
在 layouts/_default/single.html 对应位置加入:
1{{ if .Params.links }}
2 {{ partial "article/components/links" . }}
3{{ end }}
4
5<!-- Add the series partial here -->
6{{ partial "series.html" . }}
7
8{{ partial "article/components/related-content" . }}
在 hugo.yaml 最后加入:
1taxonomies:
2 category: categories
3 tag: tags
4 series: series
在多语言配置文件 i18n/zh-cn.yaml 最后面加入:
1partOf:
2 other: "本文属于"
3
4series:
5 other: "系列"
6
7currentArticle:
8 other: "本文"
在 assets/scss/custom.scss 最后加入:
1.article-series {
2 margin: 2rem 0;
3 padding: 1rem;
4 gap: 0;
5 color: var(--card-text-color-main);
6 background-color: var(--card-background);
7 border-radius: var(--card-border-radius);
8 box-shadow: var(--shadow-l1);
9}
10
11.article-series-item {
12 margin-bottom: 0.8rem;
13}
14
15.series-section-title {
16 margin-top: 8px;
17 margin-left: 8px;
18}
19
20.section-description {
21 color: var(--card-text-color-main);
22 font-weight: bold;
23}
24
25.article-series-container {
26 margin-bottom: -8px;
27}
新建 layouts/series/list.html:
1{{ define "body-class" }}template-series{{ end }}
2{{ define "main" }}
3 <header>
4 {{/* Display the series title and description */}}
5 <h2 class="section-title">{{ .Title }} {{ i18n "series" }}</h2>
6 {{ if .Description }}
7 <div class="section-description">
8 {{ .Description }}
9 </div>
10 {{ end }}
11 </header>
12
13 {{/* Get the pages associated with this specific series term */}}
14 {{ $pagesInSeries := .Pages }}
15
16 {{/* Check if there are any pages in this series */}}
17 {{ if $pagesInSeries }}
18 {{/* Group the pages in this series by year, ordered reverse chronologically (newest year first) */}}
19 {{ range ($pagesInSeries.GroupByDate "2006").Reverse }}
20 {{/* Create an ID for the year group, e.g., "2023" */}}
21 {{ $id := lower (replace .Key " " "-") }}
22 <div class="archives-group" id="{{ $id }}">
23 {{/* Display the year as a section title, linking to the ID */}}
24 <h2 class="archives-date section-title"><a href="{{ $.RelPermalink }}#{{ $id }}">{{ .Key }}</a></h2>
25 {{/* Use the compact article list style */}}
26 <div class="article-list--compact">
27 {{/* Range through the pages within this year group (already sorted by date by Hugo) */}}
28 {{ range .Pages }}
29 {{/* Display each article using the compact partial */}}
30 {{ partial "article-list/compact" . }}
31 {{ end }}
32 </div>
33 </div>
34 {{ end }}
35 {{ else }}
36 <p>{{ i18n "noPostsInSeries" | default "There are no articles in this series yet." }}</p>
37 {{/* Optional: Add a default message or translation for when a series is empty */}}
38 {{/* You might need to add "noPostsInSeries" to your i18n files */}}
39 {{ end }}
40
41 {{/* Footer partial */}}
42 {{ partialCached "footer/footer" . }}
43{{ end }}
新建 layouts/series/terms.html:
1{{ define "body-class" }}template-series{{ end }}
2{{ define "main" }}
3 <header>
4 {{- $taxonomy := $.Site.GetPage "taxonomyTerm" "series" -}}
5 {{- $terms := $taxonomy.Pages -}}
6 {{ if $terms }}
7 <h2 class="section-title">{{ i18n "series" }}</h2>
8 <div class="subsection-list">
9 <div class="article-list--tile" style="display: flex; flex-direction: column;">
10 {{ range $terms }}
11 <div class="series-group" style="flex: 1 auto; margin: 10px;">
12 <h3 class="series-title">
13 <a href="{{ .Permalink }}">{{ .Title }}</a>
14 </h3>
15 {{ if .Description }}
16 <div class="series-description">
17 {{ .Description }}
18 </div>
19 {{ end }}
20 <div class="article-list--horizontal" style="display: flex; overflow-x: auto;">
21 {{ $articles := where .Site.RegularPages "Params.series" "intersect" (slice .Title) }}
22 {{ range $index, $element := $articles }}
23 <div class="article-tile" style="flex: 0 0 auto; margin: 5px;">
24 {{ partial "article-list/tile" (dict "context" $element "size" "250x150" "Type" "taxonomy") }}
25 </div>
26 {{ end }}
27 </div>
28 </div>
29 {{ end }}
30 </div>
31 </div>
32 {{ end }}
33 </header>
34
35 {{ partialCached "footer/footer" . }}
36{{ end }}
然后只需要在文章头部加入:series: ["Hugo 博客搭建"] 就可以啦。
过期提醒
参考在 MemE 主题文章开头添加过时提醒。由于我不想每篇文章开头都加上这个提醒,所以把它写成了短代码:
1<!-- layouts/shortcodes/expiration-notice.html -->
2<div id="update-info" class="article-notice update-notice">
3 <span class="notice-icon">
4 {{ partial "helper/icon" "clock" }}
5 </span>
6 <span class="notice-content">
7 <p id="update-text">上次更新于 {{ .Page.Lastmod.Format "2006-01-02" }},部分内容可能已经过期。</p>
8 </span>
9</div>
10
11<script>
12 document.addEventListener('DOMContentLoaded', function() {
13 const lastModified = new Date("{{ .Page.Lastmod.Format "2006-01-02T15:04:05-0700" }}");
14 const now = new Date();
15 const duration = now - lastModified;
16 const days = Math.floor(duration / (1000 * 60 * 60 * 24));
17 const years = Math.floor(days / 365);
18 const remainingDays = days % 365;
19
20 let updateText = "";
21 if (years >= 1) {
22 updateText = `上次更新于 ${years} 年 ${remainingDays} 天前,部分内容可能已经过期。`;
23 } else {
24 updateText = `上次更新于 ${days} 天前,部分内容可能已经过期。`;
25 }
26
27 document.getElementById("update-text").textContent = updateText;
28 });
29</script>
1.article-notice {
2 display: flex;
3 align-items: center;
4 padding: 0.75rem 1rem;
5 margin: 1rem 0;
6 border-radius: 0.8rem;
7 background-color: var(--card-background);
8 border-left: 3.5px solid var(--accent-color);
9}
10
11.update-notice {
12 background-color: rgba(var(--primary-color-rgb), 0.1);
13 border-left-color: var(--primary-color);
14}
15
16.notice-icon {
17 margin-right: 0.75rem;
18 color: var(--primary-color);
19 margin-top: 0.75rem;
20}
21
22.notice-content p {
23 margin: 0;
24 color: crimson;
25 font-weight: bold;
26}
27
28[data-scheme=dark] .notice-content p {
29 color: gold;
30}
在文章开头或者任意地方加入:
1{{< expiration-notice >}}
上次更新于 2025-09-16,部分内容可能已经过期。
代码变化显示
参考实验田-diffcode,新建 /layouts/partials/diffcode.html:
1{{- $diffBlock := (. | replaceRE "(?m:^```\\w+)" "```diff") -}}
2{{- $diffBlock := (index (split $diffBlock "```") 1) -}}
3{{- $diffBlock := ($diffBlock | replaceRE "(?m:^([+\\-~])?.*$)" "$1") -}}
4{{- $diffBlock := ($diffBlock | replaceRE "(?m:^$)" "." | replaceRE "\n" "") -}}
5
6{{- $lines := split $diffBlock "" -}}
7{{- $mainBlock := (. | replaceRE "(?m:^[+\\-~])" "") | markdownify -}}
8
9{{- $lineCount := 0 -}}
10{{- $output := "" -}}
11
12{{- $diffBlock := (. | replaceRE "(?m:^```\\w+)" "```diff") -}}
13{{- $diffBlock := (index (split $diffBlock "```") 1) -}}
14{{- $diffBlock := ($diffBlock | replaceRE "(?m:^([+\\-~])?.*$)" "$1") -}}
15{{- $diffBlock := ($diffBlock | replaceRE "(?m:^$)" "." | replaceRE "\n" "") -}}
16
17{{- $lines := split $diffBlock "" -}}
18{{- $mainBlock := (. | replaceRE "(?m:^[+\\-~])" "") | markdownify -}}
19
20{{- $lineCount := 0 -}}
21{{- $output := "" -}}
22
23{{- range $i, $l := (split $mainBlock `<span class="line">`) -}}
24 {{- if gt $i 0 -}}
25 {{- $lineClass := "" -}}
26 {{- if and (gt $lineCount 0) (lt $lineCount (len $lines)) -}}
27 {{- if eq (index $lines (sub $lineCount -1)) "+" -}}
28 {{- $lineClass = "diff-add" -}}
29 {{- else if eq (index $lines (sub $lineCount -1)) "-" -}}
30 {{- $lineClass = "diff-remove" -}}
31 {{- else if eq (index $lines (sub $lineCount -1)) "~" -}}
32 {{- $lineClass = "diff-highlight" -}}
33 {{- end -}}
34 {{- end -}}
35
36 {{- if $lineClass -}}
37 {{- $output = printf "%s<span class=\"line %s\">" $output $lineClass -}}
38 {{- else -}}
39 {{- $output = printf "%s<span class=\"line\">" $output -}}
40 {{- end -}}
41
42 {{- $lineCount = add $lineCount 1 -}}
43 {{- else -}}
44 {{- $output = $l -}}
45 {{- end -}}
46
47 {{- if gt $i 0 -}}
48 {{- $output = printf "%s%s" $output $l -}}
49 {{- end -}}
50{{- end -}}
51
52{{ $output | safeHTML }}
新建 /layouts/shortcodes/diffcode.html:
1{{ partial "diffcode.html" .Inner }}
在 /assets/scss/custom.scss 中加入:
1.highlight {
2 .line {
3 &.diff-add {
4 background-color: rgba(0, 255, 0, 0.2);
5
6 &::before {
7 content: "+";
8 color: green;
9 position: absolute;
10 left: 0.5em;
11 user-select: none;
12 }
13 }
14
15 &.diff-remove {
16 background-color: rgba(255, 0, 0, 0.2);
17
18 &::before {
19 content: "-";
20 color: red;
21 position: absolute;
22 left: 1.1rem;
23 user-select: none;
24 }
25 }
26
27 &.diff-highlight {
28 background-color: rgba(255, 255, 0, 0.2);
29
30 &::before {
31 content: "~";
32 color: orange;
33 position: absolute;
34 left: 0.5em;
35 user-select: none;
36 }
37 }
38 }
39}
40
41// 确保行号正确对齐
42.highlight table td {
43 padding: 5px;
44}
45
46.highlight table pre {
47 margin: 0;
48}
49
50// 为行号留出足够空间
51.highlight .lnt, .highlight .ln {
52 margin-right: 0.8em;
53 padding: 0 0.4em 0 0.4em;
54}
可以这样使用这个短代码:
1{{< diffcode >}}
2```python
3def hello_world():
4+ print("Hello, World!")
5- print("Goodbye, World!")
6~ return None
7{{< /diffcode >}}
1def hello_world():
2 print("Hello, World!")
3 print("Goodbye, World!")
4 return None
代码块字体样式更改
代码块的字体还是用 ‘JetBrains Mono’ 更有味道。在自定义的scss里加入:
1.highlight *,
2.highlight pre,
3.highlight code,
4.chroma *,
5.chroma code,
6.chroma pre {
7 font-family: 'JetBrains Mono', "LXGW WenKai TC", "Gowun Batang", monospace;
8 font-size: 14px;
9}
个人主页
成品展示:ifantic.de
我用的是赛博菩萨 CloudFlare Pages 部署的。先把项目clone到本地,安装node,然后安装pnpm,安装依赖:
1npm install -g pnpm
2pnpm install
3// 预览
4pnpm dev
5// 构建
6pnpm build
然后把 .env.example 文件的名称修改为 .env,修改其中的配置。本地运行 pnpm dev 预览即可。
预览后部署到 Cloudflare Pages:
- 创建项目:点击
创建应用程序-> 选择Pages选项卡 -> 点击上传资源。 - 上传文件:给你的项目起个名字,然后将本地
dist文件夹内的所有文件和子文件夹拖拽到上传区域,或者通过浏览文件选择dist文件夹。 - 部署站点:点击
部署站点 。Cloudflare会上传你的文件并完成部署。
修复站点名称字体不一致
将 public/font 的 Pacifico-Regular-all.ttf 替换原来的 Pacifico-Regular.ttf。
修复默认的天气api
修改 src/api/index.js 第73行:
1// https://api.oioweb.cn/doc/weather/GetWeather
2export const getOtherWeather = async () => {
3 const res = await fetch("https://api.oioweb.cn/api/weather/GetWeather");
4 const res = await fetch("https://weather.sl.al/");
5 return await res.json();
6};
更改 src/components/Weather.vue 为以下内容:
1<template>
2 <div class="weather" v-if="weatherData.adCode.city && weatherData.weather.weather">
3 <span v-if="!mainKey">{{ weatherData.adCode.region }} </span>
4 <span>{{ weatherData.adCode.city }} </span>
5 <span>{{ weatherData.weather.weather }} </span>
6 <span>{{ weatherData.weather.temperature }}℃</span>
7<!--
8 <span class="sm-hidden">
9 {{
10 weatherData.weather.winddirection?.endsWith("风")
11 ? weatherData.weather.winddirection
12 : weatherData.weather.winddirection + "风"
13 }}
14 </span>
15 <span class="sm-hidden">{{ weatherData.weather.windpower }} 级</span>
16-->
17 <template v-if="mainKey">
18 <span class="sm-hidden">
19 {{
20 weatherData.weather.winddirection?.endsWith("风")
21 ? weatherData.weather.winddirection
22 : weatherData.weather.winddirection + "风"
23 }}
24 </span>
25 <span class="sm-hidden">{{ weatherData.weather.windpower }} 级</span>
26 </template>
27 </div>
28 <div class="weather" v-else>
29 <span>天气数据获取失败</span>
30 </div>
31</template>
32
33
34
35
36
37<script setup>
38import { getAdcode, getWeather, getOtherWeather } from "@/api";
39import { Error } from "@icon-park/vue-next";
40
41// 高德开发者 Key
42const mainKey = import.meta.env.VITE_WEATHER_KEY;
43
44// 天气数据
45const weatherData = reactive({
46 adCode: {
47 city: null, // 城市
48 adcode: null, // 城市编码
49 },
50 weather: {
51 weather: null, // 天气现象
52 temperature: null, // 实时气温
53 winddirection: null, // 风向描述
54 windpower: null, // 风力级别
55 },
56});
57
58// 取出天气平均值
59const getTemperature = (min, max) => {
60 try {
61 // 计算平均值并四舍五入
62 const average = (Number(min) + Number(max)) / 2;
63 return Math.round(average);
64 } catch (error) {
65 console.error("计算温度出现错误:", error);
66 return "NaN";
67 }
68};
69
70// 获取天气数据
71const getWeatherData = async () => {
72 try {
73 // 获取地理位置信息
74 if (!mainKey) {
75
76/*
77 console.log("未配置,使用备用天气接口");
78 const result = await getOtherWeather();
79 console.log(result);
80 const data = result.result;
81 weatherData.adCode = {
82 city: data.city.City || "未知地区",
83 // adcode: data.city.cityId,
84 };
85 weatherData.weather = {
86 weather: data.condition.day_weather,
87 temperature: getTemperature(data.condition.min_degree, data.condition.max_degree),
88 winddirection: data.condition.day_wind_direction,
89 windpower: data.condition.day_wind_power,
90 };
91*/
92
93 console.log("未配置,使用备用天气接口");
94 const result = await getOtherWeather();
95 console.log(result);
96 const data = result; // 不需要 .result 了,因为数据在顶层
97
98 weatherData.adCode = {
99 city: data.location?.city || "未知地区",
100 // 如果需要 region 也可以加上
101 region: data.location?.region
102 };
103
104 weatherData.weather = {
105 weather: data.current?.description, // 天气描述
106 temperature: data.current?.temperature, // 温度
107 // 如果需要体感温度
108 // feelsLike: data.current?.feelsLike,
109
110 // 如果新的数据结构中没有风向和风力数据,可以设置默认值
111 //winddirection: "暂无数据",
112 //windpower: "暂无数据",
113
114 // 如果需要添加空气质量信息
115 //airQuality: {
116 // category: data.current?.airQuality?.category,
117 // statement: data.current?.airQuality?.statement
118 //}
119 };
120
121 } else {
122 // 获取 Adcode
123 const adCode = await getAdcode(mainKey);
124 console.log(adCode);
125 if (adCode.infocode !== "10000") {
126 throw "地区查询失败";
127 }
128 weatherData.adCode = {
129 city: adCode.city,
130 adcode: adCode.adcode,
131 };
132 // 获取天气信息
133 const result = await getWeather(mainKey, weatherData.adCode.adcode);
134 weatherData.weather = {
135 weather: result.lives[0].weather,
136 temperature: result.lives[0].temperature,
137 winddirection: result.lives[0].winddirection,
138 windpower: result.lives[0].windpower,
139 };
140 }
141 } catch (error) {
142 console.error("天气信息获取失败:" + error);
143 onError("天气信息获取失败");
144 }
145};
146
147// 报错信息
148const onError = (message) => {
149 ElMessage({
150 message,
151 icon: h(Error, {
152 theme: "filled",
153 fill: "#efefef",
154 }),
155 });
156 console.error(message);
157};
158
159onMounted(() => {
160 // 调用获取天气
161 getWeatherData();
162});
163</script>
小记
好像什么都没干一年就过去了。。。