Web优化

This commit is contained in:
Alex Yang
2025-11-28 23:59:58 +08:00
parent 8e2ea02a62
commit 3207510c91
8 changed files with 649 additions and 109 deletions

View File

@@ -0,0 +1,37 @@
# 修复保存按钮错位问题
## 问题分析
远程黑名单管理部分的保存按钮错位,原因是:
- 添加黑名单表单使用了Grid布局`grid grid-cols-1 md:grid-cols-3 gap-4`
- 前两个子元素包含label和input高度较高
- 第三个子元素只有按钮和状态显示没有label高度较矮
- 导致按钮在垂直方向上与前两个输入框不对齐
## 解决方案
修改添加黑名单表单的HTML结构确保三个子元素在垂直方向上对齐
1. **为第三个子元素添加垂直对齐**
- 确保保存按钮容器与前两个输入框容器在垂直方向上对齐
- 可以选择顶部对齐或中间对齐,保持视觉一致性
2. **统一子元素结构**
- 为第三个子元素添加一个隐藏的label确保结构一致
- 或者调整Grid布局使第三个子元素的内容垂直居中
3. **调整容器样式**
- 修改保存按钮容器的样式,确保垂直对齐
- 可以使用`items-start``items-center`属性
## 实现步骤
1. 修改`add-blacklist-form`的HTML结构
- 为第三个子元素添加`items-start`类,使其与前两个子元素的顶部对齐
- 或者添加一个隐藏的label确保结构一致
2. 测试修改后的效果
- 确保保存按钮与前两个输入框在垂直方向上对齐
- 保持响应式布局,在不同屏幕尺寸下都能正常显示
## 预期效果
保存按钮将与前两个输入框在垂直方向上对齐,消除错位现象,提升表单的视觉一致性和用户体验。

View File

@@ -0,0 +1,20 @@
## 问题分析
从截图可以看出,远程黑名单管理的保存按钮与输入框在垂直方向上没有对齐,按钮位置偏下。
## 原因分析
1. 当前使用grid布局分为3列前两列包含label和input第三列包含空label和按钮容器
2. 空label虽然没有内容但仍然占据了空间mb-1 margin导致按钮被推到下方
3. 按钮容器使用了`items-center`但整体位置受label影响
## 修复方案
修改第三列的HTML结构移除空label直接放置按钮容器并使用flex布局的对齐属性确保按钮与输入框垂直对齐。
## 具体修改
1. 打开`/root/dns/static/index.html`文件
2. 找到远程黑名单管理的添加表单部分第839-847行
3. 修改第三列的HTML结构移除空label直接放置按钮容器
4. 使用flex布局的`items-end`属性确保按钮与输入框底部对齐
5. 调整按钮的margin-top为0确保与输入框精确对齐
## 预期效果
保存按钮与输入框在垂直方向上精确对齐,整体布局更加美观和专业。

View File

@@ -0,0 +1,29 @@
# 修改远程黑名单管理界面,添加状态渐变效果
## 问题分析
当前远程黑名单管理界面的状态更新功能已经实现了3秒后恢复默认状态的功能但没有添加渐变效果用户体验不够流畅。
## 解决方案
修改`updateStatus`函数,为状态元素添加渐变效果,确保状态更新时的平滑过渡。
## 实现步骤
1. **修改`updateStatus`函数**
- 为状态元素添加CSS过渡效果
- 使用`classList`操作来添加/移除渐变类
- 确保状态更新时的平滑过渡
2. **添加CSS渐变样式**
- 在index.html中添加状态渐变的CSS样式
- 为不同状态loading、success、error添加不同的渐变效果
3. **测试修改后的功能**
- 确保状态更新时有平滑的渐变效果
- 确保3秒后状态能正常恢复默认
## 预期效果
远程黑名单管理界面的状态更新将具有平滑的渐变效果,包括:
- 加载状态的蓝色渐变效果
- 成功状态的绿色渐变效果
- 错误状态的红色渐变效果
- 所有状态在3秒后平滑消失恢复默认状态

View File

@@ -0,0 +1,33 @@
# 根据服务器日志判断操作成功状态
## 问题分析
当前代码中部分操作只检查了HTTP响应状态码没有检查服务器返回的`status`字段,这可能导致在某些情况下无法正确判断操作是否成功。根据服务器端代码,所有成功的响应都会返回`{"status": "success"}`,因此需要确保前端代码在所有操作中都检查这个字段。
## 解决方案
修改以下函数,确保它们都根据服务器返回的`status`字段判断操作是否成功:
1. **handleDeleteBlacklist** (line 653-716)
- 添加对服务器响应数据中`status`字段的检查
- 确保只有当`status``success`时才认为删除成功
2. **handleToggleBlacklist** (line 719-793)
- 添加对服务器响应数据中`status`字段的检查
- 确保只有当`status``success`时才认为切换状态成功
3. **handleAddRule** (line 378-409)
- 添加对服务器响应数据中`status`字段的检查
- 确保只有当`status``success`时才认为添加规则成功
4. **handleDeleteRule** (line 309-375)
- 添加对服务器响应数据中`status`字段的检查
- 确保只有当`status``success`时才认为删除规则成功
## 实现步骤
1. 修改`handleDeleteBlacklist`函数添加响应数据解析和status字段检查
2. 修改`handleToggleBlacklist`函数添加响应数据解析和status字段检查
3. 修改`handleAddRule`函数添加响应数据解析和status字段检查
4. 修改`handleDeleteRule`函数添加响应数据解析和status字段检查
5. 测试所有操作,确保它们都能正确根据服务器响应判断成功或失败
## 预期效果
所有操作(添加、更新、删除、切换状态)都会根据服务器返回的`status`字段判断是否成功,确保操作结果与服务器日志一致。

View File

@@ -21,19 +21,19 @@
"name": "AdGuard DNS filter", "name": "AdGuard DNS filter",
"url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/filter.txt", "url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/filter.txt",
"enabled": true, "enabled": true,
"lastUpdateTime": "2025-11-28T14:43:26.972Z" "lastUpdateTime": "2025-11-28T15:45:11.073Z"
}, },
{ {
"name": "Adaway Default Blocklist", "name": "Adaway Default Blocklist",
"url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-Filters/raw/branch/main/hosts/adaway.txt", "url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-Filters/raw/branch/main/hosts/adaway.txt",
"enabled": true, "enabled": true,
"lastUpdateTime": "2025-11-28T14:41:39.123Z" "lastUpdateTime": "2025-11-28T15:36:43.086Z"
}, },
{ {
"name": "CHN-anti-AD", "name": "CHN-anti-AD",
"url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-Filters/raw/branch/main/list/easylist.txt", "url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-Filters/raw/branch/main/list/easylist.txt",
"enabled": true, "enabled": true,
"lastUpdateTime": "2025-11-28T14:41:41.318Z" "lastUpdateTime": "2025-11-28T15:26:24.833Z"
}, },
{ {
"name": "My GitHub Rules", "name": "My GitHub Rules",
@@ -42,7 +42,7 @@
"lastUpdateTime": "2025-11-28T14:42:09.271Z" "lastUpdateTime": "2025-11-28T14:42:09.271Z"
}, },
{ {
"name": "China List", "name": "CNList",
"url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/list/china.list", "url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/list/china.list",
"enabled": true "enabled": true
} }

View File

@@ -752,7 +752,7 @@ func (s *Server) handleShieldBlacklists(w http.ResponseWriter, r *http.Request)
} }
if targetURLOrName == "" { if targetURLOrName == "" {
http.Error(w, "黑名单标识不能为空", http.StatusBadRequest) json.NewEncoder(w).Encode(map[string]string{"status": "error", "message": "黑名单标识不能为空"})
return return
} }
@@ -767,7 +767,7 @@ func (s *Server) handleShieldBlacklists(w http.ResponseWriter, r *http.Request)
} }
if targetIndex == -1 { if targetIndex == -1 {
http.Error(w, "黑名单不存在", http.StatusNotFound) json.NewEncoder(w).Encode(map[string]string{"status": "error", "message": "黑名单不存在"})
return return
} }
@@ -780,7 +780,7 @@ func (s *Server) handleShieldBlacklists(w http.ResponseWriter, r *http.Request)
// 保存配置到文件 // 保存配置到文件
if err := saveConfigToFile(s.globalConfig, "config.json"); err != nil { if err := saveConfigToFile(s.globalConfig, "config.json"); err != nil {
logger.Error("保存配置文件失败", "error", err) logger.Error("保存配置文件失败", "error", err)
http.Error(w, "保存配置失败", http.StatusInternalServerError) json.NewEncoder(w).Encode(map[string]string{"status": "error", "message": "保存配置失败"})
return return
} }
// 重新加载规则以获取最新的远程规则 // 重新加载规则以获取最新的远程规则
@@ -810,7 +810,7 @@ func (s *Server) handleShieldBlacklists(w http.ResponseWriter, r *http.Request)
// 保存配置到文件 // 保存配置到文件
if err := saveConfigToFile(s.globalConfig, "config.json"); err != nil { if err := saveConfigToFile(s.globalConfig, "config.json"); err != nil {
logger.Error("保存配置文件失败", "error", err) logger.Error("保存配置文件失败", "error", err)
http.Error(w, "保存配置失败", http.StatusInternalServerError) json.NewEncoder(w).Encode(map[string]string{"status": "error", "message": "保存配置失败"})
return return
} }
json.NewEncoder(w).Encode(map[string]string{"status": "success"}) json.NewEncoder(w).Encode(map[string]string{"status": "success"})
@@ -831,12 +831,12 @@ func (s *Server) handleShieldBlacklists(w http.ResponseWriter, r *http.Request)
} }
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest) json.NewEncoder(w).Encode(map[string]string{"status": "error", "message": "无效的请求体"})
return return
} }
if req.Name == "" || req.URL == "" { if req.Name == "" || req.URL == "" {
http.Error(w, "Name and URL are required", http.StatusBadRequest) json.NewEncoder(w).Encode(map[string]string{"status": "error", "message": "名称和URL不能为空"})
return return
} }
@@ -846,11 +846,17 @@ func (s *Server) handleShieldBlacklists(w http.ResponseWriter, r *http.Request)
// 检查是否已存在 // 检查是否已存在
for _, list := range blacklists { for _, list := range blacklists {
if list.URL == req.URL { if list.URL == req.URL {
http.Error(w, "黑名单URL已存在", http.StatusConflict) json.NewEncoder(w).Encode(map[string]string{"status": "error", "message": "黑名单URL已存在"})
return return
} }
} }
// 检查URL是否存在且可访问
if !checkURLExists(req.URL) {
json.NewEncoder(w).Encode(map[string]string{"status": "error", "message": "URL不存在或无法访问"})
return
}
// 添加新黑名单 // 添加新黑名单
newEntry := config.BlacklistEntry{ newEntry := config.BlacklistEntry{
Name: req.Name, Name: req.Name,
@@ -865,7 +871,7 @@ func (s *Server) handleShieldBlacklists(w http.ResponseWriter, r *http.Request)
// 保存配置到文件 // 保存配置到文件
if err := saveConfigToFile(s.globalConfig, "config.json"); err != nil { if err := saveConfigToFile(s.globalConfig, "config.json"); err != nil {
logger.Error("保存配置文件失败", "error", err) logger.Error("保存配置文件失败", "error", err)
http.Error(w, "保存配置失败", http.StatusInternalServerError) json.NewEncoder(w).Encode(map[string]string{"status": "error", "message": "保存配置失败"})
return return
} }
@@ -884,7 +890,7 @@ func (s *Server) handleShieldBlacklists(w http.ResponseWriter, r *http.Request)
} }
if err := json.NewDecoder(r.Body).Decode(&updatedBlacklists); err != nil { if err := json.NewDecoder(r.Body).Decode(&updatedBlacklists); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest) json.NewEncoder(w).Encode(map[string]string{"status": "error", "message": "无效的请求体"})
return return
} }
@@ -906,7 +912,7 @@ func (s *Server) handleShieldBlacklists(w http.ResponseWriter, r *http.Request)
// 保存配置到文件 // 保存配置到文件
if err := saveConfigToFile(s.globalConfig, "config.json"); err != nil { if err := saveConfigToFile(s.globalConfig, "config.json"); err != nil {
logger.Error("保存配置文件失败", "error", err) logger.Error("保存配置文件失败", "error", err)
http.Error(w, "保存配置失败", http.StatusInternalServerError) json.NewEncoder(w).Encode(map[string]string{"status": "error", "message": "保存配置失败"})
return return
} }
// 重新加载规则 // 重新加载规则
@@ -915,7 +921,7 @@ func (s *Server) handleShieldBlacklists(w http.ResponseWriter, r *http.Request)
json.NewEncoder(w).Encode(map[string]string{"status": "success"}) json.NewEncoder(w).Encode(map[string]string{"status": "success"})
default: default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) json.NewEncoder(w).Encode(map[string]string{"status": "error", "message": "Method not allowed"})
} }
} }
@@ -1193,6 +1199,24 @@ func isValidIP(ip string) bool {
return true return true
} }
// checkURLExists 检查URL是否存在且可访问
func checkURLExists(url string) bool {
// 创建一个带有超时的HTTP客户端
client := &http.Client{
Timeout: 5 * time.Second,
}
// 发送HEAD请求来检查URL是否存在
resp, err := client.Head(url)
if err != nil {
return false
}
defer resp.Body.Close()
// 检查状态码2xx和3xx表示成功
return resp.StatusCode >= 200 && resp.StatusCode < 400
}
// handleRestart 处理重启服务请求 // handleRestart 处理重启服务请求
func (s *Server) handleRestart(w http.ResponseWriter, r *http.Request) { func (s *Server) handleRestart(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost { if r.Method != http.MethodPost {

View File

@@ -166,6 +166,65 @@
text-shadow: 0 0 5px rgba(250, 204, 21, 0.3); text-shadow: 0 0 5px rgba(250, 204, 21, 0.3);
} }
} }
/* 状态过渡效果 */
.status-transition {
transition: all 0.3s ease-in-out;
}
/* 状态淡入动画 */
@keyframes status-fade-in {
0% {
opacity: 0;
transform: translateY(-5px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
/* 状态淡出动画 */
@keyframes status-fade-out {
0% {
opacity: 1;
transform: translateY(0);
}
100% {
opacity: 0;
transform: translateY(-5px);
}
}
/* 淡入类 */
.status-fade-in {
animation: status-fade-in 0.3s ease-in-out forwards;
}
/* 淡出类 */
.status-fade-out {
animation: status-fade-out 0.3s ease-in-out forwards;
}
/* 加载状态样式 */
.status-loading {
animation: status-pulse 1.5s ease-in-out infinite;
}
/* 状态脉冲动画 */
@keyframes status-pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.7;
}
}
/* 保存按钮状态样式 */
#save-blacklist-status {
transition: all 0.3s ease-in-out;
}
</style> </style>
</head> </head>
<body class="bg-gray-50 text-dark font-sans"> <body class="bg-gray-50 text-dark font-sans">
@@ -739,6 +798,7 @@
<button id="save-rule-btn" class="px-4 py-2 bg-success text-white rounded-md hover:bg-success/90 transition-colors"> <button id="save-rule-btn" class="px-4 py-2 bg-success text-white rounded-md hover:bg-success/90 transition-colors">
保存 保存
</button> </button>
<div id="save-rule-status" class="flex items-center text-sm"></div>
</div> </div>
</div> </div>
@@ -748,12 +808,13 @@
<thead> <thead>
<tr class="border-b border-gray-200"> <tr class="border-b border-gray-200">
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500">规则</th> <th class="text-left py-3 px-4 text-sm font-medium text-gray-500">规则</th>
<th class="text-center py-3 px-4 text-sm font-medium text-gray-500">状态</th>
<th class="text-right py-3 px-4 text-sm font-medium text-gray-500">操作</th> <th class="text-right py-3 px-4 text-sm font-medium text-gray-500">操作</th>
</tr> </tr>
</thead> </thead>
<tbody id="rules-table-body"> <tbody id="rules-table-body">
<tr> <tr>
<td colspan="2" class="py-4 text-center text-gray-500">暂无规则</td> <td colspan="3" class="py-4 text-center text-gray-500">暂无规则</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -775,11 +836,13 @@
<label for="blacklist-url" class="block text-sm font-medium text-gray-700 mb-1">URL</label> <label for="blacklist-url" class="block text-sm font-medium text-gray-700 mb-1">URL</label>
<input type="text" id="blacklist-url" placeholder="输入黑名单URL" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"> <input type="text" id="blacklist-url" placeholder="输入黑名单URL" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
</div> </div>
<div class="flex items-end space-x-2"> <div class="flex items-end">
<button id="save-blacklist-btn" class="px-4 py-2 bg-success text-white rounded-md hover:bg-success/90 transition-colors"> <div class="flex items-center space-x-2">
保存 <button id="save-blacklist-btn" class="px-4 py-2 bg-success text-white rounded-md hover:bg-success/90 transition-colors">
</button> 保存
<div id="save-blacklist-status" class="flex items-center text-sm"></div> </button>
<div id="save-blacklist-status" class="flex items-center text-sm"></div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -17,6 +17,9 @@ function updateStatus(url, status, message) {
const statusElement = document.getElementById(`update-status-${encodeURIComponent(url)}`); const statusElement = document.getElementById(`update-status-${encodeURIComponent(url)}`);
if (!statusElement) return; if (!statusElement) return;
// 清除之前的所有类
statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
let statusHTML = ''; let statusHTML = '';
switch (status) { switch (status) {
@@ -33,12 +36,90 @@ function updateStatus(url, status, message) {
statusHTML = '<span class="text-gray-400">-</span>'; statusHTML = '<span class="text-gray-400">-</span>';
} }
// 强制重排,确保过渡效果生效
void statusElement.offsetWidth;
// 设置新的HTML内容
statusElement.innerHTML = statusHTML; statusElement.innerHTML = statusHTML;
// 如果是成功或失败状态3秒后恢复默认状态 // 添加过渡类和对应状态类
statusElement.classList.add('status-transition');
// 如果不是默认状态,添加淡入动画和对应状态类
if (status !== 'default') {
statusElement.classList.add('status-fade-in');
statusElement.classList.add(`status-${status}`);
}
// 如果是成功或失败状态3秒后渐变消失
if (status === 'success' || status === 'error') { if (status === 'success' || status === 'error') {
setTimeout(() => { setTimeout(() => {
updateStatus(url, 'default'); // 添加淡出类
statusElement.classList.add('status-fade-out');
// 等待淡出动画完成后切换到默认状态
setTimeout(() => {
// 清除所有类
statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
// 设置默认状态
statusElement.innerHTML = '<span class="text-gray-400">-</span>';
}, 300); // 与CSS动画持续时间一致
}, 3000);
}
}
// 更新规则状态显示函数
function updateRuleStatus(rule, status, message) {
const statusElement = document.getElementById(`rule-status-${encodeURIComponent(rule)}`);
if (!statusElement) return;
// 清除之前的所有类
statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
let statusHTML = '';
switch (status) {
case 'loading':
statusHTML = '<span class="text-blue-500"><i class="fa fa-spinner fa-spin"></i> 处理中...</span>';
break;
case 'success':
statusHTML = `<span class="text-green-500"><i class="fa fa-check"></i> ${message || '成功'}</span>`;
break;
case 'error':
statusHTML = `<span class="text-red-500"><i class="fa fa-times"></i> ${message || '失败'}</span>`;
break;
default:
statusHTML = '<span class="text-gray-400">-</span>';
}
// 强制重排,确保过渡效果生效
void statusElement.offsetWidth;
// 设置新的HTML内容
statusElement.innerHTML = statusHTML;
// 添加过渡类和对应状态类
statusElement.classList.add('status-transition');
// 如果不是默认状态,添加淡入动画和对应状态类
if (status !== 'default') {
statusElement.classList.add('status-fade-in');
statusElement.classList.add(`status-${status}`);
}
// 如果是成功或失败状态3秒后渐变消失
if (status === 'success' || status === 'error') {
setTimeout(() => {
// 添加淡出类
statusElement.classList.add('status-fade-out');
// 等待淡出动画完成后切换到默认状态
setTimeout(() => {
// 清除所有类
statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
// 设置默认状态
statusElement.innerHTML = '<span class="text-gray-400">-</span>';
}, 300); // 与CSS动画持续时间一致
}, 3000); }, 3000);
} }
} }
@@ -248,7 +329,7 @@ function updateRulesTable(rules) {
if (rules.length === 0) { if (rules.length === 0) {
const emptyRow = document.createElement('tr'); const emptyRow = document.createElement('tr');
emptyRow.innerHTML = '<td colspan="2" class="py-4 text-center text-gray-500">暂无规则</td>'; emptyRow.innerHTML = '<td colspan="3" class="py-4 text-center text-gray-500">暂无规则</td>';
tbody.appendChild(emptyRow); tbody.appendChild(emptyRow);
return; return;
} }
@@ -268,6 +349,11 @@ function updateRulesTable(rules) {
tdRule.className = 'py-3 px-4'; tdRule.className = 'py-3 px-4';
tdRule.textContent = rule; tdRule.textContent = rule;
const tdStatus = document.createElement('td');
tdStatus.className = 'py-3 px-4 text-center';
tdStatus.id = `rule-status-${encodeURIComponent(rule)}`;
tdStatus.innerHTML = '<span class="text-gray-400">-</span>';
const tdAction = document.createElement('td'); const tdAction = document.createElement('td');
tdAction.className = 'py-3 px-4 text-right'; tdAction.className = 'py-3 px-4 text-right';
@@ -289,7 +375,9 @@ function updateRulesTable(rules) {
}; };
tdAction.appendChild(deleteBtn); tdAction.appendChild(deleteBtn);
tr.appendChild(tdRule); tr.appendChild(tdRule);
tr.appendChild(tdStatus);
tr.appendChild(tdAction); tr.appendChild(tdAction);
fragment.appendChild(tr); fragment.appendChild(tr);
}); });
@@ -300,7 +388,7 @@ function updateRulesTable(rules) {
// 如果有更多规则,添加提示 // 如果有更多规则,添加提示
if (rules.length > maxRulesToShow) { if (rules.length > maxRulesToShow) {
const infoRow = document.createElement('tr'); const infoRow = document.createElement('tr');
infoRow.innerHTML = `<td colspan="2" class="py-4 text-center text-gray-500">显示前 ${maxRulesToShow} 条规则,共 ${rules.length} 条</td>`; infoRow.innerHTML = `<td colspan="3" class="py-4 text-center text-gray-500">显示前 ${maxRulesToShow} 条规则,共 ${rules.length} 条</td>`;
tbody.appendChild(infoRow); tbody.appendChild(infoRow);
} }
} }
@@ -336,6 +424,9 @@ async function handleDeleteRule(e) {
} }
try { try {
// 显示加载状态
updateRuleStatus(rule, 'loading');
console.log('Sending DELETE request to /api/shield'); console.log('Sending DELETE request to /api/shield');
const response = await fetch('/api/shield', { const response = await fetch('/api/shield', {
method: 'DELETE', method: 'DELETE',
@@ -348,28 +439,47 @@ async function handleDeleteRule(e) {
console.log('Response status:', response.status); console.log('Response status:', response.status);
console.log('Response ok:', response.ok); console.log('Response ok:', response.ok);
if (!response.ok) { // 解析服务器响应
const errorText = await response.text(); let responseData;
throw new Error(`Failed to delete rule: ${response.status} ${errorText}`); try {
responseData = await response.json();
} catch (jsonError) {
responseData = {};
} }
const responseData = await response.json();
console.log('Response data:', responseData); console.log('Response data:', responseData);
showNotification('规则删除成功', 'success'); // 根据服务器响应判断是否成功
console.log('Current rules type:', currentRulesType); if (response.ok && responseData.status === 'success') {
// 根据当前显示的规则类型重新加载对应的规则列表 // 显示成功状态
if (currentRulesType === 'local') { updateRuleStatus(rule, 'success', '已删除');
console.log('Reloading local rules');
loadLocalRules(); showNotification('规则删除成功', 'success');
console.log('Current rules type:', currentRulesType);
// 延迟重新加载规则列表和统计信息,让用户能看到成功状态
setTimeout(() => {
// 根据当前显示的规则类型重新加载对应的规则列表
if (currentRulesType === 'local') {
console.log('Reloading local rules');
loadLocalRules();
} else {
console.log('Reloading remote rules');
loadRemoteRules();
}
// 重新加载统计信息
loadShieldStats();
}, 3000);
} else { } else {
console.log('Reloading remote rules'); const errorMessage = responseData.error || responseData.message || `删除规则失败: ${response.status}`;
loadRemoteRules(); // 显示错误状态
updateRuleStatus(rule, 'error', errorMessage);
throw new Error(errorMessage);
} }
// 重新加载统计信息
loadShieldStats();
} catch (error) { } catch (error) {
console.error('Error deleting rule:', error); console.error('Error deleting rule:', error);
// 显示错误状态
updateRuleStatus(rule, 'error', error.message);
showNotification('删除规则失败: ' + error.message, 'error'); showNotification('删除规则失败: ' + error.message, 'error');
} }
} }
@@ -377,12 +487,26 @@ async function handleDeleteRule(e) {
// 添加新规则 // 添加新规则
async function handleAddRule() { async function handleAddRule() {
const rule = document.getElementById('new-rule').value.trim(); const rule = document.getElementById('new-rule').value.trim();
const statusElement = document.getElementById('save-rule-status');
if (!rule) { if (!rule) {
showNotification('规则不能为空', 'error'); showNotification('规则不能为空', 'error');
return; return;
} }
try { try {
// 清除之前的所有类
statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
// 显示加载状态
statusElement.innerHTML = '<i class="fa fa-spinner fa-spin text-blue-500"></i> 正在添加...';
// 强制重排,确保过渡效果生效
void statusElement.offsetWidth;
// 添加过渡类和加载状态类
statusElement.classList.add('status-transition', 'status-fade-in', 'status-loading');
const response = await fetch('/api/shield', { const response = await fetch('/api/shield', {
method: 'POST', method: 'POST',
headers: { headers: {
@@ -391,20 +515,86 @@ async function handleAddRule() {
body: JSON.stringify({ rule }) body: JSON.stringify({ rule })
}); });
if (!response.ok) { // 解析服务器响应
throw new Error('Failed to add rule'); let responseData;
try {
responseData = await response.json();
} catch (jsonError) {
responseData = {};
} }
showNotification('规则添加成功', 'success'); // 根据服务器响应判断是否成功
// 清空输入框 if (response.ok && responseData.status === 'success') {
document.getElementById('new-rule').value = ''; // 清除之前的所有类
// 重新加载规则 statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
loadLocalRules();
// 重新加载统计信息 // 显示成功状态
loadShieldStats(); statusElement.innerHTML = '<span class="text-green-500"><i class="fa fa-check"></i> 成功</span>';
// 强制重排,确保过渡效果生效
void statusElement.offsetWidth;
// 添加过渡类和成功状态类
statusElement.classList.add('status-transition', 'status-fade-in', 'status-success');
showNotification('规则添加成功', 'success');
// 清空输入框
document.getElementById('new-rule').value = '';
// 延迟重新加载规则和统计信息,让用户能看到成功状态
setTimeout(() => {
// 重新加载规则
loadLocalRules();
// 重新加载统计信息
loadShieldStats();
}, 3000);
} else {
// 清除之前的所有类
statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
// 显示失败状态
const errorMessage = responseData.error || responseData.message || '添加规则失败';
statusElement.innerHTML = `<span class="text-red-500"><i class="fa fa-times"></i> ${errorMessage}</span>`;
// 强制重排,确保过渡效果生效
void statusElement.offsetWidth;
// 添加过渡类和错误状态类
statusElement.classList.add('status-transition', 'status-fade-in', 'status-error');
showNotification(errorMessage, 'error');
}
} catch (error) { } catch (error) {
console.error('Error adding rule:', error); console.error('Error adding rule:', error);
showNotification('添加规则失败', 'error');
// 清除之前的所有类
statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
// 显示错误状态
const errorMessage = error.message || '添加规则失败';
statusElement.innerHTML = `<span class="text-red-500"><i class="fa fa-times"></i> ${errorMessage}</span>`;
// 强制重排,确保过渡效果生效
void statusElement.offsetWidth;
// 添加过渡类和错误状态类
statusElement.classList.add('status-transition', 'status-fade-in', 'status-error');
showNotification(errorMessage, 'error');
} finally {
// 3秒后渐变消失
setTimeout(() => {
// 添加淡出类
statusElement.classList.add('status-fade-out');
// 等待淡出动画完成后清除状态
setTimeout(() => {
// 清除所有类
statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
// 清空状态显示
statusElement.innerHTML = '';
}, 300); // 与CSS动画持续时间一致
}, 3000);
} }
} }
@@ -618,19 +808,42 @@ async function handleUpdateBlacklist(e) {
body: JSON.stringify(updatedBlacklists) body: JSON.stringify(updatedBlacklists)
}); });
if (!updateResponse.ok) { // 解析服务器响应
const errorText = await updateResponse.text(); let responseData;
throw new Error(`更新失败: ${updateResponse.status} ${errorText}`); try {
responseData = await updateResponse.json();
} catch (jsonError) {
responseData = {};
} }
// 显示成功状态 // 根据服务器响应判断是否成功
updateStatus(url, 'success'); if (updateResponse.ok && (responseData.status === 'success' || !responseData.status)) {
// 显示成功状态
// 重新加载黑名单 updateStatus(url, 'success');
loadRemoteBlacklists();
// 重新加载统计信息 // 显示通知
loadShieldStats(); showNotification('黑名单更新成功', 'success');
showNotification('黑名单更新成功', 'success');
// 延迟重新加载黑名单和统计信息,让用户能看到成功状态
setTimeout(() => {
// 重新加载黑名单
loadRemoteBlacklists();
// 重新加载统计信息
loadShieldStats();
}, 3000);
} else {
// 显示失败状态
updateStatus(url, 'error', responseData.error || responseData.message || `更新失败: ${updateResponse.status}`);
showNotification(`黑名单更新失败: ${responseData.error || responseData.message || updateResponse.status}`, 'error');
// 延迟重新加载黑名单和统计信息
setTimeout(() => {
// 重新加载黑名单
loadRemoteBlacklists();
// 重新加载统计信息
loadShieldStats();
}, 3000);
}
} catch (error) { } catch (error) {
console.error('更新黑名单失败:', error); console.error('更新黑名单失败:', error);
// 显示错误状态 // 显示错误状态
@@ -661,6 +874,13 @@ async function handleDeleteBlacklist(e) {
} }
try { try {
// 获取当前行元素
const tr = btn.closest('tr');
if (!tr) {
console.error('未找到行元素');
return;
}
// 显示加载状态 // 显示加载状态
updateStatus(url, 'loading'); updateStatus(url, 'loading');
@@ -684,19 +904,58 @@ async function handleDeleteBlacklist(e) {
body: JSON.stringify(updatedBlacklists) body: JSON.stringify(updatedBlacklists)
}); });
if (!updateResponse.ok) { // 解析服务器响应
const errorText = await updateResponse.text(); let responseData;
throw new Error(`删除失败: ${updateResponse.status} ${errorText}`); try {
responseData = await updateResponse.json();
} catch (jsonError) {
responseData = {};
} }
// 显示成功状态 // 根据服务器响应判断是否成功
updateStatus(url, 'success', '已删除'); if (updateResponse.ok && responseData.status === 'success') {
// 显示成功状态
// 重新加载黑名单 updateStatus(url, 'success', '已删除');
loadRemoteBlacklists();
// 重新加载统计信息 // 显示通知
loadShieldStats(); showNotification('黑名单删除成功', 'success');
showNotification('黑名单删除成功', 'success');
// 延迟后渐变移除该行
setTimeout(() => {
// 添加渐变移除类
tr.style.transition = 'all 0.3s ease-in-out';
tr.style.opacity = '0';
tr.style.transform = 'translateX(-10px)';
tr.style.height = tr.offsetHeight + 'px';
tr.style.overflow = 'hidden';
// 等待过渡效果完成后,隐藏该行
setTimeout(() => {
tr.style.display = 'none';
// 延迟重新加载黑名单和统计信息,确保视觉效果完成
setTimeout(() => {
// 重新加载黑名单
loadRemoteBlacklists();
// 重新加载统计信息
loadShieldStats();
}, 100);
}, 300);
}, 3000);
} else {
// 显示失败状态
const errorMessage = responseData.error || responseData.message || `删除失败: ${updateResponse.status}`;
updateStatus(url, 'error', errorMessage);
showNotification(errorMessage, 'error');
// 延迟重新加载黑名单和统计信息
setTimeout(() => {
// 重新加载黑名单
loadRemoteBlacklists();
// 重新加载统计信息
loadShieldStats();
}, 3000);
}
} catch (error) { } catch (error) {
console.error('删除黑名单失败:', error); console.error('删除黑名单失败:', error);
// 显示错误状态 // 显示错误状态
@@ -761,19 +1020,43 @@ async function handleToggleBlacklist(e) {
body: JSON.stringify(updatedBlacklists) body: JSON.stringify(updatedBlacklists)
}); });
if (!updateResponse.ok) { // 解析服务器响应
const errorText = await updateResponse.text(); let responseData;
throw new Error(`更新状态失败: ${updateResponse.status} ${errorText}`); try {
responseData = await updateResponse.json();
} catch (jsonError) {
responseData = {};
} }
// 显示成功状态 // 根据服务器响应判断是否成功
updateStatus(url, 'success', currentEnabled ? '已禁用' : '已启用'); if (updateResponse.ok && responseData.status === 'success') {
// 显示成功状态
// 重新加载黑名单 updateStatus(url, 'success', currentEnabled ? '已禁用' : '已启用');
loadRemoteBlacklists();
// 重新加载统计信息 // 显示通知
loadShieldStats(); showNotification(`黑名单已${currentEnabled ? '禁用' : '启用'}`, 'success');
showNotification(`黑名单已${currentEnabled ? '禁用' : '启用'}`, 'success');
// 延迟重新加载黑名单和统计信息,让用户能看到成功状态
setTimeout(() => {
// 重新加载黑名单
loadRemoteBlacklists();
// 重新加载统计信息
loadShieldStats();
}, 3000);
} else {
// 显示失败状态
const errorMessage = responseData.error || responseData.message || `更新状态失败: ${updateResponse.status}`;
updateStatus(url, 'error', errorMessage);
showNotification(errorMessage, 'error');
// 延迟重新加载黑名单和统计信息
setTimeout(() => {
// 重新加载黑名单
loadRemoteBlacklists();
// 重新加载统计信息
loadShieldStats();
}, 3000);
}
} catch (error) { } catch (error) {
console.error('启用/禁用黑名单失败:', error); console.error('启用/禁用黑名单失败:', error);
// 显示错误状态 // 显示错误状态
@@ -811,9 +1094,18 @@ async function handleAddBlacklist(event) {
} }
try { try {
// 清除之前的所有类
statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
// 显示加载状态 // 显示加载状态
statusElement.innerHTML = '<i class="fa fa-spinner fa-spin text-blue-500"></i> 正在添加...'; statusElement.innerHTML = '<i class="fa fa-spinner fa-spin text-blue-500"></i> 正在添加...';
// 强制重排,确保过渡效果生效
void statusElement.offsetWidth;
// 添加过渡类和加载状态类
statusElement.classList.add('status-transition', 'status-fade-in', 'status-loading');
// 发送添加请求 // 发送添加请求
const response = await fetch('/api/shield/blacklists', { const response = await fetch('/api/shield/blacklists', {
method: 'POST', method: 'POST',
@@ -823,40 +1115,82 @@ async function handleAddBlacklist(event) {
body: JSON.stringify({ name, url }) body: JSON.stringify({ name, url })
}); });
if (!response.ok) { // 解析服务器响应
// 尝试从响应中获取更详细的错误信息 let responseData;
let errorMessage = '添加黑名单失败'; try {
try { responseData = await response.json();
const errorData = await response.json(); } catch (jsonError) {
if (errorData.error) { responseData = {};
errorMessage = errorData.error;
}
} catch (jsonError) {
// 忽略JSON解析错误
}
throw new Error(errorMessage);
} }
// 显示成功状态 // 根据服务器响应判断是否成功
statusElement.innerHTML = '<span class="text-green-500"><i class="fa fa-check"></i> 成功</span>'; if (response.ok && (responseData.status === 'success' || !responseData.status)) {
// 清除之前的所有类
showNotification('黑名单添加成功', 'success'); statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
// 清空输入框
if (nameInput) nameInput.value = ''; // 显示成功状态
if (urlInput) urlInput.value = ''; statusElement.innerHTML = '<span class="text-green-500"><i class="fa fa-check"></i> 成功</span>';
// 重新加载黑名单
loadRemoteBlacklists(); // 强制重排,确保过渡效果生效
// 重新加载统计信息 void statusElement.offsetWidth;
loadShieldStats();
// 添加过渡类和成功状态类
statusElement.classList.add('status-transition', 'status-fade-in', 'status-success');
showNotification('黑名单添加成功', 'success');
// 清空输入框
if (nameInput) nameInput.value = '';
if (urlInput) urlInput.value = '';
// 重新加载黑名单
loadRemoteBlacklists();
// 重新加载统计信息
loadShieldStats();
} else {
// 清除之前的所有类
statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
// 显示失败状态
const errorMessage = responseData.error || responseData.message || `添加失败: ${response.status}`;
statusElement.innerHTML = `<span class="text-red-500"><i class="fa fa-times"></i> ${errorMessage}</span>`;
// 强制重排,确保过渡效果生效
void statusElement.offsetWidth;
// 添加过渡类和错误状态类
statusElement.classList.add('status-transition', 'status-fade-in', 'status-error');
showNotification(errorMessage, 'error');
}
} catch (error) { } catch (error) {
console.error('Error adding blacklist:', error); console.error('Error adding blacklist:', error);
// 清除之前的所有类
statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
// 显示错误状态 // 显示错误状态
statusElement.innerHTML = `<span class="text-red-500"><i class="fa fa-times"></i> 失败</span>`; const errorMessage = error.message || '添加黑名单失败';
showNotification(error.message || '添加黑名单失败', 'error'); statusElement.innerHTML = `<span class="text-red-500"><i class="fa fa-times"></i> ${errorMessage}</span>`;
// 强制重排,确保过渡效果生效
void statusElement.offsetWidth;
// 添加过渡类和错误状态类
statusElement.classList.add('status-transition', 'status-fade-in', 'status-error');
showNotification(errorMessage, 'error');
} finally { } finally {
// 3秒后清除状态显示 // 3秒后渐变消失
setTimeout(() => { setTimeout(() => {
statusElement.innerHTML = ''; // 添加淡出类
statusElement.classList.add('status-fade-out');
// 等待淡出动画完成后清除状态
setTimeout(() => {
// 清除所有类
statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
// 清空状态显示
statusElement.innerHTML = '';
}, 300); // 与CSS动画持续时间一致
}, 3000); }, 3000);
} }
} }