feat: 新增 PDF 转换服务(epub->pdf,cookie 用户隔离,软删/硬删两级删除)
纯 Python 转换(ebooklib 解析 epub + weasyprint 渲染 HTML/CSS 为 PDF), 无需 calibre/xvfb 系统依赖。复用 UploadedFile 存储(原始文件与产物 PDF)。 - model/dao/schema:PdfJob 记录转换任务(owner_cookie/source/output/进度/状态) - pdf_converter:epub->PDF 转换器(按 spine 顺序拼 HTML,OPF 目录解析相对资源) - pdf_service:上传落盘 + asyncio 后台转换(独立 Session)+ 进度追踪 + 两级删除 · 用户软删(user_deleted,管理页仍可见)· 管理员硬删(真正删磁盘+记录) - pdf_controller:/api/pdf/*(用户,cookie)+ /api/admin/pdf/*(Basic Auth) - /pdf、/pdf-admin 页面(原生 JS,复用 common.css/js) - 配置 pdf 段(max_size 250MB、转换超时、cookie) - pytest 7 项(SQLite→MySQL 隔离测试库):上传/转换/下载/软删/硬删/超限/越权
This commit is contained in:
40
static/pdf.css
Normal file
40
static/pdf.css
Normal file
@@ -0,0 +1,40 @@
|
||||
/* PDF 转换页专属样式:上传区、进度条、任务表格。复用 common.css 的 .btn/.tag/table 等。 */
|
||||
.uploader { margin: 0 0 1.6em; }
|
||||
.dropzone {
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
gap: 0.6em; padding: 2.4em 1.2em; min-height: 160px;
|
||||
border: 2px dashed var(--border); border-radius: var(--radius);
|
||||
background: var(--surface); text-align: center; transition: border-color 0.15s, background 0.15s;
|
||||
}
|
||||
.dropzone.is-drag { border-color: var(--primary); background: var(--primary-soft); }
|
||||
.dropzone__hint { color: var(--text-dim); font-size: 0.95em; }
|
||||
.dropzone__limit { color: var(--text-dim); font-size: 0.8em; }
|
||||
.dropzone .btn.primary { padding: 0.55em 1.4em; }
|
||||
|
||||
.upload-progress { margin-top: 0.8em; }
|
||||
.upload-progress.hidden { display: none; }
|
||||
.upload-progress__name { font-size: 0.88em; color: var(--text); margin-bottom: 0.3em; word-break: break-all; }
|
||||
|
||||
.bar { height: 8px; background: var(--surface-2); border-radius: 6px; overflow: hidden; border: 1px solid var(--border); }
|
||||
.bar__fill { height: 100%; width: 0; background: var(--primary); border-radius: 6px; transition: width 0.2s; }
|
||||
.bar--sm { display: inline-block; width: 90px; vertical-align: middle; height: 6px; }
|
||||
|
||||
.jobs-table th.col-size { width: 8em; }
|
||||
.jobs-table th.col-status { width: 12em; }
|
||||
.jobs-table th.col-time { width: 11em; }
|
||||
.jobs-table th.col-act { width: 9em; text-align: right; }
|
||||
.jobs-table td.col-act { text-align: right; white-space: nowrap; }
|
||||
.jobs-table td.col-name { font-weight: 600; word-break: break-all; }
|
||||
.jobs-table td.col-act .btn { padding: 0.3em 0.8em; font-size: 0.85em; margin-left: 0.3em; }
|
||||
|
||||
.prog { display: inline-flex; align-items: center; gap: 0.5em; font-size: 0.82em; }
|
||||
.prog__label { color: var(--text-dim); white-space: nowrap; }
|
||||
|
||||
.hidden { display: none; }
|
||||
.skel, .empty { padding: 1.6em; text-align: center; color: var(--text-dim); font-size: 0.9em; }
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.jobs-table th, .jobs-table td { padding: 0.5em 0.4em; }
|
||||
.jobs-table th.col-time, .jobs-table td.col-time { font-size: 0.78em; }
|
||||
.dropzone { padding: 1.8em 0.8em; min-height: 130px; }
|
||||
}
|
||||
Reference in New Issue
Block a user