feat: PDF 页签从 iframe 改为构建期组件挂载,清理 iframe 配置与文档
前后端分离:UI/交互在 zPDF_package,能力由 zTools2 的 /api/pdf/* REST API 提供。
组件经同源 fetch 调用,httpOnly cookie(zk_pdf)经反代第一方自动携带,无需 CORS。
与 timeTableFix 集成范式完全一致。
- src/modules/pdf/Pdf.vue:iframe 包装器重写为组件挂载包装器,套 PageShell +
透传 :locale;删除 pageSrc/getLiveness/scheduleFailCheck/openExternal 等 iframe 逻辑
- src/modules/pdf/index.js:注释从 iframe 改为构建期组件 import(requiresAlive 探针保留)
- src/config/app.config.js:移除 pdf.pagePath(iframe src),保留探针配置
- src/i18n/{zh-CN,en}.js:移除 pdf.openExternal(iframe 降级按钮文案,已无用)
- docs/submodule-zPDF_package.md:集成方式改为构建期组件 import,补 cookie 认证说明
- docs/deployment.md:拓扑去 /api/pdf 页面行,措辞改为组件 fetch
- third_party/zPDF_package:子模块指针升级到 4d12b61(去 iframe 表述)
This commit is contained in:
@@ -8,16 +8,15 @@ zPDF_package 功能涉及三个项目协同:**zMainPage**(父站,Vue3 静
|
||||
浏览器 -> Apache(:80)
|
||||
├── 静态文件 /var/www/html (zMainPage 构建产物:主页 + 页签)
|
||||
└── 反代 /api/ -> zTools2(127.0.0.1:6867)
|
||||
├── /api/pdf PDF 转换页面(iframe 嵌入源)
|
||||
├── /api/pdf/* PDF 转换 API(cookie 用户)
|
||||
├── /api/pdf-admin PDF 管理页(Basic Auth)
|
||||
├── /api/static/pdf* 前端静态资源
|
||||
└── MySQL(3306) + uploads/
|
||||
```
|
||||
|
||||
PDF 页签 = zMainPage 用 iframe 嵌入 zTools2 的 `/api/pdf` 页面(**同源相对路径**,cookie 第一方生效,与环境无关)。
|
||||
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/pdf`、`/api/health` 等),自动指向**当前环境**的 zTools2,无需区分 dev/prod,也不会误打到其它环境域名。详见 zTools2 README「路由约定」。
|
||||
> **路由约定**:zTools2 所有入口(页面 / 静态 / 探针 / WS / API)统一挂在 `/api/` 下,反代与 vite proxy 只需一条 `/api/` 规则。前端(无论是 iframe 嵌入页还是组件 fetch API)一律用同源相对路径(`/api/pdf/jobs`、`/api/health` 等),自动指向**当前环境**的 zTools2,无需区分 dev/prod,也不会误打到其它环境域名。详见 zTools2 README「路由约定」。
|
||||
|
||||
---
|
||||
|
||||
@@ -34,7 +33,9 @@ cd zTools2
|
||||
# 或:python -m uvicorn app.main:app --host 127.0.0.1 --port 6867
|
||||
```
|
||||
|
||||
访问:http://127.0.0.1:6867/api/pdf(PDF 页面)、http://127.0.0.1:6867/docs(API 文档)。
|
||||
访问:http://127.0.0.1:6867/api/pdf-admin(PDF 管理页)、http://127.0.0.1:6867/docs(API 文档)。
|
||||
|
||||
> 用户侧 PDF 页面由 zMainPage 的 zPDF_package 组件提供(构建期 import),zTools2 仅提供 `/api/pdf/*` REST API 与 `/api/pdf-admin` 管理页。
|
||||
|
||||
### 2. zPDF_package 前端独立运行
|
||||
|
||||
@@ -57,7 +58,7 @@ npm install
|
||||
npm run dev # http://localhost:5173
|
||||
```
|
||||
|
||||
> 独立模式下 PDF 页签的 iframe 用同源相对路径 `/api/pdf`,经 vite proxy 转发到本地 zTools2(127.0.0.1:6867)。如需指向其它环境的 zTools2,改 `src/config/app.config.js` 的 `pdf.baseUrl` 为该环境地址(含协议与域名)后重新 `npm run dev`,或设 `VITE_API_BASE` 环境变量。
|
||||
> 独立模式下 PDF 页签的 zPDF_package 组件经同源相对路径 `/api/pdf/*` 调用 zTools2,经 vite proxy 转发到本地 zTools2(127.0.0.1:6867)。如需指向其它环境的 zTools2,改 `src/config/app.config.js` 的 `pdf.baseUrl` 为该环境地址(含协议与域名)后重新 `npm run dev`,或设 `VITE_API_BASE` 环境变量。
|
||||
|
||||
---
|
||||
|
||||
@@ -77,7 +78,7 @@ cd zMainPage
|
||||
3. 配置 Apache vhost(`/etc/apache2/sites-available/zmainpage.conf`)反代 `/api/` 到 `127.0.0.1:6867`(含 WebSocket,靠 proxy_wstunnel 透传 `/api/ws`)
|
||||
4. 重启 zTools2(除非 `--no-restart`)
|
||||
|
||||
部署后访问 http://localhost/ 即主页,PDF 页签 iframe 同源加载 `/api/pdf`。
|
||||
部署后访问 http://localhost/ 即主页,PDF 页签渲染 zPDF_package 组件并经同源 fetch 调用 `/api/pdf/*`。
|
||||
|
||||
> 已有 HTTPS vhost 时:`deploy.sh` 不会覆盖(仅首次写入)。手动在现有 vhost 加 `ProxyPass /api/ http://127.0.0.1:6867/api/` 一条规则即可(需 `a2enmod proxy_wstunnel` 以支持 WebSocket)。
|
||||
|
||||
|
||||
@@ -4,11 +4,13 @@ zPDF_package(PDF 转换前端)作为 **git submodule** 被 mainPage 引入
|
||||
|
||||
## 集成方式
|
||||
|
||||
mainPage 侧的 `src/modules/pdf/Pdf.vue` 用 **iframe** 嵌入 zTools2 托管的 `/pdf` 页面(`https://f.zikai.wang/pdf`),套 `PageShell` 统一容器与标题,并持有 KeepAlive 匹配名 `defineOptions({ name: 'pdf' })`。
|
||||
mainPage 侧的 `src/modules/pdf/Pdf.vue` 包装器 import 子模块的 `src/App.vue` 作为组件,套 `PageShell` 统一容器与标题,并持有 KeepAlive 匹配名 `defineOptions({ name: 'pdf' })`。
|
||||
|
||||
- **iframe 同源嵌入**(非组件 import):PDF 转换有后端依赖(zTools2 的 `/api/pdf/*`),iframe 嵌入使页面与 API 同源(均在 `f.zikai.wang`),用户 cookie(`zk_pdf`)第一方自然生效,无需给 zTools2 加 CORS,不受第三方 cookie 限制。与白板页签的嵌入方式一致。
|
||||
- 页签带 `requiresAlive: healthUrl('pdf')`,zTools2 不可达时隐藏页签并卸载组件;直链进入则显示降级提示与「在新标签页打开」。
|
||||
- zPDF_package 源码作为 submodule 管理,其 `npm run build` 产物部署到 zTools2 托管。mainPage 构建不编译子模块(iframe 集成,非构建期组件集成)。
|
||||
- 子模块 `App.vue` 保持纯净(不持 name、不依赖宿主),可独立 `npm run build` / `npm test` / 部署。
|
||||
- **构建期集成(非 iframe)**,共享 mainPage 的构建与 KeepAlive 缓存。前后端分离:UI/交互在 zPDF_package,能力由 zTools2 的 `/api/pdf/*` REST API 提供。
|
||||
- 子模块样式已隔离(scoped + `zpdf-` 前缀类名),全局重置仅在独立 app 的 `main.js` 加载,不污染宿主。
|
||||
- 页签带 `requiresAlive: healthUrl('pdf')`,zTools2 不可达时隐藏页签并卸载组件。
|
||||
- **认证**:用户身份凭 httpOnly cookie(`zk_pdf`,samesite=lax)标识。该 cookie 在首次 `POST /api/pdf/jobs`(上传)的响应里种下,与页面路由无关。组件经同源 fetch 调用 `/api/pdf/*`,反代使 `/api/` 与主页同源,cookie 自动携带,无需 CORS。
|
||||
|
||||
## 克隆(含子模块)
|
||||
|
||||
@@ -36,7 +38,7 @@ npm run build # 重新构建 main
|
||||
git push # 推送 mainPage(gitlink)
|
||||
```
|
||||
|
||||
> 推送顺序:先确保子模块的 commit 已推到 zPDF_package.git,再推 mainPage 的 gitlink。
|
||||
> 推送顺序:先确保子模块的 commit 已推到 zPDF_package.git,再推 mainPage 的 gitlink,否则别人 clone mainPage 时拉不到子模块对应 commit。
|
||||
|
||||
## 移除子模块集成(仅移除 mainPage 页签,不影响 zPDF_package 本身)
|
||||
|
||||
@@ -53,10 +55,10 @@ git commit -m "chore: 移除 zPDF_package 子模块集成"
|
||||
|
||||
| 维度 | timeTableFix / z449 | zPDF_package |
|
||||
|------|---------------------|--------------|
|
||||
| 集成方式 | 构建期组件 import(共享构建/KeepAlive) | iframe 嵌入 zTools2 托管页 |
|
||||
| 集成方式 | 构建期组件 import(共享构建/KeepAlive) | 构建期组件 import(共享构建/KeepAlive) |
|
||||
| 后端依赖 | 无(纯前端) | 有(zTools2 `/api/pdf/*`) |
|
||||
| 同源/CORS | 不涉及 | 同源(f.zikai.wang),无需 CORS |
|
||||
| cookie | 不涉及 | 第一方 httpOnly cookie 自然生效 |
|
||||
| mainPage 是否编译子模块 | 是(import .vue) | 否(iframe) |
|
||||
| 同源/CORS | 不涉及 | 同源经反代,无需 CORS |
|
||||
| cookie | 不涉及 | httpOnly `zk_pdf`,同源自动携带(首次上传种下) |
|
||||
| mainPage 是否编译子模块 | 是(import .vue) | 是(import .vue) |
|
||||
|
||||
> zPDF_package 仍作为 submodule 管理源码,便于独立开发/测试/升级;其构建产物部署到 zTools2,mainPage 经 iframe 引用 zTools2 的 `/pdf`。
|
||||
> 三个子项目(timeTableFix / z449 / zPDF_package)均采用构建期组件 import 集成,无 iframe。zPDF_package 额外依赖 zTools2 后端 API,页签带存活探针。
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// 模块按需 import { appConfig } 使用,改 URL 只改这里即可(需重新 build)。
|
||||
// 每个服务项都带默认值,避免缺失字段导致渲染崩溃。
|
||||
//
|
||||
// 路由约定:zTools2 所有入口(页面/静态/探针/WS/API)统一挂在 /api/ 下,
|
||||
// iframe 与 fetch 一律用**同源相对路径**(/api/pdf、/api/health 等)。
|
||||
// 路由约定:zTools2 所有入口(API/静态/探针/WS)统一挂在 /api/ 下,
|
||||
// fetch 与探针一律用**同源相对路径**(/api/pdf/jobs、/api/health 等)。
|
||||
// 无论是 vite dev(server.proxy 转发 /api 到本地 zTools2)、还是任意部署环境
|
||||
// (Apache/Nginx 反代 /api 到当前环境的 zTools2),同源相对路径都自动指向
|
||||
// **当前环境**的服务,无需区分 dev/prod,也不会误打到其它环境域名。
|
||||
@@ -26,14 +26,12 @@ export const appConfig = {
|
||||
// 探测超时(毫秒)
|
||||
healthTimeoutMs: 4000
|
||||
},
|
||||
// PDF 转换服务(zTools2 托管的 /api/pdf 页面,iframe 同源嵌入)
|
||||
// PDF 转换服务(zTools2 提供 /api/pdf/* REST API;zPDF_package 组件同源 fetch 调用)
|
||||
pdf: {
|
||||
// 服务根地址(含协议与域名);默认空串 = 同源相对路径
|
||||
baseUrl: BASE_URL,
|
||||
// 存活探针路径;周期探测,挂掉则隐藏页签并卸载组件
|
||||
healthPath: '/api/health',
|
||||
// PDF 转换页面路径
|
||||
pagePath: '/api/pdf',
|
||||
// 探测超时(毫秒)
|
||||
healthTimeoutMs: 4000
|
||||
}
|
||||
|
||||
@@ -20,10 +20,8 @@ export default {
|
||||
openExternal: 'Open in new tab',
|
||||
boardId: 'Board ID',
|
||||
switch: 'Switch'
|
||||
},
|
||||
pdf: {
|
||||
openExternal: 'Open in new tab'
|
||||
}
|
||||
// Mobile-game copy is provided by the z449 submodule's own i18n;
|
||||
// mainPage keeps only tabs.mobileGame. Locale syncs to z449 via :locale prop.
|
||||
// PDF tab copy is provided by the zPDF_package submodule's own i18n; mainPage keeps only tabs.pdf.
|
||||
}
|
||||
|
||||
@@ -19,10 +19,8 @@ export default {
|
||||
openExternal: '在新标签页打开',
|
||||
boardId: '白板 ID',
|
||||
switch: '切换'
|
||||
},
|
||||
pdf: {
|
||||
openExternal: '在新标签页打开'
|
||||
}
|
||||
// 注:移动游戏项目(mobile-game)的文案由子模块 z449 自有 i18n 提供,
|
||||
// mainPage 仅保留 tabs.mobileGame 页签标题。切换语言时经 :locale prop 同步给 z449。
|
||||
// PDF 转换页签的文案由子模块 zPDF_package 自有 i18n 提供,mainPage 仅保留 tabs.pdf。
|
||||
}
|
||||
|
||||
@@ -1,138 +1,47 @@
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
// 组件名与模块 id 一致,供 <KeepAlive :include> 按 id 精确匹配;
|
||||
// PDF 服务后端失活时把它从 include 移除即可卸载组件
|
||||
// ★ pdf 页签的 mainPage 侧包装器。
|
||||
// 子模块 zPDF_package 的 App.vue 作为便携式组件被 import,本包装器负责:
|
||||
// 1. 套 PageShell 统一容器宽度与标题(与其他页签对齐)
|
||||
// 2. 持有 KeepAlive 匹配用的组件名(与模块 id 一致),子模块自身不持 name
|
||||
// 3. 把 mainPage 当前语言经 :locale prop 透传给子模块的独立 i18n 实例,
|
||||
// 使其跟随父项目语言开关响应式切换(不共享 messages,仅同步 locale 值)
|
||||
// 前后端分离:UI/交互在 zPDF_package,能力由 zTools2 的 /api/pdf/* REST API 提供,
|
||||
// 同源 fetch 自动携带 httpOnly cookie(zk_pdf),无需 CORS。与 timeTableFix 集成范式一致。
|
||||
// 子模块独立运行时不经过本包装器,保持纯净。
|
||||
defineOptions({ name: 'pdf' })
|
||||
import { appConfig, healthUrl } from '../../config/app.config.js'
|
||||
import { useLocale } from '../../composables/useLocale.js'
|
||||
import { getLiveness } from '../../composables/useLiveness.js'
|
||||
|
||||
import { computed } from 'vue'
|
||||
import PageShell from '../../components/ui/PageShell.vue'
|
||||
import i18n from '../../i18n/index.js'
|
||||
import ZPdfApp from '../../../third_party/zPDF_package/src/App.vue'
|
||||
|
||||
const { t } = useLocale()
|
||||
|
||||
const pdfConfig = appConfig.pdf
|
||||
|
||||
// 完整 PDF 转换页 URL(zTools2 同源托管,可被 iframe 嵌入)
|
||||
const pageSrc = computed(() => `${pdfConfig.baseUrl}${pdfConfig.pagePath}`)
|
||||
|
||||
// 存活态:服务挂掉时显示降级提示(页签本身也已隐藏,但用户若直链进入仍可见)
|
||||
const aliveState = getLiveness(healthUrl('pdf'))
|
||||
const isDown = computed(() => aliveState.value === 'down')
|
||||
|
||||
const loaded = ref(false)
|
||||
const failed = ref(false)
|
||||
|
||||
function onLoad() {
|
||||
loaded.value = true
|
||||
}
|
||||
function scheduleFailCheck() {
|
||||
setTimeout(() => {
|
||||
if (!loaded.value) failed.value = true
|
||||
}, 6000)
|
||||
}
|
||||
scheduleFailCheck()
|
||||
|
||||
function openExternal() {
|
||||
window.open(pageSrc.value, '_blank', 'noopener')
|
||||
}
|
||||
// 透传父项目当前语言给子应用(响应式:切语言时子应用内容跟随)
|
||||
const locale = computed(() => i18n.global.locale.value)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageShell title-key="tabs.pdf">
|
||||
<!-- 服务不可达 / iframe 加载超时降级 -->
|
||||
<div v-if="isDown || failed" class="pdf-fallback">
|
||||
<p class="pdf-fallback__text">{{ t('common.serviceUnavailable') }}</p>
|
||||
<button type="button" class="pdf-fallback__open" @click="openExternal">
|
||||
{{ t('pdf.openExternal') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-else class="pdf-frame">
|
||||
<!-- 加载骨架(iframe 加载完成后淡出) -->
|
||||
<Transition name="fade">
|
||||
<div v-if="!loaded" class="pdf-frame__skeleton">
|
||||
<div class="pdf-frame__skeleton-text">{{ t('common.loading') }}</div>
|
||||
</div>
|
||||
</Transition>
|
||||
<iframe
|
||||
:src="pageSrc"
|
||||
class="pdf-frame__iframe"
|
||||
frameborder="0"
|
||||
:title="t('tabs.pdf')"
|
||||
@load="onLoad"
|
||||
/>
|
||||
<div class="pdf__frame">
|
||||
<ZPdfApp :locale="locale" />
|
||||
</div>
|
||||
</PageShell>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.pdf-frame {
|
||||
/* 让子应用填满外壳容器,保留其内部布局自洽;转换页以上传与列表为主,给足高度 */
|
||||
.pdf__frame {
|
||||
position: relative;
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow);
|
||||
border: 1px solid var(--border);
|
||||
width: 100%;
|
||||
background: var(--surface);
|
||||
/* 转换页以上传与列表为主,给足高度 */
|
||||
height: 78vh;
|
||||
min-height: 520px;
|
||||
background: var(--surface);
|
||||
}
|
||||
.pdf-frame__iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
border: 0;
|
||||
}
|
||||
.pdf-frame__skeleton {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--surface);
|
||||
}
|
||||
.pdf-frame__skeleton-text {
|
||||
color: var(--text-tertiary);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.pdf-fallback {
|
||||
text-align: center;
|
||||
padding: var(--space-2xl) var(--space-lg);
|
||||
border: 1px dashed var(--border-strong);
|
||||
border-radius: var(--radius);
|
||||
background: var(--surface);
|
||||
}
|
||||
.pdf-fallback__text {
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
.pdf-fallback__open {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 8px 18px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
background: var(--accent);
|
||||
border-radius: var(--radius-sm);
|
||||
transition: background var(--transition);
|
||||
}
|
||||
.pdf-fallback__open:hover {
|
||||
background: var(--accent-hover);
|
||||
}
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.4s ease;
|
||||
}
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.pdf-frame {
|
||||
.pdf__frame {
|
||||
height: 72vh;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// 模块元数据 -- PDF 转换页签
|
||||
// 构建期组件集成:mainPage 侧包装器 Pdf.vue import 子模块 zPDF_package 的 App.vue,
|
||||
// 套 PageShell 统一容器与标题。zPDF_package 作为 git submodule 独立维护,
|
||||
// 其 UI/交互经同源 fetch 调用 zTools2 的 /api/pdf/* REST API(前后端分离)。
|
||||
// requiresAlive 指向存活探针 URL:启动时探测,zTools2 不可达则隐藏该页签。
|
||||
// 页面用 iframe 嵌入 zTools2 托管的 /pdf(同源,cookie 第一方生效,无需 CORS)。
|
||||
// 删除本目录即可移除此页签,其余模块不受影响。
|
||||
// 升级:cd third_party/zPDF_package && git pull,回 mainPage 根 git add 该子模块并重新 build。
|
||||
import { healthUrl } from '../../config/app.config.js'
|
||||
|
||||
export default {
|
||||
|
||||
2
third_party/zPDF_package
vendored
2
third_party/zPDF_package
vendored
Submodule third_party/zPDF_package updated: 8482917e88...4d12b615c8
Reference in New Issue
Block a user