@@ -1,13 +1,22 @@
< script setup >
import { ref , computed , watch } from 'vue'
// 组件名与模块 id 一致,供 <KeepAlive :include> 按 id 精确匹配;
// 白板后端失活时把它从 include 移除即可卸载组件
// ★ whiteboard 页签的 mainPage 侧包装器。
// 子模块 zWhiteBoard 的 App.vue 作为便携式组件被 import, 本包装器负责:
// 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' } )
import { ref , computed , watch } from 'vue'
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 { getLiveness } from '../../composables/useLiveness .js'
import i18n from '../../i18n/index .js'
import PageShell from '../../components/ui/PageShell.vue'
import ZWbApp from '../../../third_party/zWhiteBoard/src/App.vue'
const { t } = useLocale ( )
const route = useRoute ( )
@@ -15,6 +24,9 @@ const router = useRouter()
const wbConfig = appConfig . whiteboard
// 透传父项目当前语言给子应用(响应式:切语言时子应用内容跟随)
const locale = computed ( ( ) => i18n . global . locale . value )
// 当前白板 id: 优先取路由参数 /whiteboard/:boardId, 否则用配置默认值
function resolveBoardId ( val ) {
const id = String ( val ? ? '' ) . trim ( )
@@ -46,39 +58,6 @@ function submitBoard() {
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 >
< template >
@@ -104,28 +83,8 @@ function openExternal() {
< / form >
< / template >
<!-- 服务不可达 / iframe 加载超时降级 -- >
< div v-if = "isDown || failed" class="wb-fallback" >
< 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 class = "whiteboard__frame" >
< ZWbApp :locale = "locale" :board-id = "currentBoardId" / >
< / div >
< / PageShell >
< / template >
@@ -167,78 +126,24 @@ function openExternal() {
. wb - switch _ _go : hover {
background : var ( -- accent - hover ) ;
}
. wb - frame {
/* 让子应用填满外壳容器,保留其内部布局自洽;记事本以编辑为主,给足高度 */
. whiteboard _ _frame {
position : relative ;
border - radius : var ( -- radius ) ;
overflow : hidden ;
box - shadow : var ( -- shadow ) ;
border : 1 px solid var ( -- border ) ;
width : 100 % ;
/* 记事本以编辑为主,给足高度 */
height : 70 vh ;
min - height : 480 px ;
background : var ( -- surface ) ;
height : 78 vh ;
min - height : 520 px ;
}
. 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.95 rem ;
}
. wb - fallback {
text - align : center ;
padding : var ( -- space - 2 xl ) var ( -- space - lg ) ;
border : 1 px 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 : 8 px 18 px ;
font - size : 0.9 rem ;
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.4 s ease ;
}
. fade - enter - from ,
. fade - leave - to {
opacity : 0 ;
}
@ media ( max - width : 768 px ) {
. wb - switch _ _input {
width : 120 px ;
}
. wb - frame {
height : 65 vh ;
min - height : 36 0px ;
. whiteboard _ _ frame {
height : 72 vh ;
min - height : 40 0px ;
}
}
< / style >