Compare commits
2 Commits
5b1acbdeb7
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8dcf5f0d36 | ||
| 25dbceee2d |
67
README.md
67
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 # 根布局:顶栏 + 标签导航 + <KeepAlive router-view> + 页脚
|
||||
├── 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
|
||||
<VirtualHost *:80>
|
||||
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/
|
||||
<Directory /var/www/html>
|
||||
AllowOverride All
|
||||
Require all granted
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
> 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)
|
||||
|
||||
@@ -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/*"
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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', // 可选:路由参数名,生成 /<id>/:<param>? 子路径
|
||||
}
|
||||
```
|
||||
@@ -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 文案,渲染为 `<h1>`
|
||||
- **操作区**:`#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)。
|
||||
|
||||
@@ -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「路由约定」。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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 各文件同步修正。
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,页签带存活探针。
|
||||
|
||||
72
docs/submodule-zWhiteBoard.md
Normal file
72
docs/submodule-zWhiteBoard.md
Normal file
@@ -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 与路由参数的子模块。
|
||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
/* 标准容器宽度复用全局 .container(max-width 1080px,居中,左右 padding) */
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -4,9 +4,7 @@ export default {
|
||||
title: 'Zikai'
|
||||
},
|
||||
common: {
|
||||
loading: '加载中…',
|
||||
notAvailable: '暂不可用',
|
||||
serviceUnavailable: '服务暂不可用,请稍后再试。'
|
||||
loading: '加载中…'
|
||||
},
|
||||
tabs: {
|
||||
mobileGame: '移动游戏项目',
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import modules from '../modules/index.js'
|
||||
|
||||
const moduleRoutes = modules
|
||||
.map((m) => {
|
||||
try {
|
||||
const moduleRoutes = modules.map((m) => {
|
||||
// 模块可声明 param(如 'boardId')以接受 /<id>/<param> 形式的子路径,
|
||||
// 形如 /whiteboard/share。可选参数 (? 后缀) 使 /whiteboard 仍可访问。
|
||||
const paramPart = m.param ? `/:${m.param}?` : ''
|
||||
@@ -14,12 +12,7 @@ const moduleRoutes = modules
|
||||
component: m.component,
|
||||
meta: { moduleId: m.id }
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`[router] 模块 ${m?.id} 路由生成失败,已跳过:`, e)
|
||||
return null
|
||||
}
|
||||
})
|
||||
.filter(Boolean)
|
||||
})
|
||||
|
||||
const routes = [
|
||||
{
|
||||
|
||||
@@ -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': {
|
||||
|
||||
Reference in New Issue
Block a user