🎨 优化登录页面逻辑,增强唤醒登录体验;移除旧的登出方法,简化代码结构

This commit is contained in:
李寻欢 2025-04-09 10:22:41 +08:00
parent 932d754b8a
commit b4bb6654c5
5 changed files with 213 additions and 127 deletions
internal

@ -259,35 +259,6 @@ func LogOut(ctx context.Context, wxid string, containerHost string) (*AutoHeartb
return &response, nil
}
// LogoutWechatBot 登出微信机器人(旧方法使用HTTP请求替代)
func LogoutWechatBot(ctx context.Context, containerID string) error {
// 获取容器IP(简化处理默认使用localhost)
hostIP := "localhost"
client := newHTTPClient()
url := fmt.Sprintf("http://%s:3000/api/logout", hostIP)
var response BaseResponse[any]
resp, err := client.R().
SetContext(ctx).
SetResult(&response).
Post(url)
if err != nil {
return fmt.Errorf("failed to execute logout command: %w", err)
}
if resp.StatusCode() != 200 {
return fmt.Errorf("logout API returned non-200 status code: %d", resp.StatusCode())
}
if !response.Success {
return fmt.Errorf("logout failed: %s", response.Message)
}
return nil
}
// GetWechatBotStatus 获取微信机器人状态(使用HTTP请求替代)
func GetWechatBotStatus(ctx context.Context, containerID string) (model.RobotStatus, string, error) {
// 检查容器状态

@ -169,7 +169,7 @@ func DeleteRobot(c *fiber.Ctx) error {
defer cancel()
if robot.Status == model.RobotStatusOnline {
if err = docker.LogoutWechatBot(ctx, robot.ContainerID); err != nil {
if _, err = docker.LogOut(ctx, robot.WechatID, robot.ContainerHost); err != nil {
log.Printf("登出机器人失败: %v", err)
// 继续删除流程,不因登出失败而中断
}
@ -209,7 +209,7 @@ func DeleteRobot(c *fiber.Ctx) error {
return c.Redirect("/admin/robots")
}
// RobotLogin 显示微信登录二维码
// RobotLogin 显示微信登录二维码或唤醒登录
func RobotLogin(c *fiber.Ctx) error {
id, err := strconv.Atoi(c.Params("id"))
if err != nil {
@ -229,6 +229,45 @@ func RobotLogin(c *fiber.Ctx) error {
db.Save(&robot)
}
// 检查机器人是否已在线,如果在线则无需登录
if robot.Status == model.RobotStatusOnline {
// 渲染"已在线"页面
return c.Render("robot/login", fiber.Map{
"Title": "微信登录",
"Robot": robot,
"IsOnline": true,
})
}
// 检查是否指定使用二维码登录
forceQrcode := c.Query("qrcode") == "1"
// 如果存在WechatID且没有强制要求使用二维码则使用唤醒登录
if robot.WechatID != "" && !forceQrcode {
// 尝试唤醒登录
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
awakenResp, err := docker.AwakenLogin(ctx, robot.WechatID, robot.ContainerHost)
if err != nil {
return c.Render("robot/login", fiber.Map{
"Title": "微信登录",
"Robot": robot,
"Message": "唤醒登录失败: " + err.Error(),
"IsError": true,
})
}
// 渲染唤醒登录页面
return c.Render("robot/login", fiber.Map{
"Title": "微信登录",
"Robot": robot,
"UUID": awakenResp.Data.QrCodeResponse.Uuid,
"Expired": awakenResp.Data.QrCodeResponse.ExpiredTime,
"IsAwaken": true,
})
}
// 获取登录二维码
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
@ -236,16 +275,22 @@ func RobotLogin(c *fiber.Ctx) error {
// 使用新的GetQRCode接口获取二维码并传递容器访问地址
qrcodeResp, err := docker.GetQRCode(ctx, robot.ContainerHost)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "获取登录二维码失败: "+err.Error())
return c.Render("robot/login", fiber.Map{
"Title": "微信登录",
"Robot": robot,
"Message": "获取登录二维码失败: " + err.Error(),
"IsError": true,
})
}
// 渲染登录页面包含二维码和UUID用于后续状态检查
return c.Render("robot/login", fiber.Map{
"Title": "微信登录",
"Robot": robot,
"QRCode": qrcodeResp.Data.QRCodeURL, // 完整的data URL格式
"UUID": qrcodeResp.Data.UUID,
"Expired": qrcodeResp.Data.ExpiredTime,
"Title": "微信登录",
"Robot": robot,
"QRCode": qrcodeResp.Data.QRCodeURL,
"UUID": qrcodeResp.Data.UUID,
"Expired": qrcodeResp.Data.ExpiredTime,
"IsAwaken": false,
})
}
@ -275,11 +320,6 @@ func RobotLogout(c *fiber.Ctx) error {
if _, err = docker.LogOut(ctx, robot.WechatID, robot.ContainerHost); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "登出微信失败: "+err.Error())
}
} else {
// 如果没有WechatID使用原来的方法
if err = docker.LogoutWechatBot(ctx, robot.ContainerID); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "登出微信失败: "+err.Error())
}
}
// 更新机器人状态

@ -116,18 +116,6 @@
<i class="fas fa-eye"></i>
</a>
{{if eq .Status "offline"}}
<a href="/admin/robots/{{.ID}}/login" class="p-1.5 rounded-md text-blue-600 hover:bg-blue-50 hover:text-blue-700" title="登录微信">
<i class="fas fa-sign-in-alt"></i>
</a>
{{else}}
<form method="POST" action="/admin/robots/{{.ID}}/logout" class="inline">
<button type="submit" class="p-1.5 rounded-md text-yellow-600 hover:bg-yellow-50 hover:text-yellow-700" title="登出微信">
<i class="fas fa-sign-out-alt"></i>
</button>
</form>
{{end}}
<a href="#" class="p-1.5 rounded-md text-red-600 hover:bg-red-50 hover:text-red-700 delete-robot" data-id="{{.ID}}" data-robot-name="{{.Nickname}}" title="删除">
<i class="fas fa-trash"></i>
</a>

@ -8,43 +8,122 @@
</div>
<div class="max-w-md mx-auto">
<div class="clean-card overflow-hidden">
<div class="text-center px-6 py-5 bg-gray-50 border-b border-gray-100">
<h2 class="text-lg font-medium text-gray-800">{{.Robot.Nickname}}</h2>
<p class="text-sm text-gray-600 mt-1">请使用微信扫描二维码登录</p>
{{if .IsError}}
<div class="mb-4 bg-red-50 border border-red-200 rounded-lg p-4 text-red-700">
<div class="flex">
<div class="flex-shrink-0">
<i class="fas fa-exclamation-circle text-red-500 mt-0.5"></i>
</div>
<div class="ml-3">
<p>{{.Message}}</p>
</div>
</div>
</div>
{{end}}
<div class="clean-card overflow-hidden shadow-md rounded-lg">
<div class="p-6 flex flex-col items-center">
<div class="border border-gray-200 rounded-lg p-3 mb-6 bg-white">
<img src="{{.QRCode}}" alt="Login QR Code" class="w-64 h-64">
</div>
{{if .IsOnline}}
<!-- 已在线状态 -->
<div class="flex flex-col items-center mb-6">
<div class="relative mb-4">
<div class="w-24 h-24 rounded-full overflow-hidden border-4 shadow-lg flex items-center justify-center">
<img src="{{.Robot.Avatar}}" alt="WeChat Avatar" class="w-full h-full object-cover">
</div>
<div class="absolute bottom-0 right-0 bg-green-500 w-6 h-6 rounded-full border-2 border-white flex items-center justify-center">
<i class="fas fa-check text-white text-xs"></i>
</div>
</div>
<h3 class="text-xl font-medium text-gray-800">{{.Robot.Nickname}}</h3>
<div class="mt-2 px-4 py-1.5 bg-green-50 rounded-full">
<span class="inline-flex items-center text-sm font-medium text-green-700">
<i class="fas fa-check-circle mr-1.5"></i>
微信已在线,无需登录
</span>
</div>
</div>
<div id="qrcode-status" class="text-center mb-4">
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800">
<span class="relative flex h-3 w-3 mr-2">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-3 w-3 bg-blue-500"></span>
<div class="w-full flex justify-center mt-4">
<a href="/admin/robots/{{.Robot.ID}}" class="py-2 px-6 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-gray-800 hover:bg-gray-700 transition-colors">
返回机器人详情
</a>
</div>
{{else if .IsAwaken}}
<!-- 唤醒登录模式 -->
<div class="flex flex-col items-center mb-6">
<div class="relative mb-4">
<div class="w-24 h-24 rounded-full overflow-hidden border-4 border-white shadow-lg flex items-center justify-center bg-blue-50">
<img src="{{.Robot.Avatar}}" alt="WeChat Avatar" class="w-full h-full object-cover">
</div>
<div class="absolute bottom-0 right-0 bg-blue-500 w-6 h-6 rounded-full border-2 border-white flex items-center justify-center animate-pulse">
<i class="fas fa-bell text-white text-xs"></i>
</div>
</div>
<h3 class="text-xl font-medium text-gray-800">{{.Robot.Nickname}}</h3>
<p class="text-sm text-gray-500 mt-1">请在手机上确认登录</p>
</div>
<div id="login-status" class="text-center mb-6">
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800">
<span class="relative flex h-3 w-3 mr-2">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-3 w-3 bg-blue-500"></span>
</span>
正在唤醒
</span>
等待扫描
</span>
</div>
</div>
<p class="text-sm text-gray-500 mb-4 text-center">
请打开微信,使用"扫一扫"功能扫描上方二维码登录
</p>
<div class="text-center mb-6">
<div class="inline-block">
<div class="flex space-x-2">
<div class="w-3 h-3 bg-blue-500 rounded-full animate-bounce" style="animation-delay: 0s"></div>
<div class="w-3 h-3 bg-blue-500 rounded-full animate-bounce" style="animation-delay: 0.2s"></div>
<div class="w-3 h-3 bg-blue-500 rounded-full animate-bounce" style="animation-delay: 0.4s"></div>
</div>
</div>
</div>
<div id="timer" class="text-sm text-gray-500 mb-5">
二维码有效期: <span id="countdown">120</span>
</div>
<div class="w-full flex justify-between">
<a href="/admin/robots/{{.Robot.ID}}" class="py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 transition-colors">
取消
</a>
<a href="/admin/robots/{{.Robot.ID}}/login?qrcode=1" class="py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-gray-800 hover:bg-gray-700 transition-colors">
使用二维码
</a>
</div>
{{else}}
<!-- 二维码登录模式 -->
<div class="border border-gray-200 rounded-lg p-3 mb-6 bg-white">
<img src="{{.QRCode}}" alt="Login QR Code" class="w-64 h-64">
</div>
<div class="w-full flex justify-between">
<a href="/admin/robots/{{.Robot.ID}}" class="py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 transition-colors">
取消
</a>
<a href="/admin/robots/{{.Robot.ID}}/login" id="refresh-qrcode" class="py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-gray-800 hover:bg-gray-700 transition-colors hidden">
刷新二维码
</a>
</div>
<div id="login-status" class="text-center mb-4">
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800">
<span class="relative flex h-3 w-3 mr-2">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-3 w-3 bg-blue-500"></span>
</span>
等待扫描
</span>
</div>
<p class="text-sm text-gray-500 mb-4 text-center">
请打开微信,使用"扫一扫"功能扫描上方二维码登录
</p>
<div id="timer" class="text-sm text-gray-500 mb-5">
二维码有效期: <span id="countdown">120</span>
</div>
<div class="w-full flex justify-between">
<a href="/admin/robots/{{.Robot.ID}}" class="py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 transition-colors">
取消
</a>
<a href="/admin/robots/{{.Robot.ID}}/login" id="refresh-qrcode" class="py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-gray-800 hover:bg-gray-700 transition-colors hidden">
刷新二维码
</a>
</div>
{{end}}
</div>
</div>
@ -55,53 +134,31 @@
<script>
document.addEventListener('DOMContentLoaded', function() {
const qrcodeStatus = document.getElementById('qrcode-status');
const timer = document.getElementById('timer');
const countdown = document.getElementById('countdown');
{{if not .IsOnline}}
const loginStatus = document.getElementById('login-status');
const refreshQrcode = document.getElementById('refresh-qrcode');
let secondsLeft = 120;
let statusCheckInterval;
let countdown = document.getElementById('countdown');
// 开始倒计时
const countdownTimer = setInterval(() => {
secondsLeft--;
countdown.textContent = secondsLeft;
if (secondsLeft <= 0) {
clearInterval(countdownTimer);
clearInterval(statusCheckInterval);
qrcodeStatus.innerHTML = `
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-red-100 text-red-800">
<i class="fas fa-times-circle mr-1"></i>
二维码已过期
</span>
`;
refreshQrcode.classList.remove('hidden');
}
}, 1000);
// 检查扫码状态
function checkQRCodeStatus() {
// 检查登录状态
function checkLoginStatus() {
fetch(`/admin/robots/{{.Robot.ID}}/check-qrcode?uuid={{.UUID}}`)
.then(response => response.json())
.then(data => {
if (data.success) {
// 扫码阶段0-未扫描, 1-已扫描未确认, 2-已登录
if (data.status === 0) {
qrcodeStatus.innerHTML = `
loginStatus.innerHTML = `
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800">
<span class="relative flex h-3 w-3 mr-2">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-3 w-3 bg-blue-500"></span>
</span>
等待扫描
{{if .IsAwaken}}正在唤醒{{else}}等待扫描{{end}}
</span>
`;
} else if (data.status === 1) {
qrcodeStatus.innerHTML = `
loginStatus.innerHTML = `
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-yellow-100 text-yellow-800">
<span class="relative flex h-3 w-3 mr-2">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-yellow-400 opacity-75"></span>
@ -111,23 +168,36 @@
</span>
`;
} else if (data.status === 99 && data.userInfo && Object.keys(data.userInfo).length > 0) {
qrcodeStatus.innerHTML = `
loginStatus.innerHTML = `
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800">
<i class="fas fa-check-circle mr-1"></i>
登录成功
</span>
`;
clearInterval(countdownTimer);
clearInterval(timerInterval);
clearInterval(statusCheckInterval);
// 重定向到机器人详情页
// 延迟一下再跳转,让用户有时间看到成功状态
setTimeout(() => {
window.location.href = `/admin/robots/{{.Robot.ID}}`;
}, 1500);
}
} else {
console.error('Error checking QR code status:', data.message);
} else if (data.expired) {
loginStatus.innerHTML = `
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-red-100 text-red-800">
<i class="fas fa-times-circle mr-1"></i>
{{if .IsAwaken}}唤醒失败{{else}}二维码已过期{{end}}
</span>
`;
clearInterval(timerInterval);
clearInterval(statusCheckInterval);
// 显示刷新按钮
if (refreshQrcode) {
refreshQrcode.classList.remove('hidden');
}
}
})
.catch(error => {
@ -135,10 +205,30 @@
});
}
// 每3秒检查一次扫码状态
statusCheckInterval = setInterval(checkQRCodeStatus, 3000);
// 每3秒检查一次状态
const statusCheckInterval = setInterval(checkLoginStatus, 3000);
// 立即执行一次检查
checkQRCodeStatus();
checkLoginStatus();
{{if not .IsAwaken}}
// 开始倒计时
const timerInterval = setInterval(() => {
secondsLeft--;
if (countdown) {
countdown.textContent = secondsLeft;
}
if (secondsLeft <= 0) {
clearInterval(timerInterval);
// 显示刷新按钮
if (refreshQrcode) {
refreshQrcode.classList.remove('hidden');
}
}
}, 1000);
{{end}}
{{end}}
});
</script>

@ -16,9 +16,6 @@
</span>
</div>
<div>
<a href="/admin/robots/{{.Robot.ID}}/login" class="inline-flex items-center justify-center px-4 py-2 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors mr-2">
<i class="fas fa-qrcode mr-2"></i> 登录
</a>
<!-- 修改删除按钮使用confirmDialog而非直接调用deleteRobot函数 -->
<button onclick="confirmDelete({{.Robot.ID}}, '{{.Robot.Nickname}}')" class="inline-flex items-center justify-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-red-600 hover:bg-red-700 transition-colors">
<i class="fas fa-trash mr-2"></i> 删除
@ -82,8 +79,8 @@
</button>
</form>
{{else}}
<a href="/admin/robots/{{.Robot.ID}}/login" class="w-full flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none transition-colors">
<i class="fas fa-qrcode mr-2"></i> 扫码登录
<a href="/admin/robots/{{.Robot.ID}}/login" class="w-full flex items-center justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none transition-colors">
<i class="fas fa-qrcode mr-2"></i> 登录
</a>
{{end}}
</div>
@ -263,7 +260,7 @@
`确定要删除机器人"${name}"吗?此操作将永久删除容器及相关数据,无法恢复!`,
{ type: 'danger', title: '删除机器人' }
);
if (confirmed) {
deleteRobot(id);
}