路由页改成主从布局,出站可以真的改了

右侧详情面板取代原来的手风琴展开:空间用起来了,选中状态更清楚,行也不会被推来推去。
出站选择做成分组的按钮网格而不是下拉框——一眼看得到全部可选项和当前状态。

点一下就走完整的落盘链路(校验、备份、写入、重载),失败时按停在哪一步显示不同的话。
订阅线路那一档暂时禁用:选中它需要先自动建一个筛选组,那部分还没做。

顺带两处从截图里看出来的问题:连接线原本是固定 40px 的短横,读起来像装饰不像连接,
改成占据弹性列真的把两头连起来;默认入站的名字原本是「HTTP 和 SOCKS5 共用」,
和类型列重复,改成它来自的配置键 mixed-port。
This commit is contained in:
im0o
2026-08-10 11:55:53 +08:00
parent 1fc052028e
commit 206e14c7e7
6 changed files with 618 additions and 321 deletions
+25 -2
View File
@@ -4,13 +4,14 @@ import { ApiError, api, getToken, setToken } from './api'
import type { ConfigSnapshot } from './api'
import { NAV } from './nav'
import { density, toggleDensity } from './prefs'
import InboundList from './views/InboundList.vue'
import RouterPage from './views/RouterPage.vue'
import TokenGate from './views/TokenGate.vue'
import IconFold from '~icons/ri/sidebar-fold-line'
import IconUnfold from '~icons/ri/sidebar-unfold-line'
const snapshot = ref<ConfigSnapshot | null>(null)
const kernel = ref<{ online: boolean; version?: string }>({ online: false })
const providerLines = ref<string[]>([])
const needsToken = ref(!getToken())
const loadError = ref('')
const loading = ref(false)
@@ -24,6 +25,9 @@ async function load() {
snapshot.value = config
kernel.value = status
needsToken.value = false
// 订阅里有哪些线路只有运行中的内核知道,配置文件里只有一个订阅地址。
// 拿不到不算错误——界面照常能编辑,只是订阅线路那一档不可选。
providerLines.value = status.online ? await readProviderLines() : []
} catch (error) {
if (error instanceof ApiError && error.unauthorized) {
needsToken.value = true
@@ -35,6 +39,18 @@ async function load() {
}
}
/** 只取真正的订阅。内核会给每个用了 proxies 的策略组造一个内部 provider,那些不是订阅。 */
async function readProviderLines(): Promise<string[]> {
try {
const { providers } = await api.providers()
return Object.values(providers)
.filter((p) => p.vehicleType !== 'Compatible')
.flatMap((p) => p.proxies.map((line) => line.name))
} catch {
return []
}
}
function submitToken(value: string) {
setToken(value)
void load()
@@ -109,7 +125,14 @@ const current = computed(() => NAV.find((n) => n.key === active.value))
</ul>
</div>
<InboundList v-if="active === 'router'" :data="snapshot.data" />
<RouterPage
v-if="active === 'router'"
:data="snapshot.data"
:revision="snapshot.revision"
:kernel-online="kernel.online"
:provider-lines="providerLines"
@changed="load"
/>
<div v-else class="note">
<component :is="current?.icon" class="note-icon" />