以下是基于前期讨论的开发步骤和关键实现要点,帮助你逐步完成该导航网页的搭建:

一、后端开发(以 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/password1test2/password2 登录

  • 登录成功后,Cookie 写入login.txt,页面显示全部导航内容,按钮变为 “已登录”。

3. 移动端模拟

  • 使用浏览器调试工具切换至移动端视图,验证导航布局是否自适应,按钮点击区域是否合理。

四、扩展建议(可选)

  1. Cookie 清理:添加定时任务清理login.txt中过期的 Cookie(需结合 Cookie 有效期判断)。

  2. Loading 状态:在数据请求时显示加载动画,提升用户体验。

  3. 记住密码:添加复选框存储用户名,方便下次登录。


如需调整细节(如样式、交互逻辑),可直接修改对应代码模块。