修复服务器重启时端口被占用和数据保存问题
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
# 问题分析
|
||||
|
||||
1. **问题现象**:当用户在某个设置项页面刷新时,页面会自动回到仪表盘页面。
|
||||
|
||||
2. **问题根源**:
|
||||
- 在 `dashboard.js` 文件中,`handleHashChange` 函数被定义在 `handlePageSwitch` 函数内部
|
||||
- 当页面刷新时,`handleHashChange` 函数会在页面加载完成后立即执行
|
||||
- 此时,`menuItems` 可能还没有被正确初始化,或者 `handleHashChange` 函数内部的 `menuItems` 引用的是旧的变量,导致它无法找到对应的菜单项
|
||||
- 当 `handleHashChange` 函数无法找到对应的菜单项时,它会执行 `window.location.hash = '#dashboard'`,将页面重定向到仪表盘页面
|
||||
|
||||
3. **具体问题**:
|
||||
- `handleHashChange` 函数在找不到对应的菜单项时,会立即重定向到仪表盘页面,而不是先尝试直接显示对应的内容
|
||||
- 这导致用户在刷新页面时,即使URL中包含正确的hash,页面也会被重定向到仪表盘
|
||||
|
||||
# 修复方案
|
||||
|
||||
1. **修复 `handleHashChange` 函数**:
|
||||
- 修改 `handleHashChange` 函数,确保它在找不到对应的菜单项时,不会总是重定向到仪表盘页面
|
||||
- 当无法找到对应的菜单项时,先尝试直接显示对应的内容
|
||||
- 只有当对应的内容也不存在时,才重定向到仪表盘页面
|
||||
|
||||
2. **优化页面初始化流程**:
|
||||
- 确保 `handleHashChange` 函数在页面完全加载后才执行
|
||||
- 确保 `menuItems` 变量在 `handleHashChange` 函数执行前已经被正确初始化
|
||||
|
||||
# 实现步骤
|
||||
|
||||
1. 修改 `dashboard.js` 文件中的 `handleHashChange` 函数:
|
||||
- 当无法找到对应的菜单项时,先尝试直接显示对应的内容
|
||||
- 只有当对应的内容也不存在时,才重定向到仪表盘页面
|
||||
|
||||
2. 测试修复后的功能:
|
||||
- 启动DNS服务器
|
||||
- 访问Web界面,导航到某个设置项页面,例如 `#config`
|
||||
- 刷新页面
|
||||
- 验证页面是否仍然显示在设置项页面,而不是自动回到仪表盘页面
|
||||
|
||||
# 预期结果
|
||||
|
||||
- 用户在某个设置项页面刷新时,页面会保持在当前页面,不会自动回到仪表盘页面
|
||||
- 只有当URL中的hash无效时,页面才会重定向到仪表盘页面
|
||||
- 页面导航功能正常,用户可以通过点击菜单项切换页面
|
||||
|
||||
# 修复代码
|
||||
|
||||
```javascript
|
||||
// 修改 dashboard.js 文件中的 handleHashChange 函数
|
||||
function handleHashChange() {
|
||||
let hash = window.location.hash;
|
||||
|
||||
// 如果没有hash,默认设置为#dashboard
|
||||
if (!hash) {
|
||||
hash = '#dashboard';
|
||||
window.location.hash = hash;
|
||||
return;
|
||||
}
|
||||
|
||||
const targetId = hash.substring(1);
|
||||
|
||||
// 查找对应的内容元素
|
||||
const contentElement = document.getElementById(`${targetId}-content`);
|
||||
|
||||
// 如果找到了对应的内容元素,直接显示
|
||||
if (contentElement) {
|
||||
// 隐藏所有内容
|
||||
document.querySelectorAll('[id$="-content"]').forEach(content => {
|
||||
content.classList.add('hidden');
|
||||
});
|
||||
|
||||
// 显示目标内容
|
||||
contentElement.classList.remove('hidden');
|
||||
|
||||
// 查找对应的菜单项并更新活动状态
|
||||
let targetMenuItem = null;
|
||||
menuItems.forEach(item => {
|
||||
if (item.getAttribute('href') === hash) {
|
||||
targetMenuItem = item;
|
||||
}
|
||||
});
|
||||
|
||||
// 更新活动菜单项
|
||||
menuItems.forEach(item => {
|
||||
item.classList.remove('sidebar-item-active');
|
||||
if (item.getAttribute('href') === hash) {
|
||||
item.classList.add('sidebar-item-active');
|
||||
// 更新页面标题
|
||||
document.getElementById('page-title').textContent = item.querySelector('span').textContent;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 如果没有找到对应的内容,默认显示dashboard
|
||||
window.location.hash = '#dashboard';
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,57 @@
|
||||
## 问题分析
|
||||
|
||||
1. **端口被占用问题**:
|
||||
- 服务器重启时提示端口被占用,可能是因为之前的服务器进程没有完全关闭
|
||||
- 从之前的ps aux命令输出中,看到有一个dns-server进程在运行(PID: 233272)
|
||||
- 这导致新的服务器进程无法绑定到相同的端口
|
||||
|
||||
2. **数据保存提示no such file or directory问题**:
|
||||
- 配置文件中statsFile和shield_stats.json的路径格式不一致(有的带./,有的不带)
|
||||
- 可能存在目录创建失败或权限问题
|
||||
- 程序运行时的工作目录与预期不符
|
||||
|
||||
## 修复方案
|
||||
|
||||
1. **解决端口被占用问题**:
|
||||
- 在启动新服务器之前,确保所有旧的服务器进程都已关闭
|
||||
- 可以通过kill命令手动关闭旧进程
|
||||
- 或者在程序中添加自动检测和关闭旧进程的逻辑
|
||||
|
||||
2. **解决数据保存问题**:
|
||||
- 统一配置文件中的文件路径格式,确保所有路径都使用相对路径或绝对路径
|
||||
- 确保createRequiredFiles函数能够正确创建所有必要的目录和文件
|
||||
- 添加错误处理,确保在目录或文件创建失败时能够给出明确的错误信息
|
||||
- 检查程序运行时的工作目录,确保路径解析正确
|
||||
|
||||
## 修复步骤
|
||||
|
||||
1. **关闭旧的服务器进程**:
|
||||
- 使用kill命令关闭旧的dns-server进程
|
||||
- 验证旧进程是否已关闭
|
||||
|
||||
2. **统一配置文件中的文件路径格式**:
|
||||
- 修改配置文件,确保所有文件路径都使用一致的格式
|
||||
- 例如,将所有路径改为相对路径,不带./前缀
|
||||
|
||||
3. **修改createRequiredFiles函数**:
|
||||
- 确保函数能够正确创建所有必要的目录和文件
|
||||
- 添加更详细的错误处理和日志
|
||||
- 确保函数能够处理不同格式的文件路径
|
||||
|
||||
4. **测试修复效果**:
|
||||
- 启动服务器,检查是否能够成功绑定到端口
|
||||
- 检查数据文件是否能够正确保存
|
||||
- 重启服务器,检查是否能够正常启动
|
||||
|
||||
## 预期效果
|
||||
|
||||
- 服务器能够成功启动,不会提示端口被占用
|
||||
- 数据文件能够正确保存,不会提示no such file or directory
|
||||
- 服务器重启时能够正常启动,不会出现相同的问题
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 确保在修改配置文件之前备份原始文件
|
||||
- 确保程序有足够的权限创建和写入文件
|
||||
- 确保在关闭旧进程之前,所有重要的数据都已保存
|
||||
- 测试修复效果时,确保覆盖所有可能的情况
|
||||
@@ -0,0 +1,28 @@
|
||||
## 问题分析
|
||||
|
||||
在 `main.js` 文件中,当用户点击菜单项时,代码使用了 `e.preventDefault()` 来阻止默认行为,这导致浏览器不会自动更新地址栏中的 hash。虽然 `dashboard.js` 文件中有 `handleHashChange` 函数来处理 hash 变化,但由于 `main.js` 中的 `e.preventDefault()`,点击菜单项时不会触发 hash 变化。
|
||||
|
||||
## 修复方案
|
||||
|
||||
1. **修改 `main.js` 文件**:移除 `e.preventDefault()`,或者在处理完点击事件后手动更新地址栏中的 hash
|
||||
2. **确保 `main.js` 和 `dashboard.js` 中的点击事件处理逻辑不冲突**
|
||||
3. **统一页面导航逻辑**:确保所有页面导航都通过 hash 变化来实现
|
||||
|
||||
## 修复步骤
|
||||
|
||||
1. **修改 `main.js` 文件**:
|
||||
- 移除 `e.preventDefault()`,允许浏览器自动更新地址栏中的 hash
|
||||
- 或者在处理完点击事件后,手动设置 `window.location.hash = item.getAttribute('href')`
|
||||
- 确保点击事件处理逻辑与 `dashboard.js` 中的 `handleHashChange` 函数兼容
|
||||
|
||||
2. **测试修复效果**:
|
||||
- 点击菜单项,检查地址栏中的 hash 是否正确更新
|
||||
- 刷新页面,检查是否能保持在当前页面
|
||||
- 直接修改地址栏中的 hash,检查是否能正确导航到对应页面
|
||||
|
||||
## 预期效果
|
||||
|
||||
- 用户点击菜单项时,地址栏中的 hash 会自动更新
|
||||
- 页面刷新时,会保持在当前页面
|
||||
- 直接修改地址栏中的 hash 可以导航到对应页面
|
||||
- 所有页面导航逻辑统一,避免冲突
|
||||
@@ -0,0 +1,81 @@
|
||||
# DNS服务器项目介绍
|
||||
## 项目概述
|
||||
这是一个基于Go语言开发的高性能DNS服务器,具备域名屏蔽、Hosts管理、统计分析和远程规则管理等功能。服务器支持通过Web界面进行管理配置,同时能够自动更新和缓存远程规则列表。
|
||||
|
||||
## 技术架构
|
||||
### 核心组件
|
||||
1. DNS服务模块 ( `server.go` )
|
||||
- 基于 github.com/miekg/dns 库实现高性能DNS查询处理
|
||||
- 支持配置上游DNS服务器进行递归查询
|
||||
- 实现域名屏蔽、统计数据收集等核心功能
|
||||
|
||||
2. 屏蔽管理系统 ( `manager.go` )
|
||||
- 管理本地和远程屏蔽规则
|
||||
- 支持规则缓存、自动更新和统计
|
||||
- 实现域名和正则表达式规则的解析和匹配
|
||||
|
||||
3. HTTP控制台 ( `server.go` )
|
||||
- 提供Web管理界面
|
||||
- 实现REST API用于配置管理和数据查询
|
||||
|
||||
4. 配置管理 ( `config.go` )
|
||||
- 定义配置结构和加载功能
|
||||
- 支持JSON格式配置文件
|
||||
|
||||
## 主要功能特性
|
||||
### 1. 域名屏蔽系统
|
||||
- 支持本地规则文件和远程规则URL
|
||||
- 多种屏蔽方式:NXDOMAIN、refused、emptyIP、customIP
|
||||
- 支持域名精确匹配和正则表达式匹配
|
||||
- 远程规则自动缓存和更新机制
|
||||
### 2. Hosts管理
|
||||
- 支持自定义Hosts映射
|
||||
- 提供Web界面管理Hosts条目
|
||||
- 自动保存Hosts配置
|
||||
### 3. 统计分析功能
|
||||
- 记录屏蔽域名统计信息
|
||||
- 记录解析域名统计信息
|
||||
- 提供按小时统计的屏蔽数据
|
||||
- 支持查询最常屏蔽和解析的域名
|
||||
### 4. 远程规则管理
|
||||
- 支持添加多个远程规则URL
|
||||
- 自动定期更新远程规则
|
||||
- 本地缓存机制确保规则可用性
|
||||
- Web界面可视化管理
|
||||
### 5. 管理界面
|
||||
- 提供直观的Web控制台
|
||||
- 支持查看服务器状态和统计信息
|
||||
- 规则管理和配置修改
|
||||
- DNS查询测试工具
|
||||
## 项目结构
|
||||
```
|
||||
/root/dns/
|
||||
├── config/ # 配置管理
|
||||
├── data/ # 数据目录(包含缓存和统计)
|
||||
│ └── remote_rules/ # 远程规则缓存
|
||||
├── dns/ # DNS服务器核心
|
||||
├── http/ # HTTP控制台
|
||||
├── logger/ # 日志系统
|
||||
├── shield/ # 屏蔽规则管理
|
||||
├── static/ # 静态Web文件
|
||||
├── main.go # 程序入口
|
||||
└── config.json # 配置文件
|
||||
```
|
||||
## 配置项说明
|
||||
主要配置文件 `config.json` 包含以下部分:
|
||||
|
||||
- DNS配置 :端口、上游DNS服务器、超时设置等
|
||||
- HTTP配置 :控制台端口、主机绑定等
|
||||
- 屏蔽配置 :规则文件路径、远程规则URL、更新间隔等
|
||||
- 日志配置 :日志文件路径、级别设置等
|
||||
## 使用场景
|
||||
1. 网络内容过滤(广告、恶意网站屏蔽)
|
||||
2. 本地DNS缓存加速
|
||||
3. 企业/家庭网络DNS管理
|
||||
4. 开发测试环境DNS重定向
|
||||
## 技术栈
|
||||
- 语言 :Go
|
||||
- DNS库 :github.com/miekg/dns
|
||||
- 日志库 :github.com/sirupsen/logrus
|
||||
- Web前端 :HTML/CSS/JavaScript
|
||||
该DNS服务器具有高性能、功能全面、易于配置等特点,适用于需要精确控制DNS查询结果的各种网络环境。
|
||||
@@ -0,0 +1,4 @@
|
||||
# DNS Server Hosts File
|
||||
# Generated by DNS Server
|
||||
|
||||
::1 localhost
|
||||
+10607
File diff suppressed because it is too large
Load Diff
+82585
File diff suppressed because it is too large
Load Diff
+53291
File diff suppressed because it is too large
Load Diff
+1176
File diff suppressed because it is too large
Load Diff
+549303
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
/example.com/
|
||||
/ad./
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"blockedDomainsCount": {},
|
||||
"resolvedDomainsCount": {},
|
||||
"lastSaved": "2025-11-28T19:19:31.852280335+08:00"
|
||||
}
|
||||
+3584
File diff suppressed because it is too large
Load Diff
Executable
BIN
Binary file not shown.
+21
-10
@@ -55,6 +55,7 @@ type Server struct {
|
||||
shieldConfig *config.ShieldConfig
|
||||
shieldManager *shield.ShieldManager
|
||||
server *dns.Server
|
||||
tcpServer *dns.Server
|
||||
resolver *dns.Client
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
@@ -159,8 +160,8 @@ func (s *Server) Start() error {
|
||||
Handler: dns.HandlerFunc(s.handleDNSRequest),
|
||||
}
|
||||
|
||||
// 启动TCP服务器(用于大型响应)
|
||||
tcpServer := &dns.Server{
|
||||
// 保存TCP服务器实例,以便在Stop方法中关闭
|
||||
s.tcpServer = &dns.Server{
|
||||
Addr: fmt.Sprintf(":%d", s.config.Port),
|
||||
Net: "tcp",
|
||||
Handler: dns.HandlerFunc(s.handleDNSRequest),
|
||||
@@ -184,7 +185,7 @@ func (s *Server) Start() error {
|
||||
// 启动TCP服务
|
||||
go func() {
|
||||
logger.Info(fmt.Sprintf("DNS TCP服务器启动,监听端口: %d", s.config.Port))
|
||||
if err := tcpServer.ListenAndServe(); err != nil {
|
||||
if err := s.tcpServer.ListenAndServe(); err != nil {
|
||||
logger.Error("DNS TCP服务器启动失败", "error", err)
|
||||
s.cancel()
|
||||
}
|
||||
@@ -218,6 +219,9 @@ func (s *Server) Stop() {
|
||||
if s.server != nil {
|
||||
s.server.Shutdown()
|
||||
}
|
||||
if s.tcpServer != nil {
|
||||
s.tcpServer.Shutdown()
|
||||
}
|
||||
logger.Info("DNS服务器已停止")
|
||||
}
|
||||
|
||||
@@ -783,11 +787,18 @@ func (s *Server) saveStatsData() {
|
||||
return
|
||||
}
|
||||
|
||||
// 创建数据目录
|
||||
statsDir := filepath.Dir(s.config.StatsFile)
|
||||
err := os.MkdirAll(statsDir, 0755)
|
||||
// 获取绝对路径以避免工作目录问题
|
||||
statsFilePath, err := filepath.Abs(s.config.StatsFile)
|
||||
if err != nil {
|
||||
logger.Error("创建统计数据目录失败", "error", err)
|
||||
logger.Error("获取统计文件绝对路径失败", "path", s.config.StatsFile, "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 创建数据目录
|
||||
statsDir := filepath.Dir(statsFilePath)
|
||||
err = os.MkdirAll(statsDir, 0755)
|
||||
if err != nil {
|
||||
logger.Error("创建统计数据目录失败", "dir", statsDir, "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -849,13 +860,13 @@ func (s *Server) saveStatsData() {
|
||||
}
|
||||
|
||||
// 写入文件
|
||||
err = ioutil.WriteFile(s.config.StatsFile, jsonData, 0644)
|
||||
err = os.WriteFile(statsFilePath, jsonData, 0644)
|
||||
if err != nil {
|
||||
logger.Error("保存统计数据到文件失败", "error", err)
|
||||
logger.Error("保存统计数据到文件失败", "file", statsFilePath, "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("统计数据保存成功")
|
||||
logger.Info("统计数据保存成功", "file", statsFilePath)
|
||||
}
|
||||
|
||||
// startCpuUsageMonitor 启动CPU使用率监控
|
||||
|
||||
+119935
File diff suppressed because it is too large
Load Diff
Executable
BIN
Binary file not shown.
+2
-14
@@ -205,12 +205,7 @@
|
||||
<span>Hosts管理</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#blacklists" class="flex items-center px-4 py-3 text-gray-700 hover:bg-gray-100 rounded-md transition-all">
|
||||
<i class="fa fa-ban mr-3 text-lg"></i>
|
||||
<span>黑名单管理</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="#query" class="flex items-center px-4 py-3 text-gray-700 hover:bg-gray-100 rounded-md transition-all">
|
||||
<i class="fa fa-search mr-3 text-lg"></i>
|
||||
@@ -843,14 +838,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="blacklists-content" class="hidden">
|
||||
<!-- 黑名单管理页面内容 -->
|
||||
<div class="bg-white rounded-lg p-6 card-shadow">
|
||||
<h3 class="text-lg font-semibold mb-6">黑名单管理</h3>
|
||||
<!-- 这里将添加黑名单管理相关内容 -->
|
||||
<p>黑名单管理页面内容待实现</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="query-content" class="hidden space-y-6">
|
||||
<!-- DNS查询表单 -->
|
||||
|
||||
+91
-63
@@ -2708,77 +2708,105 @@ function handlePageSwitch() {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理hash变化
|
||||
function handleHashChange() {
|
||||
let hash = window.location.hash;
|
||||
|
||||
// 如果没有hash,默认设置为#dashboard
|
||||
if (!hash) {
|
||||
hash = '#dashboard';
|
||||
window.location.hash = hash;
|
||||
return;
|
||||
}
|
||||
|
||||
const targetId = hash.substring(1);
|
||||
|
||||
// 查找对应的菜单项
|
||||
let targetMenuItem = null;
|
||||
menuItems.forEach(item => {
|
||||
if (item.getAttribute('href') === hash) {
|
||||
targetMenuItem = item;
|
||||
}
|
||||
});
|
||||
|
||||
// 如果找到了对应的菜单项,切换页面
|
||||
if (targetMenuItem) {
|
||||
switchPage(targetId, targetMenuItem);
|
||||
} else {
|
||||
// 如果没有找到对应的菜单项,尝试显示对应的内容
|
||||
const contentElement = document.getElementById(`${targetId}-content`);
|
||||
if (contentElement) {
|
||||
// 隐藏所有内容
|
||||
document.querySelectorAll('[id$="-content"]').forEach(content => {
|
||||
content.classList.add('hidden');
|
||||
});
|
||||
|
||||
// 显示目标内容
|
||||
contentElement.classList.remove('hidden');
|
||||
|
||||
// 查找对应的菜单项并更新活动状态
|
||||
menuItems.forEach(item => {
|
||||
item.classList.remove('sidebar-item-active');
|
||||
if (item.getAttribute('href') === hash) {
|
||||
item.classList.add('sidebar-item-active');
|
||||
// 更新页面标题
|
||||
document.getElementById('page-title').textContent = item.querySelector('span').textContent;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 如果没有找到对应的内容,默认显示dashboard
|
||||
window.location.hash = '#dashboard';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化hash路由
|
||||
function initHashRoute() {
|
||||
handleHashChange();
|
||||
}
|
||||
|
||||
// 监听hash变化事件
|
||||
window.addEventListener('hashchange', handleHashChange);
|
||||
|
||||
menuItems.forEach(item => {
|
||||
item.addEventListener('click', (e) => {
|
||||
// 允许默认的hash变化
|
||||
// 页面切换会由hashchange事件处理
|
||||
});
|
||||
});
|
||||
|
||||
// 初始化hash路由
|
||||
initHashRoute();
|
||||
}
|
||||
|
||||
// 处理hash变化 - 全局函数,确保在页面加载时就能被调用
|
||||
function handleHashChange() {
|
||||
let hash = window.location.hash;
|
||||
|
||||
// 如果没有hash,默认设置为#dashboard
|
||||
if (!hash) {
|
||||
hash = '#dashboard';
|
||||
window.location.hash = hash;
|
||||
return;
|
||||
}
|
||||
|
||||
const targetId = hash.substring(1);
|
||||
const menuItems = document.querySelectorAll('nav a');
|
||||
|
||||
// 首先检查是否存在对应的内容元素
|
||||
const contentElement = document.getElementById(`${targetId}-content`);
|
||||
|
||||
// 查找对应的菜单项
|
||||
let targetMenuItem = null;
|
||||
menuItems.forEach(item => {
|
||||
if (item.getAttribute('href') === hash) {
|
||||
targetMenuItem = item;
|
||||
}
|
||||
});
|
||||
|
||||
// 如果找到了对应的内容元素,直接显示
|
||||
if (contentElement) {
|
||||
// 隐藏所有内容
|
||||
document.querySelectorAll('[id$="-content"]').forEach(content => {
|
||||
content.classList.add('hidden');
|
||||
});
|
||||
|
||||
// 显示目标内容
|
||||
contentElement.classList.remove('hidden');
|
||||
|
||||
// 更新活动菜单项和页面标题
|
||||
menuItems.forEach(item => {
|
||||
item.classList.remove('sidebar-item-active');
|
||||
});
|
||||
|
||||
if (targetMenuItem) {
|
||||
targetMenuItem.classList.add('sidebar-item-active');
|
||||
// 更新页面标题
|
||||
const pageTitle = targetMenuItem.querySelector('span').textContent;
|
||||
document.getElementById('page-title').textContent = pageTitle;
|
||||
} else {
|
||||
// 如果没有找到对应的菜单项,尝试根据hash更新页面标题
|
||||
const titleElement = document.getElementById(`${targetId}-title`);
|
||||
if (titleElement) {
|
||||
document.getElementById('page-title').textContent = titleElement.textContent;
|
||||
}
|
||||
}
|
||||
} else if (targetMenuItem) {
|
||||
// 隐藏所有内容
|
||||
document.querySelectorAll('[id$="-content"]').forEach(content => {
|
||||
content.classList.add('hidden');
|
||||
});
|
||||
|
||||
// 显示目标内容
|
||||
document.getElementById(`${targetId}-content`).classList.remove('hidden');
|
||||
|
||||
// 更新页面标题
|
||||
document.getElementById('page-title').textContent = targetMenuItem.querySelector('span').textContent;
|
||||
|
||||
// 更新活动菜单项
|
||||
menuItems.forEach(item => {
|
||||
item.classList.remove('sidebar-item-active');
|
||||
});
|
||||
targetMenuItem.classList.add('sidebar-item-active');
|
||||
|
||||
// 侧边栏切换(移动端)
|
||||
if (window.innerWidth < 1024) {
|
||||
toggleSidebar();
|
||||
}
|
||||
} else {
|
||||
// 如果既没有找到内容元素也没有找到菜单项,默认显示dashboard
|
||||
window.location.hash = '#dashboard';
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化hash路由
|
||||
function initHashRoute() {
|
||||
handleHashChange();
|
||||
}
|
||||
|
||||
// 监听hash变化事件 - 全局事件监听器
|
||||
window.addEventListener('hashchange', handleHashChange);
|
||||
|
||||
// 初始化hash路由 - 确保在页面加载时就能被调用
|
||||
initHashRoute();
|
||||
|
||||
// 侧边栏切换
|
||||
function toggleSidebar() {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
|
||||
+6
-22
@@ -8,7 +8,6 @@ function setupNavigation() {
|
||||
document.getElementById('dashboard-content'),
|
||||
document.getElementById('shield-content'),
|
||||
document.getElementById('hosts-content'),
|
||||
document.getElementById('blacklists-content'),
|
||||
document.getElementById('query-content'),
|
||||
document.getElementById('config-content')
|
||||
];
|
||||
@@ -16,30 +15,15 @@ function setupNavigation() {
|
||||
|
||||
menuItems.forEach((item, index) => {
|
||||
item.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
// 允许浏览器自动更新地址栏中的hash,不阻止默认行为
|
||||
|
||||
// 更新活跃状态
|
||||
menuItems.forEach(menuItem => {
|
||||
menuItem.classList.remove('sidebar-item-active');
|
||||
});
|
||||
item.classList.add('sidebar-item-active');
|
||||
|
||||
// 隐藏所有内容部分
|
||||
contentSections.forEach(section => {
|
||||
section.classList.add('hidden');
|
||||
});
|
||||
|
||||
// 显示对应内容部分
|
||||
const target = item.getAttribute('href').substring(1);
|
||||
const activeContent = document.getElementById(`${target}-content`);
|
||||
if (activeContent) {
|
||||
activeContent.classList.remove('hidden');
|
||||
// 移动端点击菜单项后自动关闭侧边栏
|
||||
if (window.innerWidth < 768) {
|
||||
closeSidebar();
|
||||
}
|
||||
|
||||
// 更新页面标题
|
||||
pageTitle.textContent = item.querySelector('span').textContent;
|
||||
|
||||
// 页面特定初始化
|
||||
// 页面特定初始化 - 保留这部分逻辑,因为它不会与hashchange事件处理逻辑冲突
|
||||
const target = item.getAttribute('href').substring(1);
|
||||
if (target === 'shield' && typeof initShieldPage === 'function') {
|
||||
initShieldPage();
|
||||
} else if (target === 'hosts' && typeof initHostsPage === 'function') {
|
||||
|
||||
Reference in New Issue
Block a user