mirror of
https://github.com/kuaifan/dootask.git
synced 2026-07-22 22:18:10 +00:00
feat(microapp): iframe 同源 HTTP 5xx 检测与内联错误层,错误态强制显示胶囊
- iframe.vue 读 responseStatus 检测同源 5xx(宁可漏判不误判),与原生 error 统一走 load-error - index.vue 新增可读的内联错误层(重试/关闭),覆盖深色下被反色成一条线的错误页 - 检测到错误时强制显示胶囊,保证关闭/更多入口
This commit is contained in:
parent
f8eaab5a50
commit
966c7b4ef0
@ -2531,3 +2531,4 @@ AI任务分析
|
||||
共享文件
|
||||
我共享的
|
||||
共享给我的
|
||||
服务器返回错误(HTTP (*))
|
||||
|
||||
@ -142,14 +142,36 @@ export default {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// HTTP 5xx 错误页浏览器视为加载成功、不触发原生 error,load 后主动检测
|
||||
this.detectHttpError()
|
||||
},
|
||||
|
||||
// 处理 iframe 加载错误
|
||||
handleError(e) {
|
||||
this.$emit('error', {
|
||||
// 读同源导航状态码判断加载失败。只认 5xx(服务端/网关故障,必是错误页,不误判);
|
||||
// 排除 4xx(soft-404、登录页等状态码错但页面可用);跨域读不到则放弃。
|
||||
detectHttpError() {
|
||||
let nav
|
||||
try {
|
||||
nav = this.$refs.iframe.contentWindow.performance.getEntriesByType('navigation')[0]
|
||||
} catch (e) {
|
||||
return // 跨域,无法检测
|
||||
}
|
||||
if (nav && nav.responseStatus >= 500) {
|
||||
this.$emit('load-error', {
|
||||
detail: {
|
||||
name: this.name,
|
||||
status: nav.responseStatus,
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 处理 iframe 原生加载错误(无 HTTP 状态码,status 记 0),与 5xx 统一走 load-error
|
||||
handleError() {
|
||||
this.$emit('load-error', {
|
||||
detail: {
|
||||
name: this.name,
|
||||
error: e,
|
||||
status: 0,
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
@ -8,18 +8,20 @@
|
||||
:size="1200"
|
||||
:options="app"
|
||||
:windowType="windowType"
|
||||
:forceCapsuleVisible="loadErrors[app.name] != null"
|
||||
:beforeClose="onBeforeClose"
|
||||
@on-capsule-more="onCapsuleMore"
|
||||
@on-popout-window="onPopoutWindow"
|
||||
@on-confirm-close="closeMicroApp">
|
||||
<MicroIFrame
|
||||
v-if="shouldRenderIFrame(app)"
|
||||
:key="microFrameKey(app.name)"
|
||||
:name="app.name"
|
||||
:url="app.url"
|
||||
:data="appData(app.name)"
|
||||
:immersive="app.immersive"
|
||||
@mounted="mounted"
|
||||
@error="error"/>
|
||||
@load-error="onLoadError"/>
|
||||
<micro-app
|
||||
v-else-if="shouldRenderMicro(app)"
|
||||
:name="app.name"
|
||||
@ -36,6 +38,20 @@
|
||||
<Loading/>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!--加载错误层(同源 5xx 或原生加载错误时展示)-->
|
||||
<transition name="fade">
|
||||
<div v-if="loadErrors[app.name] != null" class="micro-app-error">
|
||||
<div class="micro-app-error-box">
|
||||
<div class="micro-app-error-title">{{ $L('应用加载失败') }}</div>
|
||||
<div v-if="loadErrors[app.name] > 0" class="micro-app-error-desc">{{ $L('服务器返回错误(HTTP (*))', loadErrors[app.name]) }}</div>
|
||||
<div class="micro-app-error-actions">
|
||||
<Button type="primary" @click="retryMicroApp(app.name)">{{ $L('重试') }}</Button>
|
||||
<Button @click="closeMicroApp(app.name, true)">{{ $L('关闭') }}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</MicroModal>
|
||||
|
||||
<!--选择用户-->
|
||||
@ -80,6 +96,46 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 错误层:浅色卡片,深色模式由全局反色引擎自动转深
|
||||
.micro-app-error {
|
||||
position: absolute;
|
||||
// z-index 0:靠 DOM 顺序盖住 iframe,低层级让胶囊/cmask 自然在其之上
|
||||
z-index: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24px;
|
||||
background-color: #ffffff;
|
||||
|
||||
.micro-app-error-box {
|
||||
max-width: 360px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.micro-app-error-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #17233d;
|
||||
}
|
||||
|
||||
.micro-app-error-desc {
|
||||
margin-top: 8px;
|
||||
font-size: 13px;
|
||||
color: #808695;
|
||||
}
|
||||
|
||||
.micro-app-error-actions {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.micro-app-assist {
|
||||
width: 0;
|
||||
height: 0;
|
||||
@ -125,6 +181,8 @@ export default {
|
||||
backupConfigs: {},
|
||||
loadings: [],
|
||||
closings: [],
|
||||
loadErrors: {}, // name -> status(5xx 状态码,或原生加载错误记 0)
|
||||
reloadNonce: {}, // name -> 重试计数,用于强制重挂 iframe
|
||||
}
|
||||
},
|
||||
|
||||
@ -217,6 +275,21 @@ export default {
|
||||
this.loadings = this.loadings.filter(item => item !== name);
|
||||
},
|
||||
|
||||
onLoadError(e) {
|
||||
this.$set(this.loadErrors, e.detail.name, e.detail.status);
|
||||
},
|
||||
|
||||
// 重试:清错误态、重新加载(递增 nonce 触发 iframe 重挂)
|
||||
retryMicroApp(name) {
|
||||
this.$delete(this.loadErrors, name);
|
||||
this.loadings.push(name);
|
||||
this.$set(this.reloadNonce, name, (this.reloadNonce[name] || 0) + 1);
|
||||
},
|
||||
|
||||
microFrameKey(name) {
|
||||
return `iframe-${name}-${this.reloadNonce[name] || 0}`;
|
||||
},
|
||||
|
||||
/**
|
||||
* 应用数据
|
||||
* @param name
|
||||
@ -466,6 +539,7 @@ export default {
|
||||
// 更新微应用
|
||||
if (app.url != config.url || !app.keep_alive) {
|
||||
this.unmountMicroApp(app)
|
||||
this.$delete(this.loadErrors, app.name)
|
||||
this.loadings.push(app.name)
|
||||
}
|
||||
Object.assign(app, config)
|
||||
@ -480,6 +554,7 @@ export default {
|
||||
config.postMessage = () => {}
|
||||
config.onBeforeClose = () => true
|
||||
this.$store.commit('microApps/push', config)
|
||||
this.$delete(this.loadErrors, config.name)
|
||||
this.loadings.push(config.name)
|
||||
requestAnimationFrame(_ => {
|
||||
config.isOpen = true
|
||||
@ -604,6 +679,7 @@ export default {
|
||||
}
|
||||
|
||||
this.closeAppState(app)
|
||||
this.$delete(this.loadErrors, name)
|
||||
if (destroy === true) {
|
||||
this.unmountMicroApp(app)
|
||||
}
|
||||
|
||||
@ -76,6 +76,11 @@ export default {
|
||||
type: String,
|
||||
default: 'embed',
|
||||
},
|
||||
// 强制显示胶囊(错误态等),覆盖 capsule.visible 的隐藏配置
|
||||
forceCapsuleVisible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
beforeClose: Function
|
||||
},
|
||||
data() {
|
||||
@ -131,7 +136,7 @@ export default {
|
||||
}
|
||||
const {capsule} = this.options
|
||||
if ($A.isJson(capsule)) {
|
||||
if (!this.getCapsuleVisible(capsule.visible)) {
|
||||
if (!this.forceCapsuleVisible && !this.getCapsuleVisible(capsule.visible)) {
|
||||
styleObject.display = 'none';
|
||||
}
|
||||
if (typeof capsule.top === 'number') {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user