fix(web): 设置页按菜单加载并优化错误提示

This commit is contained in:
mofeng-git
2026-05-19 21:38:56 +08:00
parent 265852b312
commit da05656a89
4 changed files with 140 additions and 108 deletions

View File

@@ -160,7 +160,11 @@ export interface UpdateStatusResponse {
export const updateApi = { export const updateApi = {
overview: (channel: UpdateChannel = 'stable') => overview: (channel: UpdateChannel = 'stable') =>
request<UpdateOverviewResponse>(`/update/overview?channel=${encodeURIComponent(channel)}`), request<UpdateOverviewResponse>(
`/update/overview?channel=${encodeURIComponent(channel)}`,
{},
{ toastOnError: false },
),
upgrade: (payload: { channel?: UpdateChannel; target_version?: string }) => upgrade: (payload: { channel?: UpdateChannel; target_version?: string }) =>
request<{ success: boolean; message?: string }>('/update/upgrade', { request<{ success: boolean; message?: string }>('/update/upgrade', {

View File

@@ -622,6 +622,7 @@ export default {
updateStatusIdle: 'Idle', updateStatusIdle: 'Idle',
releaseNotes: 'Release Notes', releaseNotes: 'Release Notes',
noUpdates: 'No new version available for current channel', noUpdates: 'No new version available for current channel',
updateOverviewLoadFailed: 'Failed to load update information',
startUpgrade: 'Start Upgrade', startUpgrade: 'Start Upgrade',
updatePhaseIdle: 'Idle', updatePhaseIdle: 'Idle',
updatePhaseChecking: 'Checking', updatePhaseChecking: 'Checking',

View File

@@ -621,6 +621,7 @@ export default {
updateStatusIdle: '空闲', updateStatusIdle: '空闲',
releaseNotes: '更新说明', releaseNotes: '更新说明',
noUpdates: '当前通道暂无可升级新版本', noUpdates: '当前通道暂无可升级新版本',
updateOverviewLoadFailed: '获取版本更新信息失败',
startUpgrade: '开始升级', startUpgrade: '开始升级',
updatePhaseIdle: '空闲', updatePhaseIdle: '空闲',
updatePhaseChecking: '检查中', updatePhaseChecking: '检查中',

View File

@@ -112,12 +112,13 @@ const configStore = useConfigStore()
const authStore = useAuthStore() const authStore = useAuthStore()
const isWindows = computed(() => systemStore.platform?.mode === 'windows') const isWindows = computed(() => systemStore.platform?.mode === 'windows')
const msdAvailable = computed(() => systemStore.platform?.msd.available ?? systemStore.capabilities?.msd.available ?? false)
const activeSection = ref('appearance') const activeSection = ref<SettingsSectionId>('appearance')
const mobileMenuOpen = ref(false) const mobileMenuOpen = ref(false)
const loading = ref(false) const loading = ref(false)
const saved = ref(false) const saved = ref(false)
const SETTINGS_SECTION_IDS = new Set([ const SETTINGS_SECTION_IDS = [
'appearance', 'appearance',
'account', 'account',
'network', 'network',
@@ -131,7 +132,9 @@ const SETTINGS_SECTION_IDS = new Set([
'ext-rtsp', 'ext-rtsp',
'ext-remote-access', 'ext-remote-access',
'about', 'about',
]) ] as const
type SettingsSectionId = typeof SETTINGS_SECTION_IDS[number]
const SETTINGS_SECTION_ID_SET = new Set<string>(SETTINGS_SECTION_IDS)
const navGroups = computed(() => [ const navGroups = computed(() => [
{ {
@@ -147,7 +150,7 @@ const navGroups = computed(() => [
items: [ items: [
{ id: 'video', label: t('settings.video'), icon: Monitor, status: config.value.video_device ? t('settings.configured') : null }, { id: 'video', label: t('settings.video'), icon: Monitor, status: config.value.video_device ? t('settings.configured') : null },
{ id: 'hid', label: t('settings.hid'), icon: Keyboard, status: config.value.hid_backend.toUpperCase() }, { id: 'hid', label: t('settings.hid'), icon: Keyboard, status: config.value.hid_backend.toUpperCase() },
...(config.value.msd_enabled ? [{ id: 'msd', label: t('settings.msd'), icon: HardDrive }] : []), ...(msdAvailable.value ? [{ id: 'msd', label: t('settings.msd'), icon: HardDrive }] : []),
{ id: 'atx', label: t('settings.atx'), icon: Power }, { id: 'atx', label: t('settings.atx'), icon: Power },
{ id: 'environment', label: t('settings.environment'), icon: Server }, { id: 'environment', label: t('settings.environment'), icon: Server },
] ]
@@ -196,23 +199,88 @@ function sectionSubtitleKey(id: string): string {
} }
} }
function selectSection(id: string) { function isSettingsSectionId(value: string): value is SettingsSectionId {
activeSection.value = id return SETTINGS_SECTION_ID_SET.has(value)
mobileMenuOpen.value = false
} }
function normalizeSettingsSection(value: unknown): string | null { function selectSection(id: string) {
if (!isSettingsSectionId(id)) return
activeSection.value = id
mobileMenuOpen.value = false
void loadSectionData(id)
}
function normalizeSettingsSection(value: unknown): SettingsSectionId | null {
if (typeof value !== 'string') return null if (typeof value !== 'string') return null
if (value === 'access-control') return 'account' if (value === 'access-control') return 'account'
return SETTINGS_SECTION_IDS.has(value) ? value : null return isSettingsSectionId(value) ? value : null
} }
function ensureVisibleSection() { function ensureVisibleSection() {
if (!SETTINGS_SECTION_IDS.has(activeSection.value)) { if (!SETTINGS_SECTION_ID_SET.has(activeSection.value)) {
activeSection.value = 'appearance' activeSection.value = 'appearance'
} }
} }
async function loadSectionData(section: SettingsSectionId) {
switch (section) {
case 'appearance':
return
case 'account':
await loadAuthConfig()
return
case 'network':
await Promise.all([
loadWebServerConfig(),
loadRedfishConfig(),
])
return
case 'video':
await Promise.all([
loadConfig(),
loadDevices(),
loadBackends(),
])
return
case 'hid':
case 'msd':
await Promise.all([
loadConfig(),
loadDevices(),
])
return
case 'atx':
await Promise.all([
loadConfig(),
loadAtxConfig(),
loadAtxDevices(),
])
return
case 'environment':
await fetchUsbDevices()
return
case 'ext-ttyd':
case 'ext-remote-access':
await loadExtensions()
return
case 'ext-rustdesk':
await Promise.all([
loadRustdeskConfig(),
loadRustdeskPassword(),
])
return
case 'ext-rtsp':
await loadRtspConfig()
return
case 'about':
await Promise.all([
loadUpdateOverview(),
refreshUpdateStatus(),
])
return
}
}
const theme = ref<'light' | 'dark' | 'system'>('system') const theme = ref<'light' | 'dark' | 'system'>('system')
const usernameInput = ref('') const usernameInput = ref('')
@@ -643,8 +711,7 @@ async function runOtgSelfCheck() {
otgSelfCheckError.value = '' otgSelfCheckError.value = ''
try { try {
otgSelfCheckResult.value = await hidApi.otgSelfCheck() otgSelfCheckResult.value = await hidApi.otgSelfCheck()
} catch (e) { } catch {
console.error('Failed to run OTG self-check:', e)
otgSelfCheckError.value = t('settings.otgSelfCheck.failed') otgSelfCheckError.value = t('settings.otgSelfCheck.failed')
} finally { } finally {
otgSelfCheckLoading.value = false otgSelfCheckLoading.value = false
@@ -701,8 +768,7 @@ async function runVideoEncoderSelfCheck() {
videoEncoderSelfCheckError.value = '' videoEncoderSelfCheckError.value = ''
try { try {
videoEncoderSelfCheckResult.value = await streamApi.encoderSelfCheck() videoEncoderSelfCheckResult.value = await streamApi.encoderSelfCheck()
} catch (e) { } catch {
console.error('Failed to run encoder self-check:', e)
videoEncoderSelfCheckError.value = t('settings.encoderSelfCheck.failed') videoEncoderSelfCheckError.value = t('settings.encoderSelfCheck.failed')
} finally { } finally {
videoEncoderSelfCheckLoading.value = false videoEncoderSelfCheckLoading.value = false
@@ -1194,11 +1260,10 @@ async function saveConfig() {
}) })
} }
await loadConfig() await loadSectionData(activeSection.value)
saved.value = true saved.value = true
setTimeout(() => (saved.value = false), 2000) setTimeout(() => (saved.value = false), 2000)
} catch (e) { } catch {
console.error('Failed to save config:', e)
} finally { } finally {
loading.value = false loading.value = false
} }
@@ -1251,16 +1316,14 @@ async function loadConfig() {
otgSerialNumber.value = hid.otg_descriptor.serial_number || '' otgSerialNumber.value = hid.otg_descriptor.serial_number || ''
} }
} catch (e) { } catch {
console.error('Failed to load config:', e)
} }
} }
async function loadDevices() { async function loadDevices() {
try { try {
devices.value = await configApi.listDevices() devices.value = await configApi.listDevices()
} catch (e) { } catch {
console.error('Failed to load devices:', e)
} }
} }
@@ -1268,8 +1331,7 @@ async function loadBackends() {
try { try {
const result = await streamApi.getCodecs() const result = await streamApi.getCodecs()
availableBackends.value = result.backends || [] availableBackends.value = result.backends || []
} catch (e) { } catch {
console.error('Failed to load encoder backends:', e)
} }
} }
@@ -1277,8 +1339,7 @@ async function loadAuthConfig() {
authConfigLoading.value = true authConfigLoading.value = true
try { try {
authConfig.value = await configStore.refreshAuth() authConfig.value = await configStore.refreshAuth()
} catch (e) { } catch {
console.error('Failed to load auth config:', e)
} finally { } finally {
authConfigLoading.value = false authConfigLoading.value = false
} }
@@ -1290,8 +1351,7 @@ async function saveAuthConfig() {
authConfig.value = await configStore.updateAuth({ authConfig.value = await configStore.updateAuth({
single_user_allow_multiple_sessions: authConfig.value.single_user_allow_multiple_sessions, single_user_allow_multiple_sessions: authConfig.value.single_user_allow_multiple_sessions,
}) })
} catch (e) { } catch {
console.error('Failed to save auth config:', e)
} finally { } finally {
authConfigLoading.value = false authConfigLoading.value = false
} }
@@ -1317,8 +1377,7 @@ async function loadExtensions() {
virtual_ip: easytier.virtual_ip || '', virtual_ip: easytier.virtual_ip || '',
} }
} }
} catch (e) { } catch {
console.error('Failed to load extensions:', e)
} finally { } finally {
extensionsLoading.value = false extensionsLoading.value = false
} }
@@ -1330,8 +1389,7 @@ async function startExtension(id: 'ttyd' | 'gostc' | 'easytier') {
try { try {
await extensionsApi.start(id) await extensionsApi.start(id)
await loadExtensions() await loadExtensions()
} catch (e) { } catch {
console.error(`Failed to start ${id}:`, e)
} }
} }
@@ -1339,8 +1397,7 @@ async function stopExtension(id: 'ttyd' | 'gostc' | 'easytier') {
try { try {
await extensionsApi.stop(id) await extensionsApi.stop(id)
await loadExtensions() await loadExtensions()
} catch (e) { } catch {
console.error(`Failed to stop ${id}:`, e)
} }
} }
@@ -1348,8 +1405,7 @@ async function refreshExtensionLogs(id: 'ttyd' | 'gostc' | 'easytier') {
try { try {
const result = await extensionsApi.logs(id, 100) const result = await extensionsApi.logs(id, 100)
extensionLogs.value[id] = result.logs extensionLogs.value[id] = result.logs
} catch (e) { } catch {
console.error(`Failed to load ${id} logs:`, e)
} }
} }
@@ -1368,8 +1424,7 @@ async function saveExtensionConfig(id: 'ttyd' | 'gostc' | 'easytier') {
await loadExtensions() await loadExtensions()
saved.value = true saved.value = true
setTimeout(() => (saved.value = false), 2000) setTimeout(() => (saved.value = false), 2000)
} catch (e) { } catch {
console.error(`Failed to save ${id} config:`, e)
} finally { } finally {
loading.value = false loading.value = false
} }
@@ -1435,16 +1490,14 @@ async function loadAtxConfig() {
clearAtxSerialDeviceConflicts() clearAtxSerialDeviceConflicts()
normalizeAtxRelayChannels() normalizeAtxRelayChannels()
syncSharedAtxSerialBaudRate() syncSharedAtxSerialBaudRate()
} catch (e) { } catch {
console.error('Failed to load ATX config:', e)
} }
} }
async function loadAtxDevices() { async function loadAtxDevices() {
try { try {
atxDevices.value = await atxConfigApi.listDevices() atxDevices.value = await atxConfigApi.listDevices()
} catch (e) { } catch {
console.error('Failed to load ATX devices:', e)
} }
} }
@@ -1482,8 +1535,7 @@ async function saveAtxConfig() {
}) })
saved.value = true saved.value = true
setTimeout(() => (saved.value = false), 2000) setTimeout(() => (saved.value = false), 2000)
} catch (e) { } catch {
console.error('Failed to save ATX config:', e)
} finally { } finally {
loading.value = false loading.value = false
} }
@@ -1577,8 +1629,7 @@ async function loadRustdeskConfig() {
relay_server: config.relay_server || '', relay_server: config.relay_server || '',
relay_key: '', relay_key: '',
} }
} catch (e) { } catch {
console.error('Failed to load RustDesk config:', e)
} finally { } finally {
rustdeskLoading.value = false rustdeskLoading.value = false
} }
@@ -1587,8 +1638,7 @@ async function loadRustdeskConfig() {
async function loadRustdeskPassword() { async function loadRustdeskPassword() {
try { try {
rustdeskPassword.value = await configStore.refreshRustdeskPassword() rustdeskPassword.value = await configStore.refreshRustdeskPassword()
} catch (e) { } catch {
console.error('Failed to load RustDesk password:', e)
} }
} }
@@ -1676,8 +1726,7 @@ async function loadWebServerConfig() {
const config = await configStore.refreshWeb() const config = await configStore.refreshWeb()
webServerConfig.value = config webServerConfig.value = config
applyBindStateFromConfig(config) applyBindStateFromConfig(config)
} catch (e) { } catch {
console.error('Failed to load web server config:', e)
} }
} }
@@ -1685,8 +1734,7 @@ async function loadRedfishConfig() {
try { try {
const data = await redfishConfigApi.get() const data = await redfishConfigApi.get()
redfishEnabled.value = data.enabled redfishEnabled.value = data.enabled
} catch (e) { } catch {
console.error('Failed to load redfish config:', e)
} }
} }
@@ -1698,8 +1746,7 @@ async function saveRedfishConfig() {
}) })
redfishEnabled.value = data.enabled redfishEnabled.value = data.enabled
await triggerAutoRestart() await triggerAutoRestart()
} catch (e) { } catch {
console.error('Failed to save redfish config:', e)
} finally { } finally {
redfishSaving.value = false redfishSaving.value = false
} }
@@ -1718,8 +1765,7 @@ async function saveWebServerConfig() {
webServerConfig.value = updated webServerConfig.value = updated
applyBindStateFromConfig(updated) applyBindStateFromConfig(updated)
await triggerAutoRestart() await triggerAutoRestart()
} catch (e) { } catch {
console.error('Failed to save web server config:', e)
} finally { } finally {
webServerLoading.value = false webServerLoading.value = false
} }
@@ -1737,8 +1783,7 @@ async function saveCertificate() {
sslCertPem.value = '' sslCertPem.value = ''
sslKeyPem.value = '' sslKeyPem.value = ''
await triggerAutoRestart() await triggerAutoRestart()
} catch (e) { } catch {
console.error('Failed to save certificate:', e)
} finally { } finally {
certSaving.value = false certSaving.value = false
} }
@@ -1750,8 +1795,7 @@ async function clearCertificate() {
const updated = await configStore.updateWeb({ clear_custom_cert: true }) const updated = await configStore.updateWeb({ clear_custom_cert: true })
webServerConfig.value = updated webServerConfig.value = updated
await triggerAutoRestart() await triggerAutoRestart()
} catch (e) { } catch {
console.error('Failed to clear certificate:', e)
} finally { } finally {
certClearing.value = false certClearing.value = false
} }
@@ -1770,8 +1814,7 @@ async function restartServer() {
const host = formatHostForUrl(window.location.hostname || '127.0.0.1') const host = formatHostForUrl(window.location.hostname || '127.0.0.1')
window.location.href = `${protocol}://${host}:${port}` window.location.href = `${protocol}://${host}:${port}`
}, 3000) }, 3000)
} catch (e) { } catch {
console.error('Failed to restart server:', e)
restarting.value = false restarting.value = false
} }
} }
@@ -1837,8 +1880,7 @@ async function triggerAutoRestart() {
autoRestarting.value = false autoRestarting.value = false
} }
} }
} catch (e) { } catch {
console.error('Auto restart failed:', e)
autoRestartFailed.value = true autoRestartFailed.value = true
autoRestarting.value = false autoRestarting.value = false
} }
@@ -1849,7 +1891,11 @@ async function loadUpdateOverview() {
try { try {
updateOverview.value = await updateApi.overview(updateChannel.value) updateOverview.value = await updateApi.overview(updateChannel.value)
} catch (e) { } catch (e) {
console.error('Failed to load update overview:', e) const message = e instanceof Error ? e.message : t('settings.updateOverviewLoadFailed')
toast.error(t('settings.updateOverviewLoadFailed'), {
description: message,
duration: 4000,
})
} finally { } finally {
updateLoading.value = false updateLoading.value = false
} }
@@ -1865,8 +1911,7 @@ async function refreshUpdateStatus() {
window.location.reload() window.location.reload()
} }
} }
} catch (e) { } catch {
console.error('Failed to refresh update status:', e)
if (updateSawRestarting.value) { if (updateSawRestarting.value) {
updateSawRequestFailure.value = true updateSawRequestFailure.value = true
} }
@@ -1902,8 +1947,7 @@ async function startOnlineUpgrade() {
await updateApi.upgrade({ channel: updateChannel.value }) await updateApi.upgrade({ channel: updateChannel.value })
await refreshUpdateStatus() await refreshUpdateStatus()
startUpdatePolling() startUpdatePolling()
} catch (e) { } catch {
console.error('Failed to start upgrade:', e)
} }
} }
@@ -1961,8 +2005,7 @@ async function saveRustdeskConfig() {
rustdeskLocalConfig.value.relay_key = '' rustdeskLocalConfig.value.relay_key = ''
saved.value = true saved.value = true
setTimeout(() => (saved.value = false), 2000) setTimeout(() => (saved.value = false), 2000)
} catch (e) { } catch {
console.error('Failed to save RustDesk config:', e)
} finally { } finally {
loading.value = false loading.value = false
} }
@@ -1975,8 +2018,7 @@ async function regenerateRustdeskId() {
await configStore.regenerateRustdeskId() await configStore.regenerateRustdeskId()
await loadRustdeskConfig() await loadRustdeskConfig()
await loadRustdeskPassword() await loadRustdeskPassword()
} catch (e) { } catch {
console.error('Failed to regenerate RustDesk ID:', e)
} finally { } finally {
rustdeskLoading.value = false rustdeskLoading.value = false
} }
@@ -1989,8 +2031,7 @@ async function regenerateRustdeskPassword() {
await configStore.regenerateRustdeskPassword() await configStore.regenerateRustdeskPassword()
await loadRustdeskConfig() await loadRustdeskConfig()
await loadRustdeskPassword() await loadRustdeskPassword()
} catch (e) { } catch {
console.error('Failed to regenerate RustDesk password:', e)
} finally { } finally {
rustdeskLoading.value = false rustdeskLoading.value = false
} }
@@ -2004,8 +2045,7 @@ async function startRustdesk() {
await configStore.updateRustdesk({ enabled: true }) await configStore.updateRustdesk({ enabled: true })
rustdeskLocalConfig.value.enabled = true rustdeskLocalConfig.value.enabled = true
await loadRustdeskConfig() await loadRustdeskConfig()
} catch (e) { } catch {
console.error('Failed to start RustDesk:', e)
} finally { } finally {
rustdeskLoading.value = false rustdeskLoading.value = false
} }
@@ -2017,8 +2057,7 @@ async function stopRustdesk() {
await configStore.updateRustdesk({ enabled: false }) await configStore.updateRustdesk({ enabled: false })
rustdeskLocalConfig.value.enabled = false rustdeskLocalConfig.value.enabled = false
await loadRustdeskConfig() await loadRustdeskConfig()
} catch (e) { } catch {
console.error('Failed to stop RustDesk:', e)
} finally { } finally {
rustdeskLoading.value = false rustdeskLoading.value = false
} }
@@ -2089,8 +2128,7 @@ async function loadRtspConfig() {
username: status.config.username || '', username: status.config.username || '',
password: '', password: '',
} }
} catch (e) { } catch {
console.error('Failed to load RTSP config:', e)
} finally { } finally {
rtspLoading.value = false rtspLoading.value = false
} }
@@ -2120,8 +2158,7 @@ async function saveRtspConfig() {
rtspLocalConfig.value.password = '' rtspLocalConfig.value.password = ''
saved.value = true saved.value = true
setTimeout(() => (saved.value = false), 2000) setTimeout(() => (saved.value = false), 2000)
} catch (e) { } catch {
console.error('Failed to save RTSP config:', e)
} finally { } finally {
loading.value = false loading.value = false
} }
@@ -2133,8 +2170,7 @@ async function startRtsp() {
await configStore.updateRtsp({ enabled: true }) await configStore.updateRtsp({ enabled: true })
rtspLocalConfig.value.enabled = true rtspLocalConfig.value.enabled = true
await loadRtspConfig() await loadRtspConfig()
} catch (e) { } catch {
console.error('Failed to start RTSP:', e)
} finally { } finally {
rtspLoading.value = false rtspLoading.value = false
} }
@@ -2146,8 +2182,7 @@ async function stopRtsp() {
await configStore.updateRtsp({ enabled: false }) await configStore.updateRtsp({ enabled: false })
rtspLocalConfig.value.enabled = false rtspLocalConfig.value.enabled = false
await loadRtspConfig() await loadRtspConfig()
} catch (e) { } catch {
console.error('Failed to stop RTSP:', e)
} finally { } finally {
rtspLoading.value = false rtspLoading.value = false
} }
@@ -2182,26 +2217,15 @@ onMounted(async () => {
theme.value = storedTheme theme.value = storedTheme
} }
await Promise.all([ const initialSection = normalizeSettingsSection(route.query.tab)
systemStore.fetchSystemInfo(), if (initialSection) {
loadConfig(), activeSection.value = initialSection
loadDevices(), }
loadBackends(),
loadAuthConfig(), await systemStore.fetchSystemInfo()
loadExtensions(),
loadAtxConfig(),
loadAtxDevices(),
loadRustdeskConfig(),
loadRustdeskPassword(),
loadRtspConfig(),
loadWebServerConfig(),
loadRedfishConfig(),
loadUpdateOverview(),
refreshUpdateStatus(),
fetchUsbDevices(),
])
ensureVisibleSection() ensureVisibleSection()
usernameInput.value = authStore.user || '' usernameInput.value = authStore.user || ''
await loadSectionData(activeSection.value)
if (updateRunning.value) { if (updateRunning.value) {
startUpdatePolling() startUpdatePolling()
@@ -2209,7 +2233,9 @@ onMounted(async () => {
}) })
watch(updateChannel, async () => { watch(updateChannel, async () => {
if (activeSection.value === 'about') {
await loadUpdateOverview() await loadUpdateOverview()
}
}) })
watch(() => config.value.hid_backend, () => { watch(() => config.value.hid_backend, () => {
@@ -2222,7 +2248,7 @@ watch(() => route.query.tab, (tab) => {
if (section && activeSection.value !== section) { if (section && activeSection.value !== section) {
selectSection(section) selectSection(section)
} }
}, { immediate: true }) })
watch(isWindows, () => { watch(isWindows, () => {
ensureVisibleSection() ensureVisibleSection()
@@ -2288,7 +2314,7 @@ watch(isWindows, () => {
type="button" type="button"
v-for="item in group.items" v-for="item in group.items"
:key="item.id" :key="item.id"
@click="activeSection = item.id" @click="selectSection(item.id)"
:class="[ :class="[
'w-full flex items-center gap-3 px-3 py-2 text-sm rounded-md transition-colors', 'w-full flex items-center gap-3 px-3 py-2 text-sm rounded-md transition-colors',
activeSection === item.id activeSection === item.id