1.Q20新增飞机电压消息回复
This commit is contained in:
parent
44b0221f5c
commit
0de80236d7
|
|
@ -4,6 +4,7 @@ import cn.hutool.core.util.StrUtil;
|
||||||
import com.multictrl.common.constant.BusinessConstant;
|
import com.multictrl.common.constant.BusinessConstant;
|
||||||
import com.multictrl.modules.business.q20.handler.Q20EventsHandler;
|
import com.multictrl.modules.business.q20.handler.Q20EventsHandler;
|
||||||
import com.multictrl.modules.business.q20.handler.Q20OsdTopicHandler;
|
import com.multictrl.modules.business.q20.handler.Q20OsdTopicHandler;
|
||||||
|
import com.multictrl.modules.business.q20.handler.Q20RequestsHandler;
|
||||||
import com.multictrl.modules.business.q20.handler.Q20StateTopicHandler;
|
import com.multictrl.modules.business.q20.handler.Q20StateTopicHandler;
|
||||||
import com.multictrl.modules.business.q20.handler.Q20StatusHandler;
|
import com.multictrl.modules.business.q20.handler.Q20StatusHandler;
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
|
|
@ -35,6 +36,7 @@ public class TopicDistributor {
|
||||||
private final Q20EventsHandler q20EventsHandler;
|
private final Q20EventsHandler q20EventsHandler;
|
||||||
private final Q20StateTopicHandler q20StateTopicHandler;
|
private final Q20StateTopicHandler q20StateTopicHandler;
|
||||||
private final Q20StatusHandler q20StatusHandler;
|
private final Q20StatusHandler q20StatusHandler;
|
||||||
|
private final Q20RequestsHandler q20RequestsHandler;
|
||||||
private final Map<String, MessageHandler> handlerMap = new ConcurrentHashMap<>();
|
private final Map<String, MessageHandler> handlerMap = new ConcurrentHashMap<>();
|
||||||
private final Map<String, MessageHandler> q20HandlerMap = new ConcurrentHashMap<>();
|
private final Map<String, MessageHandler> q20HandlerMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
|
@ -56,6 +58,7 @@ public class TopicDistributor {
|
||||||
q20HandlerMap.put(BusinessConstant.STATE, q20StateTopicHandler);
|
q20HandlerMap.put(BusinessConstant.STATE, q20StateTopicHandler);
|
||||||
q20HandlerMap.put(BusinessConstant.STATUS, q20StatusHandler);
|
q20HandlerMap.put(BusinessConstant.STATUS, q20StatusHandler);
|
||||||
q20HandlerMap.put(BusinessConstant.SERVICES_REPLY, servicesReplyHandler);
|
q20HandlerMap.put(BusinessConstant.SERVICES_REPLY, servicesReplyHandler);
|
||||||
|
q20HandlerMap.put(BusinessConstant.REQUESTS, q20RequestsHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void route(String topic, String payload) {
|
public void route(String topic, String payload) {
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ public interface Q20Constant {
|
||||||
String METHOD_ROUTE_EXECUTE = "route_execute";
|
String METHOD_ROUTE_EXECUTE = "route_execute";
|
||||||
String METHOD_ROUTE_AUTO = "route_auto";
|
String METHOD_ROUTE_AUTO = "route_auto";
|
||||||
String METHOD_OTA = "ota";
|
String METHOD_OTA = "ota";
|
||||||
|
// Q20 requests topic method字段值(thing/device/{sn}/requests)
|
||||||
|
String METHOD_HANGAR_CHARGE_CONTROL = "hangar_charge_control";
|
||||||
|
|
||||||
// Q20 缓存key前缀
|
// Q20 缓存key前缀
|
||||||
String Q20_OSD = "q20_osd_";
|
String Q20_OSD = "q20_osd_";
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
package com.multictrl.modules.business.q20.handler;
|
||||||
|
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
|
import com.multictrl.common.constant.BusinessConstant;
|
||||||
|
import com.multictrl.common.utils.JsonUtils;
|
||||||
|
import com.multictrl.modules.business.handler.MessageHandler;
|
||||||
|
import com.multictrl.modules.business.service.MqttPushService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Q20设备请求消息处理(topic: thing/device/{device_sn}/requests)
|
||||||
|
* <p>method: hangar_charge_control(飞行器请求充电电压电流)。
|
||||||
|
* 收到飞行器充电请求后,平台充电器自动回复 requests_reply 主题,
|
||||||
|
* 回传充电器实际可提供的电压/电流(当前按请求值原样应答,表示同意按需供电)。
|
||||||
|
*
|
||||||
|
* @author 938693313@qq.com
|
||||||
|
* @since 1.0.0 2026/6/12
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class Q20RequestsHandler implements MessageHandler {
|
||||||
|
|
||||||
|
private final MqttPushService mqttPushService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleMessage(String topic, String payload, String gateway) {
|
||||||
|
JSONObject message = JsonUtils.parseObject(payload, JSONObject.class);
|
||||||
|
if (message == null) {
|
||||||
|
log.debug("Q20 requests --> payload解析失败,解析后为null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String method = message.getStr(BusinessConstant.METHOD);
|
||||||
|
if (method == null) {
|
||||||
|
log.debug("Q20 requests --> method字段缺失, topic: {}", topic);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Q20Constant.METHOD_HANGAR_CHARGE_CONTROL.equals(method)) {
|
||||||
|
handleHangarChargeControl(topic, message);
|
||||||
|
} else {
|
||||||
|
log.debug("Q20 requests --> 未知method: {}, gateway: {}", method, gateway);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 飞行器请求充电:自动回复充电器电压/电流。
|
||||||
|
* <p>请求 data: {voltage(请求充电电压V*10), current(请求充电电流A*10)};
|
||||||
|
* 回复主题 thing/device/{sn}/requests_reply,data 回传充电器电压/电流(原样应答请求值)。
|
||||||
|
*/
|
||||||
|
private void handleHangarChargeControl(String topic, JSONObject message) {
|
||||||
|
JSONObject data = message.getJSONObject(BusinessConstant.DATA);
|
||||||
|
int voltage = data != null ? data.getInt("voltage", 0) : 0;
|
||||||
|
int current = data != null ? data.getInt("current", 0) : 0;
|
||||||
|
|
||||||
|
// 原样应答 tid/bid/method/gateway,data 回传充电器电压/电流,刷新时间戳
|
||||||
|
JSONObject reply = new JSONObject();
|
||||||
|
reply.set("voltage", voltage);
|
||||||
|
reply.set("current", current);
|
||||||
|
message.set(BusinessConstant.DATA, reply);
|
||||||
|
message.set("timestamp", System.currentTimeMillis());
|
||||||
|
|
||||||
|
String replyTopic = topic + BusinessConstant._REPLY;
|
||||||
|
mqttPushService.pushMessageByClient1(replyTopic, message.toString());
|
||||||
|
log.info("Q20 requests --> 飞行器请求充电已自动回复, topic: {}, voltage: {}, current: {}",
|
||||||
|
replyTopic, voltage, current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -72,7 +72,6 @@ public class ShiroConfig {
|
||||||
filterMap.put("/swagger-resources/**", "anon");
|
filterMap.put("/swagger-resources/**", "anon");
|
||||||
filterMap.put("/captcha", "anon");
|
filterMap.put("/captcha", "anon");
|
||||||
filterMap.put("/", "anon");
|
filterMap.put("/", "anon");
|
||||||
filterMap.put("/q20-login.html", "anon");
|
|
||||||
filterMap.put("/q20-ctrl.html", "anon");
|
filterMap.put("/q20-ctrl.html", "anon");
|
||||||
filterMap.put("/srs/**", "anon");
|
filterMap.put("/srs/**", "anon");
|
||||||
filterMap.put("/mqtt/auth", "anon");
|
filterMap.put("/mqtt/auth", "anon");
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,30 @@ h6.cmd-title { font-size: 12px; color: #e6edf3; font-weight: 600; margin-bottom:
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
<!-- ===== 登录遮罩(与控制台合并为一页;账号 admin1 / 密码 admin 已内置,仅需验证码) ===== -->
|
||||||
|
<div id="loginOverlay" style="display:none;position:fixed;inset:0;z-index:3000;background:#0d1117;align-items:center;justify-content:center">
|
||||||
|
<div class="card p-4" style="max-width:400px;width:100%">
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<i class="bi bi-send-fill" style="font-size:2rem;color:var(--accent)"></i>
|
||||||
|
<h5 class="mt-2 mb-0" style="color:#e6edf3">Q20 飞行控制台</h5>
|
||||||
|
<small class="text-muted">Drone Control Panel · 自动登录 admin1</small>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">验证码</label>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<input id="loginCaptcha" class="form-control" placeholder="点击图片刷新" autocomplete="off"
|
||||||
|
onkeydown="if(event.key==='Enter')doAutoLogin()">
|
||||||
|
<img id="captchaImg" src="" alt="captcha" onclick="refreshCaptcha()" title="点击刷新"
|
||||||
|
style="cursor:pointer;border:1px solid var(--border-color);border-radius:4px;height:38px">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary w-100" onclick="doAutoLogin()">
|
||||||
|
<i class="bi bi-box-arrow-in-right me-1"></i>登 录
|
||||||
|
</button>
|
||||||
|
<div id="loginErr" class="mt-2 text-danger small" style="display:none"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div style="position:sticky;top:0;z-index:200">
|
<div style="position:sticky;top:0;z-index:200">
|
||||||
<!-- 顶栏 -->
|
<!-- 顶栏 -->
|
||||||
<div class="topbar d-flex align-items-center gap-3 flex-wrap" style="position:static">
|
<div class="topbar d-flex align-items-center gap-3 flex-wrap" style="position:static">
|
||||||
|
|
@ -1404,13 +1428,75 @@ h6.cmd-title { font-size: 12px; color: #e6edf3; font-weight: 600; margin-bottom:
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
// ==================== 初始化:未登录则跳回登录页 ====================
|
// ==================== 初始化:未登录则在本页弹出登录遮罩 ====================
|
||||||
|
// 登录与控制台已合并为同一页面,不再跳转 q20-login.html
|
||||||
const token = sessionStorage.getItem('q20_token');
|
const token = sessionStorage.getItem('q20_token');
|
||||||
if (!token) { window.location.replace('q20-login.html'); }
|
|
||||||
|
|
||||||
const API_BASE = (sessionStorage.getItem('q20_api_base') || '/multictrl').replace(/\/$/, '');
|
// 所有业务请求地址固定走该 IP 端口
|
||||||
|
const API_BASE = 'http://223.108.157.174:61620/api';
|
||||||
const username = sessionStorage.getItem('q20_username') || '';
|
const username = sessionStorage.getItem('q20_username') || '';
|
||||||
|
|
||||||
|
// ==================== 内置自动登录(账号/密码写死,仅需验证码) ====================
|
||||||
|
const AUTO_LOGIN_USER = 'admin1';
|
||||||
|
const AUTO_LOGIN_PASS = 'admin';
|
||||||
|
// 验证码 / 登录 接口固定走该 IP 端口(与业务接口 API_BASE 相互独立)
|
||||||
|
const AUTH_BASE = 'http://223.108.157.174:61620/api';
|
||||||
|
let loginUuid = '';
|
||||||
|
|
||||||
|
function genUuid() {
|
||||||
|
return crypto.randomUUID ? crypto.randomUUID()
|
||||||
|
: Math.random().toString(36).slice(2) + Date.now().toString(36);
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshCaptcha() {
|
||||||
|
loginUuid = genUuid();
|
||||||
|
document.getElementById('captchaImg').src =
|
||||||
|
AUTH_BASE + '/captcha?uuid=' + loginUuid + '&t=' + Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示登录遮罩并加载验证码(未登录时调用)
|
||||||
|
function showLoginOverlay() {
|
||||||
|
const ov = document.getElementById('loginOverlay');
|
||||||
|
ov.style.display = 'flex';
|
||||||
|
refreshCaptcha();
|
||||||
|
setTimeout(() => document.getElementById('loginCaptcha').focus(), 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用内置账号 admin1/admin 自动登录,用户仅需填写验证码
|
||||||
|
async function doAutoLogin() {
|
||||||
|
const err = document.getElementById('loginErr');
|
||||||
|
err.style.display = 'none';
|
||||||
|
const captcha = document.getElementById('loginCaptcha').value.trim();
|
||||||
|
if (!captcha) {
|
||||||
|
err.textContent = '请输入验证码';
|
||||||
|
err.style.display = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await fetch(AUTH_BASE + '/login', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ username: AUTO_LOGIN_USER, password: AUTO_LOGIN_PASS, captcha, uuid: loginUuid })
|
||||||
|
});
|
||||||
|
const json = await res.json();
|
||||||
|
if (json.code !== 0) {
|
||||||
|
err.textContent = json.msg || '登录失败';
|
||||||
|
err.style.display = '';
|
||||||
|
refreshCaptcha();
|
||||||
|
document.getElementById('loginCaptcha').value = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sessionStorage.setItem('q20_token', json.data.token);
|
||||||
|
sessionStorage.setItem('q20_api_base', API_BASE);
|
||||||
|
sessionStorage.setItem('q20_username', AUTO_LOGIN_USER);
|
||||||
|
// 登录成功后重新加载本页,令控制台以已登录状态完整初始化
|
||||||
|
window.location.reload();
|
||||||
|
} catch (e) {
|
||||||
|
err.textContent = '网络错误: ' + e.message;
|
||||||
|
err.style.display = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let drcTimer = null;
|
let drcTimer = null;
|
||||||
let currentDrcAction = '';
|
let currentDrcAction = '';
|
||||||
let autoMockEnabled = false;
|
let autoMockEnabled = false;
|
||||||
|
|
@ -2215,7 +2301,8 @@ async function doLogout() {
|
||||||
sessionStorage.removeItem('q20_token');
|
sessionStorage.removeItem('q20_token');
|
||||||
sessionStorage.removeItem('q20_api_base');
|
sessionStorage.removeItem('q20_api_base');
|
||||||
sessionStorage.removeItem('q20_username');
|
sessionStorage.removeItem('q20_username');
|
||||||
window.location.href = 'q20-login.html';
|
// 登录已合并到本页,退出后重新加载即回到登录遮罩
|
||||||
|
window.location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 命令辅助 ====================
|
// ==================== 命令辅助 ====================
|
||||||
|
|
@ -3363,6 +3450,9 @@ function validateRequiredInCard(btn) {
|
||||||
|
|
||||||
// ==================== 页面初始化 ====================
|
// ==================== 页面初始化 ====================
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// 未登录:显示登录遮罩,登录成功后会 reload 本页再完整初始化
|
||||||
|
if (!token) { showLoginOverlay(); return; }
|
||||||
|
|
||||||
document.getElementById('userInfo').textContent = username ? '用户: ' + username : '';
|
document.getElementById('userInfo').textContent = username ? '用户: ' + username : '';
|
||||||
applyFieldMarkers();
|
applyFieldMarkers();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,126 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Q20 登录</title>
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--bs-body-bg: #0d1117;
|
|
||||||
--bs-body-color: #c9d1d9;
|
|
||||||
--card-bg: #161b22;
|
|
||||||
--border-color: #30363d;
|
|
||||||
--accent: #388bfd;
|
|
||||||
--danger: #f85149;
|
|
||||||
--muted: #8b949e;
|
|
||||||
}
|
|
||||||
body { background: var(--bs-body-bg); color: var(--bs-body-color); font-size: 13px; }
|
|
||||||
.card { background: var(--card-bg); border: 1px solid var(--border-color); border-radius: 8px; max-width: 400px; width: 100%; }
|
|
||||||
.form-control { background: #0d1117; border-color: var(--border-color); color: var(--bs-body-color); font-size: 12px; }
|
|
||||||
.form-control:focus { background: #0d1117; border-color: var(--accent); color: var(--bs-body-color); box-shadow: 0 0 0 2px rgba(56,139,253,.25); }
|
|
||||||
.form-label { font-size: 11px; color: var(--muted); margin-bottom: 2px; }
|
|
||||||
.btn-primary { background: var(--accent); border-color: var(--accent); font-size: 12px; }
|
|
||||||
#captchaImg { cursor: pointer; border: 1px solid var(--border-color); border-radius: 4px; height: 38px; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body class="d-flex justify-content-center align-items-center" style="min-height:100vh">
|
|
||||||
|
|
||||||
<div class="card p-4">
|
|
||||||
<div class="text-center mb-4">
|
|
||||||
<i class="bi bi-send-fill" style="font-size:2rem;color:var(--accent)"></i>
|
|
||||||
<h5 class="mt-2 mb-0" style="color:#e6edf3">Q20 飞行控制台</h5>
|
|
||||||
<small class="text-muted">Drone Control Panel</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-2">
|
|
||||||
<label class="form-label">服务地址</label>
|
|
||||||
<input id="apiBase" class="form-control" value="/multictrl" placeholder="如 http://192.168.1.1:61620/api">
|
|
||||||
</div>
|
|
||||||
<div class="mb-2">
|
|
||||||
<label class="form-label">用户名</label>
|
|
||||||
<input id="loginUser" class="form-control" placeholder="admin">
|
|
||||||
</div>
|
|
||||||
<div class="mb-2">
|
|
||||||
<label class="form-label">密码</label>
|
|
||||||
<input id="loginPass" class="form-control" type="password" placeholder="••••••••">
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label">验证码</label>
|
|
||||||
<div class="d-flex gap-2">
|
|
||||||
<input id="loginCaptcha" class="form-control" placeholder="点击图片刷新" onkeydown="if(event.key==='Enter')doLogin()">
|
|
||||||
<img id="captchaImg" src="" alt="captcha" onclick="refreshCaptcha()" title="点击刷新">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="btn btn-primary w-100" onclick="doLogin()">
|
|
||||||
<i class="bi bi-box-arrow-in-right me-1"></i>登 录
|
|
||||||
</button>
|
|
||||||
<div id="loginErr" class="mt-2 text-danger small" style="display:none"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
let loginUuid = '';
|
|
||||||
|
|
||||||
function genUuid() {
|
|
||||||
return crypto.randomUUID ? crypto.randomUUID()
|
|
||||||
: Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
||||||
}
|
|
||||||
|
|
||||||
function baseUrl() {
|
|
||||||
return (document.getElementById('apiBase').value || '/multictrl').replace(/\/$/, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
function refreshCaptcha() {
|
|
||||||
loginUuid = genUuid();
|
|
||||||
document.getElementById('captchaImg').src =
|
|
||||||
baseUrl() + '/captcha?uuid=' + loginUuid + '&t=' + Date.now();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function doLogin() {
|
|
||||||
const err = document.getElementById('loginErr');
|
|
||||||
err.style.display = 'none';
|
|
||||||
const username = document.getElementById('loginUser').value.trim();
|
|
||||||
const password = document.getElementById('loginPass').value;
|
|
||||||
const captcha = document.getElementById('loginCaptcha').value.trim();
|
|
||||||
if (!username || !password || !captcha) {
|
|
||||||
err.textContent = '请填写完整信息';
|
|
||||||
err.style.display = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const res = await fetch(baseUrl() + '/login', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ username, password, captcha, uuid: loginUuid })
|
|
||||||
});
|
|
||||||
const json = await res.json();
|
|
||||||
if (json.code !== 0) {
|
|
||||||
err.textContent = json.msg || '登录失败';
|
|
||||||
err.style.display = '';
|
|
||||||
refreshCaptcha();
|
|
||||||
document.getElementById('loginCaptcha').value = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sessionStorage.setItem('q20_token', json.data.token);
|
|
||||||
sessionStorage.setItem('q20_api_base', baseUrl());
|
|
||||||
sessionStorage.setItem('q20_username', username);
|
|
||||||
window.location.href = 'q20-ctrl.html';
|
|
||||||
} catch (e) {
|
|
||||||
err.textContent = '网络错误: ' + e.message;
|
|
||||||
err.style.display = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
// 已登录则直接跳控制台
|
|
||||||
if (sessionStorage.getItem('q20_token')) {
|
|
||||||
window.location.href = 'q20-ctrl.html';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
refreshCaptcha();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Loading…
Reference in New Issue