@@ -34,22 +34,34 @@ type ComicGroupWithCount struct {
3434
3535// ComicGroupDetail 包含系列详情和所属漫画列表。
3636type ComicGroupDetail struct {
37- ID int `json:"id"`
38- Name string `json:"name"`
39- CoverURL string `json:"coverUrl"`
40- SortOrder int `json:"sortOrder"`
41- Author string `json:"author"`
42- Description string `json:"description"`
43- Tags string `json:"tags"`
44- Year * int `json:"year"`
45- Publisher string `json:"publisher"`
46- Language string `json:"language"`
47- Genre string `json:"genre"`
48- Status string `json:"status"`
49- CreatedAt string `json:"createdAt"`
50- UpdatedAt string `json:"updatedAt"`
51- ComicCount int `json:"comicCount"`
52- Comics []GroupComicItem `json:"comics"`
37+ ID int `json:"id"`
38+ Name string `json:"name"`
39+ CoverURL string `json:"coverUrl"`
40+ SortOrder int `json:"sortOrder"`
41+ Author string `json:"author"`
42+ Description string `json:"description"`
43+ Tags string `json:"tags"`
44+ Year * int `json:"year"`
45+ Publisher string `json:"publisher"`
46+ Language string `json:"language"`
47+ Genre string `json:"genre"`
48+ Status string `json:"status"`
49+ CreatedAt string `json:"createdAt"`
50+ UpdatedAt string `json:"updatedAt"`
51+ ComicCount int `json:"comicCount"`
52+ SeriesList []GroupSeriesItem `json:"seriesList"`
53+ Comics []GroupComicItem `json:"comics"`
54+ }
55+
56+ // GroupSeriesItem 分组内的目录作品条目。
57+ type GroupSeriesItem struct {
58+ SeriesID string `json:"id"`
59+ Title string `json:"title"`
60+ RootRelativePath string `json:"rootRelativePath"`
61+ CoverComicID string `json:"coverComicId"`
62+ CoverURL string `json:"coverUrl"`
63+ SortIndex int `json:"sortIndex"`
64+ Comics []GroupComicItem `json:"comics"`
5365}
5466
5567// GroupComicItem 分组内的漫画条目。
@@ -338,9 +350,65 @@ func GetGroupByID(groupID int, contentType ...string) (*ComicGroupDetail, error)
338350 g .Comics = append (g .Comics , item )
339351 }
340352
341- // 封面 URL:有自定义封面时返回本地缓存路径,无封面时使用第一本漫画缩略图
353+ // 获取分组内的目录作品 (ComicSeries)
354+ g .SeriesList = []GroupSeriesItem {}
355+ seriesRows , err := db .Query (`
356+ SELECT cs."id", cs."title", cs."rootRelativePath", cs."coverComicId", cgs."sortIndex"
357+ FROM "ComicGroupSeries" cgs
358+ JOIN "ComicSeries" cs ON cs."id" = cgs."seriesId"
359+ WHERE cgs."groupId" = ?
360+ ORDER BY cgs."sortIndex" ASC
361+ ` , groupID )
362+ if err == nil {
363+ defer seriesRows .Close ()
364+ for seriesRows .Next () {
365+ var sItem GroupSeriesItem
366+ if scanErr := seriesRows .Scan (& sItem .SeriesID , & sItem .Title , & sItem .RootRelativePath , & sItem .CoverComicID , & sItem .SortIndex ); scanErr == nil {
367+ if sItem .CoverComicID != "" {
368+ sItem .CoverURL = BuildComicCoverURL (sItem .CoverComicID )
369+ }
370+ sItem .Comics = []GroupComicItem {}
371+ cRows , cErr := db .Query (`
372+ SELECT c."id", c."filename", c."title", c."pageCount", c."fileSize",
373+ c."lastReadPage", c."totalReadTime", c."readingStatus", c."lastReadAt",
374+ csi."sortIndex", COALESCE(c."type", '') as "type"
375+ FROM "ComicSeriesItem" csi
376+ JOIN "Comic" c ON c."id" = csi."comicId"
377+ WHERE csi."seriesId" = ?
378+ ORDER BY csi."sortIndex" ASC
379+ ` , sItem .SeriesID )
380+ if cErr == nil {
381+ for cRows .Next () {
382+ var item GroupComicItem
383+ var lastReadAt sql.NullTime
384+ if cRows .Scan (
385+ & item .ComicID , & item .Filename , & item .Title , & item .PageCount , & item .FileSize ,
386+ & item .LastReadPage , & item .TotalReadTime , & item .ReadingStatus , & lastReadAt ,
387+ & item .SortIndex , & item .ComicType ,
388+ ) == nil {
389+ item .CoverURL = BuildComicCoverURL (item .ComicID )
390+ if lastReadAt .Valid {
391+ str := lastReadAt .Time .UTC ().Format (time .RFC3339 )
392+ item .LastReadAt = & str
393+ }
394+ sItem .Comics = append (sItem .Comics , item )
395+ }
396+ }
397+ cRows .Close ()
398+ }
399+ if sItem .CoverURL == "" && len (sItem .Comics ) > 0 {
400+ sItem .CoverURL = sItem .Comics [0 ].CoverURL
401+ }
402+ g .SeriesList = append (g .SeriesList , sItem )
403+ }
404+ }
405+ }
406+
407+ // 封面 URL:有自定义封面时返回本地缓存路径,无封面时按优先使用 Series 目录作品或第一本漫画缩略图
342408 if g .CoverURL != "" {
343409 g .CoverURL = BuildGroupCoverURL (g .ID )
410+ } else if len (g .SeriesList ) > 0 && g .SeriesList [0 ].CoverURL != "" {
411+ g .CoverURL = g .SeriesList [0 ].CoverURL
344412 } else if len (g .Comics ) > 0 {
345413 g .CoverURL = g .Comics [0 ].CoverURL
346414 }
@@ -605,18 +673,60 @@ func AddComicsToGroup(groupID int, comicIDs []string) error {
605673 return nil
606674}
607675
608- // RemoveComicFromGroup 从分组移除漫画。如果移除后分组为空 ,自动删除分组。
676+ // RemoveComicFromGroup 从分组移除漫画。如果移除后分组无任何数据 ,自动删除分组。
609677func RemoveComicFromGroup (groupID int , comicID string ) error {
610678 _ , err := db .Exec (`DELETE FROM "ComicGroupItem" WHERE "groupId" = ? AND "comicId" = ?` , groupID , comicID )
611679 if err != nil {
612680 return err
613681 }
614682 db .Exec (`UPDATE "ComicGroup" SET "updatedAt" = ? WHERE "id" = ?` , time .Now ().UTC (), groupID )
615683
616- // 检查分组是否为空,为空则自动删除
617- var count int
618- err = db .QueryRow (`SELECT COUNT(*) FROM "ComicGroupItem" WHERE "groupId" = ?` , groupID ).Scan (& count )
619- if err == nil && count == 0 {
684+ // 检查分组是否为空(无漫画且无目录作品),为空则自动删除
685+ var countItems , countSeries int
686+ _ = db .QueryRow (`SELECT COUNT(*) FROM "ComicGroupItem" WHERE "groupId" = ?` , groupID ).Scan (& countItems )
687+ _ = db .QueryRow (`SELECT COUNT(*) FROM "ComicGroupSeries" WHERE "groupId" = ?` , groupID ).Scan (& countSeries )
688+ if countItems == 0 && countSeries == 0 {
689+ db .Exec (`DELETE FROM "ComicGroup" WHERE "id" = ?` , groupID )
690+ }
691+
692+ return nil
693+ }
694+
695+ // AddSeriesToGroup 将一个或多个目录作品 (ComicSeries) 添加到分组。
696+ func AddSeriesToGroup (groupID int , seriesIDs []string ) error {
697+ if len (seriesIDs ) == 0 {
698+ return nil
699+ }
700+ var maxIdx int
701+ db .QueryRow (`SELECT COALESCE(MAX("sortIndex"), -1) FROM "ComicGroupSeries" WHERE "groupId" = ?` , groupID ).Scan (& maxIdx )
702+
703+ for i , seriesID := range seriesIDs {
704+ _ , err := db .Exec (`
705+ INSERT INTO "ComicGroupSeries" ("groupId", "seriesId", "sortIndex")
706+ VALUES (?, ?, ?)
707+ ON CONFLICT("groupId", "seriesId") DO NOTHING
708+ ` , groupID , seriesID , maxIdx + 1 + i )
709+ if err != nil {
710+ return err
711+ }
712+ }
713+
714+ db .Exec (`UPDATE "ComicGroup" SET "updatedAt" = ? WHERE "id" = ?` , time .Now ().UTC (), groupID )
715+ return nil
716+ }
717+
718+ // RemoveSeriesFromGroup 从分组移除目录作品 (ComicSeries)。如果移除后分组无任何数据,自动删除分组。
719+ func RemoveSeriesFromGroup (groupID int , seriesID string ) error {
720+ _ , err := db .Exec (`DELETE FROM "ComicGroupSeries" WHERE "groupId" = ? AND "seriesId" = ?` , groupID , seriesID )
721+ if err != nil {
722+ return err
723+ }
724+ db .Exec (`UPDATE "ComicGroup" SET "updatedAt" = ? WHERE "id" = ?` , time .Now ().UTC (), groupID )
725+
726+ var countItems , countSeries int
727+ _ = db .QueryRow (`SELECT COUNT(*) FROM "ComicGroupItem" WHERE "groupId" = ?` , groupID ).Scan (& countItems )
728+ _ = db .QueryRow (`SELECT COUNT(*) FROM "ComicGroupSeries" WHERE "groupId" = ?` , groupID ).Scan (& countSeries )
729+ if countItems == 0 && countSeries == 0 {
620730 db .Exec (`DELETE FROM "ComicGroup" WHERE "id" = ?` , groupID )
621731 }
622732
0 commit comments