diff --git a/README.md b/README.md index 995dd00..dfe75ca 100644 --- a/README.md +++ b/README.md @@ -9,28 +9,30 @@ Zikai 的作品集主页 -- Vue 3 模块化多项目展示站。浅色极简、 ``` mainPage/ ├── index.html -├── package.json # 含 ical.js / vue-i18n(子模块嵌入时共享) -├── vite.config.js # 构建输出到 dist/,再由 npm run deploy rsync 到 /root/html -├── .gitmodules # git submodule:timeTableFix / z449 +├── package.json # vue / vue-i18n / vue-router +├── vite.config.js # 构建输出到 dist/,再由 npm run deploy rsync 到 /var/www/html +├── deploy.sh # 整体部署:构建 + rsync + Apache 反代 + 重启 zTools2 +├── .gitmodules # git submodule:timeTableFix / z449 / zPDF_package / zWhiteBoard ├── README.md -├── docs/ # 架构 / 新增页签 / 子模块管理文档 +├── docs/ # 架构 / 新增页签 / 子模块管理 / 部署文档 ├── public/favicon.svg -├── third_party/ -│ ├── timeTableFix/ # ★ git submodule:ICS 日程整理器 -│ ├── z449/ # ★ git submodule:移动游戏项目展示页 -│ └── zPDF_package/ # ★ git submodule:PDF 转换前端(iframe 嵌入 zTools2) +├── third_party/ # ★ git submodule 子项目 +│ ├── timeTableFix/ # ICS 日程整理器(纯前端) +│ ├── z449/ # 移动游戏项目展示页(纯前端) +│ ├── zPDF_package/ # PDF 转换前端(同源调用 zTools2 /api/pdf/*) +│ └── zWhiteBoard/ # 共享记事本前端(同源调用 zTools2 /api/wb/* + /api/ws/wb/*) └── src/ ├── main.js # 应用入口(挂载 i18n / router / 全局样式) ├── App.vue # 根布局:顶栏 + 标签导航 + + 页脚 - ├── config/app.config.js # 集中配置:外部服务 URL(白板地址 / 存活探针等) + ├── config/app.config.js # 集中配置:各服务的同源探针路径(/api/health)与默认参数 ├── i18n/ # 国际化(中文同步注入;英文动态 import() 懒加载) ├── router/index.js # 路由表由模块注册表自动生成 ├── modules/ # ★ 扩展点:每个页签一个文件夹 │ ├── index.js # 注册表(import.meta.glob 约定式收集,带容错) - │ ├── mobile-game/ # 移动游戏项目页签(包装 z449 子模块) - │ ├── whiteboard/ # 共享记事本页签(iframe 嵌入 + 存活校验) + │ ├── mobile-game/ # 移动游戏页签(包装 z449 子模块) + │ ├── whiteboard/ # 共享记事本页签(包装 zWhiteBoard 子模块 + 存活校验) │ ├── timetable/ # 日程整理页签(包装 timeTableFix 子模块) - │ ├── pdf/ # PDF 转换页签(iframe 嵌入 zTools2 /pdf + 存活校验) + │ ├── pdf/ # PDF 转换页签(包装 zPDF_package 子模块 + 存活校验) │ └── calendar/ # 隐藏页(仅手动访问 /cqy) ├── components/ # 共享组件(TabNav / LanguageSwitcher / ui 基元含 PageShell) ├── composables/ # useProjects / useLiveness / useI18nLazy / useLocale @@ -42,11 +44,11 @@ mainPage/ | 项 | 要求 | |---|---| | Node.js | ≥ 18 | -| Web 服务器 | Apache 2.4(启用 `mod_rewrite`),对外静态服务构建产物 | +| Web 服务器 | Apache 2.4(启用 `mod_rewrite` / `mod_proxy` / `mod_proxy_wstunnel`),对外静态服务构建产物 | | SPA 回退 | history 模式需站点根 `.htaccess` 回退到 `index.html` | -| 共享记事本 | iframe 嵌入外部服务 `https://f.zikai.wang`(URL 在 `src/config/app.config.js` 配置) | +| 后端 | zTools2(127.0.0.1:6867),由 Apache 反代 `/api/`;PDF/白板页签依赖其 REST API 与 WebSocket | -> 子模块(timeTableFix / z449)为构建期组件集成(非 iframe),共享 mainPage 的构建与 KeepAlive 缓存。 +> 四个子模块均为**构建期组件集成(非 iframe)**,共享 mainPage 的构建与 KeepAlive 缓存。各子模块依赖在其自身 `node_modules`,由各自 `package.json` 管理。 ## 如何使用 @@ -55,21 +57,23 @@ mainPage/ curl -fsSL https://deb.nodesource.com/setup_18.x | sudo bash - sudo apt-get install -y nodejs -# 2. 取代码(含子模块 timeTableFix / z449) +# 2. 取代码(含四个子模块) git clone --recursive https://git.zikai.wang/zikai/zMainPage.git mainPage cd mainPage # 若已 clone 但未带子模块:git submodule update --init --recursive npm install # 子模块需各自安装依赖(构建期编译其 .vue) -(cd third_party/z449 && npm install) (cd third_party/timeTableFix && npm install) +(cd third_party/z449 && npm install) +(cd third_party/zPDF_package && npm install) +(cd third_party/zWhiteBoard && npm install) -# 3. 构建到 dist/(再由 npm run deploy rsync 同步到 /root/html) +# 3. 构建到 dist/(再由 npm run deploy rsync 同步到 /var/www/html) npm run build # 4. 安装并启用 Apache sudo apt-get install -y apache2 -sudo a2enmod rewrite +sudo a2enmod rewrite proxy proxy_http proxy_wstunnel headers ``` 站点根放 `.htaccess`(Apache 需 `AllowOverride All`)做 SPA 回退: @@ -82,7 +86,25 @@ RewriteRule ^ - [L] RewriteRule ^ index.html [L] ``` -把 `/root/html`(或 `/var/www/html`)设为 `DocumentRoot`,访问站点即可。 +并把 `/var/www/html` 设为 `DocumentRoot`。后端 zTools2 需监听 `127.0.0.1:6867`,并在 vhost 加反代规则(见下方 Apache 配置)。整体部署可用 `./deploy.sh` 一键完成(详见 [`docs/deployment.md`](./docs/deployment.md))。 + +### Apache 反代 zTools2(/api/ -> 127.0.0.1:6867) + +```apache + + DocumentRoot /var/www/html + ProxyPreserveHost On + # 所有 zTools2 入口(API/静态/探针/WS)统一在 /api/ 下,一条规则即可 + ProxyPass /api/ http://127.0.0.1:6867/api/ + ProxyPassReverse /api/ http://127.0.0.1:6867/api/ + + AllowOverride All + Require all granted + + +``` + +> WebSocket 走 `/api/ws/wb/{id}`,需 `a2enmod proxy_wstunnel` 以透传 Upgrade 头。 ## 开发命令 @@ -90,7 +112,7 @@ RewriteRule ^ index.html [L] npm install # 首次安装依赖 npm run dev # 开发服务器 http://localhost:5173 npm run build # 构建到 dist/ -npm run deploy # 构建并 rsync 同步到 /root/html +npm run deploy # 构建并 rsync 同步到 /var/www/html npm run preview # 本地预览构建产物 ``` @@ -100,6 +122,7 @@ npm run preview # 本地预览构建产物 - [如何新增一个项目页签](./docs/add-module.md) - [子项目 timeTableFix 管理(submodule 升级 / 移除)](./docs/submodule-timeTableFix.md) - [子项目 z449 管理(submodule + i18n prop 桥接)](./docs/submodule-z449.md) -- [子项目 zPDF_package 管理(submodule + iframe 同源集成)](./docs/submodule-zPDF_package.md) +- [子项目 zPDF_package 管理(submodule + 同源 API 集成)](./docs/submodule-zPDF_package.md) +- [子项目 zWhiteBoard 管理(submodule + 同源 API/WS 集成)](./docs/submodule-zWhiteBoard.md) - [部署指南(组件独立 / 整体 / 持久化 systemd)](./docs/deployment.md) - [README 勘误记录](./docs/readme-fixes.md) diff --git a/deploy.sh b/deploy.sh index 32b8a34..2822dac 100755 --- a/deploy.sh +++ b/deploy.sh @@ -28,8 +28,11 @@ echo "==> [1/4] 构建 zMainPage(含子模块)" # 子模块依赖需各自安装(构建期编译其 .vue) [[ -d third_party/timeTableFix/node_modules ]] || (cd third_party/timeTableFix && npm install) [[ -d third_party/z449/node_modules ]] || (cd third_party/z449 && npm install) -# zPDF_package 是 iframe 集成,不参与 mainPage 构建,但确保其源码存在 -[[ -d third_party/zPDF_package ]] || git submodule update --init third_party/zPDF_package +[[ -d third_party/zPDF_package/node_modules ]] || (cd third_party/zPDF_package && npm install) +[[ -d third_party/zWhiteBoard/node_modules ]] || (cd third_party/zWhiteBoard && npm install) +# 确保子模块源码存在(全新检出时) +[[ -d third_party/zPDF_package/src ]] || git submodule update --init third_party/zPDF_package +[[ -d third_party/zWhiteBoard/src ]] || git submodule update --init third_party/zWhiteBoard npm run build echo "==> [2/4] 部署 zMainPage 构建产物到 $WEB_ROOT" @@ -89,4 +92,4 @@ echo echo "部署完成。" echo " - zMainPage: http://localhost(Apache :80,静态文件 + SPA)" echo " - zTools2: http://127.0.0.1:6867(经 Apache 反代 /api/)" -echo " - PDF 页签: http://localhost -> iframe 同源嵌入 zTools2 /api/pdf" +echo " - PDF 页签: http://localhost -> 构建期组件嵌入,同源调用 zTools2 /api/pdf/*" diff --git a/docs/add-module.md b/docs/add-module.md index 90b6a9b..ad2aa8f 100644 --- a/docs/add-module.md +++ b/docs/add-module.md @@ -41,7 +41,6 @@ const { t } = useLocale() PageShell props: - `titleKey`:i18n 键,渲染页面标题;不传则无标题 - `fluid`:true 突破 1080px 全宽(如日历页) -- `padded`:false 去掉默认上下间距(页面自管布局时用) - `#actions` 具名插槽:标题旁的操作区(如白板切换表单) ### 3. 加 i18n 文案 @@ -80,7 +79,7 @@ export default { id: 'secret', tabKey: 'tabs.secret', order: 99, hidden: true, c import { healthUrl } from '../../config/app.config.js' export default { id: 'my-service', tabKey: 'tabs.myService', order: 5, - requiresAlive: 'https://example.com/health', // 存活探针 URL + requiresAlive: healthUrl('myService'), // 同源探针 URL(/api/health) component: () => import('./MyService.vue') } ``` diff --git a/docs/architecture.md b/docs/architecture.md index 33c710c..44f054e 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -13,7 +13,7 @@ export default { component: () => import('./Whiteboard.vue'), // 必填:懒加载组件(函数形式,独立 chunk) order: 2, // 可选:页签排序,缺省 999(排在显式页签之后) hidden: true, // 可选:不在导航显示,但 URL 访问后保持可见(如 cqy) - requiresAlive: 'https://f.zikai.wang/health', // 可选:存活探针 URL,服务失活则隐藏页签并卸载组件 + requiresAlive: healthUrl('whiteboard'), // 可选:存活探针 URL(同源 /api/health),服务失活则隐藏页签并卸载组件 param: 'boardId', // 可选:路由参数名,生成 //:? 子路径 } ``` @@ -39,7 +39,7 @@ export default { `src/components/ui/PageShell.vue` 是可复用的页面外壳组件,统一所有页面的: - **容器宽度**:默认复用全局 `.container`(`max-width: 1080px`,居中,左右 `padding`);`fluid` 变体突破限制全宽(如日历页) -- **垂直节奏**:`padding: var(--space-xl) 0 var(--space-2xl)`;`padded: false` 可关 +- **垂直节奏**:`padding: var(--space-xl) 0 var(--space-2xl)` - **标题**:`titleKey` prop -> i18n 文案,渲染为 `

` - **操作区**:`#actions` 具名插槽,放在标题旁(如白板的切换白板表单) @@ -59,8 +59,10 @@ export default { 部分页签由独立仓库的子项目经 git submodule 集成,mainPage 侧用包装器 import 子项目 `App.vue` 作为路由组件(非 iframe),共享构建与 KeepAlive 缓存: -- **timeTableFix**(`third_party/timeTableFix`,日程整理页签):自带独立 `vue-i18n` 实例与中英 locale 文案。与 z449 同样经 `:locale` prop 桥接父项目语言。详见 [submodule-timeTableFix.md](./submodule-timeTableFix.md)。 +- **timeTableFix**(`third_party/timeTableFix`,日程整理页签):自带独立 `vue-i18n` 实例与中英 locale 文案。经 `:locale` prop 桥接父项目语言。详见 [submodule-timeTableFix.md](./submodule-timeTableFix.md)。 - **z449**(`third_party/z449`,移动游戏页签):自带独立 `vue-i18n` 实例与 locale 文案。mainPage 包装器把当前语言经 `:locale` prop 透传,子项目 `App.vue` `watch` 该 prop 同步到自己的 i18n 实例,从而跟随父项目语言开关响应式切换(不共享 messages,仅同步 locale 值)。详见 [submodule-z449.md](./submodule-z449.md)。 +- **zPDF_package**(`third_party/zPDF_package`,PDF 转换页签):经同源 fetch 调用 zTools2 的 `/api/pdf/*` REST API。详见 [submodule-zPDF_package.md](./submodule-zPDF_package.md)。 +- **zWhiteBoard**(`third_party/zWhiteBoard`,共享记事本页签):经同源 fetch/WS 调用 zTools2 的 `/api/wb/*` 与 `/api/ws/wb/*`。详见 [submodule-zWhiteBoard.md](./submodule-zWhiteBoard.md)。 > **i18n 桥接关键点**:子项目组件用各自 `useLocale` composable **直接读自身模块级 i18n 实例**(`i18n.global`),而非 `useI18n({ useScope: 'global' })`。后者在嵌入时会解析到宿主已安装的 i18n 实例(不含子项目文案),导致文案回退成 i18n key(显示 ID 而非文字)。 @@ -72,8 +74,9 @@ export default { - 时机:应用加载即探测一次,之后每 30s 周期重探(`startPolling`)。 - 结果在全应用共享(模块级 `cache` Map),`useProjects`(页签可见性)与 `App.vue`(KeepAlive 卸载)共用同一 ref。 - **乐观显示**:探测中(pending)/存活(up)都显示页签,仅明确不可达(down)才隐藏并卸载;恢复后自动重新加载。 +- 探针 URL 由 `healthUrl(service)` 拼出同源相对路径(`/api/health`),经 vite dev proxy / Apache 反代转发到 zTools2,与环境无关。 -目前仅白板页签使用(`requiresAlive: 'https://f.zikai.wang/health'`)。 +目前白板与 PDF 页签使用(`requiresAlive: healthUrl('whiteboard'|'pdf')`),二者探针均指向 zTools2 的 `/api/health`。 ## 路由 @@ -85,4 +88,4 @@ export default { ## 集中配置 -`src/config/app.config.js` -- 外部服务 URL(白板地址、存活探针路径等)集中管理,模块按需 import。改 URL 只改这里(需重新 build)。 +`src/config/app.config.js` -- 各服务的同源探针路径(`/api/health`)与默认参数集中管理,模块按需 import。改路径只改这里(需重新 build)。 diff --git a/docs/deployment.md b/docs/deployment.md index f034f84..1e3497d 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -16,7 +16,7 @@ zPDF_package 功能涉及三个项目协同:**zMainPage**(父站,Vue3 静 PDF 页签 = zMainPage 构建期 import zPDF_package 的 `App.vue` 作为组件(非 iframe),经同源 fetch 调用 zTools2 的 `/api/pdf/*` REST API(**同源相对路径**,cookie 第一方生效,与环境无关)。 -> **路由约定**:zTools2 所有入口(页面 / 静态 / 探针 / WS / API)统一挂在 `/api/` 下,反代与 vite proxy 只需一条 `/api/` 规则。前端(无论是 iframe 嵌入页还是组件 fetch API)一律用同源相对路径(`/api/pdf/jobs`、`/api/health` 等),自动指向**当前环境**的 zTools2,无需区分 dev/prod,也不会误打到其它环境域名。详见 zTools2 README「路由约定」。 +> **路由约定**:zTools2 所有入口(页面 / 静态 / 探针 / WS / API)统一挂在 `/api/` 下,反代与 vite proxy 只需一条 `/api/` 规则。前端组件经同源相对路径(`/api/pdf/jobs`、`/api/health` 等)调用,自动指向**当前环境**的 zTools2,无需区分 dev/prod,也不会误打到其它环境域名。详见 zTools2 README「路由约定」。 --- diff --git a/docs/readme-fixes.md b/docs/readme-fixes.md index 7e15744..35270c6 100644 --- a/docs/readme-fixes.md +++ b/docs/readme-fixes.md @@ -18,3 +18,36 @@ **修正**:三处均改为说明构建输出到 `dist/`,由 `npm run deploy` rsync 到 `/root/html`,并在「开发命令」补 `npm run deploy` 一行。 > 背景:本次升级 timeTableFix 子模块(新增时区选择)时,按 README 从 0 构建复现发现该不一致。 + +## 2026-07-28:部署路径统一为 /var/www/html + +**原描述(不一致)**: + +- `package.json` 的 `npm run deploy` 脚本 rsync 到 `/root/html`(依赖 `/root/html` 软链到 `/var/www/html`)。 +- `deploy.sh` 与 README 用 `/var/www/html`(Apache 标准 DocumentRoot,不依赖软链)。 +- `vite.config.js` 顶部注释写 `/root/html(软链到 /var/www/html)`。 + +**问题**:全新 Ubuntu 安装时 `/root/html` 软链不存在,`npm run deploy` 会失败或写到错误位置;三处路径不统一。 + +**修正**:`package.json` deploy 脚本与 `vite.config.js` 注释统一为 `/var/www/html`,与 `deploy.sh`、README、Apache DocumentRoot 一致。 + +## 2026-07-28:iframe / ical.js / 子模块数量描述过时 + +**原描述(错误)**: + +- README「外部依赖」称白板页「iframe 嵌入外部服务 `https://f.zikai.wang`」。 +- README「项目结构」注释称 zPDF_package「iframe 嵌入 zTools2」、白板「iframe 嵌入」。 +- README「package.json」注释含 `ical.js`(子模块依赖被误列在父项目)。 +- README 子模块只列 timeTableFix / z449,漏 zPDF_package / zWhiteBoard;克隆后安装依赖步骤也漏后两者。 +- architecture.md 的 `requiresAlive` 示例为 `https://f.zikai.wang/health`,且称「仅白板页签使用」(PDF 也用)。 +- architecture.md / add-module.md 提及 PageShell 的 `padded` prop(已被移除)。 +- submodule-timeTableFix.md 称「共享 mainPage 的依赖 `ical.js`」(实际子模块自带)。 + +**实际行为**: + +- 四个子模块(timeTableFix / z449 / zPDF_package / zWhiteBoard)均为**构建期组件 import**(非 iframe)。 +- 白板与 PDF 页签的探针均为同源相对路径 `/api/health`(经反代/vite proxy 指向 zTools2),非外部域名。 +- `ical.js` 仅 timeTableFix 使用,由其自身 `package.json` 管理;mainPage 的 `package.json` 不再列。 +- PageShell 已无 `padded` prop(默认即上下间距)。 + +**修正**:README 改写为简洁版,结构/外部依赖/克隆步骤均对齐现状;docs 各文件同步修正。 diff --git a/docs/submodule-timeTableFix.md b/docs/submodule-timeTableFix.md index 3c95562..aae7b1a 100644 --- a/docs/submodule-timeTableFix.md +++ b/docs/submodule-timeTableFix.md @@ -7,7 +7,7 @@ timeTableFix(ICS 日程整理器)作为 **git submodule** 被 mainPage 引 mainPage 侧的 `src/modules/timetable/Timetable.vue` 包装器 import 子模块的 `src/App.vue` 作为组件,套 `PageShell` 统一容器与标题,并持有 KeepAlive 匹配名 `defineOptions({ name: 'timetable' })`。 - 子模块 `App.vue` 保持纯净(不持 name、不依赖宿主),可独立 `npm run build` / `npm test` / 部署。 -- 构建期集成(非 iframe),共享 mainPage 的构建、依赖(`ical.js`)与 KeepAlive 缓存。 +- 构建期集成(非 iframe),共享 mainPage 的构建与 KeepAlive 缓存。子模块自身依赖(如 `ical.js`)在 `third_party/timeTableFix/node_modules`,由其自身 `package.json` 管理。 - 子模块样式已隔离(scoped + `ttf-` 前缀类名),全局重置仅在独立 app 的 `main.js` 加载,不污染宿主。 ## 克隆(含子模块) @@ -23,6 +23,12 @@ cd mainPage git submodule update --init --recursive ``` +> 构建前需在子模块目录内 `npm install`(mainPage 的 Vite 编译子模块 `.vue`,但子模块依赖如 `ical.js` 须各自安装): + +```bash +cd third_party/timeTableFix && npm install +``` + ## 升级子模块到最新版本 ```bash diff --git a/docs/submodule-zPDF_package.md b/docs/submodule-zPDF_package.md index 50a6a16..b1d90be 100644 --- a/docs/submodule-zPDF_package.md +++ b/docs/submodule-zPDF_package.md @@ -61,4 +61,4 @@ git commit -m "chore: 移除 zPDF_package 子模块集成" | cookie | 不涉及 | httpOnly `zk_pdf`,同源自动携带(首次上传种下) | | mainPage 是否编译子模块 | 是(import .vue) | 是(import .vue) | -> 三个子项目(timeTableFix / z449 / zPDF_package)均采用构建期组件 import 集成,无 iframe。zPDF_package 额外依赖 zTools2 后端 API,页签带存活探针。 +> 四个子项目(timeTableFix / z449 / zPDF_package / zWhiteBoard)均采用构建期组件 import 集成,无 iframe。zPDF_package 与 zWhiteBoard 额外依赖 zTools2 后端 API,页签带存活探针。 diff --git a/docs/submodule-zWhiteBoard.md b/docs/submodule-zWhiteBoard.md new file mode 100644 index 0000000..124a3a0 --- /dev/null +++ b/docs/submodule-zWhiteBoard.md @@ -0,0 +1,72 @@ +# 子项目 zWhiteBoard(git submodule) + +zWhiteBoard(共享记事本前端)作为 **git submodule** 被 mainPage 引入,位于 `third_party/zWhiteBoard`,独立仓库为 https://git.zikai.wang/zikai/zWhiteBoard.git 。 + +## 集成方式 + +mainPage 侧的 `src/modules/whiteboard/Whiteboard.vue` 包装器 import 子模块的 `src/App.vue` 作为组件,套 `PageShell` 统一容器与标题,并持有 KeepAlive 匹配名 `defineOptions({ name: 'whiteboard' })`。 + +- 子模块 `App.vue` 保持纯净(不持 name、不依赖宿主),可独立 `npm run build` / `npm test` / 部署。 +- **构建期集成(非 iframe)**,共享 mainPage 的构建与 KeepAlive 缓存。前后端分离:UI/交互在 zWhiteBoard,能力由 zTools2 的 `/api/wb/*` REST API 与 `/api/ws/wb/*` WebSocket 提供。 +- 子模块样式已隔离(scoped),全局重置仅在独立 app 的 `main.js` 加载,不污染宿主。 +- 页签带 `requiresAlive: healthUrl('whiteboard')`(同源 `/api/health`),zTools2 不可达时隐藏页签并卸载组件。 +- **身份标识**:白板不依赖 cookie。客户端 id 由 WebSocket 首帧 `hello` 携带或服务端生成(`client_id`),用于区分广播来源。组件经同源 fetch/WS 调用,反代使 `/api/` 与主页同源,无需 CORS。 +- 包装器解析路由参数 `/whiteboard/:boardId` 并经 `:board-id` prop 透传给子模块,支持直链预填白板 id。 + +## 克隆(含子模块) + +```bash +git clone --recursive https://git.zikai.wang/zikai/zMainPage.git mainPage +``` + +若已 clone 但未带子模块: + +```bash +cd mainPage +git submodule update --init --recursive +``` + +> 构建前需在子模块目录内 `npm install`(mainPage 的 Vite 编译子模块 `.vue`,但子模块依赖如 `vue-i18n` 须各自安装): + +```bash +cd third_party/zWhiteBoard && npm install +``` + +## 升级子模块到最新版本 + +```bash +cd mainPage +git submodule update --remote third_party/zWhiteBoard # 拉取最新 +cd third_party/zWhiteBoard && git checkout master # 固定到目标分支/commit +cd ../.. +git add third_party/zWhiteBoard # 更新 gitlink 指针 +git commit -m "chore: 升级 zWhiteBoard 子模块" +npm run build # 重新构建 mainPage +git push # 推送 mainPage(gitlink) +``` + +> 推送顺序:先确保子模块的 commit 已推到 zWhiteBoard.git,再推 mainPage 的 gitlink,否则别人 clone mainPage 时拉不到子模块对应 commit。 + +## 移除子模块集成(仅移除 mainPage 页签,不影响 zWhiteBoard 本身) + +```bash +cd mainPage +git submodule deinit -f third_party/zWhiteBoard +git rm third_party/zWhiteBoard +git commit -m "chore: 移除 zWhiteBoard 子模块集成" +``` + +同时删 `src/modules/whiteboard/` 文件夹即移除页签。zWhiteBoard 独立仓库不受影响。 + +## 与其他子模块的差异 + +| 维度 | timeTableFix / z449 | zPDF_package | zWhiteBoard | +|------|---------------------|--------------|-------------| +| 集成方式 | 构建期组件 import(共享构建/KeepAlive) | 构建期组件 import(共享构建/KeepAlive) | 构建期组件 import(共享构建/KeepAlive) | +| 后端依赖 | 无(纯前端) | 有(zTools2 `/api/pdf/*`) | 有(zTools2 `/api/wb/*` + `/api/ws/wb/*`) | +| 同源/CORS | 不涉及 | 同源经反代,无需 CORS | 同源经反代,无需 CORS | +| 身份标识 | 不涉及 | httpOnly `zk_pdf` cookie,同源自动携带 | WS 首帧 `client_id`(无 cookie) | +| 路由参数 | 无 | 无 | `:boardId`(直链预填白板 id) | +| mainPage 是否编译子模块 | 是(import .vue) | 是(import .vue) | 是(import .vue) | + +> 四个子项目均采用构建期组件 import 集成,无 iframe。zWhiteBoard 与 zPDF_package 额外依赖 zTools2 后端 API,页签带存活探针;zWhiteBoard 是唯一带 WebSocket 与路由参数的子模块。 diff --git a/package-lock.json b/package-lock.json index 8febf13..03695bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,6 @@ "name": "mainpage", "version": "0.1.0", "dependencies": { - "ical.js": "^2.1.0", "vue": "^3.4.21", "vue-i18n": "^9.13.1", "vue-router": "^4.3.0" @@ -1119,11 +1118,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/ical.js": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ical.js/-/ical.js-2.2.1.tgz", - "integrity": "sha512-yK/UlPbEs316igb/tjRgbFA8ZV75rCsBJp/hWOatpyaPNlgw0dGDmU+FoicOcwX4xXkeXOkYiOmCqNPFpNPkQg==" - }, "node_modules/lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", diff --git a/package.json b/package.json index f30de6c..9de804c 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,9 @@ "dev": "vite", "build": "vite build", "preview": "vite preview", - "deploy": "vite build && rsync -a --delete dist/js/mainPage/ /root/html/js/mainPage/ && rsync -a --delete dist/css/mainPage/ /root/html/css/mainPage/ && rsync -a dist/index.html /root/html/index.html && rsync -a dist/favicon.svg /root/html/favicon.svg" + "deploy": "vite build && rsync -a --delete dist/js/mainPage/ /var/www/html/js/mainPage/ && rsync -a --delete dist/css/mainPage/ /var/www/html/css/mainPage/ && rsync -a dist/index.html /var/www/html/index.html && rsync -a dist/favicon.svg /var/www/html/favicon.svg" }, "dependencies": { - "ical.js": "^2.1.0", "vue": "^3.4.21", "vue-i18n": "^9.13.1", "vue-router": "^4.3.0" diff --git a/src/components/ui/Card.vue b/src/components/ui/Card.vue deleted file mode 100644 index 51b58ba..0000000 --- a/src/components/ui/Card.vue +++ /dev/null @@ -1,28 +0,0 @@ - - - - - diff --git a/src/components/ui/PageShell.vue b/src/components/ui/PageShell.vue index ad43ada..b4630e7 100644 --- a/src/components/ui/PageShell.vue +++ b/src/components/ui/PageShell.vue @@ -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) : '')) diff --git a/src/composables/useI18nLazy.js b/src/composables/useI18nLazy.js index 581ed2f..6de9141 100644 --- a/src/composables/useI18nLazy.js +++ b/src/composables/useI18nLazy.js @@ -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 } diff --git a/src/composables/useLocale.js b/src/composables/useLocale.js index 8f0beb0..ac0fcc5 100644 --- a/src/composables/useLocale.js +++ b/src/composables/useLocale.js @@ -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 } } diff --git a/src/config/app.config.js b/src/config/app.config.js index c70f0a0..9b15fab 100644 --- a/src/config/app.config.js +++ b/src/config/app.config.js @@ -20,18 +20,14 @@ export const appConfig = { // 存活探针路径;周期探测,挂掉则隐藏页签并卸载组件 healthPath: '/api/health', // 默认打开的记事本 id(Whiteboard.vue 的 resolveBoardId 回退用) - defaultBoardId: 'share', - // 探测超时(毫秒) - healthTimeoutMs: 4000 + defaultBoardId: 'share' }, // PDF 转换服务(zTools2 提供 /api/pdf/* REST API;zPDF_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 -} diff --git a/src/i18n/locales/en.js b/src/i18n/locales/en.js index 3cda9c1..29de762 100644 --- a/src/i18n/locales/en.js +++ b/src/i18n/locales/en.js @@ -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', diff --git a/src/i18n/locales/zh-CN.js b/src/i18n/locales/zh-CN.js index a4edb7f..1d0feec 100644 --- a/src/i18n/locales/zh-CN.js +++ b/src/i18n/locales/zh-CN.js @@ -4,9 +4,7 @@ export default { title: 'Zikai' }, common: { - loading: '加载中…', - notAvailable: '暂不可用', - serviceUnavailable: '服务暂不可用,请稍后再试。' + loading: '加载中…' }, tabs: { mobileGame: '移动游戏项目', diff --git a/src/modules/index.js b/src/modules/index.js index 9b8e856..819cda7 100644 --- a/src/modules/index.js +++ b/src/modules/index.js @@ -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)) diff --git a/src/router/index.js b/src/router/index.js index 99783da..75274d8 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -2,24 +2,17 @@ import { createRouter, createWebHistory } from 'vue-router' import modules from '../modules/index.js' -const moduleRoutes = modules - .map((m) => { - try { - // 模块可声明 param(如 'boardId')以接受 // 形式的子路径, - // 形如 /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')以接受 // 形式的子路径, + // 形如 /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 = [ { diff --git a/vite.config.js b/vite.config.js index 847c13c..4f9c8e2 100644 --- a/vite.config.js +++ b/vite.config.js @@ -3,9 +3,9 @@ import vue from '@vitejs/plugin-vue' import { minify } from 'html-minifier-terser' // 构建到项目本地 dist/(每次清空,避免旧产物残留), -// 再由 `npm run deploy` 用 rsync 同步到 Apache 站点根 /root/html(软链到 /var/www/html)。 +// 再由 `npm run deploy` 或 `deploy.sh` 用 rsync 同步到 Apache 站点根 /var/www/html。 // 文件名去掉内容 hash,保持稳定;JS 由 esbuild 压缩,HTML 由下方插件压缩。 -// 用 mainPage/ 命名空间子目录避免与 /root/html/js、/root/html/css 既有旧文件冲突。 +// 用 mainPage/ 命名空间子目录避免与 /var/www/html/js、/var/www/html/css 既有旧文件冲突。 function minifyHtml() { return { name: 'minify-html', @@ -51,19 +51,9 @@ export default defineConfig({ }, }, server: { - // 开发服务器把站点根的静态资源代理到 /root/html,方便引用 /448 /449 等 port: 5173, proxy: { - '/448': { - target: 'http://localhost:8080', - changeOrigin: true, - }, - '/449': { - target: 'http://localhost:8080', - changeOrigin: true, - }, - // zTools2 后端同源代理:所有入口(页面/静态/探针/WS/API)统一在 /api/ 下, - // 一条 /api 规则即可覆盖 iframe(/api/pdf)与 fetch(/api/pdf/*)。 + // zTools2 后端同源代理:所有入口(API/静态/探针/WS)统一在 /api/ 下,一条规则即可。 // ws:true 让 /api/ws/wb/{id} 的 WebSocket 也走此代理。 // 同源相对路径与环境无关:dev 命中本地 zTools2,生产由反代命中当前环境 zTools2。 '/api': {