소스 검색

按钮功能完成

sequoia00 1 개월 전
부모
커밋
2fb49274e1
2개의 변경된 파일179개의 추가작업 그리고 122개의 파일을 삭제
  1. 30 1
      Readme.md
  2. 149 121
      static/web/viewer.html

+ 30 - 1
Readme.md

@@ -1 +1,30 @@
-### 在线pdf阅读工具
+### 在线 PDF 阅读工具
+
+本项目是一个基于 PDF.js 的在线阅读工具,支持上传、浏览和管理 PDF 文档,并集成了阅读控制栏、夜间模式和本地配置记忆等能力。
+
+#### 功能
+
+- 在线打开和阅读 PDF 文件
+- 上传 PDF 并按文件名保存
+- 查看已上传文档目录
+- 翻页、缩放和文档浏览
+- 阅读控制栏快捷操作
+- 夜间阅读模式
+- 阅读栏尺寸切换
+- 用户登录、退出和管理员入口
+
+#### 特点
+
+- 基于 PDF.js,兼容性好,渲染稳定
+- 自定义阅读控制栏,适合阅读场景
+- 支持控制栏折叠,节省屏幕空间
+- 支持夜间模式,减少长时间阅读疲劳
+- 本地保存常用设置,刷新后可恢复
+- 适配桌面和移动端布局
+
+#### 目录
+
+- `main_server.py`:后端服务入口
+- `static/web/`:前端阅读器页面和 PDF.js 资源
+- `init_db.py`:数据库初始化脚本
+- `reading_progress.json`:阅读进度数据

+ 149 - 121
static/web/viewer.html

@@ -107,26 +107,36 @@
             color: #f9fafb;
         }
 
-        :root {
-            --reader-controls-size: 34px;
-            --reader-controls-gap: 5px;
-            --reader-controls-radius: 12px;
-            --reader-controls-padding: 6px;
-            --reader-controls-surface: rgba(255, 255, 255, 0.96);
-            --reader-controls-border: rgba(15, 23, 42, 0.08);
-            --reader-controls-icon: 14px;
-            --reader-controls-font: 9px;
-            --reader-controls-label: none;
-        }
-
-        body.reader-size-medium {
-            --reader-controls-size: 40px;
-            --reader-controls-gap: 6px;
-            --reader-controls-radius: 13px;
-            --reader-controls-padding: 7px;
-            --reader-controls-icon: 16px;
-            --reader-controls-font: 9px;
-        }
+        :root {
+            --reader-controls-size: 32px;
+            --reader-controls-gap: 5px;
+            --reader-controls-radius: 12px;
+            --reader-controls-padding: 6px;
+            --reader-controls-surface: rgba(255, 255, 255, 0.96);
+            --reader-controls-border: rgba(15, 23, 42, 0.08);
+            --reader-controls-icon: 14px;
+            --reader-controls-font: 9px;
+            --reader-controls-label: none;
+        }
+
+        body.reader-size-xsmall {
+            --reader-controls-size: 24px;
+            --reader-controls-gap: 4px;
+            --reader-controls-padding: 4px;
+            --reader-controls-radius: 9px;
+            --reader-controls-icon: 12px;
+            --reader-controls-font: 0;
+            --reader-controls-label: none;
+        }
+
+        body.reader-size-medium {
+            --reader-controls-size: 40px;
+            --reader-controls-gap: 6px;
+            --reader-controls-radius: 13px;
+            --reader-controls-padding: 7px;
+            --reader-controls-icon: 16px;
+            --reader-controls-font: 9px;
+        }
 
         body.reader-size-large {
             --reader-controls-size: 46px;
@@ -174,63 +184,71 @@
             z-index: 5;
             align-self: stretch;
             justify-content: flex-start;
-            align-items: stretch;
-            flex-shrink: 0;
-            width: calc(var(--reader-controls-size) + var(--reader-controls-padding) * 2 - 10px);
-            box-sizing: border-box;
-            height: calc(100vh - var(--toolbar-height));
-            min-height: 0;
-        }
+            align-items: stretch;
+            flex-shrink: 0;
+            width: calc(var(--reader-controls-size) + var(--reader-controls-padding) * 2);
+            box-sizing: border-box;
+            height: calc(100vh - var(--toolbar-height));
+            min-height: 0;
+        }
 
         #readerControls.collapsed {
             padding: 10px 8px;
             gap: 6px;
         }
 
-        body.reader-position-left #readerControls.collapsed,
-        body.reader-position-right #readerControls.collapsed {
-            width: calc(var(--reader-controls-size) + 10px);
-        }
+        body.reader-position-left #readerControls.collapsed,
+        body.reader-position-right #readerControls.collapsed {
+            width: calc(var(--reader-controls-size) + var(--reader-controls-padding) * 2);
+        }
 
         #readerControls.collapsed .reader-tool:not(.reader-tool-collapse),
         #readerControls.collapsed .reader-settings-launcher {
             display: none;
         }
 
-        .reader-tools {
-            display: flex;
-            gap: var(--reader-controls-gap);
-            flex: 0 0 auto;
-            min-width: 0;
-        }
-
-        .reader-tools {
-            flex-direction: column;
-            align-items: center;
-        }
-
-        .reader-tool,
-        .reader-settings-launcher {
-            width: var(--reader-controls-size);
-            min-width: var(--reader-controls-size);
-            height: var(--reader-controls-size);
-            border: 1px solid rgba(15, 23, 42, 0.12);
-            border-radius: calc(var(--reader-controls-radius) - 4px);
-            background: rgba(255, 255, 255, 0.88);
-            color: #0f172a;
+        .reader-tools {
+            display: flex;
+            gap: var(--reader-controls-gap);
+            flex: 0 0 auto;
+            min-width: 0;
+        }
+
+        .reader-tools {
+            flex-direction: column;
+            align-items: flex-start;
+        }
+
+        .reader-tool,
+        .reader-settings-launcher {
+            width: var(--reader-controls-size);
+            min-width: var(--reader-controls-size);
+            height: var(--reader-controls-size);
+            box-sizing: border-box;
+            border: 1px solid rgba(15, 23, 42, 0.12);
+            border-radius: calc(var(--reader-controls-radius) - 4px);
+            background: rgba(255, 255, 255, 0.88);
+            color: #0f172a;
             display: inline-flex;
             align-items: center;
             justify-content: center;
-            cursor: pointer;
-            transition: background-color 120ms ease, border-color 120ms ease, transform 120ms ease;
-            box-shadow: 0 2px 10px rgba(15, 23, 42, 0.05);
-            padding: 0;
-        }
-
-        .reader-tool:hover,
-        .reader-settings-launcher:hover {
-            background: #f8fafc;
-            border-color: rgba(15, 23, 42, 0.2);
+            cursor: pointer;
+            transition: background-color 120ms ease, border-color 120ms ease, transform 120ms ease;
+            box-shadow: 0 2px 10px rgba(15, 23, 42, 0.05);
+            padding: 0;
+        }
+
+        #readerControls > .reader-tool,
+        #readerControls > .reader-settings-launcher,
+        .reader-tools > .reader-tool {
+            flex: 0 0 auto;
+            justify-content: center;
+        }
+
+        .reader-tool:hover,
+        .reader-settings-launcher:hover {
+            background: #f8fafc;
+            border-color: rgba(15, 23, 42, 0.2);
         }
 
         .reader-tool:active,
@@ -277,29 +295,36 @@
             letter-spacing: 0.02em;
         }
 
-        #readerControlsSpacer {
-            display: none;
-            flex: 0 0 auto;
-            min-width: 8px;
-            min-height: 8px;
-        }
-
-        body.reader-position-right #readerControls {
-            order: 2;
-            border-right: none;
-            border-left: 1px solid var(--reader-controls-border);
+        #readerControlsSpacer {
+            display: none;
+            flex: 0 0 auto;
+            min-width: 8px;
+            min-height: 8px;
+        }
+
+        .reader-settings-anchor {
+            position: relative;
+            align-self: flex-start;
+            flex: 0 0 auto;
+        }
+
+        body.reader-position-right #readerControls {
+            order: 2;
+            border-right: none;
+            border-left: 1px solid var(--reader-controls-border);
             box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.7);
         }
 
-        .reader-settings-panel {
-            display: none;
-            position: absolute;
-            inset: auto auto 12px 12px;
-            width: min(320px, calc(100vw - 24px));
-            padding: 14px;
-            background: rgba(255, 255, 255, 0.98);
-            border: 1px solid rgba(15, 23, 42, 0.1);
-            border-radius: 18px;
+        .reader-settings-panel {
+            display: none;
+            position: absolute;
+            top: calc(100% + 8px);
+            left: 0;
+            width: min(320px, calc(100vw - 24px));
+            padding: 14px;
+            background: rgba(255, 255, 255, 0.98);
+            border: 1px solid rgba(15, 23, 42, 0.1);
+            border-radius: 18px;
             box-shadow: 0 18px 40px rgba(15, 23, 42, 0.16);
             z-index: 20;
         }
@@ -307,18 +332,18 @@
         #readerControls.settings-open .reader-settings-panel {
             display: block;
         }
-
-        body.reader-position-right .reader-settings-panel {
-            inset: auto 12px 12px auto;
-        }
-
-        body.reader-position-top .reader-settings-panel {
-            inset: 12px auto auto 12px;
-        }
-
-        body.reader-position-bottom .reader-settings-panel {
-            inset: auto auto 12px 12px;
-        }
+
+        body.reader-position-right .reader-settings-panel {
+            top: calc(100% + 8px);
+        }
+
+        body.reader-position-top .reader-settings-panel {
+            top: calc(100% + 8px);
+        }
+
+        body.reader-position-bottom .reader-settings-panel {
+            top: calc(100% + 8px);
+        }
 
         .reader-settings-title {
             font-size: 13px;
@@ -547,10 +572,10 @@
                 padding: 3px 5px;
             }
 
-            .reader-settings-panel {
-                width: min(280px, calc(100vw - 20px));
-            }
-        }
+            .reader-settings-panel {
+                width: min(280px, calc(100vw - 20px));
+            }
+        }
     </style>
 </head>
 
@@ -1133,23 +1158,26 @@
                                 <span class="reader-tool-label">选读</span>
                             </span>
                         </button>
-                    </div>
-                    <div id="readerControlsSpacer"></div>
-                    <button id="readerSettingsButton" class="reader-settings-launcher" type="button" title="设置" aria-label="设置" aria-expanded="false">
-                        <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3v3"/><path d="M12 18v3"/><path d="M3 12h3"/><path d="M18 12h3"/><path d="M5.6 5.6l2.1 2.1"/><path d="M16.3 16.3l2.1 2.1"/><path d="M18.4 5.6l-2.1 2.1"/><path d="M7.7 16.3l-2.1 2.1"/><circle cx="12" cy="12" r="3.5"/></svg>
-                    </button>
-                    <div id="readerSettingsPanel" class="reader-settings-panel">
-                        <div class="reader-settings-title">阅读栏设置</div>
-                        <div class="reader-settings-group">
-                            <label class="reader-settings-label">尺寸</label>
-                            <div class="reader-settings-options" data-setting-group="size">
-                                <button class="reader-settings-option" type="button" data-size="small">小</button>
-                                <button class="reader-settings-option" type="button" data-size="medium">中</button>
-                                <button class="reader-settings-option" type="button" data-size="large">大</button>
-                            </div>
-                        </div>
-                    </div>
-                </aside>
+                    </div>
+                    <div id="readerControlsSpacer"></div>
+                    <div class="reader-settings-anchor">
+                        <button id="readerSettingsButton" class="reader-settings-launcher" type="button" title="设置" aria-label="设置" aria-expanded="false">
+                            <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3v3"/><path d="M12 18v3"/><path d="M3 12h3"/><path d="M18 12h3"/><path d="M5.6 5.6l2.1 2.1"/><path d="M16.3 16.3l2.1 2.1"/><path d="M18.4 5.6l-2.1 2.1"/><path d="M7.7 16.3l-2.1 2.1"/><circle cx="12" cy="12" r="3.5"/></svg>
+                        </button>
+                        <div id="readerSettingsPanel" class="reader-settings-panel">
+                            <div class="reader-settings-title">阅读栏设置</div>
+                            <div class="reader-settings-group">
+                                <label class="reader-settings-label">尺寸</label>
+                                <div class="reader-settings-options" data-setting-group="size">
+                                    <button class="reader-settings-option" type="button" data-size="xsmall">超小</button>
+                                    <button class="reader-settings-option" type="button" data-size="small">小</button>
+                                    <button class="reader-settings-option" type="button" data-size="medium">中</button>
+                                    <button class="reader-settings-option" type="button" data-size="large">大</button>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </aside>
                 <div id="viewerContainer" tabindex="0">
                     <div id="viewer" class="pdfViewer"></div>
                 </div>
@@ -1539,12 +1567,12 @@
                     updateReaderToggleIcon();
                 }
 
-                function setReaderControlsSize(size) {
-                    const normalized = ['small', 'medium', 'large'].includes(size) ? size : 'small';
-                    document.body.classList.remove('reader-size-small', 'reader-size-medium', 'reader-size-large');
-                    document.body.classList.add(`reader-size-${normalized}`);
-                    document.body.dataset.readerSize = normalized;
-                    localStorage.setItem(READER_SIZE_STORAGE_KEY, normalized);
+                function setReaderControlsSize(size) {
+                    const normalized = ['xsmall', 'small', 'medium', 'large'].includes(size) ? size : 'small';
+                    document.body.classList.remove('reader-size-xsmall', 'reader-size-small', 'reader-size-medium', 'reader-size-large');
+                    document.body.classList.add(`reader-size-${normalized}`);
+                    document.body.dataset.readerSize = normalized;
+                    localStorage.setItem(READER_SIZE_STORAGE_KEY, normalized);
                     document.querySelectorAll('[data-size]').forEach(button => {
                         button.classList.toggle('is-selected', button.dataset.size === normalized);
                     });