Compare commits
6 Commits
30f5cee4a1
...
refactor/c
| Author | SHA1 | Date | |
|---|---|---|---|
| 25bc00ac51 | |||
| cfbf4ad2b4 | |||
| a48e601ed3 | |||
| 4d12b615c8 | |||
| 8482917e88 | |||
| f64cbfec31 |
24
README.md
24
README.md
@@ -1,6 +1,6 @@
|
|||||||
# zPDF_package - PDF 转换前端
|
# zPDF_package - PDF 转换前端
|
||||||
|
|
||||||
上传 epub 文件转为 PDF,显示转换进度,完成后下载。复用 [zTools2](https://git.zikai.wang/zikai/zTools2) 后端 API(`/api/pdf/*`)。便携式组件,可独立运行,亦可作为 [mainPage](https://git.zikai.wang/zikai/zMainPage) 的子模块经 iframe 集成展示。
|
上传 epub 文件转为 PDF,显示转换进度,完成后下载。复用 [zTools2](https://git.zikai.wang/zikai/zTools2) 后端 API(`/api/pdf/*`)。便携式组件,可独立运行,亦可作为 [mainPage](https://git.zikai.wang/zikai/zMainPage) 的子模块经构建期组件 import 集成展示(像 [timeTableFix](https://git.zikai.wang/zikai/timeTableFix))。
|
||||||
|
|
||||||
## 功能
|
## 功能
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ zPDF_package/
|
|||||||
├── .npmrc npmmirror 镜像加速
|
├── .npmrc npmmirror 镜像加速
|
||||||
├── src/
|
├── src/
|
||||||
│ ├── main.js 应用挂载(i18n + 全局样式)
|
│ ├── main.js 应用挂载(i18n + 全局样式)
|
||||||
│ ├── App.vue 便携式根组件(嵌入与独立两用)
|
│ ├── App.vue 便携式根组件(接受 :locale prop,独立/嵌入两用)
|
||||||
│ ├── i18n/ 独立 vue-i18n 实例 + 中英 locale
|
│ ├── i18n/ 独立 vue-i18n 实例 + 中英 locale
|
||||||
│ ├── composables/
|
│ ├── composables/
|
||||||
│ │ ├── usePdfApi.js API 客户端 + 进度轮询状态机(可单测)
|
│ │ ├── usePdfApi.js API 客户端 + 进度轮询状态机(可单测)
|
||||||
@@ -41,7 +41,7 @@ zPDF_package/
|
|||||||
| Node.js | ≥ 18 |
|
| Node.js | ≥ 18 |
|
||||||
| zTools2 | 后端 `/api/pdf/*`(同源调用,cookie 自动管理) |
|
| zTools2 | 后端 `/api/pdf/*`(同源调用,cookie 自动管理) |
|
||||||
|
|
||||||
> 前端纯静态,无运行期后端依赖;API 由 zTools2 提供。
|
> 前端纯静态,无运行期后端依赖;API 由 zTools2 提供。生产部署经 [zMainPage](https://git.zikai.wang/zikai/zMainPage) 作为子模块集成,由其 apache2 静态托管 + `/api` 反代到 zTools2(无独立 apache2 配置)。
|
||||||
|
|
||||||
## 如何使用
|
## 如何使用
|
||||||
|
|
||||||
@@ -51,25 +51,16 @@ git clone https://git.zikai.wang/zikai/zPDF_package.git
|
|||||||
cd zPDF_package
|
cd zPDF_package
|
||||||
npm install # 已配 npmmirror 镜像加速
|
npm install # 已配 npmmirror 镜像加速
|
||||||
|
|
||||||
npm run dev # 开发服务器 http://localhost:5173(需 zTools2 在同源或配置代理)
|
npm run dev # 开发服务器 http://localhost:5175(vite.config.js 已配 /api 代理到 zTools2:6867)
|
||||||
npm run build # 构建到 dist/
|
npm run build # 构建到 dist/
|
||||||
npm test # 单元测试(20 项)
|
npm test # 单元测试(20 项)
|
||||||
```
|
```
|
||||||
|
|
||||||
> 开发时 `/api/pdf/*` 需指向 zTools2。可配 Vite 代理,或直接由 zTools2 托管 `dist/`。
|
> 开发时 `/api/pdf/*` 由 vite 代理到本地 zTools2(127.0.0.1:6867);生产环境由 mainPage 同源反代到 zTools2,cookie 第一方生效。
|
||||||
|
|
||||||
## 部署到 zTools2
|
|
||||||
|
|
||||||
zTools2 的 `/pdf` 页面(`static/pdf.html` + `pdf.js`)为内置同款实现。如需用本项目的 Vue 构建产物替换:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd zPDF_package && npm run build
|
|
||||||
# 把 dist/ 部署到 zTools2 静态目录(如 static/pdf_app/),改 pdf.html 引用之
|
|
||||||
```
|
|
||||||
|
|
||||||
## 嵌入 mainPage
|
## 嵌入 mainPage
|
||||||
|
|
||||||
作为 [mainPage](https://git.zikai.wang/zikai/zMainPage) 的 git submodule(`third_party/zPDF_package`),mainPage 用 iframe 嵌入 zTools2 托管的 `/pdf` 页面(同源,cookie 第一方生效,无需 CORS)。详见 mainPage 的 `docs/submodule-zPDF_package.md`。
|
作为 [mainPage](https://git.zikai.wang/zikai/zMainPage) 的 git submodule,经构建期组件 import 集成(非 iframe)。详见 [`docs/integration.md`](./docs/integration.md)。
|
||||||
|
|
||||||
## 技术栈
|
## 技术栈
|
||||||
|
|
||||||
@@ -84,5 +75,6 @@ cd zPDF_package && npm run build
|
|||||||
|
|
||||||
## 了解更多
|
## 了解更多
|
||||||
|
|
||||||
- [嵌入 mainPage(iframe 同源集成)](../zMainPage/docs/submodule-zPDF_package.md)
|
- [嵌入 mainPage(构建期组件 import)](./docs/integration.md)
|
||||||
|
- [错误处理与日志约定](./docs/error-handling.md)
|
||||||
- 后端 API 与转换实现见 [zTools2](https://git.zikai.wang/zikai/zTools2) README
|
- 后端 API 与转换实现见 [zTools2](https://git.zikai.wang/zikai/zTools2) README
|
||||||
|
|||||||
21
docs/error-handling.md
Normal file
21
docs/error-handling.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# 错误处理与日志约定
|
||||||
|
|
||||||
|
zPDF_package 的错误处理分两层:API 客户端 fail-fast(抛错),UI 层捕获后既写控制台又给用户反馈。
|
||||||
|
|
||||||
|
## API 客户端(src/composables/usePdfApi.js)
|
||||||
|
|
||||||
|
- `load` / `getJob` / `remove`:fetch 不可用抛 `fetch unavailable`;非 2xx 抛 `HTTP <status>`。
|
||||||
|
- `uploadFile`:XHR 不可用抛 `XMLHttpRequest unavailable`;网络错误 reject `network error`;响应非 200 时解析 `detail` 字段,无则 `HTTP <status>`。
|
||||||
|
- `pollPending`:单个任务查询失败不中断整体轮询,但 `console.warn` 记录(避免持久故障完全不可见)。
|
||||||
|
- `downloadUrl`:纯函数,无副作用。
|
||||||
|
|
||||||
|
## UI 层(src/App.vue, src/components/)
|
||||||
|
|
||||||
|
- `refresh()` 失败:捕获写入 `loadError`,JobList 显示「加载失败」。
|
||||||
|
- `onDelete()` 失败:`console.error` 记录 + 写入 `loadError` 提示用户。
|
||||||
|
- `Uploader.handle()` 失败:捕获写入 `errMsg` 显示。
|
||||||
|
- `validateFile` 返回 i18n key(非抛错),由 `onUpload` 翻译后抛 `Error(t(key))` 交给 Uploader 显示。
|
||||||
|
|
||||||
|
## 控制台前缀
|
||||||
|
|
||||||
|
所有 `[zPDF]` 前缀的日志来自本子项目,便于嵌入宿主时与宿主日志区分。
|
||||||
19
docs/integration.md
Normal file
19
docs/integration.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# 嵌入 mainPage
|
||||||
|
|
||||||
|
zPDF_package 作为 [mainPage](https://git.zikai.wang/zikai/zMainPage) 的 git submodule(`third_party/zPDF_package`),经构建期组件 import 集成(非 iframe,与 timeTableFix 同范式)。
|
||||||
|
|
||||||
|
## 集成方式
|
||||||
|
|
||||||
|
- mainPage 侧 `src/modules/pdf/Pdf.vue` 套 `PageShell` 并透传 `:locale`。
|
||||||
|
- 构建期直接 `import` 本项目 `src/App.vue`,共享 mainPage 的构建与 KeepAlive 缓存。
|
||||||
|
- 组件内 `usePdfApi.js` 同源 fetch `/api/pdf/*`,httpOnly cookie(`zk_pdf`)经反代第一方自动携带,无需 CORS。
|
||||||
|
|
||||||
|
## 便携式设计(无 host 依赖)
|
||||||
|
|
||||||
|
组件自身保持「无 host 依赖」:
|
||||||
|
|
||||||
|
- 不持 `defineOptions({ name })`(避免与宿主组件名冲突)。
|
||||||
|
- 不引 mainPage 的 i18n,仅靠 `:locale` prop 驱动自有 i18n 实例。
|
||||||
|
- `useLocale` 直接读本子项目模块级 `i18n.global`,而非 `useI18n()`(后者嵌入时会解析到宿主实例,文案回退成 key)。
|
||||||
|
|
||||||
|
详见 mainPage 的 `docs/submodule-zPDF_package.md`。
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "PDF 转换前端 -- 上传 epub 转 PDF、显示进度、下载;复用 zTools2 后端 API。便携式组件,可独立运行或经 mainPage iframe 集成。",
|
"description": "PDF 转换前端 -- 上传 epub 转 PDF、显示进度、下载;复用 zTools2 后端 API。便携式组件,可独立运行或经 mainPage 构建期组件 import 集成。",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onUnmounted } from 'vue'
|
import { ref, watch, onUnmounted } from 'vue'
|
||||||
import { usePdfApi, validateFile, uploadFile } from './composables/usePdfApi.js'
|
import { usePdfApi, validateFile, uploadFile } from './composables/usePdfApi.js'
|
||||||
import { useLocale } from './composables/useLocale.js'
|
import { useLocale } from './composables/useLocale.js'
|
||||||
import { loadLocaleAsync } from './i18n/index.js'
|
import { loadLocaleAsync } from './i18n/index.js'
|
||||||
@@ -14,7 +14,6 @@ const { t } = useLocale()
|
|||||||
const api = usePdfApi()
|
const api = usePdfApi()
|
||||||
|
|
||||||
// 跟随父项目语言开关切换本子项目 i18n 实例的 locale
|
// 跟随父项目语言开关切换本子项目 i18n 实例的 locale
|
||||||
import { watch } from 'vue'
|
|
||||||
watch(
|
watch(
|
||||||
() => props.locale,
|
() => props.locale,
|
||||||
(val) => {
|
(val) => {
|
||||||
@@ -67,8 +66,13 @@ async function onUpload(file, onProgress) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function onDelete(job) {
|
async function onDelete(job) {
|
||||||
|
try {
|
||||||
await api.remove(job.id)
|
await api.remove(job.id)
|
||||||
await refresh()
|
await refresh()
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[zPDF] 删除任务失败', e)
|
||||||
|
loadError.value = String(e.message || e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { useLocale } from '../composables/useLocale.js'
|
import { useLocale } from '../composables/useLocale.js'
|
||||||
import { usePdfApi, fmtBytes } from '../composables/usePdfApi.js'
|
import { downloadUrl, fmtBytes } from '../composables/usePdfApi.js'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
items: { type: Array, default: () => [] },
|
items: { type: Array, default: () => [] },
|
||||||
@@ -11,7 +11,6 @@ const props = defineProps({
|
|||||||
|
|
||||||
const emit = defineEmits(['refresh', 'delete'])
|
const emit = defineEmits(['refresh', 'delete'])
|
||||||
const { t } = useLocale()
|
const { t } = useLocale()
|
||||||
const api = usePdfApi()
|
|
||||||
|
|
||||||
function statusText(job) {
|
function statusText(job) {
|
||||||
if (job.status === 'done') return t('job.statusDone')
|
if (job.status === 'done') return t('job.statusDone')
|
||||||
@@ -34,8 +33,7 @@ function fmtTime(s) {
|
|||||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`
|
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onDelete(job) {
|
function onDelete(job) {
|
||||||
if (!confirm(t('job.deleteConfirm', { name: job.source_filename }))) return
|
|
||||||
emit('delete', job)
|
emit('delete', job)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -67,7 +65,7 @@ async function onDelete(job) {
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="j in items" :key="j.id">
|
<tr v-for="j in items" :key="j.id">
|
||||||
<td class="col-name">{{ j.source_filename }}</td>
|
<td class="col-name">{{ j.source_filename }}</td>
|
||||||
<td class="col-size zpdf-mono">{{ fmtBytes(j.source_size) }}</td>
|
<td class="col-size zpdf-mono" :data-label="t('job.size')">{{ fmtBytes(j.source_size) }}</td>
|
||||||
<td class="col-status">
|
<td class="col-status">
|
||||||
<span v-if="j.status === 'done' || j.status === 'failed'" :class="statusClass(j)">
|
<span v-if="j.status === 'done' || j.status === 'failed'" :class="statusClass(j)">
|
||||||
{{ statusText(j) }}
|
{{ statusText(j) }}
|
||||||
@@ -79,12 +77,12 @@ async function onDelete(job) {
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="col-time zpdf-muted">{{ fmtTime(j.created_at) }}</td>
|
<td class="col-time zpdf-muted" :data-label="t('job.created')">{{ fmtTime(j.created_at) }}</td>
|
||||||
<td class="col-act">
|
<td class="col-act">
|
||||||
<a
|
<a
|
||||||
v-if="j.status === 'done'"
|
v-if="j.status === 'done'"
|
||||||
class="zpdf-btn zpdf-btn--primary zpdf-btn--sm"
|
class="zpdf-btn zpdf-btn--primary zpdf-btn--sm"
|
||||||
:href="api.downloadUrl(j.id)"
|
:href="downloadUrl(j.id)"
|
||||||
download
|
download
|
||||||
>{{ t('job.download') }}</a>
|
>{{ t('job.download') }}</a>
|
||||||
<button class="zpdf-btn zpdf-btn--danger zpdf-btn--sm" @click="onDelete(j)">
|
<button class="zpdf-btn zpdf-btn--danger zpdf-btn--sm" @click="onDelete(j)">
|
||||||
@@ -158,8 +156,27 @@ async function onDelete(job) {
|
|||||||
}
|
}
|
||||||
.zpdf-bar--sm { display: inline-block; width: 90px; vertical-align: middle; height: 6px; }
|
.zpdf-bar--sm { display: inline-block; width: 90px; vertical-align: middle; height: 6px; }
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 768px) {
|
||||||
.zpdf-table th, .zpdf-table td { padding: 8px 6px; }
|
/* 窄屏:表格转卡片式,避免横向滚动。每行展示文件名(主)+ 状态(次)+ 操作 */
|
||||||
.zpdf-table .col-time { font-size: 0.78em; }
|
.zpdf-table, .zpdf-table thead, .zpdf-table tbody, .zpdf-table tr, .zpdf-table th, .zpdf-table td {
|
||||||
|
display: block; width: 100%;
|
||||||
|
}
|
||||||
|
.zpdf-table thead { display: none; } /* 卡片模式无需表头 */
|
||||||
|
.zpdf-table tr {
|
||||||
|
padding: 12px; border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.zpdf-table tbody tr:hover td { background: transparent; }
|
||||||
|
.zpdf-table td {
|
||||||
|
padding: 4px 0; border-bottom: none; text-align: left;
|
||||||
|
}
|
||||||
|
/* 文件名为卡片标题,其余为次要行 */
|
||||||
|
.zpdf-table .col-name { font-size: 1em; margin-bottom: 4px; }
|
||||||
|
.zpdf-table .col-size::before { content: attr(data-label) " "; color: var(--text-tertiary); font-size: 0.82em; }
|
||||||
|
.zpdf-table .col-time::before { content: attr(data-label) " "; color: var(--text-tertiary); font-size: 0.82em; }
|
||||||
|
.zpdf-table .col-status { margin: 6px 0; }
|
||||||
|
.zpdf-table .col-act { margin-top: 8px; text-align: left; }
|
||||||
|
.zpdf-table .col-act .zpdf-btn { margin-left: 0; margin-right: 8px; }
|
||||||
|
/* 移动端触控目标加大 */
|
||||||
|
.zpdf-btn--sm { padding: 8px 16px; font-size: 0.9em; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -128,4 +128,12 @@ async function handle(file) {
|
|||||||
margin-top: 10px; padding: 8px 12px; border-radius: var(--radius-sm);
|
margin-top: 10px; padding: 8px 12px; border-radius: var(--radius-sm);
|
||||||
background: var(--danger-weak); color: var(--danger); font-size: 0.88em;
|
background: var(--danger-weak); color: var(--danger); font-size: 0.88em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
/* 移动端无拖拽语义,缩小拖拽区节省首屏;触控目标加大 */
|
||||||
|
.zpdf-dropzone { padding: 24px 16px; min-height: 120px; gap: 8px; }
|
||||||
|
.zpdf-dropzone__hint { font-size: 0.9em; }
|
||||||
|
.zpdf-btn { padding: 10px 22px; font-size: 1em; }
|
||||||
|
.zpdf-upload-progress__name { font-size: 0.85em; }
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { computed } from 'vue'
|
|
||||||
import i18n from '../i18n/index.js'
|
import i18n from '../i18n/index.js'
|
||||||
|
|
||||||
// ★ 子项目自有的语言辅助 composable。
|
// ★ 子项目自有的语言辅助 composable。
|
||||||
@@ -8,7 +7,5 @@ import i18n from '../i18n/index.js'
|
|||||||
// 改为直接引用本子项目实例的 .global,无论是否 app.use() 都拿到本子项目的 t。
|
// 改为直接引用本子项目实例的 .global,无论是否 app.use() 都拿到本子项目的 t。
|
||||||
export function useLocale() {
|
export function useLocale() {
|
||||||
const { t, locale } = i18n.global
|
const { t, locale } = i18n.global
|
||||||
const isZh = computed(() => locale.value === 'zh-CN')
|
return { t, locale }
|
||||||
const pick = (obj) => (isZh.value ? obj.zh : obj.en)
|
|
||||||
return { t, locale, isZh, pick }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ export function isTerminal(job) {
|
|||||||
return !!job && (job.status === 'done' || job.status === 'failed')
|
return !!job && (job.status === 'done' || job.status === 'failed')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 产物下载 URL(纯函数,供组件直接 import)。 */
|
||||||
|
export function downloadUrl(id) {
|
||||||
|
return `/api/pdf/jobs/${id}/download`
|
||||||
|
}
|
||||||
|
|
||||||
/** 格式化字节为人类可读(供组件与测试复用)。 */
|
/** 格式化字节为人类可读(供组件与测试复用)。 */
|
||||||
export function fmtBytes(n) {
|
export function fmtBytes(n) {
|
||||||
const x = Number(n)
|
const x = Number(n)
|
||||||
@@ -70,10 +75,6 @@ export function createPdfApi(opts = {}) {
|
|||||||
return res.json()
|
return res.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadUrl(id) {
|
|
||||||
return `/api/pdf/jobs/${id}/download`
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 轮询未完成任务直至全部终态。每次拉取后调 onUpdate(freshItems),全部终态后调 onDone()。
|
* 轮询未完成任务直至全部终态。每次拉取后调 onUpdate(freshItems),全部终态后调 onDone()。
|
||||||
* 返回 stop 函数,可提前停止(如组件卸载)。
|
* 返回 stop 函数,可提前停止(如组件卸载)。
|
||||||
@@ -95,8 +96,9 @@ export function createPdfApi(opts = {}) {
|
|||||||
for (const id of pendingIds) {
|
for (const id of pendingIds) {
|
||||||
try {
|
try {
|
||||||
fresh.push(await getJob(id))
|
fresh.push(await getJob(id))
|
||||||
} catch {
|
} catch (e) {
|
||||||
// 单个查询失败不中断整体轮询
|
// 单个查询失败不中断整体轮询, 但记录便于排查持久故障
|
||||||
|
console.warn('[zPDF] 轮询单任务失败', id, e)
|
||||||
fresh.push(items.find((j) => j.id === id))
|
fresh.push(items.find((j) => j.id === id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,7 +122,7 @@ export function createPdfApi(opts = {}) {
|
|||||||
return stop
|
return stop
|
||||||
}
|
}
|
||||||
|
|
||||||
return { load, getJob, remove, downloadUrl, pollPending }
|
return { load, remove, pollPending }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -156,6 +158,7 @@ export function uploadFile(file, onProgress, opts = {}) {
|
|||||||
try {
|
try {
|
||||||
resolve(JSON.parse(xhr.responseText))
|
resolve(JSON.parse(xhr.responseText))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.error('[zPDF] 解析上传响应失败', e)
|
||||||
reject(new Error('bad response'))
|
reject(new Error('bad response'))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -9,11 +9,7 @@ export default {
|
|||||||
pick: 'click to choose',
|
pick: 'click to choose',
|
||||||
limit: 'Only epub, up to 250MB',
|
limit: 'Only epub, up to 250MB',
|
||||||
requireEpub: 'Only epub files are supported',
|
requireEpub: 'Only epub files are supported',
|
||||||
oversize: 'File exceeds the 250MB limit',
|
oversize: 'File exceeds the 250MB limit'
|
||||||
uploading: 'Uploading…',
|
|
||||||
submitted: 'Submitted, converting…',
|
|
||||||
failed: 'Upload failed',
|
|
||||||
networkError: 'Network error'
|
|
||||||
},
|
},
|
||||||
list: {
|
list: {
|
||||||
count: '{total} task(s)',
|
count: '{total} task(s)',
|
||||||
@@ -32,10 +28,7 @@ export default {
|
|||||||
statusConverting: 'Converting',
|
statusConverting: 'Converting',
|
||||||
statusPending: 'Queued',
|
statusPending: 'Queued',
|
||||||
download: 'Download',
|
download: 'Download',
|
||||||
delete: 'Delete',
|
delete: 'Delete'
|
||||||
deleteConfirm: 'Delete "{name}"?\nIt will no longer be shown to you (admin can still see it; only admin hard-delete truly removes it).',
|
|
||||||
deleted: 'Deleted',
|
|
||||||
deleteFailed: 'Delete failed: {msg}'
|
|
||||||
},
|
},
|
||||||
refresh: 'Refresh'
|
refresh: 'Refresh'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,7 @@ export default {
|
|||||||
pick: '点击选择文件',
|
pick: '点击选择文件',
|
||||||
limit: '仅支持 epub,最大 250MB',
|
limit: '仅支持 epub,最大 250MB',
|
||||||
requireEpub: '仅支持 epub 文件',
|
requireEpub: '仅支持 epub 文件',
|
||||||
oversize: '文件超过 250MB 上限',
|
oversize: '文件超过 250MB 上限'
|
||||||
uploading: '上传中…',
|
|
||||||
submitted: '已提交,转换中…',
|
|
||||||
failed: '上传失败',
|
|
||||||
networkError: '网络错误'
|
|
||||||
},
|
},
|
||||||
list: {
|
list: {
|
||||||
count: '共 {total} 个任务',
|
count: '共 {total} 个任务',
|
||||||
@@ -31,10 +27,7 @@ export default {
|
|||||||
statusConverting: '转换中',
|
statusConverting: '转换中',
|
||||||
statusPending: '排队中',
|
statusPending: '排队中',
|
||||||
download: '下载',
|
download: '下载',
|
||||||
delete: '删除',
|
delete: '删除'
|
||||||
deleteConfirm: '确定删除「{name}」?\n删除后将不再显示(管理员仍可见,需管理员彻底删除才会清除)。',
|
|
||||||
deleted: '已删除',
|
|
||||||
deleteFailed: '删除失败:{msg}'
|
|
||||||
},
|
},
|
||||||
refresh: '刷新'
|
refresh: '刷新'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
fmtBytes,
|
fmtBytes,
|
||||||
validateFile,
|
validateFile,
|
||||||
uploadFile,
|
uploadFile,
|
||||||
|
downloadUrl,
|
||||||
} from '../src/composables/usePdfApi.js'
|
} from '../src/composables/usePdfApi.js'
|
||||||
|
|
||||||
describe('isTerminal', () => {
|
describe('isTerminal', () => {
|
||||||
@@ -94,8 +95,7 @@ describe('createPdfApi', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('downloadUrl 生成路径', () => {
|
it('downloadUrl 生成路径', () => {
|
||||||
const api = createPdfApi({ fetch: vi.fn() })
|
expect(downloadUrl(3)).toBe('/api/pdf/jobs/3/download')
|
||||||
expect(api.downloadUrl(3)).toBe('/api/pdf/jobs/3/download')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('pollPending 无未完成任务立即 onDone', () => {
|
it('pollPending 无未完成任务立即 onDone', () => {
|
||||||
|
|||||||
@@ -1,12 +1,23 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
// 便携式构建:base './' 使产物可被任意路径托管(含 zTools2 /static 与 mainPage iframe)。
|
// 便携式构建:base './' 使产物可被任意路径托管(含 zTools2 /api/static 与 mainPage 组件 import)。
|
||||||
// preview 开放 f.zikai.wang 便于线上预览校验。
|
// preview 开放 f.zikai.wang 便于线上预览校验。
|
||||||
|
// dev 独立运行时,zTools2 所有入口(页面/静态/探针/WS/API)统一在 /api/ 下,
|
||||||
|
// 一条 /api 代理即可覆盖 /api/pdf/* 与 /api/admin/pdf/*,保持同源调用语义。
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
base: './',
|
base: './',
|
||||||
preview: {
|
preview: {
|
||||||
allowedHosts: ['f.zikai.wang'],
|
allowedHosts: ['f.zikai.wang'],
|
||||||
},
|
},
|
||||||
|
server: {
|
||||||
|
port: 5175,
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://127.0.0.1:6867',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user