修复: 设置页和 AI 独立窗口渲染崩溃(循环依赖 TDZ)
- ProviderPanel 改静态 import useAiStore 为动态 import,打破循环依赖链 - ErrorBoundary 增强显示具体错误信息和可折叠堆栈详情
This commit is contained in:
@@ -7,7 +7,11 @@
|
||||
<template v-if="error">
|
||||
<div class="error-boundary-icon">⚠</div>
|
||||
<div class="error-boundary-title">{{ $t('error.title') }}</div>
|
||||
<div class="error-boundary-desc">{{ $t('error.desc') }}</div>
|
||||
<div class="error-boundary-desc">{{ errorMessage }}</div>
|
||||
<details class="error-boundary-details">
|
||||
<summary>堆栈详情</summary>
|
||||
<pre class="error-boundary-stack">{{ errorStack }}</pre>
|
||||
</details>
|
||||
<button class="error-boundary-retry" @click="reset">{{ $t('error.retry') }}</button>
|
||||
</template>
|
||||
<slot v-else />
|
||||
@@ -15,11 +19,26 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onErrorCaptured } from 'vue'
|
||||
import { ref, computed, onErrorCaptured } from 'vue'
|
||||
|
||||
// errorCaptured 捕获子组件树抛出的错误(渲染/生命周期),返回 false 阻止继续向上冒泡
|
||||
const error = ref<unknown>(null)
|
||||
|
||||
const errorMessage = computed(() => {
|
||||
if (!error.value) return ''
|
||||
if (error.value instanceof Error) return error.value.message
|
||||
return String(error.value)
|
||||
})
|
||||
|
||||
const errorStack = computed(() => {
|
||||
if (!error.value) return ''
|
||||
if (error.value instanceof Error) return error.value.stack || error.value.message
|
||||
try {
|
||||
return JSON.stringify(error.value, null, 2)
|
||||
} catch {
|
||||
return String(error.value)
|
||||
}
|
||||
})
|
||||
|
||||
onErrorCaptured((err) => {
|
||||
error.value = err
|
||||
console.error('[ErrorBoundary] 子树渲染错误:', err)
|
||||
@@ -61,7 +80,32 @@ function reset() {
|
||||
.error-boundary-desc {
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
max-width: 360px;
|
||||
max-width: 480px;
|
||||
word-break: break-all;
|
||||
}
|
||||
.error-boundary-details {
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
font-size: 11px;
|
||||
color: var(--df-text-dim);
|
||||
}
|
||||
.error-boundary-details summary {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
.error-boundary-stack {
|
||||
margin-top: 6px;
|
||||
padding: 8px;
|
||||
background: rgba(0,0,0,0.3);
|
||||
border-radius: 4px;
|
||||
font-family: var(--df-font-mono, monospace);
|
||||
font-size: 10px;
|
||||
line-height: 1.4;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
max-height: 240px;
|
||||
overflow: auto;
|
||||
}
|
||||
.error-boundary-retry {
|
||||
margin-top: 8px;
|
||||
|
||||
@@ -130,14 +130,22 @@
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { aiApi } from '@/api'
|
||||
import { useAiStore } from '@/stores/ai'
|
||||
import type { AiProviderConfig, ModelConfig } from '@/api/types'
|
||||
|
||||
// ============================================================
|
||||
// AI 提供商域 — 真实后端 CRUD + F-04c 负载均衡池(enabled / weight)
|
||||
// ============================================================
|
||||
const { t } = useI18n()
|
||||
|
||||
// 懒加载 useAiStore 避免静态 import 形成循环依赖:
|
||||
// ProviderPanel → stores/ai → useAiEvents → aiShared → useAppSettingsStore
|
||||
// 在 Settings chunk 首次加载时,stores/ai 模块顶层代码(__bindMessages)可能触发 TDZ。
|
||||
// 改为运行时动态 import,模块加载顺序不再耦合。
|
||||
async function syncAiStoreProviders() {
|
||||
const { useAiStore } = await import('@/stores/ai')
|
||||
const aiStore = useAiStore()
|
||||
await aiStore.loadProviders()
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'toast', msg: string, type: 'error' | 'warning' | 'info'): void
|
||||
@@ -170,7 +178,8 @@ async function loadProviders() {
|
||||
try {
|
||||
aiProviders.value = await aiApi.listProviders()
|
||||
// 同步刷新全局 store(AiChat 等 AI 面板消费方共享同一 state,避免改完仍提示「未配置」)
|
||||
await aiStore.loadProviders()
|
||||
// 懒加载避免循环依赖(见文件顶部 syncAiStoreProviders 注释)
|
||||
await syncAiStoreProviders()
|
||||
emit('providers-changed')
|
||||
} catch (e) {
|
||||
console.error(t('settings.toastLoadProviderFail'), e)
|
||||
|
||||
Reference in New Issue
Block a user