feat: 白板页签从 iframe 改为构建期组件挂载
新增 zWhiteBoard 子模块(textarea + WebSocket 实时同步、心跳、断线重连), Whiteboard.vue 改为组件包装器,经 :locale/:boardId props 透传,与 zPDF_package 范式一致。 复用 zTools2 的 /api/wb/* 与 /api/ws/wb/* 接口,后端无改动;旧版 /api/wb-page/* 保留不删。 清理 iframe 专用配置(pagePath)与降级文案(whiteboard.openExternal)。
This commit is contained in:
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -7,3 +7,6 @@
|
|||||||
[submodule "third_party/zPDF_package"]
|
[submodule "third_party/zPDF_package"]
|
||||||
path = third_party/zPDF_package
|
path = third_party/zPDF_package
|
||||||
url = https://git.zikai.wang/zikai/zPDF_package.git
|
url = https://git.zikai.wang/zikai/zPDF_package.git
|
||||||
|
[submodule "third_party/zWhiteBoard"]
|
||||||
|
path = third_party/zWhiteBoard
|
||||||
|
url = https://git.zikai.wang/zikai/zWhiteBoard.git
|
||||||
|
|||||||
@@ -19,9 +19,7 @@ export const appConfig = {
|
|||||||
baseUrl: BASE_URL,
|
baseUrl: BASE_URL,
|
||||||
// 存活探针路径;周期探测,挂掉则隐藏页签并卸载组件
|
// 存活探针路径;周期探测,挂掉则隐藏页签并卸载组件
|
||||||
healthPath: '/api/health',
|
healthPath: '/api/health',
|
||||||
// 记事本页面路径生成器
|
// 默认打开的记事本 id(Whiteboard.vue 的 resolveBoardId 回退用)
|
||||||
pagePath: (boardId) => `/api/wb-page/${encodeURIComponent(boardId)}`,
|
|
||||||
// 默认打开的记事本 id
|
|
||||||
defaultBoardId: 'share',
|
defaultBoardId: 'share',
|
||||||
// 探测超时(毫秒)
|
// 探测超时(毫秒)
|
||||||
healthTimeoutMs: 4000
|
healthTimeoutMs: 4000
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ export default {
|
|||||||
pdf: 'PDF Converter'
|
pdf: 'PDF Converter'
|
||||||
},
|
},
|
||||||
whiteboard: {
|
whiteboard: {
|
||||||
openExternal: 'Open in new tab',
|
|
||||||
boardId: 'Board ID',
|
boardId: 'Board ID',
|
||||||
switch: 'Switch'
|
switch: 'Switch'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ export default {
|
|||||||
pdf: 'PDF 转换'
|
pdf: 'PDF 转换'
|
||||||
},
|
},
|
||||||
whiteboard: {
|
whiteboard: {
|
||||||
openExternal: '在新标签页打开',
|
|
||||||
boardId: '白板 ID',
|
boardId: '白板 ID',
|
||||||
switch: '切换'
|
switch: '切换'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,22 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, watch } from 'vue'
|
// ★ whiteboard 页签的 mainPage 侧包装器。
|
||||||
// 组件名与模块 id 一致,供 <KeepAlive :include> 按 id 精确匹配;
|
// 子模块 zWhiteBoard 的 App.vue 作为便携式组件被 import,本包装器负责:
|
||||||
// 白板后端失活时把它从 include 移除即可卸载组件
|
// 1. 套 PageShell 统一容器宽度与标题(与其他页签对齐)
|
||||||
|
// 2. 持有 KeepAlive 匹配用的组件名(与模块 id 一致),子模块自身不持 name
|
||||||
|
// 3. 把 mainPage 当前语言经 :locale prop 透传给子模块的独立 i18n 实例
|
||||||
|
// 4. 解析路由参数 /whiteboard/:boardId 并经 :boardId prop 透传给子模块(直链预填 + 切换表单)
|
||||||
|
// 前后端分离:UI/交互在 zWhiteBoard,能力由 zTools2 的 /api/wb/* 与 /api/ws/wb/* 提供,
|
||||||
|
// 同源 fetch/WS 自动携带 httpOnly cookie,无需 CORS。与 zPDF_package 集成范式一致。
|
||||||
|
// 子模块独立运行时不经过本包装器,保持纯净。
|
||||||
defineOptions({ name: 'whiteboard' })
|
defineOptions({ name: 'whiteboard' })
|
||||||
|
|
||||||
|
import { ref, computed, watch } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { appConfig, healthUrl } from '../../config/app.config.js'
|
import { appConfig } from '../../config/app.config.js'
|
||||||
import { useLocale } from '../../composables/useLocale.js'
|
import { useLocale } from '../../composables/useLocale.js'
|
||||||
import { getLiveness } from '../../composables/useLiveness.js'
|
import i18n from '../../i18n/index.js'
|
||||||
import PageShell from '../../components/ui/PageShell.vue'
|
import PageShell from '../../components/ui/PageShell.vue'
|
||||||
|
import ZWbApp from '../../../third_party/zWhiteBoard/src/App.vue'
|
||||||
|
|
||||||
const { t } = useLocale()
|
const { t } = useLocale()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -15,6 +24,9 @@ const router = useRouter()
|
|||||||
|
|
||||||
const wbConfig = appConfig.whiteboard
|
const wbConfig = appConfig.whiteboard
|
||||||
|
|
||||||
|
// 透传父项目当前语言给子应用(响应式:切语言时子应用内容跟随)
|
||||||
|
const locale = computed(() => i18n.global.locale.value)
|
||||||
|
|
||||||
// 当前白板 id:优先取路由参数 /whiteboard/:boardId,否则用配置默认值
|
// 当前白板 id:优先取路由参数 /whiteboard/:boardId,否则用配置默认值
|
||||||
function resolveBoardId(val) {
|
function resolveBoardId(val) {
|
||||||
const id = String(val ?? '').trim()
|
const id = String(val ?? '').trim()
|
||||||
@@ -46,39 +58,6 @@ function submitBoard() {
|
|||||||
router.replace(`/whiteboard/${encodeURIComponent(id)}`)
|
router.replace(`/whiteboard/${encodeURIComponent(id)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 完整白板页 URL(公开,可被 iframe 嵌入);随 currentBoardId 响应式变化
|
|
||||||
const boardSrc = computed(
|
|
||||||
() => `${wbConfig.baseUrl}${wbConfig.pagePath(currentBoardId.value)}`
|
|
||||||
)
|
|
||||||
|
|
||||||
// 存活态:服务挂掉时显示降级提示(页签本身也已隐藏,但用户若直链进入仍可见)
|
|
||||||
const aliveState = getLiveness(healthUrl('whiteboard'))
|
|
||||||
const isDown = computed(() => aliveState.value === 'down')
|
|
||||||
|
|
||||||
const loaded = ref(false)
|
|
||||||
const failed = ref(false)
|
|
||||||
|
|
||||||
// 切换白板时重置加载态
|
|
||||||
watch(boardSrc, () => {
|
|
||||||
loaded.value = false
|
|
||||||
failed.value = false
|
|
||||||
scheduleFailCheck()
|
|
||||||
})
|
|
||||||
|
|
||||||
function onLoad() {
|
|
||||||
loaded.value = true
|
|
||||||
}
|
|
||||||
function scheduleFailCheck() {
|
|
||||||
setTimeout(() => {
|
|
||||||
if (!loaded.value) failed.value = true
|
|
||||||
}, 6000)
|
|
||||||
}
|
|
||||||
scheduleFailCheck()
|
|
||||||
|
|
||||||
function openExternal() {
|
|
||||||
window.open(boardSrc.value, '_blank', 'noopener')
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -104,28 +83,8 @@ function openExternal() {
|
|||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 服务不可达 / iframe 加载超时降级 -->
|
<div class="whiteboard__frame">
|
||||||
<div v-if="isDown || failed" class="wb-fallback">
|
<ZWbApp :locale="locale" :board-id="currentBoardId" />
|
||||||
<p class="wb-fallback__text">{{ t('common.serviceUnavailable') }}</p>
|
|
||||||
<button type="button" class="wb-fallback__open" @click="openExternal">
|
|
||||||
{{ t('whiteboard.openExternal') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else class="wb-frame">
|
|
||||||
<!-- 加载骨架(iframe 加载完成后淡出) -->
|
|
||||||
<Transition name="fade">
|
|
||||||
<div v-if="!loaded" class="wb-frame__skeleton">
|
|
||||||
<div class="wb-frame__skeleton-text">{{ t('common.loading') }}</div>
|
|
||||||
</div>
|
|
||||||
</Transition>
|
|
||||||
<iframe
|
|
||||||
:src="boardSrc"
|
|
||||||
class="wb-frame__iframe"
|
|
||||||
frameborder="0"
|
|
||||||
:title="t('tabs.whiteboard')"
|
|
||||||
@load="onLoad"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</PageShell>
|
</PageShell>
|
||||||
</template>
|
</template>
|
||||||
@@ -167,78 +126,24 @@ function openExternal() {
|
|||||||
.wb-switch__go:hover {
|
.wb-switch__go:hover {
|
||||||
background: var(--accent-hover);
|
background: var(--accent-hover);
|
||||||
}
|
}
|
||||||
.wb-frame {
|
/* 让子应用填满外壳容器,保留其内部布局自洽;记事本以编辑为主,给足高度 */
|
||||||
|
.whiteboard__frame {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: var(--shadow);
|
box-shadow: var(--shadow);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
width: 100%;
|
|
||||||
/* 记事本以编辑为主,给足高度 */
|
|
||||||
height: 70vh;
|
|
||||||
min-height: 480px;
|
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
|
height: 78vh;
|
||||||
|
min-height: 520px;
|
||||||
}
|
}
|
||||||
.wb-frame__iframe {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: block;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
.wb-frame__skeleton {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
z-index: 1;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: var(--surface);
|
|
||||||
}
|
|
||||||
.wb-frame__skeleton-text {
|
|
||||||
color: var(--text-tertiary);
|
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
|
||||||
.wb-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-fallback__text {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
margin-bottom: var(--space-md);
|
|
||||||
}
|
|
||||||
.wb-fallback__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-fallback__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) {
|
@media (max-width: 768px) {
|
||||||
.wb-switch__input {
|
.wb-switch__input {
|
||||||
width: 120px;
|
width: 120px;
|
||||||
}
|
}
|
||||||
.wb-frame {
|
.whiteboard__frame {
|
||||||
height: 65vh;
|
height: 72vh;
|
||||||
min-height: 360px;
|
min-height: 400px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
// 模块元数据 -- 共享记事本(白板)页签
|
// 模块元数据 -- 共享记事本(白板)页签
|
||||||
// requiresAlive 指向存活探针 URL:启动时探测,服务不可达则隐藏该页签。
|
// 构建期组件集成:mainPage 侧包装器 Whiteboard.vue import 子模块 zWhiteBoard 的 App.vue,
|
||||||
// 删除本目录即可移除此页签,其余模块不受影响。
|
// 套 PageShell 统一容器与标题。zWhiteBoard 作为 git submodule 独立维护,
|
||||||
|
// 其 UI/交互经同源 fetch/WS 调用 zTools2 的 /api/wb/* 与 /api/ws/wb/*(前后端分离)。
|
||||||
|
// requiresAlive 指向存活探针 URL:启动时探测,zTools2 不可达则隐藏该页签。
|
||||||
|
// 升级:cd third_party/zWhiteBoard && git pull,回 mainPage 根 git add 该子模块并重新 build。
|
||||||
import { healthUrl } from '../../config/app.config.js'
|
import { healthUrl } from '../../config/app.config.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
1
third_party/zWhiteBoard
vendored
Submodule
1
third_party/zWhiteBoard
vendored
Submodule
Submodule third_party/zWhiteBoard added at d7d7450296
Reference in New Issue
Block a user