feat: 新增共享记事本页签(iframe 嵌入 + 存活校验)与集中配置
- 新增 whiteboard 模块:iframe 嵌入 f.zikai.wang/wb/shared,骨架屏+降级外链 - 存活校验:模块声明 requiresAlive,启动时 no-cors 探测 /health 一次, 服务挂掉则隐藏页签(刷新页面才重探) - 集中配置 src/config/app.config.js:白板 URL/探针/默认记事本 id 统一管理 - 容错强化:注册表/路由/useProjects 单模块异常不影响其他页签,删除一个页签 仅需删其模块文件夹并重新 build - i18n:tabs.whiteboard / common.serviceUnavailable / whiteboard.openExternal - README 精简重写:仅保留项目结构、基础设施、Ubuntu 从0安装、新增页签
This commit is contained in:
152
src/modules/whiteboard/Whiteboard.vue
Normal file
152
src/modules/whiteboard/Whiteboard.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { appConfig } from '../../config/app.config.js'
|
||||
import { useLocale } from '../../composables/useLocale.js'
|
||||
|
||||
const { t } = useLocale()
|
||||
|
||||
// 完整白板页 URL(公开,可被 iframe 嵌入)
|
||||
const wbConfig = appConfig.whiteboard
|
||||
const boardSrc = computed(
|
||||
() => `${wbConfig.baseUrl}${wbConfig.pagePath(wbConfig.defaultBoardId)}`
|
||||
)
|
||||
|
||||
const loaded = ref(false)
|
||||
const failed = ref(false)
|
||||
|
||||
function onLoad() {
|
||||
loaded.value = true
|
||||
}
|
||||
// iframe 加载失败(跨域无法读 error,靠超时兜底)
|
||||
function scheduleFailCheck() {
|
||||
setTimeout(() => {
|
||||
if (!loaded.value) failed.value = true
|
||||
}, 6000)
|
||||
}
|
||||
scheduleFailCheck()
|
||||
|
||||
// 外链打开(降级时提供)
|
||||
function openExternal() {
|
||||
window.open(boardSrc.value, '_blank', 'noopener')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="wb-page">
|
||||
<div class="wb-page__inner">
|
||||
<h1 class="wb-page__title">{{ t('tabs.whiteboard') }}</h1>
|
||||
|
||||
<!-- 降级:iframe 长时间未加载完成 -->
|
||||
<div v-if="failed" class="wb-page__fallback">
|
||||
<p class="wb-page__fallback-text">{{ t('common.serviceUnavailable') }}</p>
|
||||
<button type="button" class="wb-page__open" @click="openExternal">
|
||||
{{ t('whiteboard.openExternal') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-else class="wb-page__frame">
|
||||
<!-- 加载骨架(iframe 加载完成后淡出) -->
|
||||
<Transition name="fade">
|
||||
<div v-if="!loaded" class="wb-page__skeleton">
|
||||
<div class="wb-page__skeleton-text">{{ t('common.loading') }}</div>
|
||||
</div>
|
||||
</Transition>
|
||||
<iframe
|
||||
:src="boardSrc"
|
||||
class="wb-page__iframe"
|
||||
frameborder="0"
|
||||
:title="t('tabs.whiteboard')"
|
||||
@load="onLoad"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.wb-page {
|
||||
padding: var(--space-xl) 0;
|
||||
}
|
||||
/* 全宽容器(不受全局 .container 的 1080px 限制),仅留左右内边距 */
|
||||
.wb-page__inner {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--space-lg);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.wb-page__title {
|
||||
font-size: 1.6rem;
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
.wb-page__frame {
|
||||
position: relative;
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow);
|
||||
border: 1px solid var(--border);
|
||||
width: 100%;
|
||||
/* 记事本以编辑为主,给足高度 */
|
||||
height: 70vh;
|
||||
min-height: 480px;
|
||||
}
|
||||
.wb-page__iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
border: 0;
|
||||
}
|
||||
.wb-page__skeleton {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--surface);
|
||||
}
|
||||
.wb-page__skeleton-text {
|
||||
color: var(--text-tertiary);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.wb-page__fallback {
|
||||
text-align: center;
|
||||
padding: var(--space-2xl) var(--space-lg);
|
||||
border: 1px dashed var(--border-strong);
|
||||
border-radius: var(--radius);
|
||||
background: var(--surface);
|
||||
}
|
||||
.wb-page__fallback-text {
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
.wb-page__open {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 8px 18px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
background: var(--accent);
|
||||
border-radius: var(--radius-sm);
|
||||
transition: background var(--transition);
|
||||
}
|
||||
.wb-page__open:hover {
|
||||
background: var(--accent-hover);
|
||||
}
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.4s ease;
|
||||
}
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.wb-page__frame {
|
||||
height: 65vh;
|
||||
min-height: 360px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user