修复服务器重启时端口被占用和数据保存问题
This commit is contained in:
95
.trae/documents/修复Web刷新时自动回到仪表盘页面的问题.md
Normal file
95
.trae/documents/修复Web刷新时自动回到仪表盘页面的问题.md
Normal file
@@ -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';
|
||||
}
|
||||
}
|
||||
```
|
||||
57
.trae/documents/修复服务器重启时端口被占用和数据保存问题.md
Normal file
57
.trae/documents/修复服务器重启时端口被占用和数据保存问题.md
Normal file
@@ -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
|
||||
- 服务器重启时能够正常启动,不会出现相同的问题
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 确保在修改配置文件之前备份原始文件
|
||||
- 确保程序有足够的权限创建和写入文件
|
||||
- 确保在关闭旧进程之前,所有重要的数据都已保存
|
||||
- 测试修复效果时,确保覆盖所有可能的情况
|
||||
28
.trae/documents/修复用户点击选项时不触发地址栏#后缀的问题.md
Normal file
28
.trae/documents/修复用户点击选项时不触发地址栏#后缀的问题.md
Normal file
@@ -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 可以导航到对应页面
|
||||
- 所有页面导航逻辑统一,避免冲突
|
||||
81
ReadMe.md
Normal file
81
ReadMe.md
Normal file
@@ -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查询结果的各种网络环境。
|
||||
4
data/hosts.txt
Normal file
4
data/hosts.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
# DNS Server Hosts File
|
||||
# Generated by DNS Server
|
||||
|
||||
::1 localhost
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2
data/rules.txt
Normal file
2
data/rules.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
/example.com/
|
||||
/ad./
|
||||
5
data/shield_stats.json
Normal file
5
data/shield_stats.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"blockedDomainsCount": {},
|
||||
"resolvedDomainsCount": {},
|
||||
"lastSaved": "2025-11-28T19:19:31.852280335+08:00"
|
||||
}
|
||||
3584
data/stats.json
Normal file
3584
data/stats.json
Normal file
File diff suppressed because it is too large
Load Diff
BIN
dns-server
Executable file
BIN
dns-server
Executable file
Binary file not shown.
@@ -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
logs/dns-server.log
Normal file
119935
logs/dns-server.log
Normal file
File diff suppressed because it is too large
Load Diff
BIN
output/dns-server
Executable file
BIN
output/dns-server
Executable file
Binary file not shown.
@@ -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查询表单 -->
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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