refactor: 清理死代码/提前失败/高内聚低耦合,重写文档

死代码清理:
- 删除未使用的 UI 组件 Card.vue / Tag.vue
- 移除 app.config.js 死字段 healthTimeoutMs(×2)与 isDev()
- 移除 PageShell.vue 死 prop padded(恒为默认值)
- 移除 useLocale.js 死导出 isZh/pick/locale
- 移除 router/index.js 不可达 try/catch,简化为直接 .map()
- 移除 package.json 重复依赖 ical.js(子模块自带)
- 移除 i18n 死键 common.notAvailable / common.serviceUnavailable
- 移除 vite.config.js 陈旧的 /448 /449 dev proxy(z449 为构建期组件,不走该 URL)

提前失败/日志:
- useI18nLazy.js switchLocale 加载语言包失败时 console.error 记录(原静默吞错)
- modules/index.js 字段不全的模块跳过时 console.warn 告警(原与文档承诺不符、静默跳过)
- deploy.sh 补齐 zWhiteBoard / zPDF_package 的 npm install 与子模块初始化
  (均为构建期组件 import,全新检出时缺少依赖会构建失败)

文档对齐现状(无 iframe、无外部域名):
- README 改写为简洁版,修正 iframe/ical.js/子模块数量过时描述
- architecture.md 修正 requiresAlive 示例(同源 /api/health,非外部域名)
  与「仅白板使用」断言(PDF 也用);移除已删 padded prop
- add-module.md 同步移除 padded、修正 requiresAlive 示例
- submodule-timeTableFix.md 修正 ical.js 归属(子模块自带,非父项目共享)
- submodule-zPDF_package.md 子项目数量 3 -> 4
- 新增 submodule-zWhiteBoard.md
- readme-fixes.md 记录本次勘误
This commit is contained in:
2026-07-28 11:47:25 +08:00
parent 5b1acbdeb7
commit 25dbceee2d
22 changed files with 216 additions and 168 deletions

View File

@@ -1,28 +0,0 @@
<script setup>
defineProps({
hoverable: { type: Boolean, default: false }
})
</script>
<template>
<div class="card" :class="{ 'card--hover': hoverable }">
<slot />
</div>
</template>
<style scoped>
.card {
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow-sm);
padding: var(--space-lg);
}
.card--hover {
transition: transform var(--transition), box-shadow var(--transition);
}
.card--hover:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-hover);
}
</style>

View File

@@ -3,17 +3,15 @@ import { computed } from 'vue'
import { useLocale } from '../../composables/useLocale.js'
// ★ 可复用页面外壳 -- 统一页面的容器宽度、垂直节奏与标题,消除各模块重复包裹。
// 高内聚:所有页面布局令牌集中于此;低耦合:模块只声明 titleKey/fluid/padded
// 高内聚:所有页面布局令牌集中于此;低耦合:模块只声明 titleKey/fluid
// 不关心 .container 实现。新增页签照此复用,无需各写一套 wrapper。
//
// Props:
// titleKey: i18n 键(如 'tabs.whiteboard'),渲染为页面主标题;不传则无标题
// fluid: true 时突破 1080px 限制(全宽 + 左右 padding如日历页
// padded: false 时去掉默认上下间距(页面完全自管布局时用)
const props = defineProps({
titleKey: { type: String, default: '' },
fluid: { type: Boolean, default: false },
padded: { type: Boolean, default: true }
fluid: { type: Boolean, default: false }
})
const { t } = useLocale()
@@ -21,7 +19,7 @@ const title = computed(() => (props.titleKey ? t(props.titleKey) : ''))
</script>
<template>
<div class="page-shell" :class="{ 'page-shell--padded': padded }">
<div class="page-shell">
<div :class="fluid ? 'page-shell__fluid' : 'container page-shell__inner'">
<div v-if="title || $slots.actions" class="page-shell__head">
<h1 v-if="title" class="page-shell__title">{{ title }}</h1>
@@ -35,7 +33,7 @@ const title = computed(() => (props.titleKey ? t(props.titleKey) : ''))
</template>
<style scoped>
.page-shell--padded {
.page-shell {
padding: var(--space-xl) 0 var(--space-2xl);
}
/* 标准容器宽度复用全局 .containermax-width 1080px居中左右 padding */

View File

@@ -1,25 +0,0 @@
<script setup>
defineProps({
tag: { type: String, default: 'span' }
})
</script>
<template>
<component :is="tag" class="tag">
<slot />
</component>
</template>
<style scoped>
.tag {
display: inline-flex;
align-items: center;
padding: 2px 10px;
font-size: 0.78rem;
font-weight: 500;
color: var(--accent);
background: var(--accent-weak);
border-radius: 999px;
line-height: 1.6;
}
</style>

View File

@@ -14,6 +14,9 @@ export function useI18nLazy() {
switching.value = true
try {
await loadLocaleAsync(locale)
} catch (e) {
// 语言包加载失败不能静默:记录到控制台,便于排查(如 chunk 404
console.error('[mainPage] 加载语言包失败', locale, e)
} finally {
switching.value = false
}

View File

@@ -1,15 +1,11 @@
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
// 共享的语言辅助 composable消除各组件重复定义 L()
// 共享的语言辅助 composable消除各组件重复定义
// 用法:
// const { t, locale, isZh, pick } = useLocale()
// t('some.key') // 走 i18n推荐,文案在 locale 文件)
// isZh.value // 判断当前是否中文
// pick({ zh, en }) // 从 {zh,en} 对象取当前语言值(用于数据文件)
// const { t } = useLocale()
// t('some.key') // 走 i18n文案在 locale 文件)
// 页签包装器透传 locale 给子模块时直接读 i18n.global.locale见各 wrapper
export function useLocale() {
const { t, locale } = useI18n({ useScope: 'global' })
const isZh = computed(() => locale.value === 'zh-CN')
const pick = (obj) => (isZh.value ? obj.zh : obj.en)
return { t, locale, isZh, pick }
const { t } = useI18n({ useScope: 'global' })
return { t }
}

View File

@@ -20,18 +20,14 @@ export const appConfig = {
// 存活探针路径;周期探测,挂掉则隐藏页签并卸载组件
healthPath: '/api/health',
// 默认打开的记事本 idWhiteboard.vue 的 resolveBoardId 回退用)
defaultBoardId: 'share',
// 探测超时(毫秒)
healthTimeoutMs: 4000
defaultBoardId: 'share'
},
// PDF 转换服务zTools2 提供 /api/pdf/* REST APIzPDF_package 组件同源 fetch 调用)
pdf: {
// 服务根地址(含协议与域名);默认空串 = 同源相对路径
baseUrl: BASE_URL,
// 存活探针路径;周期探测,挂掉则隐藏页签并卸载组件
healthPath: '/api/health',
// 探测超时(毫秒)
healthTimeoutMs: 4000
healthPath: '/api/health'
}
}
@@ -45,11 +41,3 @@ export function healthUrl(service) {
if (!cfg) return ''
return `${cfg.baseUrl}${cfg.healthPath}`
}
/**
* 当前是否为开发/测试环境(便于组件按环境调整行为)。
* @returns {boolean}
*/
export function isDev() {
return !import.meta.env.PROD
}

View File

@@ -5,9 +5,7 @@ export default {
title: 'Zikai'
},
common: {
loading: 'Loading…',
notAvailable: 'Not available',
serviceUnavailable: 'Service is temporarily unavailable. Please try again later.'
loading: 'Loading…'
},
tabs: {
mobileGame: 'Mobile Game Project',

View File

@@ -4,9 +4,7 @@ export default {
title: 'Zikai'
},
common: {
loading: '加载中…',
notAvailable: '暂不可用',
serviceUnavailable: '服务暂不可用,请稍后再试。'
loading: '加载中…'
},
tabs: {
mobileGame: '移动游戏项目',

View File

@@ -19,11 +19,16 @@
const moduleFiles = import.meta.glob('./*/index.js', { eager: true })
const modules = Object.values(moduleFiles)
.map((mod) => mod?.default)
.filter(Boolean)
// 字段完整性兜底:缺 id/tabKey/component 的模块无法生成页签与路由,跳过
.filter((m) => m && m.id && m.tabKey && typeof m.component === 'function')
const modules = Object.entries(moduleFiles)
.map(([path, mod]) => ({ path, mod: mod?.default }))
.filter(({ mod }) => mod != null)
// 字段完整性兜底:缺 id/tabKey/component 的模块无法生成页签与路由,跳过并告警
.filter(({ path, mod }) => {
if (mod.id && mod.tabKey && typeof mod.component === 'function') return true
console.warn('[mainPage] 模块缺少必填字段,已跳过', path)
return false
})
.map(({ mod }) => mod)
// order 缺省 999排在所有显式 order 页签之后hidden 页常用 99 之类显式值)
.sort((a, b) => (a.order ?? 999) - (b.order ?? 999))

View File

@@ -2,24 +2,17 @@
import { createRouter, createWebHistory } from 'vue-router'
import modules from '../modules/index.js'
const moduleRoutes = modules
.map((m) => {
try {
// 模块可声明 param如 'boardId')以接受 /<id>/<param> 形式的子路径,
// 形如 /whiteboard/share。可选参数 (? 后缀) 使 /whiteboard 仍可访问。
const paramPart = m.param ? `/:${m.param}?` : ''
return {
path: `/${m.id}${paramPart}`,
name: m.id,
component: m.component,
meta: { moduleId: m.id }
}
} catch (e) {
console.warn(`[router] 模块 ${m?.id} 路由生成失败,已跳过:`, e)
return null
}
})
.filter(Boolean)
const moduleRoutes = modules.map((m) => {
// 模块可声明 param如 'boardId')以接受 /<id>/<param> 形式的子路径,
// 形如 /whiteboard/share。可选参数 (? 后缀) 使 /whiteboard 仍可访问。
const paramPart = m.param ? `/:${m.param}?` : ''
return {
path: `/${m.id}${paramPart}`,
name: m.id,
component: m.component,
meta: { moduleId: m.id }
}
})
const routes = [
{