logo和配置修改

This commit is contained in:
谢宇宁
2026-09-23 16:50:34 +07:00
parent bef9f8b4ed
commit 0db8087765
163 changed files with 393 additions and 20366 deletions
-44
View File
@@ -139,20 +139,6 @@ func (c *PlayletChapterInfoResp) AllowWatch() bool {
return strings.TrimSpace(c.VideoURL) != "" && c.PayType == 0
}
// UnlockCost 金币解锁消耗:优先 costCoins。
func (c *PlayletChapterInfoResp) UnlockCost() int64 {
if c == nil {
return 0
}
if c.CostCoins > 0 {
return c.CostCoins
}
if c.SingleCoins > 0 {
return int64(c.SingleCoins)
}
return 0
}
// AllowWatch 详情选集是否可直达播放(优先 canWatch)。
func (c PlayletChapterData) AllowWatch() bool {
if c.CanWatch {
@@ -166,33 +152,3 @@ func (c PlayletChapterData) AllowWatch() bool {
}
return c.PayType == 0
}
// NeedCoinGate 是否弹出金币解锁。
func (c PlayletChapterData) NeedCoinGate() bool {
if c.AllowWatch() {
return false
}
if c.NeedUnlock {
return true
}
return c.PayType == 1
}
// NeedVipGate 是否弹出 VIP 开通。
func (c PlayletChapterData) NeedVipGate() bool {
if c.AllowWatch() || c.NeedCoinGate() {
return false
}
if c.NeedVip {
return true
}
return c.PayType == 2
}
// UnlockCost 金币解锁消耗:优先 costCoins。
func (c PlayletChapterData) UnlockCost() int {
if c.CostCoins > 0 {
return int(c.CostCoins)
}
return c.SingleCoins
}
-84
View File
@@ -1,84 +0,0 @@
package playletser
import (
"fmt"
"net/url"
"strconv"
"videoweb/app/service/authser"
"videoweb/app/service/upstream"
"videoweb/pub/common/log"
"github.com/gin-gonic/gin"
)
const chapterCollectListPath = "/api/app/playlet/chapter/collect/list"
// PlayletChapterCollectData 对应 swagger proto.PlayletChapterCollectData
type PlayletChapterCollectData struct {
ChapterID int64 `json:"chapterId"`
ChapterTitle string `json:"chapterTitle"`
PlayletID int64 `json:"playletId"`
PlayletSlug string `json:"playletSlug"`
PlayletTitle string `json:"playletTitle"`
PlayletCover string `json:"playletCover"`
CurrentEpisode int `json:"currentEpisode"`
TotalEpisode int `json:"totalEpisode"`
UpdateStatus int `json:"updateStatus"`
CountBrowse int64 `json:"countBrowse"`
CountCollect int64 `json:"countCollect"`
CountLike int64 `json:"countLike"`
IsCollect int `json:"isCollect"`
CollectedAt string `json:"collectedAt"`
MediaTime int `json:"mediaTime"`
PayType int `json:"payType"`
SingleCoins int `json:"singleCoins"`
VideoURL string `json:"videoUrl"`
}
// PlayletChapterCollectListResp 对应 swagger proto.PlayletChapterCollectListResp
type PlayletChapterCollectListResp struct {
List []PlayletChapterCollectData `json:"list"`
PageNum int `json:"pageNum"`
PageSize int `json:"pageSize"`
TotalCount int64 `json:"totalCount"`
}
// FetchChapterCollectList 拉取当前用户剧集收藏列表。
// 第二个返回值为上游业务 code(网络错误时为 0)。
func FetchChapterCollectList(c *gin.Context, q CollectListQuery) (*PlayletChapterCollectListResp, int, error) {
q.Normalize()
params := url.Values{}
params.Set("pageNum", strconv.Itoa(q.PageNum))
params.Set("pageSize", strconv.Itoa(q.PageSize))
var env APIEnvelope[PlayletChapterCollectListResp]
if err := upstream.GetJSON(c, chapterCollectListPath+"?"+params.Encode(), &env); err != nil {
return nil, 0, err
}
if env.Code != 200 {
return nil, env.Code, fmt.Errorf("playlet chapter collect list code=%d msg=%s", env.Code, env.Msg)
}
return &env.Data, env.Code, nil
}
// FetchChapterCollectListOrEmpty 失败时返回空列表;游客会话失效时尝试重登后重试一次。
func FetchChapterCollectListOrEmpty(c *gin.Context, q CollectListQuery) *PlayletChapterCollectListResp {
data, code, err := FetchChapterCollectList(c, q)
if err == nil {
if data == nil {
return &PlayletChapterCollectListResp{PageNum: q.PageNum, PageSize: q.PageSize}
}
return data
}
if retry, _ := authser.OnSessionExpired(c, code); retry {
data, _, err = FetchChapterCollectList(c, q)
if err == nil {
if data == nil {
return &PlayletChapterCollectListResp{PageNum: q.PageNum, PageSize: q.PageSize}
}
return data
}
}
log.Error("fetch playlet chapter collect list failed", log.E(err))
return &PlayletChapterCollectListResp{PageNum: q.PageNum, PageSize: q.PageSize}
}
-63
View File
@@ -1,63 +0,0 @@
package playletser
import (
"fmt"
"videoweb/app/service/upstream"
"github.com/gin-gonic/gin"
)
const (
chapterLikeSetPath = "/api/app/playlet/chapter/like/set"
chapterCollectSetPath = "/api/app/playlet/chapter/collect/set"
)
// PlayletChapterLikeSetReq 对应 swagger proto.PlayletChapterLikeSetReq
type PlayletChapterLikeSetReq struct {
ChapterID int64 `json:"chapterId"`
IsLike bool `json:"isLike"`
}
// PlayletChapterLikeSetResp 对应 swagger proto.PlayletChapterLikeSetResp
type PlayletChapterLikeSetResp struct {
ChapterID int64 `json:"chapterId"`
CountLike int64 `json:"countLike"`
IsLike int `json:"isLike"`
}
// ChapterLikeSet 设置剧集点赞/取消点赞。
func ChapterLikeSet(c *gin.Context, req PlayletChapterLikeSetReq) (*APIEnvelope[PlayletChapterLikeSetResp], error) {
if req.ChapterID <= 0 {
return nil, fmt.Errorf("chapter like invalid chapterId")
}
var env APIEnvelope[PlayletChapterLikeSetResp]
if _, err := upstream.PostJSON(c, chapterLikeSetPath, req, &env); err != nil {
return nil, err
}
return &env, nil
}
// PlayletChapterCollectSetReq 对应 swagger proto.PlayletChapterCollectSetReq
type PlayletChapterCollectSetReq struct {
ChapterID int64 `json:"chapterId"`
IsCollect bool `json:"isCollect"`
}
// PlayletChapterCollectSetResp 对应 swagger proto.PlayletChapterCollectSetResp
type PlayletChapterCollectSetResp struct {
ChapterID int64 `json:"chapterId"`
CountCollect int64 `json:"countCollect"`
IsCollect int `json:"isCollect"`
}
// ChapterCollectSet 设置剧集收藏/取消收藏。
func ChapterCollectSet(c *gin.Context, req PlayletChapterCollectSetReq) (*APIEnvelope[PlayletChapterCollectSetResp], error) {
if req.ChapterID <= 0 {
return nil, fmt.Errorf("chapter collect invalid chapterId")
}
var env APIEnvelope[PlayletChapterCollectSetResp]
if _, err := upstream.PostJSON(c, chapterCollectSetPath, req, &env); err != nil {
return nil, err
}
return &env, nil
}
-40
View File
@@ -1,40 +0,0 @@
package playletser
import (
"fmt"
"videoweb/app/service/upstream"
"github.com/gin-gonic/gin"
)
const chapterUnlockPath = "/api/app/playlet/chapter/unlock"
// PlayletChapterUnlockReq 对应 swagger proto.PlayletChapterUnlockReq
// POST /api/app/playlet/chapter/unlock
type PlayletChapterUnlockReq struct {
ChapterID int64 `json:"chapterId"`
PlayletID int64 `json:"playletId"`
}
// PlayletChapterUnlockResp 对应 swagger proto.PlayletChapterUnlockResp
type PlayletChapterUnlockResp struct {
Balance int64 `json:"balance"`
CanWatch bool `json:"canWatch"`
ChangeCoin int64 `json:"changeCoin"`
ChapterID int64 `json:"chapterId"`
CostCoins int64 `json:"costCoins"`
PlayletID int64 `json:"playletId"`
Unlocked bool `json:"unlocked"`
}
// ChapterUnlock 使用金币解锁剧集(幂等)。
func ChapterUnlock(c *gin.Context, req PlayletChapterUnlockReq) (*APIEnvelope[PlayletChapterUnlockResp], error) {
if req.ChapterID <= 0 || req.PlayletID <= 0 {
return nil, fmt.Errorf("chapter unlock invalid chapterId/playletId")
}
var env APIEnvelope[PlayletChapterUnlockResp]
if _, err := upstream.PostJSON(c, chapterUnlockPath, req, &env); err != nil {
return nil, err
}
return &env, nil
}
-39
View File
@@ -1,39 +0,0 @@
package playletser
import (
"fmt"
"videoweb/app/service/upstream"
"github.com/gin-gonic/gin"
)
const chapterWatchReportPath = "/api/app/playlet/chapter/watch/report"
// PlayletChapterWatchReportReq 对应 swagger proto.PlayletChapterWatchReportReq
type PlayletChapterWatchReportReq struct {
ChapterID int64 `json:"chapterId"`
PlayletID int64 `json:"playletId,omitempty"`
}
// PlayletChapterWatchReportResp 对应 swagger proto.PlayletChapterWatchReportResp
type PlayletChapterWatchReportResp struct {
ChapterID int64 `json:"chapterId"`
PlayletID int64 `json:"playletId"`
CurrentEpisode int `json:"currentEpisode"`
LastChapterID int64 `json:"lastChapterId"`
LastEpisode int `json:"lastEpisode"`
WatchedCount int `json:"watchedCount"`
UnwatchedCount int `json:"unwatchedCount"`
}
// ChapterWatchReport 上报短剧剧集观看。
func ChapterWatchReport(c *gin.Context, req PlayletChapterWatchReportReq) (*APIEnvelope[PlayletChapterWatchReportResp], error) {
if req.ChapterID <= 0 {
return nil, fmt.Errorf("chapter watch report invalid chapterId")
}
var env APIEnvelope[PlayletChapterWatchReportResp]
if _, err := upstream.PostJSON(c, chapterWatchReportPath, req, &env); err != nil {
return nil, err
}
return &env, nil
}
-155
View File
@@ -1,155 +0,0 @@
package playletser
import (
"fmt"
"net/url"
"strconv"
"strings"
"videoweb/app/service/authser"
"videoweb/app/service/upstream"
"videoweb/pub/common/log"
"github.com/gin-gonic/gin"
)
const (
collectSetPath = "/api/app/playlet/collect/set"
collectListPath = "/api/app/playlet/collect/list"
defaultCollectPageSize = 40
CollectTypePlaylet = "playlet"
CollectTypeChapter = "chapter"
)
// PlayletCollectSetReq 对应 swagger proto.PlayletCollectSetReq
type PlayletCollectSetReq struct {
ID int64 `json:"id"`
IsCollect bool `json:"isCollect"`
}
// PlayletCollectSetResp 对应 swagger proto.PlayletCollectSetResp
type PlayletCollectSetResp struct {
ID int64 `json:"id"`
CountCollect int64 `json:"countCollect"`
IsCollect int `json:"isCollect"`
}
// PlayletCollectData 对应 swagger proto.PlayletCollectData
type PlayletCollectData struct {
ID int64 `json:"id"`
Title string `json:"title"`
Slug string `json:"slug"`
Cover string `json:"cover"`
Desc string `json:"desc"`
TagIDs []int64 `json:"tagIds"`
TagNames []string `json:"tagNames"`
TagSlugs []string `json:"tagSlugs"`
ThemeIDs []int64 `json:"themeIds"`
ThemeNames []string `json:"themeNames"`
ThemeSlugs []string `json:"themeSlugs"`
TotalEpisode int `json:"totalEpisode"`
UpdateStatus int `json:"updateStatus"`
CountBrowse int64 `json:"countBrowse"`
CountCollect int64 `json:"countCollect"`
CountLike int64 `json:"countLike"`
IsFeatured bool `json:"isFeatured"`
IsNew bool `json:"isNew"`
IsRecommend bool `json:"isRecommend"`
IsCollect int `json:"isCollect"`
CollectedAt string `json:"collectedAt"`
WatchedCount int `json:"watchedCount"`
UnwatchedCount int `json:"unwatchedCount"`
LastChapterID int64 `json:"lastChapterId"`
LastEpisode int `json:"lastEpisode"`
}
// PlayletCollectListResp 对应 swagger proto.PlayletCollectListResp
type PlayletCollectListResp struct {
List []PlayletCollectData `json:"list"`
PageNum int `json:"pageNum"`
PageSize int `json:"pageSize"`
TotalCount int64 `json:"totalCount"`
}
// CollectListQuery 收藏列表分页参数。
type CollectListQuery struct {
PageNum int
PageSize int
Type string // playlet | chapter
}
// NormalizeCollectType 归一化收藏类型。
func NormalizeCollectType(raw string) string {
switch strings.TrimSpace(strings.ToLower(raw)) {
case CollectTypeChapter, "2", "episode", "ep":
return CollectTypeChapter
default:
return CollectTypePlaylet
}
}
// Normalize 校正分页默认值。
func (q *CollectListQuery) Normalize() {
if q.PageNum <= 0 {
q.PageNum = 1
}
if q.PageSize <= 0 {
q.PageSize = defaultCollectPageSize
}
if q.PageSize > 50 {
q.PageSize = 50
}
q.Type = NormalizeCollectType(q.Type)
}
// CollectSet 设置短剧收藏/取消收藏。
func CollectSet(c *gin.Context, req PlayletCollectSetReq) (*APIEnvelope[PlayletCollectSetResp], error) {
if req.ID <= 0 {
return nil, fmt.Errorf("playlet collect invalid id")
}
var env APIEnvelope[PlayletCollectSetResp]
if _, err := upstream.PostJSON(c, collectSetPath, req, &env); err != nil {
return nil, err
}
return &env, nil
}
// FetchCollectList 拉取当前用户短剧收藏列表。
// 第二个返回值为上游业务 code(网络错误时为 0)。
func FetchCollectList(c *gin.Context, q CollectListQuery) (*PlayletCollectListResp, int, error) {
q.Normalize()
params := url.Values{}
params.Set("pageNum", strconv.Itoa(q.PageNum))
params.Set("pageSize", strconv.Itoa(q.PageSize))
var env APIEnvelope[PlayletCollectListResp]
if err := upstream.GetJSON(c, collectListPath+"?"+params.Encode(), &env); err != nil {
return nil, 0, err
}
if env.Code != 200 {
return nil, env.Code, fmt.Errorf("playlet collect list code=%d msg=%s", env.Code, env.Msg)
}
return &env.Data, env.Code, nil
}
// FetchCollectListOrEmpty 失败时返回空列表;游客会话失效时尝试重登后重试一次。
func FetchCollectListOrEmpty(c *gin.Context, q CollectListQuery) *PlayletCollectListResp {
data, code, err := FetchCollectList(c, q)
if err == nil {
if data == nil {
return &PlayletCollectListResp{PageNum: q.PageNum, PageSize: q.PageSize}
}
return data
}
if retry, _ := authser.OnSessionExpired(c, code); retry {
data, _, err = FetchCollectList(c, q)
if err == nil {
if data == nil {
return &PlayletCollectListResp{PageNum: q.PageNum, PageSize: q.PageSize}
}
return data
}
}
log.Error("fetch playlet collect list failed", log.E(err))
return &PlayletCollectListResp{PageNum: q.PageNum, PageSize: q.PageSize}
}
-89
View File
@@ -1,89 +0,0 @@
package playletser
import (
"fmt"
"net/url"
"strconv"
"strings"
)
func collectPageURL(pageNum int, collectType string) string {
collectType = NormalizeCollectType(collectType)
path := "/collect"
if collectType != CollectTypePlaylet {
path += "/" + url.PathEscape(collectType)
}
if pageNum > 1 {
path += "/page/" + strconv.Itoa(pageNum)
}
return path + "/"
}
func CollectCanonicalPath(q CollectListQuery) string {
q.Normalize()
return collectPageURL(q.PageNum, q.Type)
}
// BuildCollectPagination 收藏列表分页。
func BuildCollectPagination(q CollectListQuery, totalCount int64) PaginationVM {
q.Normalize()
return BuildPagination(func(p int) string {
return collectPageURL(p, q.Type)
}, q.PageNum, q.PageSize, totalCount)
}
// ParseCollectListQuery 从 query 解析分页与类型(playlet 短剧 / chapter 剧集)。
func ParseCollectListQuery(pageStr, typeStr string) CollectListQuery {
q := CollectListQuery{
PageSize: defaultCollectPageSize,
Type: NormalizeCollectType(typeStr),
}
if n, err := strconv.Atoi(strings.TrimSpace(pageStr)); err == nil {
q.PageNum = n
}
q.Normalize()
return q
}
// collectProgressText 进度文案:优先最近观看集,其次未看/已看完。
func collectProgressText(item PlayletCollectData) string {
if item.LastEpisode > 0 {
return fmt.Sprintf("第%d集", item.LastEpisode)
}
if item.TotalEpisode > 0 && item.UnwatchedCount <= 0 {
return "已看完"
}
if item.UnwatchedCount > 0 {
return fmt.Sprintf("%d集未看", item.UnwatchedCount)
}
return ""
}
// collectTagText 收藏卡片标签:仅展示 TagNames 第一项,无标签时不显示。
func collectTagText(item PlayletCollectData) string {
return firstName(item.TagNames)
}
// chapterCollectEpisodeLabel 剧集收藏角标:总集数。
func chapterCollectEpisodeLabel(item PlayletChapterCollectData) string {
return episodeText(item.TotalEpisode)
}
// chapterCollectDesc 剧集收藏副标题。
func chapterCollectDesc(item PlayletChapterCollectData) string {
if t := strings.TrimSpace(item.ChapterTitle); t != "" {
return t
}
if item.CurrentEpisode > 0 {
return fmt.Sprintf("第%d集", item.CurrentEpisode)
}
return ""
}
// chapterCollectMeta 剧集收藏右侧文案:第 N 集。
func chapterCollectMeta(item PlayletChapterCollectData) string {
if item.CurrentEpisode > 0 {
return fmt.Sprintf("第%d集", item.CurrentEpisode)
}
return updateStatusText(item.UpdateStatus)
}
-139
View File
@@ -1,139 +0,0 @@
package playletser
import (
"fmt"
"net/url"
"strconv"
"videoweb/app/service/authser"
"videoweb/app/service/upstream"
"videoweb/pub/common/log"
"github.com/gin-gonic/gin"
)
const (
historyListPath = "/api/app/playlet/history/list"
historyClearPath = "/api/app/playlet/history/clear"
defaultHistoryPageSize = 40
)
// PlayletHistoryData 对应 swagger proto.PlayletHistoryData
// GET /api/app/playlet/history/list
type PlayletHistoryData struct {
ID int64 `json:"id"`
Status int `json:"status"`
IsFeatured bool `json:"isFeatured"`
IsNew bool `json:"isNew"`
IsRecommend bool `json:"isRecommend"`
ShowOnBanner bool `json:"showOnBanner"`
Banner string `json:"banner"`
OnlineAt string `json:"onlineAt"`
MediaCreatedAt string `json:"mediaCreatedAt"`
MediaUpdatedAt string `json:"mediaUpdatedAt"`
ThemeIDs []int64 `json:"themeIds"`
ThemeNames []string `json:"themeNames"`
ThemeSlugs []string `json:"themeSlugs"`
Title string `json:"title"`
Slug string `json:"slug"`
Desc string `json:"desc"`
Anchor string `json:"anchor"`
Cover string `json:"cover"`
TagIDs []int64 `json:"tagIds"`
TagNames []string `json:"tagNames"`
TagSlugs []string `json:"tagSlugs"`
TotalEpisode int `json:"totalEpisode"`
UpdateStatus int `json:"updateStatus"`
CountBrowse int64 `json:"countBrowse"`
CountCollect int64 `json:"countCollect"`
CountLike int64 `json:"countLike"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
WatchedCount int `json:"watchedCount"`
UnwatchedCount int `json:"unwatchedCount"`
LastChapterID int64 `json:"lastChapterId"`
LastEpisode int `json:"lastEpisode"`
IsFinished bool `json:"isFinished"`
HistoryAt string `json:"historyAt"`
}
// PlayletHistoryListResp 对应 swagger proto.PlayletHistoryListResp
type PlayletHistoryListResp struct {
List []PlayletHistoryData `json:"list"`
PageNum int `json:"pageNum"`
PageSize int `json:"pageSize"`
TotalCount int64 `json:"totalCount"`
}
// PlayletHistoryClearResp 对应 swagger proto.PlayletHistoryClearResp
type PlayletHistoryClearResp struct {
DeletedCount int `json:"deletedCount"`
}
// HistoryListQuery 观看历史分页参数。
type HistoryListQuery struct {
PageNum int
PageSize int
}
// Normalize 校正分页默认值。
func (q *HistoryListQuery) Normalize() {
if q.PageNum <= 0 {
q.PageNum = 1
}
if q.PageSize <= 0 {
q.PageSize = defaultHistoryPageSize
}
if q.PageSize > 100 {
q.PageSize = 100
}
}
// FetchHistoryList 分页查询当前用户观看历史。
// 第二个返回值为上游业务 code(网络错误时为 0)。
func FetchHistoryList(c *gin.Context, q HistoryListQuery) (*PlayletHistoryListResp, int, error) {
q.Normalize()
params := url.Values{}
params.Set("pageNum", strconv.Itoa(q.PageNum))
params.Set("pageSize", strconv.Itoa(q.PageSize))
var env APIEnvelope[PlayletHistoryListResp]
if err := upstream.GetJSON(c, historyListPath+"?"+params.Encode(), &env); err != nil {
return nil, 0, err
}
if env.Code != 200 {
return nil, env.Code, fmt.Errorf("playlet history list code=%d msg=%s", env.Code, env.Msg)
}
return &env.Data, env.Code, nil
}
// FetchHistoryListOrEmpty 失败时返回空列表;游客会话失效时尝试重登后重试一次。
func FetchHistoryListOrEmpty(c *gin.Context, q HistoryListQuery) *PlayletHistoryListResp {
data, code, err := FetchHistoryList(c, q)
if err == nil {
if data == nil {
return &PlayletHistoryListResp{PageNum: q.PageNum, PageSize: q.PageSize}
}
return data
}
if retry, _ := authser.OnSessionExpired(c, code); retry {
data, _, err = FetchHistoryList(c, q)
if err == nil {
if data == nil {
return &PlayletHistoryListResp{PageNum: q.PageNum, PageSize: q.PageSize}
}
return data
}
}
log.Error("fetch playlet history list failed", log.E(err))
return &PlayletHistoryListResp{PageNum: q.PageNum, PageSize: q.PageSize}
}
// HistoryClear 清空当前用户观看历史。
// POST /api/app/playlet/history/clear,无请求体。
func HistoryClear(c *gin.Context) (*APIEnvelope[PlayletHistoryClearResp], error) {
var env APIEnvelope[PlayletHistoryClearResp]
if _, err := upstream.PostJSON(c, historyClearPath, nil, &env); err != nil {
return nil, err
}
return &env, nil
}
-147
View File
@@ -1,147 +0,0 @@
package playletser
import (
"fmt"
"strconv"
"strings"
"time"
)
func historyPageURL(pageNum int) string {
if pageNum <= 1 {
return "/history/"
}
return "/history/page/" + strconv.Itoa(pageNum) + "/"
}
func HistoryCanonicalPath(q HistoryListQuery) string {
q.Normalize()
return historyPageURL(q.PageNum)
}
// BuildHistoryPagination 观看历史分页。
func BuildHistoryPagination(q HistoryListQuery, totalCount int64) PaginationVM {
q.Normalize()
return BuildPagination(func(p int) string {
return historyPageURL(p)
}, q.PageNum, q.PageSize, totalCount)
}
// ParseHistoryListQuery 从 query 解析分页。
func ParseHistoryListQuery(pageStr string) HistoryListQuery {
q := HistoryListQuery{PageSize: defaultHistoryPageSize}
if n, err := strconv.Atoi(strings.TrimSpace(pageStr)); err == nil {
q.PageNum = n
}
q.Normalize()
return q
}
func historyFinished(item PlayletHistoryData) bool {
if item.IsFinished {
return true
}
return item.TotalEpisode > 0 && item.UnwatchedCount <= 0 && item.WatchedCount > 0
}
// historyStatus 卡片筛选状态:finished / unfinished。
func historyStatus(item PlayletHistoryData) string {
if historyFinished(item) {
return "finished"
}
return "unfinished"
}
// historyProgressText PC 卡片未看文案,使用接口 unwatchedCount / isFinished。
func historyProgressText(item PlayletHistoryData) string {
if historyFinished(item) {
return "已看完"
}
if item.UnwatchedCount > 0 {
return fmt.Sprintf("%d集未看", item.UnwatchedCount)
}
if item.LastEpisode > 0 {
return fmt.Sprintf("看到第%d集", item.LastEpisode)
}
return "未看完"
}
// historyWatchLine H5 进度行。
func historyWatchLine(item PlayletHistoryData) string {
if historyFinished(item) {
if item.LastEpisode > 0 {
return fmt.Sprintf("第%d集 · 已看完", item.LastEpisode)
}
return "已看完"
}
ep := "未开始"
if item.LastEpisode > 0 {
ep = fmt.Sprintf("第%d集", item.LastEpisode)
}
if item.UnwatchedCount > 0 {
return ep + fmt.Sprintf(" · 还有%d集未看", item.UnwatchedCount)
}
return ep + " · 未看完"
}
// historyTagText 历史卡片标签:仅展示 TagNames 第一项,无标签时不显示。
func historyTagText(item PlayletHistoryData) string {
return firstName(item.TagNames)
}
// historyPlayURL 继续播放:优先最近观看剧集。
func historyPlayURL(item PlayletHistoryData) string {
return chapterPlayURL(item.Slug, item.ID, item.LastChapterID)
}
// formatHistoryAt 最近观看时间展示。
func formatHistoryAt(raw string) string {
raw = strings.TrimSpace(raw)
if raw == "" {
return ""
}
t, ok := parseHistoryTime(raw)
if !ok {
return raw
}
now := time.Now()
startToday := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
startThat := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
dayDiff := int(startToday.Sub(startThat).Hours() / 24)
hm := t.Format("15:04")
switch {
case dayDiff <= 0:
return "今天 " + hm
case dayDiff == 1:
return "昨天 " + hm
case dayDiff == 2:
return "前天 " + hm
case dayDiff < 7:
return fmt.Sprintf("%d天前 %s", dayDiff, hm)
case dayDiff < 14:
return "一周前 " + hm
default:
return t.Format("2006-01-02 15:04")
}
}
func parseHistoryTime(raw string) (time.Time, bool) {
layouts := []string{
time.RFC3339Nano,
time.RFC3339,
"2006-01-02 15:04:05",
"2006-01-02T15:04:05",
"2006-01-02T15:04:05Z07:00",
"2006-01-02 15:04:05.000",
"2006-01-02 15:04:05.999999999",
}
for _, layout := range layouts {
if t, err := time.ParseInLocation(layout, raw, time.Local); err == nil {
return t, true
}
if t, err := time.Parse(layout, raw); err == nil {
return t.In(time.Local), true
}
}
return time.Time{}, false
}
-35
View File
@@ -1,35 +0,0 @@
package playletser
import (
"fmt"
"videoweb/app/service/upstream"
"github.com/gin-gonic/gin"
)
const likeSetPath = "/api/app/playlet/like/set"
// PlayletLikeSetReq 对应 swagger proto.PlayletLikeSetReq
type PlayletLikeSetReq struct {
ID int64 `json:"id"`
IsLike bool `json:"isLike"`
}
// PlayletLikeSetResp 对应 swagger proto.PlayletLikeSetResp
type PlayletLikeSetResp struct {
ID int64 `json:"id"`
CountLike int64 `json:"countLike"`
IsLike int `json:"isLike"`
}
// LikeSet 设置短剧点赞/取消点赞。
func LikeSet(c *gin.Context, req PlayletLikeSetReq) (*APIEnvelope[PlayletLikeSetResp], error) {
if req.ID <= 0 {
return nil, fmt.Errorf("playlet like invalid id")
}
var env APIEnvelope[PlayletLikeSetResp]
if _, err := upstream.PostJSON(c, likeSetPath, req, &env); err != nil {
return nil, err
}
return &env, nil
}
+44 -75
View File
@@ -11,62 +11,50 @@ import (
// TemplateFuncs 模板辅助:仅做展示格式化 / URL,不把接口字段映射成另一套名字。
func TemplateFuncs() template.FuncMap {
return template.FuncMap{
"detailURL": detailURL,
"playURL": playURL,
"chapterPlayURL": chapterPlayURL,
"categoryURL": categoryURL,
"categoryDetailURL": categoryDetailURL,
"themeListURL": themeListURLByKey,
"coverURL": coverURL,
"formatHeat": formatHeat,
"formatCollect": formatCollectHeat,
"formatLikeCount": formatLikeCount,
"episodeLabel": episodeText,
"displayTag": displayTag,
"firstTagName": firstTagName,
"themeTagText": themeTagText,
"tagListURL": tagListURL,
"cardTag": cardTag,
"cardTags": cardTags,
"namedTags": namedTags,
"namedThemeTags": namedThemeTags,
"firstName": firstName,
"rankClass": rankClass,
"add": addInt,
"contains": strings.Contains,
"hasPrefix": strings.HasPrefix,
"footerLinkURL": footerLinkURL,
"footerLinkIsExternal": footerLinkIsExternal,
"footerContactURL": footerContactURL,
"footerContactName": footerContactName,
"footerContactValue": footerContactValue,
"navDisplayURL": navDisplayURL,
"urlquery": url.QueryEscape,
"pathescape": url.PathEscape,
"metaLine": metaLine,
"updateStatusText": updateStatusText,
"browseText": browseText,
"usableAnchor": usableAnchor,
"playletYearText": playletYearText,
"playletDateText": playletDateText,
"isPlayletAllFree": isPlayletAllFree,
"collectProgressText": collectProgressText,
"collectTagText": collectTagText,
"chapterCollectEpisodeLabel": chapterCollectEpisodeLabel,
"chapterCollectDesc": chapterCollectDesc,
"chapterCollectMeta": chapterCollectMeta,
"historyProgressText": historyProgressText,
"historyWatchLine": historyWatchLine,
"historyTagText": historyTagText,
"historyPlayURL": historyPlayURL,
"historyStatus": historyStatus,
"formatHistoryAt": formatHistoryAt,
"formatShortDateTime": formatShortDateTime,
"absInt64": absInt64Tpl,
"absURL": absURLTpl,
"canonicalOr": canonicalOr,
"joinStrings": joinStrings,
"dict": dict,
"detailURL": detailURL,
"playURL": playURL,
"chapterPlayURL": chapterPlayURL,
"categoryURL": categoryURL,
"categoryDetailURL": categoryDetailURL,
"themeListURL": themeListURLByKey,
"coverURL": coverURL,
"formatHeat": formatHeat,
"formatCollect": formatCollectHeat,
"formatLikeCount": formatLikeCount,
"episodeLabel": episodeText,
"displayTag": displayTag,
"firstTagName": firstTagName,
"themeTagText": themeTagText,
"tagListURL": tagListURL,
"cardTag": cardTag,
"cardTags": cardTags,
"namedTags": namedTags,
"namedThemeTags": namedThemeTags,
"firstName": firstName,
"rankClass": rankClass,
"add": addInt,
"contains": strings.Contains,
"hasPrefix": strings.HasPrefix,
"footerLinkURL": footerLinkURL,
"footerLinkIsExternal": footerLinkIsExternal,
"footerContactURL": footerContactURL,
"footerContactName": footerContactName,
"footerContactValue": footerContactValue,
"navDisplayURL": navDisplayURL,
"urlquery": url.QueryEscape,
"pathescape": url.PathEscape,
"metaLine": metaLine,
"updateStatusText": updateStatusText,
"browseText": browseText,
"usableAnchor": usableAnchor,
"playletYearText": playletYearText,
"playletDateText": playletDateText,
"isPlayletAllFree": isPlayletAllFree,
"absInt64": absInt64Tpl,
"absURL": absURLTpl,
"canonicalOr": canonicalOr,
"joinStrings": joinStrings,
"dict": dict,
}
}
@@ -315,25 +303,6 @@ func absInt64Tpl(n int64) int64 {
return n
}
func formatShortDateTime(raw string) string {
raw = strings.TrimSpace(raw)
if raw == "" || strings.HasPrefix(raw, "0001") {
return ""
}
raw = strings.ReplaceAll(raw, "T", " ")
if i := strings.Index(raw, "."); i > 0 {
raw = raw[:i]
}
if i := strings.IndexAny(raw, "Z+"); i > 10 {
raw = strings.TrimSpace(raw[:i])
}
if len(raw) >= 16 {
raw = raw[:16]
}
raw = strings.ReplaceAll(raw, "-", "/")
return raw
}
func metaLine(item CommonData) string {
cat := firstName(item.ThemeNames)
if cat == "" {