轻提示铺开到全部页面;内核状态悬停变重启;GEO 支持单文件下载

侧栏动作排撤掉重启图标,内核状态(圆点加版本号)本身变成按钮:悬停
换层显示「重启内核」,不在线时只当状态看。行高压成 1 修掉圆点和等宽
字体不居中的问题。

四个页面的一次性反馈(保存、删除、更新订阅、断开连接、测延迟结论)
全部改走 toast,commit() 改返回成败给「失败不关对话框」用;持续状态
(连接页轮询失败、请求页日志流失败)留在页内,会自动消失的提示装不下
需要一直可见的信息。toast 加 info 类别给中性结论用。

GEO 每行的更新改成单独下载那一个文件:内核只有全量更新的口子,单文件
只能服务端自己下(POST /api/geo/sync),代价是只有形状校验(MaxMind
末尾标记、geodata 首字节,shared/geo.ts 带测试)且内核重启后才换用新
文件;「更新全部」文字按钮保留内核的全量口子,有完整校验和热加载。
上传两行的按钮改「选择或拖放文件」,投放区拖入高亮,恢复策略挪到
恢复行下方。
This commit is contained in:
2026-08-12 02:20:46 +08:00
parent f761d13579
commit b276c4a8fe
13 changed files with 366 additions and 304 deletions
+69 -12
View File
@@ -271,22 +271,23 @@ const current = computed(() => NAV.find((n) => n.key === active.value))
>
<IconReload />
</button>
</div>
<div class="side-foot">
<!-- 平时是内核状态,悬停变成重启入口。内核不在线时只当状态看 -->
<button
type="button"
class="act"
class="kernel-btn"
:title="kernel.online ? '重启内核' : '内核未连接'"
:disabled="!kernel.online"
@click="askRestart = true"
>
<IconRestart />
<span class="k-idle">
<span :class="['dot', kernel.online ? 'dot-ok' : 'dot-off']" aria-hidden="true"></span>
<span class="kernel-text mono">{{ kernel.online ? kernel.version : 'offline' }}</span>
</span>
<span class="k-hot" aria-hidden="true"><IconRestart class="k-ico" /> 重启内核</span>
</button>
</div>
<div class="side-foot">
<div class="kernel" :title="kernel.online ? kernel.version : '内核未连接'">
<span :class="['dot', kernel.online ? 'dot-ok' : 'dot-off']" aria-hidden="true"></span>
<span class="kernel-text mono">{{ kernel.online ? kernel.version : 'offline' }}</span>
</div>
<button
type="button"
class="densify"
@@ -466,13 +467,13 @@ const current = computed(() => NAV.find((n) => n.key === active.value))
/* 收拢时淡出的文字。比外层宽度的 0.3s 快,字先消失、再收边,不露半截字 */
.brand-name,
.nav-label,
.kernel {
.kernel-btn {
transition: opacity 0.2s ease;
}
.is-compact .brand-name,
.is-compact .nav-label,
.is-compact .kernel {
.is-compact .kernel-btn {
opacity: 0;
}
@@ -579,13 +580,69 @@ const current = computed(() => NAV.find((n) => n.key === active.value))
padding: 0 44px 0 var(--s-2);
}
.kernel {
/* 内核状态兼重启按钮:两层叠在同一格里,悬停换层 */
.kernel-btn {
position: relative;
display: flex;
align-items: center;
flex: 1;
min-width: 0;
height: 26px;
padding: 0 var(--s-2);
border: none;
border-radius: var(--r-sm);
background: none;
cursor: pointer;
text-align: left;
}
.kernel-btn:disabled {
cursor: default;
}
.kernel-btn:hover:not(:disabled) {
background: var(--c-surface-sunken);
}
.k-idle {
display: flex;
align-items: center;
gap: var(--s-2);
min-width: 0;
color: var(--c-text-faint);
font-size: var(--t-xs);
/* 等宽字体的行框上下不对称,压成 1 让点和文字真正居中 */
line-height: 1;
transition: opacity 0.15s ease;
}
.k-hot {
position: absolute;
inset: 0;
display: flex;
align-items: center;
gap: var(--s-2);
padding: 0 var(--s-2);
color: var(--c-text-soft);
font-size: var(--t-xs);
line-height: 1;
white-space: nowrap;
opacity: 0;
transition: opacity 0.15s ease;
pointer-events: none;
}
.kernel-btn:hover:not(:disabled) .k-idle,
.kernel-btn:focus-visible:not(:disabled) .k-idle {
opacity: 0;
}
.kernel-btn:hover:not(:disabled) .k-hot,
.kernel-btn:focus-visible:not(:disabled) .k-hot {
opacity: 1;
}
.k-ico {
flex: none;
width: 14px;
height: 14px;
}
.dot {