Skip to content

Commit 021833e

Browse files
committed
refactor: 环境变量化站点配置
1 parent b3e117c commit 021833e

File tree

12 files changed

+269
-131
lines changed

12 files changed

+269
-131
lines changed

.env.example

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,25 @@ IMAGE_MODEL=Doubao-Seedream-4.5
1515
SERVER_HOST=your_server_ip_or_domain
1616
SERVER_USER=root
1717
SERVER_PATH=/opt/1panel/apps/openresty/openresty/www/sites/serenade/index
18+
19+
# Site Configuration
20+
SITE_AUTHOR=染念
21+
SITE_TITLE=染念的笔记
22+
SITE_DESCRIPTION=Writing code, painful and happy
23+
SITE_KEYWORDS=染念,染念的笔记,染念の笔记,染念的博客,博客,blog
24+
SITE_URL=https://dyedd.cn
25+
SITE_EMAIL=1176996982@qq.com
26+
SITE_LANG=zh-CN
27+
SITE_START_TIME=2017-02-11
28+
SITE_ANALYTICS_SCRIPT=https://statistics.dyedd.cn/script.js
29+
SITE_ANALYTICS_WEBSITE_ID=11a02a3f-0cdd-452a-bbb8-37f195db86fd
30+
SITE_PROFILE_AVATAR=/logo.jpg
31+
SITE_PROFILE_BADGE=🐟
32+
SITE_PROFILE_INTRO=AI infra研究生,目前研究大规模分布式训练以及扩散模型在短临降水领域的应用|过去我也学习过前后端,所以现在也是不专业的全栈开发者
33+
SITE_PROFILE_MOTTO=🐎 马到成功,心想事成|🌈🌈🌈
34+
SITE_PROFILE_TECH_STACK=Python::https://img.shields.io/badge/-Python-3776AB?logo=python&logoColor=white&style=flat-square|CUDA::https://img.shields.io/badge/-CUDA-76B900?logo=nvidia&logoColor=white&style=flat-square
35+
SITE_PROFILE_STATEMENT=我将在这里分享我的编程和人工智能知识。如果你对这些主题感兴趣,那么恭喜你找到宝藏了。接下来你可以查看内容或订阅
36+
SITE_PROFILE_GITHUB_CHART=https://ghchart.rshah.org/409ba5/dyedd
37+
SITE_GITHUB_URL=https://github.com/dyedd
38+
SITE_QQ_URL=https://qm.qq.com/cgi-bin/qm/qr?k=nLIdzy8UC9VkZ0g2EwnoN1rwnxaYvFx0&jump_from=webapi&authKey=mq2RvfcTQxEgImX+XZv0tBeobeHX+wTaAxOXq7pEKdsUD+a2Hi7mIOBGEj2ZtSDJ
39+
SITE_FOOTER_ICP_LABEL=备案号:浙ICP备19020194号-1

README.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ docker compose pull && docker compose up -d
7878

7979
- 默认监听 `3000`,可用 `SERENADE_PORT=8080 docker compose up -d` 映射到其它端口
8080
- `docker-compose.yml` 把宿主机 `./content` 挂载进容器,内容更新后刷新即可生效
81+
- 站点信息通过 `.env` 中的 `SITE_*` 环境变量配置,不再把个人站点配置写死到预构建镜像
8182

8283
#### 方式 B:本地构建镜像
8384

@@ -146,7 +147,42 @@ AI 与同步相关配置见 `.env.example`:
146147

147148
## 站点配置
148149

149-
编辑 `site.config.ts`
150+
站点信息通过 `.env` 中的 `SITE_*` 环境变量覆盖。
151+
152+
- 没有 `.env` 时,直接使用 [nuxt.config.ts](./nuxt.config.ts) 里的内置默认值
153+
- `.env.example` 只是示例,不会被自动加载;需要复制为 `.env` 才会生效
154+
155+
```env
156+
SITE_TITLE=染念的笔记
157+
SITE_AUTHOR=染念
158+
SITE_URL=https://dyedd.cn
159+
SITE_EMAIL=1176996982@qq.com
160+
SITE_PROFILE_AVATAR=/logo.jpg
161+
SITE_PROFILE_INTRO=第一句|第二句
162+
SITE_PROFILE_MOTTO=第一句|第二句
163+
SITE_PROFILE_TECH_STACK=Python::https://img.shields.io/...|Vue::https://img.shields.io/...
164+
```
165+
166+
常用可配置项:
167+
168+
- 站点基础信息:`SITE_TITLE``SITE_AUTHOR``SITE_DESCRIPTION``SITE_KEYWORDS``SITE_URL``SITE_EMAIL``SITE_LANG``SITE_START_TIME`
169+
- 个人资料:`SITE_PROFILE_AVATAR``SITE_PROFILE_BADGE``SITE_PROFILE_INTRO``SITE_PROFILE_MOTTO``SITE_PROFILE_STATEMENT``SITE_PROFILE_GITHUB_CHART``SITE_PROFILE_TECH_STACK`
170+
- 社交与统计:`SITE_GITHUB_URL``SITE_QQ_URL``SITE_ANALYTICS_SCRIPT``SITE_ANALYTICS_WEBSITE_ID`
171+
- 备案信息:`SITE_FOOTER_ICP_LABEL`
172+
173+
其中一些值会自动复用,不需要重复配置:
174+
175+
- `profile.name` 复用 `SITE_AUTHOR`
176+
- 邮箱链接自动由 `SITE_EMAIL` 生成
177+
- 页脚版权名复用 `SITE_AUTHOR`
178+
- `poweredBy` 固定为 `Powered by serenade` 和项目仓库地址
179+
- QQ 图标提示文案固定为 `QQ`
180+
- 友链申请联系文案固定为 `邮箱`
181+
- 友链申请模板默认复用 `SITE_AUTHOR``SITE_URL``SITE_DESCRIPTION``SITE_PROFILE_AVATAR`
182+
- `SITE_PROFILE_INTRO``SITE_PROFILE_MOTTO``|` 分隔多行
183+
- `SITE_PROFILE_TECH_STACK``标签::图标URL|标签::图标URL` 配置
184+
185+
默认值直接定义在 `nuxt.config.ts` 中:
150186

151187
```ts
152188
export const siteConfig = {

app/components/Footer.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@
5757
</template>
5858

5959
<script setup>
60-
import { siteConfig } from '../../site.config'
60+
61+
const siteConfig = useSiteConfig()
6162
6263
const currentYear = new Date().getFullYear()
6364

app/composables/useSiteConfig.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const useSiteConfig = () => {
2+
const runtimeConfig = useRuntimeConfig()
3+
4+
return runtimeConfig.public.siteConfig
5+
}
6+

app/middleware/app.global.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { siteConfig } from '../../site.config'
21
import redirectsData from '../middleware/redirects.json'
32

43
export default defineNuxtRouteMiddleware(async (to, from) => {
4+
const siteConfig = useSiteConfig()
55
const routeTitle = getRouteTitle(to.path)
66

77
useHead({
@@ -28,6 +28,7 @@ export default defineNuxtRouteMiddleware(async (to, from) => {
2828

2929
// 根据路由路径自动生成页面标题
3030
function getRouteTitle(path) {
31+
const siteConfig = useSiteConfig()
3132
const routeMap = {
3233
'/posts': '所有文章',
3334
'/columns': '专栏',

app/pages/friends/index.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
<a
3535
:href="siteConfig.socialLinks.email.url"
3636
class="text-primary-500 hover:underline font-medium"
37-
>{{ siteConfig.friends.contactLabel }}</a
37+
>邮箱</a
3838
>
3939
</p>
4040

@@ -130,7 +130,8 @@
130130
</template>
131131

132132
<script setup>
133-
import { siteConfig } from '../../../site.config';
133+
134+
const siteConfig = useSiteConfig();
134135
135136
definePageMeta({
136137
layout: "default",

app/pages/index.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@
102102
</div>
103103
</template>
104104
<script setup>
105-
import { siteConfig } from '../../site.config'
105+
106+
const siteConfig = useSiteConfig()
106107
107108
definePageMeta({
108109
layout: "home",

app/pages/projects/index.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,8 @@
239239
</template>
240240

241241
<script setup>
242-
import { siteConfig } from '../../../site.config'
242+
243+
const siteConfig = useSiteConfig()
243244
244245
definePageMeta({
245246
layout: 'default'

docker-compose.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,25 @@ services:
99
NODE_ENV: production
1010
HOST: 0.0.0.0
1111
PORT: 3000
12+
SITE_AUTHOR: ${SITE_AUTHOR:-}
13+
SITE_TITLE: ${SITE_TITLE:-}
14+
SITE_DESCRIPTION: ${SITE_DESCRIPTION:-}
15+
SITE_KEYWORDS: ${SITE_KEYWORDS:-}
16+
SITE_URL: ${SITE_URL:-}
17+
SITE_EMAIL: ${SITE_EMAIL:-}
18+
SITE_LANG: ${SITE_LANG:-}
19+
SITE_START_TIME: ${SITE_START_TIME:-}
20+
SITE_ANALYTICS_SCRIPT: ${SITE_ANALYTICS_SCRIPT:-}
21+
SITE_ANALYTICS_WEBSITE_ID: ${SITE_ANALYTICS_WEBSITE_ID:-}
22+
SITE_PROFILE_AVATAR: ${SITE_PROFILE_AVATAR:-}
23+
SITE_PROFILE_BADGE: ${SITE_PROFILE_BADGE:-}
24+
SITE_PROFILE_INTRO: ${SITE_PROFILE_INTRO:-}
25+
SITE_PROFILE_MOTTO: ${SITE_PROFILE_MOTTO:-}
26+
SITE_PROFILE_TECH_STACK: ${SITE_PROFILE_TECH_STACK:-}
27+
SITE_PROFILE_STATEMENT: ${SITE_PROFILE_STATEMENT:-}
28+
SITE_PROFILE_GITHUB_CHART: ${SITE_PROFILE_GITHUB_CHART:-}
29+
SITE_GITHUB_URL: ${SITE_GITHUB_URL:-}
30+
SITE_QQ_URL: ${SITE_QQ_URL:-}
31+
SITE_FOOTER_ICP_LABEL: ${SITE_FOOTER_ICP_LABEL:-}
1232
volumes:
1333
- ./content:/app/content

nuxt.config.ts

Lines changed: 170 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,168 @@
1-
import { siteConfig } from "./site.config";
1+
const splitEnvList = (value, fallback) => {
2+
if (!value) {
3+
return fallback;
4+
}
5+
6+
const items = value
7+
.split('|')
8+
.map((item) => item.trim())
9+
.filter(Boolean);
10+
11+
return items.length > 0 ? items : fallback;
12+
};
13+
14+
const defaultProfileTechStack = [
15+
{
16+
label: 'Python',
17+
icon: 'https://img.shields.io/badge/-Python-3776AB?logo=python&logoColor=white&style=flat-square',
18+
},
19+
{
20+
label: 'CUDA',
21+
icon: 'https://img.shields.io/badge/-CUDA-76B900?logo=nvidia&logoColor=white&style=flat-square',
22+
},
23+
{
24+
label: 'C++',
25+
icon: 'https://img.shields.io/badge/-C++-00599C?logo=cplusplus&logoColor=white&style=flat-square',
26+
},
27+
{
28+
label: 'JavaScript',
29+
icon: 'https://img.shields.io/badge/-JavaScript-yellow?logo=javascript&logoColor=white&style=flat-square',
30+
},
31+
{
32+
label: 'HTML5',
33+
icon: 'https://img.shields.io/badge/-HTML5-E34F26?logo=html5&logoColor=white&style=flat-square',
34+
},
35+
{
36+
label: 'Vue',
37+
icon: 'https://img.shields.io/badge/-Vue-4FC08D?logo=vue.js&logoColor=white&style=flat-square',
38+
},
39+
{
40+
label: 'PHP',
41+
icon: 'https://img.shields.io/badge/-PHP-777BB4?logo=php&logoColor=white&style=flat-square',
42+
},
43+
{
44+
label: 'Docker',
45+
icon: 'https://img.shields.io/badge/-Docker-2496ED?logo=docker&logoColor=white&style=flat-square',
46+
},
47+
{
48+
label: 'Slurm',
49+
icon: 'https://img.shields.io/badge/-Slurm-1F1F1F?logo=linux&logoColor=white&style=flat-square',
50+
},
51+
{
52+
label: 'Ubuntu',
53+
icon: 'https://img.shields.io/badge/-Ubuntu-E95420?logo=ubuntu&logoColor=white&style=flat-square',
54+
},
55+
];
56+
57+
const parseTechStack = (value, fallback) => {
58+
if (!value) {
59+
return fallback;
60+
}
61+
62+
const items = value
63+
.split('|')
64+
.map((item) => item.trim())
65+
.filter(Boolean)
66+
.map((item) => {
67+
const [label, icon] = item.split('::').map((part) => part.trim());
68+
69+
if (!label || !icon) {
70+
return null;
71+
}
72+
73+
return { label, icon };
74+
})
75+
.filter(Boolean);
76+
77+
return items.length > 0 ? items : fallback;
78+
};
79+
80+
const author = process.env.SITE_AUTHOR || '染念';
81+
const title = process.env.SITE_TITLE || '染念的笔记';
82+
const description = process.env.SITE_DESCRIPTION || 'Writing code, painful and happy';
83+
const keywords =
84+
process.env.SITE_KEYWORDS || '染念,染念的笔记,染念の笔记,染念的博客,博客,blog';
85+
const url = process.env.SITE_URL || 'https://dyedd.cn';
86+
const email = process.env.SITE_EMAIL || '1176996982@qq.com';
87+
const lang = process.env.SITE_LANG || 'zh-CN';
88+
const startTime = process.env.SITE_START_TIME || '2017-02-11';
89+
const profileAvatar = process.env.SITE_PROFILE_AVATAR || '/logo.jpg';
90+
const normalizedSiteUrl = url.replace(/\/$/, '');
91+
const profileAvatarUrl = /^https?:\/\//.test(profileAvatar)
92+
? profileAvatar
93+
: `${normalizedSiteUrl}/${profileAvatar.replace(/^\//, '')}`;
94+
95+
const defaultSiteConfig = {
96+
author,
97+
title,
98+
description,
99+
keywords,
100+
url,
101+
email,
102+
lang,
103+
startTime,
104+
analytics: {
105+
script: process.env.SITE_ANALYTICS_SCRIPT || 'https://statistics.dyedd.cn/script.js',
106+
websiteId:
107+
process.env.SITE_ANALYTICS_WEBSITE_ID || '11a02a3f-0cdd-452a-bbb8-37f195db86fd',
108+
},
109+
profile: {
110+
name: author,
111+
avatar: profileAvatar,
112+
badge: process.env.SITE_PROFILE_BADGE || '🐟',
113+
introduction: splitEnvList(process.env.SITE_PROFILE_INTRO, [
114+
'AI infra研究生,目前研究大规模分布式训练以及扩散模型在短临降水领域的应用',
115+
'过去我也学习过前后端,所以现在也是不专业的全栈开发者',
116+
]),
117+
motto: splitEnvList(process.env.SITE_PROFILE_MOTTO, ['🐎 马到成功,心想事成', '🌈🌈🌈']),
118+
statement:
119+
process.env.SITE_PROFILE_STATEMENT ||
120+
'我将在这里分享我的编程和人工智能知识。如果你对这些主题感兴趣,那么恭喜你找到宝藏了。接下来你可以查看内容或订阅',
121+
githubContributionChart:
122+
process.env.SITE_PROFILE_GITHUB_CHART || 'https://ghchart.rshah.org/409ba5/dyedd',
123+
techStack: parseTechStack(process.env.SITE_PROFILE_TECH_STACK, defaultProfileTechStack),
124+
},
125+
socialLinks: {
126+
github: {
127+
label: 'GitHub',
128+
icon: 'github',
129+
url: process.env.SITE_GITHUB_URL || 'https://github.com/dyedd',
130+
},
131+
email: {
132+
label: 'Email',
133+
icon: 'youxiang',
134+
url: `mailto:${email}`,
135+
},
136+
qq: {
137+
label: 'QQ',
138+
icon: 'QQ',
139+
url:
140+
process.env.SITE_QQ_URL ||
141+
'https://qm.qq.com/cgi-bin/qm/qr?k=nLIdzy8UC9VkZ0g2EwnoN1rwnxaYvFx0&jump_from=webapi&authKey=mq2RvfcTQxEgImX+XZv0tBeobeHX+wTaAxOXq7pEKdsUD+a2Hi7mIOBGEj2ZtSDJ',
142+
title: 'QQ',
143+
},
144+
},
145+
footer: {
146+
copyrightName: author,
147+
poweredBy: {
148+
label: 'Powered by serenade',
149+
url: 'https://github.com/dyedd/serenade',
150+
},
151+
icp: {
152+
label: process.env.SITE_FOOTER_ICP_LABEL || '备案号:浙ICP备19020194号-1',
153+
url: 'https://beian.miit.gov.cn/',
154+
},
155+
},
156+
friends: {
157+
applicationTemplate: {
158+
name: author,
159+
url,
160+
logo: profileAvatarUrl,
161+
description,
162+
rss: `${normalizedSiteUrl}/feed`,
163+
},
164+
},
165+
};
2166

3167
export default defineNuxtConfig({
4168
compatibilityDate: "2025-12-17",
@@ -21,24 +185,17 @@ export default defineNuxtConfig({
21185
app: {
22186
rootId: "nuxt-root",
23187
head: {
24-
title: siteConfig.title,
25-
htmlAttrs: { lang: siteConfig.lang },
26-
meta: [
27-
{ name: "description", content: siteConfig.description },
28-
{ name: "author", content: siteConfig.author },
29-
{ name: "keywords", content: siteConfig.keywords },
30-
],
31188
script: [
32189
{ src: "/iconfont.js", defer: true },
33-
{
34-
src: siteConfig.analytics.script,
35-
defer: true,
36-
"data-website-id": siteConfig.analytics.websiteId,
37-
},
38190
],
39191
noscript: [{ textContent: "JavaScript is required" }],
40192
},
41193
},
194+
runtimeConfig: {
195+
public: {
196+
siteConfig: defaultSiteConfig,
197+
},
198+
},
42199
css: ["~/assets/css/main.css", "highlight.js/styles/atom-one-dark.css"],
43200
modules: ["@nuxtjs/tailwindcss"],
44201
vite: {

0 commit comments

Comments
 (0)