修复服务器重启时端口被占用和数据保存问题
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
|
shieldConfig *config.ShieldConfig
|
||||||
shieldManager *shield.ShieldManager
|
shieldManager *shield.ShieldManager
|
||||||
server *dns.Server
|
server *dns.Server
|
||||||
|
tcpServer *dns.Server
|
||||||
resolver *dns.Client
|
resolver *dns.Client
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
@@ -159,8 +160,8 @@ func (s *Server) Start() error {
|
|||||||
Handler: dns.HandlerFunc(s.handleDNSRequest),
|
Handler: dns.HandlerFunc(s.handleDNSRequest),
|
||||||
}
|
}
|
||||||
|
|
||||||
// 启动TCP服务器(用于大型响应)
|
// 保存TCP服务器实例,以便在Stop方法中关闭
|
||||||
tcpServer := &dns.Server{
|
s.tcpServer = &dns.Server{
|
||||||
Addr: fmt.Sprintf(":%d", s.config.Port),
|
Addr: fmt.Sprintf(":%d", s.config.Port),
|
||||||
Net: "tcp",
|
Net: "tcp",
|
||||||
Handler: dns.HandlerFunc(s.handleDNSRequest),
|
Handler: dns.HandlerFunc(s.handleDNSRequest),
|
||||||
@@ -184,7 +185,7 @@ func (s *Server) Start() error {
|
|||||||
// 启动TCP服务
|
// 启动TCP服务
|
||||||
go func() {
|
go func() {
|
||||||
logger.Info(fmt.Sprintf("DNS TCP服务器启动,监听端口: %d", s.config.Port))
|
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)
|
logger.Error("DNS TCP服务器启动失败", "error", err)
|
||||||
s.cancel()
|
s.cancel()
|
||||||
}
|
}
|
||||||
@@ -218,6 +219,9 @@ func (s *Server) Stop() {
|
|||||||
if s.server != nil {
|
if s.server != nil {
|
||||||
s.server.Shutdown()
|
s.server.Shutdown()
|
||||||
}
|
}
|
||||||
|
if s.tcpServer != nil {
|
||||||
|
s.tcpServer.Shutdown()
|
||||||
|
}
|
||||||
logger.Info("DNS服务器已停止")
|
logger.Info("DNS服务器已停止")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -783,11 +787,18 @@ func (s *Server) saveStatsData() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建数据目录
|
// 获取绝对路径以避免工作目录问题
|
||||||
statsDir := filepath.Dir(s.config.StatsFile)
|
statsFilePath, err := filepath.Abs(s.config.StatsFile)
|
||||||
err := os.MkdirAll(statsDir, 0755)
|
|
||||||
if err != nil {
|
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
|
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 {
|
if err != nil {
|
||||||
logger.Error("保存统计数据到文件失败", "error", err)
|
logger.Error("保存统计数据到文件失败", "file", statsFilePath, "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info("统计数据保存成功")
|
logger.Info("统计数据保存成功", "file", statsFilePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// startCpuUsageMonitor 启动CPU使用率监控
|
// 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>
|
<span>Hosts管理</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</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>
|
<li>
|
||||||
<a href="#query" class="flex items-center px-4 py-3 text-gray-700 hover:bg-gray-100 rounded-md transition-all">
|
<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>
|
<i class="fa fa-search mr-3 text-lg"></i>
|
||||||
@@ -843,14 +838,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<div id="query-content" class="hidden space-y-6">
|
||||||
<!-- DNS查询表单 -->
|
<!-- DNS查询表单 -->
|
||||||
|
|||||||
+63
-35
@@ -2708,8 +2708,16 @@ function handlePageSwitch() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理hash变化
|
menuItems.forEach(item => {
|
||||||
function handleHashChange() {
|
item.addEventListener('click', (e) => {
|
||||||
|
// 允许默认的hash变化
|
||||||
|
// 页面切换会由hashchange事件处理
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理hash变化 - 全局函数,确保在页面加载时就能被调用
|
||||||
|
function handleHashChange() {
|
||||||
let hash = window.location.hash;
|
let hash = window.location.hash;
|
||||||
|
|
||||||
// 如果没有hash,默认设置为#dashboard
|
// 如果没有hash,默认设置为#dashboard
|
||||||
@@ -2720,6 +2728,10 @@ function handlePageSwitch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const targetId = hash.substring(1);
|
const targetId = hash.substring(1);
|
||||||
|
const menuItems = document.querySelectorAll('nav a');
|
||||||
|
|
||||||
|
// 首先检查是否存在对应的内容元素
|
||||||
|
const contentElement = document.getElementById(`${targetId}-content`);
|
||||||
|
|
||||||
// 查找对应的菜单项
|
// 查找对应的菜单项
|
||||||
let targetMenuItem = null;
|
let targetMenuItem = null;
|
||||||
@@ -2729,12 +2741,7 @@ function handlePageSwitch() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 如果找到了对应的菜单项,切换页面
|
// 如果找到了对应的内容元素,直接显示
|
||||||
if (targetMenuItem) {
|
|
||||||
switchPage(targetId, targetMenuItem);
|
|
||||||
} else {
|
|
||||||
// 如果没有找到对应的菜单项,尝试显示对应的内容
|
|
||||||
const contentElement = document.getElementById(`${targetId}-content`);
|
|
||||||
if (contentElement) {
|
if (contentElement) {
|
||||||
// 隐藏所有内容
|
// 隐藏所有内容
|
||||||
document.querySelectorAll('[id$="-content"]').forEach(content => {
|
document.querySelectorAll('[id$="-content"]').forEach(content => {
|
||||||
@@ -2744,41 +2751,62 @@ function handlePageSwitch() {
|
|||||||
// 显示目标内容
|
// 显示目标内容
|
||||||
contentElement.classList.remove('hidden');
|
contentElement.classList.remove('hidden');
|
||||||
|
|
||||||
// 查找对应的菜单项并更新活动状态
|
// 更新活动菜单项和页面标题
|
||||||
menuItems.forEach(item => {
|
menuItems.forEach(item => {
|
||||||
item.classList.remove('sidebar-item-active');
|
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;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (targetMenuItem) {
|
||||||
|
targetMenuItem.classList.add('sidebar-item-active');
|
||||||
|
// 更新页面标题
|
||||||
|
const pageTitle = targetMenuItem.querySelector('span').textContent;
|
||||||
|
document.getElementById('page-title').textContent = pageTitle;
|
||||||
} else {
|
} else {
|
||||||
// 如果没有找到对应的内容,默认显示dashboard
|
// 如果没有找到对应的菜单项,尝试根据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';
|
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 initHashRoute() {
|
||||||
|
handleHashChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听hash变化事件 - 全局事件监听器
|
||||||
|
window.addEventListener('hashchange', handleHashChange);
|
||||||
|
|
||||||
|
// 初始化hash路由 - 确保在页面加载时就能被调用
|
||||||
|
initHashRoute();
|
||||||
|
|
||||||
// 侧边栏切换
|
// 侧边栏切换
|
||||||
function toggleSidebar() {
|
function toggleSidebar() {
|
||||||
const sidebar = document.getElementById('sidebar');
|
const sidebar = document.getElementById('sidebar');
|
||||||
|
|||||||
+6
-22
@@ -8,7 +8,6 @@ function setupNavigation() {
|
|||||||
document.getElementById('dashboard-content'),
|
document.getElementById('dashboard-content'),
|
||||||
document.getElementById('shield-content'),
|
document.getElementById('shield-content'),
|
||||||
document.getElementById('hosts-content'),
|
document.getElementById('hosts-content'),
|
||||||
document.getElementById('blacklists-content'),
|
|
||||||
document.getElementById('query-content'),
|
document.getElementById('query-content'),
|
||||||
document.getElementById('config-content')
|
document.getElementById('config-content')
|
||||||
];
|
];
|
||||||
@@ -16,30 +15,15 @@ function setupNavigation() {
|
|||||||
|
|
||||||
menuItems.forEach((item, index) => {
|
menuItems.forEach((item, index) => {
|
||||||
item.addEventListener('click', (e) => {
|
item.addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
// 允许浏览器自动更新地址栏中的hash,不阻止默认行为
|
||||||
|
|
||||||
// 更新活跃状态
|
// 移动端点击菜单项后自动关闭侧边栏
|
||||||
menuItems.forEach(menuItem => {
|
if (window.innerWidth < 768) {
|
||||||
menuItem.classList.remove('sidebar-item-active');
|
closeSidebar();
|
||||||
});
|
|
||||||
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');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新页面标题
|
// 页面特定初始化 - 保留这部分逻辑,因为它不会与hashchange事件处理逻辑冲突
|
||||||
pageTitle.textContent = item.querySelector('span').textContent;
|
const target = item.getAttribute('href').substring(1);
|
||||||
|
|
||||||
// 页面特定初始化
|
|
||||||
if (target === 'shield' && typeof initShieldPage === 'function') {
|
if (target === 'shield' && typeof initShieldPage === 'function') {
|
||||||
initShieldPage();
|
initShieldPage();
|
||||||
} else if (target === 'hosts' && typeof initHostsPage === 'function') {
|
} else if (target === 'hosts' && typeof initHostsPage === 'function') {
|
||||||
|
|||||||
Reference in New Issue
Block a user