站点:纯静态 HTML + CSS + 原生 JS,零框架零构建
页面:首页 / 合作品牌 / 荣誉资质 / 客户案例 / 关于我们
内容:
- site/ 站点本体(含已压缩 webp 图片,约 4.8 MB)
- deploy/ Docker Compose / Caddy / Apache 部署配置
- _tools/ 开发自检脚本与部署脚本
- Dockerfile 自建 nginx 镜像方案
- README.md 项目文档
设计:品牌黄 #ffc107 + 白,深蓝仅用于页脚;菜单长期置顶;
交互动效只加在关键词与区块级元素上。
素材说明:原始素材(背景图/老网站/方案/荣誉资质/合作品牌,约 290 MB)
未纳入版本控制,站点使用的是 site/assets 内已处理压缩的图片。
92 lines
3.8 KiB
JavaScript
92 lines
3.8 KiB
JavaScript
/* 模拟 nginx 的 try_files 规则,验证「无扩展名地址」能否正确解析到文件。
|
|
* 规则与 nginx/default.conf 保持一致:
|
|
* try_files $uri $uri.html $uri/index.html =404;
|
|
* location ~* \.html?$ 命中后会做一次内部重定向(location 重匹配)
|
|
*/
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const ROOT = path.join(__dirname, '..', 'site');
|
|
|
|
function resolve(uri) {
|
|
if (!uri.startsWith('/')) return { status: 400 };
|
|
const clean = uri.split('?')[0].split('#')[0];
|
|
const abs = p => path.join(ROOT, decodeURIComponent(p));
|
|
|
|
// 1) $uri —— 直接命中文件
|
|
if (clean !== '/' && fs.existsSync(abs(clean)) && fs.statSync(abs(clean)).isFile()) {
|
|
return { status: 200, file: clean, via: '$uri' };
|
|
}
|
|
// 2) $uri.html
|
|
if (fs.existsSync(abs(clean + '.html'))) {
|
|
return { status: 200, file: clean + '.html', via: '$uri.html' };
|
|
}
|
|
// 3) $uri/index.html
|
|
if (fs.existsSync(abs(clean + '/index.html'))) {
|
|
return { status: 200, file: clean + '/index.html', via: '$uri/index.html' };
|
|
}
|
|
// 4) 目录(nginx 会对缺斜杠的目录做 301 补斜杠)
|
|
if (fs.existsSync(abs(clean)) && fs.statSync(abs(clean)).isDirectory() && !clean.endsWith('/')) {
|
|
return { status: 301, to: clean + '/' };
|
|
}
|
|
// 5) =404 -> 再被 error_page 兜到 /index.html
|
|
return { status: 404, fallback: '/index.html' };
|
|
}
|
|
|
|
const cases = [
|
|
['/', 200, 'index.html'],
|
|
['/index.html', 200, 'index.html'],
|
|
['/brands', 200, 'brands.html'],
|
|
['/brands.html', 200, 'brands.html'],
|
|
['/honors', 200, 'honors.html'],
|
|
['/cases', 200, 'cases.html'],
|
|
['/about', 200, 'about.html'],
|
|
['/cases#medical', 200, 'cases.html'],
|
|
['/assets/css/main.css', 200, 'assets/css/main.css'],
|
|
['/assets/js/main.js', 200, 'assets/js/main.js'],
|
|
['/assets/img/hero/hero-1.webp', 200, 'assets/img/hero/hero-1.webp'],
|
|
['/favicon.ico', 200, 'favicon.ico'],
|
|
['/healthz', 200, null], // nginx 直接 return 200,不落文件
|
|
['/assets', 301, null], // 目录补斜杠
|
|
['/not-exist-page', 404, null]
|
|
];
|
|
|
|
let bad = 0;
|
|
console.log('模拟 nginx try_files 的路由结果:\n');
|
|
for (const [uri, wantStatus, wantFile] of cases) {
|
|
if (uri === '/healthz') {
|
|
console.log(' OK ' + uri.padEnd(30) + '200 (nginx return "ok")');
|
|
continue;
|
|
}
|
|
const r = resolve(uri);
|
|
const gotFile = (r.file || '').replace(/^\/+/, '');
|
|
const statusOk = r.status === wantStatus;
|
|
const fileOk = !wantFile || gotFile === wantFile.replace(/^\/+/, '');
|
|
const pass = statusOk && fileOk;
|
|
if (!pass) bad++;
|
|
console.log(' ' + (pass ? 'OK ' : 'FAIL') + ' ' + uri.padEnd(30) +
|
|
String(r.status).padEnd(5) + (r.file || '') + (r.via ? ' [' + r.via + ']' : '') +
|
|
(r.to ? ' -> ' + r.to : '') +
|
|
(pass ? '' : ' 期望 ' + wantStatus + (wantFile ? ' ' + wantFile : '')));
|
|
}
|
|
|
|
// 额外:确认每个页面的资源引用在 nginx 规则下也能命中
|
|
console.log('\n逐页检查站内引用(模拟 nginx 解析):');
|
|
for (const f of ['index.html', 'brands.html', 'honors.html', 'cases.html', 'about.html']) {
|
|
const html = fs.readFileSync(path.join(ROOT, f), 'utf8');
|
|
const refs = new Set();
|
|
for (const m of html.matchAll(/(?:href|src)="((?!https?:|mailto:|tel:|#|data:)[^"]+)"/g)) refs.add(m[1]);
|
|
for (const m of html.matchAll(/data-(?:bg|full|src)="([^"]+)"/g)) refs.add(m[1]);
|
|
let miss = [];
|
|
for (const r of refs) {
|
|
const uri = '/' + r.replace(/^\.\//, '');
|
|
const res = resolve(uri);
|
|
if (res.status !== 200 && !(res.status === 301)) miss.push(r + ' -> ' + res.status);
|
|
}
|
|
if (miss.length) { bad++; console.log(' FAIL ' + f + ': ' + miss.join(', ')); }
|
|
else console.log(' OK ' + f + ' 的 ' + refs.size + ' 个引用全部可解析');
|
|
}
|
|
|
|
console.log('\n' + (bad ? '有 ' + bad + ' 项不通过' : '全部通过'));
|
|
process.exit(bad ? 1 : 0);
|