Files
DevFlow/src/components/ErrorBoundary.vue

76 lines
1.9 KiB
Vue

<template>
<!-- 全局错误兜底:子树渲染/生命周期抛错时显示降级 UI,防止白屏
用法: <ErrorBoundary> 包裹可能出错的子组件树(slot 透传) -->
<template v-if="error">
<div class="error-boundary">
<div class="error-boundary-icon"></div>
<div class="error-boundary-title">{{ $t('error.title') }}</div>
<div class="error-boundary-desc">{{ $t('error.desc') }}</div>
<button class="error-boundary-retry" @click="reset">{{ $t('error.retry') }}</button>
</div>
</template>
<slot v-else />
</template>
<script setup lang="ts">
import { ref, onErrorCaptured } from 'vue'
// errorCaptured 捕获子组件树抛出的错误(渲染/生命周期),返回 false 阻止继续向上冒泡
const error = ref<unknown>(null)
onErrorCaptured((err) => {
error.value = err
console.error('[ErrorBoundary] 子树渲染错误:', err)
return false
})
// 重试:重置 errorState,slot 重新挂载子树
function reset() {
error.value = null
}
</script>
<style scoped>
.error-boundary {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
padding: 32px 16px;
color: var(--df-text-secondary, #888);
text-align: center;
}
.error-boundary-icon {
font-size: 28px;
color: var(--df-danger, #e5484d);
line-height: 1;
}
.error-boundary-title {
font-size: 14px;
font-weight: 500;
color: var(--df-text, #333);
}
.error-boundary-desc {
font-size: 12px;
line-height: 1.5;
max-width: 360px;
}
.error-boundary-retry {
margin-top: 8px;
padding: 5px 16px;
font-size: 12px;
font-weight: 500;
font-family: var(--df-font-sans);
background: var(--df-accent, #7b6ff0);
color: #fff;
border: none;
border-radius: var(--df-radius-sm, 4px);
cursor: pointer;
transition: filter 0.15s;
}
.error-boundary-retry:hover {
filter: brightness(1.1);
}
</style>