feat: 新增 PDF 转换页签(iframe 嵌入 zTools2 /pdf,zPDF_package 子模块)
mainPage 用 iframe 嵌入 zTools2 同源托管的 /pdf 页面(PDF 转换:上传 epub、 进度显示、下载、用户软删/管理员硬删)。同源使 cookie 第一方生效,无需 CORS。 - src/modules/pdf/:页签模块(requiresAlive 存活校验 + iframe + 降级提示) - src/config/app.config.js:新增 pdf 服务配置(baseUrl/healthPath/pagePath) - i18n:tabs.pdf 中英文案 + pdf.openExternal - third_party/zPDF_package:git submodule(源码管理,iframe 集成非构建期编译) - docs/submodule-zPDF_package.md:集成/升级/移除文档 - README:更新结构、子模块列表、文档链接
This commit is contained in:
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -4,3 +4,6 @@
|
||||
[submodule "third_party/z449"]
|
||||
path = third_party/z449
|
||||
url = https://git.zikai.wang/zikai/z449.git
|
||||
[submodule "third_party/zPDF_package"]
|
||||
path = third_party/zPDF_package
|
||||
url = https://git.zikai.wang/zikai/zPDF_package.git
|
||||
|
||||
@@ -17,7 +17,8 @@ mainPage/
|
||||
├── public/favicon.svg
|
||||
├── third_party/
|
||||
│ ├── timeTableFix/ # ★ git submodule:ICS 日程整理器
|
||||
│ └── z449/ # ★ git submodule:移动游戏项目展示页
|
||||
│ ├── z449/ # ★ git submodule:移动游戏项目展示页
|
||||
│ └── zPDF_package/ # ★ git submodule:PDF 转换前端(iframe 嵌入 zTools2)
|
||||
└── src/
|
||||
├── main.js # 应用入口(挂载 i18n / router / 全局样式)
|
||||
├── App.vue # 根布局:顶栏 + 标签导航 + <KeepAlive router-view> + 页脚
|
||||
@@ -29,6 +30,7 @@ mainPage/
|
||||
│ ├── mobile-game/ # 移动游戏项目页签(包装 z449 子模块)
|
||||
│ ├── whiteboard/ # 共享记事本页签(iframe 嵌入 + 存活校验)
|
||||
│ ├── timetable/ # 日程整理页签(包装 timeTableFix 子模块)
|
||||
│ ├── pdf/ # PDF 转换页签(iframe 嵌入 zTools2 /pdf + 存活校验)
|
||||
│ └── calendar/ # 隐藏页(仅手动访问 /cqy)
|
||||
├── components/ # 共享组件(TabNav / LanguageSwitcher / ui 基元含 PageShell)
|
||||
├── composables/ # useProjects / useLiveness / useI18nLazy / useLocale
|
||||
@@ -98,4 +100,5 @@ 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)
|
||||
- [README 勘误记录](./docs/readme-fixes.md)
|
||||
|
||||
62
docs/submodule-zPDF_package.md
Normal file
62
docs/submodule-zPDF_package.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# 子项目 zPDF_package(git submodule)
|
||||
|
||||
zPDF_package(PDF 转换前端)作为 **git submodule** 被 mainPage 引入,位于 `third_party/zPDF_package`,独立仓库为 https://git.zikai.wang/zikai/zPDF_package.git 。
|
||||
|
||||
## 集成方式
|
||||
|
||||
mainPage 侧的 `src/modules/pdf/Pdf.vue` 用 **iframe** 嵌入 zTools2 托管的 `/pdf` 页面(`https://f.zikai.wang/pdf`),套 `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 集成,非构建期组件集成)。
|
||||
|
||||
## 克隆(含子模块)
|
||||
|
||||
```bash
|
||||
git clone --recursive https://git.zikai.wang/zikai/zMainPage.git mainPage
|
||||
```
|
||||
|
||||
若已 clone 但未带子模块:
|
||||
|
||||
```bash
|
||||
cd mainPage
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
## 升级子模块到最新版本
|
||||
|
||||
```bash
|
||||
cd mainPage
|
||||
git submodule update --remote third_party/zPDF_package # 拉取最新
|
||||
cd third_party/zPDF_package && git checkout master # 固定到目标分支/commit
|
||||
cd ../..
|
||||
git add third_party/zPDF_package # 更新 gitlink 指针
|
||||
git commit -m "chore: 升级 zPDF_package 子模块"
|
||||
npm run build # 重新构建 mainPage
|
||||
git push # 推送 mainPage(gitlink)
|
||||
```
|
||||
|
||||
> 推送顺序:先确保子模块的 commit 已推到 zPDF_package.git,再推 mainPage 的 gitlink。
|
||||
|
||||
## 移除子模块集成(仅移除 mainPage 页签,不影响 zPDF_package 本身)
|
||||
|
||||
```bash
|
||||
cd mainPage
|
||||
git submodule deinit -f third_party/zPDF_package
|
||||
git rm third_party/zPDF_package
|
||||
git commit -m "chore: 移除 zPDF_package 子模块集成"
|
||||
```
|
||||
|
||||
同时删 `src/modules/pdf/` 文件夹即移除页签。zPDF_package 独立仓库不受影响。
|
||||
|
||||
## 与其他子模块的差异
|
||||
|
||||
| 维度 | timeTableFix / z449 | zPDF_package |
|
||||
|------|---------------------|--------------|
|
||||
| 集成方式 | 构建期组件 import(共享构建/KeepAlive) | iframe 嵌入 zTools2 托管页 |
|
||||
| 后端依赖 | 无(纯前端) | 有(zTools2 `/api/pdf/*`) |
|
||||
| 同源/CORS | 不涉及 | 同源(f.zikai.wang),无需 CORS |
|
||||
| cookie | 不涉及 | 第一方 httpOnly cookie 自然生效 |
|
||||
| mainPage 是否编译子模块 | 是(import .vue) | 否(iframe) |
|
||||
|
||||
> zPDF_package 仍作为 submodule 管理源码,便于独立开发/测试/升级;其构建产物部署到 zTools2,mainPage 经 iframe 引用 zTools2 的 `/pdf`。
|
||||
@@ -15,6 +15,17 @@ export const appConfig = {
|
||||
defaultBoardId: 'share',
|
||||
// 探测超时(毫秒)
|
||||
healthTimeoutMs: 4000
|
||||
},
|
||||
// PDF 转换服务(zTools2 托管的 /pdf 页面,iframe 同源嵌入)
|
||||
pdf: {
|
||||
// 服务根地址(含协议与域名)
|
||||
baseUrl: 'https://f.zikai.wang',
|
||||
// 存活探针路径;周期探测,挂掉则隐藏页签并卸载组件
|
||||
healthPath: '/health',
|
||||
// PDF 转换页面路径
|
||||
pagePath: '/pdf',
|
||||
// 探测超时(毫秒)
|
||||
healthTimeoutMs: 4000
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,12 +13,16 @@ export default {
|
||||
mobileGame: 'Mobile Game Project',
|
||||
calendar: 'CQY Timetable',
|
||||
whiteboard: 'Shared Notepad',
|
||||
timetable: 'Schedule Organizer'
|
||||
timetable: 'Schedule Organizer',
|
||||
pdf: 'PDF Converter'
|
||||
},
|
||||
whiteboard: {
|
||||
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.
|
||||
|
||||
@@ -12,12 +12,16 @@ export default {
|
||||
mobileGame: '移动游戏项目',
|
||||
calendar: 'cqy课程表',
|
||||
whiteboard: '共享记事本',
|
||||
timetable: '日程整理'
|
||||
timetable: '日程整理',
|
||||
pdf: 'PDF 转换'
|
||||
},
|
||||
whiteboard: {
|
||||
openExternal: '在新标签页打开',
|
||||
boardId: '白板 ID',
|
||||
switch: '切换'
|
||||
},
|
||||
pdf: {
|
||||
openExternal: '在新标签页打开'
|
||||
}
|
||||
// 注:移动游戏项目(mobile-game)的文案由子模块 z449 自有 i18n 提供,
|
||||
// mainPage 仅保留 tabs.mobileGame 页签标题。切换语言时经 :locale prop 同步给 z449。
|
||||
|
||||
140
src/modules/pdf/Pdf.vue
Normal file
140
src/modules/pdf/Pdf.vue
Normal file
@@ -0,0 +1,140 @@
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
// 组件名与模块 id 一致,供 <KeepAlive :include> 按 id 精确匹配;
|
||||
// PDF 服务后端失活时把它从 include 移除即可卸载组件
|
||||
defineOptions({ name: 'pdf' })
|
||||
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 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')
|
||||
}
|
||||
</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>
|
||||
</PageShell>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.pdf-frame {
|
||||
position: relative;
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow);
|
||||
border: 1px solid var(--border);
|
||||
width: 100%;
|
||||
/* 转换页以上传与列表为主,给足高度 */
|
||||
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 {
|
||||
height: 72vh;
|
||||
min-height: 400px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
15
src/modules/pdf/index.js
Normal file
15
src/modules/pdf/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
// 模块元数据 -- PDF 转换页签
|
||||
// requiresAlive 指向存活探针 URL:启动时探测,zTools2 不可达则隐藏该页签。
|
||||
// 页面用 iframe 嵌入 zTools2 托管的 /pdf(同源,cookie 第一方生效,无需 CORS)。
|
||||
// 删除本目录即可移除此页签,其余模块不受影响。
|
||||
import { healthUrl } from '../../config/app.config.js'
|
||||
|
||||
export default {
|
||||
id: 'pdf',
|
||||
tabKey: 'tabs.pdf',
|
||||
order: 4,
|
||||
// 存活探针 URL;useProjects 启动时探测一次,挂掉则不显示页签
|
||||
requiresAlive: healthUrl('pdf'),
|
||||
// 懒加载组件 -> 独立 chunk
|
||||
component: () => import('./Pdf.vue')
|
||||
}
|
||||
1
third_party/zPDF_package
vendored
Submodule
1
third_party/zPDF_package
vendored
Submodule
Submodule third_party/zPDF_package added at 30f5cee4a1
Reference in New Issue
Block a user