diff --git a/.trae/documents/修复保存按钮错位问题.md b/.trae/documents/修复保存按钮错位问题.md
new file mode 100644
index 0000000..b62d072
--- /dev/null
+++ b/.trae/documents/修复保存按钮错位问题.md
@@ -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. 测试修改后的效果
+ - 确保保存按钮与前两个输入框在垂直方向上对齐
+ - 保持响应式布局,在不同屏幕尺寸下都能正常显示
+
+## 预期效果
+
+保存按钮将与前两个输入框在垂直方向上对齐,消除错位现象,提升表单的视觉一致性和用户体验。
\ No newline at end of file
diff --git a/.trae/documents/修复远程黑名单管理保存按钮对齐问题.md b/.trae/documents/修复远程黑名单管理保存按钮对齐问题.md
new file mode 100644
index 0000000..699b234
--- /dev/null
+++ b/.trae/documents/修复远程黑名单管理保存按钮对齐问题.md
@@ -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,确保与输入框精确对齐
+
+## 预期效果
+保存按钮与输入框在垂直方向上精确对齐,整体布局更加美观和专业。
\ No newline at end of file
diff --git a/.trae/documents/修改远程黑名单管理界面,添加状态渐变效果.md b/.trae/documents/修改远程黑名单管理界面,添加状态渐变效果.md
new file mode 100644
index 0000000..2760439
--- /dev/null
+++ b/.trae/documents/修改远程黑名单管理界面,添加状态渐变效果.md
@@ -0,0 +1,29 @@
+# 修改远程黑名单管理界面,添加状态渐变效果
+
+## 问题分析
+当前远程黑名单管理界面的状态更新功能已经实现了3秒后恢复默认状态的功能,但没有添加渐变效果,用户体验不够流畅。
+
+## 解决方案
+修改`updateStatus`函数,为状态元素添加渐变效果,确保状态更新时的平滑过渡。
+
+## 实现步骤
+
+1. **修改`updateStatus`函数**
+ - 为状态元素添加CSS过渡效果
+ - 使用`classList`操作来添加/移除渐变类
+ - 确保状态更新时的平滑过渡
+
+2. **添加CSS渐变样式**
+ - 在index.html中添加状态渐变的CSS样式
+ - 为不同状态(loading、success、error)添加不同的渐变效果
+
+3. **测试修改后的功能**
+ - 确保状态更新时有平滑的渐变效果
+ - 确保3秒后状态能正常恢复默认
+
+## 预期效果
+远程黑名单管理界面的状态更新将具有平滑的渐变效果,包括:
+- 加载状态的蓝色渐变效果
+- 成功状态的绿色渐变效果
+- 错误状态的红色渐变效果
+- 所有状态在3秒后平滑消失,恢复默认状态
\ No newline at end of file
diff --git a/.trae/documents/根据服务器日志判断操作成功状态.md b/.trae/documents/根据服务器日志判断操作成功状态.md
new file mode 100644
index 0000000..475f624
--- /dev/null
+++ b/.trae/documents/根据服务器日志判断操作成功状态.md
@@ -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`字段判断是否成功,确保操作结果与服务器日志一致。
\ No newline at end of file
diff --git a/config.json b/config.json
index eb3fd0b..275a0fc 100644
--- a/config.json
+++ b/config.json
@@ -21,19 +21,19 @@
"name": "AdGuard DNS filter",
"url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/filter.txt",
"enabled": true,
- "lastUpdateTime": "2025-11-28T14:43:26.972Z"
+ "lastUpdateTime": "2025-11-28T15:45:11.073Z"
},
{
"name": "Adaway Default Blocklist",
"url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-Filters/raw/branch/main/hosts/adaway.txt",
"enabled": true,
- "lastUpdateTime": "2025-11-28T14:41:39.123Z"
+ "lastUpdateTime": "2025-11-28T15:36:43.086Z"
},
{
"name": "CHN-anti-AD",
"url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-Filters/raw/branch/main/list/easylist.txt",
"enabled": true,
- "lastUpdateTime": "2025-11-28T14:41:41.318Z"
+ "lastUpdateTime": "2025-11-28T15:26:24.833Z"
},
{
"name": "My GitHub Rules",
@@ -42,7 +42,7 @@
"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",
"enabled": true
}
diff --git a/http/server.go b/http/server.go
index f59f33a..76f6e62 100644
--- a/http/server.go
+++ b/http/server.go
@@ -752,7 +752,7 @@ func (s *Server) handleShieldBlacklists(w http.ResponseWriter, r *http.Request)
}
if targetURLOrName == "" {
- http.Error(w, "黑名单标识不能为空", http.StatusBadRequest)
+ json.NewEncoder(w).Encode(map[string]string{"status": "error", "message": "黑名单标识不能为空"})
return
}
@@ -767,7 +767,7 @@ func (s *Server) handleShieldBlacklists(w http.ResponseWriter, r *http.Request)
}
if targetIndex == -1 {
- http.Error(w, "黑名单不存在", http.StatusNotFound)
+ json.NewEncoder(w).Encode(map[string]string{"status": "error", "message": "黑名单不存在"})
return
}
@@ -780,7 +780,7 @@ func (s *Server) handleShieldBlacklists(w http.ResponseWriter, r *http.Request)
// 保存配置到文件
if err := saveConfigToFile(s.globalConfig, "config.json"); err != nil {
logger.Error("保存配置文件失败", "error", err)
- http.Error(w, "保存配置失败", http.StatusInternalServerError)
+ json.NewEncoder(w).Encode(map[string]string{"status": "error", "message": "保存配置失败"})
return
}
// 重新加载规则以获取最新的远程规则
@@ -810,7 +810,7 @@ func (s *Server) handleShieldBlacklists(w http.ResponseWriter, r *http.Request)
// 保存配置到文件
if err := saveConfigToFile(s.globalConfig, "config.json"); err != nil {
logger.Error("保存配置文件失败", "error", err)
- http.Error(w, "保存配置失败", http.StatusInternalServerError)
+ json.NewEncoder(w).Encode(map[string]string{"status": "error", "message": "保存配置失败"})
return
}
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 {
- http.Error(w, "Invalid request body", http.StatusBadRequest)
+ json.NewEncoder(w).Encode(map[string]string{"status": "error", "message": "无效的请求体"})
return
}
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
}
@@ -846,11 +846,17 @@ func (s *Server) handleShieldBlacklists(w http.ResponseWriter, r *http.Request)
// 检查是否已存在
for _, list := range blacklists {
if list.URL == req.URL {
- http.Error(w, "黑名单URL已存在", http.StatusConflict)
+ json.NewEncoder(w).Encode(map[string]string{"status": "error", "message": "黑名单URL已存在"})
return
}
}
+ // 检查URL是否存在且可访问
+ if !checkURLExists(req.URL) {
+ json.NewEncoder(w).Encode(map[string]string{"status": "error", "message": "URL不存在或无法访问"})
+ return
+ }
+
// 添加新黑名单
newEntry := config.BlacklistEntry{
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 {
logger.Error("保存配置文件失败", "error", err)
- http.Error(w, "保存配置失败", http.StatusInternalServerError)
+ json.NewEncoder(w).Encode(map[string]string{"status": "error", "message": "保存配置失败"})
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 {
- http.Error(w, "Invalid request body", http.StatusBadRequest)
+ json.NewEncoder(w).Encode(map[string]string{"status": "error", "message": "无效的请求体"})
return
}
@@ -906,7 +912,7 @@ func (s *Server) handleShieldBlacklists(w http.ResponseWriter, r *http.Request)
// 保存配置到文件
if err := saveConfigToFile(s.globalConfig, "config.json"); err != nil {
logger.Error("保存配置文件失败", "error", err)
- http.Error(w, "保存配置失败", http.StatusInternalServerError)
+ json.NewEncoder(w).Encode(map[string]string{"status": "error", "message": "保存配置失败"})
return
}
// 重新加载规则
@@ -915,7 +921,7 @@ func (s *Server) handleShieldBlacklists(w http.ResponseWriter, r *http.Request)
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
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
}
+// 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 处理重启服务请求
func (s *Server) handleRestart(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
diff --git a/static/index.html b/static/index.html
index 5db56c8..7503240 100644
--- a/static/index.html
+++ b/static/index.html
@@ -166,6 +166,65 @@
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;
+ }
@@ -739,6 +798,7 @@
+
@@ -748,12 +808,13 @@
| 规则 |
+ 状态 |
操作 |
- | 暂无规则 |
+ 暂无规则 |
@@ -775,11 +836,13 @@
-
diff --git a/static/js/shield.js b/static/js/shield.js
index 73cfcc3..4ece01e 100644
--- a/static/js/shield.js
+++ b/static/js/shield.js
@@ -17,6 +17,9 @@ function updateStatus(url, status, message) {
const statusElement = document.getElementById(`update-status-${encodeURIComponent(url)}`);
if (!statusElement) return;
+ // 清除之前的所有类
+ statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
+
let statusHTML = '';
switch (status) {
@@ -33,12 +36,90 @@ function updateStatus(url, status, message) {
statusHTML = '-';
}
+ // 强制重排,确保过渡效果生效
+ void statusElement.offsetWidth;
+
+ // 设置新的HTML内容
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') {
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 = '-';
+ }, 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 = ' 处理中...';
+ break;
+ case 'success':
+ statusHTML = ` ${message || '成功'}`;
+ break;
+ case 'error':
+ statusHTML = ` ${message || '失败'}`;
+ break;
+ default:
+ statusHTML = '-';
+ }
+
+ // 强制重排,确保过渡效果生效
+ 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 = '-';
+ }, 300); // 与CSS动画持续时间一致
}, 3000);
}
}
@@ -248,7 +329,7 @@ function updateRulesTable(rules) {
if (rules.length === 0) {
const emptyRow = document.createElement('tr');
- emptyRow.innerHTML = '暂无规则 | ';
+ emptyRow.innerHTML = '暂无规则 | ';
tbody.appendChild(emptyRow);
return;
}
@@ -268,6 +349,11 @@ function updateRulesTable(rules) {
tdRule.className = 'py-3 px-4';
tdRule.textContent = rule;
+ const tdStatus = document.createElement('td');
+ tdStatus.className = 'py-3 px-4 text-center';
+ tdStatus.id = `rule-status-${encodeURIComponent(rule)}`;
+ tdStatus.innerHTML = '-';
+
const tdAction = document.createElement('td');
tdAction.className = 'py-3 px-4 text-right';
@@ -289,7 +375,9 @@ function updateRulesTable(rules) {
};
tdAction.appendChild(deleteBtn);
+
tr.appendChild(tdRule);
+ tr.appendChild(tdStatus);
tr.appendChild(tdAction);
fragment.appendChild(tr);
});
@@ -300,7 +388,7 @@ function updateRulesTable(rules) {
// 如果有更多规则,添加提示
if (rules.length > maxRulesToShow) {
const infoRow = document.createElement('tr');
- infoRow.innerHTML = `显示前 ${maxRulesToShow} 条规则,共 ${rules.length} 条 | `;
+ infoRow.innerHTML = `显示前 ${maxRulesToShow} 条规则,共 ${rules.length} 条 | `;
tbody.appendChild(infoRow);
}
}
@@ -336,6 +424,9 @@ async function handleDeleteRule(e) {
}
try {
+ // 显示加载状态
+ updateRuleStatus(rule, 'loading');
+
console.log('Sending DELETE request to /api/shield');
const response = await fetch('/api/shield', {
method: 'DELETE',
@@ -348,28 +439,47 @@ async function handleDeleteRule(e) {
console.log('Response status:', response.status);
console.log('Response ok:', response.ok);
- if (!response.ok) {
- const errorText = await response.text();
- throw new Error(`Failed to delete rule: ${response.status} ${errorText}`);
+ // 解析服务器响应
+ let responseData;
+ try {
+ responseData = await response.json();
+ } catch (jsonError) {
+ responseData = {};
}
- const responseData = await response.json();
console.log('Response data:', responseData);
- showNotification('规则删除成功', 'success');
- console.log('Current rules type:', currentRulesType);
- // 根据当前显示的规则类型重新加载对应的规则列表
- if (currentRulesType === 'local') {
- console.log('Reloading local rules');
- loadLocalRules();
+ // 根据服务器响应判断是否成功
+ if (response.ok && responseData.status === 'success') {
+ // 显示成功状态
+ updateRuleStatus(rule, 'success', '已删除');
+
+ 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 {
- console.log('Reloading remote rules');
- loadRemoteRules();
+ const errorMessage = responseData.error || responseData.message || `删除规则失败: ${response.status}`;
+ // 显示错误状态
+ updateRuleStatus(rule, 'error', errorMessage);
+ throw new Error(errorMessage);
}
- // 重新加载统计信息
- loadShieldStats();
} catch (error) {
console.error('Error deleting rule:', error);
+ // 显示错误状态
+ updateRuleStatus(rule, 'error', error.message);
showNotification('删除规则失败: ' + error.message, 'error');
}
}
@@ -377,12 +487,26 @@ async function handleDeleteRule(e) {
// 添加新规则
async function handleAddRule() {
const rule = document.getElementById('new-rule').value.trim();
+ const statusElement = document.getElementById('save-rule-status');
+
if (!rule) {
showNotification('规则不能为空', 'error');
return;
}
try {
+ // 清除之前的所有类
+ statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
+
+ // 显示加载状态
+ statusElement.innerHTML = ' 正在添加...';
+
+ // 强制重排,确保过渡效果生效
+ void statusElement.offsetWidth;
+
+ // 添加过渡类和加载状态类
+ statusElement.classList.add('status-transition', 'status-fade-in', 'status-loading');
+
const response = await fetch('/api/shield', {
method: 'POST',
headers: {
@@ -391,20 +515,86 @@ async function handleAddRule() {
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');
- // 清空输入框
- document.getElementById('new-rule').value = '';
- // 重新加载规则
- loadLocalRules();
- // 重新加载统计信息
- loadShieldStats();
+ // 根据服务器响应判断是否成功
+ if (response.ok && responseData.status === 'success') {
+ // 清除之前的所有类
+ statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
+
+ // 显示成功状态
+ statusElement.innerHTML = ' 成功';
+
+ // 强制重排,确保过渡效果生效
+ 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 = ` ${errorMessage}`;
+
+ // 强制重排,确保过渡效果生效
+ void statusElement.offsetWidth;
+
+ // 添加过渡类和错误状态类
+ statusElement.classList.add('status-transition', 'status-fade-in', 'status-error');
+
+ showNotification(errorMessage, 'error');
+ }
} catch (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 = ` ${errorMessage}`;
+
+ // 强制重排,确保过渡效果生效
+ 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)
});
- if (!updateResponse.ok) {
- const errorText = await updateResponse.text();
- throw new Error(`更新失败: ${updateResponse.status} ${errorText}`);
+ // 解析服务器响应
+ let responseData;
+ try {
+ responseData = await updateResponse.json();
+ } catch (jsonError) {
+ responseData = {};
}
- // 显示成功状态
- updateStatus(url, 'success');
-
- // 重新加载黑名单
- loadRemoteBlacklists();
- // 重新加载统计信息
- loadShieldStats();
- showNotification('黑名单更新成功', 'success');
+ // 根据服务器响应判断是否成功
+ if (updateResponse.ok && (responseData.status === 'success' || !responseData.status)) {
+ // 显示成功状态
+ updateStatus(url, '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) {
console.error('更新黑名单失败:', error);
// 显示错误状态
@@ -661,6 +874,13 @@ async function handleDeleteBlacklist(e) {
}
try {
+ // 获取当前行元素
+ const tr = btn.closest('tr');
+ if (!tr) {
+ console.error('未找到行元素');
+ return;
+ }
+
// 显示加载状态
updateStatus(url, 'loading');
@@ -684,19 +904,58 @@ async function handleDeleteBlacklist(e) {
body: JSON.stringify(updatedBlacklists)
});
- if (!updateResponse.ok) {
- const errorText = await updateResponse.text();
- throw new Error(`删除失败: ${updateResponse.status} ${errorText}`);
+ // 解析服务器响应
+ let responseData;
+ try {
+ responseData = await updateResponse.json();
+ } catch (jsonError) {
+ responseData = {};
}
- // 显示成功状态
- updateStatus(url, 'success', '已删除');
-
- // 重新加载黑名单
- loadRemoteBlacklists();
- // 重新加载统计信息
- loadShieldStats();
- showNotification('黑名单删除成功', 'success');
+ // 根据服务器响应判断是否成功
+ if (updateResponse.ok && responseData.status === 'success') {
+ // 显示成功状态
+ updateStatus(url, '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) {
console.error('删除黑名单失败:', error);
// 显示错误状态
@@ -761,19 +1020,43 @@ async function handleToggleBlacklist(e) {
body: JSON.stringify(updatedBlacklists)
});
- if (!updateResponse.ok) {
- const errorText = await updateResponse.text();
- throw new Error(`更新状态失败: ${updateResponse.status} ${errorText}`);
+ // 解析服务器响应
+ let responseData;
+ try {
+ responseData = await updateResponse.json();
+ } catch (jsonError) {
+ responseData = {};
}
- // 显示成功状态
- updateStatus(url, 'success', currentEnabled ? '已禁用' : '已启用');
-
- // 重新加载黑名单
- loadRemoteBlacklists();
- // 重新加载统计信息
- loadShieldStats();
- showNotification(`黑名单已${currentEnabled ? '禁用' : '启用'}`, 'success');
+ // 根据服务器响应判断是否成功
+ if (updateResponse.ok && responseData.status === 'success') {
+ // 显示成功状态
+ updateStatus(url, 'success', currentEnabled ? '已禁用' : '已启用');
+
+ // 显示通知
+ 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) {
console.error('启用/禁用黑名单失败:', error);
// 显示错误状态
@@ -811,9 +1094,18 @@ async function handleAddBlacklist(event) {
}
try {
+ // 清除之前的所有类
+ statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
+
// 显示加载状态
statusElement.innerHTML = ' 正在添加...';
+ // 强制重排,确保过渡效果生效
+ void statusElement.offsetWidth;
+
+ // 添加过渡类和加载状态类
+ statusElement.classList.add('status-transition', 'status-fade-in', 'status-loading');
+
// 发送添加请求
const response = await fetch('/api/shield/blacklists', {
method: 'POST',
@@ -823,40 +1115,82 @@ async function handleAddBlacklist(event) {
body: JSON.stringify({ name, url })
});
- if (!response.ok) {
- // 尝试从响应中获取更详细的错误信息
- let errorMessage = '添加黑名单失败';
- try {
- const errorData = await response.json();
- if (errorData.error) {
- errorMessage = errorData.error;
- }
- } catch (jsonError) {
- // 忽略JSON解析错误
- }
- throw new Error(errorMessage);
+ // 解析服务器响应
+ let responseData;
+ try {
+ responseData = await response.json();
+ } catch (jsonError) {
+ responseData = {};
}
- // 显示成功状态
- statusElement.innerHTML = ' 成功';
-
- showNotification('黑名单添加成功', 'success');
- // 清空输入框
- if (nameInput) nameInput.value = '';
- if (urlInput) urlInput.value = '';
- // 重新加载黑名单
- loadRemoteBlacklists();
- // 重新加载统计信息
- loadShieldStats();
+ // 根据服务器响应判断是否成功
+ if (response.ok && (responseData.status === 'success' || !responseData.status)) {
+ // 清除之前的所有类
+ statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
+
+ // 显示成功状态
+ statusElement.innerHTML = ' 成功';
+
+ // 强制重排,确保过渡效果生效
+ void statusElement.offsetWidth;
+
+ // 添加过渡类和成功状态类
+ 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 = ` ${errorMessage}`;
+
+ // 强制重排,确保过渡效果生效
+ void statusElement.offsetWidth;
+
+ // 添加过渡类和错误状态类
+ statusElement.classList.add('status-transition', 'status-fade-in', 'status-error');
+
+ showNotification(errorMessage, 'error');
+ }
} catch (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 = ` 失败`;
- showNotification(error.message || '添加黑名单失败', 'error');
+ const errorMessage = error.message || '添加黑名单失败';
+ statusElement.innerHTML = ` ${errorMessage}`;
+
+ // 强制重排,确保过渡效果生效
+ void statusElement.offsetWidth;
+
+ // 添加过渡类和错误状态类
+ statusElement.classList.add('status-transition', 'status-fade-in', 'status-error');
+
+ showNotification(errorMessage, 'error');
} finally {
- // 3秒后清除状态显示
+ // 3秒后渐变消失
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);
}
}