🎨 逻辑优化
All checks were successful
BuildImage / build-image (push) Successful in 1m55s

This commit is contained in:
李寻欢 2025-04-03 14:35:00 +08:00
parent f3d8e73ab4
commit bd203141d8
5 changed files with 169 additions and 21 deletions

View File

@ -1,6 +1,6 @@
server:
port: 8080
host: "localhost"
host: "0.0.0.0"
env: "development"
# 开发环境使用SQLite

View File

@ -4,6 +4,7 @@ import (
"context"
"log"
"strconv"
"strings"
"time"
"github.com/gofiber/fiber/v2"
@ -119,6 +120,13 @@ func ShowRobot(c *fiber.Ctx) error {
func DeleteRobot(c *fiber.Ctx) error {
id, err := strconv.Atoi(c.Params("id"))
if err != nil {
// 针对API请求返回JSON
if strings.HasPrefix(c.Get("Accept"), "application/json") || c.Method() == "DELETE" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"success": false,
"message": "无效的ID",
})
}
return fiber.NewError(fiber.StatusBadRequest, "无效的ID")
}
@ -126,6 +134,13 @@ func DeleteRobot(c *fiber.Ctx) error {
db := model.GetDB()
if err := db.First(&robot, id).Error; err != nil {
// 针对API请求返回JSON
if strings.HasPrefix(c.Get("Accept"), "application/json") || c.Method() == "DELETE" {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"success": false,
"message": "机器人不存在",
})
}
if err == gorm.ErrRecordNotFound {
return fiber.NewError(fiber.StatusNotFound, "机器人不存在")
}
@ -155,9 +170,25 @@ func DeleteRobot(c *fiber.Ctx) error {
// 删除数据库记录
if err := db.Delete(&robot).Error; err != nil {
// 针对API请求返回JSON
if strings.HasPrefix(c.Get("Accept"), "application/json") || c.Method() == "DELETE" {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"success": false,
"message": "删除机器人失败: " + err.Error(),
})
}
return fiber.NewError(fiber.StatusInternalServerError, "删除机器人失败: "+err.Error())
}
// 针对API请求返回JSON
if strings.HasPrefix(c.Get("Accept"), "application/json") || c.Method() == "DELETE" {
return c.JSON(fiber.Map{
"success": true,
"message": "机器人已成功删除",
})
}
// 普通请求重定向
return c.Redirect("/admin/robots")
}

View File

@ -122,7 +122,10 @@
{{embed}}
{{end}}
<!-- Alpine.js -->
<!-- 先加载组件库 -->
<script src="/public/js/components.js"></script>
<!-- 然后加载Alpine.js -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
{{if .CustomJS}}

View File

@ -200,6 +200,77 @@
});
</script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 删除机器人确认
document.querySelectorAll('.delete-robot').forEach(btn => {
btn.addEventListener('click', async function(e) {
e.preventDefault();
const robotId = this.dataset.id;
const robotName = this.dataset.robotName || '此机器人';
// 使用美观的确认对话框代替原生的window.confirm
if (typeof confirmDialog === 'function') {
// 使用confirmDialog函数
const confirmed = await confirmDialog(
`确定要删除 ${robotName} 吗?此操作不可恢复!`,
{ type: 'danger', title: '确认删除' }
);
if (confirmed) {
// 显示加载状态
this.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
this.disabled = true;
try {
// 使用fetch API发送DELETE请求
const response = await fetch(`/admin/robots/${robotId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
}
});
if (response.ok) {
// 删除成功,刷新页面
window.location.reload();
} else {
// 读取错误信息
const errorData = await response.json();
throw new Error(errorData.message || '删除失败');
}
} catch (error) {
console.error('Error:', error);
alert('删除失败: ' + error.message);
// 恢复按钮状态
this.innerHTML = '<i class="fas fa-trash"></i>';
this.disabled = false;
}
}
} else {
// 降级方案使用原生confirm
if (window.confirm(`确定要删除 ${robotName} 吗?此操作不可恢复!`)) {
// 使用fetch API发送DELETE请求
fetch(`/admin/robots/${robotId}`, { method: 'DELETE' })
.then(response => {
if (response.ok) {
window.location.reload();
} else {
throw new Error('删除失败');
}
})
.catch(error => {
console.error('Error:', error);
alert('删除失败: ' + error.message);
});
}
}
});
});
});
</script>
<style>
.robot-card {
box-shadow: 0 2px 10px rgba(0,0,0,0.08); /* 保留基础阴影 */

View File

@ -87,33 +87,41 @@ function confirmDialog(message, options = {}) {
// 创建遮罩
const overlay = document.createElement('div');
overlay.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50';
overlay.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 transition-opacity duration-300 opacity-0';
overlay.style.backdropFilter = 'blur(2px)';
// 选择颜色和图标
let iconClass = 'text-indigo-500';
let buttonClass = 'bg-indigo-600 hover:bg-indigo-700';
let icon = '<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-3a1 1 0 00-.867.5 1 1 0 11-1.731-1A3 3 0 0113 8a3.001 3.001 0 01-2 2.83V11a1 1 0 11-2 0v-1a1 1 0 011-1 1 1 0 100-2zm0 8a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd"></path></svg>';
// 选择颜色
let colorClass = 'text-blue-600';
let buttonClass = 'bg-blue-600 hover:bg-blue-700';
if (settings.type === 'warning') {
colorClass = 'text-yellow-600';
buttonClass = 'bg-yellow-600 hover:bg-yellow-700';
iconClass = 'text-amber-500';
buttonClass = 'bg-amber-600 hover:bg-amber-700';
icon = '<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v4a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path></svg>';
} else if (settings.type === 'danger') {
colorClass = 'text-red-600';
iconClass = 'text-red-500';
buttonClass = 'bg-red-600 hover:bg-red-700';
icon = '<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"></path></svg>';
}
// 创建对话框
overlay.innerHTML = `
<div class="bg-white rounded-lg shadow-xl overflow-hidden max-w-md w-full mx-4 transform transition-all scale-95 opacity-0">
<div class="px-6 py-4 border-b border-gray-200">
<h3 class="text-lg font-medium ${colorClass}">${settings.title}</h3>
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-2xl overflow-hidden max-w-md w-full mx-4 transform transition-all duration-300 scale-95 opacity-0">
<div class="p-5 flex items-start space-x-4">
<div class="flex-shrink-0 ${iconClass} p-1">
${icon}
</div>
<div class="flex-grow">
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-1">${settings.title}</h3>
<p class="text-gray-500 dark:text-gray-300">${message}</p>
</div>
</div>
<div class="p-6">
<p class="text-gray-700">${message}</p>
</div>
<div class="px-6 py-3 bg-gray-50 flex justify-end space-x-2">
<button id="cancel-btn" class="px-4 py-2 bg-gray-200 hover:bg-gray-300 text-gray-800 rounded-md">
<div class="bg-gray-50 dark:bg-gray-700/50 px-5 py-4 flex justify-end space-x-3">
<button id="cancel-btn" class="px-4 py-2 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-200 rounded-lg border border-gray-300 dark:border-gray-500 text-sm font-medium shadow-sm transition-all">
${settings.cancelText}
</button>
<button id="confirm-btn" class="px-4 py-2 ${buttonClass} text-white rounded-md">
<button id="confirm-btn" class="${buttonClass} text-white px-4 py-2 rounded-lg shadow-sm text-sm font-medium transition-all">
${settings.confirmText}
</button>
</div>
@ -124,6 +132,8 @@ function confirmDialog(message, options = {}) {
// 显示动画
setTimeout(() => {
overlay.classList.remove('opacity-0');
overlay.classList.add('opacity-100');
const dialog = overlay.querySelector('div.bg-white');
dialog.classList.remove('scale-95', 'opacity-0');
dialog.classList.add('scale-100', 'opacity-100');
@ -131,18 +141,51 @@ function confirmDialog(message, options = {}) {
// 事件处理
const closeDialog = (result) => {
// 关闭动画
overlay.classList.remove('opacity-100');
overlay.classList.add('opacity-0');
const dialog = overlay.querySelector('div.bg-white');
dialog.classList.remove('scale-100', 'opacity-100');
dialog.classList.add('scale-95', 'opacity-0');
// 等待动画完成后移除
setTimeout(() => {
overlay.remove();
resolve(result);
}, 200);
}, 300);
};
overlay.querySelector('#confirm-btn').addEventListener('click', () => closeDialog(true));
overlay.querySelector('#cancel-btn').addEventListener('click', () => closeDialog(false));
// 处理键盘事件
const handleKeyDown = (e) => {
if (e.key === 'Escape') {
closeDialog(false);
document.removeEventListener('keydown', handleKeyDown);
} else if (e.key === 'Enter') {
closeDialog(true);
document.removeEventListener('keydown', handleKeyDown);
}
};
document.addEventListener('keydown', handleKeyDown);
// 绑定按钮事件
overlay.querySelector('#confirm-btn').addEventListener('click', () => {
closeDialog(true);
document.removeEventListener('keydown', handleKeyDown);
});
overlay.querySelector('#cancel-btn').addEventListener('click', () => {
closeDialog(false);
document.removeEventListener('keydown', handleKeyDown);
});
// 点击外部区域关闭
overlay.addEventListener('click', (e) => {
if (e.target === overlay) {
closeDialog(false);
document.removeEventListener('keydown', handleKeyDown);
}
});
});
}