[{"content":"1. 创建分类列表页模板\r首先创建 layouts/categories/terms.html 文件，这个模板将专门用于渲染分类列表页面 (/categories/):\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 {{ define \u0026#34;main\u0026#34; }} \u0026lt;div class=\u0026#34;categories-page\u0026#34;\u0026gt; \u0026lt;header class=\u0026#34;page-header\u0026#34;\u0026gt; \u0026lt;h1 class=\u0026#34;page-title\u0026#34;\u0026gt;所有分类\u0026lt;/h1\u0026gt; \u0026lt;p class=\u0026#34;page-description\u0026#34;\u0026gt;本站点共有 {{ len .Data.Terms }} 个分类\u0026lt;/p\u0026gt; \u0026lt;/header\u0026gt; \u0026lt;div class=\u0026#34;categories-cloud\u0026#34;\u0026gt; {{ $categories := .Data.Terms }} {{ if $categories }} {{ $max := len (index $categories.ByCount 0).Pages }} {{ $min := len (index $categories.ByCount.Reverse 0).Pages }} {{ range $name, $taxonomy := $categories }} {{ $count := len $taxonomy.Pages }} \u0026lt;!-- 计算权重和字体大小，可以根据需要调整公式 --\u0026gt; {{ $weight := div (sub (math.Log $count) (math.Log $min)) (sub (math.Log $max) (math.Log $min)) }} {{ $fontSize := add 0.8 (mul $weight 2.5) }} \u0026lt;a href=\u0026#34;{{ .Page.Permalink }}\u0026#34; class=\u0026#34;category-cloud-item\u0026#34; style=\u0026#34;font-size: {{ $fontSize }}em;\u0026#34; title=\u0026#34;{{ $count }} 篇文章\u0026#34;\u0026gt; {{ .Page.Title }} \u0026lt;sup\u0026gt;{{ $count }}\u0026lt;/sup\u0026gt; \u0026lt;/a\u0026gt; {{ end }} {{ else }} \u0026lt;p class=\u0026#34;no-content\u0026#34;\u0026gt;暂无分类\u0026lt;/p\u0026gt; {{ end }} \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; {{ end }} 2. 创建标签云页面模板\r接下来创建 layouts/tags/terms.html 文件，这个模板将专门用于渲染标签云页面 (/tags/):\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 {{ define \u0026#34;main\u0026#34; }} \u0026lt;div class=\u0026#34;tags-page\u0026#34;\u0026gt; \u0026lt;header class=\u0026#34;page-header\u0026#34;\u0026gt; \u0026lt;h1 class=\u0026#34;page-title\u0026#34;\u0026gt;标签云\u0026lt;/h1\u0026gt; \u0026lt;p class=\u0026#34;page-description\u0026#34;\u0026gt;本站点共有 {{ len .Data.Terms }} 个标签\u0026lt;/p\u0026gt; \u0026lt;/header\u0026gt; \u0026lt;div class=\u0026#34;tag-cloud-container\u0026#34;\u0026gt; {{ $tags := .Data.Terms }} {{ if $tags }} {{ $max := len (index $tags.ByCount 0).Pages }} {{ $min := len (index $tags.ByCount.Reverse 0).Pages }} {{ range $name, $taxonomy := $tags }} {{ $count := len $taxonomy.Pages }} {{ $weight := div (sub (math.Log $count) (math.Log $min)) (sub (math.Log $max) (math.Log $min)) }} {{ $fontSize := add 0.8 (mul $weight 2.5) }} \u0026lt;a href=\u0026#34;{{ \u0026#34;/tags/\u0026#34; | relLangURL }}{{ $name | urlize }}\u0026#34; class=\u0026#34;tag-cloud-item\u0026#34; style=\u0026#34;font-size: {{ $fontSize }}em;\u0026#34; title=\u0026#34;{{ $count }} 篇文章\u0026#34;\u0026gt; {{ $name }} \u0026lt;sup\u0026gt;{{ $count }}\u0026lt;/sup\u0026gt; \u0026lt;/a\u0026gt; {{ end }} {{ else }} \u0026lt;p class=\u0026#34;no-content\u0026#34;\u0026gt;暂无标签\u0026lt;/p\u0026gt; {{ end }} \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; {{ end }} 3. 添加样式\r为了使这两个页面看起来更美观，你需要在自定义 CSS 文件中添加以下样式。可以在 assets/css/custom.css 中添加：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 /* 分类云样式 */ .categories-cloud { text-align: center; line-height: 2.5; margin: 2rem 0; } .category-cloud-item { display: inline-block; margin: 0.5rem; padding: 0.6rem 1.2rem; background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 9999px; text-decoration: none; color: #495057; transition: all 0.3s ease; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); } .category-cloud-item:hover { background-color: #3b82f6; color: white; transform: translateY(-2px); box-shadow: 0 6px 12px rgba(59, 130, 246, 0.3); border-color: #3b82f6; } /* 响应式设计 */ @media (max-width: 768px) { .categories-cloud { line-height: 2; } .category-cloud-item { margin: 0.3rem; padding: 0.5rem 1rem; font-size: 0.9em !important; /* 移动端适当减小字体 */ } } /* 分类列表页样式 */ .categories-page .page-header { text-align: center; margin-bottom: 2rem; } .categories-page .page-title { font-size: 2.5rem; margin-bottom: 0.5rem; } .categories-page .page-description { color: #6b7280; font-size: 1.1rem; } .categories-list { list-style: none; padding: 0; max-width: 700px; margin: 0 auto; } .category-item { margin-bottom: 1rem; border-bottom: 1px solid #e5e7eb; padding-bottom: 1rem; } .category-link { display: flex; justify-content: space-between; align-items: center; text-decoration: none; color: #374151; padding: 0.75rem 1rem; border-radius: 8px; transition: all 0.3s ease; } .category-link:hover { background-color: #f3f4f6; transform: translateX(5px); } .category-name { font-size: 1.2rem; font-weight: 500; } .category-count { background-color: #3b82f6; color: white; padding: 0.25rem 0.75rem; border-radius: 9999px; font-size: 0.875rem; } /* 标签云页面样式 */ .tags-page .page-header { text-align: center; margin-bottom: 2rem; } .tags-page .page-title { font-size: 2.5rem; margin-bottom: 0.5rem; } .tags-page .page-description { color: #6b7280; font-size: 1.1rem; } .tag-cloud-container { text-align: center; line-height: 2.5; margin: 2rem 0; } .tag-cloud-item { margin: 0.5rem; padding: 0.5rem 1rem; display: inline-block; border-radius: 8px; background-color: #f3f4f6; color: #4b5563; text-decoration: none; transition: all 0.3s ease; border: 1px solid #e5e7eb; } .tag-cloud-item:hover { background-color: #e5e7eb; transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); } .no-content { text-align: center; color: #6b7280; font-style: italic; margin: 2rem 0; } 4. 配置菜单项\r确保你的菜单配置正确指向这些页面：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 menu: main: - identifier: categories name: 分类 url: /categories/ weight: 2 params: icon: folder - identifier: tags name: 标签 url: /tags/ weight: 3 params: icon: tag 5. 验证和测试\r完成以上步骤后：\n运行 hugo server 启动本地服务器 访问 http://localhost:1313/categories/ 查看分类列表页 访问 http://localhost:1313/tags/ 查看标签云页面 检查导航菜单中的链接是否正确工作 注意事项\r模板优先级：Hugo 会优先使用这些专用模板（layouts/categories/terms.html 和 layouts/tags/terms.html），而不是通用的 layouts/_default/terms.html。\n分类法配置：确保你的配置文件中正确定义了分类法：\n1 2 3 taxonomies: category: categories tag: tags 内容文件：如果你的 content 目录下有 categories.md 或 tags.md，它们可能会覆盖这些自动生成的页面。如果遇到问题，可以考虑删除或重命名这些内容文件。\n样式调整：你可以根据自己网站的主题风格，调整 CSS 中的颜色、间距、字体大小等属性。\n","date":"2025-09-10T22:39:53+08:00","image":"https://sports0808008.github.io/img/a9.jpg","permalink":"https://sports0808008.github.io/post/%E5%88%86%E9%A1%B5%E5%88%97%E8%A1%A8%E5%92%8C%E6%A0%87%E7%AD%BE%E4%BA%91/","title":"分页列表和标签云"},{"content":"文章设置\r构建hugo文章格式\rhugo/content/post/HugoStack主题美化/ 创建 index.md （名字一定要叫 index ）\n原格式:\n1 2 F:\\hugo-github-local\\my-stack-site\\content\\post F:\\hugo-github-local\\my-stack-site\\content\\images 使用脚本实现hugo文章格式\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 import os import shutil import glob def organize_md_files(): # 获取当前目录中所有的 .md 文件 md_files = glob.glob(\u0026#34;*.md\u0026#34;) if not md_files: print(\u0026#34;当前目录中没有找到任何 .md 文件\u0026#34;) return print(f\u0026#34;找到了 {len(md_files)} 个 .md 文件\u0026#34;) for md_file in md_files: # 获取文件名（不含扩展名） file_name_without_ext = os.path.splitext(md_file)[0] # 创建对应的文件夹 folder_name = file_name_without_ext if not os.path.exists(folder_name): os.makedirs(folder_name) print(f\u0026#34;创建文件夹: {folder_name}\u0026#34;) else: print(f\u0026#34;文件夹已存在: {folder_name}\u0026#34;) # 构建目标路径 destination_path = os.path.join(folder_name, \u0026#34;index.md\u0026#34;) # 移动并重命名文件 shutil.move(md_file, destination_path) print(f\u0026#34;移动文件: {md_file} -\u0026gt; {destination_path}\u0026#34;) print(\u0026#34;所有文件处理完成！\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: organize_md_files() 完成后会根据文章titie名称创建文件夹\n文章引入\n文章下载cos图片并存本地\r遍历指定目录（包括子目录）下的所有Markdown文件。 对于每个Markdown文件，查找所有的网络图片（即以http://或https://开头的图片链接）。 下载这些网络图片到统一的images目录（位于posts目录同级）中，避免重复下载。 将Markdown文件中的网络图片链接替换为本地相对路径。 python脚本\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 import os import re import requests import hashlib from urllib.parse import urlparse from pathlib import Path def download_image(url, folder_path, filename=None): \u0026#34;\u0026#34;\u0026#34;下载图片到指定文件夹\u0026#34;\u0026#34;\u0026#34; try: response = requests.get(url, stream=True, timeout=10) if response.status_code == 200: # 确定文件名 if not filename: parsed_url = urlparse(url) filename = os.path.basename(parsed_url.path) if not filename or \u0026#39;.\u0026#39; not in filename: # 从Content-Type获取扩展名 content_type = response.headers.get(\u0026#39;content-type\u0026#39;, \u0026#39;\u0026#39;) if \u0026#39;jpeg\u0026#39; in content_type or \u0026#39;jpg\u0026#39; in content_type: ext = \u0026#39;jpg\u0026#39; elif \u0026#39;png\u0026#39; in content_type: ext = \u0026#39;png\u0026#39; elif \u0026#39;gif\u0026#39; in content_type: ext = \u0026#39;gif\u0026#39; elif \u0026#39;webp\u0026#39; in content_type: ext = \u0026#39;webp\u0026#39; else: ext = \u0026#39;bin\u0026#39; # 使用URL的MD5哈希作为文件名，避免重复下载相同图片 url_hash = hashlib.md5(url.encode()).hexdigest() filename = f\u0026#34;image_{url_hash}.{ext}\u0026#34; # 确保文件夹存在 os.makedirs(folder_path, exist_ok=True) # 完整文件路径 filepath = os.path.join(folder_path, filename) # 避免重复下载 if os.path.exists(filepath): print(f\u0026#34;图片已存在，跳过: {filename}\u0026#34;) return filepath # 保存图片 with open(filepath, \u0026#39;wb\u0026#39;) as f: for chunk in response.iter_content(1024): f.write(chunk) print(f\u0026#34;下载成功: {url} -\u0026gt; {filepath}\u0026#34;) return filepath except Exception as e: print(f\u0026#34;下载图片失败: {url}, 错误: {e}\u0026#34;) return None def process_markdown_file(md_file_path, image_base_dir=\u0026#34;../images\u0026#34;): \u0026#34;\u0026#34;\u0026#34;处理单个Markdown文件中的网络图片\u0026#34;\u0026#34;\u0026#34; # 确定图片存储目录 (统一放在posts目录下的images文件夹) posts_dir = os.path.dirname(os.path.dirname(md_file_path)) image_dir = os.path.join(posts_dir, \u0026#34;images\u0026#34;) with open(md_file_path, \u0026#39;r\u0026#39;, encoding=\u0026#39;utf-8\u0026#39;) as f: content = f.read() # 正则表达式匹配Markdown图片 pattern = r\u0026#39;!\\[(.*?)\\]\\((.*?)\\)\u0026#39; matches = re.findall(pattern, content) changed = False for alt_text, url in matches: if url.startswith((\u0026#39;http://\u0026#39;, \u0026#39;https://\u0026#39;)): local_path = download_image(url, image_dir) if local_path: # 使用相对路径替换原URL，并确保使用正斜杠 relative_path = os.path.relpath(local_path, os.path.dirname(md_file_path)) relative_path = relative_path.replace(\u0026#39;\\\\\u0026#39;, \u0026#39;/\u0026#39;) # 确保使用正斜杠 content = content.replace(f\u0026#34;]({url})\u0026#34;, f\u0026#34;]({relative_path})\u0026#34;) changed = True # 如果有更改，写回文件 if changed: with open(md_file_path, \u0026#39;w\u0026#39;, encoding=\u0026#39;utf-8\u0026#39;) as f: f.write(content) print(f\u0026#34;已更新: {md_file_path}\u0026#34;) else: print(f\u0026#34;无需更新: {md_file_path}\u0026#34;) def batch_process_markdown_files(root_dir): \u0026#34;\u0026#34;\u0026#34;批量处理目录中的所有Markdown文件\u0026#34;\u0026#34;\u0026#34; # 创建统一的图片目录 image_dir = os.path.join(root_dir, \u0026#34;images\u0026#34;) os.makedirs(image_dir, exist_ok=True) for root, dirs, files in os.walk(root_dir): # 跳过images目录本身 if \u0026#34;images\u0026#34; in dirs and root == root_dir: dirs.remove(\u0026#34;images\u0026#34;) for file in files: if file.endswith(\u0026#39;.md\u0026#39;): md_file_path = os.path.join(root, file) process_markdown_file(md_file_path) if __name__ == \u0026#34;__main__\u0026#34;: # 设置posts目录路径 posts_dir = \u0026#34;./posts\u0026#34; # 根据实际情况调整路径 # 开始批量处理 batch_process_markdown_files(posts_dir) print(\u0026#34;所有Markdown文件处理完成!\u0026#34;) 升级版带界面选择\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 import os import re import requests import hashlib from urllib.parse import urlparse import tkinter as tk from tkinter import filedialog, messagebox, ttk import threading from PIL import Image import io import time class MarkdownImageDownloader: def __init__(self): self.root = tk.Tk() self.root.title(\u0026#34;Markdown图片下载工具\u0026#34;) self.root.geometry(\u0026#34;600x400\u0026#34;) # 创建界面组件 self.setup_ui() # 存储处理统计信息 self.processed_files = 0 self.downloaded_images = 0 self.skipped_images = 0 def setup_ui(self): # 主框架 main_frame = ttk.Frame(self.root, padding=\u0026#34;10\u0026#34;) main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) # 配置网格权重 self.root.columnconfigure(0, weight=1) self.root.rowconfigure(0, weight=1) main_frame.columnconfigure(1, weight=1) # 选择目录标签和按钮 ttk.Label(main_frame, text=\u0026#34;Markdown文件目录:\u0026#34;).grid(row=0, column=0, sticky=tk.W, pady=5) self.dir_path = tk.StringVar() ttk.Entry(main_frame, textvariable=self.dir_path, width=50).grid(row=0, column=1, sticky=(tk.W, tk.E), padx=5) ttk.Button(main_frame, text=\u0026#34;浏览...\u0026#34;, command=self.browse_directory).grid(row=0, column=2, padx=5) # 图片目录设置 ttk.Label(main_frame, text=\u0026#34;图片保存目录:\u0026#34;).grid(row=1, column=0, sticky=tk.W, pady=5) self.image_dir_path = tk.StringVar() ttk.Entry(main_frame, textvariable=self.image_dir_path, width=50).grid(row=1, column=1, sticky=(tk.W, tk.E), padx=5) ttk.Button(main_frame, text=\u0026#34;浏览...\u0026#34;, command=self.browse_image_directory).grid(row=1, column=2, padx=5) # 选项设置 options_frame = ttk.LabelFrame(main_frame, text=\u0026#34;选项\u0026#34;, padding=\u0026#34;5\u0026#34;) options_frame.grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=10) options_frame.columnconfigure(1, weight=1) self.use_relative_path = tk.BooleanVar(value=True) ttk.Checkbutton(options_frame, text=\u0026#34;使用相对路径\u0026#34;, variable=self.use_relative_path).grid(row=0, column=0, sticky=tk.W) self.overwrite_existing = tk.BooleanVar(value=False) ttk.Checkbutton(options_frame, text=\u0026#34;覆盖已存在图片\u0026#34;, variable=self.overwrite_existing).grid(row=0, column=1, sticky=tk.W) # 进度条 ttk.Label(main_frame, text=\u0026#34;进度:\u0026#34;).grid(row=3, column=0, sticky=tk.W, pady=5) self.progress = ttk.Progressbar(main_frame, mode=\u0026#39;indeterminate\u0026#39;) self.progress.grid(row=3, column=1, columnspan=2, sticky=(tk.W, tk.E), pady=5) # 日志文本框 ttk.Label(main_frame, text=\u0026#34;处理日志:\u0026#34;).grid(row=4, column=0, sticky=tk.W, pady=5) self.log_text = tk.Text(main_frame, height=15, width=70) self.log_text.grid(row=5, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5) # 滚动条 scrollbar = ttk.Scrollbar(main_frame, orient=tk.VERTICAL, command=self.log_text.yview) scrollbar.grid(row=5, column=3, sticky=(tk.N, tk.S)) self.log_text[\u0026#39;yscrollcommand\u0026#39;] = scrollbar.set # 按钮框架 button_frame = ttk.Frame(main_frame) button_frame.grid(row=6, column=0, columnspan=3, pady=10) ttk.Button(button_frame, text=\u0026#34;开始处理\u0026#34;, command=self.start_processing).pack(side=tk.LEFT, padx=5) ttk.Button(button_frame, text=\u0026#34;清空日志\u0026#34;, command=self.clear_log).pack(side=tk.LEFT, padx=5) ttk.Button(button_frame, text=\u0026#34;退出\u0026#34;, command=self.root.quit).pack(side=tk.RIGHT, padx=5) # 配置主框架权重 main_frame.rowconfigure(5, weight=1) def browse_directory(self): directory = filedialog.askdirectory(title=\u0026#34;选择Markdown文件所在目录\u0026#34;) if directory: self.dir_path.set(directory) # 自动设置图片目录为Markdown目录下的images文件夹 self.image_dir_path.set(os.path.join(directory, \u0026#34;images\u0026#34;)) def browse_image_directory(self): directory = filedialog.askdirectory(title=\u0026#34;选择图片保存目录\u0026#34;) if directory: self.image_dir_path.set(directory) def log_message(self, message): \u0026#34;\u0026#34;\u0026#34;添加消息到日志\u0026#34;\u0026#34;\u0026#34; self.log_text.insert(tk.END, message + \u0026#34;\\n\u0026#34;) self.log_text.see(tk.END) self.root.update_idletasks() def clear_log(self): \u0026#34;\u0026#34;\u0026#34;清空日志\u0026#34;\u0026#34;\u0026#34; self.log_text.delete(1.0, tk.END) def download_image(self, url, folder_path, filename=None): \u0026#34;\u0026#34;\u0026#34;下载图片到指定文件夹\u0026#34;\u0026#34;\u0026#34; try: response = requests.get(url, stream=True, timeout=15) if response.status_code == 200: # 确定文件名 if not filename: parsed_url = urlparse(url) original_filename = os.path.basename(parsed_url.path) # 清理文件名 if original_filename and \u0026#39;.\u0026#39; in original_filename: name, ext = os.path.splitext(original_filename) # 移除可能存在的查询参数 ext = ext.split(\u0026#39;?\u0026#39;)[0] # 限制扩展名为常见图片格式 if ext.lower() not in [\u0026#39;.jpg\u0026#39;, \u0026#39;.jpeg\u0026#39;, \u0026#39;.png\u0026#39;, \u0026#39;.gif\u0026#39;, \u0026#39;.webp\u0026#39;, \u0026#39;.bmp\u0026#39;]: # 从Content-Type获取扩展名 content_type = response.headers.get(\u0026#39;content-type\u0026#39;, \u0026#39;\u0026#39;) if \u0026#39;jpeg\u0026#39; in content_type or \u0026#39;jpg\u0026#39; in content_type: ext = \u0026#39;.jpg\u0026#39; elif \u0026#39;png\u0026#39; in content_type: ext = \u0026#39;.png\u0026#39; elif \u0026#39;gif\u0026#39; in content_type: ext = \u0026#39;.gif\u0026#39; elif \u0026#39;webp\u0026#39; in content_type: ext = \u0026#39;.webp\u0026#39; else: ext = \u0026#39;.jpg\u0026#39; # 默认使用jpg # 创建友好文件名（限制长度） clean_name = re.sub(r\u0026#39;[^\\w\\-_]\u0026#39;, \u0026#39;_\u0026#39;, name) if len(clean_name) \u0026gt; 50: clean_name = clean_name[:50] filename = f\u0026#34;{clean_name}{ext}\u0026#34; else: # 从Content-Type获取扩展名 content_type = response.headers.get(\u0026#39;content-type\u0026#39;, \u0026#39;\u0026#39;) if \u0026#39;jpeg\u0026#39; in content_type or \u0026#39;jpg\u0026#39; in content_type: ext = \u0026#39;.jpg\u0026#39; elif \u0026#39;png\u0026#39; in content_type: ext = \u0026#39;.png\u0026#39; elif \u0026#39;gif\u0026#39; in content_type: ext = \u0026#39;.gif\u0026#39; elif \u0026#39;webp\u0026#39; in content_type: ext = \u0026#39;.webp\u0026#39; else: ext = \u0026#39;.jpg\u0026#39; # 使用URL的MD5哈希作为文件名 url_hash = hashlib.md5(url.encode()).hexdigest()[:8] filename = f\u0026#34;image_{url_hash}{ext}\u0026#34; # 确保文件夹存在 os.makedirs(folder_path, exist_ok=True) # 完整文件路径 filepath = os.path.join(folder_path, filename) # 避免重复下载（除非设置了覆盖） if os.path.exists(filepath) and not self.overwrite_existing.get(): self.log_message(f\u0026#34;图片已存在，跳过: {filename}\u0026#34;) self.skipped_images += 1 return filepath # 保存图片 with open(filepath, \u0026#39;wb\u0026#39;) as f: for chunk in response.iter_content(1024): f.write(chunk) self.log_message(f\u0026#34;下载成功: {filename}\u0026#34;) self.downloaded_images += 1 return filepath else: self.log_message(f\u0026#34;下载失败: HTTP状态码 {response.status_code} - {url}\u0026#34;) return None except Exception as e: self.log_message(f\u0026#34;下载图片失败: {url}, 错误: {e}\u0026#34;) return None def process_markdown_file(self, md_file_path, image_dir): \u0026#34;\u0026#34;\u0026#34;处理单个Markdown文件中的网络图片\u0026#34;\u0026#34;\u0026#34; self.log_message(f\u0026#34;处理文件: {os.path.basename(md_file_path)}\u0026#34;) with open(md_file_path, \u0026#39;r\u0026#39;, encoding=\u0026#39;utf-8\u0026#39;) as f: content = f.read() # 正则表达式匹配Markdown图片 pattern = r\u0026#39;!\\[(.*?)\\]\\((.*?)\\)\u0026#39; matches = re.findall(pattern, content) changed = False for alt_text, url in matches: if url.startswith((\u0026#39;http://\u0026#39;, \u0026#39;https://\u0026#39;)): local_path = self.download_image(url, image_dir) if local_path: # 确定使用的路径类型 if self.use_relative_path.get(): # 使用相对路径 relative_path = os.path.relpath(local_path, os.path.dirname(md_file_path)) replacement_path = relative_path.replace(\u0026#39;\\\\\u0026#39;, \u0026#39;/\u0026#39;) # 确保使用正斜杠 else: # 使用绝对路径 replacement_path = local_path.replace(\u0026#39;\\\\\u0026#39;, \u0026#39;/\u0026#39;) content = content.replace(f\u0026#34;]({url})\u0026#34;, f\u0026#34;]({replacement_path})\u0026#34;) changed = True # 如果有更改，写回文件 if changed: # 创建备份文件 backup_path = md_file_path + \u0026#39;.bak\u0026#39; with open(backup_path, \u0026#39;w\u0026#39;, encoding=\u0026#39;utf-8\u0026#39;) as f: f.write(content) # 重命名备份文件为原文件 os.replace(backup_path, md_file_path) self.log_message(f\u0026#34;已更新: {os.path.basename(md_file_path)}\u0026#34;) else: self.log_message(f\u0026#34;无需更新: {os.path.basename(md_file_path)}\u0026#34;) self.processed_files += 1 def batch_process_markdown_files(self): \u0026#34;\u0026#34;\u0026#34;批量处理目录中的所有Markdown文件\u0026#34;\u0026#34;\u0026#34; root_dir = self.dir_path.get() image_dir = self.image_dir_path.get() if not root_dir or not os.path.exists(root_dir): messagebox.showerror(\u0026#34;错误\u0026#34;, \u0026#34;请选择有效的Markdown文件目录\u0026#34;) return # 创建图片目录 os.makedirs(image_dir, exist_ok=True) # 重置统计信息 self.processed_files = 0 self.downloaded_images = 0 self.skipped_images = 0 # 查找所有Markdown文件 md_files = [] for root, dirs, files in os.walk(root_dir): # 跳过图片目录 if os.path.abspath(root) == os.path.abspath(image_dir): continue for file in files: if file.endswith(\u0026#39;.md\u0026#39;): md_files.append(os.path.join(root, file)) total_files = len(md_files) self.log_message(f\u0026#34;找到 {total_files} 个Markdown文件\u0026#34;) # 处理每个文件 for i, md_file in enumerate(md_files): self.process_markdown_file(md_file, image_dir) # 显示处理结果 self.log_message(\u0026#34;\\n处理完成!\u0026#34;) self.log_message(f\u0026#34;已处理文件: {self.processed_files}/{total_files}\u0026#34;) self.log_message(f\u0026#34;下载图片: {self.downloaded_images}\u0026#34;) self.log_message(f\u0026#34;跳过图片: {self.skipped_images}\u0026#34;) messagebox.showinfo(\u0026#34;完成\u0026#34;, f\u0026#34;处理完成!\\n已处理文件: {self.processed_files}\\n下载图片: {self.downloaded_images}\u0026#34;) def start_processing(self): \u0026#34;\u0026#34;\u0026#34;开始处理文件（在线程中运行）\u0026#34;\u0026#34;\u0026#34; if not self.dir_path.get(): messagebox.showerror(\u0026#34;错误\u0026#34;, \u0026#34;请选择Markdown文件目录\u0026#34;) return # 禁用开始按钮，防止重复点击 self.root.children[\u0026#39;!frame\u0026#39;].children[\u0026#39;!frame2\u0026#39;].winfo_children()[0].config(state=tk.DISABLED) # 启动进度条 self.progress.start() # 在线程中运行处理过程 thread = threading.Thread(target=self.batch_process_markdown_files) thread.daemon = True thread.start() # 检查线程是否完成 self.check_thread(thread) def check_thread(self, thread): \u0026#34;\u0026#34;\u0026#34;检查处理线程是否完成\u0026#34;\u0026#34;\u0026#34; if thread.is_alive(): # 线程仍在运行，稍后再次检查 self.root.after(100, lambda: self.check_thread(thread)) else: # 线程已完成，停止进度条并启用按钮 self.progress.stop() self.root.children[\u0026#39;!frame\u0026#39;].children[\u0026#39;!frame2\u0026#39;].winfo_children()[0].config(state=tk.NORMAL) def run(self): \u0026#34;\u0026#34;\u0026#34;运行应用程序\u0026#34;\u0026#34;\u0026#34; self.root.mainloop() # 运行应用程序 if __name__ == \u0026#34;__main__\u0026#34;: app = MarkdownImageDownloader() app.run() 把本地图片上传cos并自动修改本地图片地址\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34; Markdown本地图片上传到腾讯云COS工具 作者：基于腾讯云SDK和网络资源编写 日期：2025-09-09 \u0026#34;\u0026#34;\u0026#34; import os import re import glob import hashlib from pathlib import Path from qcloud_cos import CosConfig, CosS3Client class MarkdownImageUploader: def __init__(self, secret_id, secret_key, region, bucket, folder=\u0026#34;markdown-java\u0026#34;): \u0026#34;\u0026#34;\u0026#34; 初始化腾讯云COS客户端 :param secret_id: 腾讯云SecretId :param secret_key: 腾讯云SecretKey :param region: 存储桶地域 :param bucket: 存储桶名称，格式为BucketName-APPID :param folder: COS中存储图片的文件夹路径 \u0026#34;\u0026#34;\u0026#34; self.secret_id = secret_id self.secret_key = secret_key self.region = region self.bucket = bucket self.folder = folder # 初始化COS客户端 config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key) self.client = CosS3Client(config) # 创建存储桶（如果不存在） self._create_bucket_if_not_exists() # 用于记录已上传的图片，避免重复上传 self.uploaded_images = {} def _create_bucket_if_not_exists(self): \u0026#34;\u0026#34;\u0026#34;检查存储桶是否存在，如果不存在则创建\u0026#34;\u0026#34;\u0026#34; try: self.client.head_bucket(Bucket=self.bucket) print(f\u0026#34;存储桶 {self.bucket} 已存在\u0026#34;) except Exception: print(f\u0026#34;创建存储桶 {self.bucket}\u0026#34;) try: self.client.create_bucket( Bucket=self.bucket, ACL=\u0026#34;public-read\u0026#34; ) print(\u0026#34;存储桶创建成功\u0026#34;) except Exception as e: print(f\u0026#34;创建存储桶失败: {str(e)}\u0026#34;) raise def _get_file_md5(self, file_path): \u0026#34;\u0026#34;\u0026#34;计算文件的MD5值，用于判断文件是否重复\u0026#34;\u0026#34;\u0026#34; hash_md5 = hashlib.md5() with open(file_path, \u0026#34;rb\u0026#34;) as f: for chunk in iter(lambda: f.read(4096), b\u0026#34;\u0026#34;): hash_md5.update(chunk) return hash_md5.hexdigest() def _upload_image_to_cos(self, image_path): \u0026#34;\u0026#34;\u0026#34; 上传本地图片到腾讯云COS :param image_path: 本地图片路径 :return: 图片的公共访问URL \u0026#34;\u0026#34;\u0026#34; # 检查文件是否已经上传过 file_md5 = self._get_file_md5(image_path) if file_md5 in self.uploaded_images: print(f\u0026#34;图片已上传过: {image_path}\u0026#34;) return self.uploaded_images[file_md5] # 生成COS中的文件名 file_name = Path(image_path).name cos_key = f\u0026#34;{self.folder}/{file_name}\u0026#34; # 如果COS中已存在同名文件，添加前缀避免冲突 index = 1 original_cos_key = cos_key while any(obj[\u0026#39;Key\u0026#39;] == cos_key for obj in self.client.list_objects(Bucket=self.bucket).get(\u0026#39;Contents\u0026#39;, [])): name, ext = os.path.splitext(original_cos_key) cos_key = f\u0026#34;{name}_{index}{ext}\u0026#34; index += 1 try: # 上传图片到COS response = self.client.upload_file( Bucket=self.bucket, LocalFilePath=image_path, Key=cos_key, PartSize=1, MAXThread=10, EnableMD5=False ) # 构建图片的公共URL image_url = f\u0026#34;https://{self.bucket}.cos.{self.region}.myqcloud.com/{cos_key}\u0026#34; # 缓存上传结果 self.uploaded_images[file_md5] = image_url print(f\u0026#34;上传成功: {image_path} -\u0026gt; {image_url}\u0026#34;) return image_url except Exception as e: print(f\u0026#34;上传图片失败 {image_path}: {str(e)}\u0026#34;) return None def find_markdown_files(self, root_dir): \u0026#34;\u0026#34;\u0026#34; 递归查找项目中的所有Markdown文件 :param root_dir: 项目根目录 :return: 找到的Markdown文件列表 \u0026#34;\u0026#34;\u0026#34; markdown_files = [] for root, _, files in os.walk(root_dir): for file in files: if file.endswith(\u0026#39;.md\u0026#39;): markdown_files.append(os.path.join(root, file)) return markdown_files def extract_local_images(self, markdown_content, md_file_path): \u0026#34;\u0026#34;\u0026#34; 从Markdown内容中提取本地图片引用 :param markdown_content: Markdown文件内容 :param md_file_path: Markdown文件路径，用于解析相对路径 :return: 本地图片路径列表 \u0026#34;\u0026#34;\u0026#34; # Markdown图片格式: ![alt text](path/to/image.png) pattern = r\u0026#39;!\\[.*?\\]\\((.*?)\\)\u0026#39; local_images = [] md_dir = os.path.dirname(md_file_path) for match in re.findall(pattern, markdown_content): # 排除网络图片 if match.startswith((\u0026#39;http://\u0026#39;, \u0026#39;https://\u0026#39;, \u0026#39;data:image/\u0026#39;)): continue # 处理相对路径 image_path = os.path.normpath(os.path.join(md_dir, match)) # 检查文件是否存在 if os.path.isfile(image_path): local_images.append((match, image_path)) # (原引用路径, 实际路径) else: print(f\u0026#34;图片文件不存在: {image_path}\u0026#34;) return local_images def process_markdown_file(self, md_file_path): \u0026#34;\u0026#34;\u0026#34; 处理单个Markdown文件：上传其中的本地图片并更新链接 :param md_file_path: Markdown文件路径 :return: 更新后的文件内容 \u0026#34;\u0026#34;\u0026#34; with open(md_file_path, \u0026#39;r\u0026#39;, encoding=\u0026#39;utf-8\u0026#39;) as f: content = f.read() # 提取本地图片 local_images = self.extract_local_images(content, md_file_path) if not local_images: print(f\u0026#34;文件中未找到本地图片: {md_file_path}\u0026#34;) return content print(f\u0026#34;在文件 {md_file_path} 中找到 {len(local_images)} 张本地图片\u0026#34;) # 上传图片并更新内容 updated_content = content for orig_ref, image_path in local_images: image_url = self._upload_image_to_cos(image_path) if image_url: # 替换原引用为URL updated_content = updated_content.replace(orig_ref, image_url) return updated_content def process_project(self, project_root=\u0026#39;.\u0026#39;): \u0026#34;\u0026#34;\u0026#34; 处理整个项目中的所有Markdown文件 :param project_root: 项目根目录路径 \u0026#34;\u0026#34;\u0026#34; print(f\u0026#34;开始处理项目: {project_root}\u0026#34;) # 查找所有Markdown文件 md_files = self.find_markdown_files(project_root) print(f\u0026#34;找到 {len(md_files)} 个Markdown文件\u0026#34;) # 处理每个文件 for md_file in md_files: print(f\u0026#34;\\n处理文件: {md_file}\u0026#34;) try: updated_content = self.process_markdown_file(md_file) # 备份原文件并写入更新内容 backup_path = md_file + \u0026#39;.bak\u0026#39; if not os.path.exists(backup_path): os.rename(md_file, backup_path) with open(md_file, \u0026#39;w\u0026#39;, encoding=\u0026#39;utf-8\u0026#39;) as f: f.write(updated_content) print(f\u0026#34;文件更新完成: {md_file}\u0026#34;) except Exception as e: print(f\u0026#34;处理文件失败 {md_file}: {str(e)}\u0026#34;) print(f\u0026#34;\\n处理完成! 总共上传了 {len(self.uploaded_images)} 张图片\u0026#34;) def main(): \u0026#34;\u0026#34;\u0026#34;主函数\u0026#34;\u0026#34;\u0026#34; # 腾讯云配置（需要用户修改） secret_id = \u0026#34;AKIDDKEvToxvoJvLYYLvIexlNr\u0026#34; # 替换为您的SecretId secret_key = \u0026#34;LVKzRfqoOGvPgleib\u0026#34; # 替换为您的SecretKey region = \u0026#34;ap-chengdu\u0026#34; # 替换为您的存储桶地域 bucket = \u0026#34;tk-13131175\u0026#34; # 替换为您的存储桶名称，格式为BucketName-APPID # 创建上传器实例 uploader = MarkdownImageUploader( secret_id=secret_id, secret_key=secret_key, region=region, bucket=bucket, folder=\u0026#34;markdown-java\u0026#34; # COS中存储图片的文件夹 ) # 处理当前项目 uploader.process_project() if __name__ == \u0026#34;__main__\u0026#34;: main() 把本地图片上传并在文章添加 以下Front Matter\r构建Front Matter\n1 2 3 4 5 6 7 8 9 10 11 12 --- title: \u0026#39;{file_name}\u0026#39; date: {current_time} draft: false categories: [\u0026#34;{folder_name}\u0026#34;] tags: [\u0026#34;{file_name}\u0026#34;] keywords: [\u0026#34;\u0026#34;] image: https://tk- featuredImage: https://t toc: true math: false --- 脚本\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34; Markdown本地图片上传到腾讯云COS工具 作者：基于腾讯云SDK和网络资源编写 日期：2025-09-09 \u0026#34;\u0026#34;\u0026#34; import os import re import glob import hashlib from pathlib import Path from datetime import datetime from qcloud_cos import CosConfig, CosS3Client class MarkdownImageUploader: def __init__(self, secret_id, secret_key, region, bucket, folder=\u0026#34;markdown-tktest\u0026#34;): \u0026#34;\u0026#34;\u0026#34; 初始化腾讯云COS客户端 :param secret_id: 腾讯云SecretId :param secret_key: 腾讯云SecretKey :param region: 存储桶地域 :param bucket: 存储桶名称，格式为BucketName-APPID :param folder: COS中存储图片的文件夹路径 \u0026#34;\u0026#34;\u0026#34; self.secret_id = secret_id self.secret_key = secret_key self.region = region self.bucket = bucket self.folder = folder # 初始化COS客户端 config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key) self.client = CosS3Client(config) # 创建存储桶（如果不存在） self._create_bucket_if_not_exists() # 用于记录已上传的图片，避免重复上传 self.uploaded_images = {} def _create_bucket_if_not_exists(self): \u0026#34;\u0026#34;\u0026#34;检查存储桶是否存在，如果不存在则创建\u0026#34;\u0026#34;\u0026#34; try: self.client.head_bucket(Bucket=self.bucket) print(f\u0026#34;存储桶 {self.bucket} 已存在\u0026#34;) except Exception: print(f\u0026#34;创建存储桶 {self.bucket}\u0026#34;) try: self.client.create_bucket( Bucket=self.bucket, ACL=\u0026#34;public-read\u0026#34; ) print(\u0026#34;存储桶创建成功\u0026#34;) except Exception as e: print(f\u0026#34;创建存储桶失败: {str(e)}\u0026#34;) raise def _get_file_md5(self, file_path): \u0026#34;\u0026#34;\u0026#34;计算文件的MD5值，用于判断文件是否重复\u0026#34;\u0026#34;\u0026#34; hash_md5 = hashlib.md5() with open(file_path, \u0026#34;rb\u0026#34;) as f: for chunk in iter(lambda: f.read(4096), b\u0026#34;\u0026#34;): hash_md5.update(chunk) return hash_md5.hexdigest() def _upload_image_to_cos(self, image_path): \u0026#34;\u0026#34;\u0026#34; 上传本地图片到腾讯云COS :param image_path: 本地图片路径 :return: 图片的公共访问URL \u0026#34;\u0026#34;\u0026#34; # 检查文件是否已经上传过 file_md5 = self._get_file_md5(image_path) if file_md5 in self.uploaded_images: print(f\u0026#34;图片已上传过: {image_path}\u0026#34;) return self.uploaded_images[file_md5] # 生成COS中的文件名 file_name = Path(image_path).name cos_key = f\u0026#34;{self.folder}/{file_name}\u0026#34; # 如果COS中已存在同名文件，添加前缀避免冲突 index = 1 original_cos_key = cos_key while any(obj[\u0026#39;Key\u0026#39;] == cos_key for obj in self.client.list_objects(Bucket=self.bucket).get(\u0026#39;Contents\u0026#39;, [])): name, ext = os.path.splitext(original_cos_key) cos_key = f\u0026#34;{name}_{index}{ext}\u0026#34; index += 1 try: # 上传图片到COS response = self.client.upload_file( Bucket=self.bucket, LocalFilePath=image_path, Key=cos_key, PartSize=1, MAXThread=10, EnableMD5=False ) # 构建图片的公共URL image_url = f\u0026#34;https://{self.bucket}.cos.{self.region}.myqcloud.com/{cos_key}\u0026#34; # 缓存上传结果 self.uploaded_images[file_md5] = image_url print(f\u0026#34;上传成功: {image_path} -\u0026gt; {image_url}\u0026#34;) return image_url except Exception as e: print(f\u0026#34;上传图片失败 {image_path}: {str(e)}\u0026#34;) return None def find_markdown_files(self, root_dir): \u0026#34;\u0026#34;\u0026#34; 递归查找项目中的所有Markdown文件 :param root_dir: 项目根目录 :return: 找到的Markdown文件列表 \u0026#34;\u0026#34;\u0026#34; markdown_files = [] for root, _, files in os.walk(root_dir): for file in files: if file.endswith(\u0026#39;.md\u0026#39;): markdown_files.append(os.path.join(root, file)) return markdown_files def extract_local_images(self, markdown_content, md_file_path): \u0026#34;\u0026#34;\u0026#34; 从Markdown内容中提取本地图片引用 :param markdown_content: Markdown文件内容 :param md_file_path: Markdown文件路径，用于解析相对路径 :return: 本地图片路径列表 \u0026#34;\u0026#34;\u0026#34; # Markdown图片格式: ![alt text](path/to/image.png) pattern = r\u0026#39;!\\[.*?\\]\\((.*?)\\)\u0026#39; local_images = [] md_dir = os.path.dirname(md_file_path) for match in re.findall(pattern, markdown_content): # 排除网络图片 if match.startswith((\u0026#39;http://\u0026#39;, \u0026#39;https://\u0026#39;, \u0026#39;data:image/\u0026#39;)): continue # 处理相对路径 image_path = os.path.normpath(os.path.join(md_dir, match)) # 检查文件是否存在 if os.path.isfile(image_path): local_images.append((match, image_path)) # (原引用路径, 实际路径) else: print(f\u0026#34;图片文件不存在: {image_path}\u0026#34;) return local_images def _generate_front_matter(self, md_file_path): \u0026#34;\u0026#34;\u0026#34; 生成Front Matter内容 :param md_file_path: Markdown文件路径 :return: Front Matter字符串 \u0026#34;\u0026#34;\u0026#34; # 获取文件名（不含扩展名） file_name = Path(md_file_path).stem # 获取父文件夹名 folder_name = Path(md_file_path).parent.name # 生成当前日期时间 current_time = datetime.now().strftime(\u0026#34;%Y-%m-%dT%H:%M:%S+08:00\u0026#34;) # 构建Front Matter front_matter = f\u0026#34;\u0026#34;\u0026#34;--- title: \u0026#39;{file_name}\u0026#39; date: {current_time} draft: false categories: [\u0026#34;{folder_name}\u0026#34;] tags: [\u0026#34;{file_name}\u0026#34;] keywords: [\u0026#34;\u0026#34;] image: https:// featuredImage: h toc: true math: false --- \u0026#34;\u0026#34;\u0026#34; return front_matter def process_markdown_file(self, md_file_path): \u0026#34;\u0026#34;\u0026#34; 处理单个Markdown文件：上传其中的本地图片并更新链接，并添加Front Matter :param md_file_path: Markdown文件路径 :return: 更新后的文件内容 \u0026#34;\u0026#34;\u0026#34; with open(md_file_path, \u0026#39;r\u0026#39;, encoding=\u0026#39;utf-8\u0026#39;) as f: content = f.read() # 检查是否已经有Front Matter（以---开头的内容） has_front_matter = content.startswith(\u0026#39;---\u0026#39;) # 提取本地图片 local_images = self.extract_local_images(content, md_file_path) if not local_images: print(f\u0026#34;文件中未找到本地图片: {md_file_path}\u0026#34;) # 如果没有图片需要上传，但仍然需要添加Front Matter if not has_front_matter: front_matter = self._generate_front_matter(md_file_path) content = front_matter + content return content print(f\u0026#34;在文件 {md_file_path} 中找到 {len(local_images)} 张本地图片\u0026#34;) # 上传图片并更新内容 updated_content = content for orig_ref, image_path in local_images: image_url = self._upload_image_to_cos(image_path) if image_url: # 替换原引用为URL updated_content = updated_content.replace(orig_ref, image_url) # 添加Front Matter（如果还没有） if not has_front_matter: front_matter = self._generate_front_matter(md_file_path) updated_content = front_matter + updated_content return updated_content def process_project(self, project_root=\u0026#39;.\u0026#39;): \u0026#34;\u0026#34;\u0026#34; 处理整个项目中的所有Markdown文件 :param project_root: 项目根目录路径 \u0026#34;\u0026#34;\u0026#34; print(f\u0026#34;开始处理项目: {project_root}\u0026#34;) # 查找所有Markdown文件 md_files = self.find_markdown_files(project_root) print(f\u0026#34;找到 {len(md_files)} 个Markdown文件\u0026#34;) # 处理每个文件 for md_file in md_files: print(f\u0026#34;\\n处理文件: {md_file}\u0026#34;) try: updated_content = self.process_markdown_file(md_file) # 直接写入更新内容（不创建备份） with open(md_file, \u0026#39;w\u0026#39;, encoding=\u0026#39;utf-8\u0026#39;) as f: f.write(updated_content) print(f\u0026#34;文件更新完成: {md_file}\u0026#34;) except Exception as e: print(f\u0026#34;处理文件失败 {md_file}: {str(e)}\u0026#34;) print(f\u0026#34;\\n处理完成! 总共上传了 {len(self.uploaded_images)} 张图片\u0026#34;) def main(): \u0026#34;\u0026#34;\u0026#34;主函数\u0026#34;\u0026#34;\u0026#34; # 腾讯云配置（需要用户修改） secret_id = \u0026#34;AKIDDKEvToGtJWWYYLIexlNr\u0026#34; # 替换为您的SecretId secret_key = \u0026#34;LVKzRfqonfLIPNOGgeib\u0026#34; # 替换为您的SecretKey region = \u0026#34;ap-chengdu\u0026#34; # 替换为您的存储桶地域 bucket = \u0026#34;tk-1312331175\u0026#34; # 替换为您的存储桶名称，格式为BucketName-APPID # 创建上传器实例 uploader = MarkdownImageUploader( secret_id=secret_id, secret_key=secret_key, region=region, bucket=bucket, folder=\u0026#34;markdown-tktest\u0026#34; # COS中存储图片的文件夹 ) # 处理当前项目 uploader.process_project() if __name__ == \u0026#34;__main__\u0026#34;: main() 下载网络图片到指定目录，并转换为webp格式 返回新的文件名\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 import os import re import requests import hashlib import time from urllib.parse import urlparse from pathlib import Path from PIL import Image import io def download_image(url, output_dir): \u0026#34;\u0026#34;\u0026#34; 下载网络图片到指定目录，并转换为webp格式 返回新的文件名 \u0026#34;\u0026#34;\u0026#34; try: # 发送HTTP请求获取图片 response = requests.get(url, timeout=10) response.raise_for_status() # 生成唯一文件名（使用时间戳和哈希值） timestamp = time.strftime(\u0026#34;%Y%m%d%H%M%S\u0026#34;) hash_str = hashlib.md5(response.content).hexdigest()[:8] # 从URL获取文件扩展名，如果没有则使用png parsed_url = urlparse(url) orig_filename = os.path.basename(parsed_url.path) name, ext = os.path.splitext(orig_filename) if not ext: ext = \u0026#34;.png\u0026#34; # 转换为webp格式 img = Image.open(io.BytesIO(response.content)) # 生成新文件名 new_filename = f\u0026#34;image-{timestamp}-{hash_str}.webp\u0026#34; output_path = os.path.join(output_dir, new_filename) # 保存为webp格式 img.save(output_path, \u0026#34;WEBP\u0026#34;) return new_filename except Exception as e: print(f\u0026#34;下载图片失败: {url}, 错误: {e}\u0026#34;) return None def process_markdown_files(md_dir, image_dir): \u0026#34;\u0026#34;\u0026#34; 处理指定目录下的所有Markdown文件 \u0026#34;\u0026#34;\u0026#34; # 确保图片目录存在 os.makedirs(image_dir, exist_ok=True) # 遍历所有md文件 for root, _, files in os.walk(md_dir): for file in files: if file.endswith(\u0026#39;.md\u0026#39;): md_path = os.path.join(root, file) print(f\u0026#34;处理文件: {md_path}\u0026#34;) with open(md_path, \u0026#39;r\u0026#39;, encoding=\u0026#39;utf-8\u0026#39;) as f: content = f.read() # 使用正则表达式查找所有图片标记 pattern = r\u0026#39;!\\[(.*?)\\]\\((.*?)\\)\u0026#39; matches = re.findall(pattern, content) if not matches: continue new_content = content for alt_text, url in matches: # 只处理网络图片 if url.startswith(\u0026#39;http\u0026#39;): print(f\u0026#34;发现网络图片: {url}\u0026#34;) # 下载图片 new_filename = download_image(url, image_dir) if new_filename: # 构建新的相对路径 relative_path = f\u0026#34;../images/{new_filename}\u0026#34; # 替换原链接 new_content = new_content.replace( f\u0026#34;![{alt_text}]({url})\u0026#34;, f\u0026#34;![{alt_text}]({relative_path})\u0026#34; ) print(f\u0026#34;已替换为: {relative_path}\u0026#34;) # 写回更新后的内容 with open(md_path, \u0026#39;w\u0026#39;, encoding=\u0026#39;utf-8\u0026#39;) as f: f.write(new_content) if __name__ == \u0026#34;__main__\u0026#34;: # 设置目录路径 current_dir = os.getcwd() md_dir = os.path.join(current_dir, \u0026#34;tkpost\u0026#34;) image_dir = os.path.join(current_dir, \u0026#34;images\u0026#34;) # 检查tkpost目录是否存在 if not os.path.exists(md_dir): print(f\u0026#34;错误: 目录 {md_dir} 不存在\u0026#34;) exit(1) # 处理Markdown文件 process_markdown_files(md_dir, image_dir) print(\u0026#34;处理完成!\u0026#34;) ","date":"2025-09-09T13:59:53+08:00","image":"https://sports0808008.github.io/img/a9.jpg","permalink":"https://sports0808008.github.io/post/%E5%8D%9A%E5%AE%A2%E8%BF%81%E7%A7%BB%E8%84%9A%E6%9C%AC/","title":"博客迁移脚本"},{"content":"部署在github\r生成public\n1 hugo -D 进入public\n1 $ cd public/ 在github创建一个新的存储库github名字.github.io\n1 2 3 4 5 6 git init git add . git commit -m \u0026#34;首次提交\u0026#34; git branch -M main git remote add origin git@github.com:github名字/github名字.github.io.git git push -u origin main 自动部署脚本\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 #!/bin/bash echo -e \u0026#34;\\033[1;36m========================================\\033[0m\u0026#34; echo -e \u0026#34;\\033[1;36m Hugo 站点部署工具 - GitHub Pages\\033[0m\u0026#34; echo -e \u0026#34;\\033[1;36m========================================\\033[0m\u0026#34; echo \u0026#34;\u0026#34; # 检查是否在 Hugo 项目根目录 if [ ! -d \u0026#34;content\u0026#34; ] \u0026amp;\u0026amp; [ ! -d \u0026#34;themes\u0026#34; ]; then echo -e \u0026#34;\\033[1;31m❌ 错误：请在 Hugo 项目根目录运行此脚本\\033[0m\u0026#34; echo \u0026#34;当前目录: $(pwd)\u0026#34; echo \u0026#34;期望找到: content/ 或 themes/ 文件夹\u0026#34; exit 1 fi # 检查 hugo 命令是否可用 if ! command -v hugo \u0026amp;\u0026gt; /dev/null; then echo -e \u0026#34;\\033[1;31m❌ 错误：找不到 hugo 命令，请确保已安装 Hugo\\033[0m\u0026#34; exit 1 fi # 1. 构建静态文件 echo -e \u0026#34;\\033[1;33m[1/4] 🛠️ 正在生成静态文件...\\033[0m\u0026#34; hugo --minify if [ $? -ne 0 ]; then echo -e \u0026#34;\\033[1;31m❌ Hugo 构建失败！请检查错误信息。\\033[0m\u0026#34; read -p \u0026#34;按回车键继续...\u0026#34; exit 1 fi # 2. 进入公共文件夹 echo -e \u0026#34;\\033[1;33m[2/4] 📁 进入公共文件夹...\\033[0m\u0026#34; cd public # 3. 初始化一个临时 Git 仓库用于部署 echo -e \u0026#34;\\033[1;33m[3/4] 📦 准备部署到 GitHub Pages...\\033[0m\u0026#34; # 创建或清空 .git 目录（避免子模块问题） if [ -d \u0026#34;.git\u0026#34; ]; then rm -rf .git fi git init # 设置用户信息 git config user.email \u0026#34;sports0808008@users.noreply.github.com\u0026#34; git config user.name \u0026#34;sports0808008\u0026#34; # 确保使用 main 分支 git checkout -b main # 添加所有文件 git add --all . # 提交更改 msg=\u0026#34;🚀 自动部署：$(date +\u0026#39;%Y-%m-%d %H:%M:%S\u0026#39;)\u0026#34; if [ $# -gt 0 ]; then msg=\u0026#34;$*\u0026#34; fi git commit -m \u0026#34;$msg\u0026#34; # 4. 推送到 GitHub Pages echo -e \u0026#34;\\033[1;33m[4/4] ⬆️ 推送到 GitHub Pages...\\033[0m\u0026#34; # 直接推送到 main 分支（现在远程已经有这个分支了） if git push --force git@github.com:sports0808008/sports0808008.github.io.git main:main; then echo \u0026#34;\u0026#34; echo -e \u0026#34;\\033[1;32m✅ 部署成功！\\033[0m\u0026#34; echo -e \u0026#34;\\033[1;34m🌐 访问地址: https://sports0808008.github.io\\033[0m\u0026#34; echo -e \u0026#34;\\033[1;32m⏰ 更新时间: $(date +\u0026#39;%Y-%m-%d %H:%M:%S\u0026#39;)\\033[0m\u0026#34; echo \u0026#34;\u0026#34; else echo -e \u0026#34;\\033[1;31m❌ 推送到 main 分支失败！\\033[0m\u0026#34; echo -e \u0026#34;\\033[1;33m正在尝试推送到 master 分支...\\033[0m\u0026#34; # 尝试推送到 master 分支（备用方案） if git push --force git@github.com:sports0808008/sports0808008.github.io.git main:master; then echo \u0026#34;\u0026#34; echo -e \u0026#34;\\033[1;32m✅ 部署成功！\\033[0m\u0026#34; echo -e \u0026#34;\\033[1;34m🌐 访问地址: https://sports0808008.github.io\\033[0m\u0026#34; echo -e \u0026#34;\\033[1;33m📝 注意：使用了 master 分支而不是 main 分支\\033[0m\u0026#34; echo -e \u0026#34;\\033[1;32m⏰ 更新时间: $(date +\u0026#39;%Y-%m-%d %H:%M:%S\u0026#39;)\\033[0m\u0026#34; echo \u0026#34;\u0026#34; else echo -e \u0026#34;\\033[1;31m❌ 所有推送尝试都失败了！\\033[0m\u0026#34; echo -e \u0026#34;\\033[1;33m请检查以下可能的问题：\\033[0m\u0026#34; echo \u0026#34;1. GitHub 仓库权限是否正确\u0026#34; echo \u0026#34;2. SSH 密钥是否已添加到 GitHub\u0026#34; echo \u0026#34;3. 仓库名称是否正确: sports0808008/sports0808008.github.io\u0026#34; fi fi # 返回原始目录 cd .. read -p \u0026#34;按回车键退出...\u0026#34; 报错解决方案：配置 SSH 密钥\r第1步：生成 SSH 密钥\r1 ssh-keygen -t ed25519 -C \u0026#34;你的邮箱地址\u0026#34; 按回车使用默认保存路径 (~/.ssh/id_ed25519) 可以设置密码（推荐）或直接回车跳过 第2步：启动 SSH 代理并添加密钥\r1 2 3 4 5 # 启动 ssh-agent eval \u0026#34;$(ssh-agent -s)\u0026#34; # 添加 SSH 私钥到代理 ssh-add ~/.ssh/id_ed25519 第3步：复制公钥并添加到 GitHub\r1 2 # 显示并复制公钥内容 cat ~/.ssh/id_ed25519.pub 然后进行以下操作：\n复制终端显示的全部内容（以 ssh-ed25519 AAAAC3... 开头） 登录 GitHub → 点击头像 → Settings → SSH and GPG keys 点击 \u0026ldquo;New SSH key\u0026rdquo; Title 填写 \u0026ldquo;My Windows PC\u0026rdquo; 或其他标识 Key 类型选择 \u0026ldquo;Authentication Key\u0026rdquo; 在 Key 框中粘贴刚才复制的内容 点击 \u0026ldquo;Add SSH key\u0026rdquo; 第4步：测试连接\rssh -T git@github.com\n如果看到 Hi sports0808008! You've successfully authenticated... 就说明成功了！\n部署在云服务器\r自动上传脚本：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 #!/bin/bash echo -e \u0026#34;\\033[1;36m========================================\\033[0m\u0026#34; echo -e \u0026#34;\\033[1;36m Hugo 站点部署工具 - xtkyu.top\\033[0m\u0026#34; echo -e \u0026#34;\\033[1;36m========================================\\033[0m\u0026#34; echo \u0026#34;\u0026#34; # 0. 更宽松的目录检查：检查是否存在 content 文件夹和 hugo 可执行文件 if [ ! -d \u0026#34;content\u0026#34; ] \u0026amp;\u0026amp; [ ! -d \u0026#34;themes\u0026#34; ]; then echo -e \u0026#34;\\033[1;31m❌ 错误：请在 Hugo 项目根目录运行此脚本\\033[0m\u0026#34; echo \u0026#34;当前目录: $(pwd)\u0026#34; echo \u0026#34;期望找到: content/ 或 themes/ 文件夹\u0026#34; exit 1 fi # 检查 hugo 命令是否可用 if ! command -v hugo \u0026amp;\u0026gt; /dev/null; then echo -e \u0026#34;\\033[1;31m❌ 错误：找不到 hugo 命令，请确保已安装 Hugo\\033[0m\u0026#34; exit 1 fi # 1. 构建静态文件 echo -e \u0026#34;\\033[1;33m[1/4] 🛠️ 正在生成静态文件...\\033[0m\u0026#34; hugo if [ $? -ne 0 ]; then echo -e \u0026#34;\\033[1;31m❌ Hugo 构建失败！请检查错误信息。\\033[0m\u0026#34; read -p \u0026#34;按回车键继续...\u0026#34; exit 1 fi # 2. 进入公共文件夹 echo -e \u0026#34;\\033[1;33m[2/4] 📁 进入公共文件夹...\\033[0m\u0026#34; cd public # 3. 智能Git配置：只在第一次运行时初始化 echo -e \u0026#34;\\033[1;33m[3/4] 📦 配置Git仓库...\\033[0m\u0026#34; if [ ! -d \u0026#34;.git\u0026#34; ]; then echo \u0026#34;初始化新的Git仓库并设置远程...\u0026#34; git init git remote add deploy ssh://tk@xtkyu.top:96/home/git/blog.git # 设置默认分支为master git checkout -b master else echo \u0026#34;使用现有的Git仓库\u0026#34; # 确保远程仓库存在 if ! git remote | grep -q \u0026#34;deploy\u0026#34;; then echo \u0026#34;添加远程仓库...\u0026#34; git remote add deploy ssh://tk@xtkyu.top:96/home/git/blog.git fi fi # 4. 添加和提交所有更改 git add --all . msg=\u0026#34;🚀 自动部署：$(date +\u0026#39;%Y-%m-%d %H:%M:%S\u0026#39;)\u0026#34; if [ $# -gt 0 ]; then msg=\u0026#34;$*\u0026#34; fi git commit -m \u0026#34;$msg\u0026#34; # 5. 推送到服务器 echo -e \u0026#34;\\033[1;33m[4/4] ⬆️ 推送到服务器...\\033[0m\u0026#34; git push --force deploy master:master if [ $? -eq 0 ]; then echo \u0026#34;\u0026#34; echo -e \u0026#34;\\033[1;32m✅ 部署成功！\\033[0m\u0026#34; echo -e \u0026#34;\\033[1;34m🌐 访问地址: http://xtkyu.top\\033[0m\u0026#34; echo -e \u0026#34;\\033[1;32m⏰ 更新时间: $(date +\u0026#39;%Y-%m-%d %H:%M:%S\u0026#39;)\\033[0m\u0026#34; echo \u0026#34;\u0026#34; else echo \u0026#34;\u0026#34; echo -e \u0026#34;\\033[1;31m❌ 推送失败！\\033[0m\u0026#34; echo -e \u0026#34;\\033[1;33m正在尝试修复...\\033[0m\u0026#34; git remote remove deploy 2\u0026gt;/dev/null git remote add deploy ssh://tk@xtkyu.top:96/home/git/blog.git git push --force deploy master:master fi read -p \u0026#34;按回车键退出...\u0026#34; 准备工作\r已经搭建完毕的本地博客 一台windows/mac电脑 一台云服务器（本教程以腾讯云为例） 本地电脑安装一个ssh连接工具\n服务器上安装配置git\r安装依赖工具包\r1 yum install -y curl-devel expat-devel gettext-devel openssl-devel zlib-devel 安装编译工具\r1 yum install -y gcc perl-ExtUtils-MakeMaker package 删除旧版本git\r1 yum remove git -y 回到根目录\r1 cd 下载git到家目录\r1 wget https://www.kernel.org/pub/software/scm/git/git-2.34.0.tar.gz --no-check-certificate 如果提示没有wget命令，先安装：\n1 yum install wget -y 解压文件\r1 tar -zxf git-2.34.0.tar.gz 安装git\r进入到git目录下\n1 cd git-2.34.0 编译(时间可能有点久，稍微等待一下)\n1 make all prefix=/usr/local/git 安装git到/usr/local/git下\n1 make install prefix=/usr/local/git 配置git环境变量\r将git加入PATH目录中\n1 echo \u0026#39;export PATH=$PATH:/usr/local/git/bin\u0026#39; \u0026gt;\u0026gt; /etc/bashrc 使配置生效\n1 source /etc/bashrc 查看git版本\n1 git --version 创建用户配置SSH\r创建用户并设置密码\r这里的tk改成你自己的用户名，并且为用户设置一个密码。\n例如:tk是用户名\n1 2 3 adduser tk passwd tk # 注意是刚创建的用户名 tk 然后按提示输入两次密码（输入时不会显示，正常现象）。 为用户分配权限\r把用户添加到wheel组\n1 usermod tk -G wheel 本地创建密钥\r在本机电脑终端输入(cmd)：\n1 ssh-keygen -t rsa 第一次回车后输入y后面就是一直回车了，新建一个密钥，注意如果之前你将你的本地hexo博客部署到GitHub的时候生成的那个密钥是不可用的在重新生成这个密钥，不然上传的时候就会出错。\n一般来说在你创建的时候命令行会提示你密钥文件的路径，windows电脑一般是C:\\Users\\ASUS.ssh\n复制id_rsa.pub文件中的内容备用。\n配置公钥\r回到服务器终端，切换用户\ntk就是添加的用户\n1 su - tk 创建.ssh文件夹\n1 mkdir .ssh \u0026amp;\u0026amp; chmod 700 .ssh 或者\n1 2 3 # 1. 创建目录并设置权限 mkdir -p ~/.ssh chmod 700 ~/.ssh 新建authorized_keys\n1 touch .ssh/authorized_keys \u0026amp;\u0026amp; chmod 600 .ssh/authorized_keys 或者\n1 2 3 4 5 6 7 8 # 2. 创建 authorized_keys touch ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys # 3. 验证 ls -la ~/.ssh # 4. 如果需要，修复家目录权限 chmod 755 ~ sudo chown -R tk:tk ~ 查看repo的用户信息\n1 git config -l 本地查看是否在.ssh下有公钥私钥，如果没有需要创建：\n1 ssh-keygen 将刚刚id_rsa.pub公钥中的内容，复制粘贴到文件里，保存退出。（注意这里是输入i编辑，esc退出编辑模式，:wq保存）\ngit仓库配置\r退出用户登录\n1 exit 此时应该是root用户，创建git目录，并修改目录的所有权和用户权限\n1 2 3 mkdir /home/git/ chown -R tk:tk /home/git/ chmod -R 755 /home/git/ 进入目录，建立git仓库，修改权限\n1 2 3 cd /home/git/ git init --bare blog.git chown tk:tk -R blog.git 报错：\n1 git config --global init.defaultBranch master 新建钩子文件 post-receive\n1 vim /home/git/blog.git/hooks/post-receive 进入文本编辑器，粘贴下面两行。\n1 2 #!/bin/bash git --work-tree=/home/blog --git-dir=/home/git/blog.git checkout -f 保存退出。然后修改文件权限\n1 chmod +x /home/git/blog.git/hooks/post-receive nginx配置\r首先创建blog目录用户nginx转发\n1 2 3 mkdir /home/blog/ chown -R tk:tk /home/blog/ chmod -R 755 /home/blog/ 1 第一种方法安装nginx\n两种方法任选其一，如果yum安装成功就不用第二种。\n1 yum install -y nginx 启动nginx\n1 systemctl start nginx.service 查看nginx服务状态，绿色的active说明启动成功\n1 systemctl status nginx.service 第二种方法安装nginx\r针对yum无法安装的情况，这里介绍第二种方法，源码安装。\n回到家目录\n1 cd 下载安装包，鉴于下载地址的时效性，附上\n1 wget http://nginx.org/download/nginx-1.21.4.tar.gz 解压\n1 tar -zxf nginx-1.21.4.tar.gz 进入目录，编译安装\n1 2 3 cd nginx-1.21.4 ./configure make install prefix=/usr/local/nginx 配置nginx环境变量，并使环境变量生效\n1 2 echo \u0026#39;export PATH=$PATH:/usr/local/nginx/sbin\u0026#39; \u0026gt;\u0026gt; /etc/bashrc source /etc/bashrc 查看nginx版本号\n1 nginx -v 配置Systemd服务\n1 vim /lib/systemd/system/nginx.service 在文本编辑器中粘贴以下内容：\n1 2 3 4 5 6 7 8 9 10 11 12 13 [Unit] Description=nginx After=network.target [Service] Type=forking ExecStart=/usr/local/nginx/sbin/nginx ExecReload=/usr/local/nginx/sbin/nginx -s reload ExecStop=/usr/local/nginx/sbin/nginx -s quit PrivateTmp= true [Install] WantedBy=multi-user.target 内容说明：\n1 2 3 4 5 6 7 8 9 10 11 [Unit]:服务的说明 Description:描述服务 After:描述服务类别 [Service]服务运行参数的设置 Type=forking是后台运行的形式 ExecStart为服务的具体运行命令 ExecReload为重启命令 ExecStop为停止命令 PrivateTmp=True表示给服务分配独立的临时空间 注意：[Service]的启动、重启、停止命令全部要求使用绝对路径 [Install]运行级别下服务安装的相关设置，可设置为多用户，即系统运行级别为3 设置开机自启动\n1 systemctl enable nginx.service 重启电脑\n1 reboot 重启后，查看nginx服务状态，绿色的active说明启动成功\n1 systemctl status nginx.service 修改nginx转发配置\r在浏览器中输入服务器公网ip地址，测试是否能打开。\n查看nginx的默认配置文件的安装位置\n1 nginx -t 修改nginx的默认配置，其中cd后边就是刚刚查到的安装位置，每个人可能都不一样，我这里是/usr/local/nginx/conf/nginx.conf\n1 vim /usr/local/nginx/conf/nginx.conf 在文本编辑器中找到如下位置\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 server { listen 80 default_server; # 监听IPv4的80端口，设为默认服务 listen [::]:80 default_server; # 监听IPv6的80端口，设为默认服务 root /home/blog; # 网站根目录（需替换为实际路径,修改为创建的blog目录地址） server_name www.example.com; # 域名（需替换为实际域名或公网IP） # 加载默认服务块的配置文件 include /etc/nginx/default.d/*.conf; location / { # 根路径配置（当前为空，需补充） } error_page 404 /404.html; # 自定义404错误页面 location = /40x.html { # 404页面的精确匹配（应与上行路径一致） } } 优化后：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 server { listen 8888 default_server; # 修改为监听8888端口（IPv4） listen [::]:8888 default_server; # 修改为监听8888端口（IPv6） server_name www.example.com; # 改为实际域名或IP root /home/blog; index index.html index.htm; # 安全响应头 add_header X-Frame-Options \u0026#34;SAMEORIGIN\u0026#34;; add_header X-Content-Type-Options \u0026#34;nosniff\u0026#34;; add_header X-XSS-Protection \u0026#34;1; mode=block\u0026#34;; # 静态文件处理 location / { try_files $uri $uri/ =404; } # 错误页面配置 error_page 404 /404.html; location = /404.html { internal; } error_page 500 502 503 504 /50x.html; location = /50x.html { internal; } # 禁止访问隐藏文件 location ~ /\\. { deny all; access_log off; log_not_found off; } # 禁用favicon和robots.txt的日志噪声 location = /favicon.ico { access_log off; log_not_found off; } location = /robots.txt { access_log off; log_not_found off; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 server { listen 8888 default_server; listen [::]:8888 default_server; server_name www.example.com; root /home/blog; index index.html index.htm; add_header X-Frame-Options \u0026#34;SAMEORIGIN\u0026#34;; add_header X-Content-Type-Options \u0026#34;nosniff\u0026#34;; add_header X-XSS-Protection \u0026#34;1; mode=block\u0026#34;; location / { try_files $uri $uri/ =404; } error_page 404 /404.html; location = /404.html { internal; } error_page 500 502 503 504 /50x.html; location = /50x.html { internal; } location ~ /\\. { deny all; access_log off; log_not_found off; } location = /favicon.ico { access_log off; log_not_found off; } location = /robots.txt { access_log off; log_not_found off; } } 可以使用反向代理隐藏8888端口\n1 2 3 4 5 6 7 8 9 10 11 server { listen 80; server_name example.com; # 改为你的域名或IP location / { proxy_pass http://127.0.0.1:88; # 代理到本机 8888 端口 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } 保存退出。重启nginx服务\n1 systemctl restart nginx.service 在hexo站点配置文件中配置\r打开你本地的hexo博客所在文件，打开站点配置文件_config.yml\n1 2 3 4 deploy: type: git repository: tk@100.0.0.100:/home/git/blog.git #用户名@服务器Ip:git仓库位置 branch: master 注意：如果云服务器ssh端口不是22，站点配置文件_config.yml具体配置如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # 1. 进入项目目录 cd I:\\Blog\\hexo-theme-Fomalhaut-main # 2. 初始化仓库（如果未初始化） git init # 3. 关联远程仓库（指定SSH端口57） git remote add origin ssh://tk@157.82.12.146:57/home/git/blog.git #后面可以不用 # 4. 添加文件并提交 git add . git commit -m \u0026#34;Initial commit\u0026#34; # 5. 首次推送 git push -u origin master# 如果分支是main则替换为main 在本地hexo博客根目录下，打开终端，部署\n1 2 3 hexo clean hexo g hexo d 如果提示ERROR Deployer not found:git错误，先安装：\n1 npm install hexo-deployer-git --save 从远程仓库克隆到本地\r1. 直接克隆（指定端口57）\r1 git clone ssh://tk@157.82.12.146:57/home/git/blog.git 2. 如果克隆失败\r检查服务器仓库权限：\n1 ssh -p 57 tk@157.82.12.146 \u0026#34;ls -ld /home/git/blog.git\u0026#34; 1 drwxr-xr-x 7 tk tk 4096 4月 20 13:08 /home/git/blog.git 如果权限不足，需修正：\n1 ssh -p 57 tk@157.82.12.146 \u0026#34;chmod -R 755 /home/git/blog.git\u0026#34; ","date":"2025-09-08T15:58:32+08:00","image":"https://sports0808008.github.io/img/a9.jpg","permalink":"https://sports0808008.github.io/post/%E5%8D%9A%E5%AE%A2%E9%83%A8%E7%BD%B2/","title":"博客部署"},{"content":"Hugo 目录结构\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 . ├── archetypes │ └── default.md\t# hugo新建文章的默认模板 ├── config.yaml # 网站配置文件 ├── content # 站点内的内容都在这里 │ ├── categories # “分类”页面的首页 │ │ └── Test # “分类”页面下的一个具体分类页面 │ ├── page # 显示在网站主页左侧边栏菜单的选项（导航栏） │ │ ├── about # 左侧边栏菜单中的“关于”页面 │ │ ├── archives # 左侧边栏菜单中的“归档”页面 │ │ ├── links # 左侧边栏菜单中的“链接”页面 │ │ └── search # 左侧边栏菜单中的“搜索”页面 │ └── post # 用户写的帖子都放在这里，每个子文件夹对应一个帖子 │ ├── 2024 │ ├── 09 │ ├── 文章目录 │\t├── .md │\t├── .img │\t├── 10 │ ├── 文章目录 │\t├── .md ├── data ├── layouts ├── LICENSE ├── README.md ├── resources │ └── _gen │ ├── assets │ └── images ├── static # 放用户自定义字体、用户头像、网站小图标等 └── themes # 放各种主题 └── hugo-theme-stack # stack主题 ├── archetypes ├── assets ├── config.yaml ├── data ├── debug.sh ├── exampleSite ├── go.mod ├── i18n ├── images ├── layouts ├── LICENSE ├── netlify.toml ├── README.md └── theme.toml hugo.yaml 配置文件详解\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 baseurl: https://example.com/ languageCode: zh-cn theme: hugo-theme-stack # 主题 title: 额，随便测试 # 这个没用 copyright: Wei Qi # 这个是在页脚显示的 TimeZone: Asia/Shanghai DefaultContentLanguage: zh-cn # 中文支持 hasCJKLanguage: true # 如果是中文就设置为true，否则设置为false languages: # 多语言支持 zh-cn: languageName: 中文 title: 网站名称 weight: 2 params: description: 网站描述 services: # 主题配置 disqus: shortname: \u0026#34;hugo-theme-stack\u0026#34; pagination: pagerSize: 6\t# 页面显示的文章数量 params: mainSections: - post\t# 首页显示的文章所在路径，即首页会显示 hugo/content/post/ 下的所有文章 featuredImageField: image rssFullContent: true favicon: /favicon.ico # 网站的图标，放到主题根目录下 footer: since: 2024 # 页脚显示年份的开始年份 dateFormat:\t# 时间显示格式 published: 2006年1月2日 # Jan 02, 2006 lastUpdated: 2006年1月2日 15:04 # Jan 02, 2006 15:04 MST sidebar: emoji: 🍥 subtitle: Life is Passion ! # 头像下边的文字 avatar: enabled: true local: false\t# false 头像才显示本地的图片 src: /img/tx.png\t# 放到 hugo/static/img/ 下边，没有 img 则新建 article: # 文章页配置 math: false # 启用数学公式 toc: true # 文章显示目录 readingTime: true # 文字显示阅读时间 license: enabled: true default: Licensed under CC BY-NC-SA 4.0 # 默认的版权许可 comments: # 评论系统 enabled: false\t# 开评论则为 true，需要到网上搜索相关的配置 provider: disqus widgets:\t# 右侧导航栏，即在页面右边显示：搜索、归档、标签，将对应的 type 注释掉则不显示 homepage: - type: search # 首页搜索框 - type: archives # 首页归档 params: limit: 5 - type: categories # 首页分类 params: limit: 10 - type: tag-cloud # 首页标签云 params: limit: 10 page: - type: toc # 文章目录 colorScheme: # Display toggle toggle: true # 显示切换暗色模式按钮 # Available values: auto, light, dark default: auto # 默认主题模式 imageProcessing: cover: enabled: true # 是否启用封面图 content: enabled: true # 是否启用内容图片 menu: # 菜单、导航栏 main: []\t# 这里不配置，配置菜单最好是在 hugo/content/page/ 下加 .md 文件 social: # 头像下边的社交链接 - identifier: github name: GitHub url: # https://github.com/CaiJimmy/hugo-theme-stack params: icon: brand-github - identifier: twitter name: Twitter url: # https://twitter.com params: icon: brand-twitter related: # 相关文章 includeNewer: true threshold: 60 toLower: false indices: - name: tags weight: 100 - name: categories weight: 200 markup: goldmark: # 这里是 goldmark 渲染选项，默认即可 tableOfContents: # 文章目录（右边），层级 endLevel: 5\t# 显示几层标题 ordered: true\t# 标题显示序号，false则无序 startLevel: 2\t# 从第几级标题开始显示 highlight:\t# 代码块高亮 noClasses: false\t# 不启用特定css类高亮 codeFences: true\t# 启用代码块 guessSyntax: true\t# 自动识别语法 # lineNoStart: 1 lineNos: true\t# 启用行号 # lineNumbersInTable: true # tabWidth: 4 style: github-dark\t# 代码块样式 frontmatter: lastmod: :fileModTime\t# 文章末尾显示文章的更新时间 站点配置\r网站图标\r根据上边的 【hugo.yaml 配置文件】，把 .ico 文件放到主题的根目录下，然后修改\n1 favicon: /favicon.ico 首页头像\r根据上边的 【hugo.yaml 配置文件】，把 .png 放到 hugo/static/img/ 下，然后修改\n1 2 3 4 5 6 7 sidebar: emoji: 🍥 subtitle: 好好生活 # 头像下边的文字 avatar: enabled: true local: false # true 这里要改为 false 才生效 src: /img/tx.png # 头像 网站名称\r根据上边的 【hugo.yaml 配置文件】，自己改改 languages\n导航栏\r根据上边的 【Hugo 目录结构】，将 hugo-theme-stack\\exampleSite\\content 的内容放到 hugo/content/ 下，其中 page 目录就是导航栏。\n站点底部文字\r底部文字在 hugo-theme-stack/layout/partials/footer/footer.html 里，相关的网安备案、ICP备案、使用Hugo构建、文章数量、访问人数等文字都写在这里。\n更改“使用 Hugo 构建 主题Stack由Jimmy设计”等字样\rhugo-theme-stack/layout/partials/footer/footer.html 注释掉原先的代码，注意是 \u0026lt;section class=\u0026quot;powerby\u0026quot;\u0026gt; 里边\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 \u0026lt;section class=\u0026#34;powerby\u0026#34;\u0026gt; ...... \u0026lt;!-- {{- $Generator := `\u0026lt;a href=\u0026#34;https://gohugo.io/\u0026#34; target=\u0026#34;_blank\u0026#34; rel=\u0026#34;noopener\u0026#34;\u0026gt;Hugo\u0026lt;/a\u0026gt;` -}} {{- $Theme := printf `\u0026lt;b\u0026gt;\u0026lt;a href=\u0026#34;https://github.com/CaiJimmy/hugo-theme-stack\u0026#34; target=\u0026#34;_blank\u0026#34; rel=\u0026#34;noopener\u0026#34; data-version=\u0026#34;%s\u0026#34;\u0026gt;Stack\u0026lt;/a\u0026gt;\u0026lt;/b\u0026gt;` $ThemeVersion -}} {{- $DesignedBy := `\u0026lt;a href=\u0026#34;https://jimmycai.com\u0026#34; target=\u0026#34;_blank\u0026#34; rel=\u0026#34;noopener\u0026#34;\u0026gt;Jimmy\u0026lt;/a\u0026gt;` -}} {{ T \u0026#34;footer.builtWith\u0026#34; (dict \u0026#34;Generator\u0026#34; $Generator) | safeHTML }} {{ T \u0026#34;footer.designedBy\u0026#34; (dict \u0026#34;Theme\u0026#34; $Theme \u0026#34;DesignedBy\u0026#34; $DesignedBy) | safeHTML }} --\u0026gt; \u0026lt;!-- 加入这串 --\u0026gt; \u0026lt;section class=\u0026#34;powerby\u0026#34;\u0026gt; \u0026lt;a href=\u0026#34;https://gohugo.io/\u0026#34; target=\u0026#34;_blank\u0026#34; rel=\u0026#34;noopener\u0026#34;\u0026gt;Hugo\u0026lt;/a\u0026gt; theme \u0026lt;b\u0026gt;\u0026lt;a href=\u0026#34;https://github.com/CaiJimmy/hugo-theme-stack\u0026#34; target=\u0026#34;_blank\u0026#34; rel=\u0026#34;noopener\u0026#34; data-version=\u0026#34;3.27.0\u0026#34;\u0026gt;Stack\u0026lt;/a\u0026gt;\u0026lt;/b\u0026gt; \u0026lt;a\u0026gt;designed by Jimmy\u0026lt;/a\u0026gt; \u0026lt;/section\u0026gt; \u0026lt;!-- 结束 --\u0026gt; \u0026lt;/section\u0026gt; 效果就如现在的网站一致。\n[!CAUTION]\n由于自己不会写相关的代码，所以这个操作是：\n在网页上，F12控制台找到该相关元素，右键选择【编辑为 HTML 】，将这段 HTML 代码复制贴贴到 footer.html 里，然后再修改文字\n添加备案\rhugo-theme-stack/layout/partials/footer/footer.html 在 \u0026lt;section class=\u0026quot;powerby\u0026quot;\u0026gt; 里边，自己调整代码的位置，看得舒服就行。\n1 2 \u0026lt;a href=\u0026#34;https://beian.miit.gov.cn/\u0026#34; target=\u0026#34;_blank\u0026#34;\u0026gt;X ICP 备 2024XXXXXX 号\u0026lt;/a\u0026gt; \u0026lt;br/\u0026gt; \u0026lt;a href=\u0026#34;http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=XXXXXXXXXXXX\u0026#34; target=\u0026#34;_blank\u0026#34;\u0026gt;XX网安备 XXXXXXXXXX 号\u0026lt;/a\u0026gt; 统计站点文章数量和字数\r参考：多语言 hugo 添加总字数统计和建站时间 - 滑翔闪’S BLOG (huaxiangshan.com)\nhugo-theme-stack/layout/partials/footer/footer.html 在 \u0026lt;section class=\u0026quot;powerby\u0026quot;\u0026gt; 里边，加入\n1 2 3 4 5 6 7 {{ $articleCount := len .Site.RegularPages }} {{ $totalWordCount := 0 }} {{ range .Site.Pages }} {{ $totalWordCount = add $totalWordCount .WordCount }} {{ end }} \u0026lt;p\u0026gt;发布了 {{ $articleCount }} 篇文章 | 共 {{$totalWordCount}} 字\u0026lt;/p\u0026gt; 但是我觉得这个效果不好，用 k 来做单位会更直观一些。所以做如下修改：\n1 2 3 4 5 {{ $totalWordCountInK := div $totalWordCount 100.0 }} {{ $totalWordCountInKCeiling := math.Ceil $totalWordCountInK }} {{ $totalWordCountInKFloat := div $totalWordCountInKCeiling 10.0 | printf \u0026#34;%.1f\u0026#34;}} \u0026lt;p\u0026gt;发布了 {{ $articleCount }} 篇文章 | 共 {{ $totalWordCountInKFloat }}K 字\u0026lt;/p\u0026gt; 站点访问人数统计\r参考：Hugo+Stack主题集成Vercount统计网站访问信息 (farb.top)\n1）在 hugo-theme-stack/layout/partials/footer/custom.html 加入\n1 \u0026lt;script defer src=\u0026#34;https://cn.vercount.one/js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; 2）在 hugo-theme-stack/layout/partials/footer/footer.html 在 \u0026lt;section class=\u0026quot;powerby\u0026quot;\u0026gt; 里边，加入（合适的位置，看着舒服就行）\n1 \u0026lt;a\u0026gt;总访客数: \u0026lt;/a\u0026gt;\u0026lt;span id=\u0026#39;busuanzi_value_site_uv\u0026#39;\u0026gt;Loading\u0026lt;/span\u0026gt; \u0026lt;a\u0026gt; | \u0026lt;/a\u0026gt; \u0026lt;a\u0026gt;总访问量: \u0026lt;/a\u0026gt;\u0026lt;span id=\u0026#39;busuanzi_value_site_pv\u0026#39;\u0026gt;Loading\u0026lt;/span\u0026gt;\u0026lt;/br\u0026gt; 页面加载进度条显示\r在 hugo-theme-stack/layout/partials/footer/custom.html 加入\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 \u0026lt;!-- layouts/partials/footer/custom.html --\u0026gt; \u0026lt;script src=\u0026#34;https://cdn.jsdelivr.net/gh/zhixuan2333/gh-blog@v0.1.0/js/nprogress.min.js\u0026#34; integrity=\u0026#34;sha384-bHDlAEUFxsRI7JfULv3DTpL2IXbbgn4JHQJibgo5iiXSK6Iu8muwqHANhun74Cqg\u0026#34; crossorigin=\u0026#34;anonymous\u0026#34; \u0026gt;\u0026lt;/script\u0026gt; \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;https://cdn.jsdelivr.net/gh/zhixuan2333/gh-blog@v0.1.0/css/nprogress.css\u0026#34; integrity=\u0026#34;sha384-KJyhr2syt5+4M9Pz5dipCvTrtvOmLk/olWVdfhAp858UCa64Ia5GFpTN7+G4BWpE\u0026#34; crossorigin=\u0026#34;anonymous\u0026#34; /\u0026gt; \u0026lt;script\u0026gt; NProgress.start(); document.addEventListener(\u0026#34;readystatechange\u0026#34;, () =\u0026gt; { if (document.readyState === \u0026#34;interactive\u0026#34;) NProgress.inc(0.8); if (document.readyState === \u0026#34;complete\u0026#34;) NProgress.done(); }); \u0026lt;/script\u0026gt; 首页设置\r首页全局配置\r在 hugo-theme-stack/assets/scss/custom.scss 里边，加入\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 /*---------------全局设置-------------*/ :root { --card-border-radius: 20px; //小卡片圆角 --article-font-size: 1.7rem; // 全局字体大小 //--card-background: #ffffff;//小卡片背景颜色 //--body-text-color: rgba(171, 169, 169, 0.58);//字体颜色 } /*选中文字颜色*/ ::selection { color: #fff; background: #557697; } /*滚动条样式*/ html { ::-webkit-scrollbar { width: 18px; //display: none; //隐藏滚动条 } ::-webkit-scrollbar-track { //background-color: #fff; //滚动条背景颜色 } ::-webkit-scrollbar-thumb { //鼠标移动上去前的滚动条颜色 background-color: rgba(111, 114, 115, 0.65); border-radius: 20px; border: 6px solid transparent; background-clip: content-box; } ::-webkit-scrollbar-thumb:hover { //鼠标移动上去时的滚动条颜色 background-color: #232525; } } 首页欢迎栏\r1）在 hugo-theme-stack/layouts/index.html 里加入\n1 2 3 4 5 6 7 8 9 10 11 \u0026lt;!-- 首页欢迎字幅 --\u0026gt; \u0026lt;div class=\u0026#34;welcome\u0026#34;\u0026gt; \u0026lt;p style=\u0026#34;font-size: 2rem; text-align: center; font-weight: bold\u0026#34;\u0026gt; \u0026lt;span class=\u0026#34;shake\u0026#34;\u0026gt;👋\u0026lt;/span\u0026gt; \u0026lt;span class=\u0026#34;jump-text1\u0026#34; \u0026gt; Welcome\u0026lt;/span\u0026gt; \u0026lt;span class=\u0026#34;jump-text2\u0026#34;\u0026gt; To \u0026lt;/span\u0026gt; \u0026lt;span class=\u0026#34;jump-text3\u0026#34; style=\u0026#34;color:#e99312\u0026#34;\u0026gt;Wei Qi\u0026lt;/span\u0026gt; \u0026lt;span class=\u0026#34;jump-text9\u0026#34; style=\u0026#34;color:#e99312\u0026#34;\u0026gt;Blog\u0026lt;/span\u0026gt; \u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;!-- 首页欢迎字幅 --\u0026gt; 位置如下：\n2）在 hugo-theme-stack/assets/scss/custom.scss 里边，加入\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 //首页欢迎板块样式 .welcome { color: var(--card-text-color-main); background: var(--card-background); box-shadow: var(--shadow-l2); border-radius: 30px; display: inline-block; } // 👋emoji实现摆动效果 .shake { display: inline-block; animation: shake 1s; animation-duration: 1s; animation-timing-function: ease; animation-delay: 0s; animation-iteration-count: 1; animation-direction: normal; animation-fill-mode: none; animation-play-state: running; animation-name: shake; animation-timeline: auto; animation-range-start: normal; animation-range-end: normal; animation-delay: 2s; @keyframes shake { 0% { transform: rotate(0); } 25% { transform: rotate(45deg) scale(1.2); } 50% { transform: rotate(0) scale(1.2); } 75% { transform: rotate(45deg) scale(1.2); } 100% { transform: rotate(0); } } } // 实现字符跳动动画 .jump-text1 { display: inline-block; animation: jump 0.5s 1; } .jump-text2 { display: inline-block; animation: jump 0.5s 1; animation-delay: 0.1s; } .jump-text3 { display: inline-block; animation: jump 0.5s 1; animation-delay: 0.2s; } .jump-text4 { display: inline-block; animation: jump 0.5s 1; animation-delay: 0.3s; } .jump-text5 { display: inline-block; animation: jump 0.5s 1; animation-delay: 0.4s; } .jump-text6 { display: inline-block; animation: jump 0.5s 1; animation-delay: 0.5s; } .jump-text7 { display: inline-block; animation: jump 0.5s 1; animation-delay: 0.6s; } .jump-text8 { display: inline-block; animation: jump 0.5s 1; animation-delay: 0.7s; } .jump-text9 { display: inline-block; animation: jump 0.5s 1; animation-delay: 0.9s; } @keyframes jump { 0% { transform: translateY(0); } 50% { transform: translateY(-20px); } 100% { transform: translateY(0); } } 首页文章布局\r在 hugo-theme-stack/assets/scss/custom.scss 加入\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 /*主页布局间距调整*/ .main-container { gap: 50px; //文章宽度 @include respond(md) { padding: 0 30px; gap: 40px; //中等屏幕时的文章宽度 } } .related-contents { overflow-x: visible; //显示隐藏的图标 padding-bottom: 15px; } 首页文章样式\r在 hugo-theme-stack/assets/scss/custom.scss 加入\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 /*主页文章图片样式*/ $image-scale: 1.2; .article-list article .article-image img { width: 100%; height: 150px; object-fit: cover; //不同显示器（手机，小屏幕电脑，大屏幕电脑）显示的图片高度大小 @include respond(sm) { height: 305px; } @include respond(md) { height: 305px; } @include respond(xl) { height: 325px; } } /*主页文章图片圆角*/ .article-list article { --card-border-radius: 24px; } /*文章标签圆角*/ .article-category a, .article-tags a { border-radius: 11px; } /*鼠标移动到文章图片放大*/ .article-list article .article-image { position: relative; overflow: hidden; //不显示超出的部分 } .article-list article .article-image img:hover { transform: scale($image-scale); //放大尺寸 } .article-list article .article-image img { transition: transform 0.85s ease-in-out;//持续时间 } 文章内部图片样式\r在 hugo-theme-stack/assets/scss/custom.scss 加入\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 /*文章内部-页脚-相关文章图片圆角*/ .article-list--compact article .article-image img { border-radius: 17%; } /*文章内部-页脚-相关文章图片放大动画*/ .article-list--compact article \u0026gt; a { transition: .6s ease; } .article-list--compact article \u0026gt; a:hover { transform: scale(1.03, 1.03); overflow: visible; } 首页右侧导航栏美化\r在 hugo-theme-stack/assets/scss/custom.scss 加入\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 /*------------------右侧导航栏--------------*/ /*搜索菜单动画*/ .search-form.widget { transition: transform 0.6s ease; } .search-form.widget:hover { transform: scale(1.1, 1.1); } /*归档小图标放大动画*/ .widget.archives .widget-archive--list { transition: transform .3s ease; } .widget.archives .widget-archive--list:hover { transform: scale(1.05, 1.05); } /*右侧标签放大动画*/ .tagCloud .tagCloud-tags a { border-radius: 10px; font-size: 1.4rem; transition: transform .3s ease; } .tagCloud .tagCloud-tags a:hover { transform: scale(1.1, 1.1); } 首页左侧导航栏美化\r在 hugo-theme-stack/assets/scss/custom.scss 加入\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 /*--------------------左侧导航栏---------------*/ .left-sidebar { --sidebar-avatar-size: 115px; //左侧头像大小 --sidebar-element-separation: 15px; //头像下方间距 --emoji-size: 40px; //emoji容器大小 --emoji-font-size: 25px; //emoji字体大小 } /*头像旋转动画*/ .sidebar header .site-avatar .site-logo { transition: transform 1.65s ease-in-out; //旋转时间 } .sidebar header .site-avatar .site-logo:hover { transform: rotate(360deg); //旋转角度为360度 } /*社交菜单居中*/ .social-menu svg { gap: 15px; justify-content: center; width: 30px; height: 30px; //社交菜单大小 stroke: var(--body-text-color); stroke-width: 1.33; } /*暗色模式按钮距离上边按钮边距调整*/ .menu .menu-bottom-section { margin-top: 70px; } // #dark-mode-toggle { // gap: 30px; //暗色模式横向长度 // } /*下拉菜单美化*/ .menu { list-style: none; flex-direction: column; overflow-x: hidden; overflow-y: scroll; //搭配菜单滚动条美化使用 flex-grow: 1; font-size: 1.7rem; // background-color: rgb(252, 252, 252); box-shadow: var(--shadow-l2); //改个阴影 display: none; margin: 0; //改为0 // border-radius: 20px; //加个圆角 这个圆角看起来怪怪的，不适合，注释掉 padding: 30px 30px; @include respond(xl) { padding: 15px 0; } \u0026amp;, .menu-bottom-section { gap: 30px; @include respond(xl) { gap: 25px; } } \u0026amp;.show { display: flex; } @include respond(md) { align-items: flex-end; display: flex; background-color: transparent; padding: 0; box-shadow: none; margin: 0; } li { position: relative; vertical-align: middle; padding: 0; @include respond(md) { width: 100%; } svg { stroke-width: 1.33; width: 20px; height: 20px; } a { height: 100%; display: inline-flex; align-items: center; color: var(--body-text-color); gap: var(--menu-icon-separation); } span { flex: 1; } \u0026amp;.current { a { color: var(--accent-color); font-weight: bold; } } } } /*手机模式下的菜单滚动条美化*/ .menu::-webkit-scrollbar { display: none; //防止大屏幕时出现滚动条 } .sidebar header .site-name { margin: 8px; font-size: 2rem; //字体大小 } // -------------------------------- // 菜单栏样式 // 下拉菜单改圆角样式 .menu { padding-left: 0; list-style: none; flex-direction: column; overflow-x: hidden; overflow-y: scroll; flex-grow: 1; font-size: 1.6rem; background-color: var(--card-background); box-shadow: var(--shadow-l2); //改个阴影 display: none; margin: 0; //改为0 border-radius: 10px; //加个圆角 padding: 30px 30px; @include respond(xl) { padding: 15px 0; } \u0026amp;, .menu-bottom-section { gap: 30px; @include respond(xl) { gap: 25px; } } \u0026amp;.show { display: flex; } @include respond(md) { align-items: flex-end; display: flex; background-color: transparent; padding: 0; box-shadow: none; margin: 0; } li { position: relative; vertical-align: middle; padding: 0; @include respond(md) { width: 100%; } svg { stroke-width: 1.33; width: 20px; height: 20px; } a { height: 100%; display: inline-flex; align-items: center; color: var(--body-text-color); gap: var(--menu-icon-separation); } span { flex: 1; } \u0026amp;.current { a { color: var(--accent-color); font-weight: bold; } } } } 归档页面双栏样式\r在 hugo-theme-stack/assets/scss/custom.scss 加入\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 /*----------------归档页面---------------*/ /*归档页面卡片缩放*/ .article-list--tile article { transition: .6s ease; } .article-list--tile article:hover { transform: scale(1.03, 1.03); } /* 归档页面两栏 */ @media (min-width: 1024px) { .article-list--compact { display: grid; grid-template-columns: 1fr 1fr; background: none; box-shadow: none; gap: 1rem; article { background: var(--card-background); border: none; box-shadow: var(--shadow-l2); margin-bottom: 8px; border-radius: 16px; } } } 链接页面三栏样式\r在 hugo-theme-stack/assets/scss/custom.scss 加入\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 /*--------------*-链接页面-----------------*/ /*友情链接设置成三栏样式*/ @media (min-width: 1024px) { .article-list--compact.links { display: grid; grid-template-columns: 1fr 1fr 1fr; background: none; box-shadow: none; gap: 1rem; article { background: var(--card-background); border: none; box-shadow: var(--shadow-l2); margin-bottom: 8px; border-radius: var(--card-border-radius); \u0026amp;:nth-child(odd) { margin-right: 8px; } } } } 首页显示文章\r根据 【hugo.yaml 配置文件详解】，修改参数 mainSections\n1 2 3 params: mainSections: - post # hugo/content/post/ 下的所有文章都能显示，不然首页不显示文章 单页文章数量设置\r根据 【hugo.yaml 配置文件详解】，修改参数 pagerSize\n1 2 pagination: pagerSize: 8\t# 改这个 文章设置\r文章生成模板\r修改 hugo/archetypes/default.md\n1 2 3 4 5 6 7 8 9 10 11 title: \u0026#39;{{ replace .File.ContentBaseName \u0026#34;-\u0026#34; \u0026#34; \u0026#34; | title }}\u0026#39; date: {{ .Date }} draft = false categories: [\u0026#34;未分类\u0026#34;] tags: [] description: \u0026#34;文章描述\u0026#34; keywords: [] image: /img/a11.jpg featuredImage: /img/a11.jpg toc: true math: false 然后使用 hugo new content/post/xxxx.md 命令时，生成的 .md 文件，就是根据这个模块生成的。\n文章图片不显示问题\r参考：图片类型 - 文档 - Hugo 图片模块 | HugoMods\n我用的 Typora，其他应用写的 md 大概也是这样\n做法：\n1）我在 hugo/content/post/2024/10/ 创建一个文章目录：HugoStack主题美化\n2）然后到里边，hugo/content/post/2024/10/HugoStack主题美化/ 创建 index.md （名字一定要叫 index ）\n3）.md 里对图片进行设置，这样直接把图片贴进去就行了。\n4）如果不是 Typora，则需要在 index.md 的同级目录创建一个目录，例如 hugo/content/post/2024/10/HugoStack主题美化/img/ ，图片放到 img 目录下，然后引用：\n1 ![图片描述可忽略](img/1727420925875.png) [!NOTE]\nHugo 好像是只渲染 index.md 的图片，如果 .md 文件名不是 index ，则里边的图片是不显示的。\n文章代码块样式\r代码块高亮\r在 hugo.yaml 中，添加 highlight 参数，我文章里的代码块配置是这样子的：\n1 2 3 4 5 6 7 8 9 10 markup: highlight: noClasses: false codeFences: true guessSyntax: true # lineNoStart: 1 lineNos: true # lineNumbersInTable: true # tabWidth: 4 style: github-dark 代码块样式\r在 hugo-theme-stack/assets/scss/custom.scss 加入\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 //为代码块顶部添加macos样式 .article-content { .highlight:before { content: \u0026#34;\u0026#34;; display: block; background: #fc625d; border-radius: 50%; box-shadow: 20px 0 #fdbc40, 40px 0 #35cd4b; height: 12px; width: 12px; margin-bottom: 5px; } } // 代码块基础样式修改 .highlight { max-width: 102% !important; background-color: var(--pre-background-color); padding: var(--card-padding); position: relative; border-radius: 20px; margin-left: -7px !important; margin-right: -12px; box-shadow: var(--shadow-l1) !important; \u0026amp;:hover { .copyCodeButton { opacity: 1; } } // keep Codeblocks LTR [dir=\u0026#34;rtl\u0026#34;] \u0026amp; { direction: ltr; } pre { margin: initial; padding: 0; margin: 0; width: auto; // background-color: #282c34 !important; } } // light模式下的代码块样式调整 [data-scheme=\u0026#34;light\u0026#34;] .article-content .highlight { background-color: #fff9f3; } [data-scheme=\u0026#34;light\u0026#34;] .chroma { color: #ff6f00; background-color: #fff9f3cc; } 文章统计字数\r参考：为 Hugo 博客添加字数统计 - Mogeko’s Blog\n1）在 hugo-theme-stack/layouts/paritals/article/components/details.html 加入\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 \u0026lt;footer class=\u0026#34;article-time\u0026#34;\u0026gt; ... {{ if .WordCount }} \u0026lt;footer class=\u0026#34;article-wordcount\u0026#34;\u0026gt; \u0026lt;div\u0026gt; {{ partial \u0026#34;helper/字数.svg\u0026#34; }} \u0026lt;span class=\u0026#34;article-wordcount--count\u0026#34;\u0026gt;{{ T \u0026#34;article.wordCount\u0026#34; .WordCount }}\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/footer\u0026gt; {{ end }} ... \u0026lt;/footer\u0026gt; 注意：{{ partial \u0026quot;helper/字数.svg\u0026quot; }} 这里是把 .svg 图片放到 hugo-theme-stack/layouts/paritals/helper/字数.svg ，这里看代码前边的显示阅读时间引用了 helper 里的内容，所以我这里也放到这里。不用图片的话可以删掉。\n2）在 hugo-theme-stack/i18n/zh-cn.yaml 中的 article 下，加入\n1 2 wordCount: other: \u0026#34;字数: {{ .Count }}\u0026#34; 3）这个时候，预览会发现，字数的字体相当大，与日期、阅读时间不协调，所以得更改它们的样式，保持统一。在 hugo-theme-stack/assets/scss/custom.scss 加入\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // 阅读时间、字数样式 .article-time { display: flex; align-items: center; font-size: 14px; color: #666; } .article-time div { margin-right: 10px; } .article-time--count { font-size: 14px; color: #666; } 如果需要在字数前边加个小图标，则需要把小图标放到这里 hugo-theme-stack/layouts/partials/helper/xxx.svg 下，然后需要把 .svg 文件里的前缀给删掉，这个前缀删掉才可以：\n1 \u0026lt;?xml version=\u0026#34;1.0\u0026#34; standalone=\u0026#34;no\u0026#34;?\u0026gt; 效果：\n致谢\r美化过程中，参考了很多优秀的博主博客：\n博客切换到STACK 主题 (skfwe.cn)\nHugo博客 | stack主题修改第一站 (munlelee.github.io)\nHugo-theme-Stack 魔改美化 | Naive Koala (xalaok.top)\n基于 Hugo 的网站搭建日志 02 (lovir.cn)\n不是从零开始的 Hugo 建站笔记 (fourxiajiao.github.io)\nHugo | Hugo-stack-theme 主题魔改版 (qjwhz.top)\n（3）Stack主题的自定义 (linsnow.cn)\n使用 Hugo 对博客的重建与 Stack 主题优化记录 (oxidane-uni.github.io)\nHUGO+Stack 下的美化修改 (shimoko.com)\n时隔半年，博客又有了那些新变化？！ (irithys.com)\n文章来源： https://weiqifun.com/posts/2024/10/hugo+stack%E4%B8%BB%E9%A2%98%E7%BE%8E%E5%8C%96/\n","date":"2025-09-08T14:38:12+08:00","image":"https://sports0808008.github.io/img/a11.jpg","permalink":"https://sports0808008.github.io/post/hugo_stack%E4%B8%BB%E9%A2%98%E7%BE%8E%E5%8C%96/","title":"Hugo_Stack主题美化"},{"content":"添加音乐播放器\r方法一：网易云音乐\r官网：https://github.com/metowolf/MetingJS\n直接掉接口：\n1 2 3 4 5 6 7 8 9 10 11 \u0026lt;!-- 【custom.html】 --\u0026gt; \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.css\u0026#34;\u0026gt; \u0026lt;script src=\u0026#34;https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;!-- require MetingJS --\u0026gt; \u0026lt;script src=\u0026#34;https://cdn.jsdelivr.net/npm/meting@2/dist/Meting.min.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;meting-js server=\u0026#34;netease\u0026#34; type=\u0026#34;playlist\u0026#34; id=\u0026#34;117882154\u0026#34;\u0026gt; #网易云音乐id \u0026lt;/meting-js\u0026gt; 添加在其他页面继续播放\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026#34;zh-CN\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; \u0026lt;meta name=\u0026#34;viewport\u0026#34; content=\u0026#34;width=device-width, initial-scale=1.0\u0026#34;\u0026gt; \u0026lt;title\u0026gt;音乐播放器\u0026lt;/title\u0026gt; \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.css\u0026#34;\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;!-- APlayer 容器 --\u0026gt; \u0026lt;div id=\u0026#34;aplayer\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;!-- MetingJS 元素 --\u0026gt; \u0026lt;meting-js server=\u0026#34;netease\u0026#34; type=\u0026#34;playlist\u0026#34; id=\u0026#34;117882154\u0026#34; fixed=\u0026#34;true\u0026#34; autoplay=\u0026#34;false\u0026#34;\u0026gt; \u0026lt;/meting-js\u0026gt; \u0026lt;!-- 引入 APlayer 和 MetingJS --\u0026gt; \u0026lt;script src=\u0026#34;https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script src=\u0026#34;https://cdn.jsdelivr.net/npm/meting@2/dist/Meting.min.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; // 全局变量用于存储APlayer实例 let ap = null; // 初始化播放器 function initPlayer() { // 等待MetingJS加载完成 const checkInterval = setInterval(() =\u0026gt; { const playerElement = document.getElementById(\u0026#39;aplayer\u0026#39;); if (playerElement \u0026amp;\u0026amp; playerElement.aplayer) { clearInterval(checkInterval); ap = playerElement.aplayer; console.log(\u0026#39;APlayer实例已获取\u0026#39;, ap); // 恢复播放状态 restorePlaybackState(); } }, 100); } // 恢复播放状态 function restorePlaybackState() { const playInfo = JSON.parse(localStorage.getItem(\u0026#34;playInfo\u0026#34;)); if (!playInfo) { console.log(\u0026#39;没有找到之前的播放记录\u0026#39;); return; } // 等待APlayer完全加载 setTimeout(() =\u0026gt; { try { // 切换歌曲 ap.list.switch(playInfo.index); console.log(`正在恢复第${playInfo.index + 1}首歌曲...`); // 等待歌曲加载 setTimeout(() =\u0026gt; { // 调整播放进度 ap.seek(playInfo.currentTime); // 恢复播放/暂停状态 if (!playInfo.paused) { ap.play(); console.log(\u0026#39;已恢复播放\u0026#39;); } else { console.log(\u0026#39;已恢复播放进度（暂停状态）\u0026#39;); } }, 800); } catch (error) { console.error(\u0026#39;恢复播放状态时出错:\u0026#39;, error); } }, 500); } // 页面销毁前监听 window.onbeforeunload = () =\u0026gt; { if (ap) { // 将播放信息用对象封装，并存入到localStorage中 const playInfo = { index: ap.list.index, currentTime: ap.audio.currentTime, paused: ap.audio.paused }; localStorage.setItem(\u0026#34;playInfo\u0026#34;, JSON.stringify(playInfo)); console.log(\u0026#39;播放状态已保存\u0026#39;, playInfo); } }; // 页面加载后初始化播放器 window.addEventListener(\u0026#39;load\u0026#39;, initPlayer); \u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 方法2：原生 酷狗音乐\r官网：https://github.com/DIYgod/APlayer\n中文文档：https://aplayer.js.org/#/zh-Hans/\n自定义音乐\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 \u0026lt;!-- custom.html --\u0026gt; \u0026lt;!--改为了本地引入 \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.css\u0026#34;\u0026gt; --\u0026gt; \u0026lt;script src=\u0026#34;https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;div id=\u0026#34;aplayer\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;script\u0026gt; // 音乐数据预处理 const musicDir = \u0026#39;{{ .Site.Home.Permalink }}music/\u0026#39;; const songs = [ \u0026#39;任贤齐-伤心太平洋\u0026#39;, \u0026#39;张韶涵-隐形的翅膀\u0026#39; ]; let audios = []; songs.forEach((item) =\u0026gt; { const songInfo = item.split(\u0026#39;-\u0026#39;); audios.push({ name: songInfo[1], artist: songInfo[0], url: musicDir + item + \u0026#39;/song.mp3\u0026#39;, // 歌曲路径 cover: musicDir + item + \u0026#39;/cover.jpg\u0026#39;, // 封面路径 lrc: musicDir + item + \u0026#39;/lyric.lrc\u0026#39;, // 歌词路径 }); }); const ap = new APlayer({ container: document.getElementById(\u0026#39;aplayer\u0026#39;), fixed: true, lrcType: 3, audio: audios }); /** * 页面销毁前监听 */ window.onbeforeunload = () =\u0026gt; { // 将播放信息用对象封装，并存入到localStorage中 const playInfo = { index: ap.list.index, currentTime: ap.audio.currentTime, paused: ap.paused }; localStorage.setItem(\u0026#34;playInfo\u0026#34;, JSON.stringify(playInfo)); }; /** * 页面加载后监听 */ window.onload = () =\u0026gt; { // 从localStorage取出播放信息 const playInfo = JSON.parse(localStorage.getItem(\u0026#34;playInfo\u0026#34;)); if (!playInfo) { return; } // 切换歌曲 ap.list.switch(playInfo.index); // 等待500ms再执行下一步(切换歌曲需要点时间，不能立马调歌曲进度条) setTimeout(() =\u0026gt; { // 调整时长 ap.seek(playInfo.currentTime); // 是否播放 if (!playInfo.paused) { ap.play() } }, 500); }; \u0026lt;/script\u0026gt; 网易云多ID（多音乐）\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026#34;zh-CN\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; \u0026lt;meta name=\u0026#34;viewport\u0026#34; content=\u0026#34;width=device-width, initial-scale=1.0\u0026#34;\u0026gt; \u0026lt;title\u0026gt;全局音乐播放器\u0026lt;/title\u0026gt; \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.css\u0026#34;\u0026gt; \u0026lt;script src=\u0026#34;https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script src=\u0026#34;https://cdn.jsdelivr.net/npm/meting@2/dist/Meting.min.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;div class=\u0026#34;buttons\u0026#34;\u0026gt; \u0026lt;button class=\u0026#34;active\u0026#34; data-id=\u0026#34;117882154\u0026#34;\u0026gt;歌单1\u0026lt;/button\u0026gt; \u0026lt;button data-id=\u0026#34;2399935867\u0026#34;\u0026gt;歌单2\u0026lt;/button\u0026gt; \u0026lt;button data-id=\u0026#34;2057627502\u0026#34;\u0026gt;歌单3\u0026lt;/button\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div id=\u0026#34;aplayer\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;script\u0026gt; // 全局变量用于存储APlayer实例 let ap = null; let currentPlaylistId = \u0026#39;117882154\u0026#39;; // 初始化播放器 function initPlayer() { // 等待MetingJS加载完成 const checkInterval = setInterval(() =\u0026gt; { const playerElement = document.getElementById(\u0026#39;aplayer\u0026#39;); if (playerElement \u0026amp;\u0026amp; playerElement.aplayer) { clearInterval(checkInterval); ap = playerElement.aplayer; console.log(\u0026#39;APlayer实例已获取\u0026#39;, ap); // 恢复播放状态 restorePlaybackState(); } }, 100); } // 恢复播放状态 function restorePlaybackState() { const playInfo = JSON.parse(localStorage.getItem(\u0026#34;playInfo\u0026#34;)); if (!playInfo) { console.log(\u0026#39;没有找到之前的播放记录\u0026#39;); return; } // 等待APlayer完全加载 setTimeout(() =\u0026gt; { try { // 切换歌曲 ap.list.switch(playInfo.index); console.log(`正在恢复第${playInfo.index + 1}首歌曲...`); // 等待歌曲加载 setTimeout(() =\u0026gt; { // 调整播放进度 ap.seek(playInfo.currentTime); // 恢复播放/暂停状态 if (!playInfo.paused) { ap.play(); console.log(\u0026#39;已恢复播放\u0026#39;); } else { console.log(\u0026#39;已恢复播放进度（暂停状态）\u0026#39;); } }, 800); } catch (error) { console.error(\u0026#39;恢复播放状态时出错:\u0026#39;, error); } }, 500); } // 页面销毁前监听 window.onbeforeunload = () =\u0026gt; { if (ap) { // 将播放信息用对象封装，并存入到localStorage中 const playInfo = { index: ap.list.index, currentTime: ap.audio.currentTime, paused: ap.audio.paused }; localStorage.setItem(\u0026#34;playInfo\u0026#34;, JSON.stringify(playInfo)); console.log(\u0026#39;播放状态已保存\u0026#39;, playInfo); } }; // 创建Meting元素 function createMetingElement(playlistId) { const existingMeting = document.querySelector(\u0026#39;meting-js\u0026#39;); if (existingMeting) existingMeting.remove(); const metingElement = document.createElement(\u0026#39;meting-js\u0026#39;); metingElement.setAttribute(\u0026#39;server\u0026#39;, \u0026#39;netease\u0026#39;); metingElement.setAttribute(\u0026#39;type\u0026#39;, \u0026#39;playlist\u0026#39;); metingElement.setAttribute(\u0026#39;id\u0026#39;, playlistId); metingElement.setAttribute(\u0026#39;fixed\u0026#39;, \u0026#39;true\u0026#39;); metingElement.setAttribute(\u0026#39;autoplay\u0026#39;, \u0026#39;false\u0026#39;); document.getElementById(\u0026#39;aplayer\u0026#39;).appendChild(metingElement); // 重新初始化播放器 initPlayer(); } // 按钮点击事件 document.querySelectorAll(\u0026#39;button\u0026#39;).forEach(button =\u0026gt; { button.addEventListener(\u0026#39;click\u0026#39;, function() { const playlistId = this.getAttribute(\u0026#39;data-id\u0026#39;); // 更新按钮状态 document.querySelectorAll(\u0026#39;button\u0026#39;).forEach(btn =\u0026gt; { btn.classList.remove(\u0026#39;active\u0026#39;); }); this.classList.add(\u0026#39;active\u0026#39;); // 保存当前播放状态 if (ap) { const playInfo = { index: ap.list.index, currentTime: ap.audio.currentTime, paused: ap.audio.paused }; localStorage.setItem(\u0026#34;playInfo\u0026#34;, JSON.stringify(playInfo)); } // 创建新的Meting元素 createMetingElement(playlistId); currentPlaylistId = playlistId; }); }); // 初始创建Meting元素 createMetingElement(currentPlaylistId); \u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; ","date":"2025-09-08T12:40:28+08:00","image":"https://tk-1312331175.cos.ap-chengdu.myqcloud.com/imgs/cover/202509090958756.webp","permalink":"https://sports0808008.github.io/post/%E9%9F%B3%E4%B9%90%E6%92%AD%E6%94%BE%E5%99%A8/","title":"音乐播放器"}]