Skip to content

Commit c4cf815

Browse files
committed
feat(navigation): 优化导航菜单显示顺序与配置项
- 为导航菜单添加 showInMobile 字段控制移动端默认显示与否 - 新增 order 字段以支持导航项排序,值越小优先级越高 - 在配置文件中补充导航项的 order 默认设置示例 - Header 组件合并静态和动态导航时,依据 order 进行升序排序 - 未设置 order 的导航项默认赋予较大值并保留原顺序 - 动态导航项兼容 showInMobile 和 order 配置,确保显示一致性
1 parent f811f56 commit c4cf815

4 files changed

Lines changed: 121 additions & 98 deletions

File tree

config.example.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,15 @@ social:
5353
# ========================================
5454
# 导航菜单配置
5555
# ========================================
56+
# showInMobile: 控制在移动端是否默认显示 (false 则放入汉堡菜单)
57+
# order: 控制导航顺序,数值越小越靠前,默认按配置顺序(order 值从 999 开始)
5658
navigation:
5759
- name: "首页"
5860
path: "/"
61+
order: 1 # 排序优先级最高
5962
- name: "关于"
6063
path: "/about"
64+
order: 2
6165
- name: "项目"
6266
path: "/projects"
6367
- name: "博客"

public/config.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,22 @@ social:
3939
# icon: "orcid"
4040
# url: "https://orcid.org/YOUR-ORCID-ID"
4141

42-
# 导航菜单
42+
# 导航菜单配置
4343
# showInMobile: 控制在移动端是否默认显示(false则放入汉堡菜单)
44+
# order: 控制导航顺序,数值越小越靠前,默认按配置顺序(order 值从 999 开始)
4445
navigation:
4546
- name: "首页"
4647
path: "/"
4748
showInMobile: true # 始终显示
49+
order: 1 # 排序优先级最高
4850
- name: "关于"
4951
path: "/about"
5052
showInMobile: true # 始终显示
53+
order: 2
5154
- name: "项目"
5255
path: "/projects"
5356
showInMobile: false # 放入汉堡菜单
57+
# 未设置 order,按配置顺序排在后面
5458
- name: "博客"
5559
path: "/posts"
5660
showInMobile: false # 放入汉堡菜单

scripts/init.js

Lines changed: 98 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
#!/usr/bin/env node
22

3-
import fs from 'fs';
4-
import path from 'path';
5-
import { fileURLToPath } from 'url';
3+
import fs from 'fs'
4+
import path from 'path'
5+
import { fileURLToPath } from 'url'
66

7-
const __filename = fileURLToPath(import.meta.url);
8-
const __dirname = path.dirname(__filename);
9-
const rootDir = path.resolve(__dirname, '..');
7+
const __filename = fileURLToPath(import.meta.url)
8+
const __dirname = path.dirname(__filename)
9+
const rootDir = path.resolve(__dirname, '..')
1010

1111
// 颜色输出
1212
const colors = {
@@ -15,95 +15,95 @@ const colors = {
1515
blue: '\x1b[34m',
1616
yellow: '\x1b[33m',
1717
red: '\x1b[31m',
18-
};
18+
}
1919

2020
function log(message, color = 'reset') {
21-
console.log(`${colors[color]}${message}${colors.reset}`);
21+
console.log(`${colors[color]}${message}${colors.reset}`)
2222
}
2323

2424
// 检查目录是否存在
2525
function ensureDir(dirPath) {
2626
if (!fs.existsSync(dirPath)) {
27-
fs.mkdirSync(dirPath, { recursive: true });
27+
fs.mkdirSync(dirPath, { recursive: true })
2828
}
2929
}
3030

3131
// 复制文件
3232
function copyFile(src, dest) {
33-
ensureDir(path.dirname(dest));
34-
fs.copyFileSync(src, dest);
33+
ensureDir(path.dirname(dest))
34+
fs.copyFileSync(src, dest)
3535
}
3636

3737
// 复制目录
3838
function copyDir(src, dest) {
39-
ensureDir(dest);
40-
const entries = fs.readdirSync(src, { withFileTypes: true });
41-
39+
ensureDir(dest)
40+
const entries = fs.readdirSync(src, { withFileTypes: true })
41+
4242
for (const entry of entries) {
43-
const srcPath = path.join(src, entry.name);
44-
const destPath = path.join(dest, entry.name);
45-
43+
const srcPath = path.join(src, entry.name)
44+
const destPath = path.join(dest, entry.name)
45+
4646
if (entry.isDirectory()) {
47-
copyDir(srcPath, destPath);
47+
copyDir(srcPath, destPath)
4848
} else {
49-
copyFile(srcPath, destPath);
49+
copyFile(srcPath, destPath)
5050
}
5151
}
5252
}
5353

5454
// 归档当前模板文件
5555
function archiveTemplate() {
56-
log('\n📦 步骤 1: 归档当前模板文件...', 'blue');
57-
58-
const templateDir = path.join(rootDir, '_template');
59-
ensureDir(templateDir);
60-
56+
log('\n📦 步骤 1: 归档当前模板文件...', 'blue')
57+
58+
const templateDir = path.join(rootDir, '_template')
59+
ensureDir(templateDir)
60+
6161
// 归档内容目录
62-
const contentSrc = path.join(rootDir, 'content');
63-
const contentDest = path.join(templateDir, 'content');
62+
const contentSrc = path.join(rootDir, 'content')
63+
const contentDest = path.join(templateDir, 'content')
6464
if (fs.existsSync(contentSrc)) {
65-
log(' ✓ 归档 content/ 目录...', 'green');
66-
copyDir(contentSrc, contentDest);
65+
log(' ✓ 归档 content/ 目录...', 'green')
66+
copyDir(contentSrc, contentDest)
6767
}
68-
68+
6969
// 归档配置文件
70-
const configSrc = path.join(rootDir, 'config.yml');
71-
const configDest = path.join(templateDir, 'config.yml');
70+
const configSrc = path.join(rootDir, 'config.yml')
71+
const configDest = path.join(templateDir, 'config.yml')
7272
if (fs.existsSync(configSrc)) {
73-
log(' ✓ 归档 config.yml 文件...', 'green');
74-
copyFile(configSrc, configDest);
73+
log(' ✓ 归档 config.yml 文件...', 'green')
74+
copyFile(configSrc, configDest)
7575
}
76-
76+
7777
// 归档 public/config.yml
78-
const publicConfigSrc = path.join(rootDir, 'public', 'config.yml');
79-
const publicConfigDest = path.join(templateDir, 'public-config.yml');
78+
const publicConfigSrc = path.join(rootDir, 'public', 'config.yml')
79+
const publicConfigDest = path.join(templateDir, 'public-config.yml')
8080
if (fs.existsSync(publicConfigSrc)) {
81-
log(' ✓ 归档 public/config.yml 文件...', 'green');
82-
copyFile(publicConfigSrc, publicConfigDest);
81+
log(' ✓ 归档 public/config.yml 文件...', 'green')
82+
copyFile(publicConfigSrc, publicConfigDest)
8383
}
84-
85-
log(' ✅ 模板文件归档完成!', 'green');
84+
85+
log(' ✅ 模板文件归档完成!', 'green')
8686
}
8787

8888
// 创建用户内容模板
8989
function createUserTemplate() {
90-
log('\n🎨 步骤 2: 创建用户内容模板...', 'blue');
91-
90+
log('\n🎨 步骤 2: 创建用户内容模板...', 'blue')
91+
9292
// 创建用户内容目录结构
93-
const userContentDir = path.join(rootDir, 'content');
94-
ensureDir(userContentDir);
95-
93+
const userContentDir = path.join(rootDir, 'content')
94+
ensureDir(userContentDir)
95+
9696
// 创建子目录
97-
const postsDir = path.join(userContentDir, 'posts');
98-
const pagesDir = path.join(userContentDir, 'pages');
99-
const filesDir = path.join(userContentDir, 'files');
100-
const pdfsDir = path.join(filesDir, 'pdfs');
101-
102-
ensureDir(postsDir);
103-
ensureDir(pagesDir);
104-
ensureDir(filesDir);
105-
ensureDir(pdfsDir);
106-
97+
const postsDir = path.join(userContentDir, 'posts')
98+
const pagesDir = path.join(userContentDir, 'pages')
99+
const filesDir = path.join(userContentDir, 'files')
100+
const pdfsDir = path.join(filesDir, 'pdfs')
101+
102+
ensureDir(postsDir)
103+
ensureDir(pagesDir)
104+
ensureDir(filesDir)
105+
ensureDir(pdfsDir)
106+
107107
// 创建指引性模板文件
108108
const welcomePost = `---
109109
title: "欢迎使用 PPage"
@@ -128,7 +128,7 @@ tags:
128128
## 开始创作
129129
130130
删除这个文件,开始创作你自己的内容吧!
131-
`;
131+
`
132132

133133
const aboutPage = `---
134134
title: "关于我"
@@ -154,7 +154,7 @@ title: "关于我"
154154
## 工作经历
155155
156156
- 职位 - 公司/机构,时间
157-
`;
157+
`
158158

159159
const readmeFile = `# 文件目录
160160
@@ -171,22 +171,22 @@ title: "关于我"
171171
- \`pdfs/\` - PDF 文档
172172
- \`docs/\` - 其他文档
173173
- \`images/\` - 图片资源
174-
`;
175-
176-
fs.writeFileSync(path.join(postsDir, 'welcome.md'), welcomePost);
177-
fs.writeFileSync(path.join(pagesDir, 'about.md'), aboutPage);
178-
fs.writeFileSync(path.join(filesDir, 'README.md'), readmeFile);
179-
180-
log(' ✓ 创建 content/posts/welcome.md', 'green');
181-
log(' ✓ 创建 content/pages/about.md', 'green');
182-
log(' ✓ 创建 content/files/README.md', 'green');
183-
log(' ✅ 用户内容模板创建完成!', 'green');
174+
`
175+
176+
fs.writeFileSync(path.join(postsDir, 'welcome.md'), welcomePost)
177+
fs.writeFileSync(path.join(pagesDir, 'about.md'), aboutPage)
178+
fs.writeFileSync(path.join(filesDir, 'README.md'), readmeFile)
179+
180+
log(' ✓ 创建 content/posts/welcome.md', 'green')
181+
log(' ✓ 创建 content/pages/about.md', 'green')
182+
log(' ✓ 创建 content/files/README.md', 'green')
183+
log(' ✅ 用户内容模板创建完成!', 'green')
184184
}
185185

186186
// 创建用户配置文件
187187
function createUserConfig() {
188-
log('\n⚙️ 步骤 3: 创建配置文件模板...', 'blue');
189-
188+
log('\n⚙️ 步骤 3: 创建配置文件模板...', 'blue')
189+
190190
const configTemplate = `# PPage 个人主页配置文件
191191
# 请按照注释提示填写你的个人信息
192192
@@ -227,11 +227,15 @@ social:
227227
# ========================================
228228
# 导航菜单配置
229229
# ========================================
230+
# showInMobile: 控制在移动端是否默认显示 (false 则放入汉堡菜单)
231+
# order: 控制导航顺序,数值越小越靠前,默认按配置顺序(order 值从 999 开始)
230232
navigation:
231233
- name: "首页"
232234
path: "/"
235+
order: 1
233236
- name: "关于"
234237
path: "/about"
238+
order: 2
235239
- name: "项目"
236240
path: "/projects"
237241
- name: "博客"
@@ -309,44 +313,43 @@ news:
309313
date: "${new Date().toISOString().split('T')[0]}"
310314
tags:
311315
- "【请填写】标签"
312-
`;
316+
`
317+
318+
const publicConfigPath = path.join(rootDir, 'public', 'config.yml')
313319

314-
const publicConfigPath = path.join(rootDir, 'public', 'config.yml');
315-
316320
// 只生成 public/config.yml
317-
fs.writeFileSync(publicConfigPath, configTemplate);
318-
319-
log(' ✓ 创建 public/config.yml', 'green');
320-
log(' ✅ 配置文件模板创建完成!', 'green');
321+
fs.writeFileSync(publicConfigPath, configTemplate)
322+
323+
log(' ✓ 创建 public/config.yml', 'green')
324+
log(' ✅ 配置文件模板创建完成!', 'green')
321325
}
322326

323327
// 主函数
324328
async function init() {
325-
log('\n🚀 开始初始化 PPage 项目...', 'blue');
326-
329+
log('\n🚀 开始初始化 PPage 项目...', 'blue')
330+
327331
try {
328332
// 步骤 1: 归档模板
329-
archiveTemplate();
330-
333+
archiveTemplate()
334+
331335
// 步骤 2: 创建用户内容模板
332-
createUserTemplate();
333-
336+
createUserTemplate()
337+
334338
// 步骤 3: 创建用户配置文件
335-
createUserConfig();
336-
337-
log('\n✨ 初始化完成!', 'green');
338-
log('\n📝 下一步:', 'yellow');
339-
log(' 1. 编辑 config.yml 文件,填写你的个人信息', 'yellow');
340-
log(' 2. 在 content/posts/ 目录下创建你的博客文章', 'yellow');
341-
log(' 3. 在 content/pages/ 目录下创建页面内容', 'yellow');
342-
log(' 4. 运行 npm run dev 查看效果', 'yellow');
343-
log('\n💡 提示:原始模板文件已保存在 _template/ 目录中', 'blue');
344-
339+
createUserConfig()
340+
341+
log('\n✨ 初始化完成!', 'green')
342+
log('\n📝 下一步:', 'yellow')
343+
log(' 1. 编辑 config.yml 文件,填写你的个人信息', 'yellow')
344+
log(' 2. 在 content/posts/ 目录下创建你的博客文章', 'yellow')
345+
log(' 3. 在 content/pages/ 目录下创建页面内容', 'yellow')
346+
log(' 4. 运行 npm run dev 查看效果', 'yellow')
347+
log('\n💡 提示:原始模板文件已保存在 _template/ 目录中', 'blue')
345348
} catch (error) {
346-
log(`\n❌ 初始化失败: ${error.message}`, 'red');
347-
process.exit(1);
349+
log(`\n❌ 初始化失败: ${error.message}`, 'red')
350+
process.exit(1)
348351
}
349352
}
350353

351354
// 运行初始化
352-
init();
355+
init()

src/components/layout/Header.jsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,12 @@ export function Header({ folderConfigs = [] }) {
3939

4040
// 合并静态导航和动态生成的导航
4141
const allNavigation = [
42-
...navigation,
42+
// 静态导航项,添加默认顺序索引
43+
...navigation.map((item, index) => ({
44+
...item,
45+
order: item.order ?? 999 + index, // 未设置 order 时使用大数值 + 索引保持原顺序
46+
})),
47+
// 动态文件夹导航项
4348
...folderConfigs
4449
.filter(config => config.showInNavigation !== false) // 过滤不显示的项
4550
.map(config => {
@@ -52,9 +57,16 @@ export function Header({ folderConfigs = [] }) {
5257
path: `/${config.name}`,
5358
isDynamic: true,
5459
showInMobile: config.showInMobile ?? false, // 使用 config 中的配置
60+
order: config.order ?? 999, // 使用 folderConfig 中的 order
5561
}
5662
}),
57-
]
63+
].sort((a, b) => {
64+
// 按 order 字段升序排序
65+
const orderDiff = (a.order ?? 999) - (b.order ?? 999)
66+
if (orderDiff !== 0) return orderDiff
67+
// order 相同时保持相对位置稳定
68+
return 0
69+
})
5870

5971
// 分离移动端显示和汉堡菜单中的导航项
6072
const mobileVisibleNav = allNavigation.filter(

0 commit comments

Comments
 (0)