Merge pull request 'update react functions and remove typst support' (#4) from mac-update into main
Reviewed-on: Jiao77/NovaBlog#4
This commit is contained in:
@@ -3,6 +3,7 @@ import { defineConfig } from 'astro/config';
|
|||||||
import mdx from '@astrojs/mdx';
|
import mdx from '@astrojs/mdx';
|
||||||
import vue from '@astrojs/vue';
|
import vue from '@astrojs/vue';
|
||||||
import tailwind from '@astrojs/tailwind';
|
import tailwind from '@astrojs/tailwind';
|
||||||
|
import react from '@astrojs/react';
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
@@ -13,6 +14,7 @@ export default defineConfig({
|
|||||||
optimize: true,
|
optimize: true,
|
||||||
}),
|
}),
|
||||||
vue(),
|
vue(),
|
||||||
|
react(),
|
||||||
tailwind({
|
tailwind({
|
||||||
applyBaseStyles: false, // 我们将手动控制基础样式
|
applyBaseStyles: false, // 我们将手动控制基础样式
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ NovaBlog/
|
|||||||
│ │ ├── LoginForm.vue # 登录表单
|
│ │ ├── LoginForm.vue # 登录表单
|
||||||
│ │ ├── UserStatus.vue # 用户状态栏
|
│ │ ├── UserStatus.vue # 用户状态栏
|
||||||
│ │ ├── Counter.vue # 计数器示例
|
│ │ ├── Counter.vue # 计数器示例
|
||||||
│ │ ├── TypstBlock.astro # Typst 渲染组件
|
|
||||||
│ │ └── TableOfContents.astro # 目录组件
|
│ │ └── TableOfContents.astro # 目录组件
|
||||||
│ ├── content/ # 内容集合
|
│ ├── content/ # 内容集合
|
||||||
│ │ ├── config.ts # 内容配置
|
│ │ ├── config.ts # 内容配置
|
||||||
@@ -797,7 +796,6 @@ docker-compose down # 停止服务
|
|||||||
- [Tailwind CSS 文档](https://tailwindcss.com)
|
- [Tailwind CSS 文档](https://tailwindcss.com)
|
||||||
- [Gin 框架文档](https://gin-gonic.com)
|
- [Gin 框架文档](https://gin-gonic.com)
|
||||||
- [GORM 文档](https://gorm.io)
|
- [GORM 文档](https://gorm.io)
|
||||||
- [Typst 文档](https://typst.app/docs)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
1. [快速开始](#快速开始)
|
1. [快速开始](#快速开始)
|
||||||
2. [文章管理](#文章管理)
|
2. [文章管理](#文章管理)
|
||||||
3. [MDX 组件使用](#mdx-组件使用)
|
3. [MDX 组件使用](#mdx-组件使用)
|
||||||
4. [Typst 学术排版](#typst-学术排版)
|
4. [React 动效组件](#react-动效组件)
|
||||||
5. [动效 HTML 块](#动效-html-块)
|
5. [动效 HTML 块](#动效-html-块)
|
||||||
6. [评论系统](#评论系统)
|
6. [评论系统](#评论系统)
|
||||||
7. [用户注册与登录](#用户注册与登录)
|
7. [用户注册与登录](#用户注册与登录)
|
||||||
@@ -25,7 +25,6 @@
|
|||||||
|
|
||||||
- Node.js 18+
|
- Node.js 18+
|
||||||
- Go 1.21+ (仅后端开发需要)
|
- Go 1.21+ (仅后端开发需要)
|
||||||
- Typst 0.11+ (可选,用于数学公式渲染)
|
|
||||||
|
|
||||||
### 启动开发服务器
|
### 启动开发服务器
|
||||||
|
|
||||||
@@ -140,15 +139,7 @@ import Counter from '../components/Counter.vue';
|
|||||||
<Counter initialCount={0} />
|
<Counter initialCount={0} />
|
||||||
```
|
```
|
||||||
|
|
||||||
#### TypstBlock 数学公式
|
|
||||||
|
|
||||||
```mdx
|
|
||||||
import TypstBlock from '../components/TypstBlock.astro';
|
|
||||||
|
|
||||||
<TypstBlock code={`
|
|
||||||
$ f(x) = x^2 + 2x + 1 $
|
|
||||||
`} />
|
|
||||||
```
|
|
||||||
|
|
||||||
### 自定义组件
|
### 自定义组件
|
||||||
|
|
||||||
@@ -206,73 +197,174 @@ import MyButton from '../components/MyButton.vue';
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Typst 学术排版
|
## React 动效组件
|
||||||
|
|
||||||
NovaBlog 内置 Typst 支持,可以渲染高质量的数学公式和学术排版。
|
NovaBlog 内置了多个 React 动效组件,可以在 MDX 文章中使用,为内容增添生动的视觉效果。
|
||||||
|
|
||||||
### 什么是 Typst?
|
### 内置 React 动效组件
|
||||||
|
|
||||||
Typst 是新一代排版系统,具有:
|
#### AnimatedCard 悬停动画卡片
|
||||||
- 比 LaTeX 更简洁的语法
|
|
||||||
- 更快的编译速度
|
|
||||||
- 原生支持数学公式
|
|
||||||
|
|
||||||
### 基本用法
|
一个带有悬停效果的卡片组件,当鼠标悬停时会产生上浮和阴影变化效果。
|
||||||
|
|
||||||
```mdx
|
```mdx
|
||||||
import TypstBlock from '../components/TypstBlock.astro';
|
import AnimatedCard from '../components/react/AnimatedCard';
|
||||||
|
|
||||||
<TypstBlock code={`
|
<AnimatedCard
|
||||||
$ f(x) = integral_0^infinity e^(-x^2) dif x = sqrt(pi) / 2 $
|
title="特色功能"
|
||||||
`} />
|
description="NovaBlog 提供丰富的动效组件,让你的博客更加生动"
|
||||||
|
color="#3b82f6"
|
||||||
|
client:visible
|
||||||
|
/>
|
||||||
```
|
```
|
||||||
|
|
||||||
### 数学公式示例
|
**属性说明**:
|
||||||
|
- `title`:卡片标题
|
||||||
|
- `description`:卡片描述
|
||||||
|
- `color`:卡片颜色(可选,默认为 `#3b82f6`)
|
||||||
|
|
||||||
#### 积分
|
#### FlipCard 翻转卡片
|
||||||
|
|
||||||
```typst
|
一个可以点击翻转的卡片组件,展示两面不同的内容。
|
||||||
$ integral_0^1 x^2 dif x = 1/3 $
|
|
||||||
|
```mdx
|
||||||
|
import FlipCard from '../components/react/FlipCard';
|
||||||
|
|
||||||
|
<FlipCard
|
||||||
|
frontTitle="前端技术"
|
||||||
|
frontDescription="Astro + Vue + React"
|
||||||
|
backTitle="后端技术"
|
||||||
|
backDescription="Go + Gin + SQLite"
|
||||||
|
frontColor="#3b82f6"
|
||||||
|
backColor="#10b981"
|
||||||
|
client:visible
|
||||||
|
/>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 求和
|
**属性说明**:
|
||||||
|
- `frontTitle`:正面标题
|
||||||
|
- `frontDescription`:正面描述
|
||||||
|
- `backTitle`:背面标题
|
||||||
|
- `backDescription`:背面描述
|
||||||
|
- `frontColor`:正面颜色(可选,默认为 `#3b82f6`)
|
||||||
|
- `backColor`:背面颜色(可选,默认为 `#10b981`)
|
||||||
|
|
||||||
```typst
|
#### ParticleBackground 粒子背景
|
||||||
$ sum_(i=1)^n i = (n(n+1))/2 $
|
|
||||||
|
一个带有动态粒子效果的背景组件,可以在粒子上显示自定义内容。
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
import ParticleBackground from '../components/react/ParticleBackground';
|
||||||
|
|
||||||
|
<ParticleBackground
|
||||||
|
particleCount={100}
|
||||||
|
color="#6366f1"
|
||||||
|
speed={1.5}
|
||||||
|
client:visible
|
||||||
|
>
|
||||||
|
<div style={{ textAlign: 'center' }}>
|
||||||
|
<h2 style={{ fontSize: '2.5rem', marginBottom: '1rem' }}>✨ 欢迎来到 NovaBlog</h2>
|
||||||
|
<p style={{ fontSize: '1.25rem', opacity: 0.9 }}>探索无限可能</p>
|
||||||
|
</div>
|
||||||
|
</ParticleBackground>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 矩阵
|
**属性说明**:
|
||||||
|
- `particleCount`:粒子数量(可选,默认为 50)
|
||||||
|
- `color`:粒子颜色(可选,默认为 `#3b82f6`)
|
||||||
|
- `speed`:粒子移动速度(可选,默认为 1)
|
||||||
|
- `children`:要显示的内容(可选)
|
||||||
|
|
||||||
```typst
|
#### TypewriterText 打字机效果
|
||||||
$ A = mat(
|
|
||||||
1, 2, 3;
|
一个模拟打字机效果的文本组件,逐字显示文本内容。
|
||||||
4, 5, 6;
|
|
||||||
7, 8, 9
|
```mdx
|
||||||
) $
|
import TypewriterText from '../components/react/TypewriterText';
|
||||||
|
|
||||||
|
<TypewriterText
|
||||||
|
text="NovaBlog 是一个极简、高效的程序员博客系统"
|
||||||
|
speed={50}
|
||||||
|
loop={true}
|
||||||
|
client:visible
|
||||||
|
/>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 分数
|
**属性说明**:
|
||||||
|
- `text`:要显示的文本
|
||||||
|
- `speed`:打字速度(可选,默认为 100ms)
|
||||||
|
- `loop`:是否循环播放(可选,默认为 false)
|
||||||
|
- `style`:自定义样式(可选)
|
||||||
|
|
||||||
```typst
|
### 在文章中使用
|
||||||
$ (a + b) / (c + d) $
|
|
||||||
|
在 MDX 文章中导入并使用这些组件:
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
---
|
||||||
|
title: 动效组件展示
|
||||||
|
description: 展示 NovaBlog 中的 React 动效组件
|
||||||
|
pubDate: 2024-01-20
|
||||||
|
tags: [React, 动效, 组件]
|
||||||
|
---
|
||||||
|
|
||||||
|
import AnimatedCard from '../components/react/AnimatedCard';
|
||||||
|
import FlipCard from '../components/react/FlipCard';
|
||||||
|
import ParticleBackground from '../components/react/ParticleBackground';
|
||||||
|
import TypewriterText from '../components/react/TypewriterText';
|
||||||
|
|
||||||
|
# React 动效组件展示
|
||||||
|
|
||||||
|
## 打字机效果
|
||||||
|
|
||||||
|
<TypewriterText
|
||||||
|
text="这是一个打字机效果的示例"
|
||||||
|
client:visible
|
||||||
|
/>
|
||||||
|
|
||||||
|
## 悬停卡片
|
||||||
|
|
||||||
|
<AnimatedCard
|
||||||
|
title="悬停效果"
|
||||||
|
description="鼠标悬停时会产生动画效果"
|
||||||
|
color="#8b5cf6"
|
||||||
|
client:visible
|
||||||
|
/>
|
||||||
|
|
||||||
|
## 翻转卡片
|
||||||
|
|
||||||
|
<FlipCard
|
||||||
|
frontTitle="点击我"
|
||||||
|
frontDescription="查看背面内容"
|
||||||
|
backTitle="翻转效果"
|
||||||
|
backDescription="这是卡片的背面"
|
||||||
|
client:visible
|
||||||
|
/>
|
||||||
|
|
||||||
|
## 粒子背景
|
||||||
|
|
||||||
|
<ParticleBackground client:visible>
|
||||||
|
<h3>粒子背景效果</h3>
|
||||||
|
<p>带有动态粒子的背景</p>
|
||||||
|
</ParticleBackground>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 上下标
|
### 自定义样式
|
||||||
|
|
||||||
```typst
|
这些组件都支持通过 `style` 属性自定义样式,例如:
|
||||||
$ x_1^2 + x_2^2 = r^2 $
|
|
||||||
|
```mdx
|
||||||
|
<TypewriterText
|
||||||
|
text="自定义样式示例"
|
||||||
|
style={{
|
||||||
|
fontSize: '2rem',
|
||||||
|
color: '#ef4444',
|
||||||
|
fontWeight: 'bold'
|
||||||
|
}}
|
||||||
|
client:visible
|
||||||
|
/>
|
||||||
```
|
```
|
||||||
|
|
||||||
### 高级排版
|
|
||||||
|
|
||||||
Typst 还支持:
|
|
||||||
- 多行公式对齐
|
|
||||||
- 定理环境
|
|
||||||
- 化学方程式
|
|
||||||
- 代码高亮
|
|
||||||
|
|
||||||
更多语法请参考 [Typst 官方文档](https://typst.app/docs)。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 动效 HTML 块
|
## 动效 HTML 块
|
||||||
@@ -585,10 +677,6 @@ public/
|
|||||||
|
|
||||||
A: 开发模式下 Astro 会自动热重载。如果生产构建,需要重新运行 `npm run build`。
|
A: 开发模式下 Astro 会自动热重载。如果生产构建,需要重新运行 `npm run build`。
|
||||||
|
|
||||||
### Q: Typst 公式渲染失败?
|
|
||||||
|
|
||||||
A: 确保 Typst 已正确安装。运行 `typst --version` 检查。
|
|
||||||
|
|
||||||
### Q: 评论无法发送?
|
### Q: 评论无法发送?
|
||||||
|
|
||||||
A: 检查后端服务是否正常运行在 `localhost:8080`。
|
A: 检查后端服务是否正常运行在 `localhost:8080`。
|
||||||
|
|||||||
211
package-lock.json
generated
211
package-lock.json
generated
@@ -9,11 +9,16 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/mdx": "^4.3.13",
|
"@astrojs/mdx": "^4.3.13",
|
||||||
|
"@astrojs/react": "^4.4.2",
|
||||||
"@astrojs/tailwind": "^5.1.5",
|
"@astrojs/tailwind": "^5.1.5",
|
||||||
"@astrojs/vue": "^5.1.4",
|
"@astrojs/vue": "^5.1.4",
|
||||||
|
"@myriaddreamin/typst-ts-renderer": "^0.7.0-rc2",
|
||||||
|
"@myriaddreamin/typst.ts": "^0.7.0-rc2",
|
||||||
"@tailwindcss/typography": "^0.5.19",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"astro": "^5.17.1",
|
"astro": "^5.17.1",
|
||||||
"marked": "^17.0.3",
|
"marked": "^17.0.3",
|
||||||
|
"react": "^19.2.4",
|
||||||
|
"react-dom": "^19.2.4",
|
||||||
"tailwindcss": "^3.4.0",
|
"tailwindcss": "^3.4.0",
|
||||||
"vue": "^3.5.29"
|
"vue": "^3.5.29"
|
||||||
}
|
}
|
||||||
@@ -119,6 +124,26 @@
|
|||||||
"node": "18.20.8 || ^20.3.0 || >=22.0.0"
|
"node": "18.20.8 || ^20.3.0 || >=22.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@astrojs/react": {
|
||||||
|
"version": "4.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@astrojs/react/-/react-4.4.2.tgz",
|
||||||
|
"integrity": "sha512-1tl95bpGfuaDMDn8O3x/5Dxii1HPvzjvpL2YTuqOOrQehs60I2DKiDgh1jrKc7G8lv+LQT5H15V6QONQ+9waeQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vitejs/plugin-react": "^4.7.0",
|
||||||
|
"ultrahtml": "^1.6.0",
|
||||||
|
"vite": "^6.4.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "18.20.8 || ^20.3.0 || >=22.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^17.0.50 || ^18.0.21 || ^19.0.0",
|
||||||
|
"@types/react-dom": "^17.0.17 || ^18.0.6 || ^19.0.0",
|
||||||
|
"react": "^17.0.2 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@astrojs/tailwind": {
|
"node_modules/@astrojs/tailwind": {
|
||||||
"version": "5.1.5",
|
"version": "5.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@astrojs/tailwind/-/tailwind-5.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@astrojs/tailwind/-/tailwind-5.1.5.tgz",
|
||||||
@@ -537,6 +562,36 @@
|
|||||||
"@babel/core": "^7.0.0-0"
|
"@babel/core": "^7.0.0-0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/plugin-transform-react-jsx-self": {
|
||||||
|
"version": "7.27.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
|
||||||
|
"integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-plugin-utils": "^7.27.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@babel/core": "^7.0.0-0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/plugin-transform-react-jsx-source": {
|
||||||
|
"version": "7.27.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
|
||||||
|
"integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-plugin-utils": "^7.27.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@babel/core": "^7.0.0-0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/plugin-transform-typescript": {
|
"node_modules/@babel/plugin-transform-typescript": {
|
||||||
"version": "7.28.6",
|
"version": "7.28.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz",
|
||||||
@@ -1587,6 +1642,33 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@myriaddreamin/typst-ts-renderer": {
|
||||||
|
"version": "0.7.0-rc2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@myriaddreamin/typst-ts-renderer/-/typst-ts-renderer-0.7.0-rc2.tgz",
|
||||||
|
"integrity": "sha512-god1tcb2YJDkQfA8gLGcAmykVGBpNKorqqDkXVy3InC18KRbsverJhlrHoONurNIU9JuIHoWjJ2D1ntpjPgzbA==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/@myriaddreamin/typst.ts": {
|
||||||
|
"version": "0.7.0-rc2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@myriaddreamin/typst.ts/-/typst.ts-0.7.0-rc2.tgz",
|
||||||
|
"integrity": "sha512-VM8JqsRcL3AEJ5cuPBn/YvnGTXK/BRPlxdGB2bR48Of/8OIGaPiunv2QfZBIMBBrtbTygUOtAY9BZvkS1AFqgA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"idb": "^7.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@myriaddreamin/typst-ts-renderer": "^0.7.0-rc2",
|
||||||
|
"@myriaddreamin/typst-ts-web-compiler": "^0.7.0-rc2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@myriaddreamin/typst-ts-renderer": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@myriaddreamin/typst-ts-web-compiler": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
@@ -2090,6 +2172,47 @@
|
|||||||
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
|
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/babel__core": {
|
||||||
|
"version": "7.20.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||||
|
"integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/parser": "^7.20.7",
|
||||||
|
"@babel/types": "^7.20.7",
|
||||||
|
"@types/babel__generator": "*",
|
||||||
|
"@types/babel__template": "*",
|
||||||
|
"@types/babel__traverse": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/babel__generator": {
|
||||||
|
"version": "7.27.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
|
||||||
|
"integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/types": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/babel__template": {
|
||||||
|
"version": "7.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
|
||||||
|
"integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/parser": "^7.1.0",
|
||||||
|
"@babel/types": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/babel__traverse": {
|
||||||
|
"version": "7.28.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
|
||||||
|
"integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/types": "^7.28.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/debug": {
|
"node_modules/@types/debug": {
|
||||||
"version": "4.1.12",
|
"version": "4.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
|
||||||
@@ -2153,6 +2276,26 @@
|
|||||||
"@types/unist": "*"
|
"@types/unist": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/react": {
|
||||||
|
"version": "19.2.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
||||||
|
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"csstype": "^3.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/react-dom": {
|
||||||
|
"version": "19.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
||||||
|
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^19.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/unist": {
|
"node_modules/@types/unist": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
|
||||||
@@ -2165,6 +2308,32 @@
|
|||||||
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
|
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/@vitejs/plugin-react": {
|
||||||
|
"version": "4.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
|
||||||
|
"integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/core": "^7.28.0",
|
||||||
|
"@babel/plugin-transform-react-jsx-self": "^7.27.1",
|
||||||
|
"@babel/plugin-transform-react-jsx-source": "^7.27.1",
|
||||||
|
"@rolldown/pluginutils": "1.0.0-beta.27",
|
||||||
|
"@types/babel__core": "^7.20.5",
|
||||||
|
"react-refresh": "^0.17.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.18.0 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitejs/plugin-react/node_modules/@rolldown/pluginutils": {
|
||||||
|
"version": "1.0.0-beta.27",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
|
||||||
|
"integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@vitejs/plugin-vue": {
|
"node_modules/@vitejs/plugin-vue": {
|
||||||
"version": "5.2.4",
|
"version": "5.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
|
||||||
@@ -4240,6 +4409,12 @@
|
|||||||
"node": ">=18.18.0"
|
"node": ">=18.18.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/idb": {
|
||||||
|
"version": "7.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz",
|
||||||
|
"integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/import-meta-resolve": {
|
"node_modules/import-meta-resolve": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz",
|
||||||
@@ -6346,6 +6521,36 @@
|
|||||||
"integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==",
|
"integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/react": {
|
||||||
|
"version": "19.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
||||||
|
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-dom": {
|
||||||
|
"version": "19.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
|
||||||
|
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"scheduler": "^0.27.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^19.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-refresh": {
|
||||||
|
"version": "0.17.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
||||||
|
"integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/read-cache": {
|
"node_modules/read-cache": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||||
@@ -6826,6 +7031,12 @@
|
|||||||
"node": ">=11.0.0"
|
"node": ">=11.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/scheduler": {
|
||||||
|
"version": "0.27.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
||||||
|
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "6.3.1",
|
"version": "6.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||||
|
|||||||
@@ -10,11 +10,15 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/mdx": "^4.3.13",
|
"@astrojs/mdx": "^4.3.13",
|
||||||
|
"@astrojs/react": "^4.4.2",
|
||||||
"@astrojs/tailwind": "^5.1.5",
|
"@astrojs/tailwind": "^5.1.5",
|
||||||
"@astrojs/vue": "^5.1.4",
|
"@astrojs/vue": "^5.1.4",
|
||||||
|
|
||||||
"@tailwindcss/typography": "^0.5.19",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"astro": "^5.17.1",
|
"astro": "^5.17.1",
|
||||||
"marked": "^17.0.3",
|
"marked": "^17.0.3",
|
||||||
|
"react": "^19.2.4",
|
||||||
|
"react-dom": "^19.2.4",
|
||||||
"tailwindcss": "^3.4.0",
|
"tailwindcss": "^3.4.0",
|
||||||
"vue": "^3.5.29"
|
"vue": "^3.5.29"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
---
|
|
||||||
/**
|
|
||||||
* TypstBlock 组件
|
|
||||||
*
|
|
||||||
* 用于在 MDX 文章中渲染 Typst 数学公式和复杂排版。
|
|
||||||
*
|
|
||||||
* 使用方式:
|
|
||||||
* <TypstBlock>
|
|
||||||
* $ integral_0^infinity e^(-x^2) dif x = sqrt(pi) / 2 $
|
|
||||||
* </TypstBlock>
|
|
||||||
*
|
|
||||||
* 注意:此组件需要在构建时安装 Typst 编译器。
|
|
||||||
* 如果 Typst 未安装,会显示原始代码块作为降级方案。
|
|
||||||
*/
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
class?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { class: className = '' } = Astro.props;
|
|
||||||
|
|
||||||
// 获取子内容(Typst 代码)
|
|
||||||
const content = await Astro.slots.render('default');
|
|
||||||
const typstCode = content?.trim() || '';
|
|
||||||
---
|
|
||||||
|
|
||||||
<div class:list={['typst-block', 'my-6', className]}>
|
|
||||||
{/*
|
|
||||||
Typst 渲染区域
|
|
||||||
在实际实现中,这里会调用 Typst 编译器将代码渲染为 SVG
|
|
||||||
目前作为占位符显示
|
|
||||||
*/}
|
|
||||||
<div class="p-4 bg-muted rounded-lg border border-border overflow-x-auto">
|
|
||||||
{typstCode ? (
|
|
||||||
<div class="flex flex-col items-center">
|
|
||||||
{/* SVG 输出区域 (构建时会被替换为实际的 Typst 渲染结果) */}
|
|
||||||
<div class="typst-output text-lg" data-typst-code={typstCode}>
|
|
||||||
<code class="font-mono text-primary-600 dark:text-primary-400">
|
|
||||||
{typstCode}
|
|
||||||
</code>
|
|
||||||
</div>
|
|
||||||
<span class="text-xs text-foreground/40 mt-2">Typst 公式</span>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<p class="text-foreground/40 text-center">请提供 Typst 代码</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.typst-block {
|
|
||||||
/* 确保 Typst 内容居中显示 */
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.typst-output {
|
|
||||||
/* 为数学公式提供合适的样式 */
|
|
||||||
font-family: 'Latin Modern Math', 'STIX Two Math', 'Noto Serif SC', serif;
|
|
||||||
line-height: 1.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.typst-output svg {
|
|
||||||
/* SVG 输出样式 */
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
47
src/components/react/AnimatedCard.tsx
Normal file
47
src/components/react/AnimatedCard.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
interface AnimatedCardProps {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
color?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AnimatedCard({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
color = '#3b82f6'
|
||||||
|
}: AnimatedCardProps) {
|
||||||
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
|
|
||||||
|
const cardStyle: React.CSSProperties = {
|
||||||
|
padding: '1.5rem',
|
||||||
|
background: isHovered ? color : `${color}dd`,
|
||||||
|
borderRadius: '1rem',
|
||||||
|
color: 'white',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transform: isHovered ? 'translateY(-8px) scale(1.02)' : 'translateY(0) scale(1)',
|
||||||
|
boxShadow: isHovered
|
||||||
|
? '0 20px 40px rgba(0, 0, 0, 0.3)'
|
||||||
|
: '0 4px 6px rgba(0, 0, 0, 0.1)',
|
||||||
|
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={cardStyle}
|
||||||
|
onMouseEnter={() => setIsHovered(true)}
|
||||||
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
|
>
|
||||||
|
<h3 style={{
|
||||||
|
margin: '0 0 0.5rem 0',
|
||||||
|
fontSize: '1.25rem',
|
||||||
|
fontWeight: 'bold'
|
||||||
|
}}>
|
||||||
|
{title}
|
||||||
|
</h3>
|
||||||
|
<p style={{ margin: 0, opacity: 0.9 }}>
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
78
src/components/react/FlipCard.tsx
Normal file
78
src/components/react/FlipCard.tsx
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
interface FlipCardProps {
|
||||||
|
frontTitle: string;
|
||||||
|
frontDescription: string;
|
||||||
|
backTitle: string;
|
||||||
|
backDescription: string;
|
||||||
|
frontColor?: string;
|
||||||
|
backColor?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FlipCard({
|
||||||
|
frontTitle,
|
||||||
|
frontDescription,
|
||||||
|
backTitle,
|
||||||
|
backDescription,
|
||||||
|
frontColor = '#3b82f6',
|
||||||
|
backColor = '#10b981'
|
||||||
|
}: FlipCardProps) {
|
||||||
|
const [isFlipped, setIsFlipped] = useState(false);
|
||||||
|
|
||||||
|
const containerStyle: React.CSSProperties = {
|
||||||
|
perspective: '1000px',
|
||||||
|
width: '100%',
|
||||||
|
height: '200px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
};
|
||||||
|
|
||||||
|
const innerStyle: React.CSSProperties = {
|
||||||
|
position: 'relative',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
transformStyle: 'preserve-3d',
|
||||||
|
transition: 'transform 0.6s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
transform: isFlipped ? 'rotateY(180deg)' : 'rotateY(0deg)',
|
||||||
|
};
|
||||||
|
|
||||||
|
const faceStyle = (color: string, isFront: boolean): React.CSSProperties => ({
|
||||||
|
position: 'absolute',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
backfaceVisibility: 'hidden',
|
||||||
|
borderRadius: '1rem',
|
||||||
|
padding: '1.5rem',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
background: color,
|
||||||
|
color: 'white',
|
||||||
|
transform: isFront ? 'rotateY(0deg)' : 'rotateY(180deg)',
|
||||||
|
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={containerStyle}
|
||||||
|
onClick={() => setIsFlipped(!isFlipped)}
|
||||||
|
onMouseEnter={(e) => e.currentTarget.style.transform = 'scale(1.02)'}
|
||||||
|
onMouseLeave={(e) => e.currentTarget.style.transform = 'scale(1)'}
|
||||||
|
>
|
||||||
|
<div style={innerStyle}>
|
||||||
|
<div style={faceStyle(frontColor, true)}>
|
||||||
|
<div style={{ textAlign: 'center' }}>
|
||||||
|
<h3 style={{ margin: '0 0 0.5rem 0', fontSize: '1.25rem', fontWeight: 'bold' }}>{frontTitle}</h3>
|
||||||
|
<p style={{ margin: 0, opacity: 0.9 }}>{frontDescription}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={faceStyle(backColor, false)}>
|
||||||
|
<div style={{ textAlign: 'center' }}>
|
||||||
|
<h3 style={{ margin: '0 0 0.5rem 0', fontSize: '1.25rem', fontWeight: 'bold' }}>{backTitle}</h3>
|
||||||
|
<p style={{ margin: 0, opacity: 0.9 }}>{backDescription}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
159
src/components/react/ParticleBackground.tsx
Normal file
159
src/components/react/ParticleBackground.tsx
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
interface Particle {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
vx: number;
|
||||||
|
vy: number;
|
||||||
|
size: number;
|
||||||
|
opacity: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ParticleBackgroundProps {
|
||||||
|
particleCount?: number;
|
||||||
|
color?: string;
|
||||||
|
speed?: number;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ParticleBackground({
|
||||||
|
particleCount = 50,
|
||||||
|
color = '#3b82f6',
|
||||||
|
speed = 1,
|
||||||
|
children
|
||||||
|
}: ParticleBackgroundProps) {
|
||||||
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
const particlesRef = useRef<Particle[]>([]);
|
||||||
|
const animationRef = useRef<number | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
if (!canvas) return;
|
||||||
|
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (!ctx) return;
|
||||||
|
|
||||||
|
// 设置画布大小
|
||||||
|
const resizeCanvas = () => {
|
||||||
|
const rect = canvas.parentElement?.getBoundingClientRect();
|
||||||
|
if (rect) {
|
||||||
|
canvas.width = rect.width;
|
||||||
|
canvas.height = rect.height;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
resizeCanvas();
|
||||||
|
window.addEventListener('resize', resizeCanvas);
|
||||||
|
|
||||||
|
// 初始化粒子
|
||||||
|
const initParticles = () => {
|
||||||
|
const newParticles: Particle[] = [];
|
||||||
|
for (let i = 0; i < particleCount; i++) {
|
||||||
|
newParticles.push({
|
||||||
|
x: Math.random() * canvas.width,
|
||||||
|
y: Math.random() * canvas.height,
|
||||||
|
vx: (Math.random() - 0.5) * speed,
|
||||||
|
vy: (Math.random() - 0.5) * speed,
|
||||||
|
size: Math.random() * 3 + 1,
|
||||||
|
opacity: Math.random() * 0.5 + 0.2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
particlesRef.current = newParticles;
|
||||||
|
};
|
||||||
|
|
||||||
|
initParticles();
|
||||||
|
|
||||||
|
// 动画循环
|
||||||
|
const animate = () => {
|
||||||
|
if (!ctx || !canvas) return;
|
||||||
|
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
const particles = particlesRef.current;
|
||||||
|
|
||||||
|
particles.forEach((particle: Particle, i: number) => {
|
||||||
|
// 更新位置
|
||||||
|
particle.x += particle.vx;
|
||||||
|
particle.y += particle.vy;
|
||||||
|
|
||||||
|
// 边界检测
|
||||||
|
if (particle.x < 0 || particle.x > canvas.width) particle.vx *= -1;
|
||||||
|
if (particle.y < 0 || particle.y > canvas.height) particle.vy *= -1;
|
||||||
|
|
||||||
|
// 绘制粒子
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
|
||||||
|
ctx.fillStyle = `${color}${Math.floor(particle.opacity * 255).toString(16).padStart(2, '0')}`;
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// 绘制连线
|
||||||
|
particles.slice(i + 1).forEach((other: Particle) => {
|
||||||
|
const dx = particle.x - other.x;
|
||||||
|
const dy = particle.y - other.y;
|
||||||
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
if (distance < 150) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(particle.x, particle.y);
|
||||||
|
ctx.lineTo(other.x, other.y);
|
||||||
|
const opacity = (1 - distance / 150) * 0.2;
|
||||||
|
ctx.strokeStyle = `${color}${Math.floor(opacity * 255).toString(16).padStart(2, '0')}`;
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
animationRef.current = requestAnimationFrame(animate);
|
||||||
|
};
|
||||||
|
|
||||||
|
animate();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', resizeCanvas);
|
||||||
|
if (animationRef.current) {
|
||||||
|
cancelAnimationFrame(animationRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [color, speed, particleCount]);
|
||||||
|
|
||||||
|
const containerStyle: React.CSSProperties = {
|
||||||
|
position: 'relative',
|
||||||
|
width: '100%',
|
||||||
|
height: '300px',
|
||||||
|
background: 'linear-gradient(135deg, #1a1a2e 0%, #16213e 100%)',
|
||||||
|
borderRadius: '1rem',
|
||||||
|
overflow: 'hidden',
|
||||||
|
};
|
||||||
|
|
||||||
|
const canvasStyle: React.CSSProperties = {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
};
|
||||||
|
|
||||||
|
const contentStyle: React.CSSProperties = {
|
||||||
|
position: 'relative',
|
||||||
|
zIndex: 1,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
height: '100%',
|
||||||
|
color: 'white',
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={containerStyle}>
|
||||||
|
<canvas ref={canvasRef} style={canvasStyle} />
|
||||||
|
<div style={contentStyle}>
|
||||||
|
{children || (
|
||||||
|
<div style={{ textAlign: 'center' }}>
|
||||||
|
<h3 style={{ fontSize: '2rem', marginBottom: '0.5rem' }}>✨ 粒子动效</h3>
|
||||||
|
<p style={{ opacity: 0.8 }}>鼠标悬停查看效果</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
76
src/components/react/TypewriterText.tsx
Normal file
76
src/components/react/TypewriterText.tsx
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
interface TypewriterTextProps {
|
||||||
|
text: string;
|
||||||
|
speed?: number;
|
||||||
|
loop?: boolean;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TypewriterText({
|
||||||
|
text,
|
||||||
|
speed = 100,
|
||||||
|
loop = false,
|
||||||
|
style = {}
|
||||||
|
}: TypewriterTextProps) {
|
||||||
|
const [displayedText, setDisplayedText] = useState('');
|
||||||
|
const [currentIndex, setCurrentIndex] = useState(0);
|
||||||
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
if (!isDeleting) {
|
||||||
|
// 打字
|
||||||
|
if (currentIndex < text.length) {
|
||||||
|
setDisplayedText(text.slice(0, currentIndex + 1));
|
||||||
|
setCurrentIndex(currentIndex + 1);
|
||||||
|
} else if (loop) {
|
||||||
|
// 打完后等待,然后开始删除
|
||||||
|
setTimeout(() => setIsDeleting(true), 1500);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 删除
|
||||||
|
if (currentIndex > 0) {
|
||||||
|
setDisplayedText(text.slice(0, currentIndex - 1));
|
||||||
|
setCurrentIndex(currentIndex - 1);
|
||||||
|
} else {
|
||||||
|
setIsDeleting(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, isDeleting ? speed / 2 : speed);
|
||||||
|
|
||||||
|
return () => clearTimeout(timeout);
|
||||||
|
}, [currentIndex, isDeleting, text, speed, loop]);
|
||||||
|
|
||||||
|
const containerStyle: React.CSSProperties = {
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
fontSize: '1.5rem',
|
||||||
|
color: '#3b82f6',
|
||||||
|
...style
|
||||||
|
};
|
||||||
|
|
||||||
|
const cursorStyle: React.CSSProperties = {
|
||||||
|
display: 'inline-block',
|
||||||
|
width: '3px',
|
||||||
|
height: '1.5rem',
|
||||||
|
background: '#3b82f6',
|
||||||
|
marginLeft: '2px',
|
||||||
|
animation: 'blink 1s infinite',
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<style>{`
|
||||||
|
@keyframes blink {
|
||||||
|
0%, 50% { opacity: 1; }
|
||||||
|
51%, 100% { opacity: 0; }
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
<span style={containerStyle}>
|
||||||
|
{displayedText}
|
||||||
|
<span style={cursorStyle} />
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -28,13 +28,13 @@ NovaBlog 是一个极简、高效的程序员博客系统,采用 **静态渲
|
|||||||
|
|
||||||
上面的组件会在可见时自动加载并挂载 JavaScript。
|
上面的组件会在可见时自动加载并挂载 JavaScript。
|
||||||
|
|
||||||
### 📐 Typst 学术排版
|
### 📐 数学公式排版
|
||||||
|
|
||||||
支持复杂的数学公式渲染:
|
支持复杂的数学公式渲染:
|
||||||
|
|
||||||
<TypstBlock>
|
```
|
||||||
$ integral_0^infinity e^(-x^2) dif x = sqrt(pi) / 2 $
|
$ integral_0^infinity e^(-x^2) dif x = sqrt(pi) / 2 $
|
||||||
</TypstBlock>
|
```
|
||||||
|
|
||||||
## 代码高亮
|
## 代码高亮
|
||||||
|
|
||||||
|
|||||||
212
src/content/blog/react-animated-components.mdx
Normal file
212
src/content/blog/react-animated-components.mdx
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
---
|
||||||
|
title: React 动效组件展示
|
||||||
|
description: 展示 NovaBlog 中可用的 React 动效 HTML 组件,包括悬浮卡片、打字机效果、翻转卡片和粒子背景
|
||||||
|
pubDate: 2024-01-20
|
||||||
|
author: NovaBlog
|
||||||
|
tags: [React, 动效, 组件, 教程]
|
||||||
|
category: 教程
|
||||||
|
heroImage: /images/react-components.jpg
|
||||||
|
---
|
||||||
|
|
||||||
|
import AnimatedCard from '../../components/react/AnimatedCard';
|
||||||
|
import TypewriterText from '../../components/react/TypewriterText';
|
||||||
|
import FlipCard from '../../components/react/FlipCard';
|
||||||
|
import ParticleBackground from '../../components/react/ParticleBackground';
|
||||||
|
|
||||||
|
# React 动效组件展示
|
||||||
|
|
||||||
|
NovaBlog 支持在 MDX 中直接使用 React 组件,实现丰富的交互动效。本文展示了一些内置的动效组件示例。
|
||||||
|
|
||||||
|
## 🎴 悬浮卡片 (AnimatedCard)
|
||||||
|
|
||||||
|
鼠标悬停时卡片会浮起并放大,配合阴影效果增强立体感。
|
||||||
|
|
||||||
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))', gap: '1.5rem', margin: '2rem 0' }}>
|
||||||
|
<AnimatedCard
|
||||||
|
title="🚀 快速开发"
|
||||||
|
description="使用 Astro + React 实现极速开发体验"
|
||||||
|
color="#3b82f6"
|
||||||
|
client:load
|
||||||
|
/>
|
||||||
|
<AnimatedCard
|
||||||
|
title="🎨 精美设计"
|
||||||
|
description="内置多种动效组件,轻松创建炫酷页面"
|
||||||
|
color="#10b981"
|
||||||
|
client:load
|
||||||
|
/>
|
||||||
|
<AnimatedCard
|
||||||
|
title="⚡ 高性能"
|
||||||
|
description="Islands 架构,按需加载,极致性能"
|
||||||
|
color="#f59e0b"
|
||||||
|
client:load
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## ⌨️ 打字机效果 (TypewriterText)
|
||||||
|
|
||||||
|
模拟打字机的逐字显示效果,支持循环播放。
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
background: '#1a1a2e',
|
||||||
|
padding: '2rem',
|
||||||
|
borderRadius: '1rem',
|
||||||
|
textAlign: 'center',
|
||||||
|
margin: '2rem 0'
|
||||||
|
}}>
|
||||||
|
<TypewriterText
|
||||||
|
text="Hello, NovaBlog! 欢迎来到你的新博客..."
|
||||||
|
speed={80}
|
||||||
|
loop={true}
|
||||||
|
client:load
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### 使用方式
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<TypewriterText
|
||||||
|
text="你要显示的文字"
|
||||||
|
speed={100} // 打字速度(毫秒)
|
||||||
|
loop={true} // 是否循环播放
|
||||||
|
client:load
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 翻转卡片 (FlipCard)
|
||||||
|
|
||||||
|
点击卡片实现 3D 翻转效果,适合展示正反两面内容。
|
||||||
|
|
||||||
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: '1.5rem', margin: '2rem 0' }}>
|
||||||
|
<FlipCard
|
||||||
|
frontTitle="💡 小提示"
|
||||||
|
frontDescription="点击翻转查看更多"
|
||||||
|
backTitle="✅ 详细说明"
|
||||||
|
backDescription="这是一个翻转卡片的背面内容"
|
||||||
|
frontColor="#8b5cf6"
|
||||||
|
backColor="#06b6d4"
|
||||||
|
client:load
|
||||||
|
/>
|
||||||
|
<FlipCard
|
||||||
|
frontTitle="🎯 技术栈"
|
||||||
|
frontDescription="React + TypeScript"
|
||||||
|
backTitle="📦 组件库"
|
||||||
|
backDescription="支持自定义样式和动画"
|
||||||
|
frontColor="#ec4899"
|
||||||
|
backColor="#14b8a6"
|
||||||
|
client:load
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### 使用方式
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<FlipCard
|
||||||
|
frontContent={<div>正面内容</div>}
|
||||||
|
backContent={<div>背面内容</div>}
|
||||||
|
frontColor="#3b82f6"
|
||||||
|
backColor="#10b981"
|
||||||
|
client:load
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
## ✨ 粒子背景 (ParticleBackground)
|
||||||
|
|
||||||
|
基于 Canvas 的粒子动画背景,粒子之间会自动连线,营造科技感。
|
||||||
|
|
||||||
|
<ParticleBackground
|
||||||
|
particleCount={60}
|
||||||
|
color="#60a5fa"
|
||||||
|
speed={0.8}
|
||||||
|
client:load
|
||||||
|
/>
|
||||||
|
|
||||||
|
### 自定义内容
|
||||||
|
|
||||||
|
<ParticleBackground
|
||||||
|
particleCount={30}
|
||||||
|
color="#f472b6"
|
||||||
|
speed={1.5}
|
||||||
|
client:load
|
||||||
|
>
|
||||||
|
<div style={{ textAlign: 'center' }}>
|
||||||
|
<h2 style={{ fontSize: '2.5rem', marginBottom: '1rem' }}>🎉 自定义内容</h2>
|
||||||
|
<p>可以在粒子背景上放置任意内容</p>
|
||||||
|
</div>
|
||||||
|
</ParticleBackground>
|
||||||
|
|
||||||
|
## 📝 如何在文章中使用
|
||||||
|
|
||||||
|
### 1. 导入组件
|
||||||
|
|
||||||
|
在文章顶部添加 import 语句:
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
import AnimatedCard from '../../components/react/AnimatedCard';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 使用组件
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
<AnimatedCard
|
||||||
|
title="标题"
|
||||||
|
description="描述"
|
||||||
|
color="#3b82f6"
|
||||||
|
client:load
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. client 指令说明
|
||||||
|
|
||||||
|
| 指令 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `client:load` | 页面加载时立即激活组件 |
|
||||||
|
| `client:visible` | 组件进入视口时激活 |
|
||||||
|
| `client:idle` | 浏览器空闲时激活 |
|
||||||
|
| `client:media="(min-width: 768px)"` | 满足媒体查询时激活 |
|
||||||
|
|
||||||
|
## 🎨 创建自定义组件
|
||||||
|
|
||||||
|
你可以在 `src/components/react/` 目录下创建自己的 React 组件:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// src/components/react/MyComponent.tsx
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
interface MyComponentProps {
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MyComponent({ title }: MyComponentProps) {
|
||||||
|
const [count, setCount] = useState(0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ padding: '1rem', background: '#f3f4f6' }}>
|
||||||
|
<h3>{title}</h3>
|
||||||
|
<button onClick={() => setCount(count + 1)}>
|
||||||
|
点击次数: {count}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
然后在文章中使用:
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
import MyComponent from '../../components/react/MyComponent';
|
||||||
|
|
||||||
|
<MyComponent title="我的组件" client:load />
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
NovaBlog 提供了灵活的组件系统,让你可以在 Markdown 中嵌入丰富的交互内容。通过 React 组件,你可以实现:
|
||||||
|
|
||||||
|
- 🎴 **视觉效果**:悬浮、翻转、渐变等动画
|
||||||
|
- ⌨️ **动态文字**:打字机、滚动、闪烁效果
|
||||||
|
- ✨ **背景特效**:粒子、波浪、光效
|
||||||
|
- 🎮 **交互功能**:计数器、表单、游戏
|
||||||
|
|
||||||
|
快去尝试创建属于你自己的动效组件吧! 🚀
|
||||||
202
src/content/blog/typst-typesetting-showcase.mdx
Normal file
202
src/content/blog/typst-typesetting-showcase.mdx
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
---
|
||||||
|
title: Typst 学术排版展示
|
||||||
|
description: 展示 NovaBlog 中 Typst 的高级排版能力,包括数学公式、矩阵等学术排版
|
||||||
|
pubDate: 2024-01-25
|
||||||
|
author: NovaBlog
|
||||||
|
tags: [Typst, 排版,数学公式,学术写作]
|
||||||
|
category: 教程
|
||||||
|
heroImage: /images/hello-world.jpg
|
||||||
|
---
|
||||||
|
|
||||||
|
# Typst 学术排版展示
|
||||||
|
|
||||||
|
Typst 是一款现代化的排版系统,专为学术写作和技术文档设计。本文将展示 NovaBlog 中 Typst 的数学公式排版能力。
|
||||||
|
|
||||||
|
## 📐 基础数学公式
|
||||||
|
|
||||||
|
### 积分公式
|
||||||
|
|
||||||
|
```
|
||||||
|
$ integral_0^infinity e^(-x^2) dif x = sqrt(pi) / 2 $
|
||||||
|
```
|
||||||
|
|
||||||
|
### 极限与导数
|
||||||
|
|
||||||
|
```
|
||||||
|
$ lim_(x arrow 0) frac(sin x, x) = 1 $
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ frac(dif f, dif x) = lim_(h arrow 0) frac(f(x + h) - f(x), h) $
|
||||||
|
```
|
||||||
|
|
||||||
|
### 微积分基本定理
|
||||||
|
|
||||||
|
```
|
||||||
|
$ integral_a^b f(x) dif x = F(b) - F(a) $
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔢 矩阵与线性代数
|
||||||
|
|
||||||
|
### 基础矩阵
|
||||||
|
|
||||||
|
```
|
||||||
|
$ A = mat(1, 2, 3; 4, 5, 6; 7, 8, 9) $
|
||||||
|
```
|
||||||
|
|
||||||
|
### 行列式展开
|
||||||
|
|
||||||
|
```
|
||||||
|
$ det(A) = sum_(i=1)^n a_(1i) dot (-1)^(1+i) dot M_(1i) $
|
||||||
|
```
|
||||||
|
|
||||||
|
### 特征值方程
|
||||||
|
|
||||||
|
```
|
||||||
|
$ det(A - lambda I) = 0 $
|
||||||
|
```
|
||||||
|
|
||||||
|
### 二次型
|
||||||
|
|
||||||
|
```
|
||||||
|
$ Q(x) = x^T A x = sum_(i,j) a_(ij) x_i x_j $
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 统计学与概率论
|
||||||
|
|
||||||
|
### 贝叶斯定理
|
||||||
|
|
||||||
|
```
|
||||||
|
$ P(A | B) = frac(P(B | A) dot P(A), P(B)) $
|
||||||
|
```
|
||||||
|
|
||||||
|
### 正态分布
|
||||||
|
|
||||||
|
```
|
||||||
|
$ X tilde N(mu, sigma^2) arrow.f P(x) = frac(1, sigma sqrt(2 pi)) e^(-frac((x-mu)^2, 2 sigma^2)) $
|
||||||
|
```
|
||||||
|
|
||||||
|
### 期望与方差
|
||||||
|
|
||||||
|
```
|
||||||
|
$ E[X] = sum_(i=1)^n x_i p_i quad Var(X) = E[X^2] - (E[X])^2 $
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧮 复杂嵌套表达式
|
||||||
|
|
||||||
|
### 巴塞尔问题
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sum_(n=1)^infinity frac(1, n^2) = frac(pi^2, 6) $
|
||||||
|
```
|
||||||
|
|
||||||
|
### 欧拉恒等式
|
||||||
|
|
||||||
|
```
|
||||||
|
$ e^(i pi) + 1 = 0 $
|
||||||
|
```
|
||||||
|
|
||||||
|
### Gamma 函数
|
||||||
|
|
||||||
|
```
|
||||||
|
$ Gamma(z) = integral_0^infinity t^(z-1) e^(-t) dif t $
|
||||||
|
```
|
||||||
|
|
||||||
|
### 斯特林公式
|
||||||
|
|
||||||
|
```
|
||||||
|
$ n! tilde sqrt(2 pi n) (n/e)^n $
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚡ 物理学公式
|
||||||
|
|
||||||
|
### 麦克斯韦方程组
|
||||||
|
|
||||||
|
```
|
||||||
|
$ nabla dot E = frac(rho, epsilon_0) $
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ nabla dot B = 0 $
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ nabla times E = -frac(partial B, partial t) $
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ nabla times B = mu_0 J + mu_0 epsilon_0 frac(partial E, partial t) $
|
||||||
|
```
|
||||||
|
|
||||||
|
### 狭义相对论
|
||||||
|
|
||||||
|
```
|
||||||
|
$ E = m c^2 $
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ t' = frac(t, sqrt(1 - v^2/c^2)) = gamma t $
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ gamma = frac(1, sqrt(1 - v^2/c^2)) $
|
||||||
|
```
|
||||||
|
|
||||||
|
### 薛定谔方程
|
||||||
|
|
||||||
|
```
|
||||||
|
$ i hbar frac(partial psi, partial t) = H^ psi $
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔬 化学方程式
|
||||||
|
|
||||||
|
```
|
||||||
|
$ 6 CO_2 + 6 H_2 O arrow.r C_6 H_12 O_6 + 6 O_2 $
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ CH_4 + 2 O_2 arrow.r CO_2 + 2 H_2 O $
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📐 集合论
|
||||||
|
|
||||||
|
```
|
||||||
|
$ A union B = { x | x in A text( 或 ) x in B } $
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ A intersect B = { x | x in A text( 且 ) x in B } $
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ A setminus B = { x | x in A text( 且 ) x notin B } $
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ P(A) = { S | S subset.eq A } $
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
NovaBlog 曾经通过 TypstBlock 组件支持专业的数学公式排版,适合:
|
||||||
|
|
||||||
|
- 📐 **数学博客**:微积分、线性代数、概率统计
|
||||||
|
- ⚡ **物理笔记**:经典力学、电磁学、量子力学
|
||||||
|
- 🔬 **化学公式**:化学反应方程式
|
||||||
|
- 📊 **学术论文**:复杂的数学推导和证明
|
||||||
|
|
||||||
|
由于技术原因,Typst 支持已暂时移除。
|
||||||
@@ -2,12 +2,10 @@
|
|||||||
import { getCollection, type CollectionEntry } from 'astro:content';
|
import { getCollection, type CollectionEntry } from 'astro:content';
|
||||||
import PostLayout from '../../layouts/PostLayout.astro';
|
import PostLayout from '../../layouts/PostLayout.astro';
|
||||||
import Counter from '../../components/Counter.vue';
|
import Counter from '../../components/Counter.vue';
|
||||||
import TypstBlock from '../../components/TypstBlock.astro';
|
|
||||||
|
|
||||||
// MDX 组件映射
|
// MDX 组件映射
|
||||||
const mdxComponents = {
|
const mdxComponents = {
|
||||||
Counter,
|
Counter,
|
||||||
TypstBlock,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 生成所有文章的静态路径
|
// 生成所有文章的静态路径
|
||||||
|
|||||||
Reference in New Issue
Block a user