|
|
@@ -1,113 +1,345 @@
|
|
|
<!DOCTYPE html>
|
|
|
-<html lang="en">
|
|
|
+<html lang="zh-CN">
|
|
|
<head>
|
|
|
- <meta charset="UTF-8">
|
|
|
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
- <title>用户登录</title>
|
|
|
- <style>
|
|
|
- body {
|
|
|
- font-family: Arial, sans-serif;
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- height: 100vh;
|
|
|
- background-color: #f0f0f0;
|
|
|
- margin: 0;
|
|
|
- }
|
|
|
- .login-container {
|
|
|
- background-color: white;
|
|
|
- padding: 30px;
|
|
|
- border-radius: 10px;
|
|
|
- box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
|
|
|
- text-align: center;
|
|
|
- width: 350px;
|
|
|
- }
|
|
|
- .login-container h1 {
|
|
|
- margin-bottom: 20px;
|
|
|
- font-size: 24px;
|
|
|
- }
|
|
|
- .login-container input[type="text"], .login-container input[type="password"] {
|
|
|
- width: 100%;
|
|
|
- padding-top: 15px;
|
|
|
- padding-bottom: 15px;
|
|
|
- margin-bottom: 20px;
|
|
|
- border: 1px solid #ccc;
|
|
|
- border-radius: 5px;
|
|
|
- }
|
|
|
- .login-container button {
|
|
|
- width: 100%;
|
|
|
- padding: 15px;
|
|
|
- background-color: #007BFF;
|
|
|
- border: none;
|
|
|
- border-radius: 5px;
|
|
|
- color: white;
|
|
|
- font-size: 16px;
|
|
|
- cursor: pointer;
|
|
|
- }
|
|
|
- .login-container button:hover {
|
|
|
- background-color: #0056b3;
|
|
|
- }
|
|
|
- .error-message {
|
|
|
- color: red;
|
|
|
- margin-top: 10px;
|
|
|
- }
|
|
|
- </style>
|
|
|
- <script src="/js/jquery-3.4.1.min.js"></script>
|
|
|
- <script>
|
|
|
- var token = ""
|
|
|
- function login() {
|
|
|
- const username = $('#username').val();
|
|
|
- const password = $('#password').val();
|
|
|
- if (!username || !password) {
|
|
|
- alert('请输入用户名和密码');
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- $.ajax({
|
|
|
- url: '/api/user.lua?action=login',
|
|
|
- type: 'POST',
|
|
|
- contentType: 'application/json',
|
|
|
- data: JSON.stringify({ username: username, password: password }),
|
|
|
- success: function(response) {
|
|
|
- if (response.code === 200) {
|
|
|
- token = response.data.token;
|
|
|
- fetchUserInfo();
|
|
|
- } else {
|
|
|
- $('#errorMessage').text(response.msg || '登录失败,请重试');
|
|
|
- }
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
+ <meta charset="UTF-8" />
|
|
|
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
|
+ <title>WebSocket 测试</title>
|
|
|
+ <style>
|
|
|
+ :root {
|
|
|
+ --bg: #0f1419;
|
|
|
+ --surface: #1a2332;
|
|
|
+ --border: #2d3a4d;
|
|
|
+ --text: #e7ecf3;
|
|
|
+ --muted: #8b9bb4;
|
|
|
+ --accent: #3d9cf0;
|
|
|
+ --accent-hover: #5aadf5;
|
|
|
+ --ok: #3ecf8e;
|
|
|
+ --err: #f07178;
|
|
|
+ --warn: #e6c07b;
|
|
|
+ }
|
|
|
|
|
|
- function fetchUserInfo() {
|
|
|
- $.ajax({
|
|
|
- url: '/api/user.lua?action=getinfo',
|
|
|
- type: 'GET',
|
|
|
- headers:{
|
|
|
- "token":token
|
|
|
- },
|
|
|
- success: function(response) {
|
|
|
- if (response.code === 200) {
|
|
|
- const userInfo = response.data;
|
|
|
- alert(`用户信息:\n登录时间: ${userInfo.login_time}`);
|
|
|
- } else {
|
|
|
- alert('获取用户信息失败,请重试');
|
|
|
- }
|
|
|
- },
|
|
|
- error: function() {
|
|
|
- alert('获取用户信息失败,请重试');
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
- </script>
|
|
|
+ * { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
+
|
|
|
+ body {
|
|
|
+ font-family: "IBM Plex Sans", "Segoe UI", sans-serif;
|
|
|
+ background:
|
|
|
+ radial-gradient(ellipse 80% 50% at 20% -10%, #1a3050 0%, transparent 55%),
|
|
|
+ radial-gradient(ellipse 60% 40% at 90% 100%, #152030 0%, transparent 50%),
|
|
|
+ var(--bg);
|
|
|
+ color: var(--text);
|
|
|
+ min-height: 100vh;
|
|
|
+ padding: 2rem 1.25rem 3rem;
|
|
|
+ line-height: 1.5;
|
|
|
+ }
|
|
|
+
|
|
|
+ main {
|
|
|
+ max-width: 720px;
|
|
|
+ margin: 0 auto;
|
|
|
+ }
|
|
|
+
|
|
|
+ h1 {
|
|
|
+ font-family: "IBM Plex Mono", "Cascadia Code", monospace;
|
|
|
+ font-size: 1.35rem;
|
|
|
+ font-weight: 600;
|
|
|
+ letter-spacing: -0.02em;
|
|
|
+ margin-bottom: 0.35rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .sub {
|
|
|
+ color: var(--muted);
|
|
|
+ font-size: 0.9rem;
|
|
|
+ margin-bottom: 1.75rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .sub code {
|
|
|
+ font-family: "IBM Plex Mono", monospace;
|
|
|
+ font-size: 0.85em;
|
|
|
+ color: var(--accent);
|
|
|
+ }
|
|
|
+
|
|
|
+ .row {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 0.6rem;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 1rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .status {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 0.45rem;
|
|
|
+ font-size: 0.85rem;
|
|
|
+ color: var(--muted);
|
|
|
+ margin-left: auto;
|
|
|
+ }
|
|
|
+
|
|
|
+ .dot {
|
|
|
+ width: 8px;
|
|
|
+ height: 8px;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: var(--muted);
|
|
|
+ }
|
|
|
+
|
|
|
+ .dot.open { background: var(--ok); box-shadow: 0 0 8px var(--ok); }
|
|
|
+ .dot.connecting { background: var(--warn); }
|
|
|
+ .dot.closed, .dot.error { background: var(--err); }
|
|
|
+
|
|
|
+ button, input {
|
|
|
+ font: inherit;
|
|
|
+ }
|
|
|
+
|
|
|
+ button {
|
|
|
+ background: var(--accent);
|
|
|
+ color: #0a1220;
|
|
|
+ border: none;
|
|
|
+ padding: 0.55rem 1.1rem;
|
|
|
+ font-weight: 600;
|
|
|
+ font-size: 0.875rem;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: background 0.15s;
|
|
|
+ }
|
|
|
+
|
|
|
+ button:hover:not(:disabled) { background: var(--accent-hover); }
|
|
|
+ button:disabled {
|
|
|
+ opacity: 0.4;
|
|
|
+ cursor: not-allowed;
|
|
|
+ }
|
|
|
+
|
|
|
+ button.ghost {
|
|
|
+ background: transparent;
|
|
|
+ color: var(--text);
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ }
|
|
|
+
|
|
|
+ button.ghost:hover:not(:disabled) {
|
|
|
+ border-color: var(--muted);
|
|
|
+ background: var(--surface);
|
|
|
+ }
|
|
|
+
|
|
|
+ .url {
|
|
|
+ width: 100%;
|
|
|
+ background: var(--surface);
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ color: var(--text);
|
|
|
+ padding: 0.65rem 0.85rem;
|
|
|
+ font-family: "IBM Plex Mono", monospace;
|
|
|
+ font-size: 0.85rem;
|
|
|
+ margin-bottom: 1rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .url:focus {
|
|
|
+ outline: none;
|
|
|
+ border-color: var(--accent);
|
|
|
+ }
|
|
|
+
|
|
|
+ .send-row {
|
|
|
+ display: flex;
|
|
|
+ gap: 0.6rem;
|
|
|
+ margin-bottom: 1rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .send-row input {
|
|
|
+ flex: 1;
|
|
|
+ background: var(--surface);
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ color: var(--text);
|
|
|
+ padding: 0.65rem 0.85rem;
|
|
|
+ font-size: 0.9rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .send-row input:focus {
|
|
|
+ outline: none;
|
|
|
+ border-color: var(--accent);
|
|
|
+ }
|
|
|
+
|
|
|
+ .log {
|
|
|
+ background: var(--surface);
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ height: min(52vh, 420px);
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 0.75rem;
|
|
|
+ font-family: "IBM Plex Mono", monospace;
|
|
|
+ font-size: 0.8rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .log .line {
|
|
|
+ padding: 0.2rem 0;
|
|
|
+ border-bottom: 1px solid transparent;
|
|
|
+ word-break: break-all;
|
|
|
+ white-space: pre-wrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .log .line .t { color: var(--muted); margin-right: 0.5rem; }
|
|
|
+ .log .line.sys { color: var(--muted); }
|
|
|
+ .log .line.out { color: var(--accent); }
|
|
|
+ .log .line.in { color: var(--ok); }
|
|
|
+ .log .line.err { color: var(--err); }
|
|
|
+ </style>
|
|
|
</head>
|
|
|
<body>
|
|
|
- <div class="login-container">
|
|
|
- <h1>用户登录</h1>
|
|
|
- <input type="text" id="username" placeholder="请输入用户名">
|
|
|
- <input type="password" id="password" placeholder="请输入密码">
|
|
|
- <button onclick="login()">确认</button>
|
|
|
- <div id="errorMessage" class="error-message"></div>
|
|
|
+ <main>
|
|
|
+ <h1>WebSocket 测试</h1>
|
|
|
+ <p class="sub">连接到当前服务器 <code id="pathHint">/api/ws.lua</code></p>
|
|
|
+
|
|
|
+ <input class="url" id="url" type="text" spellcheck="false" />
|
|
|
+
|
|
|
+ <div class="row">
|
|
|
+ <button id="btnConnect" type="button">连接</button>
|
|
|
+ <button id="btnDisconnect" class="ghost" type="button" disabled>断开</button>
|
|
|
+ <button id="btnClear" class="ghost" type="button">清空日志</button>
|
|
|
+ <span class="status">
|
|
|
+ <span class="dot" id="dot"></span>
|
|
|
+ <span id="statusText">未连接</span>
|
|
|
+ </span>
|
|
|
</div>
|
|
|
+
|
|
|
+ <div class="send-row">
|
|
|
+ <input id="msg" type="text" placeholder="输入要发送的消息…" disabled />
|
|
|
+ <button id="btnSend" type="button" disabled>发送</button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="log" id="log" aria-live="polite"></div>
|
|
|
+ </main>
|
|
|
+
|
|
|
+ <script>
|
|
|
+ (function () {
|
|
|
+ var path = "/api/ws.lua";
|
|
|
+ var proto = location.protocol === "https:" ? "wss:" : "ws:";
|
|
|
+ var defaultUrl = proto + "//" + location.host + path;
|
|
|
+
|
|
|
+ var urlEl = document.getElementById("url");
|
|
|
+ var msgEl = document.getElementById("msg");
|
|
|
+ var logEl = document.getElementById("log");
|
|
|
+ var dotEl = document.getElementById("dot");
|
|
|
+ var statusEl = document.getElementById("statusText");
|
|
|
+ var btnConnect = document.getElementById("btnConnect");
|
|
|
+ var btnDisconnect = document.getElementById("btnDisconnect");
|
|
|
+ var btnSend = document.getElementById("btnSend");
|
|
|
+ var btnClear = document.getElementById("btnClear");
|
|
|
+
|
|
|
+ urlEl.value = defaultUrl;
|
|
|
+
|
|
|
+ var ws = null;
|
|
|
+
|
|
|
+ function now() {
|
|
|
+ var d = new Date();
|
|
|
+ return (
|
|
|
+ String(d.getHours()).padStart(2, "0") + ":" +
|
|
|
+ String(d.getMinutes()).padStart(2, "0") + ":" +
|
|
|
+ String(d.getSeconds()).padStart(2, "0") + "." +
|
|
|
+ String(d.getMilliseconds()).padStart(3, "0")
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ function log(kind, text) {
|
|
|
+ var line = document.createElement("div");
|
|
|
+ line.className = "line " + kind;
|
|
|
+ var t = document.createElement("span");
|
|
|
+ t.className = "t";
|
|
|
+ t.textContent = now();
|
|
|
+ line.appendChild(t);
|
|
|
+ line.appendChild(document.createTextNode(text));
|
|
|
+ logEl.appendChild(line);
|
|
|
+ logEl.scrollTop = logEl.scrollHeight;
|
|
|
+ }
|
|
|
+
|
|
|
+ function setState(state, label) {
|
|
|
+ dotEl.className = "dot " + state;
|
|
|
+ statusEl.textContent = label;
|
|
|
+ var open = state === "open";
|
|
|
+ var connecting = state === "connecting";
|
|
|
+ btnConnect.disabled = open || connecting;
|
|
|
+ btnDisconnect.disabled = !open && !connecting;
|
|
|
+ btnSend.disabled = !open;
|
|
|
+ msgEl.disabled = !open;
|
|
|
+ urlEl.disabled = open || connecting;
|
|
|
+ }
|
|
|
+
|
|
|
+ function connect() {
|
|
|
+ var url = urlEl.value.trim();
|
|
|
+ if (!url) return;
|
|
|
+
|
|
|
+ if (ws) {
|
|
|
+ try { ws.close(); } catch (e) {}
|
|
|
+ ws = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ setState("connecting", "连接中…");
|
|
|
+ log("sys", "→ 连接 " + url);
|
|
|
+
|
|
|
+ try {
|
|
|
+ ws = new WebSocket(url);
|
|
|
+ } catch (e) {
|
|
|
+ setState("error", "错误");
|
|
|
+ log("err", "创建失败: " + e.message);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ ws.onopen = function () {
|
|
|
+ setState("open", "已连接");
|
|
|
+ log("sys", "✓ 连接已建立 (readyState=" + ws.readyState + ")");
|
|
|
+ msgEl.focus();
|
|
|
+ };
|
|
|
+
|
|
|
+ ws.onmessage = function (ev) {
|
|
|
+ var data = ev.data;
|
|
|
+ if (data instanceof Blob) {
|
|
|
+ data.arrayBuffer().then(function (buf) {
|
|
|
+ log("in", "← binary " + buf.byteLength + " bytes");
|
|
|
+ });
|
|
|
+ } else if (data instanceof ArrayBuffer) {
|
|
|
+ log("in", "← binary " + data.byteLength + " bytes");
|
|
|
+ } else {
|
|
|
+ log("in", "← " + data);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ ws.onerror = function () {
|
|
|
+ log("err", "WebSocket error");
|
|
|
+ setState("error", "错误");
|
|
|
+ };
|
|
|
+
|
|
|
+ ws.onclose = function (ev) {
|
|
|
+ var reason = ev.reason ? " — " + ev.reason : "";
|
|
|
+ log("sys", "× 已关闭 (code=" + ev.code + reason + ", clean=" + ev.wasClean + ")");
|
|
|
+ setState("closed", "已断开");
|
|
|
+ ws = null;
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ function disconnect() {
|
|
|
+ if (!ws) return;
|
|
|
+ log("sys", "→ 主动关闭");
|
|
|
+ ws.close(1000, "client close");
|
|
|
+ }
|
|
|
+
|
|
|
+ function send() {
|
|
|
+ if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
|
+ var text = msgEl.value;
|
|
|
+ ws.send(text);
|
|
|
+ log("out", "→ " + text);
|
|
|
+ msgEl.value = "";
|
|
|
+ msgEl.focus();
|
|
|
+ }
|
|
|
+
|
|
|
+ btnConnect.addEventListener("click", connect);
|
|
|
+ btnDisconnect.addEventListener("click", disconnect);
|
|
|
+ btnSend.addEventListener("click", send);
|
|
|
+ btnClear.addEventListener("click", function () {
|
|
|
+ logEl.innerHTML = "";
|
|
|
+ });
|
|
|
+
|
|
|
+ msgEl.addEventListener("keydown", function (e) {
|
|
|
+ if (e.key === "Enter") send();
|
|
|
+ });
|
|
|
+
|
|
|
+ urlEl.addEventListener("keydown", function (e) {
|
|
|
+ if (e.key === "Enter") connect();
|
|
|
+ });
|
|
|
+
|
|
|
+ setState("closed", "未连接");
|
|
|
+ })();
|
|
|
+ </script>
|
|
|
</body>
|
|
|
</html>
|