Web优化
This commit is contained in:
37
.trae/documents/修复保存按钮错位问题.md
Normal file
37
.trae/documents/修复保存按钮错位问题.md
Normal 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. 测试修改后的效果
|
||||||
|
- 确保保存按钮与前两个输入框在垂直方向上对齐
|
||||||
|
- 保持响应式布局,在不同屏幕尺寸下都能正常显示
|
||||||
|
|
||||||
|
## 预期效果
|
||||||
|
|
||||||
|
保存按钮将与前两个输入框在垂直方向上对齐,消除错位现象,提升表单的视觉一致性和用户体验。
|
||||||
20
.trae/documents/修复远程黑名单管理保存按钮对齐问题.md
Normal file
20
.trae/documents/修复远程黑名单管理保存按钮对齐问题.md
Normal 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,确保与输入框精确对齐
|
||||||
|
|
||||||
|
## 预期效果
|
||||||
|
保存按钮与输入框在垂直方向上精确对齐,整体布局更加美观和专业。
|
||||||
29
.trae/documents/修改远程黑名单管理界面,添加状态渐变效果.md
Normal file
29
.trae/documents/修改远程黑名单管理界面,添加状态渐变效果.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# 修改远程黑名单管理界面,添加状态渐变效果
|
||||||
|
|
||||||
|
## 问题分析
|
||||||
|
当前远程黑名单管理界面的状态更新功能已经实现了3秒后恢复默认状态的功能,但没有添加渐变效果,用户体验不够流畅。
|
||||||
|
|
||||||
|
## 解决方案
|
||||||
|
修改`updateStatus`函数,为状态元素添加渐变效果,确保状态更新时的平滑过渡。
|
||||||
|
|
||||||
|
## 实现步骤
|
||||||
|
|
||||||
|
1. **修改`updateStatus`函数**
|
||||||
|
- 为状态元素添加CSS过渡效果
|
||||||
|
- 使用`classList`操作来添加/移除渐变类
|
||||||
|
- 确保状态更新时的平滑过渡
|
||||||
|
|
||||||
|
2. **添加CSS渐变样式**
|
||||||
|
- 在index.html中添加状态渐变的CSS样式
|
||||||
|
- 为不同状态(loading、success、error)添加不同的渐变效果
|
||||||
|
|
||||||
|
3. **测试修改后的功能**
|
||||||
|
- 确保状态更新时有平滑的渐变效果
|
||||||
|
- 确保3秒后状态能正常恢复默认
|
||||||
|
|
||||||
|
## 预期效果
|
||||||
|
远程黑名单管理界面的状态更新将具有平滑的渐变效果,包括:
|
||||||
|
- 加载状态的蓝色渐变效果
|
||||||
|
- 成功状态的绿色渐变效果
|
||||||
|
- 错误状态的红色渐变效果
|
||||||
|
- 所有状态在3秒后平滑消失,恢复默认状态
|
||||||
33
.trae/documents/根据服务器日志判断操作成功状态.md
Normal file
33
.trae/documents/根据服务器日志判断操作成功状态.md
Normal 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`字段判断是否成功,确保操作结果与服务器日志一致。
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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();
|
showNotification('黑名单更新成功', 'success');
|
||||||
// 重新加载统计信息
|
|
||||||
loadShieldStats();
|
// 延迟重新加载黑名单和统计信息,让用户能看到成功状态
|
||||||
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();
|
showNotification('黑名单删除成功', 'success');
|
||||||
// 重新加载统计信息
|
|
||||||
loadShieldStats();
|
// 延迟后渐变移除该行
|
||||||
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();
|
showNotification(`黑名单已${currentEnabled ? '禁用' : '启用'}`, 'success');
|
||||||
// 重新加载统计信息
|
|
||||||
loadShieldStats();
|
// 延迟重新加载黑名单和统计信息,让用户能看到成功状态
|
||||||
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)) {
|
||||||
|
// 清除之前的所有类
|
||||||
|
statusElement.classList.remove('status-transition', 'status-loading', 'status-success', 'status-error', 'status-fade-in', 'status-fade-out');
|
||||||
|
|
||||||
showNotification('黑名单添加成功', 'success');
|
// 显示成功状态
|
||||||
// 清空输入框
|
statusElement.innerHTML = '<span class="text-green-500"><i class="fa fa-check"></i> 成功</span>';
|
||||||
if (nameInput) nameInput.value = '';
|
|
||||||
if (urlInput) urlInput.value = '';
|
// 强制重排,确保过渡效果生效
|
||||||
// 重新加载黑名单
|
void statusElement.offsetWidth;
|
||||||
loadRemoteBlacklists();
|
|
||||||
// 重新加载统计信息
|
// 添加过渡类和成功状态类
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user