以下是基于前期讨论的开发步骤和关键实现要点,帮助你逐步完成该导航网页的搭建:
一、后端开发(以 Node.js 为例)
1. 环境搭建
bash
# 初始化项目
mkdir navi-project && cd navi-project
npm init -y
npm install express xml2js uuid cookie-parser
2. 核心文件结构
plaintext
project-root/
├─ res/
│ ├─ base.xml # 导航数据
│ ├─ user.json # 用户信息
│ └─ login.txt # 存储有效Cookie
├─ src/
│ ├─ server.js # 后端服务
│ └─ public/ # 前端静态资源
└─ package.json
3. 后端核心逻辑(server.js)
javascript
const express = require('express');
const xml2js = require('xml2js');
const uuid = require('uuid');
const cookieParser = require('cookie-parser');
const fs = require('fs').promises;
const path = require('path');
const app = express();
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.json()); // 解析JSON请求体
// 解析XML文件为导航数据
async function parseNaviXml() {
const xml = await fs.readFile('./res/base.xml', 'utf8');
const parser = new xml2js.Parser();
return parser.parseStringPromise(xml);
}
// 验证Cookie是否有效
async function isCookieValid(cookie) {
if (!cookie) return false;
const loginCodes = (await fs.readFile('./res/login.txt', 'utf8')).split('\n').filter(Boolean);
return loginCodes.includes(cookie);
}
// 登录接口
app.post('/api/login', async (req, res) => {
const { name, password } = req.body;
const users = JSON.parse(await fs.readFile('./res/user.json', 'utf8'));
const user = users.find(u => u.name === name && u.password === password);
if (!user) {
return res.status(401).json({ error: '用户名或密码错误' });
}
// 生成Cookie
const naviCode = uuid.v4();
await fs.appendFile('./res/login.txt', `${naviCode}\n`);
res.cookie('navi_code', naviCode, {
expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30天有效期
httpOnly: false, // 前端需读取Cookie,故不开启httpOnly
path: '/'
});
res.json({ success: true });
});
// 导航数据接口
app.get('/api/nav', async (req, res) => {
const { navi_code } = req.cookies;
const isLoggedIn = await isCookieValid(navi_code);
const naviData = await parseNaviXml();
// 构建返回数据(过滤受限内容)
const response = {
public: naviData.navi.public[0].category,
restricted: []
};
if (isLoggedIn) {
response.restricted = naviData.navi.restricted[0].category;
}
res.json(response);
});
// 启动服务
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
二、前端开发
1. 基础 HTML 结构(public/index.html)
html
预览
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>动态导航系统</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', sans-serif;
}
body {
background-color: #f0f2f5; /* 浅色背景 */
display: flex;
flex-direction: column;
min-height: 100vh;
}
.nav-container {
display: flex;
flex-wrap: wrap;
padding: 20px;
gap: 15px;
}
.category {
background: white;
border-radius: 8px;
padding: 15px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
min-width: 250px;
flex-grow: 1;
}
.category-title {
color: #1a73e8;
margin-bottom: 10px;
font-size: 1.2em;
}
.link {
display: flex;
align-items: center;
gap: 8px;
text-decoration: none;
color: #333;
padding: 8px 12px;
border-radius: 4px;
margin-bottom: 6px;
}
.link:hover {
background-color: #f0f8ff;
}
.link img {
width: 24px;
height: 24px;
object-fit: cover;
}
/* 登录按钮样式 */
.login-btn {
position: fixed;
top: 20px;
right: 20px;
padding: 8px 16px;
background-color: #1a73e8;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
/* 模态框样式 */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.4);
}
.modal-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 25px;
border-radius: 8px;
width: 300px;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.form-group {
margin-bottom: 15px;
}
input {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1em;
}
.btn {
width: 100%;
padding: 10px;
background-color: #1a73e8;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
</head>
<body>
<button class="login-btn" id="loginBtn">登录以展示全部</button>
<div class="nav-container" id="navContainer">
<!-- 动态生成导航内容 -->
</div>
<!-- 登录模态框 -->
<div class="modal" id="loginModal">
<div class="modal-content">
<div class="form-group">
<input type="text" id="username" placeholder="用户名" required>
</div>
<div class="form-group">
<input type="password" id="password" placeholder="密码" required>
</div>
<button class="btn" onclick="handleLogin()">登录</button>
<div id="errorMsg" style="color: red; text-align: center; margin-top: 10px;"></div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
2. 前端逻辑(public/script.js)
javascript
const loginBtn = document.getElementById('loginBtn');
const navContainer = document.getElementById('navContainer');
const loginModal = document.getElementById('loginModal');
const errorMsg = document.getElementById('errorMsg');
// 初始化:加载导航数据
async function init() {
try {
const response = await fetch('/api/nav');
const data = await response.json();
// 渲染公开导航
renderNav(data.public, false);
// 检查登录状态
const naviCode = document.cookie.split('; ').find(row => row.startsWith('navi_code='))?.split('=')[1];
if (naviCode) {
// 验证Cookie有效性(可通过再次请求接口隐式验证,因/api/nav已过滤受限内容)
loginBtn.textContent = '已登录';
renderNav(data.restricted, true); // 渲染受限内容
}
} catch (error) {
console.error('加载导航失败:', error);
}
}
// 渲染导航内容
function renderNav(categories, isRestricted) {
categories.forEach(category => {
const categoryDiv = document.createElement('div');
categoryDiv.className = 'category';
if (isRestricted) categoryDiv.style.display = 'none'; // 初始隐藏,待登录后显示
const title = document.createElement('h3');
title.className = 'category-title';
title.textContent = category.$.name;
category.link.forEach(link => {
const linkElement = document.createElement('a');
linkElement.className = 'link';
linkElement.href = link.$.url;
linkElement.target = '_blank';
const icon = document.createElement('img');
icon.src = link.$.icon;
icon.alt = link.$.name;
const text = document.createElement('span');
text.textContent = link.$.name;
linkElement.appendChild(icon);
linkElement.appendChild(text);
categoryDiv.appendChild(linkElement);
});
navContainer.appendChild(title);
navContainer.appendChild(categoryDiv);
// 登录后显示受限内容
if (loginBtn.textContent === '已登录' && isRestricted) {
categoryDiv.style.display = 'block';
}
});
}
// 登录按钮点击事件
loginBtn.addEventListener('click', () => {
loginModal.style.display = 'block';
});
// 登录处理
async function handleLogin() {
errorMsg.textContent = '';
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: username, password }),
});
if (response.ok) {
loginModal.style.display = 'none';
loginBtn.textContent = '已登录';
// 重新加载导航数据(包含受限内容)
init();
} else {
const { error } = await response.json();
errorMsg.textContent = error;
}
} catch (error) {
console.error('登录失败:', error);
errorMsg.textContent = '网络请求失败,请重试';
}
}
// 关闭模态框
window.addEventListener('click', (e) => {
if (e.target === loginModal) {
loginModal.style.display = 'none';
}
});
// 初始化页面
init();
三、测试与验证
1. 启动服务
bash
node src/server.js
访问 http://localhost:3000
,未登录时应仅显示公开导航,右上角按钮为 “登录以展示全部”。
2. 登录测试
使用账号
test1
/password1
或test2
/password2
登录登录成功后,Cookie 写入
login.txt
,页面显示全部导航内容,按钮变为 “已登录”。
3. 移动端模拟
使用浏览器调试工具切换至移动端视图,验证导航布局是否自适应,按钮点击区域是否合理。
四、扩展建议(可选)
Cookie 清理:添加定时任务清理
login.txt
中过期的 Cookie(需结合 Cookie 有效期判断)。Loading 状态:在数据请求时显示加载动画,提升用户体验。
记住密码:添加复选框存储用户名,方便下次登录。
如需调整细节(如样式、交互逻辑),可直接修改对应代码模块。
评论