fix(btpanel): 连接配置页始终显示全局设置

未选服务器时不再隐藏整页内容,避免白屏。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
r
2026-06-21 09:30:03 +08:00
parent 461ac81221
commit 7c99298419
3 changed files with 62 additions and 10 deletions
@@ -0,0 +1,24 @@
# Changelog — 宝塔连接配置页空白修复(2026-06-21)
**日期**2026-06-21
## 摘要
修复「连接配置」在未选服务器时整页几乎空白:全局设置(中心 IP、自动获取、批量初始化)现始终可见。
## 动机
`BtPanelPageShell` 原先要求先选服务器才渲染插槽,全局配置被挡在门外,用户看到白屏或仅一条提示。
## 涉及文件
- `frontend/src/components/btpanel/BtPanelPageShell.vue` — 新增 `requireServer`(默认 true
- `frontend/src/pages/btpanel/BtPanelSettingsPage.vue``require-server=false`,单机块按选中状态展示
## 迁移 / 重启
- 仅前端
## 验证
打开 `#/btpanel/settings`:应立刻看到「全局」卡片;选服务器后显示「当前服务器」表单。
@@ -16,9 +16,9 @@
</div> </div>
</div> </div>
<v-alert v-if="!serverId" type="info" variant="tonal" class="mb-4" text="请选择服务器" /> <v-alert v-if="props.requireServer && !serverId" type="info" variant="tonal" class="mb-4" text="请选择服务器" />
<slot v-else :server-id="serverId" :server="selected()" /> <slot :server-id="serverId" :server="selected()" />
</v-container> </v-container>
</template> </template>
@@ -28,10 +28,14 @@ import BtPanelServerPicker from '@/components/btpanel/BtPanelServerPicker.vue'
import { btPanelContextKey } from '@/composables/btpanel/btPanelContext' import { btPanelContextKey } from '@/composables/btpanel/btPanelContext'
import { useBtPanelServer } from '@/composables/btpanel/useBtPanelServer' import { useBtPanelServer } from '@/composables/btpanel/useBtPanelServer'
defineProps<{ const props = withDefaults(defineProps<{
title: string title: string
subtitle?: string subtitle?: string
}>() /** 为 false 时未选服务器也渲染默认插槽(如连接配置页的全局块) */
requireServer?: boolean
}>(), {
requireServer: true,
})
const ctx = useBtPanelServer() const ctx = useBtPanelServer()
const { servers, serverId, loading, loadServers, setServerId, selected } = ctx const { servers, serverId, loading, loadServers, setServerId, selected } = ctx
@@ -1,5 +1,9 @@
<template> <template>
<BtPanelPageShell title="宝塔 · 连接配置" subtitle="SSH 自动获取 API 或手动填写面板地址与密钥"> <BtPanelPageShell
title="宝塔 · 连接配置"
subtitle="SSH 自动获取 API 或手动填写面板地址与密钥"
:require-server="false"
>
<template #default="{ serverId }"> <template #default="{ serverId }">
<v-card max-width="720" variant="outlined" rounded="lg" class="mb-4"> <v-card max-width="720" variant="outlined" rounded="lg" class="mb-4">
<v-card-title class="text-subtitle-1">全局</v-card-title> <v-card-title class="text-subtitle-1">全局</v-card-title>
@@ -33,17 +37,34 @@
> >
批量初始化未配置{{ unconfiguredIds.length }} 批量初始化未配置{{ unconfiguredIds.length }}
</v-btn> </v-btn>
<v-alert
v-if="globalLoaded && !serverList.length"
type="warning"
variant="tonal"
class="mt-4"
title="暂无服务器"
text="请先在「服务器」页添加子机,再回到此处配置宝塔连接。"
/>
</v-card-text> </v-card-text>
</v-card> </v-card>
<v-card max-width="720" variant="outlined" rounded="lg" :loading="loading"> <v-alert
v-if="!serverId"
type="info"
variant="tonal"
class="mb-4"
title="单机配置"
text="请在右上角选择服务器,或从「服务器列表」点击「连接配置」进入。"
/>
<v-card v-else max-width="720" variant="outlined" rounded="lg" :loading="loading">
<v-card-title class="text-subtitle-1">当前服务器</v-card-title>
<v-card-text> <v-card-text>
<div class="d-flex flex-wrap align-center ga-2 mb-3"> <div class="d-flex flex-wrap align-center ga-2 mb-3">
<v-chip :color="btInstalledChipColor" size="small" variant="tonal"> <v-chip :color="btInstalledChipColor" size="small" variant="tonal">
SSH 检测{{ btInstalledLabel }} SSH 检测{{ btInstalledLabel }}
</v-chip> </v-chip>
<v-btn <v-btn
v-if="serverId"
icon="mdi-refresh" icon="mdi-refresh"
size="x-small" size="x-small"
variant="text" variant="text"
@@ -66,11 +87,11 @@
variant="tonal" variant="tonal"
class="mb-4" class="mb-4"
:loading="bootstrapping" :loading="bootstrapping"
@click="runBootstrap(serverId!)" @click="runBootstrap(serverId)"
> >
立即 SSH 获取 立即 SSH 获取
</v-btn> </v-btn>
<v-form @submit.prevent="save(serverId!)"> <v-form @submit.prevent="save(serverId)">
<v-switch <v-switch
v-model="form.auto_bootstrap" v-model="form.auto_bootstrap"
label="自动获取(每 5 分钟重试,直至成功)" label="自动获取(每 5 分钟重试,直至成功)"
@@ -145,6 +166,7 @@ const globalSettings = ref<BtPanelGlobalSettings | null>(null)
const serverList = ref<Awaited<ReturnType<typeof listBtServers>>>([]) const serverList = ref<Awaited<ReturnType<typeof listBtServers>>>([])
const form = reactive({ base_url: '', api_key: '', verify_ssl: false, auto_bootstrap: true }) const form = reactive({ base_url: '', api_key: '', verify_ssl: false, auto_bootstrap: true })
const globalForm = reactive({ bt_panel_source_ip: '', bt_panel_auto_bootstrap_enabled: true }) const globalForm = reactive({ bt_panel_source_ip: '', bt_panel_auto_bootstrap_enabled: true })
const globalLoaded = ref(false)
const bootstrapStateLabels: Record<string, string> = { const bootstrapStateLabels: Record<string, string> = {
pending: '等待获取', pending: '等待获取',
@@ -201,7 +223,7 @@ async function loadConfig(id: number, refreshBtInstalled = false) {
} }
} }
const { serverId } = useBtPanelPageLoad(async (id) => { useBtPanelPageLoad(async (id) => {
await loadConfig(id) await loadConfig(id)
}) })
@@ -224,6 +246,8 @@ async function loadGlobal() {
serverList.value = await listBtServers() serverList.value = await listBtServers()
} catch (e: unknown) { } catch (e: unknown) {
window.$snackbar?.(e instanceof Error ? e.message : '加载全局设置失败', 'error') window.$snackbar?.(e instanceof Error ? e.message : '加载全局设置失败', 'error')
} finally {
globalLoaded.value = true
} }
} }