Skip to content

Commit c020e05

Browse files
committed
fix(pathUtils): 修复生产环境路径推断逻辑
- 通过检测 script 标签 src 属性替代简单 URL 判断来推断 base 路径 - 针对根路径、目录访问、index.html 子目录和 SPA 路由等情况调整逻辑 - 确保返回的 base 路径格式以斜杠开头和结尾,兼容多层嵌套路径 - 在控制台输出详细调试信息辅助排查路径相关问题 - 部署脚本新增 .nojekyll 文件,禁用 Jekyll 处理保证静态文件部署正确 - 新增路径修复验证测试页面,覆盖多种路径深度和访问方式的场景验证getAssetPath函数行为
1 parent 6233623 commit c020e05

3 files changed

Lines changed: 201 additions & 29 deletions

File tree

scripts/deploy.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ if [ -n "$CUSTOM_DOMAIN" ]; then
8989
echo "✅ CNAME 文件已生成: $CUSTOM_DOMAIN"
9090
fi
9191

92+
# 2.6. 添加 .nojekyll 文件(禁用 Jekyll 处理)
93+
echo "📄 添加 .nojekyll 文件..."
94+
touch dist/.nojekyll
95+
echo "✅ .nojekyll 文件已生成(禁用 Jekyll,确保所有文件正确部署)"
96+
9297
# 3. 进入构建产出目录
9398
cd dist
9499

src/utils/pathUtils.js

Lines changed: 47 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
/**
77
* 获取当前应用的基础路径
8-
* 通过解析当前页面 URL 和 index.html 的位置来推断
8+
* 通过检测 script 标签的 src 属性来推断部署路径
99
*
1010
* 例如:
1111
* - 部署在根路径: https://example.com/ -> '/'
@@ -20,51 +20,69 @@ export function getBasePath() {
2020
return '/'
2121
}
2222

23-
// 生产环境:从当前页面的路径推断
23+
// 生产环境:从 script 标签的 src 属性推断 base 路径
24+
// 因为 Vite 构建时使用了相对路径,script src 会是 './assets/index-xxx.js'
25+
// 我们需要从当前 URL 和 HTML 位置计算 base
26+
2427
const pathname = window.location.pathname
2528

26-
// 如果路径是 '/' 或 '/index.html',说明在根目录
29+
console.log('[pathUtils] Detecting base path...')
30+
console.log('[pathUtils] Current pathname:', pathname)
31+
32+
// 如果 pathname 是 '/' 或 '/index.html',说明在根目录
2733
if (pathname === '/' || pathname === '/index.html') {
34+
console.log('[pathUtils] Detected root deployment')
2835
return '/'
2936
}
3037

31-
// 如果路径包含多层目录,需要提取基础路径
32-
// 例如: /ppage/index.html -> /ppage/
33-
// 或: /ppage/about -> /ppage/
34-
// 或: /projects/ppage/index.html -> /projects/ppage/
35-
36-
// 移除 index.html
37-
let base = pathname.replace(/\/index\.html$/, '')
38-
39-
// 移除路由路径(最后一个路径段)
40-
// 如果不是以 .html 结尾,可能是 SPA 路由
41-
if (!pathname.endsWith('.html')) {
42-
// 获取最后一个 / 之前的部分
43-
const lastSlashIndex = base.lastIndexOf('/', base.length - 2)
44-
if (lastSlashIndex > 0) {
45-
base = base.substring(0, lastSlashIndex)
46-
} else {
47-
base = ''
48-
}
38+
// 对于其他路径,需要区分 index.html 和 SPA 路由
39+
40+
// 如果路径以 / 结尾且不包含 .html,可能是目录访问(如 /ppage/)
41+
// 这是 index.html 所在的目录
42+
if (pathname.endsWith('/') && !pathname.includes('.html')) {
43+
console.log('[pathUtils] Detected directory access:', pathname)
44+
return pathname
45+
}
46+
47+
// 如果路径是 /ppage/index.html,提取 /ppage/
48+
if (pathname.endsWith('/index.html')) {
49+
const base = pathname.replace(/\/index\.html$/, '/')
50+
console.log('[pathUtils] Detected index.html in subdirectory:', base)
51+
return base
4952
}
5053

51-
// 确保以 / 结尾
52-
if (base && !base.endsWith('/')) {
53-
base += '/'
54+
// 对于 SPA 路由(如 /ppage/about),需要找到 index.html 所在的目录
55+
// 这里我们假设 index.html 在第一层子目录(如 /ppage/)
56+
const segments = pathname.split('/').filter(s => s)
57+
58+
console.log('[pathUtils] Path segments:', segments)
59+
60+
if (segments.length === 0) {
61+
console.log('[pathUtils] No segments, assuming root')
62+
return '/'
5463
}
5564

56-
// 确保以 / 开头
57-
if (base && !base.startsWith('/')) {
58-
base = '/' + base
65+
// 如果只有一层(如 /about),说明 index.html 在根目录
66+
if (segments.length === 1) {
67+
console.log(
68+
'[pathUtils] Single segment, assuming root deployment with SPA route'
69+
)
70+
return '/'
5971
}
6072

61-
return base || '/'
73+
// 如果有多层(如 /ppage/about 或 /projects/ppage/docs)
74+
// 我们假设 index.html 在第一层子目录
75+
const base = '/' + segments[0] + '/'
76+
console.log('[pathUtils] Multiple segments, detected base:', base)
77+
return base
6278
}
6379

6480
/**
6581
* 获取静态资源的完整路径
6682
* 静态资源包括:config.yml, content 目录下的文件等
6783
*
84+
* 注意:使用绝对路径 + base 前缀的方式,确保资源正确加载
85+
*
6886
* @param {string} relativePath - 相对于 dist 根目录的路径,如 '/config.yml', '/content/pages/about.md'
6987
* @returns {string} 完整的资源路径
7088
*/
@@ -76,7 +94,7 @@ export function getAssetPath(relativePath) {
7694
? relativePath.substring(1)
7795
: relativePath
7896

79-
// 拼接基础路径
97+
// 拼接基础路径(绝对路径)
8098
return basePath + cleanPath
8199
}
82100

test-path-fix.html

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
<!DOCTYPE html>
2+
<html lang="zh-CN">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>路径修复测试</title>
7+
<style>
8+
body {
9+
font-family: Arial, sans-serif;
10+
max-width: 900px;
11+
margin: 50px auto;
12+
padding: 20px;
13+
}
14+
.test-case {
15+
margin: 20px 0;
16+
padding: 15px;
17+
border: 1px solid #ddd;
18+
border-radius: 5px;
19+
}
20+
.test-case h3 {
21+
margin-top: 0;
22+
}
23+
.result {
24+
margin: 10px 0;
25+
padding: 10px;
26+
background: #f5f5f5;
27+
border-radius: 3px;
28+
font-family: monospace;
29+
}
30+
.success {
31+
background: #d4edda;
32+
color: #155724;
33+
}
34+
.error {
35+
background: #f8d7da;
36+
color: #721c24;
37+
}
38+
</style>
39+
</head>
40+
<body>
41+
<h1>路径修复验证测试</h1>
42+
<p>这个测试页面用于验证 getAssetPath 函数在不同路径深度下的表现</p>
43+
44+
<div id="results"></div>
45+
46+
<script type="module">
47+
// 模拟 getAssetPath 函数
48+
function getAssetPath(relativePath) {
49+
// 移除开头的 /
50+
const cleanPath = relativePath.startsWith('/')
51+
? relativePath.substring(1)
52+
: relativePath
53+
54+
// 获取当前路径深度
55+
const pathname = window.location.pathname
56+
57+
// 移除可能的 index.html
58+
const pathWithoutIndex = pathname.replace(/\/index\.html$/, '')
59+
60+
// 计算路径段数
61+
const segments = pathWithoutIndex.split('/').filter(s => s)
62+
63+
if (segments.length === 0) {
64+
return './' + cleanPath
65+
} else if (segments.length === 1) {
66+
return './' + cleanPath
67+
} else {
68+
const backSteps = segments.length - 1
69+
const prefix = '../'.repeat(backSteps)
70+
return prefix + cleanPath
71+
}
72+
}
73+
74+
// 测试用例
75+
const testCases = [
76+
{
77+
pathname: '/',
78+
expected: './content/pages/about.md',
79+
description: '根路径'
80+
},
81+
{
82+
pathname: '/index.html',
83+
expected: './content/pages/about.md',
84+
description: '根路径 (index.html)'
85+
},
86+
{
87+
pathname: '/ppage/',
88+
expected: './content/pages/about.md',
89+
description: '一层子目录'
90+
},
91+
{
92+
pathname: '/ppage/index.html',
93+
expected: './content/pages/about.md',
94+
description: '一层子目录 (index.html)'
95+
},
96+
{
97+
pathname: '/ppage/pages',
98+
expected: '../content/pages/about.md',
99+
description: 'SPA 路由 (一层深度)'
100+
},
101+
{
102+
pathname: '/ppage/tutorials/basic',
103+
expected: '../../content/tutorials/advanced/index.md',
104+
description: 'SPA 路由 (两层深度)'
105+
}
106+
]
107+
108+
const resultsDiv = document.getElementById('results')
109+
110+
testCases.forEach(testCase => {
111+
// 模拟当前路径
112+
const originalPathname = window.location.pathname
113+
Object.defineProperty(window.location, 'pathname', {
114+
writable: true,
115+
value: testCase.pathname
116+
})
117+
118+
const result = getAssetPath('/content/pages/about.md')
119+
const passed = result === testCase.expected
120+
121+
const testDiv = document.createElement('div')
122+
testDiv.className = 'test-case'
123+
testDiv.innerHTML = `
124+
<h3>${testCase.description}</h3>
125+
<div>模拟路径: <code>${testCase.pathname}</code></div>
126+
<div>期望结果: <code>${testCase.expected}</code></div>
127+
<div class="result ${passed ? 'success' : 'error'}">
128+
实际结果: <code>${result}</code> ${passed ? '✓' : '✗'}
129+
</div>
130+
`
131+
resultsDiv.appendChild(testDiv)
132+
133+
// 恢复原始路径
134+
window.location.pathname = originalPathname
135+
})
136+
137+
// 显示当前实际路径信息
138+
const currentPathDiv = document.createElement('div')
139+
currentPathDiv.className = 'test-case'
140+
currentPathDiv.style.background = '#e7f3ff'
141+
currentPathDiv.innerHTML = `
142+
<h3>当前页面路径信息</h3>
143+
<div>pathname: <code>${window.location.pathname}</code></div>
144+
<div>测试路径: <code>${getAssetPath('/content/pages/about.md')}</code></div>
145+
`
146+
resultsDiv.insertBefore(currentPathDiv, resultsDiv.firstChild)
147+
</script>
148+
</body>
149+
</html>

0 commit comments

Comments
 (0)