redis
This commit is contained in:
@@ -0,0 +1,505 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Redis 缓存查看(超管)</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0f1419;
|
||||
--panel: #1a212b;
|
||||
--panel-2: #222c38;
|
||||
--border: #2c3845;
|
||||
--text: #d7e0ea;
|
||||
--muted: #8b9aab;
|
||||
--accent: #4c9aff;
|
||||
--accent-2: #2f6fd1;
|
||||
--green: #36c275;
|
||||
--red: #ff6b6b;
|
||||
--yellow: #f7c948;
|
||||
--mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-size: 14px;
|
||||
}
|
||||
header {
|
||||
padding: 16px 24px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: linear-gradient(180deg, #16202c, #0f1419);
|
||||
}
|
||||
header h1 { margin: 0; font-size: 18px; font-weight: 600; }
|
||||
header .sub { margin-top: 4px; color: var(--muted); font-size: 12px; }
|
||||
.wrap { display: flex; gap: 16px; padding: 16px 24px; align-items: flex-start; }
|
||||
.left { width: 320px; flex: 0 0 320px; }
|
||||
.right { flex: 1 1 auto; min-width: 0; }
|
||||
.card {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 16px;
|
||||
}
|
||||
.card + .card { margin-top: 16px; }
|
||||
.field { margin-bottom: 12px; }
|
||||
.field:last-child { margin-bottom: 0; }
|
||||
label { display: block; color: var(--muted); font-size: 12px; margin-bottom: 6px; }
|
||||
input[type=text] {
|
||||
width: 100%;
|
||||
padding: 9px 11px;
|
||||
background: var(--panel-2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
color: var(--text);
|
||||
font-family: var(--mono);
|
||||
outline: none;
|
||||
}
|
||||
input[type=text]:focus { border-color: var(--accent); }
|
||||
.btn {
|
||||
padding: 9px 14px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--panel-2);
|
||||
color: var(--text);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
}
|
||||
.btn:hover { border-color: var(--accent); }
|
||||
.btn.primary { background: var(--accent-2); border-color: var(--accent); color: #fff; }
|
||||
.btn.primary:hover { background: var(--accent); }
|
||||
.btn.danger { background: transparent; border-color: var(--red); color: var(--red); }
|
||||
.btn.danger:hover { background: rgba(255,107,107,.12); }
|
||||
.btn-row { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
.keys-list { max-height: 60vh; overflow: auto; margin-top: 10px; border-top: 1px solid var(--border); }
|
||||
.keys-list .k {
|
||||
padding: 8px 10px;
|
||||
font-family: var(--mono);
|
||||
font-size: 12px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.keys-list .k:hover { background: var(--panel-2); }
|
||||
.keys-list .k.active { background: rgba(76,154,255,.15); border-left: 3px solid var(--accent); padding-left: 7px; }
|
||||
.keys-list .k .kname {
|
||||
display: block; color: var(--accent); font-size: 11px; margin-bottom: 2px;
|
||||
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
||||
}
|
||||
.keys-list .k .kkey {
|
||||
display: block; font-family: var(--mono); font-size: 12px;
|
||||
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
||||
}
|
||||
.meta { display: grid; grid-template-columns: 90px 1fr; gap: 10px 14px; margin-bottom: 14px; }
|
||||
.meta .mk { color: var(--muted); font-size: 12px; }
|
||||
.meta .mv { font-family: var(--mono); word-break: break-all; }
|
||||
.badge {
|
||||
display: inline-block; padding: 2px 8px; border-radius: 999px;
|
||||
font-size: 11px; border: 1px solid var(--border); color: var(--muted);
|
||||
}
|
||||
.badge.string { color: var(--green); border-color: rgba(54,194,117,.4); }
|
||||
.badge.hash { color: var(--accent); border-color: rgba(76,154,255,.4); }
|
||||
.badge.list, .badge.set, .badge.zset { color: var(--yellow); border-color: rgba(247,201,72,.4); }
|
||||
.toolbar { display: flex; gap: 8px; align-items: center; margin-bottom: 12px; flex-wrap: wrap; }
|
||||
.count { color: var(--muted); font-size: 12px; }
|
||||
select {
|
||||
padding: 7px 10px;
|
||||
background: var(--panel-2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
color: var(--text);
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
select:focus { border-color: var(--accent); }
|
||||
pre.json {
|
||||
margin: 0; padding: 12px; background: #0b0f14; border: 1px solid var(--border);
|
||||
border-radius: 8px; overflow: auto; max-height: 60vh; font-family: var(--mono);
|
||||
font-size: 12px; line-height: 1.5; color: #cfe3ff; white-space: pre;
|
||||
}
|
||||
pre.json .jk { color: #e06c75; } /* key */
|
||||
pre.json .js { color: #98c379; } /* string value */
|
||||
pre.json .jn { color: #d19a66; } /* number */
|
||||
pre.json .jb { color: #c678dd; } /* boolean / null */
|
||||
.empty { color: var(--muted); padding: 24px; text-align: center; }
|
||||
.toast {
|
||||
position: fixed; left: 50%; bottom: 28px; transform: translateX(-50%);
|
||||
background: #2a1d1d; border: 1px solid var(--red); color: #ffd5d5;
|
||||
padding: 10px 16px; border-radius: 8px; font-size: 13px; max-width: 80vw;
|
||||
display: none; z-index: 50;
|
||||
}
|
||||
.toast.ok { background: #1d2a20; border-color: var(--green); color: #c9f4d8; }
|
||||
.toast.show { display: block; }
|
||||
.hint { color: var(--muted); font-size: 11px; margin-top: 6px; }
|
||||
.spinner {
|
||||
display: inline-block; width: 14px; height: 14px; border: 2px solid var(--muted);
|
||||
border-top-color: var(--accent); border-radius: 50%; animation: spin .7s linear infinite;
|
||||
vertical-align: -2px; margin-right: 6px;
|
||||
}
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
code { background: var(--panel-2); padding: 1px 5px; border-radius: 4px; font-family: var(--mono); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Redis 缓存查看 <span style="color:var(--accent)">(超管)</span></h1>
|
||||
<div class="sub">仅超级管理员可访问。部署后同源访问自动带登录态。</div>
|
||||
</header>
|
||||
|
||||
<div class="wrap">
|
||||
<div class="left">
|
||||
<div class="card">
|
||||
<div class="field">
|
||||
<label>按 Key 精确查询</label>
|
||||
<input type="text" id="keyInput" placeholder="例如 bsch.classmanage.sch_class_1" autocomplete="off"/>
|
||||
</div>
|
||||
<div class="btn-row">
|
||||
<button class="btn primary" id="btnQuery">查询</button>
|
||||
<button class="btn" id="btnClear">清空</button>
|
||||
</div>
|
||||
<div class="field" style="margin-top:14px">
|
||||
<label>按模式列出 Keys(支持 * ? )</label>
|
||||
<input type="text" id="patternInput" placeholder="例如 bsch.classmanage.*" autocomplete="off"/>
|
||||
</div>
|
||||
<div class="btn-row">
|
||||
<button class="btn" id="btnList">列出 Keys</button>
|
||||
</div>
|
||||
<div class="hint">生产大库下 KEYS 会阻塞,请尽量指定明确前缀,勿用单独的 <code>*</code>。</div>
|
||||
<div class="keys-list" id="keysList"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right">
|
||||
<div class="card" id="detailCard">
|
||||
<div class="empty" id="detailEmpty">输入 Key 或列出 Keys 后点击查询,结果将显示在此处。</div>
|
||||
<div id="detailBody" style="display:none">
|
||||
<div class="meta">
|
||||
<div class="mk">Key</div><div class="mv" id="mKey"></div>
|
||||
<div class="mk">缓存名称</div><div class="mv" id="mCacheName"></div>
|
||||
<div class="mk">类型</div><div class="mv" id="mType"></div>
|
||||
<div class="mk">过期时间</div><div class="mv" id="mExpire"></div>
|
||||
<div class="mk">大小</div><div class="mv" id="mSize"></div>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<select id="formatSelect" title="格式">
|
||||
<option value="json" selected>JSON</option>
|
||||
<option value="text">Text</option>
|
||||
</select>
|
||||
<button class="btn" id="btnCopy" title="复制当前内容">复制</button>
|
||||
<button class="btn" id="btnRefresh" title="刷新当前 Key">刷新</button>
|
||||
<span class="count" id="rowCount"></span>
|
||||
<span style="flex:1"></span>
|
||||
<button class="btn danger" id="btnDelete">删除此 Key</button>
|
||||
</div>
|
||||
<pre class="json" id="valueJson"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
// 同源时从页面自身路径推导 API 基址(兼容 context-path = 根 / 或 /bschclass)
|
||||
function resolveApi() {
|
||||
const base = location.pathname.substring(0, location.pathname.lastIndexOf('/') + 1);
|
||||
return base + 'api/cache';
|
||||
}
|
||||
const API = resolveApi();
|
||||
|
||||
let currentKey = null;
|
||||
let currentData = null;
|
||||
let currentValue = null;
|
||||
let currentFormat = 'json';
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
|
||||
function escHtml(s) {
|
||||
return String(s == null ? '' : s)
|
||||
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
// 缓存名称映射:prefix 与 Key 精确相等或以 prefix_ 开头即命中。
|
||||
// 注意:数组须按前缀长度降序排列,避免 sch_class 抢走 sch_class_teach / sch_class_student。
|
||||
const CACHE_NAMES = [
|
||||
{ prefix: 'bsch.classmanage.sch_class_student', name: '班级学生缓存' },
|
||||
{ prefix: 'bsch.classmanage.sch_class_teach', name: '班级任教缓存' },
|
||||
{ prefix: 'bsch.classmanage.sch_group_teacher', name: '教师组缓存' },
|
||||
{ prefix: 'bsch.classmanage.sch_class', name: '班级缓存' },
|
||||
{ prefix: 'bsch.classmanage.external_plan_data',name: '执行任务数据缓存' },
|
||||
{ prefix: 'bsch.classmanage.external_progress', name: '执行进度缓存' },
|
||||
{ prefix: 'bsch.classmanage.system_group_tree', name: '全区树缓存' },
|
||||
{ prefix: 'bsch.classmanage.system_schsystem', name: '系统学制缓存' },
|
||||
{ prefix: 'bsch.classmanage.system_yearterm', name: '系统学年学期缓存' },
|
||||
{ prefix: 'bsch.classmanage.system_subject', name: '系统科目缓存' },
|
||||
{ prefix: 'bsch.classmanage.system_grdae', name: '系统年级缓存' },
|
||||
{ prefix: 'bsch.classmanage.sch_tree', name: '学校树缓存' },
|
||||
{ prefix: 'bsch.classmanage.sch_list', name: '学校列表缓存' },
|
||||
{ prefix: 'bsch.classmanage.sch_subject', name: '学校科目缓存' },
|
||||
{ prefix: 'bsch.classmanage.sch_campus', name: '学校院校缓存' },
|
||||
{ prefix: 'bsch.classmanage.sch_division', name: '学部缓存' },
|
||||
{ prefix: 'bsch.classmanage.sch_majors', name: '专业缓存' },
|
||||
{ prefix: 'bsch.classmanage.sch_course', name: '课程缓存' },
|
||||
{ prefix: 'bsch.classmanage.sch_grdae', name: '学校年级缓存' },
|
||||
{ prefix: 'bsch.classmanage.sch_teacher', name: '教师缓存' }
|
||||
];
|
||||
|
||||
function cacheName(key) {
|
||||
if (!key) return '';
|
||||
for (const c of CACHE_NAMES) {
|
||||
if (key === c.prefix || key.startsWith(c.prefix + '_')) return c.name;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function authHeaders() {
|
||||
return { 'Accept': 'application/json' };
|
||||
}
|
||||
|
||||
function toast(msg, ok) {
|
||||
const t = $('toast');
|
||||
t.textContent = msg;
|
||||
t.className = 'toast show' + (ok ? ' ok' : '');
|
||||
clearTimeout(t._timer);
|
||||
t._timer = setTimeout(() => t.classList.remove('show'), 3500);
|
||||
}
|
||||
|
||||
function showLoading(btn, text) {
|
||||
btn.dataset.old = btn.textContent;
|
||||
btn.innerHTML = '<span class="spinner"></span>' + text;
|
||||
btn.disabled = true;
|
||||
}
|
||||
function hideLoading(btn) {
|
||||
btn.textContent = btn.dataset.old || btn.textContent;
|
||||
btn.disabled = false;
|
||||
}
|
||||
|
||||
function formatSize(bytes) {
|
||||
if (bytes == null || bytes < 0) return '-';
|
||||
if (bytes < 1024) return bytes + ' B';
|
||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB';
|
||||
return (bytes / 1024 / 1024).toFixed(2) + ' MB';
|
||||
}
|
||||
|
||||
// 检测被 CAS 登录拦截(302 重定向到登录页后再被 fetch 跟随后返回 HTML)
|
||||
function checkLoginRedirect(res) {
|
||||
const ct = res.headers.get('content-type') || '';
|
||||
if (res.redirected || !ct.includes('json')) {
|
||||
// 页面地址动态推导:开发 http://192.168.10.166:8086/,生产 http://base.yunhaixingtong.com/bschclass/
|
||||
const pageUrl = location.origin + location.pathname.substring(0, location.pathname.lastIndexOf('/') + 1) + 'cache-view.html';
|
||||
return '未登录或登录态无效(请求被重定向到 CAS 登录页)。' +
|
||||
'请在「同一个浏览器、已 CAS 登录且为超级管理员」的会话下访问:' + pageUrl;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function apiGet(path, params) {
|
||||
const url = API + path + '?' + new URLSearchParams(params).toString();
|
||||
const res = await fetch(url, { credentials: 'include', headers: authHeaders() });
|
||||
const loginErr = checkLoginRedirect(res);
|
||||
let data = null;
|
||||
try { data = await res.json(); } catch (e) { /* ignore */ }
|
||||
if (loginErr) throw new Error(loginErr);
|
||||
if (!res.ok) {
|
||||
const msg = (data && (data.msg || data.message)) || ('请求失败 HTTP ' + res.status);
|
||||
throw new Error(msg);
|
||||
}
|
||||
// 统一解包:后端统一返回 { code, msg, data } 结构,成功(code===0)时取内层 data
|
||||
if (data && typeof data === 'object' && 'code' in data && 'data' in data) {
|
||||
if (data.code !== 0) {
|
||||
throw new Error(data.msg || data.message || ('业务错误 code=' + data.code));
|
||||
}
|
||||
return data.data;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
async function queryByKey(key) {
|
||||
if (!key) { toast('请输入 Key'); return; }
|
||||
const btn = $('btnQuery');
|
||||
showLoading(btn, '查询中');
|
||||
try {
|
||||
const data = await apiGet('/get', { key });
|
||||
currentKey = key;
|
||||
currentData = data;
|
||||
renderDetail(data);
|
||||
markActiveKey(key);
|
||||
} catch (e) {
|
||||
toast('查询失败:' + e.message);
|
||||
} finally {
|
||||
hideLoading(btn);
|
||||
}
|
||||
}
|
||||
|
||||
async function listKeys(pattern) {
|
||||
pattern = pattern || '*';
|
||||
const btn = $('btnList');
|
||||
showLoading(btn, '扫描中');
|
||||
try {
|
||||
const keys = await apiGet('/keys', { pattern });
|
||||
renderKeys(Array.from(keys || []).sort());
|
||||
} catch (e) {
|
||||
toast('列出失败:' + e.message);
|
||||
} finally {
|
||||
hideLoading(btn);
|
||||
}
|
||||
}
|
||||
|
||||
function renderKeys(keys) {
|
||||
const box = $('keysList');
|
||||
box.innerHTML = '';
|
||||
if (!keys.length) {
|
||||
box.innerHTML = '<div class="k" style="cursor:default;color:var(--muted)">未匹配到 Key</div>';
|
||||
return;
|
||||
}
|
||||
keys.forEach(k => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'k';
|
||||
const name = cacheName(k);
|
||||
div.innerHTML = (name ? '<span class="kname">' + escHtml(name) + '</span>' : '') +
|
||||
'<span class="kkey">' + escHtml(k) + '</span>';
|
||||
div.title = k;
|
||||
div.onclick = () => { $('keyInput').value = k; queryByKey(k); };
|
||||
box.appendChild(div);
|
||||
});
|
||||
}
|
||||
|
||||
function markActiveKey(key) {
|
||||
document.querySelectorAll('#keysList .k').forEach(el => {
|
||||
el.classList.toggle('active', el.textContent === key);
|
||||
});
|
||||
}
|
||||
|
||||
function renderDetail(data) {
|
||||
$('detailEmpty').style.display = 'none';
|
||||
$('detailBody').style.display = 'block';
|
||||
|
||||
$('mKey').textContent = data.key || '';
|
||||
$('mCacheName').textContent = cacheName(data.key) || '-';
|
||||
const type = data.type || '-';
|
||||
$('mType').innerHTML = '<span class="badge ' + type + '">' + type + '</span>';
|
||||
|
||||
const expireSec = data.expire;
|
||||
let exp = data.expireText || '';
|
||||
if (expireSec !== undefined && expireSec !== null && expireSec >= 0) {
|
||||
exp += '(' + expireSec + ' 秒)';
|
||||
}
|
||||
$('mExpire').textContent = exp || '-';
|
||||
$('mSize').textContent = (data.size !== undefined ? formatSize(data.size) : '-') +
|
||||
(data.size !== undefined ? '(' + data.size + ' 字节)' : '');
|
||||
|
||||
renderValue(data.value);
|
||||
}
|
||||
|
||||
function renderValue(value) {
|
||||
currentValue = value;
|
||||
currentData = Object.assign({}, currentData, { value });
|
||||
const valType = Array.isArray(value) ? 'array' : (value && typeof value === 'object' ? 'object' : 'scalar');
|
||||
const count = Array.isArray(value) ? value.length : (value && typeof value === 'object' ? Object.keys(value).length : 1);
|
||||
renderFormatted();
|
||||
$('rowCount').textContent = '类型=' + valType + ' · 数量=' + count;
|
||||
}
|
||||
|
||||
function renderFormatted() {
|
||||
if (currentValue === undefined || currentValue === null) return;
|
||||
const value = currentValue;
|
||||
if (currentFormat === 'text') {
|
||||
const text = JSON.stringify(value);
|
||||
$('valueJson').textContent = text;
|
||||
} else {
|
||||
const json = JSON.stringify(value, null, 2);
|
||||
$('valueJson').innerHTML = highlightJson(json);
|
||||
}
|
||||
}
|
||||
|
||||
function highlightJson(json) {
|
||||
let html = escHtml(json);
|
||||
// 高亮 key(引号后跟冒号)
|
||||
html = html.replace(/("(?:\\.|[^"\\])*")(\s*:)/g, '<span class="jk">$1</span>$2');
|
||||
// 高亮 string value(冒号+空白后跟引号串)
|
||||
html = html.replace(/(:\s*)("(?:\\.|[^"\\])*")/g, '$1<span class="js">$2</span>');
|
||||
// 高亮 number
|
||||
html = html.replace(/(:\s*)(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)\b/g, '$1<span class="jn">$2</span>');
|
||||
// 高亮 boolean / null
|
||||
html = html.replace(/(:\s*)(true|false|null)\b/g, '$1<span class="jb">$2</span>');
|
||||
return html;
|
||||
}
|
||||
|
||||
async function deleteKey() {
|
||||
if (!currentKey) return;
|
||||
if (!confirm('确认删除 Key:\n' + currentKey + '\n此操作不可恢复!')) return;
|
||||
const btn = $('btnDelete');
|
||||
showLoading(btn, '删除中');
|
||||
try {
|
||||
const res = await fetch(API + '/delete?key=' + encodeURIComponent(currentKey), {
|
||||
method: 'POST', credentials: 'include', headers: authHeaders()
|
||||
});
|
||||
const loginErr = checkLoginRedirect(res);
|
||||
let data = null; try { data = await res.json(); } catch (e) {}
|
||||
if (loginErr) throw new Error(loginErr);
|
||||
if (!res.ok) throw new Error((data && (data.msg || data.message)) || ('HTTP ' + res.status));
|
||||
toast('已删除:' + (data.msg || ''), true);
|
||||
$('detailBody').style.display = 'none';
|
||||
$('detailEmpty').style.display = 'block';
|
||||
currentKey = null; currentData = null;
|
||||
} catch (e) {
|
||||
toast('删除失败:' + e.message);
|
||||
} finally {
|
||||
hideLoading(btn);
|
||||
}
|
||||
}
|
||||
|
||||
async function copyValue() {
|
||||
if (currentValue === null || currentValue === undefined) { toast('当前没有可复制的内容'); return; }
|
||||
const text = currentFormat === 'text' ? JSON.stringify(currentValue) : JSON.stringify(currentValue, null, 2);
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
toast('已复制到剪贴板', true);
|
||||
} catch (e) {
|
||||
// 降级方案
|
||||
const ta = document.createElement('textarea');
|
||||
ta.value = text;
|
||||
ta.style.position = 'fixed';
|
||||
ta.style.opacity = '0';
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
toast('已复制到剪贴板', true);
|
||||
} catch (err) {
|
||||
toast('复制失败:' + err.message);
|
||||
}
|
||||
document.body.removeChild(ta);
|
||||
}
|
||||
}
|
||||
|
||||
function refreshValue() {
|
||||
if (!currentKey) { toast('当前没有 Key 可刷新'); return; }
|
||||
queryByKey(currentKey);
|
||||
}
|
||||
|
||||
function changeFormat(fmt) {
|
||||
currentFormat = fmt || $('formatSelect').value;
|
||||
$('formatSelect').value = currentFormat;
|
||||
renderFormatted();
|
||||
}
|
||||
|
||||
// 事件绑定
|
||||
$('btnQuery').onclick = () => queryByKey($('keyInput').value.trim());
|
||||
$('btnList').onclick = () => listKeys($('patternInput').value.trim() || '*');
|
||||
$('btnClear').onclick = () => { $('keyInput').value = ''; $('patternInput').value = ''; $('keysList').innerHTML = ''; };
|
||||
$('btnDelete').onclick = deleteKey;
|
||||
$('btnCopy').onclick = copyValue;
|
||||
$('btnRefresh').onclick = refreshValue;
|
||||
$('formatSelect').onchange = () => changeFormat();
|
||||
$('keyInput').addEventListener('keydown', e => { if (e.key === 'Enter') queryByKey(e.target.value.trim()); });
|
||||
$('patternInput').addEventListener('keydown', e => { if (e.key === 'Enter') listKeys(e.target.value.trim() || '*'); });
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user