mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-05 04:56:37 +08:00
feat(reader): P1 前端——首页影视/阅读切换 + 书架/搜索/书源/详情/阅读器
- 首页顶部「影视/阅读」分段切换(zustand 持久化,阅读模式直渲染书架) - /reader/* 独立全屏路由(不套影视 Layout): 书架(响应式网格 3→8 列)、搜索页(结果流+失败书源明细)、 书源管理(导入/启停/删除/快速调试日志)、详情页(目录+加书架) - 阅读器仿 legado ReadMenu:6 套内置主题(日/夜色值对齐 readConfig.json)、 CSS 多栏分页 + 平移翻页/滚动双模式、三区点击 + 键盘翻页(桌面)、 上下章、目录抽屉(Virtuoso 虚拟滚动)、字号/行距/段距调节、 进度记忆(服务端同步 + 下一章预取) - 桌面兼容:内容列 900px 限宽居中、鼠标悬停态、键盘快捷键 - 后端:详情/目录/正文接口支持 source_url 定位书源(书架链路)
This commit is contained in:
@@ -552,8 +552,8 @@ type BookInfo struct {
|
||||
}
|
||||
|
||||
// GetBookInfo 抓取书籍详情。
|
||||
func (s *ReaderService) GetBookInfo(ctx context.Context, sourceID, bookURL string) (*BookInfo, error) {
|
||||
src, bs, err := s.loadSource(ctx, sourceID)
|
||||
func (s *ReaderService) GetBookInfo(ctx context.Context, sourceID, sourceURL, bookURL string) (*BookInfo, error) {
|
||||
src, bs, err := s.loadSourceFlexible(ctx, sourceID, sourceURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -637,8 +637,8 @@ type TocChapter struct {
|
||||
}
|
||||
|
||||
// GetToc 抓取目录。
|
||||
func (s *ReaderService) GetToc(ctx context.Context, sourceID, bookURL, tocURL string) ([]TocChapter, error) {
|
||||
src, bs, err := s.loadSource(ctx, sourceID)
|
||||
func (s *ReaderService) GetToc(ctx context.Context, sourceID, sourceURL, bookURL, tocURL string) ([]TocChapter, error) {
|
||||
src, bs, err := s.loadSourceFlexible(ctx, sourceID, sourceURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -702,8 +702,8 @@ type ChapterContent struct {
|
||||
}
|
||||
|
||||
// GetContent 抓取正文(含 nextContentUrl 翻页合并与净化替换)。
|
||||
func (s *ReaderService) GetContent(ctx context.Context, sourceID, bookURL, chapterURL string) (*ChapterContent, error) {
|
||||
src, bs, err := s.loadSource(ctx, sourceID)
|
||||
func (s *ReaderService) GetContent(ctx context.Context, sourceID, sourceURL, bookURL, chapterURL string) (*ChapterContent, error) {
|
||||
src, bs, err := s.loadSourceFlexible(ctx, sourceID, sourceURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -886,14 +886,14 @@ func (s *ReaderService) Debug(ctx context.Context, sourceID, key string) ([]stri
|
||||
}
|
||||
first := books[0]
|
||||
logf("访问详情页: %s", first.BookURL)
|
||||
info, err := s.GetBookInfo(ctx, sourceID, first.BookURL)
|
||||
info, err := s.GetBookInfo(ctx, sourceID, "", first.BookURL)
|
||||
if err != nil {
|
||||
logf("详情失败: %v", err)
|
||||
return logs, nil
|
||||
}
|
||||
logf("书名: %s 作者: %s 最新章节: %s", info.Name, info.Author, info.LatestChapter)
|
||||
logf("访问目录页: %s", info.TocURL)
|
||||
chapters, err := s.GetToc(ctx, sourceID, first.BookURL, info.TocURL)
|
||||
chapters, err := s.GetToc(ctx, sourceID, "", first.BookURL, info.TocURL)
|
||||
if err != nil {
|
||||
logf("目录失败: %v", err)
|
||||
return logs, nil
|
||||
@@ -911,7 +911,7 @@ func (s *ReaderService) Debug(ctx context.Context, sourceID, key string) ([]stri
|
||||
continue
|
||||
}
|
||||
logf("访问正文: %s", c.URL)
|
||||
content, err := s.GetContent(ctx, sourceID, first.BookURL, c.URL)
|
||||
content, err := s.GetContent(ctx, sourceID, "", first.BookURL, c.URL)
|
||||
if err != nil {
|
||||
logf("正文失败: %v", err)
|
||||
return logs, nil
|
||||
@@ -940,6 +940,26 @@ func (s *ReaderService) loadSource(ctx context.Context, sourceID string) (*model
|
||||
return src, bs, nil
|
||||
}
|
||||
|
||||
// loadSourceFlexible 按 ID 或 URL 加载书源(书架上只存 origin URL,
|
||||
// 前端阅读链路用 source_url 定位书源)。
|
||||
func (s *ReaderService) loadSourceFlexible(ctx context.Context, sourceID, sourceURL string) (*model.ReaderBookSource, *BookSource, error) {
|
||||
if sourceID != "" {
|
||||
return s.loadSource(ctx, sourceID)
|
||||
}
|
||||
if sourceURL == "" {
|
||||
return nil, nil, fmt.Errorf("缺少书源标识(source_id 或 source_url)")
|
||||
}
|
||||
src, err := s.repo.GetSourceByURL(ctx, sourceURL)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("书源不存在或已被删除")
|
||||
}
|
||||
bs, err := ParseBookSource(src.RawJSON)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("书源 JSON 解析失败: %w", err)
|
||||
}
|
||||
return src, bs, nil
|
||||
}
|
||||
|
||||
func applySourceHeaders(req *rule.Request, src *model.ReaderBookSource) {
|
||||
if src.Header == "" {
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user