diff --git a/src/main/java/com/novelreader/model/History.java b/src/main/java/com/novelreader/model/History.java index 85266b1..2128c98 100644 --- a/src/main/java/com/novelreader/model/History.java +++ b/src/main/java/com/novelreader/model/History.java @@ -2,12 +2,17 @@ package com.novelreader.model; import lombok.Data; import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.CompoundIndex; +import org.springframework.data.mongodb.core.index.CompoundIndexes; import org.springframework.data.mongodb.core.mapping.Document; import java.time.Instant; @Data @Document(collection = "history") +@CompoundIndexes({ + @CompoundIndex(name = "userId_novelId_unique", def = "{'userId': 1, 'novelId': 1}", unique = true) +}) public class History { @Id private String id; diff --git a/src/main/resources/static/css/reader.css b/src/main/resources/static/css/reader.css index 8eb1daa..29f6c8c 100644 --- a/src/main/resources/static/css/reader.css +++ b/src/main/resources/static/css/reader.css @@ -10,6 +10,80 @@ transition: background var(--transition-normal); } +/* ========== 滚动模式 ========== */ +.reader-page.scroll-mode .reader-content { + max-width: 720px; + margin: 0 auto; + padding: var(--spacing-lg); + padding-top: calc(var(--spacing-xl) + var(--safe-area-top)); + padding-bottom: calc(100px + var(--safe-area-bottom)); +} + +/* ========== 翻页模式 ========== */ +.reader-page.page-mode { + position: fixed; + inset: 0; + overflow: hidden; + display: flex; + flex-direction: column; +} + +.page-container { + flex: 1; + overflow: hidden; + position: relative; + padding: var(--spacing-lg); + padding-top: calc(var(--spacing-xl) + var(--safe-area-top)); + padding-bottom: calc(var(--spacing-lg) + var(--safe-area-bottom)); +} + +.page-content { + max-width: 720px; + margin: 0 auto; + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); + will-change: transform; +} + +/* 页码指示器 */ +.page-indicator { + position: fixed; + bottom: calc(var(--spacing-md) + var(--safe-area-bottom)); + left: 50%; + transform: translateX(-50%); + font-size: var(--font-size-xs); + color: var(--text-muted); + background: rgba(0, 0, 0, 0.5); + padding: 4px 12px; + border-radius: var(--radius-full); + z-index: 50; +} + +/* 点击区域 */ +.page-tap-zones { + position: fixed; + inset: 0; + display: flex; + z-index: 10; + pointer-events: auto; +} + +.tap-zone { + height: 100%; +} + +.tap-prev { + width: 30%; +} + +.tap-center { + width: 40%; +} + +.tap-next { + width: 30%; +} + +/* 兼容旧的reader-content类 */ .reader-content { max-width: 720px; margin: 0 auto; @@ -271,6 +345,40 @@ color: var(--bg-primary); } +/* 阅读模式控制 */ +.reading-mode-control { + display: flex; + gap: var(--spacing-sm); +} + +.reading-mode-btn { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + gap: var(--spacing-xs); + padding: var(--spacing-md); + background: var(--bg-card); + border-radius: var(--radius-md); + color: var(--text-secondary); + transition: all var(--transition-fast); +} + +.reading-mode-btn svg { + width: 28px; + height: 28px; + fill: currentColor; +} + +.reading-mode-btn span { + font-size: var(--font-size-xs); +} + +.reading-mode-btn.active { + background: var(--primary); + color: var(--bg-primary); +} + /* 主题选择 */ .theme-control { display: flex;