refactor: 引入 PageShell 统一页面外壳,高内聚低耦合
- 新增可复用 PageShell 组件(titleKey/fluid/padded + actions 插槽), 统一容器宽度(1080px)、垂直节奏与标题,消除各模块重复包裹 - whiteboard/calendar/mobile-game 全部改用 PageShell,去掉各自手写的 .container/.__inner/.__title 重复样式;calendar 用 fluid 全宽变体 - timetable 改用 mainPage 侧包装器 Timetable.vue:import 子模块 App.vue 套 PageShell(对齐其他页宽度),并持有 KeepAlive 匹配名 name:'timetable' - 子模块 timeTableFix 的 App.vue 去掉 defineOptions(保持纯净),name 约定 由 mainPage 包装器持有,消除跨仓库脆弱约定;submodule 升级到 17aeea8 - 注册表清理:去掉无效 try/catch,order 缺省改 999(避免与 hidden 页 99 冲突), 注释文档化全部元数据字段与 KeepAlive 约定 - 新增页签照 timetable 模式复用 PageShell,注册方式一致
This commit is contained in:
@@ -7,6 +7,7 @@ import { useRoute, useRouter } from 'vue-router'
|
||||
import { appConfig, healthUrl } from '../../config/app.config.js'
|
||||
import { useLocale } from '../../composables/useLocale.js'
|
||||
import { getLiveness } from '../../composables/useLiveness.js'
|
||||
import PageShell from '../../components/ui/PageShell.vue'
|
||||
|
||||
const { t } = useLocale()
|
||||
const route = useRoute()
|
||||
@@ -81,96 +82,65 @@ function openExternal() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="wb-page">
|
||||
<div class="container wb-page__inner">
|
||||
<div class="wb-page__head">
|
||||
<h1 class="wb-page__title">{{ t('tabs.whiteboard') }}</h1>
|
||||
<form class="wb-page__switch" @submit.prevent="submitBoard">
|
||||
<label class="wb-page__switch-label" for="boardInput">
|
||||
{{ t('whiteboard.boardId') }}
|
||||
</label>
|
||||
<input
|
||||
id="boardInput"
|
||||
v-model="inputId"
|
||||
class="wb-page__input"
|
||||
type="text"
|
||||
:placeholder="wbConfig.defaultBoardId"
|
||||
spellcheck="false"
|
||||
autocomplete="off"
|
||||
autocapitalize="off"
|
||||
/>
|
||||
<button type="submit" class="wb-page__go">
|
||||
{{ t('whiteboard.switch') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 服务不可达降级 -->
|
||||
<div v-if="isDown" 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>
|
||||
|
||||
<!-- iframe 加载超时降级 -->
|
||||
<div v-else-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"
|
||||
<PageShell title-key="tabs.whiteboard">
|
||||
<template #actions>
|
||||
<form class="wb-switch" @submit.prevent="submitBoard">
|
||||
<label class="wb-switch__label" for="boardInput">
|
||||
{{ t('whiteboard.boardId') }}
|
||||
</label>
|
||||
<input
|
||||
id="boardInput"
|
||||
v-model="inputId"
|
||||
class="wb-switch__input"
|
||||
type="text"
|
||||
:placeholder="wbConfig.defaultBoardId"
|
||||
spellcheck="false"
|
||||
autocomplete="off"
|
||||
autocapitalize="off"
|
||||
/>
|
||||
</div>
|
||||
<button type="submit" class="wb-switch__go">
|
||||
{{ t('whiteboard.switch') }}
|
||||
</button>
|
||||
</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>
|
||||
|
||||
<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>
|
||||
</PageShell>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.wb-page {
|
||||
padding: var(--space-xl) 0 var(--space-2xl);
|
||||
}
|
||||
/* 内容区用全局 .container(max-width 1080px,左右 padding),
|
||||
与移动游戏项目页保持一致的左右留白。 */
|
||||
.wb-page__inner {
|
||||
/* 仅补充容器内部纵向间距,宽度由 .container 控制 */
|
||||
}
|
||||
.wb-page__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-md);
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
.wb-page__title {
|
||||
font-size: 1.6rem;
|
||||
margin: 0;
|
||||
}
|
||||
.wb-page__switch {
|
||||
.wb-switch {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-xs);
|
||||
}
|
||||
.wb-page__switch-label {
|
||||
.wb-switch__label {
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.wb-page__input {
|
||||
.wb-switch__input {
|
||||
width: 160px;
|
||||
padding: 6px 10px;
|
||||
font-size: 0.9rem;
|
||||
@@ -181,11 +151,11 @@ function openExternal() {
|
||||
border-radius: var(--radius-sm);
|
||||
transition: border-color var(--transition);
|
||||
}
|
||||
.wb-page__input:focus {
|
||||
.wb-switch__input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.wb-page__go {
|
||||
.wb-switch__go {
|
||||
padding: 7px 14px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
@@ -194,10 +164,10 @@ function openExternal() {
|
||||
border-radius: var(--radius-sm);
|
||||
transition: background var(--transition);
|
||||
}
|
||||
.wb-page__go:hover {
|
||||
.wb-switch__go:hover {
|
||||
background: var(--accent-hover);
|
||||
}
|
||||
.wb-page__frame {
|
||||
.wb-frame {
|
||||
position: relative;
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
@@ -209,13 +179,13 @@ function openExternal() {
|
||||
min-height: 480px;
|
||||
background: var(--surface);
|
||||
}
|
||||
.wb-page__iframe {
|
||||
.wb-frame__iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
border: 0;
|
||||
}
|
||||
.wb-page__skeleton {
|
||||
.wb-frame__skeleton {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
@@ -224,22 +194,22 @@ function openExternal() {
|
||||
justify-content: center;
|
||||
background: var(--surface);
|
||||
}
|
||||
.wb-page__skeleton-text {
|
||||
.wb-frame__skeleton-text {
|
||||
color: var(--text-tertiary);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.wb-page__fallback {
|
||||
.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-page__fallback-text {
|
||||
.wb-fallback__text {
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
.wb-page__open {
|
||||
.wb-fallback__open {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 8px 18px;
|
||||
@@ -250,7 +220,7 @@ function openExternal() {
|
||||
border-radius: var(--radius-sm);
|
||||
transition: background var(--transition);
|
||||
}
|
||||
.wb-page__open:hover {
|
||||
.wb-fallback__open:hover {
|
||||
background: var(--accent-hover);
|
||||
}
|
||||
.fade-enter-active,
|
||||
@@ -263,13 +233,10 @@ function openExternal() {
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.wb-page__head {
|
||||
align-items: flex-start;
|
||||
}
|
||||
.wb-page__input {
|
||||
.wb-switch__input {
|
||||
width: 120px;
|
||||
}
|
||||
.wb-page__frame {
|
||||
.wb-frame {
|
||||
height: 65vh;
|
||||
min-height: 360px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user