fix(ui): narrow-screen header (hide brand name, icon-only nav, scrollable), wrap wide tables for horizontal scroll, add favicon

This commit is contained in:
root
2026-08-10 09:35:21 +08:00
parent 597babe331
commit fa65dcbe70

View File

@ -4,6 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ModelRouter</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 34 34'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%233f6ef5'/%3E%3Cstop offset='1' stop-color='%236a8ffb'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='34' height='34' rx='10' fill='url(%23g)'/%3E%3Ctext x='17' y='24' text-anchor='middle' font-family='Arial,sans-serif' font-size='17' font-weight='700' fill='%23fff'%3EM%3C/text%3E%3C/svg%3E">
<style>
:root{
--bg:#f4f6fb; --card:#ffffff; --card2:#f9fafd; --line:#e3e6f0; --fg:#1d2434; --muted:#66708a;
@ -38,6 +39,7 @@ header { display:flex; align-items:center; gap:14px; padding:14px 24px; border-b
nav { display:flex; gap:6px; padding:0; }
nav button { background:transparent; border:1px solid transparent; color:var(--muted); padding:7px 16px;
cursor:pointer; border-radius:9px; font:inherit; font-weight:500; transition:all .15s; }
nav button .ni { font-style:normal; display:none; }
nav button:hover { color:var(--fg); background:var(--card2); }
nav button.active { background:var(--card2); border-color:var(--line); color:var(--accent); box-shadow:var(--shadow); }
main { padding:20px 24px 48px; max-width:1240px; margin:0 auto; }
@ -343,16 +345,22 @@ html[data-theme="dark"] .dropzone.dragover, html[data-theme="dark"] .dropzone:ho
}
@media (max-width: 640px) {
header { gap:8px; padding:10px 12px; }
.brand h1 { font-size:15px; }
.brand h1 { display:none; }
.brand .sub { display:none; }
.hd-actions button { padding:5px 9px; }
nav { gap:4px; padding:10px 12px 0; }
nav { gap:4px; padding:10px 12px 0; flex:1 1 auto; min-width:0; }
nav button { padding:6px 10px; font-size:13px; }
nav button .ni { display:inline-block; }
nav button span { display:none; }
#btn-logout .ni { display:none; }
#btn-lang span, #btn-logout span { display:none; }
#btn-logout .ni { display:inline-block; font-style:normal; }
main { padding:12px 12px 32px; }
.card { padding:13px; border-radius:12px; margin-bottom:14px; }
.card h2 { font-size:13px; }
th,td { padding:8px 10px; }
.row { flex-direction:column; gap:0; }
.dash-row { flex-direction:column; }
.model-row { flex-wrap:wrap; }
.model-row input { flex:1 1 120px; }
.model-row select { flex:0 0 auto; }
@ -384,18 +392,18 @@ html[data-theme="dark"] .dropzone.dragover, html[data-theme="dark"] .dropzone:ho
<div><h1>ModelRouter</h1><div class="sub" data-i="tagline">统一 LLM 网关</div></div>
</div>
<nav>
<button data-tab="status" class="active" data-i="navStatus">状态</button>
<button data-tab="chat" data-i="navChat">Chat 测试</button>
<button data-tab="keys" data-i="navKeys">密钥</button>
<button data-tab="sort" data-i="navSort">优先级</button>
<button data-tab="sources" data-i="navSources"></button>
<button data-tab="adapters" data-i="navAdapters">适配器</button>
<button data-tab="status" class="active"><i class="ni" aria-hidden="true"></i><span data-i="navStatus">状态</span></button>
<button data-tab="chat"><i class="ni" aria-hidden="true"></i><span data-i="navChat">对话</span></button>
<button data-tab="keys"><i class="ni" aria-hidden="true"></i><span data-i="navKeys">密钥</span></button>
<button data-tab="sort"><i class="ni" aria-hidden="true"></i><span data-i="navSort">优先级</span></button>
<button data-tab="sources"><i class="ni" aria-hidden="true"></i><span data-i="navSources"></span></button>
<button data-tab="adapters"><i class="ni" aria-hidden="true"></i><span data-i="navAdapters">适配器</span></button>
</nav>
<div class="spacer"></div>
<div class="hd-actions">
<button id="btn-theme" title="theme"></button>
<button id="btn-lang">EN</button>
<button class="ghost" id="btn-logout" data-i="logout">退出登录</button>
<button class="ghost" id="btn-logout"><i class="ni"></i><span data-i="logout">退出登录</span></button>
</div>
</header>
<main>
@ -607,7 +615,7 @@ async function renderStatus() {
<div class="chips" id="model-chips"></div>
</div>
<div class="card"><h2>${t('srcTitle')} (${s.sources.length})</h2>
<table><tr><th>${t('tName')}</th><th>${t('tAdapter')}</th><th>${t('tModels')}</th><th>${t('tURL')}</th><th>${t('tConn')}</th><th>${t('tConc')}</th></tr>${srcRows || `<tr><td colspan="6" class="empty">${t('srcEmpty')}</td></tr>`}</table>
<div class="tbl-wrap"><table><tr><th>${t('tName')}</th><th>${t('tAdapter')}</th><th>${t('tModels')}</th><th>${t('tURL')}</th><th>${t('tConn')}</th><th>${t('tConc')}</th></tr>${srcRows || `<tr><td colspan="6" class="empty">${t('srcEmpty')}</td></tr>`}</table></div>
</div>
<div class="dash-row">
<div class="card"><h2>${t('dashModel')}</h2><div id="tb-model"></div></div>
@ -622,8 +630,8 @@ async function renderStatus() {
<div class="recs-scroll" id="tb-recs"></div>
</div>
<div class="card"><h2>${t('adTitle')} (${s.adapters.length})</h2>
<table><tr><th>${t('tName')}</th><th>${t('tVersion')}</th></tr>
${s.adapters.map(a => `<tr><td>${esc(a.name)}</td><td>${esc(a.version || '')}</td></tr>`).join('')}</table>
<div class="tbl-wrap"><table><tr><th>${t('tName')}</th><th>${t('tVersion')}</th></tr>
${s.adapters.map(a => `<tr><td>${esc(a.name)}</td><td>${esc(a.version || '')}</td></tr>`).join('')}</table></div>
</div>`;
$('#conncfg').textContent = '';
showModelConfig(null, '');
@ -997,8 +1005,8 @@ async function renderSources() {
$('#tab-sources').innerHTML = `
<div class="card"><h2><span>${t('srcTitle')}</span><span class="grow"></span>
<button class="small" onclick="editSource('')">${t('srcAdd')}</button></h2>
<table><tr><th>${t('tName')}</th><th>${t('tURL')}</th><th>${t('tAdapter')}</th><th>${t('tModels')}</th><th></th></tr>
${rows || `<tr><td colspan="5" class="empty">${t('srcEmpty')}</td></tr>`}</table>
<div class="tbl-wrap"><table><tr><th>${t('tName')}</th><th>${t('tURL')}</th><th>${t('tAdapter')}</th><th>${t('tModels')}</th><th></th></tr>
${rows || `<tr><td colspan="5" class="empty">${t('srcEmpty')}</td></tr>`}</table></div>
</div>`;
}
function editSource(name) {
@ -1625,8 +1633,8 @@ async function renderAdapters() {
<td><button class="danger small" onclick="delAdapter('${escAttr(a.name)}')">${t('srcDel')}</button></td></tr>`).join('');
$('#tab-adapters').innerHTML = `
<div class="card"><h2>${t('adTitle')} (${j.adapters.length})</h2>
<table><tr><th>${t('tName')}</th><th>${t('tVersion')}</th><th></th></tr>
${rows || `<tr><td colspan="3" class="empty">${t('adEmpty')}</td></tr>`}</table>
<div class="tbl-wrap"><table><tr><th>${t('tName')}</th><th>${t('tVersion')}</th><th></th></tr>
${rows || `<tr><td colspan="3" class="empty">${t('adEmpty')}</td></tr>`}</table></div>
</div>
<div class="card"><h2>${t('uploadTitle')}</h2>
<div class="dropzone" id="dz">${t('dropHint')}</div>
@ -1704,13 +1712,13 @@ async function renderKeys() {
async function renderKeysUser(me) {
$('#tab-keys').innerHTML = `
<div class="card"><h2>${t('kMeTitle')}</h2>
<table><tr><th>${t('kName')}</th><th>${t('kMeRole')}</th><th>${t('kKey')}</th><th>${t('kMeModels')}</th></tr>
<div class="tbl-wrap"><table><tr><th>${t('kName')}</th><th>${t('kMeRole')}</th><th>${t('kKey')}</th><th>${t('kMeModels')}</th></tr>
<tr><td><b>${esc(me.name || '—')}</b></td><td>${roleTag(me.role)}</td>
<td><span class="kr-key">${esc(me.key)}</span>
<button class="ghost small" onclick="copyText('${escAttr(me.key)}')">${t('kCopy')}</button></td>
<td>${(me.models && me.models.length)
? me.models.map(m => { const src = normSrc(m.source); return `<span class="tag tag-blue" title="${m.token_quota ? 'quota ' + fmtQuota(m.token_quota) : t('kQuotaUnlim')}">${esc(m.model)}${src ? ' · ' + esc(src) : ''}${m.token_quota ? ' · ' + esc(fmtQuota(m.token_quota)) : ''}</span>` }).join('')
: `<span class="scope-unlim">${t('kAll')}</span>`}</td></tr></table>
: `<span class="scope-unlim">${t('kAll')}</span>`}</td></tr></table></div>
<div class="muted" style="margin-top:10px">${t('kMeHint')}</div>
</div>`;
}