Files
Toolbox/public/tools/greedy_snake/js/ui.js
T
yangxiangyuan 6dc79a2c12 feat: 新增多项功能并添加新工具
- 私人剪贴板新增限时直通URL生成功能
- doc_cloud_keeper新增标题复制按钮
- 新增贪蛇出动游戏工具
- 新增OSS中转直链工具
- 添加批量抽帧测试脚本
2026-08-11 10:22:31 +08:00

214 lines
9.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 贪蛇出动 - UI:HUD 刷新 / 道具转盘 / 复活 / 结算 / Toast 与交互详情弹窗(双模式) */
(function () {
'use strict';
window.GS = window.GS || {};
const U = GS.U;
/* ---------- 道具图标:程序化 SVG ---------- */
function itemSvg(key) {
switch (key) {
case 'helmet':
return '<svg viewBox="0 0 64 64"><path d="M10 38a22 22 0 0 1 44 0v6H36l-4-8-4 8H10z" fill="#e04a3a"/><path d="M14 38a18 18 0 0 1 36 0" fill="none" stroke="#fff" stroke-width="3" opacity=".55"/><rect x="8" y="42" width="48" height="6" rx="3" fill="#b83224"/></svg>';
case 'rocket':
return '<svg viewBox="0 0 64 64"><path d="M32 4c8 8 10 20 10 28l-10 10-10-10c0-8 2-20 10-28z" fill="#e8ecf5"/><circle cx="32" cy="24" r="5" fill="#5a8dd0"/><path d="M22 32l-8 12 10-2zM42 32l8 12-10-2z" fill="#e04a3a"/><path d="M28 44l4 12 4-12z" fill="#ffb62e"/></svg>';
case 'magnet':
return '<svg viewBox="0 0 64 64"><path d="M18 12v20a14 14 0 0 0 28 0V12H36v20a4 4 0 0 1-8 0V12z" fill="#e04a3a"/><rect x="18" y="12" width="10" height="10" fill="#e8ecf5"/><rect x="36" y="12" width="10" height="10" fill="#e8ecf5"/></svg>';
case 'expcard':
return '<svg viewBox="0 0 64 64"><rect x="8" y="16" width="48" height="32" rx="5" fill="#ffd23e"/><rect x="8" y="16" width="48" height="32" rx="5" fill="none" stroke="#c98f00" stroke-width="2"/><circle cx="24" cy="32" r="8" fill="#fff3c4"/><path d="M38 26h12M38 32h12M38 38h8" stroke="#c98f00" stroke-width="3" stroke-linecap="round"/></svg>';
default: return '';
}
}
class UI {
constructor() {
this.el = id => document.getElementById(id);
this.statsEl = this.el('gs-hud-stats');
this.coinsEl = this.el('gs-stat-coins');
this.luckyEl = this.el('gs-stat-lucky');
this.killsEl = this.el('gs-stat-kills');
this.progEl = this.el('gs-hud-progress');
this.progFill = this.el('gs-progress-fill');
this.progText = this.el('gs-progress-text');
this.boardEl = this.el('gs-hud-board');
this.boardList = this.el('gs-board-list');
this.bestName = this.el('gs-best-name');
this.bestScore = this.el('gs-best-score');
this.rouletteEl = this.el('gs-roulette');
this.reviveEl = this.el('gs-revive');
this.resultEl = this.el('gs-result');
this.toastEl = this.el('gs-toast');
this.dlgEl = this.el('gs-dialog');
this._bulbs();
this._toastTimer = null;
this._dlgTimer = null;
}
/* 转盘灯泡装饰 */
_bulbs() {
document.querySelectorAll('.gs-bulbs').forEach(box => {
const vert = box.classList.contains('gs-bulbs-left') || box.classList.contains('gs-bulbs-right');
const n = vert ? 7 : 12;
for (let i = 0; i < n; i++) box.appendChild(document.createElement('i'));
});
setInterval(() => {
document.querySelectorAll('.gs-bulbs i').forEach((b, i) => b.classList.toggle('off', (i + Math.floor(Date.now() / 400)) % 2 === 0));
}, 400);
}
setUiMode(mode) { GS.CFG.uiMode = mode; }
/* ---------- HUD ---------- */
showHud(v) {
this.statsEl.classList.toggle('gs-hidden', !v);
this.boardEl.classList.toggle('gs-hidden', !v);
}
updateStats(totalCoins, lucky, kills) {
this.coinsEl.textContent = U.fmt(totalCoins);
this.luckyEl.textContent = U.fmt(lucky);
this.killsEl.textContent = U.fmt(kills);
}
updateProgress(score, target, visible) {
this.progEl.classList.toggle('gs-hidden', !visible);
if (!visible) return;
this.progFill.style.width = U.clamp(score / target * 100, 0, 100) + '%';
this.progText.textContent = Math.floor(Math.min(score, target)) + '/' + target;
}
updateBoard(rows, me, best) {
if (best) {
this.bestName.textContent = best.name || '-';
this.bestScore.textContent = U.fmt(best.score || 0);
}
let html = '';
rows.forEach((r, i) => {
const rank = i + 1;
const medal = rank === 1 ? '👑' : (rank <= 3 ? ['①', '②', '③'][rank - 1] : rank);
html += `<div class="gs-board-row${r.me ? ' me' : ''}">
<span class="gs-board-rank r${rank}">${medal}</span>
<span class="gs-board-name">${this._esc(r.name)}</span>
<span class="gs-board-score">${U.fmt(r.score)}</span></div>`;
});
this.boardList.innerHTML = html;
}
_esc(s) { return String(s).replace(/[<>&"]/g, c => ({ '<': '&lt;', '>': '&gt;', '&': '&amp;', '"': '&quot;' }[c])); }
/* ---------- 转盘 ---------- */
showRoulette(gems) {
this.rouletteEl.classList.remove('gs-hidden');
this.el('gs-reroll-text').textContent = GS.CFG.rerollCost + ' 换一换';
this.updateGems(gems);
}
hideRoulette() { this.rouletteEl.classList.add('gs-hidden'); }
updateGems(gems) {
const btn = this.el('gs-reroll');
btn.disabled = gems < GS.CFG.rerollCost;
}
setReels(keys, selected) {
document.querySelectorAll('.gs-reel').forEach((reel, i) => {
reel.classList.toggle('selected', selected === i);
reel.querySelector('.gs-reel-icon').innerHTML = itemSvg(keys[i]);
reel.title = GS.ITEMS[keys[i]].name + ':' + GS.ITEMS[keys[i]].desc;
});
}
spinReels(keys) {
// 滚动动画:快速切换图标后落定
const reels = document.querySelectorAll('.gs-reel');
reels.forEach((reel, i) => {
const icon = reel.querySelector('.gs-reel-icon');
let n = 0;
const timer = setInterval(() => {
icon.innerHTML = itemSvg(U.pick(GS.ITEM_KEYS));
if (++n > 8 + i * 4) { clearInterval(timer); icon.innerHTML = itemSvg(keys[i]); }
}, 70);
});
}
/* ---------- 复活 ---------- */
showRevive(killerName, usedFree) {
this.reviveEl.classList.remove('gs-hidden');
this.el('gs-revive-killer').textContent = killerName || '对手';
const btn = this.el('gs-revive-btn');
btn.disabled = usedFree;
btn.textContent = usedFree ? '已复活过' : '▶ 免费复活';
}
hideRevive() { this.reviveEl.classList.add('gs-hidden'); }
updateReviveCount(n, pct) {
this.el('gs-revive-count').textContent = n;
this.el('gs-revive-count-ring').style.setProperty('--p', (pct * 100) + '%');
}
/* ---------- 结算 ---------- */
showResult(data) {
this.resultEl.classList.remove('gs-hidden');
this.el('gs-result-score').textContent = U.fmt(data.score);
this.el('gs-result-coins').textContent = U.fmt(data.coins);
this.el('gs-result-kills').textContent = U.fmt(data.kills);
this.el('gs-result-lucky').textContent = U.fmt(data.lucky);
this.el('gs-result-best-num').textContent = U.fmt(data.best);
}
hideResult() { this.resultEl.classList.add('gs-hidden'); }
/* ---------- Toast(normal 模式 / 强提示) ---------- */
toast(msg, type) {
const el = this.toastEl;
el.textContent = msg;
el.className = type || '';
el.classList.remove('gs-hidden');
clearTimeout(this._toastTimer);
this._toastTimer = setTimeout(() => el.classList.add('gs-hidden'), 2000);
}
/* ---------- 交互详情弹窗(debug 模式) ---------- */
showDialog(info, mode) {
// mode: view=查看类(normal 降级 toast) / error=异常(双模式保留)
const isDebug = GS.CFG.uiMode === 'debug';
if (!isDebug && mode === 'view') { this.toast(info.action + ' 成功', 'success'); return; }
if (!isDebug && mode !== 'error') { this.toast(info.action + (info.ok ? ' 成功' : ' 失败'), info.ok ? 'success' : 'error'); return; }
const body = this.el('gs-dialog-body');
body.textContent =
'触发动作:' + info.action + '\n' +
'时间:' + U.nowStr() + '\n' +
'接口地址:' + (info.url || '-') + '\n' +
'HTTP 方法:' + (info.method || '-') + '\n' +
'请求参数:' + (info.req || '-') + '\n' +
'返回结果:' + (info.res || '-') + '\n' +
'错误信息:' + (info.err || '-') + '\n' +
'耗时:' + (info.ms != null ? info.ms + 'ms' : '-') + '\n' +
'当前状态:' + (info.ok ? '成功' : '失败');
this.dlgEl.classList.remove('gs-hidden');
this._dlgInfo = info;
clearTimeout(this._dlgTimer);
if (mode !== 'error') this._dlgTimer = setTimeout(() => this.hideDialog(), 3000);
}
hideDialog() { this.dlgEl.classList.add('gs-hidden'); }
/* 带交互记录的 fetch 包装(双模式) */
async trackFetch(action, url, opts) {
const t0 = performance.now();
let info = { action, url, method: (opts && opts.method) || 'GET', req: opts && opts.body ? opts.body : '-' };
try {
const res = await fetch(url, opts);
const text = await res.text();
info.ms = Math.round(performance.now() - t0);
info.res = text.slice(0, 400);
info.ok = res.ok;
if (!res.ok) info.err = 'HTTP ' + res.status;
this.showDialog(info, res.ok ? 'view' : 'error');
return res.ok ? JSON.parse(text) : null;
} catch (e) {
info.ms = Math.round(performance.now() - t0);
info.ok = false;
info.err = String(e && e.message || e);
this.showDialog(info, 'error');
return null;
}
}
}
GS.UI = UI;
GS.itemSvg = itemSvg;
})();