| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <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;
- }
- * { 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>
- <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>
|